https://www.youtube.com/watch?v=2Fr69gOLpAA
양방향 바인딩, 단방향 바인딩에 대해 제대로 설명하는 사람을 못봤어요.
단테는 잘 설명하는지 어디 한번 봅시다.
해당 개념에 대해 제대로 설명하는 사람이 적은 것은 모던 프레임워크를 다양하고 심도있게 다뤄본 사람이 적기 때문입니다. 그리고 한정된 인터뷰 시간동안 어느정도 숙련도 있는 개발자에게 면접질문으로 데이터 바인딩에 대해 설명하길 요청하길 보다 그동안 어떤 프로젝트에 어떤 발자취를 남겼는지 물어보는 것이 더 나을수도 있겠죠.
양방향 바인딩 (bidirectional binding)
단방향 바인딩 (unidirectional binding)은 컴포넌트와 뷰 그리고 데이터간의 연결방식에 대한 방법입니다.
단방향 바인딩은 데이터가 오직 한 방향으로 흐른다는 말입니다. 데이터가 오직 한 방향으로 흐른다니 이게 무슨 말일까요?
리엑트에서 컴포넌트의 상태를 변경하면 뷰가 업데이트 됩니다. 그리고 상태를 업데이트하기 위해서는 유저가 화면을 통해 이벤트를 발생시켜야 합니다.
화면에 있는 인풋창에 유저가 타이핑을 하거나 화면의 이곳저곳을 클릭할 수 있지만 명시적으로 이벤트 핸들러가 프로그래머에 의해 미리 구현되어있지 않았다면 뷰가 자동으로 상태값을 업데이트 시키지는 않습니다.
// react
const [state, setState] = useState(0);
const handleClick = () => {
// ?
}
return (
<div>
<button onClick={handleClick}>+</button>
<div>{state}<div>
</div>
)
버튼을 클릭할 수 있어도 onClick handler 로직에 명시적으로 상태값을 업데이트 시키는 로직이 없다면 데이터를 변경시키지 못합니다.
앞선 리엑트 코드에서 상태 값인 state를 통해 ui를 표현했습니다. 리엑트와 같은 선언적 프로그래밍을 채택한 프레임워크에서는 상태 값이 곧 ui를 표현하는 요소이죠.
화면을 표현하는 요소 - 상태
프로그래밍 패러다임 - 선언형 프로그래밍
SPA 이전 세대는 어떨까요?
잠깐 SPA 이전 세대인 handlebar template에 대해 이야기해보려고 합니다.
.hbs
파일은 핸들바 템플릿 기반에서 작성한 html 태그들, javascript 코드들을 작성하는 곳입니다. webpack 이전 세대에서 사용했던 기술로 grunt라는 번들링 툴을 이용해 .hbs파일에서 참조하는 정적파일들을 css, js 파일들로 나누어 생성해주고 .hbs파일은 .html 파일로 변환해주었습니다. 불과 5년전만해도 이런 템플릿 엔진을 이용해 많은 정적 웹사이트를 생성했었습니다.
핸들바는 템플릿과 입력객체를 사용해 HTML이나 다른 텍스트 형식을 생성하는데 코드는 다음처럼 생겼습니다.
<p>{{firstname}} {{lastname}}</p>
이러한 핸들바 표현식은 {{, 내용, }}
으로 구성됩니다. 템플릿이 실행되면 위에 선언한 표현식이 입력 객체의 값으로 대체됩니다. 입력 객체가 아래처럼 생겼다면 템플릿 엔진을 통해 해석된 html 내부의 텍스트는 다음과 같습니다.
{
"firstname": "벚",
"lastname": "꼬ㅊ"
}
<p>벚 꼬ㅊ</p>
템플릿 언어는 단방향 흐름을 따릅니다. 화면의 데이터와 메모리의 데이터가 한 방향으로만 영향을 줍니다.
.hbs 파일 내부에서 사용한 js 코드를 사용해 서버에서 불러온 json 객체를 입력 객체의 값으로 대체해주었습니다. 화면에서 데이터를 변경하려면 메모리에 데이터를 변경하고 메모리의 데이터를 변경하려면 화면에 특정 이벤트를 발생시켜야 합니다.
<input type="text" value="{{name}}" />
<p>Hello, {{name}}</p>
// 입력 객체
{
"name": "Dante"
}
위의 도식도를 통해 렌더링된 초기 화면은 서버에서 가져온 초기 json 객체의 값으로 매핑되겠지만 이후 DOM을 업데이트 하기 위해서 input 태그에서 이벤트를 발생시키더라도 p 요소에 값은 변경되지 않습니다. input 요소의 값은 DOM에 의해 관리되고 p 요소의 값은 메모리에 있는 입력 객체의 값에 의해 결정됩니다.
위와 같은 템플릿 엔진으로 만들어진 html 은 템플릿 언어에 입력객체를 업데이트 하는 방식으로 값을 변경한다고 우리가 모래판의 규칙
을 이미 세워두었기 때문에 해당 값을 변경시키기 위해서는 화면을 우리가 명령형 방식을 이용해 다시 렌더링 해야 할 것입니다.
input 태그의 입력값과 name 값을 일치시키기 위해 아래처럼 입력 객체를 업데이트하여 다시 템플릿 엔진을 통해 리렌더링 합니다.
var template = Handlebars.compile(
'<input type="text" value="{{name}}" />' + "<p>Hello, {{name}}</p>"
);
var data = { name: "Dante" };
var html = template(data);
document.getElementById("app").innerHTML = html;
function updateName() {
// input 요소의 값을 가져옵니다.
var input = document.getElementById("input").value;
// 입력 객체의 name 속성을 변경합니다.
data.name = input;
// 템플릿을 다시 렌더링합니다.
html = template(data);
// 화면에 반영합니다.
document.getElementById("app").innerHTML = html;
}
// 버튼을 클릭하면 updateName 함수를 호출합니다.
document.getElementById("button").addEventListener("click", updateName);
// input 요소에 입력하면 updateName 함수를 호출합니다.
document.getElementById("input").addEventListener("input", updateName);
프로그래밍 패러다임 - 명령형 프로그래밍
화면을 표현하는 요소 - 데이터 객체
명령형 프로그래밍이든 선언형 프로그래밍이든 화면을 표현하는 요소를 업데이트하기 위해서는 js 코드 내부에서 명시적으로 바인딩하는 요소가 있어야 합니다.
단방향 바인딩은 화면의 데이터와 메모리의 데이터가 한 방향으로만 영향을 주는 방식이기 때문에 상태 관리에 용이하고 성능 최적화에 용이합니다. 어떤 변화가 어디서 발생했는지 명확하게 알 수 있습니다. 필요한 경우에만 화면을 업데이트하기 때문에 성능 저하나 메모리 누수등의 문제를 방지할 수 있습니다.
이에 대한 트레이드 오프로 화면을 변경하는 요소를 일일이 js로 뷰와 바인딩해주고 업데이트 하는 핸들러 로직을 작성하기 때문에 코드가 복잡해질 수 있습니다.
모던 프론트엔드 프레임워크에서 Angular와 Vue는 양방향 바인딩을 제공하는 대표적인 도구입니다. Angular는 ngModel
, Vue는 v-model
디렉티브를 사용합니다. 이러한 디렉티브를 사용해 화면의 입력 요소와 코드 상의 변수를 자동으로 연결해줍니다.
Angular에서는 화면의 입력 요소와 코드 상의 변수를 연결하기 위해 ngModel 디렉티브를 사용합니다. 아래는 사용자가 입력한 이름을 화면에 표시하는 간단한 컴포넌트입니다.
<div>
<input type="text" [(ngModel)]="name" />
<p>Hello, {{name}}</p>
</div>
import { Component } from "@angular/core";
@Component({
selector: "name-input",
templateUrl: "./name-input.component.html",
})
export class NameInputComponent {
name: string = "";
}
코드에서 input 요소의 ngModel 속성에 name 변수를 할당하고, p 요소에 {{name}}템플릿 문법을 사용합니다. 이렇게 하면 사용자가 input 요소에 값을 입력할 때마다 name 변수의 값이 자동으로 변경되고 그 값이 p 요소에도 반영됩니다.
Vue에서는 v-model 디렉티브를 사용하여 양방향 바인딩을 구현합니다.
<template>
<div>
<input type="text" v-model="message" />
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: "Hello Vue!",
};
},
};
</script>
이 코드에서 input 요소의 v-model 속성에 message 변수를 할당하고, p 요소에 {{message}} 템플릿 문법을 사용했습니다. 사용자가 input 요소에 값을 입력할 때마다 message 변수의 값이 자동으로 변경되고 그 값이 p 요소에 바영됩니다.
근데 리엑트에서는 화면을 표현하는 모든 요소를 꼭 일일이 이벤트 핸들러를 작성하지 않아도 업데이트 시킬 수 있습니다.
바로 uncontrolled component입니다.
양방향 바인딩과 uncontrolled component는 모두 화면의 입력 요소와 코드 상의 변수를 직접 연결하지 않고 DOM에 의해 관리되는 값을 사용한다는 공통점이 있습니다.
Vue와 Angular의 v-model, ngModel 디렉티브는 화면의 입력요소와 코드 상의 변수를 자동으로 연결해줍니다. 이 과정에서 실제로 사용되는 값은 DOM에 의해 관리되는 값입니다.
즉 입력 요소의 value 속성이나 ngModel 디렉티브가 참조하는 값은 DOM에 저장되어 있으며, 코드 상의 변수는 그 값을 복사하여 사용합니다.
import React, { useRef } from "react";
function MessageInput() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
const message = inputRef.current.value;
console.log(message);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} defaultValue="Hello React!" />
<button type="submit">Submit</button>
</form>
);
}
export default MessageInput;
위의 MessageInput 컴포넌트는 uncontrolled 컴포넌트입니다. input 요소의 ref 속성에 inputRef라는 훅을 할당하고 defaultValue 속성에 "Hello React"라는 문자열을 할당했습니다. 사용자가 값을 변경할 때마다 DOM에 저장됩니다. 그리고 form요소의 onSubmit 이벤트에 handleSubmit 함수를 할당합니다.
이 함수 안에서 inputRef.current.value를 통해 DOM에 저장된 값을 가져와서 콘솔에 출력합니다.
이렇게 양방향 바인딩과 uncontrolled component사이의 공통점은 화면의 입력 요소와 코드 상의 변수를 직접 연결하지 않고 DOM에 의해 관리되는 값을 사용한다는 점입니다.
글 잘 읽었습니다.
v-model 같은 경우는 v-bind, v-on 의 기능이 축약된 디렉티브 입니다.
ngModel 같은 경우는 [attr], (event) 의 기능이 축약된 디렉티브 입니다.
양방향 바인딩이지만, 사실상 단방향 바인딩 구문을 축약해주는거죠.
앵귤러 디렉티브를 잘 보시면 저걸 축약해주기때문에 form 요소에 바인딩 할 때,
[()] 형태로 작성을 합니다.
양방향 바인딩 내용에서 궁금한게,
양방향 바인딩과 uncontrolled component는 모두 화면의 입력 요소와 코드 상의 변수를 직접 연결하지 않고 DOM에 의해 관리되는 값을 사용한다는 공통점이 있습니다.
위 부분입니다.
양방향 바인딩을 하는 데이터들을 잘 살펴보시면, 결국에는 해당 컴포넌트에서 관리되는 상태변수를 바인딩 해줍니다. 저는 이 상태가 DOM에서 직접 관리되는 상태가아닌 Vue 또는 Angular 에 의해 관리되는 상태라고 이해를 하고있는데요. 제가 혹시 이해하는게 잘못되었을까요?
컴포넌트에서 관리되는 상태값과 템플릿영역에 바인딩 되는부분에 동기화를 해주는게 양방향 바인딩인데, 이 상태값 동기화가 DOM에서 직접 관리되는 값이라면 동기화가 이루어질까요?
좋은 예제로 잘 설명해주셔서 감사합니다!