Redux ๊ตด๊ธฐ(ๅด›่ตท)

#redux
#typescript
Suggest Edit

Redux ์‚ฌ์šฉ์— ์žˆ์–ด์„œ ์•„์‰ฌ์› ๋˜ ์ ๋“ค๊ณผ ๊ทธ๊ฑธ ๊ณ ์ณ๋‚˜๊ฐ„ ๊ฒฝํ—˜์„ ์จ๋ณด๋ ค ํ•œ๋‹ค. (์†”์งํžˆ, Redux๋Š” MobX์— ๋น„ํ•ด ๋ถ€์กฑํ•œ ์ ์ด ๋งŽ๋‹ค.)

๋ฆฌ๋•์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

  1. Memoization์ด ์•ˆ๋œ๋‹ค.
  2. ๋น„ํšจ์œจ์ ์ธ ๋ฆฌ๋“€์‹ฑ
    • ์•ก์…˜๋งˆ๋‹ค ๋ Œ๋”๋ฅผ ๋‹ค์‹œ ์‹œํ‚จ๋‹ค.(Batched actions ์ฒ˜๋ฆฌ์— ๋ถˆ๋ฆฌ)
    • ๋ชจ๋“  ๋ฆฌ๋“€์„œ์— ์•ก์…˜์„ ๋„˜๊ฒจ์ค€๋‹ค.(combineReducers)
  3. ๊นŠ์€ ๊ตฌ์กฐ๋ฅผ ๋‹ค๋ฃจ๊ธฐ ํž˜๋“ค๋‹ค.

ํ•˜๋‚˜์”ฉ ๊นŒ์—Ž์–ด๋ณด์ž.

Memoization

Redux๋Š” ์Šคํ…Œ์ดํŠธ์— ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚˜๋ฉด ๋ชจ๋“  ์—ฐ๊ฒฐ๋œ(connect) ์ปดํฌ๋„ŒํŠธ๋“ค์—๊ฒŒ ์ƒˆ๋กœ์šด ์Šคํ…Œ์ดํŠธ๋ฅผ ์ „๋‹ฌํ•ด์ค€๋‹ค. ์—ฌ๊ธฐ์„œ, ์Šคํ…Œ์ดํŠธ์˜ ์ผ๋ถ€์— ์ดํ„ฐ๋ ˆ์ด์…˜์ด๋‚˜(๋ฐฐ์—ด์ด๋‚˜ ๋งต์˜ ์ •๋ ฌ, ํ•„ํ„ฐ) ๊ณ„์‚ฐ์ด ์žˆ์„ ๊ฒฝ์šฐ, ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์ž์‹ ์ด ํ•„์š”๋กœ ํ•˜๋Š” ๋ถ€๋ถ„์€ ๋ณ€๊ฒฝ์ด ์—†์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋‹ค์‹œ ๊ณ„์‚ฐ์„ ํ•ด์•ผํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ Memoization์€ ์ด์ „์— ๋ฐ›์€ ์ธ์ˆ˜๋“ค๊ณผ ๋ฆฌํ„ด ๊ฐ’์„ ๊ธฐ์–ตํ•ด๋‘์–ด, ์ƒˆ๋กœ ๋ฐ›์€ ์ธ์ˆ˜๊ฐ€ ์ด์ „๊ณผ ๋™์ผ ํ•  ๊ฒฝ์šฐ, ๊ธฐ์–ตํ•ด๋‘” ๋ฆฌํ„ด๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋Œ๋ ค์ฃผ๊ฒŒ ๋งŒ๋“ค์–ด์ ธ์žˆ๋‹ค.

์ง์ ‘ ๋งŒ๋“ค๊ธฐ ์–ด๋ ค์šด๊ฑด ์•„๋‹ˆ์ง€๋งŒ, Reselect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ข€ ๋” ๊ฐ•๋ ฅํ•˜๊ฒŒ Memoization์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Reselect๋Š” 2๊ฐœ์˜ ๋‹จ๊ณ„๋กœ Redux์˜ ์Šคํ…Œ์ดํŠธ๋กœ๋ถ€ํ„ฐ ๊ณ„์‚ฐ์‹์— ํ•„์š”ํ•œ ์ธ์ˆ˜๋ฅผ ๊ตฌํ•˜๋Š” ํ•จ์ˆ˜๋“ค๊ณผ, ์ด ์ธ์ˆ˜๋“ค๋กœ ๊ฒฐ๊ณผ๊ฐ’์„ ๋งŒ๋“œ๋Š” ๊ณ„์‚ฐ ํ•จ์ˆ˜๋กœ ๋˜์–ด์žˆ๋‹ค.

