React에서 중복 재렌더링

목차


  • When components re-render?

  • How to prevent re-render
  • useCallback and useMemo
  • React.memo
  • Move state down
  • Move content up
  • HOC for loading
  • Uncontrolled input

  • Wrap up



  • React 애플리케이션에서 재렌더링의 양을 최적화하기 전에 문제가 있는지 프로파일링하고 찾아보십시오.

    성능에 영향을 미치는 과도한 재렌더링 문제를 프로파일링하고 확인한 경우 최적화를 시도할 수 있습니다.

    다음으로 React 구성 요소가 다시 렌더링되는 경우와 이를 방지하는 방법을 살펴보겠습니다.

    구성 요소가 다시 렌더링될 때?

    Look at the example below

    const Child = () => <p>some text</p>
    
    const Parent = () => { 
      const [count, setCount] = useState(0)
    
      return (
        <>
          <button onClick={() => setCount(c => c + 1)}>
            {count}
          </button>
          <Child />
        </>
      )
    }
    

    Will Child re-render on the button click? Spoiler: yes

    React re-renders all the component's tree by default if the state or props changes. That's why in the example above Child will re-render.

    The thing is that Child 's re-render is not a problem as long as the render is lightweight. Often it is better for performance to re-render, than use optimizations like useMemo , useCallback , React.memo , etc.

    useCallback 및 useMemo

    Don't use these hooks by default. It is crucial to get used to the fact that it is a bad practice to put useCallback and useMemo hooks everywhere.

    useCallback and useMemo are just functions with their own abstractions. On every render, these functions are called, and a callback that is passed as an argument is created again.

    However, if child components are not wrapped in React.memo , there will be no effect and everything will re-render.

    You can use useCallback or useMemo in these cases:

    • Child components are wrapped in memo and you don't want them to re-render
    • You have heavy calculations that you don't want to recalculate on each render

    반응.메모

    This hoc is useful when you have a deep component tree or some heavy calculations on render

    const Component = (props) => {
      return (
        // deep tree of components 
      )
    }
    
    export const MemoizedComponent = React.memo(Component)
    

    Component wrapped in memo would re-render only if props, internal state, or context will change.

    It is important to remember that React.memo uses shallow equal of props by default. That's why you should memoize non-primitive values like objects, arrays, functions, etc. in the parent component.

    상태를 아래로 이동

    Let's imagine you have this component:

    const Parent = () => {
      const [count, setCount] = useState(0)
    
      return (
        <>
          <ComponentA onChange={setCount} count={count} />
          <ComponentB count={count} />
          <ComponentC />
        </>
      )
    }
    

    Component A and component B are using count state, but component C doesn't need it. If we let it be as it is, component C will re-render each time the count changes if it is not wrapped in React.memo .

    We can move count state down by creating another component like this:

    const Union = () => {
      const [count, setCount] = useState(0)
    
      return (
        <>
          <ComponentA onChange={setCount} count={count} />
          <ComponentB count={count} />
        </>
      )
    } 
    
    const Parent = () => {
      return (
        <>
          <Union />
          <ComponentC />
        </>
      )
    }
    

    Now component C does not depend on count state and will not re-render when the count changes.

    콘텐츠 위로 이동

    Let's complicate the previous example

    const Parent = () => {
      const [count, setCount] = useState(0)
    
      return (
        <ComponentA onChange={setCount} count={count}>
          <ComponentB count={count} />
          <ComponentC />
        </ComponentA>
      )
    }
    

    Now component A wraps all the content and we can't move the state down. In that case, we can move component A and component B up.

    const Union = ({ children }) => {
      const [count, setCount] = useState(0)
    
      return (
        <ComponentA onChange={setCount} count={count}>
          <ComponentB count={count} />
          {children}
        </ComponentA>
      )
    }
    

    After we moved component A, component B, and their state up, we can pass component C as a child in Union component.

    const Parent = () => {
      return (
        <Union>
          <ComponentC />
        </Union>
      )
    }
    

    Now again, if count state changes, component C will not re-render.

    로드를 위한 HOC

    Sometimes we want to handle some loading state inside a component.

    For example:

    const Parent = ({ isLoading }) => {
      const someCb = useCallback(...)
      const someCalculatedValue = useMemo(...)
    
      useEffect(...)
    
      if (isLoading) {
        return <Loader />
      }
    
      return ...
    }
    

    Here we see a lot of hooks, that we don't need while data is loading. It is redundant to create a callback, calculate values, or fire effects while isLoading is false.

    If you try to move isLoading condition upwards, when isLoading changes from true to false, the number of hooks on render will change, and React will fire an error.

    To avoid errors and redundant hooks we can move this condition in HOC.

    const WithLoading = (Component) => {
      return ({ isLoading, ...props }) => {
        if (isLoading) {
          return <Loader />
        }
    
        return <Component {...props} />
      }
    }
    

    Then we can use WithLoading HOC like this.

    const Parent = () => { ... }
    
    export const WithLoadingParent = WithLoading(Parent)
    

    통제되지 않은 입력

    Quite often you can see controlled input in React apps. Many developers use this pattern by default even if there is no reason to use it.

    If you don't need to validate input value or run any other logic on the fly, you can just use native inputs.

    const Parent = () => {
      const inputRef = useRef()
    
      const handleSearch = () => {
        console.log(inputRef.current.value)
      }
    
      return (
        <>
          <input ref={inputRef} type="text" />
          <button onClick={handleSearch}>Search</button>
        </>
      )
    }
    

    Thereby component will not re-render when the input's value changes, but we can easily get the value when we need it.

    마무리

    The methods described above are only applicable if there are problems with heavy re-renders that you want to avoid.

    It is very important to remember that optimizations are often not free. You should profile first and only then try to solve the problems.

    좋은 웹페이지 즐겨찾기