๐ŸŽ‰ berenickt ๋ธ”๋กœ๊ทธ์— ์˜จ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰
Front
Redux ToolKit
Redux/Toolkit

Redux


1. ๊ฐœ๋…

1.1 ์•ก์…˜ (Action)

  • ์ƒํƒœ์— ์–ด๋– ํ•œ ๋ณ€ํ™”๊ฐ€ ํ•„์š”ํ•˜๊ฒŒ ๋  ๋•, ์•ก์…˜์ด๋ž€ ๊ฒƒ์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ์ด๋Š”, ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ํ‘œํ˜„๋˜๋Š”๋ฐ์š”, ์•ก์…˜ ๊ฐ์ฒด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ ์ด๋ค„์ ธ์žˆ์Šต๋‹ˆ๋‹ค.
1
{
2
type: 'TOGGLE_VALUE'
3
}

์•ก์…˜ ๊ฐ์ฒด๋Š” type ํ•„๋“œ๋ฅผ ํ•„์ˆ˜์ ์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•˜๊ณ , ๊ทธ ์™ธ์˜ ๊ฐ’๋“ค์€ ๊ฐœ๋ฐœ์ž ๋งˆ์Œ๋Œ€๋กœ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ:

1
{
2
type: "ADD_TODO",
3
data: {
4
id: 0,
5
text: "๋ฆฌ๋•์Šค ๋ฐฐ์šฐ๊ธฐ"
6
}
7
}
8
{
9
type: "CHANGE_INPUT",
10
text: "์•ˆใ„ด"
11
}

1.2 ์•ก์…˜ ์ƒ์„ฑํ•จ์ˆ˜ (Action Creator)

  • ์•ก์…˜์„ ๋งŒ๋“œ๋Š” ํ•จ์ˆ˜
  • ๋‹จ์ˆœํžˆ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ ์•ก์…˜ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ๋งŒ๋“ค์–ด์คŒ
1
function addTodo(data) {
2
return {
3
type: 'ADD_TODO',
4
data,
5
}
6
}
7
8
// ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๋กœ๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
9
const changeInput = text => ({
10
type: 'CHANGE_INPUT',
11
text,
12
})

1.3 ๋ฆฌ๋“€์„œ (Reducer)

  • ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ค๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
  • ๋ฆฌ๋“€์„œ๋Š” ๋‘๊ฐ€์ง€์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.
  • ๋ฆฌ๋“€์„œ๋Š”, ํ˜„์žฌ์˜ ์ƒํƒœ์™€, ์ „๋‹ฌ ๋ฐ›์€ ์•ก์…˜์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
1
function reducer(state, action) {
2
// ์ƒํƒœ ์—…๋ฐ์ดํŠธ ๋กœ์ง
3
return alteredState
4
}

์ž์„ธํ•œ๊ฑด, ์ถ”ํ›„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ก Reduce

  • to change something into a simpler or more general form
  • ๋‹จ์ˆœํ•˜๊ฒŒ ์ค„์ด๋‹ค๋ผ๋Š” ์˜๋ฏธ๋ณด๋‹ค ๋ณ€๊ฒฝ์ด๋ผ๋Š” ์˜๋ฏธ์— ๊ฐ€๊นŒ์›€
  • ์ˆ˜ํ•™์—์„œ ์–ด๋–ค ๋ณต์žกํ•œ ์ˆ˜ํ•™๋ฌธ์ œ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ํ‘ธ๋Š” ๋ฐฉ๋ฒ•์„ reduction์ด๋ผ๊ณ  ํ•จ
  • โ€œ๊ณ ์ณ๋‚˜๊ฐ„๋‹คโ€ (๊ฐ„๋‹จํ•˜๊ฒŒ๋งŒ๋“ค๊ธฐ์œ„ํ•ด์„œ, ํ˜น์€ ํŠน์ •๊ทœ์น™์„ ์ ์šฉํ•˜๊ธฐ์œ„ํ•ด์„œ)
  • ์ฃผ์–ด์ง„ ์ƒํƒœ๋ฅผ ๊ณ ์ณ๋‚˜๊ฐ€๋Š” ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ์ž์ฃผ๋ณด์ด๋Š” reduce()ํ•จ์ˆ˜
  • ๋ฆฌ๋•์Šค์—์„œ์˜ reduce()๋Š” ํ˜„์žฌ์ƒํƒœ(previousState)๋ฅผ ์ƒˆ๋กœ์šด์ƒํƒœ(newState)๋กœ ๋ณ€๊ฒฝํ• ๋•Œ ์“ฐ๋Š” ํ•จ์ˆ˜
  • ๋ฆฌ๋“€์„œ๊ฐ€ reduce()ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜์ด๊ธฐ๋•Œ๋ฌธ์— ๋ฆฌ๋“€์„œ๋ผ๊ณ  ๋ถˆ๋ฆผ

