02. React 활용해 보기 - 2

surra77·2024년 2월 19일
0

39. React.memo를 이용한 성능 최적화

React.memo를 이용해 B 컴포넌트의 성능 최적화하기

현재 B 컴포넌트의 문제점

B 컴포넌트는 B, List, ListItem, message 컴포넌트로 나눠져 있음
input에 글을 타이핑 할 때 원래는 message 컴포넌트와 그 state를 갖고 있는 app 컴포넌트만 렌더링이 돼야 하는데 현재는 상관 없는 다른 부분들까지 모두 렌더링 되고 있음

React.memo 적용으로 문제 해결

React.memo 적용은 간단하게 원하는 컴포넌트를 React.memo로 감싸주면 됨
-> 렌더링이 필요치 않은 List, ListItem 컴포넌트는 렌더링이 안됨

-> B 컴포넌트의 성능이 확연히 좋아짐 (A: 1.5ms, B: 0.1ms)

React.memo()란?

React는 먼저 컴포넌트를 렌더링 한 뒤, 이전에 렌더링 된 결과와 비교하여 DOM 업데이트를 결정
만약 렌더링 결과가 이전과 다르다면, React는 DOM을 업데이트 함

이 과정에서 만약 컴포넌트가 React.memo()로 둘러 쌓여 있다면, React는 컴포넌트를 렌더링하고 결과를 메모이징(Memoizing)한다. 그리고 다음 렌더링이 일어날 때 렌더링하는 컴포넌트의 props가 같다면, React는 메모이징된 내용을 재사용한다.

메모제이션 (Memoization) 이란?
Memoization은 주어진 입력값에 대한 결과를 저장함으로써 같은 입력값에 대해 함수가 한 번만 실행되는 것을 보장함

React Memo가 props를 비교하는 방법은?

React.memo()는 props 혹은 props의 객체를 비교할 때 얕은 비교를 함

React Memo Props 비교 방식 수정하기

비교 방식을 원하는 대로 수정하고 싶다면 React.memo()의 두 번째 매개변수로 비교함수를 넣어주면 됨

예시)

React.memo(Component, [compareFunction(prevProps, nextProps)]);

function compareFunction(prevProps, nextProps) {
  return (
    prevProps.a === nextProps.a &&
    prevProps.b === nextProps.b
  )
}

React Memo 사용을 지양해야하는 상황

렌더링 될 때 props가 다른 경우가 대부분인 컴포넌트인 경우, 메모이제이션 기법의 이점을 얻기 힘듬

props가 자주 변하는 컴포넌트를 React.memo()로 래핑 할지라도, React는 두 가지 작업을 렌더링 할때마다 수행함
1. 이전 props와 다음 props의 동등 비교를 위해 비교 함수 수행
2. 비교 함수는 거의 항상 false를 반환할 것이기 때문에, React는 이전 렌더링 내용과 다음 렌더링 내용을 비교함
-> 비교 함수 결과가 대부분 false 라면 props 비교는 불필요

React.memo()는 리 렌더링을 막기 위한 도구보다 성능 개선의 도구

React에서 메모이제이션은 성능 개선을 위한 하나의 도구
대부분의 상황에서 React는 메모이징 된 컴포넌트의 리 렌더링을 피할 수 있지만, 렌더링을 막기 위해 메모이제이션에 너무 의존하면 안됨 (버그 유발 가능성)

결론

리액트에서 렌더링 성능 최적화를 위해서는 컴포넌트를 분리하고 React.memo를 사용하면 됨
또한 React.memo 사용은 항상 좋은 것은 아니기에 profiler를 이용해서 성능상 이점이 있는지 확인 후 사용하면 좋음


40. 얕은 비교 (Shallow Equal)

얕은 비교란?

숫자, 문자열 등 원시 자료형은 값을 비교
배열, 객체 등 참조 자료형은 값 혹은 속성을 비교하지 않고, 참조되는 위치를 비교

const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };

console.log(obj1 === obj2); // false

깊은 비교란?

