React Hook 성능 최적화

이춘길·2021년 11월 18일
0

React

목록 보기
4/9
post-thumbnail
post-custom-banner

목표

  • Hooks Performance-optimizations 정리

1. useEffect 사례

1) Props와 State를 사용하는 경우

function Example({ someProp }) {
  useEffect(() => {
    function doSomething() {
      console.log(someProp);
    }

    doSomething();
  }, [someProp]); // ✅ OK (우리 effect는`someProp` 만 사용합니다)
}
  • useEffect 내부에서 props 값인 someProp을 사용하는 경우 종속성에 추가

2) Props와 State를 사용하지 않는 경우

useEffect(() => {
  function doSomething() {
    console.log('hello');
  }

  doSomething();
}, []);
  • 초기 렌더링 시, 호출 후 종료
  • componentDidMount와 같은 효과, 종속성이 없으면 componentDidUpdate

3) effect 내부 변수를 이용한 분기 처리

 useEffect(() => {
    let ignore = false;
    async function fetchProduct() {
      const response = await fetch('http://myapi/product/' + productId);
      const json = await response.json();
      if (!ignore) setProduct(json);
    }

    fetchProduct();
    return () => { ignore = true };
}, [productId]);
  • 내부적으로 분기가 필요하면 위와 같이 적용
  • 모양새가 약간 클로저랑 비슷한 거 같음

4) Interval과 같은 형태를 사용하는 경우

4-1) 내부 State 상태를 사용하는 방법

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count => count + 1); // 이 effect는 'count' state에 따라 다릅니다
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{count}</h1>;
}

4-2) useRef를 사용하는 방법

function Example(props) {
  // 최신 props를 ref에 보관해주세요.
  const latestProps = useRef(props);
  useEffect(() => {
    latestProps.current = props;
  });

  useEffect(() => {
    function tick() {
      // 언제든지 최신 props 읽기
      console.log(latestProps.current);
    }

    const id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []); // 이 effect는 다시 실행되지 않습니다
}
  • 대표적인 사례 : Counter(Timer)
  • 종속성을 사용하지 않고, setCount 내부 state 상태를 통해 지속적인 업데이트 수행

2. Memoization

1) shouldComponentUpdate

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);
  • useMemo, useCallback과 같은 Hook 제공
  • Memoization 없이 작동하도록 코드를 작성한 다음 추가한 뒤 성능 최적화 권장
function Parent({ a, b }) {
  // 'a'가 변경된 경우에만 다시 렌더링 됩니다:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // 'b'가 변경된 경우에만 다시 렌더링 됩니다:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}
  • 컴포넌트 메모이제이션 생성은 Hook 규칙으로 인하여 위와 같이 적용한다.

2) Memoization을 위한 useState, useRef

2-1) useState

function Table(props) {
  // ⚠️ createRows()는 모든 렌더링에서 호출됩니다
  const [rows, setRows] = useState(createRows(props.count));
  // ...
}

function Table(props) {
  // ✅ createRows()는 한 번만 호출됩니다
  const [rows, setRows] = useState(() => createRows(props.count));
  // ...
}
  • 비용이 많이 드는 initialState 시 활용

2-2) useRef

function Image(props) {
  // ⚠️ IntersectionObserver는 모든 렌더링에서 생성됩니다
  const ref = useRef(new IntersectionObserver(onIntersect));
  // ...
}

function Image(props) {
  const ref = useRef(null);

  // ✅ IntersectionObserver는 한 번 느리게 생성됩니다
  function getObserver() {
    if (ref.current === null) {
      ref.current = new IntersectionObserver(onIntersect);
    }
    return ref.current;
  }

  // 필요할 때 getObserver()를 호출해주세요
  // ...
}
  • 비용이 많이 드는 initialValue에 활용

3) useReducer를 이용한 깊이 있는 props 전달 방지

const TodosDispatch = React.createContext(null);

function TodosApp() {
  // 주의: `dispatch`는 다시 렌더링 간에 변경되지 않습니다
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}
  • 깊을 것 같은 컴포넌트가 나타나면, Context + useReducer 패턴을 사용한다.
function DeepChild(props) {
  // 작업을 수행하려면 context에서 dispatch를 얻을 수 있습니다.
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}
  • Child는 위와 같이 적용하여 편리하면서 직관적으로 사용할 수 있다.

출처

Hook 자주 묻는 질문

profile
일지를 꾸준히 작성하자.
post-custom-banner

0개의 댓글