반응형
TL;DR:
useState : 간단한 상태. 상태가 같이 업데이트 되야 하는지를 고려하여 묶고 분리한다.
useReducer : 서버 상태와 섞여있는 경우, 로직 구현 시 상태 간 종속성이 필요한 경우. 복잡한 로직.
어떤 상태가 함께 속하는지(업데이트 되어야 하는지) 추론할 수 있는 방식으로 코드를 구조화하면
성능 문제와 상관없이 장기적으로 가독성과 유지 보수에 도움이 됩니다.
별도의 useState
상태가 독립적으로 업데이트되는 경우
ex) 성과 이름
// asis
state = {
firstName: '',
lastName: '',
}
// tobe
const [firstName, setFirstName] = React.useState('')
const [lastName, setLastName] = React.useState('')
단일 useState 객체
항상 함께 업데이트되는 상태 or 한 번에 하나의 필드만 업데이트
ex) x,y 좌표
const [{ x, y }, setCoordinates] = React.useState({ x: 0, y: 0 })
ex) form 상태
const useForm = <State extends Record<string, unknown>>(
initialState: State
) => {
const [values, setValues] = React.useState(initialState)
const update = <Key extends keyof State>(name: Key, value: State[Key]) =>
setValues((form) => ({ ...form, [name]: value }))
return [values, update] as const
}
useReducer
사용자 상호 작용이 상태 객체의 다른 부분을 업데이트하는 경우, 상태의 필드 간 로직 종속성이 존재하는 경우
이벤트로 모델링!
토글 구현에 좋음
const [value, toggleValue] = React.useReducer(previous => !previous, true)
<button onClick={toggleValue}>Toggle</button>
리듀서 사용 팁
- 불변 상태 사용하기. (immer)
- 리듀서 내에 사이드 이펙트 집어넣지 않기. (미들웨어)
- 액션을 이벤트로 모델링하기
✅ 이벤트로 모델링한 리듀서 - 해당 컴포넌트의 로직을 해당 함수로 이동
const reducer = (state, action) => {
// ✅ ui only dispatches events, logic is in the reducer
switch (action) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
}
}
function App() {
const [count, dispatch] = React.useReducer(reducer, 0)
return (
<div>
Count: {count}
<button onClick={() => dispatch('increment')}>Increment</button>
<button onClick={() => dispatch('decrement')}>Decrement</button>
</div>
)
}
🚨 새 값만 받아들이는 바보 리듀서 - 로직이 UI 에 존재
const reducer = (state, action) => {
switch (action.type) {
// 🚨 dumb reducer that doesn't do anything, logic is in the ui
case 'set':
return action.value
}
}
function App() {
const [count, dispatch] = React.useReducer(reducer, 0)
return (
<div>
Count: {count}
<button onClick={() => dispatch({ type: 'set', value: count + 1 })}>
Increment
</button>
<button onClick={() => dispatch({ type: 'set', value: count - 1 })}>
Decrement
</button>
</div>
)
}
리듀서에 프롭 전달하기
고차 함수를 이용해 항상 최신 상태를 전달받을 수 있음.
초기값이 서버 값을 통해 설정되어야 한다면, initializer는 동작하지 않음.
Currying 통해 서버와 클라이언트 상태 분리
// data 서버상태
// state, action 클라이언트 상태
// 서버와 클라이언트 상태 분리 good
const reducer = (data) => (state, action) => {
// ✅ you'll always have access to the latest
// server state in here
}
function App() {
const { data } = useQuery(key, queryFn)
const [state, dispatch] = React.useReducer(reducer(data))
}
엔드포인트에서 amount 매개변수를 가져오는 이벤트 기반 카운터 예제 확장
const reducer = (amount) => (state, action) => {
switch (action) {
case 'increment':
return state + amount
case 'decrement':
return state - amount
}
}
const useCounterState = () => {
const { data } = useQuery(['amount'], fetchAmount)
return React.useReducer(reducer(data ?? 1), 0)
}
function App() {
const [count, dispatch] = useCounterState()
return (
<div>
Count: {count}
<button onClick={() => dispatch('increment')}>Increment</button>
<button onClick={() => dispatch('decrement')}>Decrement</button>
</div>
)
}
참고 :
https://tkdodo.eu/blog/use-state-vs-use-reducer
https://www.steveruiz.me/posts/zoom-ui
반응형
'FrontEnd' 카테고리의 다른 글
useState에 대해 좀 더 잘 알아보기 (0) | 2022.06.17 |
---|---|
useState의 함정 : useEffect와 사용 시 주의할 점. (0) | 2022.06.16 |
React에서 마진 대신 Spacer 컴포넌트 활용하기 (0) | 2022.06.15 |
Styled-component와 Tailwind 함께 사용하기 with Twin Macro (0) | 2022.06.14 |
React Query에는 Debounce와 Throttling이 없다? (0) | 2022.06.13 |