액션의 타입을 정의하여 변수로 빠는 단계,
액션의 타입은 문자열, 문자열로 넣기에는 실수를 유발할 가능성이 있으므로 미리 정의한 변수를 사용하면, 스펠링에 주의를 덜 기울여도 된다.
하나의 액션 객체를 만들기 위해 하나의 함수를 만들고, 액션의 타입을 미리 정의한 타입 상수로 부터 사용하면 사용 시 오타를 방지할 수 있고 변경이 수월하다.
<action.js>
const ADD_TODO = "ADD_TODO"; // 액션 타입
function addTodo(){
return{
type: ADD_TODO,
};
}
전역 state(상태 객체)안에 새로운 todo를 넣는다.
어떤 todo를 넣을 것인지 설정해야 한다.
<actions.js>
export default const ADD_TODO = "ADD_TODO"; // 액션 타입
function addTodo(todo){
return{
type: ADD_TODO,
todo, //같은 이름이기 때문에 하나만 정의해서 사용해도 된다.
//todo : todo,
};
}
액션을 주면, 그 액션이 적용되어 달라지는 결과를 만들어주는 함수이다..
Pure Function : 같은 input을 주면 같은 결과를 리턴하는 함수, 리듀서 안에 시간에 따른 달라지는 값이나, 코드가 들어갈 수 없다.
Immutable : orginal state(기존 상태), 새로운 state가 별도의 객체로 만들어야 한다.
reducer를 통해서 state를 인지하는 방식이 Immutable이기 때문에 그렇게 처리되지 않으면 문제가 발생할 수 있음
function 리듀서(previousState, action){
return newState;
}
import {ADD_TODO} from "./actions";
//state
//['코딩','점심 먹기'];
function todoApp(previousState, action){
// 초기값을 설정해 주는 부분
if(previousState === undefined){
return [];
}
if(action.type === ADD_TODO){
return[...previousState, action.todo];
}
//previousState.push('');
return previousState;
}
previousState에 대하여 설정을 해주어야 한다. todoApp이 호출되지 않았다면 previousState 상태는 초깃값이기 때문에 초기값을 지정해 주어야 한다.
초기값에 설정을 더 간편하게 하는 법을 살펴보자
import {ADD_TODO} from "./actions";
//state
//['코딩','점심 먹기'];
const initialState = [];
export function todoApp(previousState = initialState , action){
// 초기값을 설정해 주는 부분
if(previousState === undefined){
return [];
}
if(action.type === ADD_TODO){
return[...previousState, action.todo];
}
//previousState.push('');
return previousState;
}
스토어를 만드는 함수를 알아보자.
const store = createStore(리듀서);
createStore<S>
(1 : reducer:Reducer<S>
,2 : preloadedState:S, 3 : enhancer?: StoreEnhancer<S>
):Store<S>
;
reducer 함수(위에서 정의한 todoApp 같은 함수)
preloadedState : initialstate , 하지만 넣지 않았을 경우 각각의 reducer에서 최초값으로 undefined가 들어오기 때문에 그쪽에서 initialState를 설정할 수 있다.
redux의 기능을 도화주는 미들웨어나 데브툴즈 같은 것들(추후에 설명한다)
<store.js>
import {createStore} from 'redux';
import {todoApp} from "./reducers";
const store = createStore(todoApp);
export default store;
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo} from './redux/actions';
console.log(store);
console.log(store.getState());
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
빈 배열이 나오는 이유는 createStore() 호출시 최초로 실행되면서 todoApp에서 previousState는 undefined 되어서 initialState값으로 되는 [ ]가 설정된다.
store의 상태를 변환 시켜보자.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo} from './redux/actions';
console.log(store);
console.log(store.getState());
// store.dispatch({type: addTodo, todo}); 이렇게 삽입이 가능하나, 기존에 구현한 action함수를 이용하여 아래와 같이 사용한다.
store.dispatch(addTodo("coding"));
console.log(store.getState());
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import {addTodo} from './redux/actions';
//store의 변경사항 구독
store.subscribe(()=>{
console.log(store.getState());
})
// store.dispatch({type: addTodo, todo}); 이렇게 삽입이 가능하나, 기존에 구현한 action함수를 이용하여 아래와 같이 사용한다.
const unsubscribe = store.subscribe(()=>{
});
store.dispatch(addTodo("coding"));
store.dispatch(addTodo("read book"));
store.dispatch(addTodo("eat"));
// console.log(store.getState());
store.dispatch(addTodo("coding")); // => 실제로 store에 값은 저장된다.
store.dispatch(addTodo("read book"));
store.dispatch(addTodo("eat"));
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
combineReducer가 필요한 이유를 살펴보기 위해 기존 코드를 살펴보도록 하자.
todo를 수행한 경우에 done 처리를 해야하기 때문에 redecer의 state를 변경하도록 하자.
<reducer.js>
import {ADD_TODO} from "./actions";
//state
//['코딩','점심 먹기'];
//[{text: '코딩', done: false}, {text : '점심 먹기', done:true}]
const initialState = [];
export function todoApp(previousState = initialState , action){
// 초기값을 설정해 주는 부분
if(previousState === undefined){
return [];
}
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;
})
}
//previousState.push('');
return previousState;
}
<action.js>
export const ADD_TODO = "ADD_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
//{type : ADD_TODO, text : '할일'}
export fuction addTodo(text){
return{
type: ADD_TODO,
text,
};
}
//{type : COMPLETE_TODO, index : 3}
export function completeTodo(index){
return {
type: COMPLETE_TODO
index
}
}
<index.js>
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
store.subscribe(()=>{
console.log(store.getStore());
});
//console.log(store)
store.dispatch(addTodo("할일"));
store.dispatch(completeTodo(0));
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
<reducer.js>
import {ADD_TODO} from "./actions";
//state
//{todos : [{text: '코딩', done: false}, {text : '점심 먹기', done:true}], filter : 'ALL'}
const initialState = {todos : [], filter: 'ALL'}
export function todoApp(previousState = initialState , action){
// 초기값을 설정해 주는 부분
// if(previousState === undefined){
// return [];
// }
if(action.type === ADD_TODO){
return{
...previousState,
todos : [...previousState.todos, {text : action.text, done : false},};
}
if(action.type === COMPLETE_TODO) {
return {
...previousState
todos: previousState.todos.map((todo, index) => {
if(index === action.index){
return {...todo, done: true};
}
return todo;
})}
}
if(action.type === SHOW_COMPLETE){
return {
...previousState,
filter : "COMPLETE",
}
}
if(action.type === SHOW_ALL){
return {
...previousState,
filter : "ALL",
}
}
//previousState.push('');
return previousState;
}
<action.js>
export const ADD_TODO = "ADD_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
//{type : ADD_TODO, text : '할일'}
export fuction addTodo(text){
return{
type: ADD_TODO,
text,
};
}
//{type : COMPLETE_TODO, index : 3}
export function completeTodo(index){
return {
type: COMPLETE_TODO
index
}
}
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};
}
<index.js>
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from "./redux/store";
import {addTodo, complete, showComplete} from ./redux/action;
store.subscribe(()=>{
console.log(store.getStore());
});
//console.log(store)
store.dispatch(addTodo("할일"));
store.dispatch(completeTodo(0));
store.dispatch(showComplate(0));
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
reducer안에 있는 로직들이 app의 state가 많아질수록 복잡해진다. redux는 단일 store이기 때문에 reducer안에 있는 내용을 분할하는 능력이 필요하다.
다음과 같이 합칠 수 있는데
<reducer.js>
```javascript
import {ADD_TODO, COMPLETE_TODO, SHOW_ALL, SHOW_COMPLETE} from "./actions";
import {combineReducers} from "redux";
//state
//{todos : [{text: '코딩', done: false}, {text : '점심 먹기', done:true}], filter : 'ALL'}
const initialState = {todos : [], filter: 'ALL'}
const todosInitialState = initialState.todos;
const fileterInitialState = initialState.filter;
const reducer = combineReducers({
todos: todosReducer,
filter : filterReducer
})
export default reducer;
function todoReducer(previousState = todosInitialState , 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;
});
}
//previousState.push('');
return previousState;
}
function filterReducer(previousState = initialState , action){
if(action.type === SHOW_COMPLETE){
return "COMPLETE";
}
if(action.type === SHOW_ALL){
return "ALL";
}
//previousState.push('');
return previousState;
}
<store.js>
import {createStore} from "redux";
import todoApp from "./reducers";
const store = createStore(todoApp);
export default store;
폴더를 만들어서(redux/reduce/)
<todos.js>
import{ADD_TODO, COMPLETE_TODO} from "../action/todos
const initialState = [];
function todoReducer(previousState = initialState , 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;
});
}
//previousState.push('');
return previousState;
}
<filter.js>
```javascript
import {SHOW_COMPLETE, SHOW_ALL} from '../actions/filter'
const initialState = 'ALL'
function filter(previousState = initialState , action){
if(action.type === SHOW_COMPLETE){
return "COMPLETE";
}
if(action.type === SHOW_ALL){
return "ALL";
}
//previousState.push('');
return previousState;
}
<reducer.js>
import {combineReducers} from "redux";
import todos from './todos';
import filter from './filter';
const reducer = combineReducers({
todos,
filter,
})
export default reducer;
Redux를 react에 연결하기
단일 store를 만들고, subscribe와 get State를 이용하여 변경되는 state 데이터를 얻어, props로 계속 아래로 전달