컴포넌트는 상호 작용의 결과를 화면에 반영해야 하는 경우가 있습니다. React는 컴포넌트별 메모리를 state라고 합니다.
state를 사용하지 않고 일반 변수에 상호 작용의 결과를 저장을 하면 작동할 거라 생각할 수 있습니다. React에서는 아래의 코드에 클릭을 하더라도 number 값은 변경되지 않습니다. 이러한 변화를 보이지 않게 하는 2가지 이유가 있습니다.
1. React에서 렌더링을 할 때 지역변수에 대한 변경사항을 고려하지 않고 렌더링 합니다. 즉, 지역 변수는 렌더링 간에 유지되지 않습니다.
2. 지역변수가 변경해도 React가 새로운 데이터로 컴포넌트를 다시 렌더링해야 하는 것을 인식하지 못합니다.
export default function Counter(){
let number=0;
function handleClick(){
number=number+1;
}
return <button onClick={handleClick}> {number} </button>;
}
useState
컴포넌트를 새로운 데이터로 업데이트하기 위해서는 렌더링 사이에 데이터가 유지되어야 하고, 새로운 데이터 변경될 경우 컴포넌트를 재렌더링하도록 유발해야 합니다. 이를 위해 React에서는 useState 훅을 제공합니다. useState는 렌더링 사이에 데이터를 유지하기 위해 state 변수를, 변수를 업데이트하고 React가 컴포넌트를 재렌더링하도록 유발하는 state setter 함수가 있습니다.
** React에서는 useState를 비롯해 "use"로 시작하는 함수들을 훅(hook)이라고 합니다. 훅은 React 렌더링 중일 때만 사용할 수 있는 특별한 함수로 다양한 React 기능을 연결할 수 있습니다. (훅은 컴포넌트의 최상위 수준 또는 커스텀 훅에서만 호출할 수 있습니다. 조건문, 반복문 등 내부에서 훅을 호출할 수 없습니다.)
export default function Counter(){
const [number, setNumber] = useState(0);
function handleClick(){
setNumber(number+1);
}
return <button onClick={handleClick}> {number} </button>;
}
위의 코드에서 number는 state 변수이고 setNumber는 setter 함수입니다. useState에 유일한 인수는 state 변수의 초기값입니다.
1. 컴포넌트가 처음 렌더링 됩니다. (number의 초기값으로 0을 useState에 전달했으므로 [0, setNumber]가 반환됩니다. React는 0을 최신 state 값으로 기억합니다.)
2. state를 업데이트합니다. (사용자가 버튼을 클릭하면 setNumber(number + 1)를 호출합니다. number는 0이므로 setNumber (1)입니다. 이렇게 하면 React는 이제 number 가 1 임을 기억하고 다음 렌더링을 유발합니다.)
3. 컴포넌트가 2번째로 렌더링 됩니다. (React는 여전히 useState(0)을 보지만, number를 1로 설정한 것을 기억하고 있기 때문에, 이번에는 [1, setNumber]를 반환합니다.)
** [ ] 구문은 배열 구조분해이며 useSate가 반환하는 배열은 2개의 항목입니다.
** 동일한 컴포넌트를 2번 렌더링 한다면 각 복사본은 격리된 state를 가지기 때문에 하나가 변경하더라도 다른 하나에 영향을 주지 않습니다. 또한 state는 이를 선언한 컴포넌트 외에 비공개적이고 부모 컴포넌트도 이를 맘대로 변경할 수 없습니다. 만약 state를 동기화하고 싶다면 자식 컴포넌트에 state를 제거하고 공통적인 부모 컴포넌트에 state를 추가하는 방법이 있습니다.
React의 렌더링
React 렌더링 동작에는 3가지 과정으로 이루어집니다. 처음 렌더링이 트리거 되면 컴포넌트를 렌더링 하고 DOM에 커밋을 합니다.
1. 렌더링 트리거
렌더링이 트리거될 때는 컴포넌트가 초기 렌더링일 경우와 컴포넌트의 state가 업데이트된 경우입니다. 앱을 시작할 때 초기 렌더링을 트리거해야 합니다. DOM 노드와 함께 createRoot를 호출한 다음 해당 컴포넌트로 render 메서드를 호출하면 이 작업이 완료됩니다. 컴포넌트가 처음 렌더링 된 후 setter 함수를 통해 state를 업데이트하여 추가적인 렌더링을 트리거할 수 있습니다. 컴포넌트의 상태를 업데이트하면 자동으로 렌더링 대기열에 추가됩니다.
** 렌더링은 항상 순수한 계산이어야 합니다. 동일한 입력에는 동일한 출력이 나와야 하고, 이전의 state를 변경해서는 안 됩니다. 그렇지 않으면 버그 또는 예측할 수 없는 동작이 발생합니다.
2. React 컴포넌트 렌더링
렌더링을 트리거한 후 React는 컴포넌트를 호출하여 화면에 표시할 내용을 파악합니다. (렌더링은 React에서 컴포넌트를 호출하는 것입니다.) 초기 렌더링에서 React 루트 컴포넌트를 호출합니다. 이후에 렌더링에서 React는 state 업데이트가 일어나 렌더링을 트리거한 컴포넌트를 호출합니다. (업데이트된 컴포넌트가 다른 컴포넌트를 반환하면 React 다음으로 해당 컴포넌트를 렌더링 합니다. 중첩된 컴포넌트가 더 이상 없고, React가 화면에 표시되어야 하는 내용을 정확히 알 때까지 이 단계는 계속됩니다.) 변경된 속성을 계산하고 다음 단계인 커밋 단계까지는 해당 정보로 아무런 작업을 수행하지 않습니다.
** 상위 요소가 변경될 경우 자식 요소도 렌더링은 일어납니다. 단지 이전과 변경사항이 없으면 그대로이지만 변경사항이 있다면 커밋이 돼서 DOM 노드가 변경됩니다.
3. React가 DOM에 변경사항을 커밋
초기 렌더링의 경우 React는 appendChild() DOM API를 사용하여 생성한 모든 DOM 노드를 화면에 표시합니다. 리렌더링의 경우 렌더링 간에 차이가 있는 경우에만 DOM 노드를 변경합니다. 렌더링이 완료되고 React가 DOM을 업데이트한 후 브라우저는 화면을 다시 그립니다. (브라우저 렌더링 또는 페인팅)
** React18 공식 문서 보고 정리한 내용입니다.
https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/state-a-components-memory
State: 컴포넌트의 기억 저장소 – React
The library for web and native user interfaces
ko.react.dev
https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/render-and-commit
렌더링 그리고 커밋 – React
The library for web and native user interfaces
ko.react.dev
'Dev > React.js' 카테고리의 다른 글
[React18] 7. state 관리하기 (0) | 2023.09.30 |
---|---|
[React18] 6. state 업데이트 (0) | 2023.09.29 |
[React18] 4. 이벤트 핸들러 (0) | 2023.09.27 |
[React18] 3. UI 표현하기 (0) | 2023.09.15 |
[React18] 2. 컴포넌트 (import/export, JSX) (0) | 2023.09.13 |
댓글