[Redux] redux 중급 - 문자열 입력/삭제 및 화면에 구현하기

31226 단어 reduxredux

1. Redux를 활용한 문자열 입력/삭제 및 화면 구현

문자열을 입력받아 화면에 나타나도록 하는 logic을 구현해본다.

2. 기본 구조 구성하기

기본적인 store, reducer 등 redux 구조 및 HTML을 구성한다.

2-1. HTML template 구성

화면을 먼저 구성하고, 각 tag 속성을 정의하여 준다.

<body>
    <h1>To Do LIST</h1>
    <form>
      <input type="text" placeholder="Write to do" />
      <button>Add</button>
    </form>
    <ul></ul>
  </body>
  • form tag 안에 input / button을 넣는다.
  • input tag를 통해 문자열을 입력받고, button을 통해 이벤트를 발생시킨다.
    ※ form tag 전부를 addEventListener(submit) 발생시키도록 구성하여, 문자열입력과 버튼클릭 모두 이벤트 발생 및 서로 연결할 수 있도록 구성한다.

2-2. 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) => {
  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로 구성하여 준다.

2-3. addEventListener (Event 설정 및 logic 연결)

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한다.

  • ADD_ToDO type을 전달하여 ADD_TODO 분기처리를 해준다(입력받은 문자열 추가).
  • 이때 입력받은 문자열은 dispatch와 함께 전달되며, input.value를 통해 전달 및 toDO 변수에 최종 저장된다.
  • onSubmit은 기본적으로 하위 tag들의 이벤트들(특히 onClick)을 감지하고, 이에 따른 하위 tag들의 value 값들을 확보할 수 있는 hooks의 일종이다.

※ prventDefault는 브라우저에서 이벤트 실행 시 자체적으로 동작하는 이벤트들을 강제적으로 막아주는 기능이다.

※ dispatch를 통해 action 뿐만 아니라 다른 여러 객체나 변수들도 전달할 수 있음을 기억한다.

2-4. action을 log 출력하여 출력 확인해보기

console.log(action)

전달받은 action을 log 출력해서 어떤 인자들이 출력되는지 확인해본다.

dispatch 받은 action 인자는 type과 text 모두 전달받았다.
특히 text는 input.value, 즉 입력받은 문자열이 그대로 전달받았음을 알 수 있다.

이를 활용하여 문자열을 화면에 구현하거나 삭제하는 기능을 추가 구현해본다.

3. state 변화를 반영하고 이를 subscribe 및 화면구현

변화한 state를 subscribe를 통해 화면에 구현한다(log 출력).

3-1. subscribe

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 해준다.

3-2. log 확인

store.subscribe(() => console.log(store.getState()))

위 reducer logic을 통해 변화한 state를 받아오면, 이를 subscribe해서 logic을 출력해본다.

기존 state인 공배열 상태에서 변화한 객체들이 배열(state)에 누적되는 것을 살펴볼 수 있다.

  • state 배열에 추가한 객체들이 누적된다.
  • 누적된 객체들은 배열의 한 인덱스 공간을 차지(=인덱싱)하면서 저장된다.
  • state의 getState, 즉 현재 state는 누적되어있는 객체들이 저장된 배열 상태이다.

3-3. 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의 흐름은 아래와 같이 작동한다.

  • add버튼을 누르면 submit 이벤트가 작동하면서 subscribe를 통해 paintTexttoView 함수가 실행된다.
  • getState를 통해 변화한 상태값을 받아온다(action.text가 누적된 state 배열).
  • 해당 상태값의 요소들을 loop하면서 li tag를 생성하고 화면에 구현한다(innerText).
  • 이 생성한 li 속성들을 ul tag 하위에 종속시켜준다(appendChild).

※ 받은 객체를 그대로 loop(forEach)할 경우 누적된 배열상태를 그대로 복사하므로, 최근에 반영한 요소 값만 온전히 생성하도록 기존의 innerHTML 속성들을 공배열화.

3-4. 구현된 화면 확인

logic이 정상적으로 작동하는지 확인해본다.

3-5. (참조) reducer return 순서에 따른 구현 화면

return 하는 객체의 순서를 바꾸면 화면에 구현되는 모습도 바꿀 수 있다.

case ADD_TODO : return [{ text : action.text, id : action.id }, ...state,  ]

위 코드처럼 return 하는 배열의 모습을 새로 생성된 객체 - 기존의 state 배열 순으로 하면, 최종적으로 getState를 통해 전달받는 구조도 새로운 객체 - 기존 stae({...state}) 순이므로 해당 순서 그대로 화면에 구현한다.

return할 때는 모든 배열요소를 전달해주므로, 순서와 함께 ul.innerHTML= ""을 선행해야 변화 요소만 화면에 구현해줄 수 있다는 것을 기억하자.

4. 화면에 구현된 문자열 삭제하기

화면에 문자열이 생성될 때 마다 DELETE 버튼을 생성하고,
해당 버튼을 누르면 삭제하는 logic을 구성한다.

4-1. 문자열 생성하면서 button 생성

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로 하도록 구성해준다.

4-2. Delete button과 삭제 항목의 id 연결

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 이벤트 실행 시

  • e.target을 통해 event가 실행되는 tag 속성을 받아올 수 있다.
  • e.target.parentNode.id를 통해 해당 tag가 종속된 부모 tag인 li tag id 값을 확보할 수 있다.
  • 확보한 id 값을 이후 삭제하는 logic을 구성하도록 설정한다.

4-3. 삭제하는 action을 dispatch 및 reducer 내부 logic 구성

이벤트 발생 시 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을 구현해야 하는지 생각해본다.

4-4. array.filter를 통한 새로운 객체 반환 구조(삭제 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을 구현하게 되며

  • state.filter를 통해 현재 나타난 배열 상태에서 조건을 만족하는 배열만 return하고 이를 화면에 구현하게 된다.

  • forEach는 배열의 요소를 모두 loop하고, 우리가 활용할 조건은 id값이므로 이를 염두하면서 조건을 처리한다.
  • 조건을 만족하는 요소만 return하여 화면에 구현하면 되는데, id값이 다른 요소들만 state에 반영한다면 id값을 제외한 나머지 요소들이 state에 나타날 것이다.
  • 이 state는 id값을 제외하므로, 삭제된 효과를 가진다.

4-5. 삭제 logic 확인

logic이 정상적으로 작동하는지 확인한다.
(※ 화면 상에서는 second 항목이 삭제된 모습)

5. 참조링크

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

좋은 웹페이지 즐겨찾기