[#2] redux-toolkit-todo
redux-toolkit-todo
이번 포스팅에서는 TodoApp에서 사용되는 모든 action
을 정의해보도록 하겠습니다. 이전 시리즈에서 TodoApp을 만들어보셨다면 어떤 기능들이 있는지 아실겁니다. 모르셔도 큰 상관은 없습니다.
TodoApp에서 사용되는 기능들은 다음과 같습니다.
- 새로운 Todo 추가하기
- Todo의
done
상태 변경하기 - Todo의
text
상태 변경하기 - Todo 삭제하기
done
상태에 따라 목록 필터링하기done: true
인 목록 제거하기- Todo 목록의
done
상태 변경하기
현재 src/state/todos.js
는 아래와 같습니다.
// src/state/todos.js
import { createSlice } from '@reduxjs/toolkit'
let uniqId = 0
const todosSlice = createSlice({
name: 'todos',
initialState: {
filterType: 'all',
items: [],
},
reducers: {
add: {
reducer: (state, action) => {
state.items.push(action.payload)
},
prepare: text => {
return {
payload: {
id: ++uniqId,
done: false,
text,
},
}
},
},
},
})
export const { add } = todosSlice.actions
export default todosSlice.reducer
action
들 구현하기
이제부터 하나씩 구현해보겠습니다.
1. 새로운 Todo 추가하기
이는 이미 구현되어 있는 기능입니다. add
가 그 역할을 합니다.
2. Todo의 done
상태 변경하기
이 기능의 action
을 check
라고 정의하겠습니다. add
밑에 다음과 같이 구현합니다.
check: (state, action) => {
const { id, checked } = action.payload
state.items = state.items.map(todo =>
todo.id === id
? { ...todo, done: checked }
: todo
)
}
변경되는 아이템의 id
와 체크 상태인 checked
값을 받아서 적용해줍니다. immer
를 사용하고 있기 때문에, state.items =
와 같이 할당해줘야 변경됩니다. 참고로 reducer
가 어떤 값을 리턴하게 되면, 이는 새로운 state
setter로 동작하게 되므로 화살표 함수를 사용하실때 유의하셔야 합니다. 예를 들어
clearCompleted: (state, action) => state.items.filter(todo => !todo.done)
위와 같이 구현한다면, clearCompleted
액션의 reducer
결과는 필터링된 state.items
가 됩니다. 원래의 initialState
가 객체인데 배열로 바뀌어 버리는 것입니다. 이는 의도하지 않은 동작이기 때문에 주의해야 합니다.
3. Todo의 text
상태 변경하기
이는 done
상태를 변경하는 것과 유사합니다. check
밑에 edit
으로 아래와 같이 추가합니다.
edit: (state, action) => {
const { id, text } = action.payload
state.items = state.items.map(todo =>
todo.id === id
? { ...todo, text }
: todo
)
}
4. Todo 삭제하기
계속 비슷합니다. edit
밑에 remove
을 아래와 같이 추가합니다.
remove: (state, action) => {
const id = action.payload
state.items = state.items.filter(todo => todo.id !== id)
}
5. done
상태에 따라 목록 필터링하기
이는 filterType
의 상태를 변경하면 됩니다. filter
를 remove
밑에 추가합니다.
filter: (state, action) => {
state.filterType = action.payload
}
6. done: true
인 목록 제거하기
filter
밑에 clearCompleted
를 추가합니다.
clearCompleted: state => {
state.items = state.items.filter(todo => !todo.item)
}
7. Todo 목록의 done
상태 변경하기
모두 done: true
로 만들거나 done: false
로 만드는 기능입니다. checkAll
로 추가하겠습니다.
checkAll: state => {
const done = action.payload
state.items = state.items.map(todo => ({
...todo,
done,
}))
}
정리
TodoApp에서 사용하는 모든 기능들을 구현했습니다. 이제 이 action
들을 export
해줍시다.
// 하단
export const {
add,
check,
edit,
remove,
filter,
clearCompleted,
checkAll,
} = todosSlice.actions
액션들을 모두 export
했습니다. 따로 action type
, action creator
, reducer
들을 구현하지 않고 오직 reducer
들만 구현하면 됩니다! 이제 리액트 컴포넌트는 이 액션들을 import
하고 dispatch
해주면 reducer
가 동작하게 될 것입니다.
지금까지 구현된 모습은 아래와 같습니다.
// src/state/todos.js
import { createSlice } from '@reduxjs/toolkit'
let uniqId = 0
const todosSlice = createSlice({
name: 'todos',
initialState: {
filterType: 'all',
items: [],
},
reducers: {
add: {
reducer: (state, action) => {
state.items.push(action.payload)
},
prepare: text => {
return {
payload: {
id: ++uniqId,
done: false,
text,
},
}
},
},
check: (state, action) => {
const { id, checked } = action.payload
state.items = state.items.map(todo =>
todo.id === id
? { ...todo, done: checked }
: todo
)
},
edit: (state, action) => {
const { id, text } = action.payload
state.items = state.items.map(todo =>
todo.id === id
? { ...todo, text }
: todo
)
},
remove: (state, action) => {
const id = action.payload
state.items = state.items.filter(todo => todo.id !== id)
},
filter: (state, action) => {
state.filterType = action.payload
},
clearCompleted: state => {
state.items = state.items.filter(todo => !todo.done)
},
checkAll: (state, action) => {
const done = action.payload
state.items = state.items.map(todo => ({
...todo,
done,
}))
}
},
})
export const {
add,
check,
edit,
remove,
filter,
clearCompleted,
checkAll,
} = todosSlice.actions
export default todosSlice.reducer
이제부터 컴포넌트들을 셋팅해보도록 하겠습니다.
컴포넌트 기본설정
현재 src
에 App.js
와 index.js
가 있습니다. 우선 index.js
에서, Provider
를 이용해 store
를 등록해줘야 합니다. 이는 기존 redux
에서와 똑같습니다.
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './store'
import { Provider } from 'react-redux'
import 'todomvc-app-css/index.css'
ReactDOM.render(
<React.StrictMode>
<Provider store={ store }>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
)
그 다음, TodoApp
의 기본 컴포넌트들을 만들어보겠습니다. src/components
디렉터리를 먼저 생성한 뒤, 이전 시리즈에서처럼 Header
Main
Footer
Info
컴포넌트들을 구성해보겠습니다. 사실 Main
컴포넌트는 따로 자식컴포넌트인 Todo
컴포넌트를 가질 것을 생각해보면, 굳이 각 컴포넌트별로 디렉터리를 만들 필요는 없기 때문에 이번엔 Main
을 제외하고 나머지는 .js
파일로 생성해주도록 하겠습니다.
// src/components/Header.js
function Header() {
return (
<header className="header">
<h1>todos</h1>
<input className="new-todo" placeholder="What needs to be done?" autoFocus />
</header>
)
}
export default Header
// src/components/Footer.js
function Footer() {
return (
<footer className="footer">
<span className="todo-count"><strong>0</strong> item left</span>
<ul className="filters">
<li>
<a className="selected" href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button className="clear-completed">Clear completed</button>
</footer>
)
}
export default Footer
// src/components/Info.js
function Info() {
return (
<footer className="info">
<p>Double-click to edit a todo</p>
<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
<p>Created by <a href="http://todomvc.com">you</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
)
}
export default Info
이제 Main
컴포넌트는 디렉터리를 생성해주고 index.js
로 만듭니다. 그 전에 Todo
컴포넌트를 만들겠습니다.
// src/components/Main/Todo.js
function Todo(props) {
return (
<li className="completed">
<div className="view">
<input className="toggle" type="checkbox" checked />
<label>Taste JavaScript</label>
<button className="destroy" />
</div>
<input className="edit" value="Create a TodoMVC template" />
</li>
)
}
export default Todo
이제 Main
컴포넌트를 구현합니다.
// src/components/Main/index.js
import Todo from './Todo'
function Main() {
return (
<section className="main">
<input id="toggle-all" className="toggle-all" type="checkbox" />
<label htmlFor="toggle-all">Mark all as complete</label>
<ul className="todo-list" />
</section>
)
}
export default Main
아직 자식 컴포넌트가 없기 때문에 ul
은 self-closing으로 구현합니다.
마지막으로, 이를 하나로 합친 App
컴포넌트를 아래와 같이 수정합니다.
// src/App.js
import Header from './components/Header'
import Main from './components/Main'
import Footer from './components/Footer'
import Info from './components/Info'
function App() {
return <>
<section className="todoapp">
<Header />
<Main />
<Footer />
</section>
<Info />
</>
}
export default App
이제 npm start
를 하시면 아래와 같은 화면이 보입니다.
정리
지금까지 밑그림을 모두 그려봤습니다. 이제 색칠만 하면 됩니다. 리액트는 hook API가 나온 이후로 써드파티 생태계 역시 hook에 맞춰 진화하고 있는 것 같습니다. redux 역시 예외가 아닙니다.
redux는 redux-toolkit이 아닌 redux 자체에 이미 hook API가 구현되어있습니다. 그리고 redux에서는 앞으로 redux-toolkit을 사용하라고 강력히 권고하고 있습니다. 공식문서의 가장 첫번째 챕터인 introduction - Getting Started With Redux 페이지를 보면
위와 같이 Redux Toolkit에 대한 소개가 첫번째 페이지에 나오며, 공식적으로 권고하고 있습니다. redux-toolkit은 전통적인 방법으로도 redux를 사용할 수도 있지만, 일반적으로 useSelector
훅과 useDispatch
훅을 이용하여 쉽게 사용합니다.
다음 포스팅에서는 기능을 구현해보면서 useSelector
, useDispatch
그리고 createSelector
에 대해 알아보겠습니다.
Author And Source
이 문제에 관하여([#2] redux-toolkit-todo), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@undefcat/2-redux-toolkit-todo저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)