๋ฆฌ์•กํŠธ(React) ์ƒํƒœ(State) ์ƒ์„ฑ์ž(Producer)

  • ์•ก์…˜(Action)์ด ๋‚ ๋ผ์˜ค๋ฉด ๋ฆฌ๋“€์„œ(Reducer)๊ฐ€ ์Šคํ† ์–ด(Store)์˜ ์ƒํƒœ(State)๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘

1.4 ์Šคํ† ์–ด (Store)

  • state๋“ค์„ ๋ณด๊ด€ํ•˜๋Š” ํŒŒ์ผ
  • ๋ฆฌ๋•์Šค์—์„œ๋Š” ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹น ํ•˜๋‚˜์˜ ์Šคํ† ์–ด๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • ์Šคํ† ์–ด ์•ˆ์—๋Š”, ํ˜„์žฌ์˜ ์•ฑ ์ƒํƒœ์™€, ๋ฆฌ๋“€์„œ๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ๊ณ , ์ถ”๊ฐ€์ ์œผ๋กœ ๋ช‡๊ฐ€์ง€ ๋‚ด์žฅ ํ•จ์ˆ˜๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค.

1.5 ๋””์ŠคํŒจ์น˜ (dispatch)

  • ์Šคํ† ์–ด์˜ ๋‚ด์žฅํ•จ์ˆ˜ ์ค‘ ํ•˜๋‚˜
  • ๋””์ŠคํŒจ์น˜๋Š”, ์•ก์…˜์„ ๋ฐœ์ƒ ์‹œํ‚ค๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ดํ•ดํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • dispatch ๋ผ๋Š” ํ•จ์ˆ˜์—๋Š” ์•ก์…˜์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.. dispatch(action) ์ด๋Ÿฐ์‹์œผ๋กœ ๋ง์ด์ฃ .
    • ๊ทธ๋ ‡๊ฒŒ ํ˜ธ์ถœ์„ ํ•˜๋ฉด, ์Šคํ† ์–ด๋Š” ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œ์ผœ์„œ
    • ํ•ด๋‹น ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์ด ์žˆ๋‹ค๋ฉด ์•ก์…˜์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

1.6 ๊ตฌ๋… (subscribe)

  • ๊ตฌ๋… ๋˜ํ•œ ์Šคํ† ์–ด์˜ ๋‚ด์žฅํ•จ์ˆ˜ ์ค‘ ํ•˜๋‚˜
  • subscribe ํ•จ์ˆ˜๋Š”, ํ•จ์ˆ˜ ํ˜•ํƒœ์˜ ๊ฐ’์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.
  • subscribe ํ•จ์ˆ˜์— ํŠน์ • ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์ฃผ๋ฉด, ์•ก์…˜์ด ๋””์ŠคํŒจ์น˜ ๋˜์—ˆ์„ ๋•Œ ๋งˆ๋‹ค ์ „๋‹ฌํ•ด์ค€ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

