Angular | React | Vue |
단방향, 양방향 | 단방향 | 단방향, 양방향 |
프론트엔드 프레임워크 및 라이브러리 삼대장은 모두 데이터 바인딩을 지원한다. 그만큼 필요하다는 뜻이라고 생각한다. 이번 포스트에서 데이터 바인딩에 대해서 알아보겠다.
이 포스트는 데이터 바인딩 개념 자체를 알아보는 글이다. vue에서의 데이터 바인딩을 알아보고 싶다면 v-model, angular에서의 데이터 바인딩을 알아보고 싶다면 Binding syntax 을 참고하면 된다.
사전 지식
- Observation pattern
- 클래스
Data Binding
컴퓨터 프로그래밍에서 데이터 바인딩은 제공자와 소비자로부터 데이터 원본을 결합시켜 이것들을 동기화하는 기법이다. (위키백과)
여기서 소비자와 제공자는 뷰와 데이터이다. 즉, 데이터와 뷰를 묶는다는 뜻이다.
웹 어플리케이션의 복잡도가 증가한다면 뷰와 데이터를 일치시키기가 어려워진다. 그래서 데이터와 뷰가 자동으로 일치하도록 묶어 두는 것이 데이터 바인딩이다.
데이터 바인딩은 다시 단방향과 양방향으로 나뉜다. 단방향 데이터 바인딩은 데이터가 변경되면 템플릿과 데이터를 합쳐 뷰를 만든다. 양방향 데이터 바인딩은 뷰의 변경을 감지하여 데이터에 반영해주는 동시에, 데이터의 변경이 일어나면 템플릿과 데이터를 합쳐 뷰에 반영해준다. 두 가지의 개념을 그림으로 나타내면 아래와 같다.
자세한 것은 코드와 함께 알아보겠다.
1 way, 2 way
1초 뒤 data
변수의 값이 변경되면 변경된 값을 화면 p
태그에 반영하고 싶다. 이것의 코드는 다음과 같다.
<p id="view"></p>
<script>
let data = '';
const view = document.getElementById('view');
setTimeout(() => changeData(1), 1000);
function changeData(newData) {
data = newData; // 데이터 변경
view.innerText = data; // 변경된 데이터를 뷰에 반영
}
</script>
변수의 값을 변경한 후에 뷰에 반영해주는 작업을 직접 해야 한다. 그렇지 않으면 값의 변경은 뷰에 반영되지 않는다.
직접 뷰에 반영해주는 것이 뭐가 그리 문제냐고 생각할 수 있다. 하지만 데이터에 연관된 뷰가 많아질 경우를 생각해보자. 그리고 데이터가 하나가 아닌 수십수백 개가 된다면 어떤가?
뷰와 데이터를 묶어서 복잡함을 없애보자.
단방향 바인딩 (1 way)
데이터의 변경을 뷰에 반영하는 과정은 이렇다.
1. 뷰가 특정 데이터를 구독한다.
2. 그 데이터가 변경된다.
3. 그 데이터를 구독한 모든 뷰(구독자)에게 알림을 발송한다.
4. 알림을 받은(구독을 한) 뷰가 새로운 데이터로 업데이트된다.
위 과정을 관찰자 패턴으로 간단하게 구현하면 다음과 같다. 예제 코드를 이해하기 위해선 클래스에 대한 이해가 필요하다.
class Data {
constructor(값) {
this.값 = 값;
this.구독자들 = [];
}
구독(새로운_구독자) {
this.구독자들.push(새로운_구독자);
}
알림_발송() {
this.구독자들.forEach(구독자 => 구독자.업데이트(this.값));
}
업데이트(새로운_값) {
this.값 = 새로운_값;
this.알림_발송();
}
}
class View {
constructor(뷰) {
this.뷰 = 뷰;
}
업데이트(새로운_값) {
this.뷰.innerText = 새로운_값;
}
}
두 클래스를 사용해서 바인딩을 구현하겠다.
const 데이터 = new Data('');
const 뷰 = new View(view);
데이터.구독(뷰);
setTimeout(() => 데이터.업데이트(1), 1000);
뷰가 데이터를 구독한다. 그리고 1초 뒤, 데이터가 업데이트되면 뷰에 새로운 데이터가 반영된다.
이제 데이터를 업데이트 하면 수동으로 뷰를 업데이트해야 할 의무가 사라졌다. 즉, 데이터와 뷰를 묶었다.
이제 추가적인 상황이 필요하다. 바로 아래와 같은 상황이다.
<input id="inputView">
<p id="pView"></p>
<script>
const data = '';
setTimeout(() => data = 'new', 1000);
</script>
사용자가input
태그에 값을 입력하면 그 값을 데이터에 반영하고 싶음과 동시에 데이터의 변경을 다시 p에 반영하고 싶다. 데이터의 변경을 뷰에 반영하는 것은 성공했으니, 뷰의 변경을 데이터에 반영해보자.
양방향 바인딩 (2 way) 🌟
뷰의 변경을 데이터에 반영하는 과정은 이렇다.
1. 데이터가 특정 뷰를 구독한다.
2. 그 뷰가 변경된다.
3. 그 뷰를 구독한 모든 데이터(구독자)에게 알림을 발송한다.
4. 알림을 받은(구독을 한) 데이터가 업데이트 된다.
앞서 봤던 과정과 같고, 입장만 바뀐 것이다. 이를 바탕으로 View 클래스를 수정하겠다.
class View {
constructor(뷰) {
this.뷰 = 뷰;
}
구독(새로운_구독자) {
this.뷰.addEventListener('input', () => {
새로운_구독자.업데이트(this.뷰.value);
});
}
업데이트(새로운_값) {
this.뷰.innerText = 새로운_값;
this.뷰.value = 새로운_값;
}
}
입력을 통해 input
의 값을 변경하면서 3초 뒤에 데이터를 변경해보겠다. 여기서 3초 뒤에 데이터를 변경하는 이유는 데이터의 변경이 뷰에 잘 반영되는지 보기 위함이다.
<input id="inputView">
<p id="pView"></p>
<script>
const 데이터 = new Data('');
const 인풋_뷰 = new View(inputView1);
const 피_뷰 = new View(pView);
인풋_뷰.구독(데이터);
데이터.구독(인풋_뷰);
데이터.구독(피_뷰);
setTimeout(() => 데이터.업데이트('new'), 3000);
</script>
양방향 바인딩을 구현하여 복잡도를 줄였지만, 아직 충분히 아름답지 않다. 아쉬운 점은 뷰와 데이터가 묶여있다는 것이 직관적이지 않다는 것이다. 코드를 좀 더 선언적으로 변경하여 개선해보겠다.
<input model="데이터1">
<input model="데이터2">
<p bind="데이터1"></p>
<p bind="데이터1"></p>
<p bind="데이터2"></p>
<p bind="데이터2"></p>
<script>
const 데이터들 = {};
데이터들.데이터1 = new Data('');
데이터들.데이터2 = new Data('');
const $양방향_뷰들 = document.querySelectorAll('[model]');
const $단방향_뷰들 = document.querySelectorAll('[bind]');
$양방향_뷰들.forEach(($양방향_뷰) => {
const 데이터_이름 = $양방향_뷰.getAttribute('model');
const 양방향_뷰 = new View($양방향_뷰);
양방향_뷰.구독(데이터들[데이터_이름]);
데이터들[데이터_이름].구독(양방향_뷰);
});
$단방향_뷰들.forEach(($단방향_뷰) => {
const 데이터_이름 = $단방향_뷰.getAttribute('bind');
const 단방향_뷰 = new View($단방향_뷰);
데이터들[데이터_이름].구독(단방향_뷰);
});
</script>
bind는 단방향 데이터 바인딩을, model은 양방향 데이터 바인딩을 의미한다. 데이터가 변경되면 어떤 뷰가 변경될지, 해당 뷰를 변경하면 어떤 데이터가 변경될지 한눈에 알 수 있다. 실행결과는 다음과 같다.
마무리
전체 코드는 아래 저장소에서 확인할 수 있다.
참조
https://ko.wikipedia.org/wiki/데이터_바인딩
https://stackoverflow.com/questions/13504906/what-is-two-way-binding
https://blog.jeremylikness.com/blog/client-side-javascript-databinding-without-a-framework/
'Web > JavaScript' 카테고리의 다른 글
자바스크립트의 Promise 직접 구현하기 (13) | 2021.09.20 |
---|---|
자바스크립트의 bind, call, apply (0) | 2021.09.11 |