[패스트캠퍼스 수강 후기] 프론트엔드 인강 100% 환급 챌린지 27회차 미션 - 30강 useReducer
useReducer
컴포넌트 상태를 업데이트해야할때는 useState를 사용해서 업데이트 해줬는데 그것 말고도 useReducer를 사용해서도 업데이트 해줄 수 있다. 어떤 차이가 있냐면 useState는 설정하고 싶은 다음 상태를 직접 지정해주는 방식으로 상태를 업데이트 하는 반면에 액션이라는 객체를 기반으로 상태를 업데이트 한다. 여기서 액션객체라는 것은 업데이트할때 참조하는 것인데, 여기서 타입이란 값을 어떤 업데이트를 할것인지 명시할 수 있고, 업데이트할때 참조하고 싶은 다른 값이 있다면 이 객체안에 넣을 수도 있다. useReducer는 상태 업데이트 로직을 컴포넌트 밖으로 분리시킬 수 있다. 심지어 다른파일에 작성 후 불러올 수도 있다. 여기서 reducer이란 개념이 있는데 상태를 업데이트하는 함수를 의미한다. 이 함수는 아래와 같이 생겼다.
현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 형태를 갖추고 있어야 한다.
(액션타입을 받아오고 액션타입이 INCREMENT면 +1을 반환, DECREMENT면 -1을 반환)
useReducer를 쓸때는 위와 같이 쓴다.(reducer 뒤에는 기본값을 넣어줌-숫자.문자.객체등 무관)
number는 현재 상태, dispatch의 의미는 보내다, 여기서는 액션을 발생시킨다고 이해하면 쉽다.
이제 우리가 기존에 만들었던 코드에 실습해보자.
Counter.js
import React, { useReducer } from 'react';
function reducer(state, action){
switch (action.type){
case 'INCREMENT' :
return state + 1;
case 'DECREMENT' :
return state -1;
default:
throw new Error('Unhandled action');
}
}
function Counter(){
const [number, dispatch] = useReducer(reducer, 0);
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;
이렇게 수정하고 index.js에서 새로 고친 Counter컴포넌트를 받아와줘서 화면에 테스트해보자.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import Counter from './Counter';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<Counter />,document.getElementById('root'));
serviceWorker.unregister();
화면을 돌려보면 기존의 카운트가 잘 구현되는 것을 볼 수 있다.
이제 우리가 useReducer가 어떻게 구현되는지는 잘 확인해보았다. 이번에는 우리가 만들었던 App.js를 아예 useReducer를 사용해서 전면 개편해보자.
아래와 같이 App,js코드를 고쳐준다.
App.js
import React, { useRef, useReducer, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function countActiveUsers(users){
console.log('활성 사용자 수를 세는중...');
return users.filter(user => user.active).length;
}
const initialState = {
inputs:{
username: '',
email: '',
},
users: [
{
id: 1,
username: 'bmyu',
email: 'bomee88@naver.com',
active: true,
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false,
},
{
id: 3,
username: 'sample',
email: 'sample@example.com',
active: false,
}
]
}
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:
throw new Error('Unhandled action');
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
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
})
},[]);
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를 쓰고 어떨때 useState를 쓰는지 궁금해진다.
정해진 답은 없다. 그런데 관리할 값이 간단할 경우에는 useState가 편하고 관리할 값이 많아서 복잡할때는 useReducer가 편하다.
오늘은 여기까지..
시청 영상 30강 21~22까지
프론트엔드 개발 올인원 패키지 with React Online. 👉 https://bit.ly/31Cf1hp
Comments