๊ณ ๋กœ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์–ด State์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ ธ๋„, ์ธ์ˆ˜๋กœ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฐ’์ด ์•„์ง ๋ณ€๊ฒฝ์ด ์•ˆ๋ฌ์œผ๋ฉด ์ด์ „ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ”๋กœ ์žฌํ™œ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.

import { createSelector } from 'reselect'

// ์Šคํ…Œ์ดํŠธ์—์„œ ์–ด๋–ค ๊ฐ’์„ ์ธ์ˆ˜๋กœ ์“ธ์ง€ ์ฐพ์•„์ฃผ๋Š” ํ•จ์ˆ˜๋“ค์ด๋‹ค.
const getVisibilityFilter = state => state.visibilityFilter
const getTodos = state => state.todos

export const getVisibleTodos = createSelector(
  [getVisibilityFilter, getTodos],
  // ์ฐพ์•„์ง„ ์ธ์ˆ˜์— ๋Œ€ํ•ด ๊ณ„์‚ฐ์„ ํ–‰ํ•œ๋‹ค.
  (visibilityFilter, todos) => {
    switch (visibilityFilter) {
      case 'SHOW_ALL':
        return todos
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed)
    }
  }
)

Batched Actions

์—ฐ์†์ ์ธ ์•ก์…˜ ๋””์ŠคํŒจ์น˜์‹œ, ๋ฆฌ๋•์Šค๋Š” ๋งค ๋””์ŠคํŒจ์น˜๋งˆ๋‹ค ์—ฐ๊ฒฐ๋œ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ ์ƒˆ ์Šคํ…Œ์ดํŠธ๋ฅผ ์ „๋‹ฌํ•ด์ค€๋‹ค. ์Šคํ…Œ์ดํŠธ๊ฐ€ ์•ˆ๋ฐ”๋€๋‹ค๋ฉด, mapStateToProps๋ฅผ ๋‘๋“œ๋ฆฌ์ง„ ์•Š๊ฒ ์ง€๋งŒ, ๋งค ๋””์ŠคํŒจ์น˜๋งˆ๋‹ค ์Šคํ…Œ์ดํŠธ๊ฐ€ ๋ฐ”๋€Œ๊ณ  ์•ž์„œ ์„ค๋ช…ํ•œ Memoization์ด ์ œ๋Œ€๋กœ ์•ˆ๋˜์–ด์žˆ์œผ๋ฉด ์•ฑ์€ ์—„์ฒญ๋‚˜๊ฒŒ ๋Š๋ ค์งˆ ๊ฒƒ์ด๋‹ค.

๋ฌผ๋ก  ๋Œ€์ฑ…์€ ๋””์ŠคํŒจ์น˜๋ฅผ ์ž์ฃผ ์•ˆํ•˜๋ฉด ๋œ๋‹ค. ๋ผ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ์ด๋Š” ์ž‘์€ ์•ก์…˜์„ ์—ฌ๋Ÿฌ๊ฐœ ๋””์ŠคํŒจ์น˜ํ•˜๋Š”๊ฑธ ์‚ผ๊ฐ€๊ณ , ์•„์ฃผ ๊ฐ•๋ ฅํ•œ ํ•˜๋‚˜์˜ ์•ก์…˜์„ ์†Œ์ˆ˜ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค. ๋ฌธ์ œ๋Š” ์—ฌ๊ธฐ์„œ ์‹œ์ž‘๋œ๋‹ค. ์ด๋ ‡๊ฒŒ ๋งŒ๋“  ์•ก์…˜์ด ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ์ผ๊นŒ? ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ๋•Œ๋„ ์ž‘์€ ์ผ์„ ํ™•์‹คํ•˜๊ฒŒ ํ•˜๋Š” ๋…€์„๋“ค์„ ๋งŽ์ด ๋งŒ๋“ค์–ด์•ผ ํ…Œ์ŠคํŠธ๋„ ์‰ฝ๊ณ , ์žฌ์‚ฌ์šฉ์„ฑ๋„ ๋†’์•„์ง€๊ณ  ์ดํ•ดํ•˜๊ธฐ๋„ ์‰ฌ์›Œ์ง„๋‹ค. ๊ณ ๋กœ, ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ๋†’์ด๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ž‘๊ณ  ํ•˜๋‚˜์˜ ์ผ์„ ํ™•์‹คํžˆ ํ•ด๋‚ด๋Š” ์•ก์…˜์„ ๋งŽ์ด ๊ฐ€์ง€๋Š”๊ฒŒ ์ด๋กญ๋‹ค.

