이 글은 Corbin Crutchley의 What is Reactivity? 를 번역한 글입니다. 오역, 의역 있을 수 있습니다.
프론트엔드 개발자라면 이런 질문을 자주 들어봤을 겁니다.
왜 React, Vue, Angular 같은 모던 프레임워크를 사용하나요?
"반응성(Reactivity)" 때문이라 대답할 수 있습니다.
그렇다면 반응성은 무엇일까요?
간단히 말해, 반응성은 자바스크립트 애플리케이션의 메모리 내용을 DOM에 HTML로 반영하는 기능입니다.
정적 HTML로만 웹사이트를 만드는 경우 DOM 출력은 간단합니다.
<main id="a">
<ul id="b">
<li id="c">Item 1</li>
<li id="d">Item 2</li>
</ul>
<p id="e">Text here</p>
</main>
문제는 인터랙티브(상호작용) 기능을 도입하려고 할 때 일어납니다.
소규모 애플리케이션을 만든다고 생각해봅시다:
- 내부에 카운터가 있는 버튼이 있습니다.
- 카운터는 0에서 시작합니다.
- 버튼을 클릭할 때마다 카운터가 1씩 증가합니다.
이를 위해 먼저 HTML을 만들어봅시다:
<main>
<button id="add-button">Count: 0</button>
</main>
그 다음 자바스크립트를 추가하여 버튼이 동작하도록 만듭니다.
<script>
let count = 0;
const addBtn = document.querySelector('#add-button');
addBtn.addEventListener('click', () => {
count++;
addBtn.innerText = `Count: ${count}`;
});
</script>
괜찮네요. 그러면 조금 난이도를 높여 보겠습니다:
<ul>
로 리스트를 만들어봅시다.count
가 증가할 때마다 내부에 고유 문자열이 있는 새 <li>
추가하기<main>
<button id="add-button">Count: 0</button>
<ul id="list"></ul>
</main>
<script>
let count = 0;
const listEl = document.querySelector('#list');
function makeListItem(innerText) {
const li = document.createElement('li');
li.innerText = innerText;
listEl.append(li);
}
const addBtn = document.querySelector('#add-button');
addBtn.addEventListener('click', () => {
count++;
addBtn.innerText = `Count: ${count}`;
makeListItem(`List item: ${count}`);
});
</script>
좋습니다! 이제 마지막 연습입니다:
count
에서 1을 제거하는 버튼을 추가합니다.로직 트리가 얼마나 복잡해져가는지 보이나요?
<main>
<button id="add-button">Add one to: 0</button>
<button id="remove-button">Remove one from: 0</button>
<ul id="list"></ul>
</main>
<script>
let count = 0;
const listEl = document.querySelector('#list');
function makeListItem(innerText) {
const li = document.createElement('li');
li.innerText = innerText;
listEl.append(li);
}
function removeListItem() {
listEl.lastChild.remove();
}
const addBtn = document.querySelector('#add-button');
const removeBtn = document.querySelector('#remove-button');
function updateBtnTexts() {
addBtn.innerText = `Add one to: ${count}`;
removeBtn.innerText = `Remove one from: ${count}`;
}
addBtn.addEventListener('click', () => {
count++;
updateBtnTexts();
makeListItem(`List item: ${count}`);
});
removeBtn.addEventListener('click', () => {
count--;
updateBtnTexts();
removeListItem();
});
</script>
와! 정말 순식간에 복잡해졌네요, 그렇죠?!
네..그래서 질문이 생겼습니다:
count
에 의존하는 다른 항목을 추가할 때, 데이터가 변경되지 않았다는 것에 집중해주세요.
대신, 자바스크립트 state를 해당 state의 DOM 표현에 붙이기 위해 점점 더 많은 복잡성을 추가해야 했습니다.
이 모든 '접착제(가져다 붙이기 위한 코드들)'를 제거하면 아주 간소화된 코드베이스가 남게 됩니다:
<main>
<button id="add-button">Add one to: 0</button>
<button id="remove-button">Remove one from: 0</button>
<ul id="list"></ul>
</main>
<script>
// Magical land where `count` changes auto-update the DOM
let count = 0;
addBtn.addEventListener('click', () => {
count++;
});
removeBtn.addEventListener('click', () => {
count--;
});
</script>
얼마나 많은 줄들이 사라졌는지 보세요!
이보다 더 좋은 코드 작성 방법은 이론적으로 가능할 뿐만 아니라, 수많은 개발자가 프론트엔드 프레임워크를 통해 채택하고 있습니다.
const App = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Add one to: {count}
</button>
<button onClick={() => setCount(count - 1)}>
Remove one from: {count}
</button>
<ul>
{Array.from({ length: count }).map((_, i) => (
<li>List item {i}</li>
))}
</ul>
</div>
);
};
이것이 바로 반응성의 핵심 아이디어입니다. 자바스크립트에 저장된 상태를 어떻게 변경할 것인지에 집중할 수 있게 해주고, 다른 메커니즘을 통해 화면에 표시되는 방식을 추상화 할 수 있게 해줍니다.
예를 들어 각 프레임워크가 화면 표시 방법에 대해 내부적으로 활용하는 메커니즘은 다음과 같습니다:
Framework | Reactivity Method | Rendering Method |
---|---|---|
React | Explicit Function Calls | VDOM |
Angular | Zone.js | Incremental DOM |
Vue | Proxies | VDOM |
지금까지 반응성이란 무엇이고, 오늘날 앱에서 반응성을 활용하기 위해 모던 프론트엔드 프레임워크를 사용해야하는 이유에 대해 살펴보았습니다.
다음 시간에는 "Reconciliation"이 무엇이며 오늘날 대부분의 React 및 Vue 프론트엔드 애플리케이션에 어떤 영향을 미치는지에 대해 이야기하겠습니다.