불변성: 값이나 상태를 변경할 수 없는 값을 의미
상태를 변경하는데, 상태를 변경하지 않으면서 원하는 상태를 바꾼다는게 말이 어렵다...
필요한 값을 변형해서 사용하고 싶다면 어떤 값의 사본을 만들어서 사용해야 한다.
// 리듀서
function todos(state = initialState, action) {
switch (action.type) {
case CHANGE_INPUT:
return {
...state,
input: action.input
};
case INSERT:
return {
...state,
todos: state.todos.concat(action.todo)
};
case TOGGLE:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
)
};
case REMOVE:
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.id )
};
default:
return state;
}
}
// 리듀서
// redux-actions 라이브러리 사용
const todos = handleActions({
[INSERT]: (state,action) => ({...state, todos: state.todos.concat(action.payload)}),
[CHANGE_INPUT]: (state,action) => ({...state, input: action.payload}),
[REMOVE]: (state,action) => ({...state, todos: state.todos.filter((todo) => (todo.id !== action.payload))}),
[TOGGLE]: (state,{payload: id}) => ({...state, todos: state.todos.map((todo) => todo.id === id ? {...todo,done: !todo.done} : todo)})
}, initialState)
// 리듀서
// immer 사용
const todos = handleActions(
{
[CHANGE_INPUT]: (state, { payload: input }) =>
produce(state, draft => {
draft.input = input;
}),
[INSERT]: (state, { payload: todo }) =>
produce(state, draft => {
draft.todos.push(todo);
}),
[TOGGLE]: (state, { payload: id }) =>
produce(state, draft => {
const todo = draft.todos.find(todo => todo.id === id);
todo.done = !todo.done;
}),
[REMOVE]: (state, { payload: id }) =>
produce(state, draft => {
const index = draft.todos.findIndex(todo => todo.id === id);
draft.todos.splice(index, 1);
})
},
initialState,
);
리액트는 얕은 비교를 통해 새로운 값인지 아닌지를 판단한 후 새로운 값인 경우 리렌더링을 한다.
얕은 비교란 객체, 배열, 함수와 같은 참조 타입들을 실제 내부 값까지 비교하지 않고 동일 참조(동일한 메모리 값을 사용하는지)를 비교한다.
다음 시나리오를 보면서 왜 리액트에서 state 값을 직접 변경하면 안되는지 느껴보자.
state.push('a')
을 통해 state 배열에 직접 접근하여 요소를 추가한다.위와 같은 이유로 새로운 객체 or 배열을 만들어 새로운 참조값을 만들고, 리액트에게 이 값은 이전과 다른 참조값임을 알려야 한다.
const user = { name: 'chichi', age: 30 }
const copyUser = user; // 배열의 복사가 아니라 같은 참조 값을 가짐
user.age += 1;
console.log('user: ', user);
console.log('copyUser: ', copyUser);
/*
user: { name: 'chichi', age: 31 }
copyUser: { name: 'chichi', age: 31 }
*/
const arr = ['b', 'c', 'd'];
const copyArr = arr;
arr.push('a');
console.log('arr: ', arr);
console.log('copyArr: ', copyArr);
/*
arr: ["b", "c", "d", "a"]
copyArr:["b", "c", "d", "a"]
*/
setUsers(state.array.concat(user));
const onRemove = id => {
// user.id 가 id 인 것을 제거
setUsers(users.filter(user => user.id !== id));
};
const onToggle = id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
};
setState(state => {...state, key: value})
setState(state => {..._.omit(state, 'deleteKey')})
setState(state => {...state, key: newValue})
produce
함수만 기억하면 된다.import produce from "immer";
const baseState = [
{
todo: "javascript 공부",
done: true
},
{
todo: "Algorithm 공부",
done: false
}
];
const nextState = produce(baseState, draftState => {
draftState.push({ todo: "CS 공부" });
draftState[1].done = true;
});
const initialState = [{ name: "chichi", address: { city: "seoul" } }];
export default function auth(state = initialState, action) {
switch (action.type) {
case SET_INFO:
return {
...state,
name: "sh",
address: {
...state.address,
city: "busan"
}
};
default:
return state;
}
}
const initialState = [{ name: "chichi", address: { city: "seoul" } }];
export default function auth(state = initialState, action) {
produce(state, draft => {
switch (action.type) {
case SET_INFO:
draft[0].name = action.data.name;
draft[0].address.city = action.data.city;
break;
case ADD_INFO:
draft.push({ name: "sh", address: { city: "busan" } });
default:
return draft;
}
});
}
참조