이번 편에서는 useReducer를 알아보겠습니다.
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로 줄이고 보니 괜히 더 번잡하게 변해버린 느낌이 듭니다. 보다 더 일관적인 방법으로 작성할 수는 없을까요?
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를 사용하는 것 보다 로직을 파악하기가 쉽고 보다 더 체계적이라는 느낌을 받을 수 있습니다.
좋은 글 감사합니다.
간단히 테스트 페이지 만들었습니다.
두번째 예시
https://codesandbox.io/s/wizardly-roentgen-yyzeg
세번째 예시
https://codesandbox.io/s/lucid-pine-u4y7w
자잘한 버그는 수정했습니다.
좋아요와 댓글 감사합니다.
오탈자, 질문 등은 언제든지 댓글로 달아주세요!