728x90
반응형
소개
prop가 새로 전달되었을때 또는 내부 state가 변경되었을때 리렌더링이 일어난다.
어디가 변경되었는지 확인하고 변경된 부분만 리렌더링 해야하는데 성능때문에 얕은 비교를 한다.
특히 참조 타입(함수)의 경우 얕은 비교 때문에 원치 않은 부분까지 전부 리렌더링 될 수 있는데
useCallback, useMemo 를 통해 이를 방지해주어야 한다.
- 원시 타입 얕은 비교
참조값(메모리주소)이 달라져도 값만 같으면 같은 것으로 간주 → 리렌더링 X - 참조 타입 얕은 비교
참조값(메모리주소)이 달라지면 변경된 것으로 간주 → 리렌더링 O
불필요한 리렌더링 일어나는 경우 *이렇게 쓰면 안됨!
- 하위 컴포넌트에 전달하는 콜백 함수를 인라인함수로 사용
<Component callback={() => console.log("123")}/>
- 컴포넌트 내에서 로컬 함수를 생성
const 컴포넌트 = () => { const localFunction = () => { ... } return (...); }
불필요한 리렌더링 방지법
1. state나 props값에 의존적이지않고 변경되지 않을 값이라면 컴포넌트 외부에 선언해라
2. 매번 새로 만들지 말고 동일 참조값(메모리주소)을 가진 함수를 사용하자
- useCallback 은 동일 참조(메모리주소)값을 가지는 함수를 반환한다.
- 의존성 값이 있을 경우 componentDidUpdate 로 동작
- 의존성 없을 경우(빈배열) componentDidMount 로 동작
- 동일 참조값(메모리주소)을 사용하여 꼭 필요할 때만 함수를 다시 생성함
- 꼭 필요할 때에만 사용한다. 의존성이 빈 배열이면 오히려 손해임
인라인 함수에 적용
// Before : 항상 새로운 함수를 만든다.
<Component callback={() => doSomething(a,b)}/>
// After : a,b가 변경될 때만 함수를 만든다.
const memoizedCallback = useCallback(
() => {doSomething(a, b);}, // 인라인 콜백
[a, b], // 의존성
);
<Component callback={memoizedCallback}/>
로컬 함수에 적용
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const increment = () => setCount((c) => c + 1)
// Before : 전혀 관계없는 count가 update되어도 addTodo 함수가 다시 생성됨
const addTodo = () => setTodos((t) => [...t, "New Todo"])
// After : 전혀 관게없는 count가 update되어도 addTodo 함수가 다시 생성되지 않음
const addTodo = useCallback(
() => { setTodos((t) => [...t, "New Todo"]);
}, [todos]
);
3. 동일 참조값(메모리주소)인 경우 리렌더링 하지 말자
- useMemo 은 메모이제이션 된 값을 반환한다.
- 의존성이 변경되는 경우에만 실행한다. (=꼭 필요할 때만 값을 다시 계산함)
- 리렌더링 불필요한 경우에는 값을 재계산 하지않고 이전 결과값을 사용하여 성능최적화
- 고 비용의 연산에만 사용하도록 한다.
옛날 방식
const memoizedComponent = React.memo(
(props) => // props를 사용하여 렌더링
(prev, next) => // nextProp가 prevProps와 동일한 값을 가지면 true를 반환
);
최신 Hook 사용방식
const calculation = useMemo(
() => expensiveCalculation(count),
[count] // 의존성 count 외의 값이 변경되면 리렌더링 되지 않음
);
useCallback, useMemo 함께 사용한 예시
// Before : count1 또는 count2 어느쪽이 변경되든 리렌더링
function CountButton({ onClick, count }) {
return <button onClick={onClick}>{count}</button>;
}
function DualCounter() {
const [count1, setCount1] = React.useState(0);
const increment1 = () => setCount1(c => c + 1); // 매번 새로 생성 됨
const [count2, setCount2] = React.useState(0);
const increment2 = () => setCount2(c => c + 1); // 매번 새로 생성 됨
return (
<>
<CountButton count={count1} onClick={increment1} /> // 매번 리렌더링 됨
<CountButton count={count2} onClick={increment2} /> // 매번 리렌더링 됨
</>
);
}
// After : onClick과 count가 변경될 때에만 리렌더링
const CountButton = React.memo(
function CountButton({ onClick, count }) {
return <button onClick={onClick}>{count}</button>;
},
[onClick, count]
);
function DualCounter() {
const [count1, setCount1] = React.useState(0);
const increment1 = React.useCallback(() => setCount1(c => c + 1), []); // 1번만 생성 됨
const [count2, setCount2] = React.useState(0);
const increment2 = React.useCallback(() => setCount2(c => c + 1), []); // 1번만 생성 됨
return (
<>
<CountButton count={count1} onClick={increment1} /> // count1 이 변할때만 리렌더링
<CountButton count={count2} onClick={increment2} /> // count2 이 변할때만 리렌더링
</>
);
}
반응형