npm create-react-app redux-start
npm i redux : Store를 만드는 라이브러리
{type: 'TEST'} // payload 없는 액션
{type: 'TEST', params: 'hello'} // payload 있는 액션
function 액션 생성자(...args) { return 액션; }
1) 액션 타입 정의 후 변수로 만들기
강제는 아님
타입을 문자열로 넣기에는 실수 유발 가능성
미리 정의한 변수 사용하면 스펠링에 주의를 덜 기울여도 됨
2) 액션 객체를 만들어 내는 함수 만들기
하나의 액션 객체를 만들기 위해 하나의 함수를 만들기
액션 타입은 미리 정의한 타입 변수로부터 가져와서 사용
src/redux/actions.js
export const ADD_TODO = 'ADD_TODO';
export function addTodo(todo) {
return {
type: ADD_TODO,
todo,
};
}
Pure Function
: 같은 인풋을 받으면 같은 결과를 내는 함수Immutable
: original state와 바뀐 state가 별도의 객체로 만들어져야 함function 리듀서(previousState, action) {
return newState;
}
액션을 받아서 state를 리턴하는 구조
src/redux/reducers.js
import { ADD_TODO } from './actions';
// state 모습 구상 (배열)
// ['코딩', '휴식', ''];
const ininitialState = []; // 미리 초기값
export function todoApp(previousState = ininitialState, action) {
// 초기값을 설정해주는 부분
// if(previousState === undefined){
// return [];
// }
if(action.type === ADD_TODO) {
return [...previousState, action.todo];
}
return previousState;
}
redux 라이브러리를 이용해서 createStore import
위의 예시의 파란 공을 만드는 함수
const store = createStore(리듀서);
createStore<S>(
reducer: Reducer<S>,
preloadedState: S,
enhancer?: StoreEnhancer<S>
):Store<S>;
src/redux/store.js
import { createStore } from 'redux';
import { todoApp } from './reducers';
const store = createStore(todoApp);
export default store;
root: index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import store from './redux/store';
console.log(store);
콘솔에 출력된 객체 확인
console.log(store.getState()); // []
getState
root: index.js
import { addTodo } from './redux/actions';
console.log(store);
console.log(store.getState());
// store 상태 변경 시키기
store.dispatch(addTodo("coding"));
console.log(store.getState());
subscribe
// store 상태 구독
const unsubscribe = store.subscribe(()=>{
console.log(store.getState());
});
// console.log(store);
// store 상태 변경 시키기
store.dispatch(addTodo("coding"));
store.dispatch(addTodo("reading"));
store.dispatch(addTodo("eating"));
unsubscribe();
store가 복잡해질 때 처리하는 방법
root: index.js
store.subscribe(()=>{
console.log(store.getState());
});
src/redux/reducers.js
import { ADD_TODO } from './actions';
// [{text: '코딩', done:false}, {text: '휴식', done:false}]
const ininitialState = []; // 미리 초기값
export function todoApp(previousState = ininitialState, action) {
if(action.type === ADD_TODO) {
return [...previousState, {text:action.text, done:false}];
}
return previousState;
}
src/redux/actions.js
// {type: ADD_TODO, text: '할일'}
export function addTodo(text) {
return {
type: ADD_TODO,
text,
};
}
root: index.js
import store from './redux/store';
import { addTodo } from './redux/actions';
store.subscribe(()=>{
console.log(store.getState());
});
store.dispatch(addTodo('할일'));
done: true로 바꿔주는 또 하나의 액션 생성
src/redux/actions.js
export const COMPLETE_TODO = 'COMPLETE_TODO';
// {type: COMPLETE_TODO, index: 3} <- 배열의 id값이 나오도록
export function completeTodo(index) {
return {
type: COMPLETE_TODO,
index,
};
}
액션에 대응되는 reducer의 로직 만들기
src/redux/reducers.js
import { ADD_TODO, COMPLETE_TODO } from './actions';
...
// COMPLETE_TODO
if(action.type === COMPLETE_TODO) {
return previousState.map((todo, index)=>{
if (index === action.index) {
return { ...todo, done:true };
}
return todo;
});
}
root: index.js
import store from './redux/store';
import { addTodo, completeTodo } from './redux/actions';
store.subscribe(()=>{
console.log(store.getState());
});
store.dispatch(addTodo("할일"));
store.dispatch(completeTodo(0));
두번째 객체 상태 변경
src/redux/reducers.js
import { ADD_TODO, COMPLETE_TODO } from './actions';
// {todos: [{text: '코딩', done:false}, {text: '휴식', done:false}], filter: 'ALL'}
const ininitialState = {todos: [], filter: 'ALL'};
export function todoApp(previousState = ininitialState, action) {
// ADD_TODO
if(action.type === ADD_TODO) {
return {
...previousState, // 필터를 가지고 있도록 처리
todos: [...previousState.todos, {text:action.text, done:false}] };
}
// COMPLETE_TODO
if(action.type === COMPLETE_TODO) {
return {
...previousState, // 필터를 가지고 있도록 처리
todos: previousState.todos.map((todo, index)=>{
if (index === action.index) {
return { ...todo, done:true };
}
return todo;
})};
}
return previousState;
}
대응되는 actions 추가
src/redux/actions.js
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_COMPLETE = 'SHOW_COMPLETE';
export function showALL() {
return { type: SHOW_ALL }
}
export function showComplete() {
return { type: SHOW_COMPLETE }
}
로직 추가
src/redux/reducers.js
import { ADD_TODO, COMPLETE_TODO, SHOW_COMPLETE } from './actions';
...
if (action.type === SHOW_COMPLETE) {
return {
...previousState,
filter: "COMPLETE",
}
}
if (action.type === SHOW_ALL) {
return {
...previousState,
filter: "ALL",
}
}
출력 확인
root: index.js
import store from './redux/store';
import { addTodo, completeTodo, showComplete } from './redux/actions';
store.subscribe(()=>{
console.log(store.getState());
});
store.dispatch(addTodo("할일"));
store.dispatch(completeTodo(0));
store.dispatch(showComplete());
=> 단일 스토어인 경우와 복잡해지는 경우 (쪼개기)
=> 별개의 리듀서 처리
src/redux/reducer.js
import { ADD_TODO, COMPLETE_TODO, SHOW_ALL, SHOW_COMPLETE } from './actions';
import { combineReducers } from 'redux';
const ininitialState = {todos: [], filter: 'ALL'};
// store에서 받을 수 있도록 초기 상태를 reducer 위로 옮기기
const todoIntialState = ininitialState.todos;
const filterInitialState = ininitialState.filter;
const reducer = combineReducers({
todos: todosReducer,
filter: filterReducer,
});
export default reducer;
// todoApp을 todoReducer로 변경
// todoReducer에는 todo에 관련된 로직만 남기기
// 배열 상태로 바꿔주기
// {todos: [{text: '코딩', done:false}, {text: '휴식', done:false}], filter: 'ALL'}
function todosReducer(previousState = todoIntialState, action) {
if(action.type === ADD_TODO) {
return [...previousState, {text:action.text, done:false}];
}
if(action.type === COMPLETE_TODO) {
return previousState.map((todo, index)=>{
if (index === action.index) {
return { ...todo, done:true };
}
return todo;
});
}
return previousState;
}
// todoApp 복사해서 filterReducer로 변경
// filterReducer에는 filter에 관련된 로직만 남기기
// 객체가 아닌 값으로 리턴하기
function filterReducer(previousState = filterInitialState, action) {
if (action.type === SHOW_COMPLETE) {
return "COMPLETE";
}
if (action.type === SHOW_ALL) {
return "ALL";
}
return previousState;
}
src/redux/store.js
combine한 reducer를 store에 import
import { createStore } from 'redux';
import todoApp from './reducers';
const store = createStore(todoApp);
export default store;
하나의 reducer에 있을 경우 복잡하므로 각각의 파일로 나눔
src/redux/reducers/todos.js
import { ADD_TODO, COMPLETE_TODO } from '../actions'; // path 변경
const ininitialState = []; // 초기값 변경
export default function todos(previousState = intialState, action) {
if(action.type === ADD_TODO) {
return [...previousState, {text:action.text, done:false}];
}
if(action.type === COMPLETE_TODO) {
return previousState.map((todo, index)=>{
if (index === action.index) {
return { ...todo, done:true };
}
return todo;
});
}
return previousState;
}
src/redux/reducers/filter.js
import { SHOW_ALL, SHOW_COMPLETE } from '../actions'; // path 변경
const ininitialState = [];
export default function filter(previousState = ininitialState, action) {
if (action.type === SHOW_COMPLETE) {
return "COMPLETE";
}
if (action.type === SHOW_ALL) {
return "ALL";
}
return previousState;
}
src/redux/reducers/reduce.js
import { combineReducers } from 'redux';
import todos from './todos';
import filter from './filter';
const reducer = combineReducers({
todos,
filter,
});
export default reducer;
(모두 옮겼으니) 기존에 있던 src/redux/reduces.js는 삭제 후 src/redux/store.js 변경
import { createStore } from 'redux';
import todoApp from './reducers/reducer';
const store = createStore(todoApp);
export default store;
💬 이전에 갖고 있던 패스트캠퍼스 한 번에 끝내는 프론트엔드 개발(Online) 강의-2021에서 Redux 기초 정리
💬 강의흐름처럼 처음에 한 덩어리에서 개별로 분리하거나 아니면 전체 구상 후에 개별 분리된 것을 하나로 합쳐서 만드는 식인 거 같은데 그럴려면 처음에 어떻게 설계하고 나눌지 판단을 해야하는 거 같다. (기초개념인데도 복잡..)