๊ณ ๋กœ, ์šฐ๋ฆฌ๋Š” ์ผ๋ จ์˜ ์•ก์…˜์— ๋Œ€ํ•ด ๋ฆฌ๋“€์‹ฑ์„ ๋ชจ๋‘ ๋‹ค ๋๋‚ด์ฃผ๊ณ  ์ปดํฌ๋„ŒํŠธ๋“ค์—๊ฒŒ ๋„˜๊ฒจ ์ค„ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ช‡๊ฐ€์ง€์˜ ์ž‘์€ ๊ธฐ์กด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ๋Š”๋ฐ, ์‚ฌ์šฉ์„ฑ์ด ๋ณ„๋กœ ๋งˆ์Œ์— ์•ˆ๋“ค๊ณ , Redux Saga์— ์ œ๋Œ€๋กœ ๋Œ€์‘ํ•˜์ง€๋„ ์•Š๋Š”๊ฒŒ ๋Œ€๋ถ€๋ถ„์ด์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ, ์ƒˆ๋กญ๊ฒŒ Batch Enhancer๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

const sagaMiddleware = createSagaMiddleware()
const middlewareEnhancer = applyMiddleware(sampleMiddleware)
const enhancer = compose<Redux.StoreEnhancerStoreCreator<State>>(
  middlewareEnhancer,
  batchEnhancer(sagaMiddleware)
  // Saga๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋„˜๊ฒจ์ฃผ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
  // batchEnhancer(),
)
// ์ ์šฉ์€ ๊ฐ€๋ณ๊ฒŒ ์ธํ•ธ์„œ๋งŒ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.
const store = createStore(reducer, enhancer)

// ์ด์ œ ๋ฐฐ์—ด๋กœ ๋””์ŠคํŒจ์น˜๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
store.dispatch([
  {
    type: 'SayHello'
  },
  {
    type: 'SayHello'
  },
  {
    type: 'SayHello'
  }
])

// `put` ์ดํŽ™ํŠธ์—์„œ๋„ ๋˜‘๊ฐ™์ด ์“ธ ์ˆ˜ ์žˆ๋‹ค.
function* saga() {
  while (true) {
    yield take('SayHello')
    yield put([
      {
        type: 'SayBye'
      },
      {
        type: 'SayBye'
      },
      {
        type: 'SayBye'
      }
    ])
  }
}

์ด์ œ, ๋ฐฐ์—ด์— ๋‹ด๊ธด ์•ก์…˜๋“ค์„ ๋‹ค ๋ฆฌ๋“€์Šคํ•˜๊ณ ๋‚˜์„œ ์ปดํฌ๋„ŒํŠธ๋“ค์—๊ฒ ์ตœ์ข…์ ์ธ ๊ฒฐ๊ณผ๋ฌผ๋งŒ ์•Œ๋ ค์ฃผ๊ฒŒ ๋œ๋‹ค.

Efficient reducing with Map

๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ๋กœ, combineReducers๊ณผ switch ๊ตฌ๋ฌธ์ด ๋ณ„๋กœ ๋งˆ์Œ์— ์•ˆ๋“ค์—ˆ๋‹ค. ์ฒ˜์Œ ์“ฐ๊ธฐ์—” ์‰ฝ์ง€๋งŒ, ์•ฑ์ด ์ปค์งˆ์ˆ˜๋ก ๋งค ์•ก์…˜๋“ค์„ ๋ชจ๋“  ์Šค์œ„์น˜ ๊ตฌ๋ฌธ์œผ๋กœ ํ†ต๊ณผ์‹œํ‚ค๋Š”๊ฑด ๋„ˆ๋ฌด ๋น„ํšจ์œจ ์ ์ธ๋“ฏ ํ•ด๋ณด์˜€๋‹ค.

