JotaiJotai

상태
React를 위한 기본적이고 유연한 상태 관리

Performance

성능 최적화: 불필요한 리렌더링 줄이기

리액트 애플리케이션의 성능을 개선하려면 불필요한 리렌더링을 최소화하는 것이 중요합니다. 리렌더링은 컴포넌트의 상태나 속성이 변경될 때 발생하며, 이 과정에서 불필요한 연산이 반복되면 애플리케이션의 성능이 저하될 수 있습니다.

리렌더링이 발생하는 주요 원인

  1. 상태 변경: 컴포넌트의 상태가 업데이트되면 해당 컴포넌트와 그 자식 컴포넌트들이 리렌더링됩니다.
  2. 속성 변경: 부모 컴포넌트에서 전달된 속성이 변경되면 자식 컴포넌트가 리렌더링됩니다.
  3. 컨텍스트 변경: 컨텍스트 값이 변경되면 해당 컨텍스트를 사용하는 모든 컴포넌트가 리렌더링됩니다.

불필요한 리렌더링을 줄이는 방법

  1. React.memo 사용: React.memo는 컴포넌트를 메모이제이션하여 속성이 변경되지 않으면 리렌더링을 방지합니다.

    const MyComponent = React.memo(function MyComponent(props) {
    /* 렌더링 로직 */
    });
  2. useCallback과 useMemo 활용: useCallback은 함수를 메모이제이션하고, useMemo는 값을 메모이제이션합니다. 이를 통해 불필요한 함수 생성과 계산을 방지할 수 있습니다.

    const memoizedCallback = useCallback(() => {
    doSomething(a, b);
    }, [a, b]);
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  3. 컨텍스트 분할: 컨텍스트를 여러 개로 나누어 변경이 잦은 값과 그렇지 않은 값을 분리하면, 불필요한 리렌더링을 줄일 수 있습니다.

  4. shouldComponentUpdate 또는 PureComponent: 클래스 컴포넌트에서는 shouldComponentUpdate를 구현하거나 PureComponent를 사용하여 리렌더링을 제어할 수 있습니다.

성능 측정 도구 활용

리액트 개발자 도구의 Profiler를 사용하면 컴포넌트의 렌더링 시간을 측정하고, 어떤 컴포넌트가 리렌더링되는지 확인할 수 있습니다. 이를 통해 성능 병목 현상을 찾아내고 최적화할 수 있습니다.

결론

불필요한 리렌더링을 줄이는 것은 리액트 애플리케이션의 성능을 개선하는 핵심 전략입니다. React.memo, useCallback, useMemo와 같은 훅을 적절히 활용하고, 컨텍스트를 분할하여 성능을 최적화하세요. 또한, 리액트 개발자 도구를 활용해 성능 문제를 진단하고 해결하는 것이 중요합니다.

참고: 이 가이드는 개선의 여지가 있습니다. 현재는 참고용으로 생각해 주세요.

Jotai와 React는 앱 생명주기에서 발생하는 리렌더링을 관리하기 위한 여러 도구를 제공합니다.
먼저, 렌더링과 커밋의 차이에 대해 읽어보세요. 이는 앞으로 진행하기 전에 꼭 이해해야 할 중요한 개념입니다.

저렴한 렌더링

코어 섹션에서 살펴본 것처럼, React 18의 기본 동작(그리고 전반적으로 좋은 관행) 때문에 여러분은 컴포넌트 함수가 멱등성을 가지도록 해야 합니다. 이 함수들은 렌더링 단계에서 여러 번 호출될 수 있으며, 심지어 마운트 시점에도 호출될 수 있습니다. 따라서 우리는 어떤 경우에도 렌더링 비용을 최소화해야 합니다!

무거운 계산 처리

무거운 계산은 항상 React 생명주기 외부에서 처리해야 합니다. 예를 들어, 액션 내부에서 처리하는 것이 좋습니다.

하지 말아야 할 것:

// 각 항목에 대해 무거운 계산 수행
const selector = (s) => s.filter(heavyComputation)
const Profile = () => {
const [computed] = useAtom(selectAtom(friendsAtom, selector))
}

해야 할 것:

const friendsAtom = atom([])
const fetchFriendsAtom = atom(null, async (get, set, payload) => {
// 모든 친구 데이터 가져오기
const res = await fetch('https://...')
// 한 번만 무거운 계산 수행
const computed = res.filter(heavyComputation)
set(friendsAtom, computed)
})
// 컴포넌트 내에서 사용
const Profile = () => {
const [friends] = useAtom(friendsAtom)
}

작은 컴포넌트

관찰된 아톰은 업데이트가 필요한 애플리케이션의 작은 부분만 리렌더링해야 합니다. React가 비교해야 할 작업이 적을수록 렌더링 시간이 짧아집니다.

하지 말아야 할 예제:

const Profile = () => {
const [name] = useAtom(nameAtom)
const [age] = useAtom(ageAtom)
return (
<>
<div>{name}</div>
<div>{age}</div>
</>
)
}

해야 할 예제:

const NameComponent = () => {
const [name] = useAtom(nameAtom)
return <div>{name}</div>
}
const AgeComponent = () => {
const [age] = useAtom(ageAtom)
return <div>{age}</div>
}
const Profile = () => {
return (
<>
<NameComponent />
<AgeComponent />
</>
)
}

필요할 때만 렌더링하기

일반적으로 성능 오버헤드는 앱의 일부가 필요 이상으로 리렌더링될 때 발생합니다.

React가 컴포넌트를 언제 렌더링해야 하는지 다루기 위해 몇 가지 도구가 있습니다. useMemouseCallback 사용법을 아직 모른다면, 공식 React 문서를 참고해 보세요. 이 도구들은 앱이 원활하지 않을 때 불필요한 렌더링을 줄이는 데 매우 유용합니다.

Jotai도 아톰이 언제 리렌더링을 트리거해야 하는지 다루는 도구를 제공합니다.

  • 기본적으로 Jotai는 데이터를 아톰 단위로 분할하도록 권장합니다. 이렇게 하면 각 아톰이 별도로 저장되고, 자신의 값이 변경될 때만 리렌더링이 발생합니다.
  • selectAtom은 큰 객체의 특정 부분만 구독하고, 해당 부분의 값이 변경될 때만 리렌더링하도록 합니다.
  • focusAtomselectAtom과 비슷하지만, 새로운 아톰을 생성하여 특정 부분을 쉽게 업데이트할 수 있는 세터를 제공합니다.
  • splitAtom은 동적 리스트에 대해 selectAtom/focusAtom의 역할을 수행합니다.

이 방법은 단순해 보이지만, 이해하기 쉽습니다. 이게 목표였죠. 단순하게 유지해서 빠르게 유지합시다.

자주 혹은 드물게 업데이트되는 경우

여러분의 아톰이 주로 자주 업데이트되는지 아니면 드물게 업데이트되는지 스스로 질문해 보세요. 예를 들어, 거의 매초마다 변경되는 객체를 포함하는 아톰을 상상해 보세요. 이런 경우 focusAtom을 사용해 특정 속성에 집중하는 것이 최선의 선택이 아닐 수 있습니다. 왜냐하면 어차피 모든 속성이 동시에 리렌더링되기 때문에, 오버헤드를 추가하지 않고 추가 아톰을 생성하지 않는 것이 더 나을 수 있습니다.

반대로, 객체의 속성이 드물게 변경되고, 특히 다른 속성과 독립적으로 변경된다면, 불필요한 리렌더링을 방지하기 위해 focusAtom이나 selectAtom을 사용하는 것이 좋을 수 있습니다.