Redux
Redux
[TOC]
๐What is Redux?
์ํ๋ฅผ ๊ด๋ฆฌํด์ฃผ๋ API์ด๋ฉฐ, ์ต๊ทผ React์ ์ ๋ชฉํด์ ๋จ๊ณ ์๋ ์ถ์ธ๋ผ Reduxํ๋ฉด React๋ง ์ฌ์ฉํ ์ ์์ ๊ฒ ๊ฐ์ง๋ง, Redux๋ Node ๋ชจ๋์ด๋ผ JS ๊ธฐ๋ฐ์ผ๋กํ๋ ๋ชจ๋ ํ๋ ์์ํฌ๋ ์ฌ์ฉ๊ฐ๋ฅํฉ๋๋ค.
๊ฐ๋จํ ํ๋ก๊ทธ๋จ์ ์๋ก ๋ค๋ฉดโฆ
์๋์ ํ๋ก๊ทธ๋จ์ ์ฌ์ฉ์์ ๋ฒํผ ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ํตํด์ add, minus์ด๋ฒคํธ๋ฅผ ๋ฐ์ ์์ผ count์ ๊ฐ์ ๋ณ๊ฒฝํ๋ ์์ ์ด๋ค.
const add = document.getElementById('add')
const minus = document.getElementById('minus')
const number = document.querySelector('span')
let count = 0
number.innerText = count
const updateText = () => {
number.innerText = count
}
const handleAdd = () => {
count = count + 1
updateText()
}
const handleMinus = () => {
count = count - 1
updateText()
}
add.addEventListener('click', handleAdd)
minus.addEventListener('click', handleMinus)
<body>
<button id="add">Add</button>
<span>0</span>
<button id="minus">Minus</button>
</body>
๊ฐ๋จํ๊ฒ ์ฝ๋์ ๋ํด ์ค๋ช ํ๋ฉด, html์ ์๋ add, minus๋ฒํผ์ ํด๋ฆญํ๋ฉด ๊ฐ๊ฐ ์์ฒญ์ ๋ง๊ฒ count๋ฅผ ์์ ํฉ๋๋ค.
ํ์ง๋ง ์ด๋ ๊ฒ ํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ์์ ํ ๋๋ง๋ค ๋ ๋๋ง์ด ๋ฐ์ํ๊ณ , ์ฝ๋๊ฐ ๊ธธ์ด์ง์๋ก ๊ด๋ฆฌํ๊ธฐ๋ ํ๋ค์ด์ง๋๋ค.
Redux์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ ์๋ก ๋ค๋ฉด MVC ํจํด์ ๋จ์ ์ธ ์๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ์ผ๋ก ๊ด๋ฆฌ๊ฐ ๋ณต์กํ ๋ถ๋ถ์ store์์ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ state๊ด๋ฆฌ๋ฅผ ์์ํ๊ฒ ๋์ ์ค๋๋ค.
๐How to use Redux?
์ Vanilla JS์ Redux๋ฅผ ํตํด์ผ๋ง state๋ณ๊ฒฝ ํ ์ ์๋๋ก ๋ณ๊ฒฝํ๋ค.
import { createStore } from 'redux'
const add = document.getElementById('add')
const minus = document.getElementById('minus')
const number = document.querySelector('span')
const countModifier = (count = 0, action) => {
if (action.type === 'ADD')
return count + 1
else if (action.type === 'MINUS')
return count - 1
else
return count
}
const countStore = createStore(countModifier)
const onChange = () => {
number.innerText = countStore.getState()
}
countStore.subscribe(onChange)
const addHandler = () => {
countStore.dispatch({ type: 'ADD' })
}
const minusHandler = () => {
countStore.dispatch({ type: 'MINUS' })
}
add.addEventListener('click', addHandler)
minus.addEventListener('click', minusHandler)
createStore์ ์ธ์ ๊ฐ์ผ๋ก Reducer๋ฅผ ์ค์ผํ๋ฉฐ, Reducer๋ฅผ ํตํด์์ผ๋ง state๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ค.
Reduer๋ ์ฌ์ฉ์์ ์ด๋ฒคํธ๋ฅผ ํตํด, ๋ณ๊ฒฝ๋ state๋ฅผ dispatch์ ๊ฐ์ฒด๋ก ๋ฐ์์ Reducer์ ๋ฏธ๋ฆฌ ์์ฑ๋ ํ๋ก์ธ์ค ๋ฐ๋ก ์ฒ๋ฆฌํ๋ค.
์ด ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฌด์กฐ๊ฑด Object ์์ type์ด ์์ด์ผ ํ๋ค.(์๋๋ฉด error ๋ฐ์)
event listener => dispatch(with type object) => onChange => Reducer
๐Reducer๋ mutate ์ฆ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ ๊ฒ์ ์ง์ํฉ๋๋ค. ์ฌ๊ธฐ์ mutate๋ array.push(blabla)๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ ๊ฑด๋ฐ, JS๋ ์์์ ํ์ (Number, String, Object ๋ฑ)์ ์ ์ธํ ๋ชจ๋ ๊ฒ์ด ๊ฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋์๋์ง ํ์ธ ํ๋ ๋ฐฉ๋ฒ์ด ๋ฐ์ดํฐ๋ฅผ ์ฐธ์กฐํ๊ณ ์๋ ์ฃผ์๊ฐ์ด ๋ฐ๋์๋์ง๋ฅผ ํ์ธํด Re-Rendering ํฉ๋๋ค.
๐What is Redux Toolkit?
state๋ฅผ ๊ด๋ฆฌํ๊ธฐ ํธํ๊ธฐ ์ํด ์ฌ์ฉํ๋ Redux๊ฐ ๋ง์ ์ฌ์ฉํด๋ณด๋ ์์ฑํด์ผ ํ๋ ์ฝ๋๋ค ๋๋ฌธ์ ์ฌ์ฉํ๊ธฐ ๋ ๋ณต์กํ๋ค๋ ์๊ฒฌ๋ค์ด ์๊ฒจ๋ฌ์ต๋๋ค.
yarn add @reduxjs/toolkit
npm install @reduxjs/toolkit
Toolkit์ ๊ฐ๋์ฑ ๋ฐ ์ฝ๋์์ฑ์ ๋ง์ ๋์์ ์ค๋๋ค.
// Original Redux
const ADD = 'ADD'
const DEL = 'DEL'
const addToDo = (text) => {
return {
type: ADD,
text
}
}
const deleteToDo = (id) => {
return {
type: DEL,
id: parseInt(id)
}
}
const reducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [{ text: action.text, id: Date.now() }, ...state]
case DEL:
return state.filter((toDo) => toDo.id !== action.id)
default:
return state
}
}
๊ธฐ์กด์ Redux์์๋ type์ ํต์ผ๋ ์๊ณ ๊ฐ๋จํ ํ๋ก๊ทธ๋จ์ ๋ง๋๋๋ฐ๋ ์ค๋ณต์ธ ์ฝ๋๊ฐ ๋ง์์์ต๋๋ค.
ํธ๋ฆฌํ๊ธฐ ์ํด์ ์ฌ์ฉํ๋๋ฐ ์ญ์ค์ ์ด๊ฒ๋ ๋ถํธํ ์ํฉ์ ๋๋ค.
ํ์ง๋ง Toolkit์ ์ด์ฉํ๋ฉด ์๋์ ๊ฐ์ด
import { createAction, createReducer } from '@reduxjs/toolkit'
// ToolKit
const addToDo = createAction('ADD')
const deleteToDo = createAction('DEL')
const reducer = createReducer([], {
[addToDo]: (state, action) => {
state.push({ text: action.payload, id: Date.now() })
},
[deleteToDo]: (state, action) =>
state.filter((toDo) => toDo.id !== parseInt(action.payload))
})
๊ฐ๋จํ ์ฝ๋๋ก ๋ฐ๋์์ต๋๋ค.
state๋ฅผ returnํ๋ ํจ์๋ฅผ ๋ฐ๋ก ๋ง๋ค์ง ์์๋ createAction()์ ํ ๋นํ ํธ์ถํ๋ฉด
{type: 'ADD', payload: 'blabla'}
JSONํ์์ผ๋ก state๋ฅผ ์ถ๋ ฅํด ์ค๋๋ค! ์ฌ๊ธฐ์ ๊ธฐ์กด์ Redux์ ๋ค๋ฅธ ์ ์ ๊ฐ๊ธฐ ๋ค๋ฅธ ์ด๋ฆ์ state๋ฅผ ์ฌ์ฉํ์ง ์๊ณ payload๋ผ๋ ์ด๋ฆ์ผ๋ก state๋ฅผ ์ ๋ฌํด ์ฃผ๊ฒ ๋ฉ๋๋ค
๋ switch-case๋ฌธ์ ์ฌ์ฉํ์ง ์์๋ ๋๋ ์ฅ์ ์ด ์์ด์ ์ฝ๋๊ฐ ๊ฐ๊ฒฐํด์ง๊ณ ,
๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ธ๋ฐ state.push๋ฅผ ํ๋ฉด mutate๊ฐ ๋ผ์ ์ค๋ฅ๊ฐ ๋ฐ์ํด์ผ ํ์ง๋ง node package์ค์ immer๋ผ๋ ์์ฒญ๋ ํจํค์ง๊ฐ ์๋๋ฐ ๊น์ ๋ณต์ฌ๋ฅผ ์ ๊ณตํด์ฃผ์ด์ spread์ฐ์ฐ์๋, ๊ฐ๋ฐ์๊ฐ ์ผ์ผํ ๋ณต์ฌํด์ฃผ์ด์ผ ํ๋ ๋ฒ๊ฑฐ๋ฌ์์ ๋์ ํด์ค๋๋ค.
๋ ๊ธฐ์กด์ return์ผ๋ก ๊ฐ์ ๋๊ฒจ์ฃผ๋ ๋ฐฉ์๋ ๊ทธ๋๋ก ์ ๊ณตํ๋ ์ํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํด๋ ๋์ง๋ง! return์ ํ ๊ฒฝ์ฐ ๊ธฐ์กด๊ณผ ๋์ผํ๊ฒ non-mutate๋ฅผ ์งํฅํ๊ธฐ ๋๋ฌธ์ new mutate๋ฅผ returnํด์ผํฉ๋๋ค.
ํ.์ง.๋ง ์ฌ๊ธฐ์ ๋ ํธํ ๊ธฐ๋ฅ์ toolkit์ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
const addToDo = createAction('ADD')
const deleteToDo = createAction('DEL')
const reducer = createReducer([], {
[addToDo]: (state, action) => {
state.push({ text: action.payload, id: Date.now() })
},
[deleteToDo]: (state, action) =>
state.filter((toDo) => toDo.id !== parseInt(action.payload))
})
const store = createStore(reducer)
export const actionCreator = {
addToDo,
deleteToDo
}
๊ธฐ์กด์๋ createAction์ ํตํด์ action์ ๋ง๋ค๊ณ , reducer์ state๋ง๋ค ์ผ์ผํ ์ ๋ ฅํด ์ค์ผ ํ๋๋ฐ
const toDos = createSlice({
name: 'toDosReducer',
initialState: [],
reducers: {
add: (state, action) => {
state.push({ text: action.payload, id: Date.now() })
},
remove: (state, action) =>
state.filter((toDo) => toDo.id !== parseInt(action.payload))
}
})
const store = configureStore({ reducer: toDos.reducer })
export const { add, remove } = toDos.actions
createSlice๋ฅผ ์ด์ฉํ๋ฉด reducer, action์ ํ๋ฒ์ ์ ์ธ, ํ ๋น์ด ๊ฐ๋ฅํ๋ค
toolkit ์ฌ์ฉ ์
import { createStore } from 'redux'
const ADD = 'ADD'
const DEL = 'DEL'
const addTodo = (text) => {
return {
type: ADD,
text
}
}
const deleteTodo = (id) => {
return {
type: DEL,
id: parseInt(id)
}
}
const reducer = (state = [], action) => {
switch (action.type) {
case ADD:
return [{ text: action.text, id: Date.now() }, ...state]
case DEL:
return state.filter((toDo) => toDo.id !== action.id)
default:
return state
}
}
export const actionCreator = {
addTodo,
deleteTodo
}
const store = createStore(reducer)
export default store
toolkit ์ฌ์ฉ ํ
import { configureStore, createSlice } from '@reduxjs/toolkit'
const toDos = createSlice({
name: 'toDosReducer',
initialState: [],
reducers: {
add: (state, action) => {
state.push({ text: action.payload, id: Date.now() })
},
remove: (state, action) =>
state.filter((toDo) => toDo.id !== parseInt(action.payload))
}
})
export const { add, remove } = toDos.actions
export default configureStore({ reducer: toDos.reducer })
Reference
-
๋ ธ๋ง๋ ์ฝ๋ Redux ๋ฌด๋ฃ๊ฐ์
https://nomadcoders.co/redux-for-beginners/lobby