์ด์—, Mapped Reducer๋ฅผ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ, ์•ก์…˜ ํƒ€์ž…์„ ํ‚ค๋กœ ๊ทธ์— ๋งž์ถฐ ์Šคํ…Œ์ดํŠธ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๊ฐ’์œผ๋กœ ๊ฐ€์ง€๋Š” ๋งต์„ ๋งŒ๋“ค์—ˆ๋‹ค. ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ž‘ ๋น„์Šทํ•ด์ง„ ๋Š๋‚Œ์ธ๋ฐ, ๊ธฐ๋Šฅ์ƒ์œผ๋ก  combineReducers์™€ ํฌ๊ฒŒ ๋‹ค๋ฅธ๊ฑด ์—†๋‹ค.

์žฅ์ ์€ ์•ก์…˜์— ๋Œ€ํ•ด ํŠน์ • ๋ฆฌ๋“€์„œ๋งŒ ์ž‘๋™ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๊ณ , Map์„ ํ™œ์šฉํ–ˆ๊ธฐ์— ์•ก์…˜ ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ ์ˆ˜๋ก Switch๋ณด๋‹ค ์ธ๋ฑ์‹ฑ์—์„œ ์œ ๋ฆฌํ•œ ๊ณ ์ง€๋ฅผ ์ ํ•  ์ˆ˜ ์žˆ๋‹ค.(Map์€ ํ‚ค๋ฅผ ํ•ด์‹œ๊ฐ’์œผ๋กœ ๋‹ค๋ฃฌ๋‹ค.)

import { createStore } from 'redux'
import { MappedPipeReducer } from 'typed-redux-kit.mapped-reducer'
import { PureAction, PayloadAction } from 'typed-redux-kit.base'

enum ActionTypes {
  Plus = 'test_Plus',
  Set = 'test_Set'
}

namespace Actions {
  export interface Plus extends PureAction<ActionTypes.Plus> {}

  export interface Set
    extends PayloadAction<
      ActionTypes.Set,
      {
        count: number
      }
    > {}
}

interface State {
  count: number
}

const plusReducer = (state: State, action: Actions.PlusAction) => ({
  ...state,
  count: state.count + 1
})

const setReducer = (state: State, action: Actions.SetAction) => ({
  ...state,
  ...action.payload
})

// ์ดˆ๊ธฐ ์Šคํ…Œ์ดํŠธ๋Š” ์Šคํ† ์–ด์— ๋„ฃ์–ด์ค˜๋„ ๋œ๋‹ค.
const reducer = new MappedPipeReducer<State>({
  initialState: {
    count: 0
  }
})

reducer
  .set(ActionTypes.Plus, plusReducer)
  .set(ActionTypes.Set, setReducer)
  // ๋ณต์ˆ˜์˜ ์•ก์…˜ํƒ€์ž…์— ๋Œ€ํ•ด์„œ๋„ ๊ฐ„๋‹จํžˆ ๋ฐฐ์—ด๋กœ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
  .set([ActionTypes.Plus, ActionTypes.Set], anotherReducer)
  // String enum๋„ ๋”ฐ๋กœ ๋ณ€ํ™˜์—†์ด ๋ฐ”๋กœ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
  .set(ActionTypes, yetAnotherReducer)

const store = createStore(reducer.reduce)
store.dispatch({
  type: ActionTypes.Plus
} as Actions.Plus)

Deep state

Redux์—์„  ๊นŠ์€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋‹ค๋ฃจ๊ธฐ ๋งค์šฐ ๊ท€์ฐฎ๋‹ค. ์ด๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ํ•ญ์ƒ Immutableํ•œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•ด์•ผํ•˜๊ธฐ ๋–„๋ฌธ์ธ๋ฐ, ๊ทธ๋ƒฅ ์˜ค๋ธŒ์ ํŠธ๋กœ ๊นŠ์€ ๊ณณ์— ์žˆ๋Š” ๊ฐ’์„ ์ˆ˜์ •ํ•˜๋ ค ํ• ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋œ๋‹ค:

const myReducer = (state, action) => ({
  ...state,
  depth1: {
    ...state.depth1,
    depth2: {
      ...state.depth1.depth2,
      depth3: {
        ...state.depth1.depth2.depth3,
        depth4: action.payload
      }
    }
  }
})

์ฝœ๋ฐฑํ—ฌ ์ฒ˜๋Ÿผ ํ”ผ๋ผ๋ฏธ๋“œ๊ฐ€ ๋˜์–ด๊ฐ€...