2. ๋ฆฌ๋•์Šค without ๋ฆฌ์•กํŠธ

  • ๋ฆฌ๋•์Šค๋Š” ๋ฆฌ์•กํŠธ์— ์ข…์†๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฆฌ์•กํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ๋งŒ๋“ ๊ฑฐ๊ธด ํ•˜์ง€๋งŒ,
  • ์‹ค์ œ๋กœ ๋‹ค๋ฅธ UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ˆ: angular-redux, ember-reduxโ€ฆ)
  • ๋ฌผ๋ก , ๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์™€๋„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ๋งํฌ๋ฅผ ํด๋ฆญํ•ด์ฃผ์„ธ์š”:


2.1 DOM ๋ ˆํผ๋Ÿฐ์Šค ๊ฐ€์ ธ์˜ค๊ธฐ

1
// index.js
2
import { createStore } from 'redux'
3
4
const lightDiv = document.getElementsByClassName('light')[0]
5
const switchButton = document.getElementById('switch-btn')
6
7
const counterHeadings = document.getElementsByTagName('h1')[0]
8
const plusButton = document.getElementById('plus-btn')
9
const minusButton = document.getElementById('minus-btn')
  • ๋”ฐ๋กœ UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— DOM ์„ ์ง์ ‘ ๊ฑด๋“ค์ž„
  • DOM API ๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ HTML ์ƒ์— ๋‚˜ํƒ€๋‚˜๊ณ  ์žˆ๋Š” ๊ฐ ์š”์†Œ๋“ค์— ๋Œ€ํ•œ ๋ ˆํผ๋Ÿฐ์Šค ๋งŒ๋“ฌ

2.2 ์•ก์…˜ ํƒ€์ž… ์ •์˜

1
// index.js
2
// ์ƒ๋žต
3
4
// **** ์•ก์…˜ ํƒ€์ž… ์ •์˜
5
const TOGGLE_SWITCH = 'TOGGLE_SWITCH'
6
const INCREMENT = 'INCREMENT'
7
const DECREMENT = 'DECREMENT'
  • ํ”„๋กœ์ ํŠธ์—์„œ ์ƒํƒœ์— ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ค๋Š”๊ฒƒ์„ ํ•˜๋‚˜์˜ ์•ก์…˜์œผ๋กœ ๋ณด๊ณ , ๊ทธ ์•ก์…˜์— ๋Œ€ํ•œ ์ด๋ฆ„์„ ์ •ํ•ด์ฃผ๋Š” ๊ณผ์ •
  • ์ด๋ฆ„์€ ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ, ์ฃผ๋กœ ๋Œ€๋ฌธ์ž๋กœ ์ž‘์„ฑํ•˜๋ฉฐ ์•ก์…˜ ์ด๋ฆ„์€ ๊ณ ์œ ํ•ด์•ผ ํ•˜๊ณ , ์ค‘๋ณต๋˜๋ฉด ์•ˆ๋จ

2.3 ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜ ์ •์˜

1
// index.js
2
// ์ƒ๋žต
3
4
// **** ์•ก์…˜ ์ƒ์„ฑํ•จ์ˆ˜ ์ •์˜
5
// ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜ : ์•ก์…˜ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ํ•จ์ˆ˜
6
// ์•ก์…˜ ๊ฐ์ฒด๋Š” type ๊ฐ’์„ ํ•„์ˆ˜
7
// ๋‚˜๋จธ์ง€ ์•ก์…˜์—์„œ ์ฐธ๊ณ ํ•˜๊ณ  ์‹ถ์€ ๊ฐ’๋“ค์€ ๋งˆ์Œ๋Œ€๋กœ
8
const toggleSwitch = () => ({ type: TOGGLE_SWITCH })
9
const increment = diff => ({ type: INCREMENT, diff })
10
const decrement = () => ({ type: DECREMENT })
  • ์•ก์…˜ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ํ•จ์ˆ˜๋ฅผ, ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜๋ผ๊ณ  ๋ถ€๋ฆ„
  • ์•ก์…˜ ๊ฐ์ฒด๋Š” type ๊ฐ’์„ ํ•„์ˆ˜๋กœ ๋“ค๊ณ ์žˆ์–ด์•ผ ํ•˜๋ฉฐ, ๋‚˜๋จธ์ง€ ์•ก์…˜์—์„œ ์ฐธ๊ณ ํ•˜๊ณ  ์‹ถ์€ ๊ฐ’๋“ค์€ ๊ฐœ๋ฐœ์ž ๋งˆ์Œ๋Œ€๋กœ

