react와 memoization

zmin·2022년 7월 22일
0

들어가기에 앞서 의존성으로 variable을 던져주는건 아주 의미없는 행동…의존성이라는게 결국 이전값과 비교하는 용도인데 일반 변수의 경우 rerendering이 진행되면 아주 싸그리 사라졌다가 다시 선언되기 때문에 애초에 이전과 값을 비교할 수도 없고 비교 한다고 해도 이미 다시 초기화된 값과 비교하기 때문에 변화를 감지하지 못함

memoization

성능(속도) 향상을 위해 고비용 연산의 결과 값을 저장해두고 이를 필요할 때 꺼내 쓰거나, 같은 입력이 들어왔을 때 동일한 값을 내야한다면 캐싱해두었던 값을 사용

리액트의 경우 한 부분이 업데이트 되더라도 전체 컴포넌트 트리를 다시 그림 → 변화된 부분만 바꿔 끼우더라도 일단 다시그림 → 이때 불필요한 렌더링이 진행 될 수 있다ㅠㅠㅠ 일때 사용하는게 memoization


React.memo

함수형 컴포넌트를 위한 것
클래스형 컴포넌트해서는 React.PureComponent를 이용하여 사용 가능


React.PureComponent?
고차 컴포넌트

컴포넌트로 전달된 props에 변경이 있을 때만 re-rendering을 하고 그게 아닐 경우에는 저장해둔 값을 가져와서 씀 → 메모이제이션
(물론 해당 컴포넌트 내부에 hooks가 있고 state가 쓰인다면 당연히 그 값이 변할 때는 re-rendering)


memoization을 render 방지로 이용하는 것은 안됨!!

이건 상위 컴포넌트의 re-rendering에 따른 불필요한 리렌더링을 방지하기 위한 것 → 성능 개선

공식 홈페이지에서의 예시는 하위 컴포넌트에 상위 컴포넌트의 state를 render하고 이 값에 변화를 주게되면 해당 하위 컴포넌트 이외에도 다른 하위 컴포넌트들도 re-rendering된다 → 불필요한 렌더링

  • 수정한 코드
    import { memo, useCallback, useMemo, useState } from "react";
    import "./styles.css";
    
    const CountButton = memo(function CountButton({ onClick, count }) {
      console.log(count, " 하위 컴포넌트 렌더링");
      return <button onClick={onClick}>{count}</button>;
    });
    
    export default function App() {
      const [count1, setCount1] = useState(0);
      const increment1 = useCallback(() => setCount1(c => c + 1), [])
    
      const [count2, setCount2] = useState(0)
      const increment2 = useCallback(() => setCount2(c => c + 1), [])
    
      console.log("상위 컴포넌트 렌더링");
    
      return (
        <div className="App">
          <CountButton count={count1} onClick={increment1} />
          <CountButton count={count2} onClick={increment2} />
        </div>
      );
    }

같은 props에 대해 같은 값이 나오는 컴포넌트인 Countbutton는 이제 onClick또는 value가 변하게 되면 그때 다시 렌더링하여 반환한다.
이때 상위 컴포넌트 함수인 increment에도 매 렌더링마다 새로운 함수를 만들지 않고 저장해둔 함수를 쓰기 위해 useCallback 사용
(→ 그런데 사실 이 경우는 예시라서 그런거지 이정도면 그냥 memoization없어도 성능에 큰 문제는 없어보인다.)

useMemo, useCallback

docs에 나와있는 대로만 본다면 그냥 콜백으로 넘겨준 값을 메모이제이션 해뒀다가 의존성으로 전달된 값이 변화될 때 이 저장된 것을 연산하여 넘겨주는 것 → 불필요한 render를 막는다고 되어있음

