useReducer
알아보기이전에 컴포넌트 상태를 업데이트 할 때에는 useState
사용해서 새로운 상태를 설정했다.
useState
말고, useReducer
라는 Hook을 사용해서 상태를 업데이트 할 수 있다.
useState
와 useReducer
의 차이 useState
->설정하고 싶은 다음상태를 직접 지정해주는 방식으로 상태를 업데이트.
setValue(5)
useReducer
-> action
이라는 객체를 기반으로 상태를 업데이트 ( action
: 업데이트할때 참조하는 객체)
-> 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다.
-> 상태 업데이트 로직을 컴포넌트 바깥에 작성 할 수 있다
-> 다른 파일에 작성 후 불러와서 사용 할 수 있다.
dispatch({type : 'INCREMENT'})
-> type
이라는 값을 사용해서 어떤 업데이트를 진행 할 것인지 명시를 할 수 잇음.
dispatch({
type : 'INCREMENT',
diff : 4
})
-> 업데이트 할 때 필요한 참조하고 싶은 다른 값이 있다면, 이 객체 안에 넣을 수도 있다.
reducer
는 상태를 업데이트 하는 함수이다.redecer
는 이렇게 생겼다~!
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
state
(type은 뭐든지 될 수 있음)action
action
type의 이름 직접 설정 가능.{
type: 'INCREMENT',
}
{
type: 'DECREMENT'
}
action
type이 뭐가 들어오냐에 따라 다르게 업데이트 해줌.switch
문을 사용함.
useReducer
사용const [number, dispatch] = useReducer(reducer, 0)
- 첫번째 파라미터 :
reducer
함수를 넣어줌.- 두번째 파라미터 : 기본 값을 넣어줌 (숫자, 문자열, 객체, 배열...)
number
: 현재 상태를 의미.dispatch
: 액션을 발생시키는 함수.
📍
useReducer
사용 전
- 기존에는 useState를 사용해서 구현해줌
import React, {useState} from "react"; function Counter() { // Counter 컴포넌트 만들기 const [number, setNumber] = useState(0); const onIncrease = () => { setNumber(preNumber => preNumber+1); } const onDecrease = () => { setNumber(preNumber => preNumber-1); } return ( <div> <h1>{number}</h1> <button onClick={onIncrease}>+1</button> <button onClick={onDecrease}>-1</button> </div> ) } export default Counter;
📍
useReducer
사용import React, {useReducer} from "react"; // reducer 생성 function reducer(state, action){ switch (action.type){ case 'INCREMENT': return state +1; case 'DECREMENT': return state -1; default: return state; // throw new Error('Unhandels action'); 해도됨. // return state하면, 준비되지 않은 action이 들어와도 별일 안생김. // Error를 발생시킨다면, console에서 에러발생했다는 것을 볼 수 있음. } } function Counter() { // useReducer hook 사용 const [number, dispatch] = useReducer(reducer, 0); // 시작할 때 값이 0이고, reducer함수의 action이 발생함에 따라서 바뀌게 됨. const onIncrease = () => { dispatch({ type : 'INCREMENT' }) } const onDecrease = () => { dispatch({ type : 'DECREMENT' }) } return ( <div> <h1>{number}</h1> <button onClick={onIncrease}>+1</button> <button onClick={onDecrease}>-1</button> </div> ) } export default Counter;
잘 작동한다~!
상태의 업데이트 로직이 컴포넌트 밖에 있는 것을 볼 수 있다.
function reducer(state, action){ switch (action.type){ case 'INCREMENT': return state +1; case 'DECREMENT': return state -1; default: return state; // throw new Error('Unhandels action'); 해도됨. (console에서 에러발생했다는 것을 볼 수 있음) // return state하면 -> 준비되지 않은 action이 들어와도 별일 안생김. } }
useState
사용해서 구현한 코드를 useReducer
사용해서 구현한 코드로 전환하기~!useState
사용해서 구현한 코드
App.js
import React,{useRef, useState, useMemo, useCallback} from 'react'; import CreateUser from './CreateUser'; import UserList from './UserList'; function countActiveUsers(users){ console.log('활성 사용자 수를 세는중...'); return users.filter(user=>user.active).length; } function App(){ // 상태 설정 const [inputs, setInputs] =useState({ username:'', email:'', }); const {username, email} = inputs; const onChange = useCallback(e =>{ const {name, value}=e.target; setInputs({ ...inputs, [name]:value }); },[inputs]); const [users, setUsers] = useState([ { id:1, username: 'gyomni', email: 'hi1@gmail.com', active:true, }, { id:2, username: 'joy', email: 'hi2@gmail.com', active:false, }, { id:3, username: 'zoe', email: 'hi3@gmail.com', active:false, } ]); const nextId = useRef(4); const onCreate = useCallback(()=>{ const user={ id:nextId.current, username, email, }; setInputs({ username:'', email:'' }); nextId.current+=1; },[username,email]); const onRemove = useCallback(id =>{ setUsers(users=>users.filter(user=>user.id !==id)); }, []); const onToggle = useCallback(id =>{ setUsers(users=>users.map( user=>user.id ===id ?{ ...user, active: !user.active} : user )); }, []) const count =useMemo(()=>countActiveUsers(users),[users]); return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle}/> <div>활성 사용자 수 : {count}</div> </> ) } export default App;
useReducer
사용해서 구현한 코드
App.js
1)App
컴포넌트에서 사용할 초기상태를 컴포넌트 바깥에 선언하고 나머지 로직 정리하고 내부Props
설정을 지웠다.import React,{useRef, useState, useMemo, useCallback} from 'react'; import CreateUser from './CreateUser'; import UserList from './UserList'; function countActiveUsers(users){ console.log('활성 사용자 수를 세는중...'); return users.filter(user=>user.active).length; } const initialState = { inputs: { username: '', email: '' }, users: [ { id:1, username: 'gyomni', email: 'hi1@gmail.com', active:true, }, { id:2, username: 'joy', email: 'hi2@gmail.com', active:false, }, { id:3, username: 'zoe', email: 'hi3@gmail.com', active:false, } ] }; function App(){ return ( <> <CreateUser/> <UserList users={[]} /> <div>활성 사용자 수 : 0</div> </> ) } export default App;
2)
useReducer
를 불러 오고reducer
함수 틀 만들기import React,{useRef, useReducer, useMemo, useCallback} from 'react'; . . . function reducer(state, action){ return state; } function App(){ const [state, dispatch] = useReducer(reducer, initialState); return ( <> <CreateUser /> <UserList users={[]} /> <div>활성 사용자 수 : 0</div> </> ) }
3)
const [state, dispatch] = useReducer(reducer, initialState);
-> 위 코드의
state
안에users
랑inputs
이 들어 있다.
여기에 있는 값들을 비구조화할당을 통해서 추출해준 다음에 Props로 컴포넌트에게 전달해주기.function App(){ const [state, dispatch] = useReducer(reducer, initialState); const {users} = state; const {username, email} = state.inputs; return ( <> <CreateUser username={username} email={email} /> <UserList users={users} /> <div>활성 사용자 수 : 0</div> </> ) }
->
users
,username
,
4)
onChange
구현하고CreateUser
컴포넌트에게 전달.const onChange = useCallback( e => { const {name, value} = e.target; dispatch({ type: 'CHANGE_INPUT', name, value }) },[]);
return ( <> <CreateUser username={username} email={email} onChange={onChange} /> <UserList users={users} /> <div>활성 사용자 수 : 0</div> </> ) }
4) - 1
reducer
구현
action.type
이 뭐냐에 따라 다른 작업 해준다.- 만약에
action.type
이CHANGE_INPUT
이라면 현재 자신이 지니고 있는 상태에서inputs
안에 있는 특정 값을 바꿔주도록 구현.function reducer(state, action) { switch (action.type) { case 'CHANGE_INPUT': return { ...state, inputs: { ...state.inputs, [action.name]: action.value } }; default: return state; } }
->
CHANGE_INPUT
이라는 액션 객체를 사용하여inputs
상태를 업데이트 해주기.
->reducer
함수에서 새로운 상태를 만들 때는 불변성을 지켜주어야 하기 때문에spread
연산자 사용.
->default
값 넣어줘야함 .
(throw new Error('Unhandels action')
넣어주면console
창에 에러발생했다는 것을 볼 수 있음.)
5)
onCreate
구현const onCreate = useCallback(() => { dispatch({ type: 'CREATE_USER', user: { id: nextId.current, username, email } }); nextId.current += 1; }, [username, email]);
->
useCallback
사용const nextId = useRef(4); // 기존에 3개 등록 되있어서.
->
id
값은useRef
로 관리해줘야함.return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} /> <div>활성 사용자 수 : 0</div> </> ) }
5) - 1
reducer
구현function reducer(state, action) { switch (action.type) { case 'CHANGE_INPUT': return { ...state, inputs: { ...state.inputs, [action.name]: action.value } }; case 'CREATE_USER': return { inputs: initialState.inputs, users: state.users.concat(action.user) }; default: return state; } }
-> 기존에
useState
사용했을 때는inputs
를 날리는 작업 따로하고,users
배열 업데이트 하는 작업 따로 하고 했었는데, 이번에는CREATE_USER
라는 액션이 발생하면 두가지 작업 동시에 할 수 있다.
6)
onToggle
구현const onToggle = useCallback(id => { dispatch({ type: 'TOGGLE_USER', id }); }, []);
->
[]
비어있음 : 함수 한번 만들고 재사용 가능.return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onToggle={onToggle}/> <div>활성 사용자 수 : 0</div> </> ) }
6) - 1
reducer
구현function reducer(state, action) { switch (action.type) { case 'CHANGE_INPUT': return { ...state, inputs: { ...state.inputs, [action.name]: action.value } }; case 'CREATE_USER': return { inputs: initialState.inputs, users: state.users.concat(action.user) }; case 'TOGGLE_USER': return { ...state, users: state.users.map(user => user.id === action.id ? { ...user, active: !user.active } : user ) }; }
7)
onRemove
구현const onRemove = useCallback(id => { dispatch({ type: 'REMOVE_USER', id }); }, []);
->
[]
비어있음 : 함수 한번 만들고 재사용 가능.return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onToggle={onToggle} onRemove={onRemove}/> <div>활성 사용자 수 : 0</div> </> ) }
7) - 1
reducer
구현function reducer(state, action) { switch (action.type) { case 'CHANGE_INPUT': return { ...state, inputs: { ...state.inputs, [action.name]: action.value } }; case 'CREATE_USER': return { inputs: initialState.inputs, users: state.users.concat(action.user) }; case 'TOGGLE_USER': return { ...state, users: state.users.map(user => user.id === action.id ? { ...user, active: !user.active } : user ) }; case 'REMOVE_USER': return { ...state, users: state.users.filter(user => user.id !== action.id) }; default: return state; } }
8) 활성 사용자수 구하기
useMemo
사용const count = useMemo(() => countActiveUsers(users), [users]); return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onToggle={onToggle} onRemove={onRemove}/> <div>활성 사용자 수 : {count}</div> </> ) }
-> 위에 만들어 놨던
countActiveUsers
사용
-> 활성 사용자수 0 에서{count}
로 변경.
useState
VS. useReducer
언제 useState
를 써야하고 언제 useReducer
를 써야할까?
-> 정해진 답은 없다 !
예를 들어, 컴포넌트에서 관리하는 값이 하나고, 그 값이 Number
or String
or boolean
값이라면 확실하게 useState
로 관리하는 것이 편할 것이다.
그러나, 컴포넌트에서 관리하는 값이 여러개가 되어서 상태의 값이 복잡해진다면 useReducer
가 좀더 편할 수도 있다.
이것은 자주 사용해보고 마음에 드는 방식 사용하기!
간단한거다 싶으면 useState
, 복잡하다 싶으면 useReducer
사용해보기 !
학습 : 벨로퍼트와 함께 하는 모던 리엑트