2.4 ์ดˆ๊ธฐ๊ฐ’ ์„ค์ •

1
// index.js
2
// ์ƒ๋žต
3
4
// **** ์ดˆ๊นƒ๊ฐ’ ์„ค์ •
5
const initialState = {
6
light: false,
7
counter: 0,
8
}

2.5 ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜ ์ •์˜

1
// index.js
2
// ์ƒ๋žต
3
4
// **** ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜ ์ •์˜
5
// ๋ฆฌ๋“€์„œ : ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ค๋Š” ํ•จ์ˆ˜
6
function reducer(state = initialState, action) {
7
switch (action.type) {
8
case TOGGLE_SWITCH:
9
return {
10
...state, // ๊ธฐ์กด์˜ ๊ฐ’์€ ๊ทธ๋Œ€๋กœ ๋‘๋ฉด์„œ ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋ฎ์–ด์“ฐ๋Š” ๋ฐฉ์‹(์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž)
11
light: !state.light, // light ๊ฐ’ ๋ฐ˜์ „์‹œํ‚ค๊ธฐ
12
}
13
case INCREMENT:
14
return {
15
...state,
16
counter: state.counter + action.diff,
17
}
18
case DECREMENT:
19
return {
20
...state,
21
counter: state.counter - 1,
22
}
23
default:
24
// ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์•ก์…˜์˜ ๊ฒฝ์šฐ ์ƒํƒœ ์œ ์ง€
25
return state
26
}
27
}
  • ๋ฆฌ๋“€์„œ๋Š” ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ค๋Š” ํ•จ์ˆ˜
  • ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ๋Š” state ์™€ action ์„ ๋ฐ›์•„์˜ด
    • ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ์ฒ˜์Œ ํ˜ธ์ถœ ๋  ๋•Œ๋Š” state ๊ฐ€ undefined
    • state๊ฐ€ undefined ๋กœ ์ฃผ์–ด์กŒ์„๋• initialState ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ•˜๊ธฐ์œ„ํ•ด ํŒŒ๋ผ๋ฏธํ„ฐ์ชฝ์—์„œ ๊ธฐ๋ณธ๊ฐ’์ด ์„ค์ •

2.6 ์Šคํ† ์–ด ๋งŒ๋“ค๊ธฐ

1
// index.js
2
// ์ƒ๋žต
3
4
// **** ์Šคํ† ์–ด ๋งŒ๋“ค๊ธฐ
5
// createStore ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉ, ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌ
6
const store = createStore(reducer)
  • ์Šคํ† ์–ด๋ฅผ ๋งŒ๋“ค๋• createStore ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉ
  • ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌ

2.7 render() ๊ตฌํ˜„

1
// index.js
2
// ์ƒ๋žต
3
4
// **** render ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ
5
const render = () => {
6
const state = store.getState() // ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
7
const { light, counter } = state // ํŽธ์˜์ƒ ๋น„๊ตฌ์กฐํ™” ํ• ๋‹น
8
if (light) {
9
lightDiv.style.background = 'green'
10
switchButton.innerText = '๋„๊ธฐ'
11
} else {
12
lightDiv.style.background = 'gray'
13
switchButton.innerText = '์ผœ๊ธฐ'
14
}
15
counterHeadings.innerText = counter
16
}
17
18
render()
  • ์Šคํ† ์–ด์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋• ์Šคํ† ์–ด์˜ ๋‚ด์žฅํ•จ์ˆ˜ getState๋ฅผ ์‚ฌ์šฉ