์ด์— ํŽ˜์ด์Šค๋ถ์ด ๋งŒ๋“  Immutable.js๋Š” ๋” ๋‚˜์€ API๋กœ ์ด๋ฅผ ์‰ฝ๊ฒŒ ๋‹ค๋ฃจ๊ฒŒ ํ•ด์ค€๋‹ค.

const myReducer = (state, action) =>
  state.setIn(['depth1', 'depth2', 'depth3', 'depth4'], action.payload)

๊ทผ๋ฐ, ๋ฌธ์ œ์ ์€... getIn, setIn, ...In๊ณผ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ๋Š” ๋ฌธ์ž์—ด์˜ ๋ฐฐ์—ด๋กœ ํ‚ค๋“ค์„ ๊ฐ€์ ธ์™€์„œ ๊ฐ’์„ ์ฐพ์•„ ๋‚ด๋„๋ก ๋งŒ๋“ค์–ด์ ธ ์žˆ์œผ๋ฏ€๋กœ, ํƒ€์ž… ์ถ”๋ก ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์ด๋ฏ€๋กœ, ํ‚ค๊ฐ’์„ ์ž˜๋ชป๋„ฃ์œผ๋ฉด ๋Ÿฐํƒ€์ž„๊นŒ์ง€ ๊ฐ€์•ผ ์—๋Ÿฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ•ญ์ƒ ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

์ด๋ฅผ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ํ‹€์–ด๋ง‰๊ธฐ์œ„ํ•ด์„  ...In์„ ์“ฐ๋ฉด ์•ˆ๋˜๋Š”๋ฐ, ๊ทธ๋Ÿฌ๋ฉด...:

const myReducer = (state, action) =>
  state.update('depth1', depth1 =>
    depth1.update('depth2', depth2 =>
      depth2.update('depth3', depth3 =>
        depth3.update('depth4', depth4 => action.payload)
      )
    )
  )

๋˜ ๋‹ค๋ฅธ ํ”ผ๋ผ๋ฏธ๋“œ๊ฐ€ ์ƒ๊ธด๋‹ค.

์ด์— ํ•ด๊ฒฐ์ฑ…์„ ๊ณ ๋ฏผํ•˜๋‹ค MobX์˜ Observable Object๋ฅผ ๋ณด๊ณ  ์˜๊ฐ์„ ๋ฐ›์•„ Trackable์„ ๋งŒ๋“ค์—ˆ๋‹ค.

Trackable์€ ๋ง๊ทธ๋Œ€๋กœ ์ถ”์ ์„ ํ•ด์ฃผ๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด๋กœ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ํ”์ ์„ ๋‚จ๊ฒจ๋‘๊ณ  ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฆฌ๋“€์„œ์—์„œ ๋‚˜๊ฐˆ ๋•Œ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๋กœ ๋งŒ๋“ค์–ด์ฃผ๋ฉด ์ค€๋‹ค.

import * as Redux from 'redux'
import { trackEnhancer, TrackableRecord } from '../lib'

const CountRecord = TrackableRecord({
  count: 0
})
type CountRecord = TrackableRecord<{
  count: number
}>
type State = TrackableMap<string, CountRecord>
const defaultChildState = CountRecord({
  count: 0
})
const defaultState: State = new TrackableMap({
  a: defaultChildState
})

const myReducer = (state: State = defaultState, action: Redux.Action) => {
  if (action.type === 'add') {
    // ์ด์ œ ๋ถˆ๋ณ€์„ฑ ์‹ ๊ฒฝ์•ˆ์“ฐ๊ณ  ์‹ ๋‚˜๊ฒŒ ๋ฐ”๊ฟ”๋„ ๋œ๋‹ค!
    state.get('a').count++
  }
  return state
}
// ์™œ๋ƒํ•˜๋ฉด `trackEnhancer`๊ฐ€ ๋ณ€๊ฒฝ์„ ์ถ”์ ํ•ด์„œ ๋”๋Ÿฌ์›Œ์ง„(๋ณ€๊ฒฝ๋œ) ๋ถ€๋ถ„์„ ๊น”๋”ํ•˜๊ฒŒ ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—!
const store = Redux.createStore(myReducer, trackEnhancer)

store.dispatch({
  type: 'add'
})

