리액트에서 렌더링이란, 컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미한다. 즉 사용자 화면에 View(내용)를 보여 주는 것.
렌더링을 담당하는 render 함수는 컴포넌트의 정보를 이용해 화면을 구성(렌더링)한다.
컴포넌트 내부에는 또 다른 컴포넌트들이 들어갈 수 있어, render 함수가 실행되면 그 내부에 있는 컴포넌트들도 재귀적으로 렌더링 된다.
렌더링이 끝나면 render 함수가 반환한 객체의 정보를 이용하여 문자열 형식의 html코드를 반환해 특정 DOM에 주입한다.
리액트에서 뷰를 업테이트(리렌더링)할 때 “ 조화과정을 거친다 ” 라고 표현한다.
뷰를 변형시키는 것으로 보이는게 사실 render 함수로 새로운 요소로 갈아 끼워지는 것이기 때문이다.
이전에 생성한 컴포넌트 정보와 다시 렌더링한 정보를 비교해 최소한의 연산으로 DOM 트리를 업데이트한다.(Virtual DOM 개념)
state
변경props
변경useMemo와 useCallback은 React Hook 중 하나. 메모이제이션으로 중복 연산 ****을 피할 수 있기 때문에 메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화할 수 있다.
memoization: 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법
컴퍼넌트를 렌더링하고 결과를 메모이제이션. 그리고 다음 렌더링이 일어날 때 props가 같다면, 메모이징된 내용을 재사용한다. props 혹은 props의 객체를 비교할 때 얕은 비교를 한다.
// 생성자 함수
<Component prop={new Obj("x")} />
// 객체 리터럴
<Component prop={{property: "x"}} />
위와같이 props로 객체를 넘겨줄 경우 컴포넌트가 리렌더링 될 때마다 새로운 객체가 생성되어 자식 컴포넌트로 전달된다.
props로 전달한 객체가 동일한 값이어도 새로 생성된 객체는 이전 객체와 다른 참조 주소를 가진 객체이기 때문에 자식 컴포넌트는 메모이제이션이 되지않는다.
state를 그대로 하위컴포넌트에 넘겨주어 필요한 데이터 가공을 그 하위컴포넌트에서 해주는 것이 좋다.
map함수를 돌릴 때 고유 key값을 부여해야하는데 이때 index로 key값을 설정한다면 중간에 새로운 값이 삽입된 경우 이후에 위치한 요소들은 전부 index가 변경된다.
이로 인해 key값이 변경되어 React는 key가 동일 할 경우, 동일한 DOM Element를 보여주기 때문에 예상치 못한 문제가 발생합니다. 또한, 데이터가 key와 매치가 안되어 서로 꼬이는 부작용도 발생한다.
다음과 같은 경우는 사용해도 괜찮다.
기존의 useState를 사용하며, 대부분 setState시에 새로운 상태를 파라미터로 넣어주었다.
setState를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣을 수도 있는데,
이렇게 하면 useCallback을 사용할 때 두 번째 파라미터로 넣는 배열에 값을 넣어주지 않아도 된다.
// 예시) 삭제 함수
const onRemove = useCallback(
(id) => {
setTodos(todos.filter((todo) => todo.id !== id));
},
[todos],
);
// 예시) 함수형 업데이트 후
const onRemove = useCallback((id) => {
setTodos((todos) => todos.filter((todo) => todo.id !== id));
}, []);
React 최적화 방식을 공부하면서 배웠던 내용중 하나는 아무때나 무분별한 사용은 지양하라는 것
useMemo
, useCallback
, React.memo
와 같은 것들을 성능을 걱정해서 미리 사용하는 것은 비추천
→ 실제로 Hello cocone에 useMemo
, useCallback
, React.memo를 적용했을 때 렌더링 시간이 크게 줄어들지 않고 비슷하거나 오히려 렌더링 시간이 늘어나기도 했다.
📌 이외에 추가로 적용할 수 있는 최적화 방법