React Developer Tools의 components 에서 highlight updates when components render항목을 check하면 렌더링이 되는 영역을 눈으로 확인 할 수 있습니다.
React는 먼저 컴퍼넌트를 렌더링(rendering) 한 뒤, 이전 렌더된 결과와 비교하여 DOM 업데이트를 결정합니다. 만약 렌더 결과가 이전과 다르다면, React는 DOM을 업데이트합니다.
Re-Rendering이 되는 경우에는 크게 3가지가 존재합니다.
여러개의 state 와 props 중에 하나만 변경되어도 계속 re-render가 일어나게 되고, 부모 컴포넌트의 re-ender로 인해 자식 컴포넌트까지 불필요한 re-rendering이 일어나게 된다면 성능 저하문제를 유발할 수 있습니다.
React에서 성능 최적화란 Re-Rendering을 최소화 하는 것입니다.
import React, { useCallback } from "react";
useCallback(() => {}, []);
//첫번째 인자 = 콜백함수
//두번쨰 인자 = 의존성배열
메모이제이션된 콜백을 반환합니다.인라인 콜백과 그것의 의존성 값의 배열을 전달하세요. useCallback은 콜백의 메모이제이션된 버전을 반환할 것입니다. 그 메모이제이션된 버전은 콜백의 의존성이 변경되었을 때에만 변경됩니다. 이것은, 불필요한 렌더링을 방지하기 위해 (예로 shouldComponentUpdate를 사용하여) 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용합니다
참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용하다는것은 다시말해 최적화된 자식 컴포넌트에 props로 콜백 함수를 내려줄 때를 말하는것 같습니다.
const TodoTemplate = () => {
const [localTodo, setLocalTodo] = useLocalStorage<ITodo[]>('todoList', []);
const deleteCompletedTodo = useCallback((): void => {
const todoUpDate = useCallback((todo: ITodo): void => {
setLocalTodo((prevState) => [...prevState, todo]);
}, []);
return (
<>
<TodoCreate todoUpDate={todoUpDate} />
<TodoList localTodo={localTodo} setLocalTodo={setLocalTodo} />
</>
);
};
TodoTemplate에서 todoUpDate라는 함수를 자식 컴포넌트인 TodoCreate에 전달하였고 TodoList에서는 바뀐 localTodo를 사용하여 화면을 그려냅니다. 만약에 useCallback 사용하지 않으면 localTodo값이 TodoList안에서 바뀌게 될 때마다 전혀 상관없는 todoUpDate가 재생성 됩니다. 그것을 막기위해 위와같이 의존성 배열에 빈 배열을 주어 초기에 한번만 함수를 생성되도록 하였습니다.import React, { useMemo } from "react";
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
“생성(create)” 함수와 그것의 의존성 값의 배열을 전달하세요.useMemo는 의존성이 변경되었을 때에만 메모이제이션된 값만 다시 계산 할 것입니다. 이 최적화는 모든 렌더링 시의 고비용 계산을 방지하게 해 줍니다.useMemo로 전달된 함수는 렌더링 중에 실행된다는 것을 기억하세요. 통상적으로 렌더링 중에는 하지 않는 것을 이 함수 내에서 하지 마세요. 예를 들어, 사이드 이펙트(side effects)는 useEffect에서 하는 일이지 useMemo에서 하는 일이 아닙니다.배열이 없는 경우 매 렌더링 때마다 새 값을 계산하게 될 것입니다.
어떤 함수가 있고 그 함수가 어떤 값을 리턴하고있는데 그 리턴까지의 연산을 최적화하고싶다면 useMemo를 사용해서 댑스에 어떤값이 변화할때만 그 리턴까지의 연산을 수행할것인지를 명시하면 그 함수를 값처럼 사용하여 연산최적화를 할 수 있습니다.
export function Movie({ title, releaseDate }) {
return (
<div>
<div>Movie title: {title}</div>
<div>Release date: {releaseDate}</div>
</div>
);
}
export default React.memo(Movie);
//사용시
<Movie
movieTitle="Heat"
releaseDate="December 15, 1995"
/>
// 다시 렌더링 할 때 React는 MemoizedMovie 함수를 호출하지 않는다.(props로 넘겨주는 값이 변하지 않았을 경우!)
// 리렌더링을 막는다.
<Movie
movieTitle="Heat"
releaseDate="December 15, 1995"
/>
메모이징 한 결과를 재사용 함으로써, React에서 리렌더링을 할 때 가상 DOM에서 달라진 부분을 확인하지 않아 성능상의 이점을 누릴 수 있다.React.memo()
는 props 혹은 props의 객체를 비교할 때 얕은(shallow) 비교를 합니다.
비교 방식을 수정하고 싶다면 React.memo()
두 번째 매개변수로 비교함수를 만들어 넘겨주면 됩니다.
React.memo(Component, [areEqual(prevProps, nextProps)]);
areEqual(prevProps, nextProps)
함수는 prevProps
와 nextProps
가 같다면 true
를 반환합니다.
위에 예제 코드의 Movie의 porps가 동일한지 수동으로 비교해 보겠습니다.
function moviePropsAreEqual(prevMovie, nextMovie) {
return (
prevMovie.title === nextMovie.title &&
prevMovie.releaseDate === nextMovie.releaseDate
);
}
const MemoizedMovie = React.memo(Movie, moviePropsAreEqual);
moviePropsAreEqual()
함수는 이전 props
와 현재 props
가 같다면 true
를 반환하며 이전값을 그대로 사용하여 리렌더링이 발생하지 않습니다.