오늘은 일회성 초기화에 관해서 글을 써보려합니다.
예제
// ✅ 정적 인스턴스가 한번만 생성됩니다.
const resource = new Resource()
const Component = () => (
<ResourceProvider resource={resource}>
<App />
</ResourceProvider>
)
Redux나 React Query를 사용하시는 분들은 앱 수명당 한번만 초기화 되는 변수가 필요합니다.
보통 컴포넌트 외부에서 인스턴스를 초기화합니다.
위 코드의 const resource는 자바스크립트 번들이 evaluated(평가)될 때 한번만 생성되고 ResourceProvider를 통해서 App컴포넌트에서 사용가능합니다.
하지만 컴포넌트를 여러번 렌더링해서 렌더링마다 자체 resource가 필요한 경우에는 어떡할까요?
resource를 컴포넌트 안에 입력하는 방법을 사용할 수도 있습니다.
const Component = () => {
// 🚨 주의하세요: 새로운 인스턴스가 매 렌더링마다 생성됩니다.
const resource = new Resource()
return (
<ResourceProvider resource={new Resource()}>
<App />
</ResourceProvider>
)
}
위 코드가 별로인 점은 resource가 렌더링을 할때마다 새로 생성된다는 것 입니다. 만약에 한번만 컴포넌트가 한번만 렌더링 됬을 때 운이 좋다면 실행이 될 수도 있지만 운에 의존해서는 안되고 리렌더링에 대비해야합니다.
그럼 위의 코드를 어떤 식으로 리팩토링 할까요? 종속성(dependency)이 변경될 때만 수행하는 useMemo를 사용해보겠습니다. useMemo에 종속성을 안주면 문제가 해결되지 않을까요?
const Component = () => {
// 🚨 아직도 안정화 되지 못했습니다..
const resource = React.useMemo(() => new Resource(), [])
return (
<ResourceProvider resource={resource}>
<App />
</ResourceProvider>
)
}
방금 예제처럼 운이 좋게 실행될 수는 있지만 정답은 아니었습니다. React 공식문서의 useMemo 설명을 확인해보겠습니다.
useMemo는 성능 최적화를 위해 사용할 수는 있지만 의미상으로 보장이 있다고 생각하지는 마세요.
가까운 미래에 React에서는, 이전 메모이제이션된 값들의 일부를 “잊어버리고” 다음 렌더링 시에 그것들을 재계산하는 방향을 택할지도 모르겠습니다. 예를 들면, 오프스크린 컴포넌트의 메모리를 해제하는 등이 있을 수 있습니다.
useMemo를 사용하지 않고도 동작할 수 있도록 코드를 작성하고 그것을 추가하여 성능을 최적화하세요.
공식문서에서는 useMemo를 사용하기 전에 먼저 코드를 작동하게 해야하며 최적화를 수행할때 사용하라고 권장합니다.
우리의 목적은 성능 개선이 아니었기 때문에 다소 어긋난 것을 알 수 있습니다.
State로 해결 가능합니다.
state는 setter가 호출될때만 업데이트를 보장해줍니다. useState에서 setter를 호출하지 않은 형태로 코드를 작성하면
저번 글에서 익혔던 lazy initializer와 같이 활용하여 resource 생성자(constructor)가 한번만 호출되도록 할 수 있습니다.
const Component = () => {
// ✅ 완전히 안정적인 코드가 되었습니다!
const [resource] = useState(() => new Resource())
return (
<ResourceProvider resource={resource}>
<App />
</ResourceProvider>
)
}
위 코드의 트릭을 이용하면 resource가 컴포넌트 생명주기(lifecycle) 당 한번만 생성되게 할 수 있습니다.
refs 는 어떨까요?
useRef을 사용하면 리액트의 규칙을 깨트리지 않고 렌더링 기능의 순도도 떨어뜨리지 않습니다.
const Component = () => {
// ✅ 제대로 작동하지만, 이렇게 까지..
const resource = useRef(null)
if (!resource.current) {
resource.current = new Resource()
}
return (
<ResourceProvider resource={resource.current}>
<App />
</ResourceProvider>
)
}
다만 useState를 사용하는 방식보다 복잡해지고 useRef을 쓰는 순간 타입스크립트에서 resouce.current는 null일수도 있기 때문입니다. 타입 선언을 제대로 해주어야하죠. 또한 DOM에 직접적인 영향이 가기 때문에 useRef 사용을 개인적으로 지양하는 분도 계실겁니다.
Reference:
https://tkdodo.eu/blog/use-state-for-one-time-initializations
useState for one-time initializations
Why you shouldn't rely on useMemo for guaranteed referential stability but prefer useState instead
tkdodo.eu
'Development > React' 카테고리의 다른 글
Next.js) msw로 백엔드 Mocking 해보기 (0) | 2023.01.12 |
---|---|
Next.js)@testing-library로 컴포넌트 테스팅하기 (0) | 2023.01.11 |
React) useState에서 놓치기 쉬운 사실들 (2) (0) | 2022.12.26 |
React) useState에서 놓치기 쉬운 사실들 (1) (0) | 2022.12.23 |
Next.js 구글 로그인 구현 (0) | 2022.12.09 |