ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 성능 최적화를 위한 React-hook
    React 2022. 12. 21. 15:54

    memo

    - 정확히 말하면 hook은 아님

    - 밑에서 정리할 useCallback이나 useMemo를 사용하기 위해 반드시 알아야하는 개념

    - 컴포넌트에 불필요한 렌더링을 하지 않도록 해주는 함수 (화면에서 변경되는 부분이 없음에도 불구하고 화면이 다시 렌더링 되는 것)

     

    화면이 리렌더링 되는 경우

    • 부모 컴포넌트가 렌더링 된 경우
    • 컴포넌트 스테이트가 변경 된 경우
    • 부모로부터 전달받은 props 값이 변경되는 경우

    불필요한 리렌더링을 줄이는 것만으로도 프로젝트의 부하를 줄이고 퍼포먼스 능력을 향상시킬 수 있기에

    최적화를 잘 하면 굉장히 좋은 결과를 기대할 수 있음

     

     

     

    리렌더링 과정

    위 사진에서 보듯이, input의 value값에 한글자라도 변경이 일어나면 리렌더링 되는 것을 볼 수 있다.

    input은 App.js에 있지만 리렌더링이 일어나지 않아도 될 button.jsx까지 리렌더링이 되고있다.

    부모 컴포넌트인 App.js에서 리렌더링이 일어나고있기 때문이다.

     

    memo 사용

    // components/Button.jsx
    
    import React from 'react';
    import {memo} from 'react'
    
    const Button = () => {
      console.log('리렌더링되고 있어요.');
      return <button>버튼</button>;
    };
    
    export default memo(Button);

    Button.jsx에 memo를 사용하여 재시동 해봤다.

     

     

     

    최초 한번만 렌더링 되고 그 뒤 input의 value값이 변경되어도 리렌더링이 일어나지 않는 것을 볼 수 있다.

    근데 부모에서 Button.jsx를 부모에서 동작하게 하고자 어떤 함수를 props로 넘겨주면 Button.jsx는 다시 리렌더링이 시작된다.

     

     

     

    위 memo를 사용하고 props로 함수를 넘겨주니 리렌더링 다시 시작됨

     

    // App.jsx
    
    import React, {useState} from 'react';
    import Button from './components/Button';
    
    const App = () => {
      // App.js가 리렌더링 될때마다 재생성됨
      const [value, setValue] = useState('');
    
      // App.js가 리렌더링 될때마다 재생성됨
      const onChangeHandler = (e) => {
        setValue(e.target.value);
      };
    
      // App.js가 리렌더링 될때마다 재생성됨
      const onClickHandler = () => {
        console.log('hello button!');
      };
    
      return (
        <div>
          <input type='text' value={value} onChange={onChangeHandler} />
          {/* 매번 재생성되는 함수를 props로 넘겨줌 -> Button.js 리렌더링 유발 */}
          <Button onClick={onClickHandler} />
        </div>
      );
    };
    
    export default App;

    App.js에서 Button.jsx까지 input에 값을 넣을 때마다 어떤 식으로 컴포넌트들이 동작하고 있을까?

    1.  input의 값을 입력
    2. onChangeHandler 실행
    3. onChangeHandler 에 의해서  setValue 실행
    4. value state 변경
    5. value state 가 변경됨에 따라 App.js 리렌더링
    6. App.js가 리렌더링 되면 App.js 안에 있는 useState, 함수(onChangeHandler, onClickHandler)들이 다시 생성됨
    7. 위 useSate,함수 들이 재선언 되었기 때문에 Button.jsx 입장에서는 넘어온 onChangeHandler 함수가 새로운 것이라고 판단
    8. 새로운 props가 왔다 판단해서 계속 리렌더링이 된다.

     

    Button.jsx의 리렌더링을 막으려면?

    • App.js가 리렌더링을 할 때 함수를 계속 재생성 하지 않게 하면 된다.
    • 이 재생성 하지 않게 해주는 hook이 밑에서 정리할 useCallback 이다.

    useCallback

    - 컴포넌트가 리렌더링 되더라도 생성된 함수를 새로 만들지 않고 재사용하고자 할 때 사용하는 하는 hook이다.

    - useCallback은 useEffect 사용법과 굉장히 유사하다.

       두번째 매개변수 자리에 의존성 배열을 받고, 그 안에 값이 변경될 때만 useCallback 안에 함수를 생성하는 로직을 가지고 있다.

     

    // src/App.jsx
    
    import React, {useCallback, useState} from 'react';
    import Button from './components/Button';
    
    const App = () => {
      // App.js가 리렌더링 될때마다 재생성됨
      const [value, setValue] = useState('');
    
      // App.js가 리렌더링 될때마다 재생성됨
      const onChangeHandler = (e) => {
        setValue(e.target.value);
      };
    
      // App.js가 리렌더링 되어도 Button.jsx는 재생성되지 않음
      const onClickHandler = useCallback(() => {
        console.log('hello button!');
      }, []);
    
      return (
        <div>
          <input type='text' value={value} onChange={onChangeHandler} />
          {/* 매번 재생성되는 함수를 props로 넘겨줌 -> Button.js 리렌더링 유발 */}
          <Button onClick={onClickHandler} />
        </div>
      );
    };
    
    export default App;

    useCallback을 적용해주니 리렌더링이 되지 않는것을 볼 수 있다.

     

     

    useMemo

    - useCallback과 똑같은 기능을 하는 hook 이다. 사용방법도 똑같다.

    - 다만, 그 대상이 함수가 아니라 배열이나 객체와 같은 값일 때 사용한다는 점의 차이가 있다.

     

     

    // src/components/List.jsx
    
    import React, {memo} from 'react';
    
    const List = ({data}) => {
      console.log('리렌더링되고 있어요.');
      return (
        <div>
          {data.map((todo) => (
            <div key={todo.id}>{todo.title}</div>
          ))}
        </div>
      );
    };
    
    export default memo(List);

    App.js에서 List.jsx로 data(배열형태)를 받아와 실행하였더니 memo를 사용하였음에도 리렌더링이 일어나고 있다.

    마찬가지로 App.js가 리렌더링 될 때마다 재생성 되고있기 때문이다.

     

     

    // src/App.jsx
    
    import React, { useState } from 'react';
    import { useMemo } from 'react';
    import List from './components/List';
    
    const App = () => {
      const [value, setValue] = useState('');
    
      const onChangeHandler = (e) => {
        setValue(e.target.value);
      };
    
    // useMemo 사용
      const data = useMemo(() => {
        return [
          {
            id: 1,
            title: 'react',
          },
        ]
      }, []);
    
      return (
        <div>
          <input type='text' value={value} onChange={onChangeHandler} />
          <List data={data} />
        </div>
      );
    };
    
    export default App;

    이렇게 useMemo를 적용 해주니,

    리렌더링이 일어나지 않는 것을 볼 수 있다.

     

     

    주의사항

    • 무분별한 사용은 퍼포먼스 성능에 악영향이 될 수도 있다.
    • 반드시 리렌더링이 필요한 컴퍼넌트나 값 에는 최적화 hook을 사용하는 것이 오히려 좋지 않다.
      (의도치 않게 업데이트가 되지 않는 상황이 올 수 있기 때문)
    • 사용 시에는 React에게 전, 후 값을 비교하는 과정 하나가 추가되는 것 이기 때문에 
      굳이 비교하지 않아도 되는 것 들에게도 React가 비교하는 과정을 추가하기 때문에 퍼포먼스 성능에 악영향을 미칠 수 있다.
      라는 것이다.

     

     

    정리

    useCallback의 사용 대상은 함수이고,

    useMemo의 사용 대상은 객체나 배열과 같은 값이다.

    그 외 사용 원리나 방법은 동일하다.

     

    추가로 원시타입 데이터. 예를 들면 setState를 쓰지 않는 변수들은 useMemo를 사용하지 않아도 리렌더링 하지 않는다.

     

    'React' 카테고리의 다른 글

    Custom Hook  (0) 2022.12.21
    Redux 이용해서 TodoApp 만들기  (0) 2022.12.20
    Redux 미들웨어(Thunk)  (0) 2022.12.20
    styled component porps해서 쓰기  (0) 2022.12.20
    Redux(3) - react-router-dom, props children, Dynamic Route  (0) 2022.12.20
Designed by Tistory.