TIL26. hooks (useMemo, useCallbak)

imloopy·2022년 5월 27일
0

Today I Learned

목록 보기
29/56
post-custom-banner

useMemo

  • useMemo hook을 사용하여 복잡하게 계산된 값을 재사용할 수 있다. useMemo로 저장된 값은 두번째 인자로 받은 deps 배열의 값의 변화가 없는 이상, 컴포넌트가 재렌더링될 때에도 다시 값을 계산하지 않는다.
function sum(n: number) {
  console.log('Start');
  let result = 0;
  for (let i = 0; i <= n; i += 1) {
    result += i;
  }
  console.log('Finish');
  return result;
}

export const Box = ({ label, n }: IBox): JSX.Element => {
  // expensive 비용인 경우, 해당 의존성에 영향을 미치는 n의 변화가 없다면
  //재렌더링이 발생해도 다시 계산하지 않음
  const result = useMemo(() => sum(n), [n]); // 뒤 배열은 의존성
  return (
    <div>
      <div>
        {label}: {result}
      </div>
    </div>
  );
};

용도

  • 3D, 실시간, graph 등의 극단적인 퍼포먼스가 필요한 경우
  • Pure Functional Component에서 같은 Props가 전달됨에도 불구하고 불필요한 렌더링이 발생할 경우

최적화

useMemo를 사용하여 다음의 경우의 재렌더링에의한 계산을 막을 수 있다:

  • 현재 컴포넌트의 상태가 변경되어 재렌더링이 발생하는 경우
  • 현재 컴포넌트가 받는 prop의 값이 변경되어 재렌더링이 발생하는 경우

그러나 다음의 경우는 useMemo만 사용하여 렌더링 최적화가 불가능하다:

  • 부모의 상태가 변경되어 자식 컴포넌트가 렌더링 되는 경우

이를 해결하기 위하여 React.memo로 렌더링을 방지할 컴포넌트를 감싸야 한다.

React.memo

  • React.memo는 동일한 prop을 받고, 그 결과가 이전과 동일하다면 다시 렌더링을 하지 않는다.(불필요한 컴포넌트 렌더링 방지)

React.memo를 사용하는 것이 항상 좋은가?

결론부터 말하자면 아니다. React.memo는 다음의 경우에 유리하다.

  • 같은 props로 렌더링이 자주 일어나는 경우
    • 서버에서 매 초 fetch를 하되, 하위 컴포넌트의 pops가 자주 바뀌지 않는 경우
  • 무겁고 비용이 큰 연산이 있는 경우

React.memo는 다음의 경우에 불리하다. 성능적인 이점을 얻지 못한다면 오히려 React.memo를 사용하지 않는 것이 낫다. React.memo는 컴포넌트가 업데이트 되기 전에 컴포넌트가 변화가 일어났는지, 일어나지 않았는지 확인하므로

  • 대부분의 경우에는 늘 Props가 변화하므로, 성능상의 이점을 얻기 어려움

모든 최적화 과정은 Trade-off 과정이므로, 항상 어떤 것의 기회 비용이 더 큰지를 따져 최적화를 진행하는 것이 좋다.

콜백함수를 prop으로 사용하는 경우, 함수의 참조값이 바뀌어 React.Memo로 감싼 컴포넌트가 이전 props와 현재 props의 동등 비교하는 과정을 통해 false를 반환하게 되므로, useCallback으로 callback 함수 최적화가 필요하다.

useCallback

  • useMemo와 역할은 동일하지만, useMemo가 값을 반환하는 것과는 달리, useCallback은 메모이제이션된 함수를 반환한다. useCallback의 두 번째 인자인 deps 배열 내부의 값이 변하지 않으면 반환된 함수의 참조값이 변하지 않는다.
  • 컴포넌트가 렌더링되더라도 변하지 않기 때문에, 최적화를 위하여 React.memo로 감싼 컴포넌트에 인자로 내려주기 매우 적합하다. 그래서 이름이 useCallback인가보다.

용도

  • 최적화 작업을 진행한 컴포넌트의 prop의 콜백 함수로 넘겨줄 수 있음
  • useInput 같은 customhook에서 자주 사용
// ... App.tsx
function App(): JSX.Element {
  const [foodOn, setFoodOn] = useState(false);
  const [servicesOn, setServicesOn] = useState(false);
  const [transportationOn, setTransportationOn] = useState(false);
  const foodChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => setFoodOn(e.target.checked),
    [],
  );
  const servicesChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => setServicesOn(e.target.checked),
    [],
  );
  const transportationChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) =>
    setTransportationOn(e.target.checked),
    [],
  );
  return (
    <div className="App">
      <Checkbox label="food" on={foodOn} onChange={foodChange} />
      <Checkbox label="services" on={servicesOn} onChange={servicesChange} />
      <Checkbox
        label="transportation"
        on={transportationOn}
        onChange={transportationChange}
      />
    </div>
  );
}

// 최적화를 진행한 컴포넌트에서 부모 컴포넌트의 재렌더링에 의한
// 하위 컴포넌트 제랜더링 방지를 위하여 useCallback 사용
const Checkbox = ({ label, on, onChange }: ICheckbox): JSX.Element => {
  console.log(label, on);
  return (
    <label>
      {label}
      <input type="checkbox" defaultChecked={on} onChange={onChange} />
    </label>
  );
};

export default React.memo(Checkbox);

이슈

파일을 만들고, 파일 경로를 수정하는데 자꾸 대소문자만 다른 모듈이 있다고 떴다. 어떻게 해결해도 잘 해결이 안됬다.

검색을 해보니, 리액트에서는 components 폴더 내부에 각 컴포넌트마다 폴더를 만들고, 컴포넌트는 해당 폴더의 index.tsx에 작성하는 방법을 주로 사용한다고 한다.

그 외에 아직 다른 방법으로 해결할 수 있는지는 찾아보지 못했다.

느낀 점

  • 리액트를 사용하면서 그 전에는 최적화에 대하여 그렇게 신경쓰지 않고 홈페이지를 만들었고, 데이터를 1초마다 서버로부터 확인하는 컴포넌트가 있었는데 왜 이렇게 버벅이냐고 생각했는데, 이제는 그 이유를 알 것 같다.
  • 작은 것들을 개발하더라도 늘 어디에선가 예상하지 못한 이슈들이 발생하는 것 같다. 언제쯤 이를 줄일 수 있을까?
  • 현재 hook을 공부하면서 각 hook의 차이점? 들을 공부하고 있는데, 아직까지는 애매하게 알고 있는 것 같다. 다음 주 있을 면접 스터디를 공부하면서 개념을 다시 익혀야겠다.
  • 요즘들어 자꾸 피곤함을 느낀다. 오늘은 일찍 자야겠다......

출처

React.memo() 현명하게 사용하기
경고! 대소문자만 다른 이름을 가진 여러 모듈이 있습니다 | React, typescript, webpack ERROR
React 공식 문서

post-custom-banner

0개의 댓글