2.8 ์Šคํ† ์–ด ๊ตฌ๋…(subscribe) ํ•˜๊ธฐ

1
// **** ์˜ˆ์‹œ์ž„
2
const listener = () => console.log('์—…๋ฐ์ดํŠธ ๋์–ด์š”!')
3
const unsubscribe = store.subscribe(listener)
4
// ๋‚˜์ค‘์— unsubscribe();
  • ์Šคํ† ์–ด์˜ ์ƒํƒœ๊ฐ€ ๋ฐ”๋€” ๋•Œ ๋งˆ๋‹ค, ์šฐ๋ฆฌ๋Š” render ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์ค˜์•ผ ํ•จ
  • ๊ทธ๋Ÿฌ๋ ค๋จผ, ์Šคํ† ์–ด๋ฅผ ๊ตฌ๋…ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ตฌ๋…์„ ํ•  ๋•Œ์—๋Š” ์Šคํ† ์–ด์˜ ๋‚ด์žฅํ•จ์ˆ˜ subscribe๋ฅผ ์‚ฌ์šฉ
1
// index.js
2
// ์ƒ๋žต
3
4
// **** ๊ตฌ๋…ํ•˜๊ธฐ
5
store.subscribe(render)
  • subscribe ํ•จ์ˆ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ๋Š”, ํ•จ์ˆ˜ํ˜•ํƒœ์˜ ๊ฐ’์„ ์ „๋‹ฌ
  • ์ „๋‹ฌ๋œ ํ•จ์ˆ˜๋Š”, ์•ก์…˜์ด ๋””์ŠคํŒจ์น˜ ๋  ๋•Œ ๋งˆ๋‹ค ํ˜ธ์ถœ์ด ๋จ
  • subscribe ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ ๊ตฌ๋…์„ ํ•ด์ œํ•˜๋Š” unsubscribe()๋ฅผ ๋ฐ›๋Š”๋ฐ ๋‚˜์ค‘์— ํ•„์š”ํ•ด์งˆ ๋•Œ ํ˜ธ์ถœํ•˜๋ฉด ๋จ
  • cf. ๋ฆฌ์•กํŠธ ์—†์ด ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ ‡๊ฒŒ subscribe ํ•จ์ˆ˜์— ๋Œ€ํ•œ ์‚ฌ์šฉ๋ฒ•์„ ์ตํ˜€๋ณด๊ณ  ์žˆ์ง€๋งŒ, ๋‚˜์ค‘์—” ๋ฆฌ์•กํŠธ์—์„œ ๋ฆฌ๋•์Šค๋ฅผ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด react-redux ๋ผ๋Š”๊ฑธ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ๋Œ€์‹  ํ•ด์ฃผ๋ฏ€๋กœ ๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ์—์„œ subscribe ๋ฅผ ์ง์ ‘ ํ•ด์•ผ ๋˜๋Š” ์ผ์€ ํŠน๋ณ„ํ•œ ์ƒํ™ฉ์„ ์ œ์™ธํ•˜๊ณ ๋Š” ๊ฑฐ์˜ ์—†์Œ

2.9 ์ด๋ฒคํŠธ ๋‹ฌ์•„์ฃผ๊ธฐ, ์•ก์…˜ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ

1
// index.js
2
// ์ƒ๋žต
3
4
// **** ์ด๋ฒคํŠธ ๋‹ฌ์•„์ฃผ๊ธฐ, ์•ก์…˜ ๋ฐœ์ƒ ์‹œํ‚ค๊ธฐ
5
// ์•ก์…˜์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š”๊ฒƒ์„ ๋””์ŠคํŒจ์น˜(dispatch) ๋ผ๊ณ  ํ•จ
6
// ๋””์ŠคํŒจ์น˜๋ฅผ ํ•  ๋•, ์Šคํ† ์–ด์˜ ๋‚ด์žฅํ•จ์ˆ˜ dispatch ๋ฅผ ์‚ฌ์šฉ
7
switchButton.onclick = () => {
8
store.dispatch(toggleSwitch())
9
}
10
11
plusButton.onclick = () => {
12
store.dispatch(increment(5))
13
}
14
15
minusButton.onclick = () => {
16
store.dispatch(decrement())
17
}
  • ์•ก์…˜์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š”๊ฒƒ์„ ๋””์ŠคํŒจ์น˜ (dispatch)๋ผ๊ณ  ๋ถ€๋ฆ„
  • ๋””์ŠคํŒจ์น˜๋ฅผ ํ•  ๋•, ์Šคํ† ์–ด์˜ ๋‚ด์žฅํ•จ์ˆ˜ dispatch ๋ฅผ ์‚ฌ์šฉ
  • ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์•ก์…˜ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌ

