React 강좌) trendy React 2-5. useReducer 알아보기

Danuel·2019년 4월 18일
7

trendy React

목록 보기
15/21
post-thumbnail

이번 편에서는 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 { ...state, isAdmin: !state.isAdmin }
    }
    case 'updateNickname': {
      return { ...state, nickname: action.nickname }
    }
    case 'updateEmail': {
      return { ...state, 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>
  )
}

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

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

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

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

profile
다뉴하는 코딩

14개의 댓글

comment-user-thumbnail
2019년 4월 18일

좋아요와 댓글 감사합니다.
오탈자, 질문 등은 언제든지 댓글로 달아주세요!

답글 달기
comment-user-thumbnail
2019년 4월 19일

좋은 글 감사합니다.

1개의 답글
comment-user-thumbnail
2019년 4월 24일

글보면서 큰 도움이 되고있습니다. 감사합니다.

1개의 답글
comment-user-thumbnail
2019년 5월 29일

userReducer 의 예제의 object를 리턴해주는 부분에서 사용한 "...user" 는 어디서 가져오는건지 알수있을까요?

1개의 답글
comment-user-thumbnail
2019년 10월 8일

좋은 글 감사합니다.

간단히 테스트 페이지 만들었습니다.

두번째 예시
https://codesandbox.io/s/wizardly-roentgen-yyzeg

세번째 예시
https://codesandbox.io/s/lucid-pine-u4y7w

자잘한 버그는 수정했습니다.

답글 달기
comment-user-thumbnail
2019년 10월 20일

첫번째 예제 13 라인의 setBeAdmin(!admin)setBeAdmin(!isAdmin) 으로 수정되어야 할 것 같네요.

답글 달기
comment-user-thumbnail
2019년 10월 20일

두번째 예제에는 updateNickname, updateEmail에 에러가 있네요.
근데 첫번째 예제에 비해 두번째 예제가 갖는 장점이 뭔지 와닿지 않네요. 테스트를 해보니 두 예제 모두 어떤 경우에도 User component 는 한번만 실행되는데요. 코드에서 정확히 어떤 부분이 최적화 된다는 것인지 설명해 주실 수 있나요?

1개의 답글
comment-user-thumbnail
2021년 3월 8일

코드가 너무 읽기 좋게 작성되어 있어서 이해하기 쉬웠습니다 다른 포스트도 쭉 한 번 읽어봐야겠네요 감사합니다!

답글 달기
comment-user-thumbnail
2021년 3월 31일

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

1개의 답글