학습목표
- Virtual DOM이 나오게 된 배경을 학습합니다.
- React가 어떻게 Virtual DOM을 사용하는지 학습합니다.
- Virtual DOM이 어떻게 생겼는지 학습합니다.
- React가 DOM 트리를 탐색하는 방법에 대해 학습합니다.
- DOM 엘리먼트의 타입이 같을 때와 다를 때의 React 동작 방식에 대해 학습합니다.
<html>
, <head>
, <body>
와 같은 태그들에 접근하고 조작할 수 있도록 태그들을 트리 구조로 객체화 시킨 것을 의미한다.const vDom = {
tagName: "html",
children: [
{ tagName: "head" },
{ tagName: "body",
children: [
tagName: "ul",
attributes: { "class": "list"},
children: [
{
tagName: "li",
attributes: { "class": "list_item" },
textContent: "List item"
}
]
]
}
]
}
< React가 DOM 트리를 탐색하는 방법 >
<Counter />
가 갖고 있던 기존의 state 또한 파괴 <div>
<Counter />
</div>
//부모 태그가 div에서 span으로 바뀝니다.
<span>
<Counter />
</span>
<div className="before" title="stuff" />
//기존의 엘리먼트가 태그는 바뀌지 않은 채 className만 바뀌었습니다.
<div className="after" title="stuff" />
=> React는 두 요소를 비교했을 때 className
만 수정되고 있다는 것을 알게 된다.
//className이 before인 컴포넌트
<div style={{color: 'red', fontWeight: 'bold"}} title="stuff" />
//className이 after인 컴포넌트
<div style={{color: 'green', fontWeight: 'bold"}} title="stuff" />
=> className
before와 after는 각자 이런 스타일을 갖고 있다고 하면, React는 color
스타일만 수정하고 fontWeight
및 다른 요소는 수정하지 않는다. 이렇게 하나의 DOM 노드를 처리한 뒤 React는 뒤이어서 해당 노드들 밑의 자식들을 순차적으로 동시에 순회하면서 차이가 발견될 때마다 변경한다. 이를 재귀적으로 처리한다고 표현한다.
<ul>
<li>first</li>
<li>second</li>
</ul>
//자식 엘리먼트의 끝에 새로운 자식 엘리먼트를 추가했습니다.
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
=>React는 자식 노드를 순차적으로 위에서부터 아래로 비교하면서 바뀐 점을 찾는다.
그렇기 때문에 예상대로 React는 첫 번째 자식 노드들과 두 번째 자식 노드들이 일치하는 걸 확인한 뒤 세 번째 자식 노드를 추가.
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
//자식 엘리먼트를 처음에 추가합니다.
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
=> React는 우리의 기대대로 최소한으로 동작하지 못하게 된다. React는 원래의 동작하던 방식대로 처음의 노드들을 비교하게 된다.
=> 처음의 자식 노드를 비교할 때,React는 리스트 전체가 바뀌었다고 받아들인다.
즉 전부 버리고 새롭게 렌더링 해버린다.이는 굉장히 비효율적인 동작 방식이다.
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
//key가 2014인 자식 엘리먼트를 처음에 추가합니다.
//기존의 동작 방식대로 다른 자식 엘리먼트는 변경하지 않고 추가된 엘리먼트만 변경
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
학습목표
- 함수 컴포넌트와 클래스 컴포넌트의 차이를 학습하고, 함수 컴포넌트에서 Hook을 사용하는 이유를 이해합니다.
- Hook의 사용 규칙에 대해 학습하고 이해합니다.
- useMemo의 쓰임새와 작성 방법에 대해 학습합니다.
- useCallback의 쓰임새와 작성 방법에 대해 학습합니다.
- custom hooks의 쓰임새와 작성 방법에 대해 학습합니다.
useState()
가 Hookfunction Counter () {
const [counter, setCounter] = useState(0);
// Counter 컴포넌트에서 useState() Hook을 호출해 함수 컴포넌트(function component) 안에 state를 추가한 형태
const handleIncrease = () => {
setCounter(counter + 1)
}
return (
<div>
<p>You clicked {counter} times</p>
<button onClick={handleIncrease}>
Click me
</button>
</div>
)
}
function Calculator({value}){
const result = calculate(value);
return <>
<div>
{result}
</div>
</>;
}
=>calculate가 내부적으로 복잡한 연산을 해야 하는 함수라 계산된 값을 반환하는 데에 시간이 몇 초 이상 걸린다고 가정하면, 해당 컴포넌트는 렌더링을 할 때마다 이 함수를 계속해서 호출 =>그 때마다 시간이 몇 초 이상 소요, 렌더링 지연
/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import { useMemo } from "react";
function Calculator({value}){
const result = useMemo(() => calculate(value), [value]);
return <>
<div>
{result}
</div>
</>;
}
=> 렌더링을 할 때마다 이 value값이 계속 바뀌는 게 아니라고 가정
=> 그럼 이 값을 어딘가에 저장을 해뒀다가 다시 꺼내서 쓸 수만 있다면 굳이 calculate 함수를 호출할 필요도 없을 것이다. 여기서 useMemo Hook을 사용
=> useMemo를 호출하여 calculate를 감싸주면, 이전에 구축된 렌더링과 새로이 구축되는 렌더링을 비교해 value값이 동일할 경우에는 이전 렌더링의 value값을 그대로 재활용
useMemo
는 바로 이 개념을 이용하여 복잡한 연산의 중복을 피하고 React 앱의 성능을 최적화val1
과 val2
add
함수가 계속 같은 결괏값을 리턴함에도 불구하고 불필요하게 계속 호출되고 있기 때문에, useMemo
를 이용하여 add
함수의 호출을 최소화해야만 한다. 즉 이름을 입력할 때는 add
함수가 호출되지 않아야 최적화가 된 컴포넌트라고 볼 수 있다.//App.js
import React, { useState, useMemo } from "react";
import "./styles.css";
import { add } from "./add";
export default function App() {
const [name, setName] = useState("");
const [val1, setVal1] = useState(0);
const [val2, setVal2] = useState(0);
const answer = useMemo(()=> add (val1,val2), [val1,val2])
// const answer = add(val1, val2);
return (
<div>
<input
className="name-input"
placeholder="이름을 입력해주세요"
value={name}
type="text"
onChange={(e) => setName(e.target.value)}
/>
<input
className="value-input"
placeholder="숫자를 입력해주세요"
value={val1}
type="number"
onChange={(e) => setVal1(Number(e.target.value))}
/>
<input
className="value-input"
placeholder="숫자를 입력해주세요"
value={val2}
type="number"
onChange={(e) => setVal2(Number(e.target.value))}
/>
<div>{answer}</div>
</div>
);
}
//add.js
export const add = (num1, num2) => {
console.log("숫자가 들어옵니다.");
return Number(num1) + Number(num2);
};
useCallback
을 사용useCallback
을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다고 볼 수 있다.function doubleFactory(){
return (a) => 2 * a;
}
const double1 = doubleFactory();
const double2 = doubleFactory();
double1(8); // 16
double2(8); // 16
double1 === double2; // false
double1 === double1; // true
/*double1과 double2는 동일한 코드를 공유하더라도 메모리 주소가 다르기 때문에,
메모리 주소에 의한 참조 비교 시 다른 함수로 본다 */
button
dark mode를 누르면 “아이템을 가져옵니다.”가 콘솔에 출력되는 걸 볼 수 있다.getItems()
함수가 다시 만들어진다. useEffect
은 setItems
를 호출하고 종속성이 변경됨에 따라 “아이템을 가져옵니다.”를 출력하는 것이다.//App.js
import { useState, useCallback} from "react";
import "./styles.css";
import List from "./List";
export default function App() {
const [input, setInput] = useState(1);
const [light, setLight] = useState(true);
const theme = {
backgroundColor: light ? "White" : "grey",
color: light ? "grey" : "white"
};
/* 이전 코드
const getItems = () => {
return [input + 10, input + 100];
};
*/
const getItems = useCallback(() => [input + 10, input + 100],[input]);
const handleChange = (event) => {
if (Number(event.target.value)) {
setInput(Number(event.target.value));
}
};
return (
<>
<div style={theme} className="wall-paper">
<input
type="number"
className="input"
value={input}
onChange={handleChange}
/>
<button
className={(light ? "light" : "dark") + " button"}
onClick={() => setLight((prevLight) => !prevLight)}
>
{light ? "dark mode" : "light mode"}
</button>
<List getItems={getItems} />
</div>
</>
);
}
// List.js
import { useState, useEffect } from "react";
function List({ getItems }) {
/* Initial state of the items */
const [items, setItems] = useState([]);
/* This hook sets the value of items if
getItems object changes */
useEffect(() => {
console.log("아이템을 가져옵니다.");
setItems(getItems());
}, [getItems]);
/* Maps the items to a list */
return (
<div>
{items.map((item) => (
<div key={item}>{item}</div>
))}
</div>
);
}
export default List;
=> 버튼을 눌러 리렌더링이 되어도, 자식 컴포넌트의 props로 전해줄 getItems()
함수가 useCallback을 이용해 함수 자체를 저장
=> 함수의 메모리 주소 값이 저장돼서 같은함수이기 때문에 React가 List 구성 요소 내에서 useEffect은 setItems를 호출하지 않는다.