https://codesandbox.io/s/vvzqnvw17y

ํ•œ๋ฒˆ ๋ฒ„ํŠผ๋“ค์„ ๋ˆŒ๋Ÿฌ๋ณด์„ธ์š”. ๊ฐ’์ด ์ž˜ ๋ฐ”๋€Œ๋‚˜์š”? ๋ชจ๋‘ ๋‹ค ์ •์ƒ ์ž‘๋™ํ•œ๋‹ค๋ฉด, ๊ฐœ๋… ๋ถ€๋ถ„์˜ ํ‚ค์›Œ๋“œ๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ญ‰ ํ›‘์–ด๋ณด์…ˆ


3. ๋ฆฌ๋•์Šค with ๋ฆฌ์•กํŠธ

3.1 ๋ฆฌ๋•์Šค์˜ 3๊ฐ€์ง€ ๊ทœ์น™

  1. ํ•˜๋‚˜์˜ ์•ฑ์—์„  ์Šคํ† ์–ด 1๊ฐœ๋งŒ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉ
    1. ์—ฌ๋Ÿฌ๊ฐœ์˜ ์Šคํ† ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์€ ์‚ฌ์‹ค ๊ฐ€๋Šฅํ•˜๊ธฐ๋Š” ํ•˜๋‚˜, ๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ
  2. ์ƒํƒœ๋Š” ์ฝ๊ธฐ์ „์šฉ
    • ๋ฆฌ์•กํŠธ์—์„œ state ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด์•ผ ํ•  ๋•Œ, setState ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ ,
    • ๋ฐฐ์—ด์„ ์—…๋ฐ์ดํŠธํ•  ๋–„๋Š” ๋ฐฐ์—ด ์ž์ฒด์— push ๋ฅผ ์ง์ ‘ ํ•˜์ง€ ์•Š๊ณ ,
      • concat ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๊ธฐ์กด์˜ ๋ฐฐ์—ด์€ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ 
      • ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ๋งŒ๋“ค์–ด์„œ ๊ต์ฒดํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์—…๋ฐ์ดํŠธ
    • ์—„์ฒญ ๊นŠ์€ ๊ตฌ์กฐ๋กœ ๋˜์–ด์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ๋•Œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ,
      1. ๊ธฐ์กด์˜ ๊ฐ์ฒด๋Š” ๊ฑด๋“ค์ด์ง€ ์•Š๊ณ  Object.assign ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ spread ์—ฐ์‚ฐ์ž (...) ๋ฅผ ์‚ฌ์šฉ
    • ๋ฆฌ๋•์Šค์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ธฐ์กด์˜ ์ƒํƒœ๋Š” ๊ฑด๋“ค์ด์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์—…๋ฐ์ดํŠธ
    • ๋ฆฌ๋•์Šค์—์„œ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š”
      • ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ ๋˜๋Š” ๊ฒƒ์„ ๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ shallow equality ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ
      • ์ด๋กœ ์ธํ•ด ๊ฐ์ฒด์˜ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•  ๋•Œ ๊ฐ์ฒด์˜ ๊นŠ์ˆ™ํ•œ ์•ˆ์ชฝ๊นŒ์ง€ ๋น„๊ต๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ,
      • ๊ฒ‰ํ•ฅ๊ธฐ ์‹์œผ๋กœ ๋น„๊ต๋ฅผ ํ•˜์—ฌ ์ข‹์€ ์„ฑ๋Šฅ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ
      • ์—ฌ๊ธฐ์„œ๋Š” Immer.js ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•˜๋ฉฐ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›€
  3. ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ค๋Š” ํ•จ์ˆ˜(๋ฆฌ๋“€์„œ)๋Š” ์ˆœ์ˆ˜ํ•œ ํ•จ์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๋Š” ์ด์ „ ์ƒํƒœ์™€, ์•ก์…˜ ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์Œ
    • ์ด์ „์˜ ์ƒํƒœ๋Š” ์ ˆ๋Œ€๋กœ ๊ฑด๋“ค์ด์ง€ ์•Š๊ณ , ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚จ ์ƒˆ๋กœ์šด ์ƒํƒœ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ฐ˜ํ™˜ํ•จ
    • ๋˜‘๊ฐ™์€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํ˜ธ์ถœ๋œ ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๋Š” ์–ธ์ œ๋‚˜ ๋˜‘๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผ๋งŒ ํ•จ

