이번 편에서는 useReducer를 알아보겠습니다.

  • useReducer는 State를 다루는 관점과 방법 정도의 차이 정도이고, useState로 작성할 때에 비해 작성해야 하는 코드의 양이 많으므로 어떻게 활용하면 좋을지 감을 잡기 어렵습니다.
  • 이번 편은 이렇게 작성하는 방법이 있다는 정도만 이해해도 충분합니다.

useState

import React, { useState } from 'react'

const useUser = () => {
  const [isAdmin, setBeAdmin] = useState(false)
  const [nickname, setNickname] = useState('')
  const [email, setEmail] = useState('')

  const reset = () => {
    setBeAdmin(false)
    setNickname('')
    setEmail('')
  }
  const toggleToBeAdmin = () => setBeAdmin(!admin)
  const updateNickname = event => {
    const nickname = event.target.value

    setNickname(nickname)
  }
  const updateEmail = event => {
    const email = event.target.value

    setEmail(email)
  }

  return {
    isAdmin,
    nickname,
    email,
    reset,
    toggleToBeAdmin,
    updateNickname,
    updateEmail
  }
}

const User = () => {
  const user = useUser()

  let label = 'user'
  if (user.isAdmin) {
    label = 'admin'
  }

  return (
    <div>
      <label>{label}</label>
      <h1>{user.name}</h1>
      <h3>{user.email}</h3>
      <button onClick={user.reset}>RESET</button>
      <button onClick={user.toggleToBeAdmin}>toggle admin mode</button>
      <input type='text' onChange={user.updateNickname} />
      <input type='text' onChange={user.updateEmail} />
    </div>
  )
}

React는 State를 변경하면 바뀐 부분을 새로 그리기 위해 해당 하는 모든 컴포넌트를 다시 실행합니다.

간단한 프로젝트일 때에는 충분히 잘 작동하지만, 프로젝트의 스케일이 클수록 퍼포먼스가 중요하기 때문에 최적화에 신경을 써야 합니다.

import React, { useState } from 'react'

const useUser = () => {
  const [user, setUser] = useState({
    isAdmin: false,
    nickname: '',
    email: ''
  })

  const reset = () => setUser({
    isAdmin: false,
    nickname: '',
    email: ''
  })
  const toggleToBeAdmin = () => setUser(user => ({ ...user, isAdmin: !user.isAdmin }))
  const updateNickname = event => setUser(user => ({ ...user, nickname: event.target.value }))
  const updateEmail = event => setUser(user => ({ ...user, email: event.target.value }))

  return {
    isAdmin,
    nickname,
    email,
    reset,
    toggleToBeAdmin,
    updateNickname,
    updateEmail
  }
}

const User = () => {
  const user = useUser()

  let label = 'user'
  if (user.isAdmin) {
    label = 'admin'
  }

  return (
    <div>
      <label>{label}</label>
      <h1>{user.name}</h1>
      <h3>{user.email}</h3>
      <button onClick={user.reset}>RESET</button>
      <button onClick={user.toggleToBeAdmin}>toggle admin mode</button>
      <input type='text' onChange={user.updateNickname} />
      <input type='text' onChange={user.updateEmail} />
    </div>
  )
}

React Hooks를 사용한다면 isAdmin, nickname, email와 같은 여러 State를 1개의 State로 줄이는 것이 가장 먼저 시도해볼 수 있는 최적화입니다.

1개의 State로 줄이고 보니 괜히 더 번잡하게 변해버린 느낌이 듭니다. 보다 더 일관적인 방법으로 작성할 수는 없을까요?

useReducer

import React, { useReducer } from 'react'

const initialUserState = {
  isAdmin: false,
  nickname: '',
  email: ''
}

const userReducer = (state, action) => {
  switch (action.type) {
    case 'reset': {
      return initialUserState
    }
    case 'toggleToBeAdmin': {
      return { ...user, isAdmin: !state.isAdmin }
    }
    case 'updateNickname': {
      return { ...user, nickname: action.nickname }
    }
    case 'updateEmail': {
      return { ...user, email: action.email }
    }
    default: {
      throw new Error(`unexpected action.type: ${action.type}`)
    }
  }
}

const User = () => {
  const [user, dispatchUser] = useReducer(userReducer, initialUserState)

  let label = 'user'
  if (user.isAdmin) {
    label = 'admin'
  }

  const reset = () => dispatchUser({ type: 'reset' })
  const toggleToBeAdmin = () => dispatchUser({ type: 'toggleToBeAdmin' })
  const updateNickname = event => dispatchUser({ type: 'updateNickname', nickname: event.target.value })
  const updateEmail = event => dispatchUser({ type: 'updateEmail', email: event.target.value })

  return (
    <div>
      <label>{label}</label>
      <h1>{user.name}</h1>
      <h3>{user.email}</h3>
      <button onClick={reset}>RESET</button>
      <button onClick={toggleToBeAdmin}>toggle admin mode</button>
      <input type='text' onChange={updateNickname} />
      <input type='text' onChange={updateEmail} />
    </div>
  )
}

useRedcer를 이용하면 이렇게 작성할 수 있습니다.

useState와 비슷하게 return 배열의 1번째에는 State가, 2번째에는 State를 변경하는 함수가 있습니다.

useState의 State를 변경하는 함수는 넘긴 값을 그대로 다음 State로 사용하지만, useReducer의 State를 변경하는 함수는 reducer를 거치면서 추가적으로 가공한 State로 사용할 수 있습니다.

살펴보았듯이, 일관적인 구조를 가지고 있기 때문에 코드의 양이 늘어났음에도 useState를 사용하는 것 보다 로직을 파악하기가 쉽고 보다 더 체계적이라는 느낌을 받을 수 있습니다.