modules/counter
import { createAction } from "redux-action";
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";
//액션 타입 정의
// export const increase = () => ({ type: INCREASE });
// export const decrease = () => ({ type: DECREASE });
//액션 생성함수 정의
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
createAction : 액션생성 함수를 더 짧게 정의가능
const initialState = {
number: 0
};
// function counter(state = initialState, action) {
// switch (action.type) {
// case INCREASE:
// return {
// number: state.number + 1
// };
// case DECREASE:
// return {
// number: state.number - 1
// };
// default:
// return state;
// }
// }
// 리듀서 함수 정의
const counter = handleActions(
{
[INCREASE]: (state, action) => ({ number: state.number + 1 }),
[DECREASE]: (state, action) => ({ number: state.number - 1 })
},
initialState
);
handleActions : 첫 번째 함수에 각 액션에 대한 업데이트 함수, 두 번째 파라미터에는 초깃값
const MY_ACTION = 'sample/MY_ACTION';
const myAction = createAction(MY_ACTION);
const action = myAction('hello world');
//결과: { type: MY_ACTION, payload: 'hello world' }
- 액션 생성함수에서 받아온 파라미터를 그대로 payload에 넣는 것이 아니라 다른 형태로 넣고 싶다면, createAction의 두번째 파라미터에 payload를 정의하는 함수를 넣으면 됨
modules/todos
import { createAction } from "redux-action";
const CHANGE_INPUT = "todos/CHANGE_INPUT";
const INSERT = "todos/INSERT";
const TOGGLE = "todos/TOGGLE";
const REMOVE = "todos/REMOVE";
// export const changeInput = (input) => ({
// type: CHANGE_INPUT,
// input
// });
// let id = 3;
// export const insert = (text) => ({
// type: INSERT,
// todo: {
// id: id++,
// text,
// done: false
// }
// });
// export const toggle = (id) => ({
// type: TOGGLE,
// id
// });
// export const remove = (id) => ({
// type: REMOVE,
// id
// });
export const changeInput = createAction(CHANGE_INPUT, (input) => input);
let id = 3;
export const insert = createAction(INSERT, (text) => ({
id: id++,
text,
done: false
}));
export const toggle = createAction(TOGGLE, (id) => id);
export const remove = createAction(REMOVE, (id) => id);
action.payload : 액션에 필요한 추가 데이터를 모두 payload라는 이름 사용하므로, 공통적으로 action.payload값을 조회하도록 리듀서 구현해야 함
// 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;
// }
// }
const todos = handleActions(
{
[CHANGE_INPUT]: (state, action) => ({ ...state, input: action.payload }),
[INSERT]: (state, action) => ({
...state,
todos: state.todos.concat(action.payload)
}),
[TOGGLE]: (state, action) => ({
...state,
todos: state.todos.map((todo) =>
todo.id === action.payload ? { ...todo, done: !todo.done } : todo
)
}),
[REMOVE]: (state, action) => ({
...state,
todos: state.todos.filter((todo) => todo.id !== action.id)
})
},
initialState
);
export default todos;
- 객체 비구조화 할당을 통해 action값의 payload 이름 재설정하기
const todos = handleActions({
[CHANGE_INPUT]: (state, { payload: input }) => ({ ...state, input }),
[INSERT]: (state, { payload: todo }) => ({ ...state, todos: state.todos.concat(todo) }),
[TOGGLE]: (state, { payload: id }) => ({
...state,
todos: state.todos.map((todo) => todo.id === id ? { ...todo, done: !todo.done } : todo)
}),
[REMOVE]: (state, { payload: id }) => ({
...state,
todos: state.todos.filter((todo) => todo.id !== id)
})
}, initialState);
yarn add immer
const deepObject = {
modal: {
open: false,
content: {
title: '알림',
body: '성공',
buttons: {
confirm: '확인',
cancel: '취소',
},
},
},
waiting: false,
settings: {
theme: 'dark',
zoomLevel: 5,
},
};
const shallowObject = {
modal: {
open: false,
title: '알림',
body: '성공',
confirm: '확인',
cancel: '취소',
},
waiting: false,
theme: 'dark',
zoomLevel: 5
}
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)
const 결과 = useSelector(상태 선택 함수);
import { connect, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";
// const CounterContainer = ({ number, increase, decrease }) => {
// return (
// <Counter number={number} onIncrease={increase} onDecrease={decrease} />
// );
// };
// export default connect(
// (state) => ({
// number: state.counter.number
// }),
// (dispatch) => ({
// increase: () => {
// dispatch(increase());
// },
// decrease: () => {
// dispatch(decrease());
// }
// })
// )(CounterContainer);
const CounterContainer = () => {
const number = useSelector(state => state.counter.number);
return(<Counter number={number} />);
}
export default CounterContainer;
const dispatch = useDispatch();
dispatch({ type: 'SAMPLE_ACTION' });
containers/CounterContainer
import { connect, useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { increase, decrease } from "../modules/counter";
// const CounterContainer = ({ number, increase, decrease }) => {
// return (
// <Counter number={number} onIncrease={increase} onDecrease={decrease} />
// );
// };
// export default connect(
// (state) => ({
// number: state.counter.number
// }),
// (dispatch) => ({
// increase: () => {
// dispatch(increase());
// },
// decrease: () => {
// dispatch(decrease());
// }
// })
// )(CounterContainer);
const CounterContainer = () => {
const number = useSelector(state => state.counter.number);
const dispatch = useDispatch();
return (<Counter number={number}
onIncrease={() => dispatch(increase())}
onDecrease={() => dispatch(decrease())}
/>);
}
export default CounterContainer;
const CounterContainer = () => {
const number = useSelector(state => state.counter.number);
const dispatch = useDispatch();
const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);
return (<Counter number={number}
onIncrease={onIncrease}
onDecrease={onDecrease}
/>);
}
containers/TodosContainer
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import Todos from "../components/Todos";
import { changeInput, insert, toggle, remove } from "../modules/todos";
// const TodosContainer = ({
// input,
// todos,
// changeInput,
// insert,
// toggle,
// remove
// }) => {
// return (
// <Todos
// input={input}
// todos={todos}
// onChangeInput={changeInput}
// onInsert={insert}
// onToggle={toggle}
// onRemove={remove}
// />
// );
// };
// export default connect(
// ({ todos }) => ({
// input: todos.input,
// todos: todos.todos
// }),
// {
// changeInput,
// insert,
// toggle,
// remove
// }
// )(TodosContainer);
const TodosContainer = () => {
const { input, todos } = useSelector(({ todos }) => ({
input: todos.input,
todos: todos.todos
}));
const dispatch = useDispatch();
const onChangeInput = useCallback(input => dispatch(changeInput(input)), [dispatch]);
const onInsert = useCallback(text => dispatch(insert(text)), [dispatch]);
const onToggle = useCallback(id => dispatch(toggle(id)), [dispatch]);
const onRemove = useCallback(id => dispatch(remove(id)), [dispatch]);
return (
<Todos
input={input}
todos={todos}
onChangeInput={onChangeInput}
onInsert={onInsert}
onToggle={onToggle}
onRemove={onRemove}
/>)
}
export default TodosContainer;
const store = useStore();
store.dispatch({ type: 'SAMPLE_ACTION' });
store.getState();
lib/useActions
import { useMemo } from "react";
import { useDispatch } from "react-redux";
import { bindActionCreators } from "redux";
export default function useActions(actions, deps) {
const dispatch = useDispatch();
return useMemo(
() => {
if (Array.isArray(actions)) {
return actions.map(a => bindActionCreators(a, dispatch));
}
return bindActionCreators(actions, dispatch);
},
deps ? [dispatch, ...deps] : deps
);
}
containers/TodosContainer
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import Todos from "../components/Todos";
import useActions from "../lib/useActions";
import { changeInput, insert, toggle, remove } from "../modules/todos";
// const TodosContainer = ({
// input,
// todos,
// changeInput,
// insert,
// toggle,
// remove
// }) => {
// return (
// <Todos
// input={input}
// todos={todos}
// onChangeInput={changeInput}
// onInsert={insert}
// onToggle={toggle}
// onRemove={remove}
// />
// );
// };
// export default connect(
// ({ todos }) => ({
// input: todos.input,
// todos: todos.todos
// }),
// {
// changeInput,
// insert,
// toggle,
// remove
// }
// )(TodosContainer);
const TodosContainer = () => {
const { input, todos } = useSelector(({ todos }) => ({
input: todos.input,
todos: todos.todos
}));
// const dispatch = useDispatch();
// const onChangeInput = useCallback(input => dispatch(changeInput(input)), [dispatch]);
// const onInsert = useCallback(text => dispatch(insert(text)), [dispatch]);
// const onToggle = useCallback(id => dispatch(toggle(id)), [dispatch]);
// const onRemove = useCallback(id => dispatch(remove(id)), [dispatch]);
const [onChangeInput, onInsert, onToggle, onRemove] = useActions(
[changeInput, insert, toggle, remove],
[]
);
return (
<Todos
input={input}
todos={todos}
onChangeInput={onChangeInput}
onInsert={onInsert}
onToggle={onToggle}
onRemove={onRemove}
/>)
}
export default TodosContainer;
connect : 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링되어도 props가 변경되지 않았다면 재렌더링 되지 않음(성능 최적화)
Hook : 성능 최적화를 위해 React.memo()사용