๋™์ผํ•œ input์ด๋ผ๋ฉด ์–ธ์ œ๋‚˜ ๋™์ผํ•œ output์ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ new Date(), ๋žœ๋ค์ˆซ์ž, ๋„คํŠธ์›Œํฌ ์š”์ฒญ๊ฐ™์€ ์‹คํ–‰๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ’์ด ๋‚˜์˜ค๋Š” ์ž‘์—…๋“ค์€ ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜ ๋ฐ”๊นฅ์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๋ฆฌ๋•์Šค ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.


3.2 ๋ฆฌ์•กํŠธ์—์„œ ๋ฆฌ๋•์Šค๋ฅผ ์“ฐ๋ ค๋ฉด

๋‹ค์Œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์„ค์น˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • redux: ๋ฆฌ๋•์Šค ๋ชจ๋“ˆ
  • react-redux: ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฆฌ๋•์Šค๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ์œ„ํ•œ ์œ ์šฉํ•œ ๋„๊ตฌ๋“ค์ด ๋“ค์–ด๊ฐ€์žˆ์Šต๋‹ˆ๋‹ค.
  • redux-actions: ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ผญ ์„ค์น˜ ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋‹จ, ์•Œ์•„๋‘๋ฉด ๊ต‰์žฅํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์‹ค์Šต


3.3 Immer.js๋ฅผ ์‚ฌ์šฉํ•œ ๋ถˆ๋ณ€์„ฑ ๊ด€๋ฆฌ


4. ๊ณต์‹๋ฌธ์„œ ์˜ˆ์ œ ๋”ฐ๋ผํ•ด๋ณด๊ธฐ

1
npm install @reduxjs/toolkit react-redux

4.1 Redux Store ๋งŒ๋“ค๊ธฐ

store = state๋“ค์„ ๋ณด๊ด€ํ•˜๋Š” ํŒŒ์ผ

1
// app/store.js
2
import { configureStore } from '@reduxjs/toolkit'
3
4
export default configureStore({
5
reducer: {},
6
})

4.2 Redux State Slice ์ƒ์„ฑ

1
// src/index.js
2
import React from 'react'
3
import ReactDOM from 'react-dom/client'
4
import './index.css'
5
import App from './App'
6
import { Provider } from 'react-redux' // ์ถ”๊ฐ€
7
import store from './app/store'
8
9
// Start the mocking conditionally.
10
if (process.env.NODE_ENV === 'development') {
11
const { worker } = require('./mocks/browser')
12
worker.start()
13
}
14
15
const root = ReactDOM.createRoot(document.getElementById('root'))
16
root.render(
17
<React.StrictMode>
18
{/* ์ถ”๊ฐ€ */}
19
<Provider store={store}>
20
<App />
21
</Provider>
22
</React.StrictMode>,
23
)
  • Provider๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ์™€ ์•„๊นŒ ์ž‘์„ฑํ•œ ํŒŒ์ผ์„ import
  • <Provider store={importํ•ด์˜จ๊ฑฐ}> ์ด๊ฑธ๋กœ <App/> ์„ ๊ฐ์‹ธ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • ๊ทธ๋Ÿผ ์ด์ œ <App>๊ณผ ๊ทธ ๋ชจ๋“  ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์€ store.js์— ์žˆ๋˜ state๋ฅผ ๋ง˜๋Œ€๋กœ ๊บผ๋‚ด์“ธ ์ˆ˜ ์žˆ์Œ

4.3 Store์— Slice Reducer ์ถ”๊ฐ€

Redux store์— state ๋ณด๊ด€ํ•˜๋Š” ๋ฒ•

  1. createSlice()๋กœ state ๋งŒ๋“ค๊ณ 
  2. configureStore()์•ˆ์— ๋“ฑ๋ก
1
// features/counter/counterSlice.js
2
import { createSlice } from '@reduxjs/toolkit'
3
4
// 1. createSlice()๋กœ state ๋งŒ๋“ค๊ธฐ
5
export const counterSlice = createSlice({
6
name: 'counter',
7
initialState: {
8
value: 0,
9
},
10
reducers: {
11
// Redux Toolkit์€ immer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ "๋ถˆ๋ณ€์„ฑ"์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
12
// ๋ถˆ๋ณ€์„ฑ์„ ๋ณด์žฅํ•˜๋ฉด ๋” ์‰ฝ๊ฒŒ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
13
increment: state => {
14
state.value += 1
15
},
16
decrement: state => {
17
state.value -= 1
18
},
19
incrementByAmount: (state, action) => {
20
state.value += action.payload
21
},
22
},
23
})
24
25
// Action creators๋Š” reducer ํ•จ์ˆ˜ ๋ณ„๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
26
export const { increment, decrement, incrementByAmount } = counterSlice.actions
27
28
export default counterSlice.reducer
1
// app/store.js
2
import { configureStore } from '@reduxjs/toolkit'
3
import counterReducer from '../features/counter/counterSlice'
4
5
// 2. configureStore() ์•ˆ์— ๋“ฑ๋ก
6
export default configureStore({
7
reducer: {
8
counter: counterReducer,
9
},
10
})

4.4 ์ปดํฌ๋„ŒํŠธ์—์„œ State, Actions ์‚ฌ์šฉ

1
import { useDispatch, useSelector } from 'react-redux'
2
import { decrement, increment, incrementByAmount } from './counterSlice'
3
4
export default function Counter() {
5
const count = useSelector(state => state.counter.value)
6
const dispatch = useDispatch()
7
8
return (
9
<div>
10
<div>
11
<button onClick={() => dispatch(increment())}>Increment</button>
12
<span>{count}</span>
13
<button onClick={() => dispatch(decrement())}>Decrement</button>
14
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
15
</div>
16
</div>
17
)
18
}

4.5 ๋น„๊ต์šฉ Counter2

1
import { useDispatch, useSelector } from 'react-redux'
2
import { decrement, increment, incrementByAmount } from '../features/counter/counterSlice'
3
4
export default function Counter2() {
5
const count = useSelector(state => state.counter.value)
6
const dispatch = useDispatch()
7
8
return (
9
<div>
10
<div>
11
<button onClick={() => dispatch(increment())}>Increment</button>
12
<span>{count}</span>
13
<button onClick={() => dispatch(decrement())}>Decrement</button>
14
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
15
</div>
16
</div>
17
)
18
}
1
import './App.css'
2
import TestMocking from './components/TestMocking'
3
import Counter from './features/counter/Counter'
4
import Counter2 from './components/Counter2'
5
6
export default function App() {
7
return (
8
<div className="App">
9
<TestMocking />
10
<Counter />
11
12
<Counter2 />
13
</div>
14
)
15
}

[์ฐธ๊ณ ]