[React]How to optimize context value (번역)
원글: https://kentcdodds.com/blog/how-to-optimize-your-context-value — Kent C. Dodds
주의: 시작하기 전 알아둘것. 아래 조건에 해당하는 사항이 여러 개 있는 경우 컨텍스트 값 최적화를 효과적으로 도입 할 수 있습니다.
1. 컨텍스트 값이 자주 바뀔 경우
2. 컨텍스트 값을 사용하는 소비자 컴포넌트가 많을 경우
3. `React.memo` 의 느린 성능에 지친 경우
4. 당신이 프로그램을 실행 했을때 느리다고 생각하여, 최적화가 필요한 경우만약 위 설명 중 해당사항이 있으면, 계속 읽으세요 (끝까지 읽고 더 나은 대안을 놓치지 마세요!).
정말로 대안은 확실히 좋을 겁니다. 심지어 나는 원래 작성했던 블로그 포스트를 삭제하고 다른 더 나은 방법을 알려주기 위해 이 게시물을 다시 작성했습니다. 기존에 작성했던 포스트는 여기서
진지하게, 만약 당신 코드가 느리다고 생각해서 이짓을 하려고 하는 거면 굳이 안해도 되요. 사실 리액트는 정말 빠르고, 성능이 충분할 때 성능이라는 명목하에 복잡성을 늘리는건 오히려 복잡성을 늘릴 뿐이니깐요.
컨텍스트 값을 최적화 할 수 있는 가장 심플한 방법은 상태 관리를 위해 useReducer
혹은 useState
를 사용하는 것이에요. 그리고 state
를 하나의 특정 함수에 넣고 dispatch
를 다른 함수에 넣으세요. ( 아래 코드의 예시에서 state 는 useCountState 에서, dispatch 는 useCountUpdater 에서 불러옵니다.)
import React from 'react'
import ReactDOM from 'react-dom'
import {CountProvider, useCountState, useCountUpdater} from './count-context'
function useRenderCounter() {
const ref = React.useRef()
React.useEffect(() => {
ref.current.textContent = Number(ref.current.textContent || '0') + 1
})
return (
<span
style={{
backgroundColor: '#ccc',
borderRadius: 4,
padding: '2px 4px',
fontSize: '0.8rem',
margin: '0 6px',
display: 'inline-block',
}}
ref={ref}
/>
)
}
const CountDisplay = React.memo(function CountDisplay() {
const count = useCountState()
const renderCount = useRenderCounter()
return (
<div style={{border: '1px solid black', padding: 10}}>
{renderCount}
{`The current count is ${count}. `}
</div>
)
})
const Counter = React.memo(function Counter() {
const increment = useCountUpdater()
const renderCount = useRenderCounter()
return (
<div style={{border: '1px solid black', padding: 10}}>
{renderCount}
<button onClick={increment}>Increment count</button>
</div>
)
})
function App() {
const [, forceUpdate] = React.useState()
const renderCount = useRenderCounter()
return (
<div style={{border: '1px solid black', padding: 10}}>
{renderCount}
<button onClick={() => forceUpdate({})}>force render</button>
<CountProvider>
<CountDisplay />
<Counter />
</CountProvider>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
사실 위 경우에는 useMemo
를 사용할 필요가 없고, 단지 업데이터 컨텍스트(useCountUpdater) 만을 사용해서 리랜더링을 방지 할 수 있습니다.
<Counter />
컴포넌트의 컨텍스트가 업데이트 되지 않아서 해당 컴포넌트의 리랜더링을 막을 수 있다는 것을 제외하곤, 이 방법은 기존의 useMemo
를 활용한 해결책과 같습니다. (Counter 컴포넌트에서 useCountState 를 통해 context value 를 구독하고 있지 않기 때문에 dispatch 가 발생해 useCountState 의 value 가 변해도 Counter 컴포넌트는 리랜더링 되지 않습니다.)
저는 개인적으로 이 방법이 대부분의 상황에 있어서 오히려 더 복잡하게 만든다고 생각하기 때문에 모든 상황에서 굳이 이 방법을 고수하진 않을겁니다. 하지만, 만약에 위에서 언급한 모든 문제를 가지고 있으면 이 방법을 도입해 해결해보는것도 좋은 시도일겁니다.
State 와 dispatch 를 분리하는 건 귀찮다.
몇몇 사람들은 State 와 dispatch 로 분리하는 것은 귀찮은 과정이라고 생각합니다.
const state = useCountState()
const dispatch = useCountDispatch()
그들은 그냥 이렇게 하면 안돼? 라고 묻습니다.
const [state, dispatch] = useCount()
물론 그렇게 해도 됩니다!!
function useCount() {
return [useCountState(), useCountDispatch()]
}
하지만 기억해두세요. 이러한 구조를 사용할 때, state 와 dispatch 둘 중 하나만 필요할 경우 성능상 이점을 잃게될 수 있습니다.
그리고 리액트 컨텍스트를 효과적으로 사용하는 방법 도 꼭 읽어보세요.