memo의 경우는 어떤 값을 얻어야할 때 / callback의 경우는 함수가 어떤 행위를 할때(memo도 가능하긴 하나 특화되어 있음

rendering 될 때 함수가 동작 → useEffect등을 통해 렌더링 이후에 값을 전달하거나 하는 것은 의미 없음

의존성은 필수적!!! 그렇지 않으면 이 hook을 사용하는 의미가 없음…그냥 렌더링 될 때 기존 state 사용하는 것 마냥 연산

그래서 차라리 빈 배열을 전달한다면 계속 같은 값 또는 행위를 메모이제이션 해두고 이를 사용하게 됨

→ 그런데 그 의존성으로 전달된 것이 primitive 값이 아니라 객체라면 이 내부의 값이 변화된 것은 변경되었다고 감지를 못함


그러면 생각을 해봅시다.

기본적인 컨셉은 어떤 값이 변화했을 때(의존성)만 미리 지정해둔 연산을 실행하여 값을 업데이트 해준다 → 그렇지 않은 경우 이전 값 사용

미리 지정해둔 연산

useMemo의 경우 의존성이 변경된 것을 감지할 때 새로운 값을 계산하여 반환한다 → 함수를 저장해둠(이부분이 중요) + 이전 의존성으로 계산된 값도 저장해둠,

기존 리액트의 작동방식은 렌더링할 때 열심히 값을 계산하고 업데이트하다가 리렌더링 되기 전에 전부 다 날려버리고 새로 연산한다(state, props(아마도) 제외..) 그렇기 때문에 이 hook들을 쓰면 다음 렌더링에 기억해야하는 정보의 가짓수가 늘어나기 때문에 이를 잘 판단해야함

이전에 넘겨준 함수를 저장하는 메모리를 계속 참조하기 때문에 GC되지 않아 메모리를 차지하게 됨!!

알지만 모를 수도있기 때문에 순수함수에 대한 정의를 또 찾아보면…

순수함수(pure function)

일대일 함수? 같은 값을 넣으면 무조건 같은 값을 반환함

그리고 부수효과(side effect)가 없음. 외부의 어떤 값을 변경하는 것과 같이 (DOM-manipulation 역시 비순수

꼭 필요한 것이 아니면 순수함수를 유지하는 것이 좋은 것 같음.. 아무래도 직관적이고…배열에 값을 넣는 것도 바로 push하는 것이 아니라 그걸 복사해서 new 배열을 반환하는 형태 → 순수함수…


그래서 처음부터 memo를 사용할 생각을 하기 보다는 성능을 개선하기 위해 추후 적용하는게 더 좋을 것이라는 판단

그럼 그 판단의 기준은…?

  1. 참조성 동일(Reference equality)

    의존성이 객체형태인 참조형 변수로 주어진다면 이 변수에 객체가 재할당 되지 않는 이상 변경되었다고 인지하지 못한다

    그럴 때 아예 재할당 해주는 방향으로 사용 가능

  2. 정말!!!!!!!!!!! 계산하는데 미친듯이 비용이 많이드는 아주아주 무겁고 오래걸리는 연산

인 경우에만 하는 것이 좋음 → 근데 사실 리액트는 생각보다 되게 빨라서 정말 상위 컴포넌트를 정말 업데이트해서는 안되거나 이 연산이 성능에 영향을 미칠 정도라거나.. 그정도가 아니면 그냥 두는게 더 효율적이고 좋을 수도 있음 오히려 메모이제이션 하는 것이 더 안좋을 가능성이 있는 것이 일반적

useMemo/Callback, React.memo등을 비롯하여 memoization을 진행하는 애들은 실제 성능을 먼저 측정하고 이 성능 결함의 문제가 위의 것들로 해결될 수 있는 경우에 사용하는 것이 좋음 → 미리 예측한다거나 그런 동작을 한다는 이유만으로 사용하는 것은 좋지 않음 → 최대한 피하기

참고자료

https://stackoverflow.com/questions/55026139/whats-the-difference-between-usecallback-with-an-empty-array-as-inputs-and-u

https://medium.com/geekculture/great-confusion-about-react-memoization-methods-react-memo-usememo-usecallback-a10ebdd3a316#:~:text=The useMemo is used to,is used to memoize functions.

https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976#:~:text=Pure Functions-,A pure function is a function which%3A,Produces no side effects.

profile
308 Permanent Redirect

0개의 댓글