본문 바로가기

FrontEnd

useState와 useReducer의 사용사례

반응형

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

 

useState vs useReducer

To useState or useReducer, that is the question...

tkdodo.eu

https://www.steveruiz.me/posts/zoom-ui

 

Steve Ruiz

Reordering Part 2: Tables and Fractional Indexing Implementing reordering commands (Send to Back, Send Backward, Bring Forward, and Bring to Front) in using fractional indexing. Monday, 14 February 2022

www.steveruiz.me

React docs

 

Hooks API Reference – React

A JavaScript library for building user interfaces

reactjs.org

 

반응형