실제 DOM의 사본 같은 개념. React는 실제 DOM 객체에 접근하여 조작하는 대신 이 가상의 DOM 객체에 접근하여 변화 전/후를 비교 후 바뀐 부분을 적용한다
DOM (Document Object Model)
JavaScript 같은 스크립팅 언어가 <html>
, <head>
, <body>
와 같은 태그들에 접근하고 조작할 수 있도록 브라우저가 태그들을 트리 구조로 객체화 시킨 것
브라우저가 트리구조로 만든 객체 모델
트리는 "데이터 저장" 자체의 의미보다 저장된 데이터를 더 효과적으로 탐색하기 위한 용도로 사용됨 => 빠른 자료 탐색 성능이 장점!
DOM의 변경과 업데이트로 인한 브라우저의 리플로우(Reflow)/리페인트 과정은 속도를 느리게 만드는 원인
=> JavaScript로 조작하는 DOM 요소가 많을수록 모든 DOM 업데이트에 대하여 리플로우하게된다는 뜻 => 업데이트된 부분만 렌더링 할 수 없을까? ==> Virtual DOM 탄생배경
가상 DOM은 가상의 UI 요소를 메모리에 유지시키고 이를 ReactDOM과 같은 라이브러리를 통해 실제 DOM과 동기회시키는 것
=> 실제 DOM처럼 브라우저에 실제로 그리는 것이 아니기 때문에 속도가 훨씬 빠름
트리의 레벨 순서대로 순회함 (같은 레벨-위치끼리 비교) => BFS(너비우선탐색)
<div>
가 <span>
으로 바뀌는 등 부모 태그가 달라지면,
이전 트리를 버리고 새로운 트리를 구축해버림.
태그 자체가 바뀌지 않고 className
만 바뀌는 경우 그로 인해 변경된 스타일만 수정하고 다른 요소는 수정하지 않음
하나의 DOM 노드를 처리한 뒤 뒤이어 해당 노드들 맡의 자식들을 순차적으로 동시에 순회하면서 차이가 발견될 때 변경함 => 재귀적으로 처리
<ul> <li>Duke</li> <li>Villanova</li> </ul> //자식 엘리먼트를 처음에 추가합니다. <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul>
React는 위에서 아래로 순차적으로 탐색함
자식 엘리먼트를 맨 위에 추가하는 경우, 리액트는 리스트 전체가 바뀌었다고 받아들이고 전부 버리고 새롭게 렌더링을 해버림==> 매우 비효율적인 동작 방식
기존 트리의 자식과 새로운 트리의 자식이 일치하는지 key
를 통해 확인할 수 있음
키는 전역적으로 유일할 필요는 없고 형제 엘리먼트 사이에서만 유일하면 됨
<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>
shortid.generate()
class Counter extends Component { constructor(props) { super(props); this.state = { counter: 0 } this.handleIncrease = this.handleIncrease.bind(this); } handleIncrease = () => { this.setState({ counter: this.state.counter + 1 }) } render(){ return ( <div> <p>You clicked {this.state.counter} times</p> <button onClick={this.handleIncrease}> Click me </button> </div> ) } }
단순한 counting하는 기능만 구현한 것인데도 매우매우 복잡함
function Counter () { const [counter, setCounter] = useState(0); const handleIncrease = () => { setCounter(counter + 1) } return ( <div> <p>You clicked {counter} times</p> <button onClick={handleIncrease}> Click me </button> </div> ) }
일단 보는 눈이 편안...😌
훨씬 직관적이고 보기 쉬움
함수형 컴포넌트에서 상태값 및 다른 여러 기능 사용을 편리하게 해주는 메소드
클래스형 컴포넌트에서는 동작하지 않음
반복문, 조건문, 중첩된 함수 내에서 Hook 실행 시 예상대로 동작하지 않을 수도 있음
리액트 함수형 컴포넌트나 커스텀 Hook이 아닌 다른 일반 JavaScript안에서 호출 안됨
렌더링 최적화를 위한 Hook
특정 값을 재사용할 때 사용하는 Hook
고정된 값을 가져다 쓰는 경우, 그 값을 어딘가에 저장해뒀다가 꺼내서 쓰는 개념
=> 그렇게 되면 값이 필요할 때마다 매번 해당함수를 호출하지 않아도 됨!
/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import { useMemo } from "react";
function Calculator({value}){
const result = useMemo(() => calculate(value), [value]);
return <>
<div>
{result}
</div>
</>;
}
컴포넌트도 감쌀 수 있음
난수생성방법:
Math.randon().toString(36).slice(2)
색깔 랜덤 생성함수: Hex, Math.floor(), Math.random(), 16진법
`#${Math.floor(Math.random()*16777215).toString(16)}` //#265bb3 과 같이 색상번호로 표기됨
useEffect : fetch(API) 해온 값을 초기에 렌더링 시킬때 주로 사용
useMemo : 그 외에 값들에 주로 사용, 시간이 많이 걸리는 연산들
기존에 수행한 연산 결과값을 메모리에 저장해두고 동일한 입력이 들어오면 재활용하는 기법
메모이제이션을 활용할 경우 굳이 중복 연산을 할 필요가 없으므로 앱의 성능을 최적화할 수 있음
useMemo는 값의 재사용을 위해 사용하는 Hook인 반면, useCallback은 함수의 재사용을 위해 사용하는 Hook
/* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import React, { useCallback } from "react";
function Calculator({x, y}){
const add = useCallback(() => x + y, [x, y]);
return <>
<div>
{add()}
</div>
</>;
}
새로이 만들어 호출된 함수는 기존의 함수와 같은 함수가 아님. 주소값이 달라짐.
그 주소값까지 저장을 해버리는 것이 useCallback
useCallback을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같은 것 => 주소 값까지 저장하여 재선언을 막는 기능을 함
따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있음!