티스토리 뷰
렌더링이란
제너럴한 의미에서 렌더링이란 이미지를 나타내는 과정을 의미하고, 리액트에서의 렌더링이란 클래스 컴포넌트의 경우 render 함수의 실행을 말하고 함수형 컴포넌트의 경우는 함수 전체의 실행을 의미한다. (이하 render 함수를 실행한다고 표현하겠다)
참고로 함수형 컴포넌트는 진짜 함수라서
<Component />
이렇게 쓰지 않고{Component()}
라고 호출하는 것도 가능하다. 하지만 이렇게 하지 않기를 권장하고 있다. (Don't call function components. Render them.)다시 말하자면 리액트의 렌더링 = render 함수 실행(혹은 함수형 컴포넌트 실행)이고, render 함수 실행의 결과로 react element tree가 산출된다.
React element tree는 React element라는 오브젝트로 구성된 트리인데, 이 React Element는 UI가 어떻게 생겨야 하는지 묘사하는 단순한 object이다. 어떻게 구성된 객체인지 보려면 what is jsx 참고.
그러면 React element tree 같은 것을 왜 만드는가? React가 화면 업데이트를 효율적으로 하기 위해서이다.
만약 유저가 버튼의 컬러만 바꾸는 조작을 했는데 모든 DOM이 다시 새로 만들어진다면 매우 비효율적일 것이다. 왜냐면 DOM을 만드는 것은 많은 연산이 필요하다. 얼마나 오래 걸리는지 참고
그래서 버튼 컬러가 바뀌면 state 또는 props가 update될 거고, render 함수를 호출해서 react element를 산출한다.
그리고 산출된 react element 객체를 보고, diff를 확인해서 자식들을 리렌더링할 지 결정한다. 이렇게 재귀적으로 트리의 위에서 아래까지 내려가면서 리렌더링을 하게 된다.
위에서 말했듯이 diff가 있으면 리렌더링을 하게 되는데, 이 diff 유무를 판단하는 부분이 흥미롭다. 예를 들어 자식 컴포넌트의 props가 달라지면 변경사항을 반영하기 위해 자식 컴포넌트를 리렌더링을 해야하는데, 코드 상으로 props는 매번 새로 만들어 달라지게 되어있다. 이게 무슨 뜻이냐면 부모 컴포넌트의 render함수가 호출됐을 때 만들어지는 react element 객체는 새로 만들어지는 객체이기 때문에, object refrence가 달라서 프로퍼티가 완전히 동일하더라도 다른 객체로 인지한다는 것이다. 그러므로 원래는 리렌더링하는 컴포넌트의 모든 자식 컴포넌트는 리렌더링이 된다. 좀 더 자세히 보려면 optimize react re renders 참고.
그렇지만 프로퍼티가 하나도 바뀌지 않은 자식 컴포넌트를 전부 리렌더링하는 것은 비효율적이다. 화면 그려져야 하는 내용이 동일한데 굳이 불필요하게 함수를 호출하고 연산하는 셈이다. 그렇다면 이 불필요한 리렌더링을 어떻게 제거해야할까?
불필요한 리렌더링을 막는 법
주로 언급되는 방법은 아래와 같다.
React.memo를 사용하여 컴포넌트를 감싸서 props가 변하지 않았을 때에는 리렌더링을 방지하게 하는 방법이 있다.
부모의 상태가 변하면 자식 컴포넌트의 props가 변하지 않았더라도 리렌더되므로, 동일한 props라면 리렌더하지 않게끔 그 자식 컴포넌트를 memo로 감싸는 것이다.
다만, 이 때 해당 자식 컴포넌트가 받는 props가 object 타입의 데이터라면 referential equality를 신경써줘야 한다. React.memo는 shallow comparison을 하여 object의 동일성을 판단할 때 reference만 본다. 즉 내부의 값이 변하였더라도 동일한 reference에서 mutate된 것이면 동일하다고 판단할 것이고, 내부의 값이 동일하더라도 다른 reference를 갖고 있으면 다르다고 판단할 것이다.
그렇기 때문에 object 값들이 변하지 않았을지라도, 부모 컴포넌트가 리렌더되면서 자식에게 넘겨주는 object가 새로 만들어져서 다른 reference를 갖게 되므로 다른 props로 인식되는 케이스가 있다.
이럴 때에는 useMemo를 사용해서 dependency array의 원소들이 변하지 않는 한 re-computation하지 않도록 하여 동일한 reference를 유지할 수도 있고, React.memo의 두 번째 패러미터를 사용해서 isEqual인지 아닌지 판단하는 함수를 직접 써주면 된다.가능하면 상태를 트리의 낮은 쪽으로 위치시키는 방법이 있다. 위에서 언급했듯이 부모의 상태가 변하면 부모를 포함한 모든 자식들이 props 변화와 관계없이 리렌더링되므로 상태를 불필요하게 높게 두지 않는 것이다.
상태를 끌어내려서 불필요한 렌더링을 방지하는 것과 반대로, expensive한 연산을 하는 자식 컴포넌트를 위로 끌어올려서(lift) 통째로 props로 전달하는 방법이 있다. 부모 컴포넌트의 상태가 변하면 이 expensive한 자식 컴포넌트가 리렌더링될 것이므로, 이를 방지하기 위해 이 비싼 컴포넌트를 위로 올려서 부모 컴포넌트의 props로 내려준다. 만약 이 비싼 컴포넌트의 props에 변화가 없었다면, 리액트는 이미 만들어진 이 컴포넌트에 변화가 없음을 알고 리렌더링하지 않는다. 즉 이미 만들어진 컴포넌트를 re use할 뿐이다.
조금 더 자세한 코드는 Overreacted 블로그 글을 참고해보자.
Ref
https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
https://dev.to/tylerthehaas/referential-equality-in-react-127h
https://reactjs.org/docs/react-api.html#reactmemo
불필요한 리렌더링을 방지하는 방법 - https://felixgerschau.com/react-rerender-components/
'공부일지(TIL) > JS Framework + Library' 카테고리의 다른 글
[React] Reconciliation (0) | 2021.07.23 |
---|---|
[Redux] Redux랑 RxJS 같이 사용하기 (feat. Redux-Observable) (0) | 2021.04.23 |
[React] useImperativeHandle의 장점 (1) | 2021.03.16 |
[React] Cancelable Promise (0) | 2021.03.16 |
[React] useEffect dependency array에 object와 array 넣기 (0) | 2021.02.26 |
- Total
- Today
- Yesterday
- CSS
- 인스턴스
- 제네릭스
- 리덕스
- react
- c언어
- SQL
- useEffect
- oracle
- Session
- JavaScript
- Conflict
- 알고리즘
- 포인터 변수
- this
- getter
- jQuery
- 깃
- 개발 공부
- package.json
- Data Structure
- youtube data api
- linkedlist
- 자바
- til
- GIT
- Redux
- Prefix Sums
- Java
- rxjs
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |