노마드강의의 초보자를 위한 리덕스 101을 복습하며 정리하려고 포스팅한다.
react에 redux를 사용하기 전 바닐라로 조금 더 쉽게 개념을 잡아보려고한다. 생략된 개념이 있을 수 있으므로 이해가 가지않는다면
Vanilla Redux-(1) Counter를 참고하길바란다.
심플하다.
h1
,input
,button
그리고ul
로 구성되어있다.
form
이submit
되면onSubmit
함수가 실행이되고input
에서
값을 가져온다음createToDo
함수를 호출하고
input
에서 얻은 텍스트를 인자로 보낸다.
그러면list item
을 만들어주고list
의innerText
를
인자로받은toDo
로 바꿔준다음list
에append
시키는 구조이다.
하지만 지금 코드는
어플리케이션이 데이터를 가지고 있지 않고 HTML을 수정해주는 것 뿐이다.
이전 섹션에 배운 것을 연습삼아 Redux를 이용해보자.
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를 import
해주고
import {createStore} from "redux";
이전에 배운 reducer와 store함수를 만들어줬다.
const reducer = (state = [], action = []) => {}
const store = createStore(reducer);
그리고 각각의 action들을 만들고
const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";
reducer함수에 swtich문
을 이용하여 (뭐가됐든 우선)return
하게 만들었다.
const reducer = (state = [], action) => {
console.log(action);
switch(action.type){
case ADD_TODO:
return [];
case DELETE_TODO:
return [];
default:
return state;
}
};
마지막으로 사용자가 submit
할때 리스트를 만들고 리스트 아이템들을
리스트에 넣어주는 createToDo를 호출하는대신 dispatch
시켜줬다.
현 케이스에서는 action말고도 text
가 필요하다.
store.dispatch({ type: ADD_TODO, text: toDo });
이것을 콘솔로 확인해보자.
input에 text를 입력하고 add버튼을 눌렸을때 콘솔로그가 실행되며
dispatch로 인해store.dispatch({ type: ADD_TODO, text: toDo });
action의type
과text
가 맞게 나오는걸 확인할 수 있다.
여기서 꼭 이해하고 넘어가야하는게 있다
Redux 공식문서 Three Principles(세가지 원칙) 카테고리를보면
Changes are made with pure functions라는 소제목이 있다.
내용을 살펴보면
Remember to return new state objects,
instead of mutating the previous state.
이전 상태를 변경하는 대신 새 상태 개체를 반환해야 합니다.
즉, state를 mutate하는게 아니라 새로운 objects를 리턴해야한다.
상태를 수정하는 것이 아니라 새로운 것을 리턴해야된다는 의미이다.
앞서 말한 것처럼 state.push()같이 state를 변형하는것은 하지않을것이다.
return state.push(action.text)
Es6 spread 문법을 이용해 기존의 state를 똑같이 가져와
새로운 array로 return할 수 있다.
return [...state, { text: action.text }];
콘솔로 확인해보자
subscribe을 이용해 변화가 생길때 콘솔로그를 실행하게된다.
사진에서와같이 text를 입력할때마다 기존의 state값을 가져와 입력한text를 포함한 새로운 array를 출력하는것을 확인할 수 있다.
store.subscribe(() => console.log(store.getState()));
이제 delete하는 기능을 추가할건데 현재는 text값밖에 없어 새로운 array를 생성할때 Date.now()를 이용해 고유값을 추가해줄것이다.
case ADD_TODO:
return [...state, { text: action.text, id: Date.now() }];
삭제 기능을 만들기전 onSubmit
함수에서 dispatch를 하는것이 아닌
각각의 addToDo
, deleteToDo
함수를 만들겠다.
먼저 addToDo
함수는 text(input의 value)
를 파라미터로 받고
store.dispatch를 호출해준다.
const addToDo = text => {
store.dispatch({ type: ADD_TODO, text });
}
그리고 todo들을 paint해줘야하는데 paintToDos
함수를 만들고
이 함수를 subcribe해준다.
paintToDos
함수가 실행되면 getState
를 가져와 toDos
변수에 담고
리스트를 생성해 text값과 id값을 지정해주고 append
시켜준다.
const paintToDos = () =>{
const toDos = store.getState();
ul.innerText ="";
toDos.forEach(toDo => {
const li = document.createElement('li');
li.id = toDo.id;
li.innerText = toDo.text;
ul.appendChild(li);
})
}
store.subscribe(paintToDos);
추가적으로 reducer에서 ADD_TODO
일때 리턴하는 순서를 수정해주겠다. 이렇게 수정해준다면 todo
가 새롭게 추가될때마다 array
의 첫부분에 있게된다.
return [{ text: action.text, id: Date.now() }, ...state];
이제 삭제기능을 만들건데 list item이 생성될때마다 삭제버튼 또한 필요하다. 버튼을 생성하고 클릭이벤트를 등록하였다. deleteToDo
함수가 실행되게끔
const paintToDos = () =>{
const toDos = store.getState();
ul.innerText ="";
toDos.forEach(toDo => {
const li = document.createElement('li');
const btn = document.createElement('button');
btn.innerText = "DEL";
btn.addEventListener('click', deleteToDo);
li.id = toDo.id;
li.innerText = toDo.text;
li.appendChild(btn);
ul.appendChild(li);
})
}
이제 deleteToDo
함수를 만들어보자.
자 생각해보자.
우리는 list item
에 고유의 id
값을 넣어주었다. 현재 버튼에는 eventListener
가 등록되어있고
event
객체의 target.parentNode
를 이용하면
고유의 id
를 가진 해당 list item
에 접근할 수 있다.
const deleteToDo = e => {
const id = e.target.parentNode.id;
store.dispatch({ type: DELETE_TODO, id });
}
이렇게 addToDo
, deleteToDo
함수 생성하였고 추가적으로
action을 return
하는 함수를 생성할것이다.
대부분의 사람들이 주로 reducer 위에 추가한다고한다.
const addToDo = text => {
return {
type: ADD_TODO,
text
}
}
const deleteToDo = id => {
return {
type: DELETE_TODO,
id
}
}
그리고 기존의 함수를 수정해주겠다.
const dispatchAddToDo = text => {
store.dispatch(addToDo(text));
}
const dispatchDeleteToDo = e => {
const id = e.target.parentNode.id;
store.dispatch(deleteToDo(id));
}
자 조금 정리를 하자면 addToDo
, deleteToDo
함수는 오로지 action을 return
dispatchAddToDo
, dispatchDeleteToDo
함수는 addToDo
, deleteDo
함수의
return하는 object를 dispatch해준다.
이제 정말 삭제기능을 만들어볼 차례이다.
조건은
내가 삭제할,선택한 todo
를 삭제하되
내가 삭제할 todo
의 id
에 해당하지 않는 todo
들을 유지시키면된다.
Array.filter
를 사용해 조건을 작성하고 그 조건이 충족되면 해당 요소는 새로운 array
에 남게하면된다.
절~~대 Array.prototype.splice()
같이 기존의 배열을 수정하는것이아닌
새로운 array
값을 return
해야한다.
case DELETE_TODO:
return state.filter(toDo => toDo.id !== action.id );
자 이제 완성이다! 아래는 완성한 index.js
이다.
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:
//Never state mutation!!
//return state.push(action.text);
// Return something new
return [{ text: action.text, id: Date.now() }, ...state];
case DELETE_TODO:
return [];
default:
return state;
}
};
const store = createStore(reducer);
store.subscribe(() => console.log(store.getState()));
const dispatchAddToDo = text => {
store.dispatch(addToDo(text));
}
const dispatchDeleteToDo = e => {
const id = e.target.parentNode.id;
store.dispatch(deleteToDo(id));
}
const paintToDos = () =>{
const toDos = store.getState();
ul.innerText ="";
toDos.forEach(toDo => {
const li = document.createElement('li');
const btn = document.createElement('button');
btn.innerText = "DEL";
btn.addEventListener('click', deleteToDo);
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);