얕은 비교와 달리 깊은 비교는 객체의 경우에도 갚으로 비교함
1. Object depth가 깊지 않은 경우: JSON.stringify() 사용
2. Object depth가 깊은 경우: lodash 라이브러리의 isEqual() 사용

const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };

console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true

얕은 비교는 언제 사용하나요?

  1. React.memo()에서 props를 비교할 때
  2. 리액트 컴포넌트가 리 렌더링을 하기 전
    • state의 변경이 있을 때
    • 부모 컴포넌트가 랜더링 될 때

참고) 리액트에서 컴포넌트가 리랜더링 되는 경우
1. state 변경이 있을 때
2. 부모로부터 받은 값(props)이 바뀐 경우
3. 부모가 리랜더링 되는 경우
+ shouldComponentUpdate에서 true가 반환될 때
+ forceUpdate가 실행 될 때

참고) App.js 에는 꼭 필요한 경우가 아니면 상태를 넣지 않는다
-> 렌더링 최적화를 위해. 컴포넌트화를 해서 거기에서 상태관리


41. useCallback을 이용한 함수 최적화

원래 컴포넌트가 렌더링 될 때 그 안에 있는 함수도 다시 만들게 됨. 하지만 똑같은 함수를 컴포넌트가 리 렌더링 된다고 해서 계속 다시 만드는 것은 좋은 현상이 아님
그리고 만약 리 렌더링이 되는 컴포넌트에서 이 함수를 자식 컴포넌트에 props로 내려 준다면 그 함수를 포함하고 있는 컴포넌트가 리 렌더링 될 때마다 자식 컴포넌트도 함수가 새롭게 만들어지니 계속 리 렌더링을 하게 됨

-> 원래 React.memo로 감싸줘서 리 렌더링이 되지 않던 컴포넌트들이 함수를 props로 받으니 input에 문자를 입력할 때 마다 다시 리 렌더링됨

React.useCallback 적용으로 문제 해결

useCallback은 메모이제이션된 함수를 반환하는 함수
useCallback 적용은 useCallback 안에 콜백함수와 의존성 배열을 순서대로 넣어주면 됨

예시)

const testFunction = useCallback(() => {}, []);
  • 함수 내에서 참조하는 state, props가 있다면 의존성 배열에 추가하면 됨
  • useCallback으로 인해서 의존성 배열에 추가해준 state 혹은 props가 변하지 않는다면 함수는 새로 생성되지 않음
  • 새로 생성되지 않기 때문에 메모리에 새로 할당되지 않고 동일 참조 값을 그대로 사용
  • 의존성 배열에 아무것도 없다면 컴포넌트가 최초 렌더링될 시에만 함수가 생성되며 그 이후에는 동일한 참조 값을 사용하는 함수가 됨

-> useCallback 함수로 감싸주니 리 렌더링이 안됨


42. useMemo를 이용한 결과 값 최적화

Memoization

메모이제이션은 비용이 많이 드는 함수 호출의 결과를 저장하고 동일한 입력이 다시 발생할 때 캐시된 결과를 반환하여 컴퓨터 프로그램의 속도를 높이는데 주로 사용되는 최적화 기술

function Component({ a, b }) {
  const result = conpute(a, b);
  return <div>{result}</div>
}

Component 내의 compute 함수가 만약 복잡한 연산을 수행하면 결과 값을 리턴하는데 오랜 시간이 걸리게 됨
이럴 때 컴포넌트가 리 렌더링 된다면 연산을 계속 수행하는데 오랜 시간이 걸려서 성능에 안 좋은 영향을 미치게 되며, UI 지연 현상이 일어남

-> 이런 현상을 useMemo를 이용해 해결
compute 함수에 넘겨주는 a, b의 값이 이전과 동일하다면 컴포넌트가 리 렌더링 되더라도 연산을 다시 하지 않고 이전 렌더링 때 저장해 두었던 값을 재사용

useMemo 적용하기

useMemo로 감싸준 후에 첫번째 인수에 compute함수를, 두번째 인수인 의존성 함수에는 compute 함수에서 사용하는 값을 넣어줌

profile
개발자 준비생

0개의 댓글