초기 웹 개발은 Vanilla JavaScript로 DOM을 직접 선택하고 조작하는 방식으로 이루어졌다.
2006년 jQuery가 등장하면서 DOM 조작 문법을 간소화했지만, 근본적인 한계는 여전했기에 역사는 오래가지 못했다.
JavaScript의 등장 (1995년)> jQuery의 등장(2006년) > React의 등장(2013년)
React 이전의 핵심문제는 데이터와 UI가 분리되어 있다는 점이였다.
① UI 업데이트 = DOM요소를 수동 업데이트
② 업데이트 타이밍이 엇갈리면 UI가 일관되지 않은 상태로 화면에 노출
③ 하나의 데이터가 여러곳에서 사용될 때, 사용처를 기억하고 관리해야했다
④ 어플리케이션 규모가 커질 수록 조건문과 이벤트 관리가 증가한다.
// DOM 직접 조작 예제
let count = 0;
const $value = document.getElementById('value');
const $btn = document.getElementById('btn');
$btn.addEventListener('click', () => {
count++; // 데이터 변경
$value.textContent = count; // 직접 DOM 반영
});
2000년대 후반 Gmail, Facebook, Twitter와 같은 서비스가 등장하면서, 웹은 더 이상 단순 문서가 아닌 애플리케이션이 되었다. 기존의 DOM 방식으로는 관리가 어려워졌다.
사용자 정보가 한곳에서 변경되면 해당 데이터를 사용하는 곳의 정보도 동시에 업데이트 되어야하는데 기존 DOM 방식으로는 전역 상태를 관리하기에 한계가 있었다.
*애플리케이션 :사용자와 실시간 상호작용이 가능한 소프트웨어
"데이터가 변했을 때, UI를 어떻게 효율적으로 업데이트 할 것인가" 가 당시의 개발자들이 직면한 핵심 과제였다. "상태가 변하면 UI는 자동으로 다시 그려진다"는 선언적 패러다임으로 이 문제의 전환점을 제시했다.
React 공식 문서에서는 상태에 대해서 컴포넌트의 기억 저장소라고 소개한다.
상태는 UI에 영향을 주는 데이터이며, 언제든 변경될 수 있다.
State : A Component's Memory
일반변수는 값이 변경 되어도 리렌더링이 발생하지 않는다는 점에서 차이점이 있다.
// 일반 변수 - 값이 바뀌어도 화면 변화 없음
let count = 0;
count = count + 1; // 렌더링 발생 X
// 상태 - 값이 바뀌면 자동으로 리렌더링
const [count, setCount] = useState(0);
setCount(count + 1); // 렌더링 발생 O
상태 변경 === UI 자동 업데이트
개발자가 setState()를 호출하면
React는 새로운 UI를 계산하고 변경된 부분 업데이트 한다.
→ 개발자는 DOM을 전혀 조작하지 않는다. (DOM업데이트는 React가 담당)
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
JSX란?
Javacscript안에서 HTML과 유사한 형태로 UI 구조를 표현할 수 있게해주는 문법이다.
전통적인 웹개발에서는 기술로 관심사를 분리했다면
전통적인 웹개발
📄 HTML ← 구조
📄 CSS ← 스타일
📄 Jacascript ← 동작
React에서는 기술이 아닌, 기능 단위로 관심사를 분리해야한다고 생각했다.
그래서 컴포넌트안에 HTML과 CSS, Jacascript를 합쳐놓은 컴포넌트 방식을 제안했다.
📁 components/
📄 Button.jsx ← 버튼과 관련된 모든 것
📄 Modal.jsx ← 모달과 관련된 모든 것
📄 UserCard.jsx ← 유저 카드와 관련된 모든 것
컴포넌트는 독립적으로 재사용 가능한 UI의 단위이다.
Mount(마운트) : 컴포넌트가 화면에 처음 나타날 때
Update(업데이트) : state나 props가 변경되어 컴포넌트가 다시 렌더링 될 때
UnMount(언마운트) : 컴포넌트가 메모리에서 제거 될 때(화면에서 사라질 때)
부모 → 자식 (단방향) 데이터 전달을 기본 원칙으로 한다.
🤔 onClick 이벤트의 경우 자식에서 부모로 데이터를 전달하는 경우도 있는데 이때는 역방향으로 보여지기도 한다
// 부모
function CardListView() {
const [list, setList] = useState([
{ idx: 1, isCheer: false, cheerCount: 0 },
{ idx: 2, isCheer: true, cheerCount: 5 },
]);
const toggleCheer = (idx) => {
setList(prev => prev.map(item =>
item.idx === idx
? { ...item, isCheer: !item.isCheer, cheerCount: item.cheerCount + (item.isCheer ? -1 : 1) }
: item
));
};
return (
<div>
{list.map(item => (
<Card
key={item.idx}
item={item}
onCheer={toggleCheer} // 함수 자체를 전달
/>
))}
</div>
);
}
// 자식
function Card({ item, onCheer }) {
return (
<div>
<span>👍 {item.cheerCount}</span>
<button onClick={() => onCheer(item.idx)}> {/* 자식이 idx를 담아서 호출 */}
{item.isCheer ? '응원 취소' : '응원하기'}
</button>
</div>
);
}
"선언적 UI"는 React를 대표하는 수식어이기도 하다.
정확한 체감을 위해서 명령형과 선언형을 비교해본다.
변화에 대한 과정을 모두 지시
// 명령형에서의 handleClick
function handleClick() {
// DOM을 직접 찾아서
const btn = document.querySelector('.btn-primary');
// 스타일을 직접 변경하고
btn.style.backgroundColor = 'blue';
btn.textContent = 'Clicked!';
// 다른 요소도 찾아서 변경
const message = document.getElementById('message');
message.style.display = 'block';
message.textContent = 'Button was clicked';
}
무엇을 보여줄지만 선언
→ DOM 처리는 React가 알아서
// 선언형에서의 handleClick
function App() {
const [isClicked, setIsClicked] = useState(false);
const handleClick = () => {
setIsClicked(true); // 상태만 변경하면 UI는 자동으로 업데이트
};
return (
<>
<button
className="btn-primary"
style={{ backgroundColor: isClicked ? 'blue' : 'gray' }}
onClick={handleClick}
>
{isClicked ? 'Clicked!' : 'Click me'}
</button>
{isClicked && <p>Button was clicked</p>}
</>
);
}
명령형에서는 변화에 대한 모든 과정을 어떻게 바꿀지를 지정했다면
선언형에서는 무엇을 보여줄지만 정의하고, DOM 처리는 React에게 위임한다.
Virtual DOM은 React가 렌더링 성능을 최적화하기 위해서 사용하는 가상 DOM 트리이다.
Reconciliation(재조정) 과정
1. 상태 변경 발생
2. 새로운 Virtual DOM 트리 생성
3. 이전 Virtual DOM과 비교 (Diffing)
4. 변경된 부분만 실제 DOM에 반영 (Patch)
🤔 Virtual DOM 이 성능상 유리한가?
Q: Virtual DOM은 변경된 부분을 식별하기 위한 대조군이다.
DOM이 하나 더 생기는 셈인데, 성능상 불리하지 않을까?
A: 완벽하게 최적하된 DOM 조작방식이라면 Virtual DOM을 사용하지 않는편이 빠를 수 있다. 하지만 DOM을 일일이 조작하는 비용(렌더링 비용 및 개발비용)을 고려한다면, Virtual DOM은 가장 빠른 방식은 아니지만 충분히 빠르면서 편리한 절충안이라고 볼 수 있을 것 같다.
const [items, setItems] = useState([]);
const itemCount = items.length; // 계산된 값