문자열을 입력받아 화면에 나타나도록 하는 logic을 구현해본다.
기본적인 store, reducer 등 redux 구조 및 HTML을 구성한다.
화면을 먼저 구성하고, 각 tag 속성을 정의하여 준다.
<body>
<h1>To Do LIST</h1>
<form>
<input type="text" placeholder="Write to do" />
<button>Add</button>
</form>
<ul></ul>
</body>
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) => {
switch(action.type){
case ADD_TODO : return []
case DELETE_TODO : return []
default : return state
}
}
const store = createStore(reducer)
위 코드와 같이 form , input, ul tag를 document.querySelector를 통해 연결해준다.
이후 store를 생성하고, 기본적인 reducer 구조를 만들어준다.
action에 대한 분기처리는 switch로 구성하여 준다.
Event를 연결해주고, action과 입력받은 문자열(text)를 모두 dispatch를 통해 전달하기
const onSubmit = e => {
e.preventDefault()
const toDO = input.value
input.value = ""
store.dispatch({type : "ADD_ToDo", text : toDO})
}
form.addEventListener("submit", onSubmit)
form 내부에서 일어나는 모든 이벤트(여기서는 ADD버튼을 클릭하였을 때의 submit 이벤트)에 대해 이벤트 설정을 해주고, onSubmit 함수를 호출하여 해당 logic을 작동한다.
onSubmit를 통해 action을 disptach한다.
※ prventDefault는 브라우저에서 이벤트 실행 시 자체적으로 동작하는 이벤트들을 강제적으로 막아주는 기능이다.
※ dispatch를 통해 action 뿐만 아니라 다른 여러 객체나 변수들도 전달할 수 있음을 기억한다.
console.log(action)
전달받은 action을 log 출력해서 어떤 인자들이 출력되는지 확인해본다.
dispatch 받은 action 인자는 type과 text 모두 전달받았다.
특히 text는 input.value, 즉 입력받은 문자열이 그대로 전달받았음을 알 수 있다.
이를 활용하여 문자열을 화면에 구현하거나 삭제하는 기능을 추가 구현해본다.
변화한 state를 subscribe를 통해 화면에 구현한다(log 출력).
dispatch를 통해 text 객체(변화 state) 전달하고, 이를 subscribe를 통해 연결한다.
const reducer = (state = [], action) => {
console.log(action)
switch(action.type){
case ADD_TODO : return [...state, { text : action.text } ]
case DELETE_TODO : return []
default : return state
}
}
새로운 객체를 반환할때 기존 state(...state)와 새로운 state 객체(text : action.text)를 반영하여 return 해준다.
store.subscribe(() => console.log(store.getState()))
위 reducer logic을 통해 변화한 state를 받아오면, 이를 subscribe해서 logic을 출력해본다.
기존 state인 공배열 상태에서 변화한 객체들이 배열(state)에 누적되는 것을 살펴볼 수 있다.
전달받은 text를 새로운 tag를 생성하면서 해당 innerText에 생성하고,
새 tag의 id(unique 인자)에 할당할 id값을 dispatch 항목에 추가해준다.
const reducer = (state = [], action) => {
console.log(action)
switch(action.type){
case ADD_TODO : return [...state, { text : action.text, id : action.id } ]
case DELETE_TODO : return []
default : return state
}
}
const onSubmit = e => {
e.preventDefault()
const toDO = input.value
input.value = ""
store.dispatch({type : "ADD_TODO", text : toDO, id: Date.now()})
}
action 인자를 전달할 때, dispatch를 통해 text와 id 값을 전달한다.
※ 이때 id값은 Date.now를 통해 생성해주었다.
const paintTextToView = () => {
const lists = store.getState()
ul.innerHTML = ""
lists.forEach(listItem => {
const li = document.createElement("li")
li.id = listItem.id
li.innerText = listItem.text
ul.appendChild(li)
})
}
store.subscribe(paintTextToView)
전달받은 객체(text, id 값)을 그대로 활용하여, li tag를 생성하면서(document.createElement("li") 화면에 입력받은 text가 나타나도록 구성한다.
위 logic의 흐름은 아래와 같이 작동한다.
※ 받은 객체를 그대로 loop(forEach)할 경우 누적된 배열상태를 그대로 복사하므로, 최근에 반영한 요소 값만 온전히 생성하도록 기존의 innerHTML 속성들을 공배열화.
logic이 정상적으로 작동하는지 확인해본다.
return 하는 객체의 순서를 바꾸면 화면에 구현되는 모습도 바꿀 수 있다.
case ADD_TODO : return [{ text : action.text, id : action.id }, ...state, ]
위 코드처럼 return 하는 배열의 모습을 새로 생성된 객체 - 기존의 state 배열 순으로 하면, 최종적으로 getState를 통해 전달받는 구조도 새로운 객체 - 기존 stae({...state}) 순이므로 해당 순서 그대로 화면에 구현한다.
return할 때는 모든 배열요소를 전달해주므로, 순서와 함께 ul.innerHTML= ""을 선행해야 변화 요소만 화면에 구현해줄 수 있다는 것을 기억하자.
화면에 문자열이 생성될 때 마다 DELETE 버튼을 생성하고,
해당 버튼을 누르면 삭제하는 logic을 구성한다.
createElement button도 같이 생성해준다.
lists.forEach(listItem => {
const li = document.createElement("li")
const btn = document.createElement("button")
li.id = listItem.id
li.innerText = listItem.text
ul.appendChild(li)
btn.innerText = "DELETE the list"
li.appendChild(btn)
})
subscribe 하면서 문자열 생성과 동시에, button tag도 같이 생성해준다.
이 버튼 tag는 문자열과 마찬가지로 상위 tag를 li로 하도록 구성해준다.
Delete button과, Delete button이 생성된 상위 tag인 li의 id 값을 연결한다.
btn.innerText = "DELETE the list"
btn.addEventListener("click", (e) => {console.log(e.target.parentNode.id)})
li.appendChild(btn)
click 이벤트 실행 시
이벤트 발생 시 action을 dispatch 해주며 이때 action을 통해 id값을 전달한다.
실질적으로 해당 id 값을 삭제하는 logic은 reducer 내부에서 구성해준다.
btn.innerText = "DELETE the list"
btn.addEventListener("click", (e) => {
const id = e.target.parentNode.id
store.dispatch({ type : "DELETE_TODO", id })
})
li.appendChild(btn)
DELETE 버튼을 누르면 click 이벤트가 발생하며, 삭제할 요소의 id 값을 action dispatch를 통해 전달하는 구조를 구성해준다.
또한 DELETER 버튼을 눌렀을 때, reducer 내부에서 해당하는 logic을 동작할 수 있도록 reducer 에서 정의해준 삭제 type을 그대로 객체화하여( {type : "DELETE_TODO"} ) 전달해준다.
const reducer = (state = [], action) => {
console.log(action)
switch(action.type){
case ADD_TODO : return [{ text : action.text, id : action.id }, ...state, ]
case DELETE_TODO : return state.filter
default : return state
}
}
reducer 내부는 해당 action을 dispatch 받고, 전달받은 type과 id를 통해 어떤 삭제 logic을 구현해야 하는지 생각해본다.
삭제 logic을 구성하는 핵심 요소는 아래 두가지 요소이다.
- dispatch를 통해 전달받은 action의 type
- dispatch를 통해 전달받은 action의 id
이 요소를 활용하여 state를 직접 mutation하지 않고(push, attend 등),
새로운 객체 값(배열)을 반환해주는 filter 함수를 이용한다.
const reducer = (state = [], action) => {
console.log(action)
switch(action.type){
case ADD_TODO : return [{ text : action.text, id : action.id }, ...state]
case DELETE_TODO : return state.filter(deletedList => deletedList.id !== Number(action.id))
default : return state
}
}
위 코드처럼 삭제 type이 전달되어, 삭제 logic을 구현하게 되며
logic이 정상적으로 작동하는지 확인한다.
(※ 화면 상에서는 second 항목이 삭제된 모습)
array 관련 javascript method 공식문서(filter 참조)
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
event.preventDefault
https://pa-pico.tistory.com/20