불변성을 지키며 상태를 업데이트하기 위해 우리가 해왔던 방법
const nextState = {
...state,
posts: state.posts.map(post =>
post.id === 1
? {
...post,
comments: post.comments.concat({
id: 3,
text: '새로운 댓글'
})
}
: post
)
};
immer 라이브러리를 사용한다면 보다 쉽게 구현 가능하다
const nextState = produce(state, draft => {
const post = draft.posts.find(post => post.id === 1);
post.comments.push({
id: 3,
text: '와 정말 쉽다!'
});
});
yarn add immer
import produce from 'immer'
import React, { useReducer, useMemo } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import produce from 'immer';
function countActiveUsers(users) {
console.log('활성 사용자 수를 세는중...');
return users.filter(user => user.active).length;
}
const initialState = {
users: [
{
id: 1,
username: 'velopert',
email: '[email protected]',
active: true
},
{
id: 2,
username: 'tester',
email: '[email protected]',
active: false
},
{
id: 3,
username: 'liz',
email: '[email protected]',
active: false
}
]
};
function reducer(state, action) {
switch (action.type) {
case 'CREATE_USER':
return produce(state, draft => {
draft.users.push(action.user);
});
case 'TOGGLE_USER':
return produce(state, draft => {
const user = draft.users.find(user => user.id === action.id);
user.active = !user.active;
});
case 'REMOVE_USER':
return produce(state, draft => {
const index = draft.users.findIndex(user => user.id === action.id);
draft.users.splice(index, 1);
});
default:
return state;
}
}
// UserDispatch 라는 이름으로 내보내줍니다.
export const UserDispatch = React.createContext(null);
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const { users } = state;
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<UserDispatch.Provider value={dispatch}>
<CreateUser />
<UserList users={users} />
<div>활성사용자 수 : {count}</div>
</UserDispatch.Provider>
);
}
export default App;
setTodo
함수 안에 업데이트 하는 함수를 넣음으로써 useCallback
을 사용하는 경우 두번째 파라미터인 deps
배열에 todo
를 넣지 않아도 되게 된다
produce
함수에 두개의 파라미터를 넣게 된다면 첫번째 파라미터에 넣은 상태를 불변성을 유지하면서 새로운 상태를 만들어줌const todo = {
text: 'Hello',
done: false
};
const updater = produce(draft => {
draft.done = !draft.done;
});
const nextTodo = updater(todo);
console.log(nextTodo);
// { text: 'Hello', done: true }
이것을 useCallback에 적용해보면,
const [todo, setTodo] = useState({text: "hello", done: false});
const onClick = useCallback(()=> {
setTodo(produce(draft => {draft.done = !draft.done}), []);
});
Immer는 JS 엔진의 Proxy라는 기능을 사용 → react-native와 같은 환경에서는 지원되지 않으므로 ES5 fallback을 사용하게 됨