Study #19 까지의 상태관리는 어떻게 해왔을까?
- 주요 상태 업데이트 로직을 App 컴포넌트 내부에서 이루어왔다
- 상태를 업데이트 할 때, useState를 사용해서 새로운 상태를 설정했다.
💡useReduder?
- useState를 사용하여 상태를 관리하는 것이 아닌 또다른 상태관리 방법
- 이 Hook 함수를 사용하면 컴포넌트 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다.
- 상태 업데이트 로직을 컴포넌트 바깥에 작성할 수도 있다.
- 심지어 다른 파일에 상테 업데이트 로직을 작성한 후, 사용할 수 있다.
💡reducer?
- 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수
-
function reduer(state, action) { // 새로운 상태를 만드는 로직 // const nextState = ... return nextState; }
- 이렇게 reducer에서 반환하는 상태 =. 컴포넌트가 지닐 새로운 상태
➰ reducer함수의 두번째 파라미터 action?
- action은 업데이트를 위한 정보를 가지고 있다.
- 주로 type 값을 지닌 객체 형태로 사용 (하지만 꼭 따라야할 규칙은 따로 없다.)
- 액션의 예시들( 아래의 코드 )
-
// 카운터에 1을 더하는 액션 { type: 'INCREMENT' } // 카운터에서 1을 빼는 액션 { type: 'DECREMENT' } // input 값을 바꾸는 액션 { type: 'CHANGE_INPUT', key: 'email', value: 'test@test.com' } // 새 할 일을 등록하는 액션 { type: 'ADD_TODO', todo: { id: 1, text: 'reducer 공부중', done: false, } }
💡useReducer의 사용법
const [state, dispatch] = useReducer(reducer, initialState);
- state : 우리가 앞으로 컴포넌트에서 사용할 수 있는 상태
- dispatch : 액션을 발생시키는 함수
- dispatch({ type: 'INCREMENT'})
- useReducer의 첫번째 파라미터 : reducer함수
- useReducer의 두번째 파라미터 : 초기 상태
일단 App 컴포넌트의 상태관리 방법을 바꾸기전에,
Counter.js 컴포넌트에 useReducer을 사용해보자.
이전 코드
Counter.js
import React,{useState} from 'react'
function Count() {
const [number, setNumber] = useState(0);
const onIncrease = () => {
setNumber(prevNum => prevNum +1);
}
const onDecrease = () => {
setNumber(prevNum => prevNum -1);
}
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
)
}
export default Count
useReducer로 구현한 Counter 컴포넌트
Counter.js
import React,{useReducer} from 'react' //useReducer 불러오기
// 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수
function reducer(state, action) {
switch(action.type){
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
function Count() {
const [number, dispatch] = useReducer(reducer, 0);
const onIncrease = () => {
dispatch({type: 'INCREMENT'});
}
const onDecrease = () => {
dispatch({type: 'DECREMENT'});
}
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
)
}
export default Count
💡App 컴포넌트를 useReducer로 구현하기
- App 에서 사용할 초기 상태를 컴포넌트 바깥으로 분리
- App 내부에 있던 기존 로직을 모두 제거 (차근차근 구현 할 예정임
- reducer 함수 틀만들기
- state에서 필요한 비구조화 할당 문법을 사용하고 추출하여 각 컴포넌트에게 전달
-
import React,{useRef, useState, useMemo, useCallback} from 'react'; // useCallback 불러오기 import UserList from './UserList'; import CreateUser from './CreateUser'; function countActiveUsers(users){ console.log('활성 사용자 수를 세는 중...'); return users.filter(user => user.active).length; } // 초기 상태 (default) const initialState = { // inputs의 초기 상태 inputs: { uername: '', email: '' }, // users의 초기 상태 users: [ {id: 1, username: 'John', email: 'john@example.com', active: true, }, {id: 2, username: 'Car', email: 'car@example.com', active: false, }, {id: 3, username: 'lee', email: 'lee@example.com', active: false, }, ] } // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수 function reduer(state, action){ return state; } function App() { //비구조화 할당 const [state, dispatch] = useReducer(reducer, initialState); const { users } = state; const { username, email } = state.input; return ( <> <CreateUser username={username} email={email}/> <UserList users={[]} /> <div>활성 사용자 수 : 0 </div> </> ); } export default App;
- onChange 구현 : CHANGE_INPUT 이라는 액션 객체를 사용하여 inputs 상태를 업데이트
-
import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기 import UserList from './UserList'; import CreateUser from './CreateUser'; function countActiveUsers(users){ console.log('활성 사용자 수를 세는 중...'); return users.filter(user => user.active).length; } const initialState = { inputs: { uername: '', email: '' }, users: [ {id: 1, username: 'John', email: 'john@example.com', active: true, }, {id: 2, username: 'Car', email: 'car@example.com', active: false, }, {id: 3, username: 'lee', email: 'lee@example.com', active: false, }, ] } // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수 function reducer(state, action){ switch (action.type) { case 'CHANGE_INPUT': return{ ...state, inputs: { [action.name]: action.value } } default: return state; }; } function App() { // useReducer 사용 const [state, dispatch] = useReducer(reducer, initialState); const { users } = state; const { username, email } = state.input; const onChange = useCallback(e => { const {name, value} = e.target; // 액션 발생시키는 함수 = dispatch dispatch({ type: 'CHANGE_INPUT', name, value, }) },[]); return ( <> <CreateUser username={username} email={email} onChange={onChange}/> <UserList users={users} /> <div>활성 사용자 수 : 0 </div> </> ); } export default App;
- onCreate 구현
-
import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기 import UserList from './UserList'; import CreateUser from './CreateUser'; function countActiveUsers(users){ console.log('활성 사용자 수를 세는 중...'); return users.filter(user => user.active).length; } const initialState = { inputs: { uername: '', email: '' }, users: [ {id: 1, username: 'John', email: 'john@example.com', active: true, }, {id: 2, username: 'Car', email: 'car@example.com', active: false, }, {id: 3, username: 'lee', email: 'lee@example.com', active: false, }, ] } // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 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 초기화 inputs: initialState.inputs, // 새로운 user 추가하기 users: state.users.concat(action.user), } default: return state; }; } function App() { // useReducer 사용 const [state, dispatch] = useReducer(reducer, initialState); // user를 새로 만들기 위해 useRef 사용 const nextId = useRef(4); const { users } = state; const { username, email } = state.inputs; const onChange = useCallback(e => { const {name, value} = e.target; dispatch({ type: 'CHANGE_INPUT', name, value, }, ) },[]); // 액션 발생시키는 함수 = dispatch const onCreate = useCallback(()=>{ dispatch({ type: 'CREATE_USER', user: { id: nextId.current, username, email, } }); nextId.current += 1; },[username, email]); return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/> <UserList users={users} /> <div>활성 사용자 수 : 0 </div> </> ); } export default App;
- onToggle과 onRemove도 구현
-
import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기 import UserList from './UserList'; import CreateUser from './CreateUser'; function countActiveUsers(users){ console.log('활성 사용자 수를 세는 중...'); return users.filter(user => user.active).length; } const initialState = { inputs: { uername: '', email: '' }, users: [ {id: 1, username: 'John', email: 'john@example.com', active: true, }, {id: 2, username: 'Car', email: 'car@example.com', active: false, }, {id: 3, username: 'lee', email: 'lee@example.com', active: false, }, ] } // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 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 초기화 inputs: initialState.inputs, // 새로운 user 추가하기 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; }; } function App() { // useReducer 사용 const [state, dispatch] = useReducer(reducer, initialState); // user를 새로 만들기 위해 useRef 사용 const nextId = useRef(4); const { users } = state; const { username, email } = state.inputs; const onChange = useCallback(e => { const {name, value} = e.target; dispatch({ type: 'CHANGE_INPUT', name, value, }, ) },[]); // 액션 발생시키는 함수 = dispatch const onCreate = useCallback(()=>{ dispatch({ type: 'CREATE_USER', user: { id: nextId.current, username, email, } }); nextId.current += 1; },[username, email]); const onToggle = useCallback((id)=>{ dispatch({ type: 'TOGGLE_USER', id }) },[]) 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> </> ); } export default App;
- 활성 사용자 수 구하기
import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기 import UserList from './UserList'; import CreateUser from './CreateUser'; function countActiveUsers(users){ console.log('활성 사용자 수를 세는 중...'); return users.filter(user => user.active).length; } const initialState = { inputs: { uername: '', email: '' }, users: [ {id: 1, username: 'John', email: 'john@example.com', active: true, }, {id: 2, username: 'Car', email: 'car@example.com', active: false, }, {id: 3, username: 'lee', email: 'lee@example.com', active: false, }, ] } // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 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 초기화 inputs: initialState.inputs, // 새로운 user 추가하기 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; }; } function App() { // useReducer 사용 const [state, dispatch] = useReducer(reducer, initialState); // user를 새로 만들기 위해 useRef 사용 const nextId = useRef(4); const { users } = state; const { username, email } = state.inputs; const onChange = useCallback(e => { const {name, value} = e.target; dispatch({ type: 'CHANGE_INPUT', name, value, }, ) },[]); // 액션 발생시키는 함수 = dispatch const onCreate = useCallback(()=>{ dispatch({ type: 'CREATE_USER', user: { id: nextId.current, username, email, } }); nextId.current += 1; },[username, email]); const onToggle = useCallback((id)=>{ dispatch({ type: 'TOGGLE_USER', id }) },[]) const onRemove = useCallback((id)=>{ dispatch({ type: 'REMOVE_USER', id }) },[]) 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> </> ); } export default App;
💡useReducer vs useState
useState로 관리하기 편할 경우
- 컴포넌트에서 관리하는 값이 딱 하나고, 그 값이 단순한 숫자, 문자열 또는 boolean 값일 경우
useRefucer로 관리하기 편할 경우
- 컴포넌트에서 관리하는 값이 여러개가 되어서 상태의 구조가 복잡해 질경우
Reference
'React > Velopert's React' 카테고리의 다른 글
[React] Study #21 | Context API를 사용한 전역 값 관리 (0) | 2021.11.13 |
---|---|
[React] Study #20 | 커스텀 Hooks 만들기 (0) | 2021.10.09 |
[React] Study # 18 | React.memo (0) | 2021.10.09 |
[React] Study #17 | useCallback (0) | 2021.10.08 |
[React] Study # 16 | useMemo (0) | 2021.10.08 |