Reducer
: Action이 발생하면, Action을 실제로 처리하는 역할을 하는 함수
= 입력에 어떤 처리를 해서, 원하는 결과로 축소시키는 과정
[역할] Redux State에 변화를 주는 역할
function appReducer(state = initialState, action) {
switch(action.type) {
case 'PUSH_ITEM':
return [...state, action.item];
case 'POP_ITEM':
const newState = [...state]; // (state 변화가 아닌) 새로운 state를 생성!
newState.pop();
return newState;
default:
return state;
}
}
Rules of Reducer
- Only calculate the new state value based on the state and action arguments.
: 새로운 state는 파라미터로 받은 현재 state와 Action 객체를 기반으로 생성해야 한다.
(외부의 다른 값을 참조해서 state 변화주는 것 X)
→ pure function을 위한 규칙 - Not allowed to modify the existing state.
Instead, make immutable updates.
: 현재 state를 조작할 수 없으며, 새로운 state를 만들어 immutalbe 업데이트를 해야 한다. - Not do any asynchronous logic or other "side effects".
: 비동기 로직이나 사이드 이펙트는 허용하지 않는다.
예) Reducer 내에서 서버와 통신해서 데이터 받아오는 등의 동작(X)
*side effect: reducer 외부에서 보여질 수 있는 상태의 변경 또는 동작
(콘솔 로그 출력, 파일 저장 등 순수함수의 return 값과 무관한 것들)
=> Predictable (예측 가능) 위해!
Immutable Update (불변적 업데이트)
: 현재 state를 변경하지 않고 새로운 state를 만들어 업데이트하는 방식
- Redux Toolkit을 사용하면 복잡한 객체에 대해서도 손쉽게 immutable update를 처리할 수 있다.
combineReducers()
: 여러 개의 리듀서들을 하나로 모으는 역할
=> Root Reducer
: 이렇게 모은 리듀서들이 하나로 합쳐진 것
import { combineReducers } from 'redux';
import postReducer from './reducers/postReducer';
import commentReducer from './reducers/commentReducer';
const rootReducer = combineReducers({ // postReducer와 commentReducer를 합침
post: postReducer, // 객체 형태: 객체의 키 = RootState의 키
comment: commentReducer
});
export default rootReducer;
[실습] TODO 애플리케이션에 MEMO 기능 추가하기
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>처음 만난 리덕스 - TODO</title>
</head>
<body>
<h3>오늘 할 일</h3>
<ul id="todo-list"></ul>
<div>
<input id="input-text"/>
<button id="add-button">할 일 추가</button>
<button id="remove-button">할 일 삭제</button>
<button id="remove-all-button">모두 삭제</button>
<button id="logging-state">State Logging</button>
</div>
<h3>메모</h3>
<ul id="memo-list"></ul>
<div>
<input id="input-memo-text"/>
<button id="add-memo-button">메모 추가</button>
<button id="remove-memo-button">메모 삭제</button>
</div>
<script>
// 액션 타입을 별도로 선언해 코드의 반복을 줄임
// TODO 관련 ACTION Type
var ACTION_TYPE_ADD_TODO = "ADD_TODO";
var ACTION_TYPE_REMOVE_TODO = "REMOVE_TODO";
var ACTION_TYPE_REMOVE_ALL = "REMOVE_ALL";
// MEMO 관련 ACTION Type
var ACTION_TYPE_ADD_MEMO = "ADD_MEMO";
var ACTION_TYPE_REMOVE_MEMO = "REMOVE_MEMO";
var todoInitialState = []; // 리턴값이 없으면 에러 발생 -> 초기값 무조건 필요!
var memoInitialState = [];
function todoReducer(state = todoInitialState, action){
switch(action.type){
case ACTION_TYPE_ADD_TODO:
return state.concat(action.text);
case ACTION_TYPE_REMOVE_TODO:
return state.slice(0, -1); // slice 함수를 사용해 배열의 마지막 아이템 하나를 삭제
case ACTION_TYPE_REMOVE_ALL:
return [];
default:
return state;
}
}
function memoReducer(state = memoInitialState, action){
switch(action.type){
case ACTION_TYPE_ADD_MEMO:
return state.concat(action.text);
case ACTION_TYPE_REMOVE_MEMO:
return state.slice(0, -1);
default:
return state;
}
}
function loggerMiddleware({getState}){ // 구조분해할당: 파라미터 중 getState만 꺼내서 사용
return (next) => (action) => {
console.log("dispatch 예정 action", action);
// Middleware chain에 있는 다음 dispatch 함수를 호출
const returnValue = next(action);
console.log("dispatch 이후 state", getState());
return returnValue;
};
}
var rootReducer = Redux.combineReducers({
todo: todoReducer,
memo: memoReducer,
});
var store = Redux.createStore(
rootReducer,
Redux.applyMiddleware(loggerMiddleware)
);
var todoListElem = document.getElementById("todo-list");
var memoListElem = document.getElementById("memo-list");
var inputElem = document.getElementById("input-text");
var inputMemoElem = document.getElementById("input-memo-text");
function render() {
// 이전 TODO 목록 초기화 (아이템이 중복으로 쌓이지 않기 위함)
todoListElem.innerHTML="";
memoListElem.innerHTML="";
// TODO 목록 렌더링
store.getState().todo.forEach((todo) => { // 각 state 접근시, root reducer 만들 때 사용한 키 값 사용
const todoListItemElem = document.createElement("li");
todoListItemElem.textContent=todo;
todoListElem.appendChild(todoListItemElem);
});
// MEMO 목록 렌더링
store.getState().memo.forEach((todo) => {
const memoListItemElem = document.createElement("li");
memoListItemElem.textContent=todo;
memoListElem.appendChild(memoListItemElem);
});
}
render();
store.subscribe(render); // Redux Store의 state가 변경될 때마다 render 함수 호출
function addTodoActionCreator(text){
return {
type: ACTION_TYPE_ADD_TODO,
text: text,
};
}
function removeTodoActionCreator(){
return {
type: ACTION_TYPE_REMOVE_TODO,
};
}
function removeAllActionCreator(){
return {
type: ACTION_TYPE_REMOVE_ALL,
};
}
function addMemoActionCreator(text){
return {
type: ACTION_TYPE_ADD_MEMO,
text: text,
};
}
function removeMemoActionCreator(){
return {
type: ACTION_TYPE_REMOVE_MEMO,
};
}
document
.getElementById("add-button")
.addEventListener("click", function(){
// Action을 실제로 dispatch
store.dispatch(addTodoActionCreator(inputElem.value));
// Input 초기화
inputElem.value = "";
});
document
.getElementById("remove-button")
.addEventListener("click", function(){
store.dispatch(removeTodoActionCreator());
});
document
.getElementById("remove-all-button")
.addEventListener("click", function(){
store.dispatch(removeAllActionCreator());
});
document
.getElementById("logging-state")
.addEventListener("click", function(){
console.log("현재 state", store.getState());
});
document
.getElementById("add-memo-button")
.addEventListener("click", function(){
store.dispatch(addMemoActionCreator(inputMemoElem.value));
inputElem.value = "";
});
document
.getElementById("remove-memo-button")
.addEventListener("click", function(){
store.dispatch(removeMemoActionCreator());
});
</script>
</body>
</html>
'Web > React' 카테고리의 다른 글
[처음 만난 리덕스] 7. Container (0) | 2023.12.31 |
---|---|
[처음 만난 리덕스] 6. Redux와 UI (1) | 2023.12.25 |
[처음 만난 리덕스] 4. Action (1) | 2023.12.25 |
[처음 만난 리덕스] 3. Store (1) | 2023.12.25 |
[처음 만난 리덕스] 2. Redux 시작하기 (0) | 2023.12.25 |