const reducedState = store.getState()
expect(reducedState.get('a').count).toBe(1)
// ๊ณ ๋กœ ๋ณ€๊ฒฝ์€ ๋ฎคํ„ฐ๋ธ”ํ•˜๊ฒŒ ํ–ˆ์ง€๋งŒ ๊ฒฐ๊ณผ์˜ ์ธ์Šคํ„ด์Šค๋Š” ๋ฐ”๋€Œ์–ด์žˆ๋‹ค.
expect(reducedState).not.toBe(defaultState)

์ฃผ์˜ํ•  ์ ์€ Object๋‚˜ Map, Array๋ฅผ ๋ชจ๋‘ Trackable๋กœ ๊ด€๋ฆฌํ•ด์•ผํ•œ๋‹ค. (์˜ˆ์ œ์—” ์—†์ง€๋งŒ ๋ฐฐ์—ด์„ ์œ„ํ•ด TrackableArray ์—ญ์‹œ ์ค€๋น„๋˜์–ด ์žˆ๋‹ค.)

๊ทธ๋ฆฌ๊ณ  Immutable.js๋ณด๋‹ค ์กฐ๊ธˆ ์„ฑ๋Šฅ์ƒ ๋ชจ์ž๋ž€ ๋ถ€๋ถ„์ด ์žˆ๋Š”๋ฐ, Immutable.js๋Š” HAMT๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ ์ธ์Šคํ„ด์Šค๋ฅผ ๋”์šฑ ํšจ์œจ์ ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ Trackable์€ ์•„์ง ๋‹จ์ˆœํžˆ 1์ฐจ์›์ ์œผ๋กœ ํ‚ค์™€ ๊ฐ’์˜ ์ดํ„ฐ๋ ˆ์ด์…˜์œผ๋กœ ์ƒˆ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ํ˜น์‹œ ์ˆ˜ํ‰์ ์œผ๋กœ ์—„์ฒญ ๊ฑฐ๋Œ€ํ•œ ๋งต์„ ๋งŒ๋“ค๋ ค๊ณ  ํ•  ๋•Œ์—๋Š” ๋ณดํ‹€๋„ฅ์ด ๋ ๊ฒƒ์ด๋‹ค. ์•„๋งˆ ์ด๋ฒˆ๋‹ฌ ์ค‘์œผ๋กœ ์ž‘์—…ํ•  ๊ฒƒ์ด๋ฏ€๋กœ ์กฐ๊ธˆ๋งŒ ๋” ๊ธฐ๋‹ค๋ ค์คฌ์œผ๋ฉด ํ•œ๋‹ค. (ํ˜น์€ ์ˆ˜์ง์ ์œผ๋กœ ๋” ๊นŠ์€ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด ์นดํ…Œ๊ณ ๋ฆฌํ™” ์‹œํ‚ค๋Š” ๊ฒƒ๋„ ํ•œ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด๋‹ค.)

์•ž์œผ๋กœ ํ•  ๊ฑด HAMT์™€ Set ๊ตฌ์กฐ๋ฅผ ๋” ์ถ”๊ฐ€ํ•  ๊ณ„ํš์ด๋‹ค.

๋งˆ๋ฌด๋ฆฌ

๋ฆฌ๋•์Šค์—์„œ ๋Š๊ปด์ง€๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฌธ์ œ์ ๋“ค์„ ํ•˜๋‚˜์”ฉ ํƒœํดํ•ด ๋ณด์•˜๋‹ค. ๋•๋ถ„์— ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ข€ ๋” ์ž˜ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๋“ฑ ๋ฐฐ์šด๊ฒŒ ๋งŽ์€ ๊ฑฐ ๊ฐ™๋‹ค.

๋‹จ, ์•„์ง๊นŒ์ง€ ๋ฆฌ๋•์Šค์—์„œ ์•„์‰ฝ๋‹ค๊ณ  ๋Š๊ปด์ง€๋Š” ์ ์€ ์–ธ์ œ๋‚˜ ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ์žฅํ™ฉํ•ด์ง„๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์–ด์ฉŒ๋ฉด CLI๊ฐ™์€๊ฑธ ๋งŒ๋“ค์–ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ ๋ผ๋Š” ์ƒ์ƒ๋„ ํ•˜๋Š”๋ฐ ์ผ๋‹จ์€ ์ข€ ๋” ์œ ์ฆˆ์ผ€์ด์Šค๋ฅผ ๋Š˜๋ ค์„œ ํžŒํŠธ๋ฅผ ์ฐพ์œผ๋Ÿฌ ๋‹ค๋…€์•ผ๊ฒ ๋‹ค.