본 글은 노마더코더의 초보자를 위한 리덕스 101 강의를 들으며 작성한 글입니다.
Dataless System
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>Vanilla Redux</title>
</head>
<body>
<h1>To DOs</h1>
<form>
<input type="text" placeholder="write to do">
<button>ADD</button>
</form>
<ul></ul>
</body>
</html>
const form = document.querySelector("form");
const input = document.querySelector("input");
const ul = document.querySelector("ul");
const toDos = [];
const createToDo = (toDo) => {
const li = document.createElement("li");
li.innerText = toDo;
ul.appendChild(li);
};
const onSubmit = (e) => {
e.preventDefault();
const toDo = input.value;
input.value = "";
createToDo(toDo);
};
form.addEventListener("submit", onSubmit);
다음과 같은 toDo List를 생각해보자.
form의 input에 입력하면 ul에 li로 todo list가 추가되는 시스템이다.
우리의 시스템은 dataless이기 때문에 toDo 리스트를 "저장"할 수 없다.
그렇기에 새로고침을 하면 모두 사라진다.
"저장"을 하기 위해서는 우린 const toDos = [];로 배열을 선언하고 이 배열을 관리해주어야한다.
배열에 toDo를 추가하고 local storage에 추가하는 함수를 만들어서 처리하고
삭제할 때는 찾아서 삭제하고 배열이 수정될 때마다 toDos를 다시 그려주어야한다.
이는 물론 할 수 있지만 Redux는 dispatch, subscribe와 같은 함수를 이용해 이를 좀 더 쉽게 만들어준다.
import { createStore } from "redux";
const form = document.querySelector("form");
const input = document.querySelector("input");
const ul = document.querySelector("ul");
const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";
const reducer = (state = [], action) => {
console.log(action);
switch (action.type) {
case ADD_TODO:
return [];
case DELETE_TODO:
return [];
default:
return state;
}
};
const store = createStore(reducer);
const onSubmit = (e) => {
e.preventDefault();
const toDo = input.value;
input.value = "";
store.dispatch({ type: ADD_TODO, text: toDo });
};
form.addEventListener("submit", onSubmit);
처음 시스템에서 Redux를 이용하여 store와 reducer를 작성하였다.
reducer와의 소통은 action object를 통해 진행되기 때문에, action object에 text 속성을 추가하여 reducer에 전달해준다.
NEVER MUTATE STATE
잠깐 Redux의 3가지 원칙을 보자.
- State is single source of truth
- State is read-only
- Never mutate state, should return new state
마지막 원칙은 reducer는 return 값으로 state를 주지만 이는 이전의 state에 변형된 것이 아닌 새로운 state여야 한다는 뜻이다.
const example = [1]
example.push(2) //mutation
위와 같은 상황이 mutation이다. 즉, 원본 값에 수정을 가하여 사용하는 경우를 말한다.
그렇다면 우리는 무엇을 해야할까.
우리의 시스템인 ToDo list에서 state가 변경되는 경우는 크게 2가지이다 .
하나는 추가하는 경우이고, 다른 하나는 삭제하는 경우이다.
이 두 경우에 대해 원본 배열을 수정하는 것이 아닌 새로운 성분을 추가한, 혹은 선택된 성분이 제외된 새로운 배열을 만들어
reducer에서 return해주고 이 새로운 배열로 state가 update 되어야 하는 것이다.
import { createStore } from "redux";
const form = document.querySelector("form");
const input = document.querySelector("input");
const ul = document.querySelector("ul");
const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";
const addToDo = (text) => {
return {
type: ADD_TODO,
text,
};
};
const deleteToDo = (id) => {
return { type: DELETE_TODO, id };
};
const reducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return [{ text: action.text, id: Date.now() }, ...state]; //ES6 spread
case DELETE_TODO:
return state.filter((toDo) => toDo.id !== action.id);
default:
return state;
}
};
const store = createStore(reducer);
const dispatchAddToDo = (text) => {
store.dispatch(addToDo(text));
};
const dispatchDeleteToDo = (event) => {
const id = parseInt(event.target.parentNode.id);
store.dispatch(deleteToDo(id));
};
const paintToDos = () => {
ul.innerHTML = "";
const toDos = store.getState();
toDos.forEach((toDo) => {
const li = document.createElement("li");
const btn = document.createElement("button");
btn.innerText = "DELETE";
btn.addEventListener("click", dispatchDeleteToDo);
li.id = toDo.id;
li.innerText = toDo.text;
li.appendChild(btn);
ul.appendChild(li);
});
};
store.subscribe(paintToDos);
const onSubmit = (e) => {
e.preventDefault();
const toDo = input.value;
input.value = "";
dispatchAddToDo(toDo);
};
form.addEventListener("submit", onSubmit);
결과적으로 다음과 같은 코드가 만들어진다.
reducer 전에 위치한 addToDo, deleteToDo 함수는 Action Creator라고 칭하는데
action object를 return 해주기만 하는 함수이다. 이를 만들어서 관례상 reducer 앞쪽에 둔다.
form에서 input을 입력하면 addToDo에서 받아온 action 객체를 dispatchAddToDo를 통해
reducer에게 전달해주고, reducer는 ADD_TODO 부분에서 새로운 배열을 만들어 state를 업데이트한다.
(...state 부분을 ES6 spread라고 하는데, state 내부의 성분을 모두 꺼내놓는 것을 의미한다.)
redux는 store.subscribe를 통해 state가 업데이트되면 알아서 paintToDos 함수를 수행하고,
추가된 toDos가 화면에 paint된다.(= 그려진다)
delete 역시 비슷하게 동작하는데 Date.now()로 action 객체를 넣을 때 id를 정해주고
reducer에서 toDo list 생성 시 이 id를 li 태그의 id 값으로 활용한다.
btn에 이벤트 리스너를 달아주어 클릭 이벤트가 발생하면 dispatchDeleteToDo 함수를 실행한다.
dispatchDeleteToDo 함수 실행 시 reducer에서 해당 id를 가진 성분을 거르고(filter) 새로운 배열을 만들어 return한다.
이렇듯 기존 배열을 수정하는 것이 아닌 새로운 배열을 만들어 return 해주어야한다.
'Frontend > 상태관리' 카테고리의 다른 글
[React-Redux] mapDispatchProps (0) | 2022.01.14 |
---|---|
[React-Redux] connect, mapStateProps (0) | 2022.01.13 |
[Redux] Subscribe (0) | 2022.01.13 |
[Redux] Actions - Dispatch (0) | 2022.01.13 |
[Redux] Store, Reducer (0) | 2022.01.12 |