Redux: ๊ฐ์†๊ธฐ ๋ฐ ๊ตฌ์„ฑ ์š”์†Œ ํ–ฅ์ƒ๊ธฐ๋กœ ์ผ๋ฐ˜ ๋ชฉ๋ก์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

์— ๋งŒ๋“  2015๋…„ 09์›” 30์ผ  ยท  50์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: reduxjs/redux

์—ฌ๋ณด์„ธ์š”. ์นด์šดํ„ฐ ์˜ˆ์ œ ๋ฅผ ๋…๋ฆฝ ์นด์šดํ„ฐ์˜ ๋™์  ๋ชฉ๋ก์œผ๋กœ ํ™•์žฅํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

๋™์ ์ด๋ž€ ์นด์šดํ„ฐ ๋ชฉ๋ก ๋์— ์žˆ๋Š” UI์— ๋ชฉ๋ก์— ์ƒˆ ์นด์šดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋งˆ์ง€๋ง‰ ์นด์šดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•œ + ๋ฐ - ๋ฒ„ํŠผ์ด ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์ด์ƒ์ ์œผ๋กœ๋Š” ์นด์šดํ„ฐ ๊ฐ์†๊ธฐ์™€ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์ข…๋ฅ˜์˜ ์—”ํ„ฐํ‹ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ธฐ ์œ„ํ•ด ์ผ๋ฐ˜ํ™”๋œ ๋ชฉ๋ก ์ €์žฅ์†Œ+๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? todomvc-example ์—์„œ ์นด์šดํ„ฐ์™€ ํ•  ์ผ ํ•ญ๋ชฉ์„ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ๋ชฉ๋ก ์ €์žฅ์†Œ+๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋”์šฑ ์ผ๋ฐ˜ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์˜ˆ์ œ์— ์ด์™€ ๊ฐ™์€ ๊ฒƒ์ด ์žˆ์œผ๋ฉด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

examples

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

๋ชจ๋“  ์ข…๋ฅ˜์˜ ์—”ํ„ฐํ‹ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ธฐ ์œ„ํ•ด ์ผ๋ฐ˜ํ™”๋œ ๋ชฉ๋ก ์ €์žฅ์†Œ+๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? todomvc-example์—์„œ ์นด์šดํ„ฐ์™€ ํ•  ์ผ ํ•ญ๋ชฉ์„ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ๋ชฉ๋ก ์ €์žฅ์†Œ+๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋”์šฑ ์ผ๋ฐ˜ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์˜ˆ, ํ™•์‹คํžˆ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
๊ณ ์ฐจ ๊ฐ์†๊ธฐ์™€ ๊ณ ์ฐจ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

๊ณ ์ฐจ ๊ฐ์†๊ธฐ์˜ ๊ฒฝ์šฐ ์—ฌ๊ธฐ์—์„œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

function list(reducer, actionTypes) {
  return function (state = [], action) {
    switch (action.type) {
    case actionTypes.add:
      return [...state, reducer(undefined, action)];
    case actionTypes.remove:
      return [...state.slice(0, action.index), ...state.slice(action.index + 1)];
    default:
      const { index, ...rest } = action;
      if (typeof index !== 'undefined') {
        return state.map(item => reducer(item, rest));
      }
      return state;
    }
  }
}

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return counter + 1;
  case 'DECREMENT':
    return counter - 1;
  }
}

const listOfCounters = list(counter, {
  add: 'ADD_COUNTER',
  remove: 'REMOVE_COUNTER'
});

const store = createStore(listOfCounters);
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'INCREMENT',
  index: 0
});
store.dispatch({
  type: 'INCREMENT',
  index: 1
});
store.dispatch({
  type: 'REMOVE_COUNTER',
  index: 0
});

(์‹คํ–‰ํ•˜์ง€๋Š” ์•Š์•˜์ง€๋งŒ ์ตœ์†Œํ•œ์˜ ๋ณ€๊ฒฝ์œผ๋กœ ์ž‘๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)

๋ชจ๋“  50 ๋Œ“๊ธ€

๋‚˜๋Š” ์ด๊ฒƒ์ด ์ข‹์€ ์˜ˆ๋ผ๋Š” ๋ฐ ๋™์˜ํ•ฉ๋‹ˆ๋‹ค.
Redux๋Š” ์—ฌ๊ธฐ์—์„œ Elm Architecture ์™€ ์œ ์‚ฌํ•˜๋ฏ€๋กœ ์ž์œ ๋กญ๊ฒŒ ์˜ˆ์ œ์—์„œ ์˜๊ฐ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  ์ข…๋ฅ˜์˜ ์—”ํ„ฐํ‹ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ธฐ ์œ„ํ•ด ์ผ๋ฐ˜ํ™”๋œ ๋ชฉ๋ก ์ €์žฅ์†Œ+๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? todomvc-example์—์„œ ์นด์šดํ„ฐ์™€ ํ•  ์ผ ํ•ญ๋ชฉ์„ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ๋ชฉ๋ก ์ €์žฅ์†Œ+๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋”์šฑ ์ผ๋ฐ˜ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์˜ˆ, ํ™•์‹คํžˆ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
๊ณ ์ฐจ ๊ฐ์†๊ธฐ์™€ ๊ณ ์ฐจ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

๊ณ ์ฐจ ๊ฐ์†๊ธฐ์˜ ๊ฒฝ์šฐ ์—ฌ๊ธฐ์—์„œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.

function list(reducer, actionTypes) {
  return function (state = [], action) {
    switch (action.type) {
    case actionTypes.add:
      return [...state, reducer(undefined, action)];
    case actionTypes.remove:
      return [...state.slice(0, action.index), ...state.slice(action.index + 1)];
    default:
      const { index, ...rest } = action;
      if (typeof index !== 'undefined') {
        return state.map(item => reducer(item, rest));
      }
      return state;
    }
  }
}

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return counter + 1;
  case 'DECREMENT':
    return counter - 1;
  }
}

const listOfCounters = list(counter, {
  add: 'ADD_COUNTER',
  remove: 'REMOVE_COUNTER'
});

const store = createStore(listOfCounters);
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'ADD_COUNTER'
});
store.dispatch({
  type: 'INCREMENT',
  index: 0
});
store.dispatch({
  type: 'INCREMENT',
  index: 1
});
store.dispatch({
  type: 'REMOVE_COUNTER',
  index: 0
});

(์‹คํ–‰ํ•˜์ง€๋Š” ์•Š์•˜์ง€๋งŒ ์ตœ์†Œํ•œ์˜ ๋ณ€๊ฒฝ์œผ๋กœ ์ž‘๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์ด ์ž‘๋™ํ•˜๋„๋ก ๋…ธ๋ ฅํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” ์—ฌ์ „ํžˆ combineReducers ๋ฅผ ํ†ตํ•ด ๋ชฉ๋ก ๊ธฐ๋Šฅ์„ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ์นด์šดํ„ฐ ๋ชฉ๋ก๊ณผ ํ•  ์ผ ํ•ญ๋ชฉ ๋ชฉ๋ก์„ ๋‘˜ ๋‹ค ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š”์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์ด ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค๋ฉด. ๊ทธ๋Ÿฌ๋‚˜ ๋‚˜๋Š” ํ™•์‹คํžˆ ๊ทธ๊ฒƒ์„ ์‹œ๋„ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ์—ฌ์ „ํžˆ ์นด์šดํ„ฐ ๋ชฉ๋ก๊ณผ ํ•  ์ผ ํ•ญ๋ชฉ ๋ชฉ๋ก์„ ๋ชจ๋‘ ๊ฐ–๊ธฐ ์œ„ํ•ด CombineReducers๋ฅผ ํ†ตํ•ด ๋ชฉ๋ก ๊ธฐ๋Šฅ์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์ด ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค๋ฉด. ๊ทธ๋Ÿฌ๋‚˜ ๋‚˜๋Š” ํ™•์‹คํžˆ ๊ทธ๊ฒƒ์„ ์‹œ๋„ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์˜ˆ, ์™„์ „ํžˆ:

const reducer = combineReducers({
  counterList: list(counter, {
    add: 'ADD_COUNTER',
    remove: 'REMOVE_COUNTER'
  }),
  todoList: list(counter, {
    add: 'ADD_TODO',
    remove: 'REMOVE_TODO'
  }),
});

@gaearon ๋ชฉ๋ก ์ž‘์—…์€ index ๋ฅผ ์–ด๋–ป๊ฒŒ ์–ป์–ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

๊ท€ํ•˜์˜ ์ง€์‹œ์— ๋”ฐ๋ผ ๊ณ ์ฐจ ๊ฐ์†๊ธฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ ๊ณ ์ฐจ ๊ตฌ์„ฑ ์š”์†Œ๋กœ ์ธํ•ด ์–ด๋ ค์›€์„ ๊ฒช๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์šฐ๋ฆฌ์˜ ๊ตฌ์„ฑ ์š”์†Œ๋Š” Counter ์ด์™ธ์˜ ๋‹ค๋ฅธ ๊ตฌ์„ฑ ์š”์†Œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ์— ์ถฉ๋ถ„ํžˆ ์ผ๋ฐ˜์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ๋ฌธ์ œ๋Š” ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ž‘์—…์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์—์„œ ์†”๋ฃจ์…˜์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. https://github.com/Zeikko/redux/commit/6a222885c8c93950dbdd0d4cf3532cd99a32206c

๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ๋ถ€๋ถ„์„ ๊ฐ•์กฐํ•˜๊ธฐ ์œ„ํ•ด ์ปค๋ฐ‹์— ๋ช‡ ๊ฐ€์ง€ ์ฃผ์„์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

์นด์šดํ„ฐ ๋ชฉ๋ก ๋ชฉ๋ก์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ๋ฐ˜ ๋ชฉ๋ก ๋ฆฌ๋“€์„œ + ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์žˆ์œผ๋ฉด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ˜„์žฌ ์šฐ๋ฆฌ์˜ ๊ตฌ์„ฑ ์š”์†Œ๋Š” Counter ์ด์™ธ์˜ ๋‹ค๋ฅธ ๊ตฌ์„ฑ ์š”์†Œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ์— ์ถฉ๋ถ„ํžˆ ์ผ๋ฐ˜์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ๋ฌธ์ œ๋Š” ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ž‘์—…์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

"์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ธ๋ฑ์Šค ์ถ”๊ฐ€"๊ฐ€ ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์ž‘์—…์—์„œ index ํ‚ค์— ๋Œ€ํ•ด ๋‹ค๋ฅธ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์œผ์‹ญ๋‹ˆ๊นŒ?

์ด์ œ ๋ฌด์Šจ ๋ง์ธ์ง€ ์•Œ ๊ฒƒ ๊ฐ™์•„์š”.

์ง€๊ธˆ์€ ๋Œ“๊ธ€์„ ๋งŽ์ด ๋ชป์จ์„œ ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ๋‚ด์ผ ๋Œ์•„์˜ฌ ๊ฒƒ์ด๋‹ค.

์ด์ œ ๋ฌธ์ œ๋ฅผ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์ฐพ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Redux๊ฐ€ Elm ์•„ํ‚คํ…์ฒ˜์—์„œ ๋ฒ—์–ด๋‚˜๋Š” ๋ฐฉ์‹์— ๋‚ด์žฌ๋œ ๋ช‡ ๊ฐ€์ง€ ์ œํ•œ ์‚ฌํ•ญ์— ๋น ๋ฅด๊ฒŒ ๋ถ€๋”ช์ณค์Šต๋‹ˆ๋‹ค.
๋‚ด๊ฐ€ ์ „์— ๊ทธ๋“ค์„ ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ๋‹ค๋Š” ์œ ๊ฐ์ž…๋‹ˆ๋‹ค!

  • ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ๋ž˜ํ•‘๋  ๋•Œ๊นŒ์ง€ ์ฝœ๋ฐฑ์ด ์•„๋‹Œ dispatch ์†Œํ’ˆ์„ ์ˆ˜๋ฝํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์•ก์…˜ ์ƒ์„ฑ์ž๋ฅผ ์†Œํ’ˆ์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ํ”ผํ•˜๊ณ  ๊ตฌ์„ฑ๋œ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ dispatch() ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ connect() bindActionCreators(Component, actionCreators) => Component ๋ฅผ ๋„์ž…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‹ค์ œ๋กœ ์ƒ์ ์— ์—ฐ๊ฒฐํ•˜์ง€ ์•Š๊ณ  ๋Œ€์‹  action-creator-as-props-wanting-component๋ฅผ this.props.dispatch -wanting-component๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
  • ๋ž˜ํ•‘๋œ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํ•„์š”ํ•œ ์ž‘์—…์„ ์ „๋‹ฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋ถ€๋„๋Ÿฌ์šด ์ผ์ž…๋‹ˆ๋‹ค! ์นด์šดํ„ฐ๋ฅผ ๋ชฉ๋ก์œผ๋กœ ๋ž˜ํ•‘ํ•˜๋ฉด ๋ฐฉ๊ธˆ ๋ฐœ์†กํ•œ function (dispatch, getState) { ... } $์ด ๋ชฉ๋ก์— ์˜ํ•ด { action: function (dispatch, getState) { ... } } ๋กœ ๋ฐ”๋€Œ๊ธฐ ๋•Œ๋ฌธ์— ๋” ์ด์ƒ incrementAsync() ๋ฅผ ๋ฐœ์†กํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  bam! ์ฝํฌ ๋ฏธ๋“ค์›จ์–ด๋Š” ๋” ์ด์ƒ ๊ทธ๊ฒƒ์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์— ๋Œ€ํ•œ ์–ด์ƒ‰ํ•˜์ง€ ์•Š์€ ์†”๋ฃจ์…˜์ด ์žˆ์„ ์ˆ˜ ์žˆ์ง€๋งŒ ์•„์ง ๋ณด์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.
์ง€๊ธˆ์€ ์ด ์ปค๋ฐ‹ ์„ ์˜ˆ์‹œ๋กœ ๋ณด์‹ญ์‹œ์˜ค(์œ„์— ์„ค๋ช…๋œ ์ œํ•œ ์‚ฌํ•ญ ํฌํ•จ).

์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ตฌ์„ฑ ์š”์†Œ/Counter.js

import React, { Component, PropTypes } from 'react';
import { increment, incrementIfOdd, incrementAsync, decrement } from '../actions/counter';

class Counter extends Component {
  render() {
    const { dispatch, counter } = this.props;
    return (
      <p>
        Clicked: {counter} times
        {' '}
        <button onClick={() => dispatch(increment())}>+</button>
        {' '}
        <button onClick={() => dispatch(decrement())}>-</button>
        {' '}
        <button onClick={() => dispatch(incrementIfOdd())}>Increment if odd</button>
        {' '}
        <button onClick={() => dispatch(incrementAsync())}>Increment async</button>
      </p>
    );
  }
}

Counter.propTypes = {
  dispatch: PropTypes.func.isRequired,
  counter: PropTypes.number.isRequired
};

export default Counter;

๊ตฌ์„ฑ ์š”์†Œ/list.js

import React, { Component, PropTypes } from 'react';
import { addToList, removeFromList, performInList } from '../actions/list';

export default function list(mapItemStateToProps) {
  return function (Item) {
    return class List extends Component {
      static propTypes = {
        dispatch: PropTypes.func.isRequired,
        items: PropTypes.array.isRequired
      };

      render() {
        const { dispatch, items } = this.props;
        return (
          <div>
            <button onClick={() =>
              dispatch(addToList())
            }>Add counter</button>

            <br />
            {items.length > 0 &&
              <button onClick={() =>
                dispatch(removeFromList(items.length - 1))
              }>Remove counter</button>
            }
            <br />
            {this.props.items.map((item, index) =>
              <Item {...mapItemStateToProps(item)}
                    key={index}
                    dispatch={action =>
                      dispatch(performInList(index, action))
                    } />
            )}
          </div>
        )
      }
    }
  };
}

์•ก์…˜/counter.js

export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

export function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

export function decrement() {
  return {
    type: DECREMENT_COUNTER
  };
}

export function incrementIfOdd() {
  return (dispatch, getState) => {
    const { counter } = getState();

    if (counter % 2 === 0) {
      return;
    }

    dispatch(increment());
  };
}

export function incrementAsync(delay = 1000) {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment());
    }, delay);
  };
}

์ž‘์—…/๋ชฉ๋ก.js

export const ADD_TO_LIST = 'ADD_TO_LIST';
export const REMOVE_FROM_LIST = 'REMOVE_FROM_LIST';
export const PERFORM_IN_LIST = 'PERFORM_IN_LIST';

export function addToList() {
  return {
    type: ADD_TO_LIST
  };
}

export function removeFromList(index) {
  return {
    type: REMOVE_FROM_LIST,
    index
  };
}

export function performInList(index, action) {
  return {
    type: PERFORM_IN_LIST,
    index,
    action
  };
}

๊ฐ์†๊ธฐ/counter.js

import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter';

export default function counter(state = 0, action) {
  switch (action.type) {
  case INCREMENT_COUNTER:
    return state + 1;
  case DECREMENT_COUNTER:
    return state - 1;
  default:
    return state;
  }
}

๊ฐ์†๊ธฐ/list.js

import { ADD_TO_LIST, REMOVE_FROM_LIST, PERFORM_IN_LIST } from '../actions/list';

export default function list(reducer) {
  return function (state = [], action) {
    const {
      index,
      action: innerAction
    } = action;

    switch (action.type) {
    case ADD_TO_LIST:
      return [
        ...state,
        reducer(undefined, action)
      ];
    case REMOVE_FROM_LIST:
      return [
        ...state.slice(0, index),
        ...state.slice(index + 1)
      ];
    case PERFORM_IN_LIST:
      return [
        ...state.slice(0, index),
        reducer(state[index], innerAction),
        ...state.slice(index + 1)
      ];
    default:
      return state;
    }
  }
}

๊ฐ์†๊ธฐ/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);

const rootReducer = combineReducers({
  counterList
});

export default rootReducer;

์ปจํ…Œ์ด๋„ˆ/App.js

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import Counter from '../components/Counter';
import list from '../components/list';

const CounterList = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(Counter);

export default connect(function mapStateToProps(state) {
  return {
    items: state.counterList
  };
})(CounterList);

cc @acdlite โ€” ํ˜„์žฌ ๋ฏธ๋“ค์›จ์–ด + React Redux ๋””์ž์ธ์ด ๋‹ค์†Œ ๋ฌด๋„ˆ์ง„ ์˜ˆ์ž…๋‹ˆ๋‹ค.
์ด๊ฒƒ์„ wontfix๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์šฐ๋ฆฌ๊ฐ€ ์ด๊ฒƒ์„ ํ”ผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ผ ๊ฒƒ์ด๋‹ค
https://github.com/erikras/multireducer/ ๋„์›€๋ง?

์ €๋Š” React์šฉ ์„œ๋น„์Šค(IoC) ์ปจํ…Œ์ด๋„ˆ ์‚ฌ์šฉ์„ ์‹คํ—˜ํ•ด ์™”์œผ๋ฉฐ ์–ด์ œ ์ด ํ…Œ์ŠคํŠธ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. https://github.com/magnusjt/react-ioc

CounterList๊ฐ€ ์•Œ์ง€ ๋ชปํ•˜๋Š” ์ƒํƒœ์—์„œ ์ž‘์—… ์ƒ์„ฑ์ž๋ฅผ Counter์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž ์žฌ์ ์œผ๋กœ ๋ฌธ์ œ์˜ ์ผ๋ถ€๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์•ก์…˜ ์ƒ์„ฑ์ž๊ฐ€ props๊ฐ€ ์•„๋‹Œ Counter์˜ ์ƒ์„ฑ์ž์— ๋“ค์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ƒˆ๋กœ ๋งŒ๋“œ๋Š” ๋ชจ๋“  Counter ๊ตฌ์„ฑ ์š”์†Œ์— ๋Œ€ํ•ด ๋‹ค๋ฅธ ์ž‘์—… ์ž‘์„ฑ์ž๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(์•„๋งˆ๋„ ์ธ๋ฑ์Šค ๊ฐ’์„ ์ž‘์—… ์ž‘์„ฑ์ž์— ๋ฐ”์ธ๋”ฉํ•˜์—ฌ). ๋ฌผ๋ก  ๋ฐ์ดํ„ฐ๋ฅผ ์นด์šดํ„ฐ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์ด ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ธ์ง€๋Š” ์•„์ง ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

@gaearon , ๋‹น์‹ ์˜ ์˜ˆ๋Š” ๋‚˜์—๊ฒŒ ๋”ฑ ๋งž๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์•ก์…˜ ์ƒ์„ฑ์ž๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ์•„๋ž˜๋กœ ํŒŒ๊ฒฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์‹์œผ๋กœ ๊ณ ์ฐจ์› ๊ธฐ๋Šฅ์œผ๋กœ ์•ก์…˜์„ ์ œ๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” ๋‹น์‹ ์˜ ๋‘ ๋ฒˆ์งธ ์š”์ ์ด ํ•„์š”ํ•œ์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€ ํ˜•์‹์œผ๋กœ ์ธํ•ด ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋†“์น  ์ˆ˜ ์žˆ์ง€๋งŒ performInList ์˜ ๋” ํฐ ๋ฌธ์ œ๋Š” ์ถ”์ƒํ™”๋ฅผ ํ•˜๋‚˜์˜ ๋ชฉ๋ก์œผ๋กœ ์ œํ•œํ–ˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. @pe3 ์€ ์นด์šดํ„ฐ ๋ชฉ๋ก ๋ชฉ๋ก์„ ์–ธ๊ธ‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์€ ์ž„์˜์˜ ์ถ”์ƒํ™”๋ฅผ ์œ„ํ•ด์„œ๋Š” ์–ด๋–ป๊ฒŒ๋“  ์•ก์…˜์„ ์ค‘์ฒฉํ•ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์ด ํ‹ฐ์ผ“์—์„œ ์œ ํ˜•์„ ์ค‘์ฒฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•ด ๋ƒˆ์Šต๋‹ˆ๋‹ค.
https://github.com/rackt/redux/issues/897

๊ทธ๋Ÿฌ๋‚˜ ๋” ๊ทผ๋ณธ์ ์œผ๋กœ, ๋‹น์‹ ์€ ํ–‰๋™์„ ์™„์ „ํžˆ ์ค‘์ฒฉํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค....

์ข‹์•„, ๋ฐฉ๊ธˆ ํ•ด๋ดค์–ด.

๋‚˜๋Š” ๋ฌผ๊ฑด์„ ๊ฝค ๋‹จ์ˆœํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ‹์ง„ ์ผ์„ ํ•˜๊ธฐ ์ „์— ์นด์šดํ„ฐ ์•ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

https://github.com/ccorcos/redux-lifted-reducers/blob/80295a09c4d04654e6b36ecc8bc1bfac4ae821c7/index.js#L49

๋‹ค์Œ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

https://github.com/ccorcos/redux-lifted-reducers/blob/1fdafe8ed29303822018cde4973fda3305b43bb6/index.js#L57

๋‚˜๋Š” "๋ฆฌํ”„ํŠธ"๊ฐ€ ์ ์ ˆํ•œ ์šฉ์–ด์ธ์ง€ ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ๊ทธ๊ฒƒ์ด ์˜๋ฏธํ•˜๋Š” ๋ฐ”๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์ง€๋งŒ ๋‚˜์—๊ฒŒ๋Š” ๊ดœ์ฐฎ๋‹ค๊ณ  ๋Š๊ผˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ ์ž‘์—…์„ ํ•ด์ œํ•˜๋ฉด ๋‹ค๋ฅธ ์ž‘์—… ์•ˆ์— ์ž‘์—…์„ ์ค‘์ฒฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

const liftActionCreator = liftingAction => actionCreator => action => Object.assign({}, liftingAction, { nextAction: actionCreator(action) })

๊ทธ๋ฆฌ๊ณ  ๋‚ดํฌ๋œ ๋™์ž‘์€ ๊ฐ์†๊ธฐ๋ฅผ ๋“ค์–ด ์˜ฌ๋ ค ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. ๋ฆฌํ”„ํŒ… ๊ฐ์†๊ธฐ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•˜์œ„ ๊ฐ์†๊ธฐ(์ ์ ˆํ•œ ๋™์ž‘์œผ๋กœ ๋ถ€๋ถ„์ ์œผ๋กœ ์ ์šฉ๋จ)๋ฅผ ์ผ๋ถ€ ํ•˜์œ„ ์ƒํƒœ์— ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

const liftReducer = liftingReducer => reducer => (state, action) => liftingReducer(state, action)((subState) => reducer(subState, action.nextAction))

๋”ฐ๋ผ์„œ ๊ตฌ์„ฑ ์š”์†Œ ๋ชฉ๋ก ๊ฐ์†๊ธฐ์˜ ๊ฒฝ์šฐ ํ•˜์œ„ ์ž‘์—…์ด ์ ์šฉ๋˜๋Š” ์ธ๋ฑ์Šค์˜ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์ง€์ •ํ•˜๋Š” ์ž‘์—…์ด ์žˆ์Šต๋‹ˆ๋‹ค.

// list actions
const LIST_INDEX = 'LIST_INDEX'
function actOnIndex(i) {
  return {
    type: LIST_INDEX,
    index: i
  }
}

๊ทธ๋ฆฌ๊ณ  ํ•˜์œ„ ๊ฐ์†๊ธฐ๋ฅผ ์ ์ ˆํ•œ ํ•˜์œ„ ์ƒํƒœ์— ์ ์šฉํ•˜๋Š” "๊ณ ์ฐจ"(๋ฐ”๋กœ ๋Š๋‚Œ์ด ๋“œ๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฉ‹์ง„ ์šฉ์–ด, ํ•˜ํ•˜ ;) ๊ฐ์†๊ธฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

const list = (state=[], action) => (reduce) => {
  switch (action.type) {
    case LIST_INDEX:
      let nextState = state.slice(0)
      nextState[action.index] = reduce(nextState[action.index])
      return nextState
    default:
      return state;
  }
}

๊ทธ๋ฆฌ๊ณ  ๋‚จ์€ ๊ฒƒ์€ ์นด์šดํŠธ ๊ฐ์†๊ธฐ๋ฅผ ๋ชฉ๋ก ๊ฐ์†๊ธฐ๋กœ "๋“ค์–ด ์˜ฌ๋ฆฌ๋Š”" ๊ฒƒ์ž…๋‹ˆ๋‹ค.

const reducer = combineReducers({
  counts: liftReducer(list)(count)
});

์ด์ œ ์นด์šดํ„ฐ ๋ชฉ๋ก์— ๋Œ€ํ•ด ์นด์šดํ„ฐ๋กœ ์ „๋‹ฌํ•  ๋•Œ ์ž‘์—…์„ ํ•ด์ œํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

class App extends Component {
  render() {
    const counters = [0,1,2,3,4].map((i) => {
      return (
        <Counter count={this.props.state.counts[i]}
                 increment={liftActionCreator(actOnIndex(i))(increment)}
                 decrement={liftActionCreator(actOnIndex(i))(decrement)}
                 dispatch={this.props.dispatch}
                 key={i}/>
      )
    })
    return (
      <div>
        {counters}
      </div>
    );
  }
}

๋‚˜๋Š” ์ด๊ฒƒ์ด ์ ์ ˆํ•œ ์šฉ์–ด๋กœ ๋” ๊ณต์‹ํ™” ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ ๊ณ ์ฐจ ๋ฆฌ๋“€์„œ์—๋„ ๋ Œ์ฆˆ๋ฅผ ์“ธ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์€๋ฐ, ์ œ๋Œ€๋กœ ์จ๋ณธ ์ ์ด ์—†๊ตฐ์š”, ํ•˜ํ•˜.

๊ทธ๋ฆฌ๊ณ  ์ €๋Š” ์ง€๋‚œ๋ฒˆ ๋Œ“๊ธ€์—์„œ ์ œ๊ฐ€ ํ•œ ๋ง์„ ๋˜๋Œ๋ฆฝ๋‹ˆ๋‹ค -- @gaearon ์ด ์˜ณ์Šต๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์ด ์ž‘์—…์„ ์ค‘์ฒฉํ•˜๋ฉด ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋†“์น˜๊ฒŒ ๋˜๊ณ  ์ž‘์—… ์ƒ์„ฑ์ž๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋””์ŠคํŒจ์น˜๋ฅผ โ€‹โ€‹๋๊นŒ์ง€ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด Redux๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ํ•˜์œ„ ์ž‘์—…์„ ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ๋Š” ๋ชฉ๋ก ๋‚ด์˜ ์ƒํƒœ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค...

๋‹น์‹ ์ด ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์€ Elm Architecture๋กœ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค: https://github.com/gaearon/react-elmish-example

์นœ๊ตฌ, ๋‹น์‹ ์€ ํ•ญ์ƒ ํ•œ๋ฐœ ์•ž์„œ ์žˆ์Šต๋‹ˆ๋‹ค! ๋ฉ‹์ง„ ๊ฒƒ๋“ค์— ๋Œ€ํ•œ ๋งํฌ๋กœ ์ €์—๊ฒŒ ์ƒค์›Œํ•˜์„ธ์š” :+1:

๋ž˜ํ•‘๋œ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํ•„์š”ํ•œ ์ž‘์—…์„ ์ „๋‹ฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋ถ€๋„๋Ÿฌ์šด ์ผ์ž…๋‹ˆ๋‹ค! ๋ชฉ๋ก์œผ๋กœ ์นด์šดํ„ฐ๋ฅผ ๋ž˜ํ•‘ํ•˜๋ฉด function (dispatch, getState) { ... } ๋ฐฉ๊ธˆ ๋””์ŠคํŒจ์น˜ํ•œ ๊ฒƒ์ด { action: function (dispatch, getState) { ... } }๋กœ ๋ฐ”๋€Œ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋” ์ด์ƒ incrementAsync()๋ฅผ ๋””์ŠคํŒจ์น˜ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ชฉ๋ก๊ณผ bam! ์ฝํฌ ๋ฏธ๋“ค์›จ์–ด๋Š” ๋” ์ด์ƒ ๊ทธ๊ฒƒ์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.

@gaearon ์ด ์†”๋ฃจ์…˜์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ? ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž์‹ ๊ฐ์†๊ธฐ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ผ๋ฐ˜ ๊ฐ์†๊ธฐ ๋Œ€์‹ 

case PERFORM_IN_LIST:
      return [
        ...state.slice(0, index),
        reducer(state[index], innerAction),
        ...state.slice(index + 1)
      ];

์ƒ์ ์— dispatch ์ฒ˜๋Ÿผ ์ž‘๋™ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ํŠน๋ณ„ํ•œ ๋ฉ”์†Œ๋“œ dispatchTo(reducer, state, action, callback) ์ œ๊ณต

export default function list(reducer, dispatchTo) {
  return function (state = [], action) {
    ...
    case PERFORM_IN_LIST:
      dispatchTo(reducer, state[index], innerAction, newState =>
         [
           ...state.slice(0, index),
           newState,
           ...state.slice(index + 1)
        ]);
       default:
          return state;
    }
  }
}

์ด๊ฒƒ์ด Redux์—์„œ ๊ฐ€๋Šฅํ•œ์ง€ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ์•„์ด๋””์–ด๋Š” ์ผ๋ถ€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ฃจํŠธ ์ €์žฅ์†Œ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ์„ฑ๋œ ์ƒํƒœ ํŠธ๋ฆฌ์˜ ํ•ด๋‹น ๋ถ€๋ถ„์— ๋Œ€ํ•œ ํ•˜์œ„ ์ €์žฅ์†Œ ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ผ๋ถ€ ๋‚ด๋ถ€ store.derive(reducer, state) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ dispatchTo ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด

function dispatchTo(reducer, state, action, callback) {
  const childStore = store.derive(reducer, state)
  childStore.subscribe(() => setRootState( callback(getState() ))
  childStore.dispatch(action)
}

์ด๊ฒƒ์€ ๋‚ด๊ฐ€ Redux์˜ ๋‚ด๋ถ€๋ฅผ ์•Œ์ง€ ๋ชปํ•œ๋‹ค๊ณ  ๋งํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์ง€ ์•„์ด๋””์–ด์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์•„๋งˆ๋„ ์ œ๊ฐ€ ๋ญ”๊ฐ€๋ฅผ ๋†“์ณค์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํŽธ์ง‘ํ•˜๋‹ค
_์•„๋งˆ๋„ ์ด๊ฒƒ์€ ๊ฐ์†๊ธฐ ๋ฉ”์„œ๋“œ๊ฐ€ ๋™๊ธฐ์‹์ด์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์–ด์ƒ‰ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฉ”์„œ๋“œ๋ฅผ ๋น„๋™๊ธฐ์‹์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ๋ชจ๋“  ์—ฐ๊ฒฐ๋„ ๋น„๋™๊ธฐ์‹์ด์–ด์•ผ ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
์•„๋งˆ๋„ ๊ฐ€์žฅ ์ข‹์€ ํ•ด๊ฒฐ์ฑ…์€ store.derive(reducer) ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ๋…ธ์ถœํ•˜๊ณ  ์ผ์ข…์˜ Store ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๋ฐ˜ ๊ฐ์†๊ธฐ๋ฅผ ๋นŒ๋“œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์€ ๋„ˆ๋ฌด ๋งŽ์€ ํ•ฉ๋ณ‘์ฆ์ด๋ฉฐ IMO์˜ ๊ฐ€์น˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์‹์œผ๋กœ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค(๋˜๋Š” applyMiddleware ์˜ ๋Œ€์ฒด ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค). ๊ทธ๋Ÿฌ๋ฉด ๋ชจ๋“  ์ค€๋น„๊ฐ€ ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ, ์ด์— ๋Œ€ํ•ด ์กฐ์น˜๋ฅผ ์ทจํ•  ๊ณ„ํš์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋‹ซ์Šต๋‹ˆ๋‹ค.

ํ† ๋ก ์„ ์œ„ํ•ด @gaearon :
์ด ์ปค๋ฐ‹์— ์ฒจ๋ถ€ํ•œ ์ฝ”๋“œ ์˜ˆ์ œ: https://github.com/rackt/redux/commit/a83002aed8e36f901ebb5f139dd14ce9c2e4cab4

2๊ฐœ์˜ ์นด์šดํ„ฐ ๋ชฉ๋ก(๋˜๋Š” ๋ณ„๋„์˜ '๋ชจ๋ธ')์ด ์žˆ๋Š” ๊ฒฝ์šฐ addToList ๋ฅผ ๋ฐœ์†กํ•˜๋ฉด ๋‘ ๋ชฉ๋ก์— ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์ž‘์—… ์œ ํ˜•์ด ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

// reducers/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);
const counterList2 = list(counter);

const rootReducer = combineReducers({
  counterList,
  counterList2
});

export default rootReducer;

์—ฌ๊ธฐ์„œ reducers/list ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋„์›€์ด ๋˜๋‚˜์š”? ์ž‘์—… ์œ ํ˜•์ด๋‚˜ ๋‹ค๋ฅธ ๊ฒƒ์— ์ ‘๋‘์‚ฌ๋ฅผ ๋ถ™์ผ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๊นŒ?

2๊ฐœ์˜ ์นด์šดํ„ฐ ๋ชฉ๋ก(๋˜๋Š” ๋ณ„๋„์˜ '๋ชจ๋ธ')์ด ์žˆ๋Š” ๊ฒฝ์šฐ addToList๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ์ž‘์—… ์œ ํ˜•์ด ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‘ ๋ชฉ๋ก์— ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

a83002aed8e36f901ebb5f139dd14ce9c2e4cab4๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณด์‹ญ์‹œ์˜ค. ๊ทธ๊ฒƒ์€ ํ–‰๋™์„ ์ค‘์ฒฉํ•ฉ๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์ „๋‹ฌ๋œ dispatch ๋Š” ์•ก์…˜์„ performInList ์•ก์…˜์œผ๋กœ ๋ž˜ํ•‘ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๋‚ด๋ถ€ ์ž‘์—…์ด ๊ฐ์†๊ธฐ์—์„œ ๊ฒ€์ƒ‰๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด Elm Architecture๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

@gaearon ์–ด์ฉŒ๋ฉด ๋‚ด๊ฐ€ ๋ญ”๊ฐ€๋ฅผ ๋†“์น˜๊ณ  ์žˆ์„์ง€๋„ ๋ชจ๋ฅด์ง€๋งŒ ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ์ถ”๊ฐ€ counterList2 ๋ฆฌ๋“€์„œ์™€ ํ•จ๊ป˜ ์ด UI๋Š” ์—ฌ์ „ํžˆ ๊ฐ ์ž‘์—…์— ๋Œ€ํ•œ ๋‘ ๋ชฉ๋ก์„ ๋ชจ๋‘ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค(๊ตฌ์„ฑ ๋ฐฉ์‹์— ๋”ฐ๋ผ ์˜ˆ์ƒ๋˜์ง€๋งŒ ์†”๋ฃจ์…˜์€ ๋ฌด์—‡์ธ๊ฐ€์š”?):

// reducers/index.js

import { combineReducers } from 'redux';
import counter from './counter';
import list from './list'

const counterList = list(counter);
const counterList2 = list(counter);

const rootReducer = combineReducers({
  counterList,
  counterList2
});

export default rootReducer;


// containers/App.js

import React from 'react';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import counter from '../components/Counter';
import list from '../components/list';

let CounterList = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(counter);

CounterList = connect(function mapStateToProps(state) {
  return {
    items: state.counterList
  };
})(CounterList);

let CounterList2 = list(function mapItemStateToProps(itemState) {
  return {
    counter: itemState
  };
})(counter);

CounterList2 = connect(function mapStateToProps(state) {
  return {
    items: state.counterList2
  };
})(CounterList2);


export default class App extends React.Component {
  render() {
    return (
      <div>
        <CounterList />
        <CounterList2 />
      </div>
    )
  }
}

@elado ์นด์šดํ„ฐ ๋ชฉ๋ก๊ณผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ๋‘ ๋ชฉ๋ก์— ๋Œ€ํ•ด ์ž‘์—…์ด ์ถฉ๋Œํ•˜์ง€ ์•Š๋„๋ก ๋ชฉ๋ก์œผ๋กœ ๋‹ค์‹œ ๋ž˜ํ•‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@ccorcos

๋ชฉ๋ก์œผ๋กœ ๋‹ค์‹œ ํฌ์žฅํ•˜๋ ค๋ฉด

์ •ํ™•ํžˆ ๋ฌด์—‡์„ ํฌ์žฅ?

@ccorcos
์—ฌ๊ธฐ์— ์˜ˆ์ œ๋ฅผ ์—…๋กœ๋“œํ–ˆ์Šต๋‹ˆ๋‹ค: http://elado.s3-website-us-west-1.amazonaws.com/redux-counter/
์†Œ์Šค ๋งต์ด ์žˆ์Šต๋‹ˆ๋‹ค.

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

๊ทธ๋ž˜์„œ ๋‚˜๋Š” ์‹ค์ œ Redux์˜ ๊ธฐ๋Šฅ์„ ์ž˜ ๋ชจ๋ฅด์ง€๋งŒ ๋Š๋ฆ…๋‚˜๋ฌด์— ๋Œ€ํ•ด์„œ๋Š” ์•„์ฃผ ์ž˜ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์ƒˆ ์˜ˆ์—์„œ๋Š” ์ตœ์ƒ์œ„ ์ˆ˜์ค€์—์„œ ์ž‘์—… ์ƒ์„ฑ์ž๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ๋””์ŠคํŒจ์น˜ ํ•จ์ˆ˜๋ฅผ ํ•˜์œ„ ๊ตฌ์„ฑ ์š”์†Œ๋กœ ์ „๋‹ฌํ•˜๊ณ  ํ•ด๋‹น ํ•˜์œ„ ๊ตฌ์„ฑ ์š”์†Œ๋Š” ์ž‘์—…์„ ๋””์ŠคํŒจ์น˜ ํ•จ์ˆ˜๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถ”์ƒํ™”๊ฐ€ ์ž˜ ์ž‘๋™ํ•˜๋„๋ก ํ•˜์—ฌ ์ž‘์—… ์ถฉ๋Œ์ด ์—†๋„๋ก ํ•˜๋ ค๋ฉด "listOf" ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ๋””์ŠคํŒจ์น˜๋ฅผ โ€‹โ€‹์ž์‹์—๊ฒŒ ์ „๋‹ฌํ•  ๋•Œ ์‹ค์ œ๋กœ ๋ชฉ๋ก ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•์‹์œผ๋กœ ์ž‘์—…์„ ๋ž˜ํ•‘ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

children.map((child, i) => {
  childDispatch = (action) => dispatch({action, index: i})
  // ...

์ด์ œ listOf(counter) ๋˜๋Š” listOf(listOf(counter)) ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ pairOf ๋ผ๋Š” ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋ฉด ์•ก์…˜์„ ์ „๋‹ฌํ•  ๋•Œ ๋žฉํ•‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ App ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๋””์ŠคํŒจ์น˜ ํ•จ์ˆ˜๋ฅผ ๋ž˜ํ•‘ํ•˜์ง€ ์•Š๊ณ  ๋‚˜๋ž€ํžˆ ๋ Œ๋”๋งํ•˜๋ฏ€๋กœ ์ž‘์—… ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

@ccorcos

๊ฐ์‚ฌ ํ•ด์š”. ๋”ฐ๋ผ์„œ ๊ฒฐ๋ก ์ ์œผ๋กœ "๋งˆ๋ฒ•"์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž‘์—…์—๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ์†๊ธฐ์— ์•Œ๋ ค์ฃผ๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ์ •๋ณด๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. listOf(listOf(counter)) ์ธ ๊ฒฝ์šฐ ์ž‘์—…์—๋Š” 2D ๋ฐฐ์—ด์˜ ๋‘ ์ธ๋ฑ์Šค๊ฐ€ ๋ชจ๋‘ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. listOf(listOf(counter)) ๋Š” ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋‹จ์ผ ํ”Œ๋žซ ๋ชฉ๋ก์˜ ๋ชจ๋“  ์นด์šดํ„ฐ๊ฐ€ ์ž‘์—…์—์„œ ์ „๋‹ฌ๋˜๋Š” ์œ ์ผํ•œ ID๋กœ ์ธ๋ฑ์‹ฑ๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ๋” ์œ ์—ฐํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

์œ ์—ฐํ•˜๊ณ  ๋ณต์žกํ•œ Redux ์•ฑ์„ ๋นŒ๋“œํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ ์‹œ์Šคํ…œ์˜ ๋ชจ๋“  ์—”ํ„ฐํ‹ฐ๊ฐ€ ์Šคํ† ์–ด์˜ ID๋กœ ์ธ๋ฑ์‹ฑ๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋•Œ๋•Œ๋กœ ์˜ˆ์ œ์—์„œ ์ œ๊ณต๋˜๋Š” ๋‹ค๋ฅธ ๊ฐ„๋‹จํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์€ ํ•œ๊ณ„์— ๋น ๋ฅด๊ฒŒ ๋„๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด ์ธ๋ฑ์Šค๋Š” ๊ฑฐ์˜ ๊ด€๊ณ„ํ˜• DB์˜ ๋ฏธ๋Ÿฌ์ž…๋‹ˆ๋‹ค.

์ž‘์—…์—๋Š” 2d ๋ฐฐ์—ด์˜ ๋‘ ์ธ๋ฑ์Šค๊ฐ€ ๋ชจ๋‘ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‹น์‹ ์˜ ์ƒ๊ฐ๊ณผ ํ–‰๋™์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{type: 'increment', index:[0 5]}

ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

{type:'child', index: 0, action: {type: 'child', index: 5, action: {type: 'increment'}}}

๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด listOf(listOf(listOf(...listOf(counter)...))) ์˜์›ํžˆ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์ด๊ฒƒ์€ ๋ชจ๋‘ ๋Š๋ฆ…๋‚˜๋ฌด์—์„œ ์˜ต๋‹ˆ๋‹ค. btw. Elm ์•„ํ‚คํ…์ฒ˜ ํŠœํ† ๋ฆฌ์–ผ ์„ ํ™•์ธํ•˜์„ธ์š”

๋‚˜๋Š” ํŒŒํ‹ฐ์— ์กฐ๊ธˆ ๋Š๋ฆฌ์ง€ ๋งŒ ์ด๊ฒƒ์ด "๋ฏธ๋“ค์›จ์–ด์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”"๊ณณ์ด ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๊นŒ? @ccorcos ๋กœ ๋ฌดํ•œ ์ค‘์ฒฉ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ ์ค‘์ฒฉ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด ์ค‘์ฒฉ๋œ ์ž‘์—…์ด ์ด์ƒํ•จ์„ ์˜๋ฏธํ•˜๋Š” redux-thunk ์— ๋Œ€ํ•ด์„œ๋งŒ ์ด์•ผ๊ธฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

๋ฏธ๋“ค์›จ์–ด๋Š” ๋™์ž‘์„ ์žˆ๋Š” ๊ทธ๋Œ€๋กœ ํ•ด์„ํ• ์ง€ ์•„๋‹ˆ๋ฉด ์ค‘์ฒฉ ๋™์ž‘์„ ์ฐพ์•„์•ผ ํ•˜๋Š”์ง€ ์–ด๋–ป๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์•„ํ•˜.

@gaearon

์•ˆ๋…•.

๋‚˜๋Š” ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ดํ•ดํ–ˆ๋‹ค๊ณ  ๊ธ์ •์ ์ด์ง€ ์•Š์œผ๋ฉฐ ๊ฐ„๋‹จํ•œ ๋‹ต๋ณ€์— ์ •๋ง ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

๋™์ผํ•œ ํŽ˜์ด์ง€์— ์—ฌ๋Ÿฌ ๊ตฌ์„ฑ ์š”์†Œ ๋ณต์‚ฌ๋ณธ์ด ์žˆ๋Š” ๊ฒฝ์šฐ _๋ฏธ๋“ค์›จ์–ด(๋ฐ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋Œ€๋ถ€๋ถ„์˜ Redux ์—์ฝ”์‹œ์Šคํ…œ)๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๊นŒ? ๋‚˜๋Š” ๋˜ํ•œ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๋‹ค์ค‘ ๊ฐ์†๊ธฐ ์ œ์•ˆ์— ์‘๋‹ตํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋ณด์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  ์„ค๋ช…์ด ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์•„๋‹ˆ์š”, ์ด๊ฒƒ์€ ํ•ด์ƒ๋„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์Šค๋ ˆ๋“œ๋Š” ํŽ˜์ด์ง€์—์„œ ๊ตฌ์„ฑ ์š”์†Œ์˜ ์—ฌ๋Ÿฌ ID๋ฅผ ๊ฐ–๋Š” ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์ž‘์—…์—์„œ ID๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์“ฐ๋ ˆ๋“œ๋Š” _๊ทธ๊ฒƒ์„ ํ•˜๊ธฐ ์œ„ํ•œ ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ_์— ๊ด€ํ•œ ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๋ถˆํ–‰ํžˆ๋„ ๋ฏธ๋“ค์›จ์–ด์˜ ๊ฐœ๋…๊ณผ ์ถฉ๋Œํ•ฉ๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ๋ถ„, ์•ˆ๋…•ํ•˜์„ธ์š”,
์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์„ ์ˆ˜๋„ ์žˆ์ง€๋งŒ react deku ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค ^^

๋ˆ„๊ตฐ๊ฐ€ ์ด ์‹คํ—˜, ํŠนํžˆ taskMiddleware ์•„์ด๋””์–ด์— ๋Œ€ํ•œ ํ†ต์ฐฐ๋ ฅ์„ ์ œ๊ณตํ•˜๋ฉด ๊ธฐ์  ๊ฒƒ์ž…๋‹ˆ๋‹ค. @gaearon ์‹œ๊ฐ„๋‚˜์‹ค์‹œ๊ฐ„์žˆ๋‚˜์š”? :ํ˜€:

์นด์šดํ„ฐ ๋ชฉ๋ก

๊ฐ์‚ฌ ํ•ด์š”!

์ด๋Ÿฌํ•œ ์†”๋ฃจ์…˜ ์ค‘ ์–ด๋Š ๊ฒƒ๋„ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์„ ๋ช…ํ™•ํžˆ ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.
์ด๊ฒƒ์€ ์–ด๋–ป๊ฒŒ๋“  @gaearon ์„ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์œ„์˜ ๋ชฉ๋ก ๊ฐ์†Œ๊ธฐ๊ฐ€ ์นด์šดํ„ฐ์˜ ์ดˆ๊ธฐ ๋ชฉ๋ก์„ ๊ฐ–๋„๋ก ํ—ˆ์šฉํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

๊ทธ๊ฒŒ ์™œ ๋ฌธ์ œ๊ฐ€ ๋ ๊นŒ์š”? undefined ์ƒํƒœ์˜ ์ž์‹ ๊ฐ์†๊ธฐ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ํ•ด๋‹น ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ƒ์ ์ด ์ฒซ ๋ฒˆ์งธ ๋””์ŠคํŒจ์น˜๋กœ ์ดˆ๊ธฐํ™”๋  ๋•Œ (๋‚ด ๋‚ด๋ถ€ ๋…ผ๋ฆฌ์— ๋Œ€ํ•ด) ํ•ด๋‹น ๋ชฉ๋ก์— ๋Œ€ํ•œ ์ดˆ๊ธฐ ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•˜๊ณ  ์‚ฌ์ „ ์ •์˜๋œ ์นด์šดํ„ฐ ๋ชฉ๋ก(๋ชฉ๋ก ํ•ญ๋ชฉ ์œ ํ˜•)์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ฐ™์€?

export default function list(reducer) {
  return function (state = [

    // e.g. 2 counters with default values
    reducer(undefined, {}),
    reducer(undefined, {}),

      ], action) {
    const {
      index,
      action: innerAction
    } = action;
   // ...
  }
}

์›ํ•œ๋‹ค๋ฉด ์ด๊ฒƒ์„ list ์— ๋Œ€ํ•œ ์ธ์ˆ˜๋กœ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ํŽ˜์ด์ง€์— ๋™์ผํ•œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ์ •๋ง ๋ณต์žกํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ์—ฌ์ „ํžˆ Redux์— ๋Œ€ํ•ด ๋จธ๋ฆฌ๋ฅผ ์‹ธ๋งค๊ณ  ์žˆ์ง€๋งŒ ๊ถŒ์žฅ๋˜๋Š” Redux ์‚ฌ์šฉ ํŒจํ„ด์ด ์•„๋‹ˆ๋”๋ผ๋„ ์—ฌ๋Ÿฌ ์ €์žฅ์†Œ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

@deevus : ์ด๋Ÿฌํ•œ ํ† ๋ก  ์ค‘ ์ผ๋ถ€๊ฐ€ ๋‹น์‹ ์„ ๋†€๋ผ๊ฒŒ ํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค. Redux ์ปค๋ฎค๋‹ˆํ‹ฐ์—๋Š” ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€ํ–ฅํ•˜๋Š” ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์Šค๋ ˆ๋“œ์™€ ๋‹ค๋ฅธ ์œ ์‚ฌํ•œ ๊ฐœ๋…์—์„œ ๋…ผ์˜๋œ ๊ฐœ๋… ์ค‘ ์ผ๋ถ€๋Š” ๊ฐ€์น˜๊ฐ€ ์žˆ์ง€๋งŒ "์™„๋ฒฝํ•œ" ๊ฒƒ์„ ์ถ”๊ตฌํ•˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. , ๋‹จ์ˆœํžˆ "์ข‹์€"์ด ์•„๋‹ˆ๋ผ.

์ผ๋ฐ˜์ ์œผ๋กœ ํ•œ ํŽ˜์ด์ง€์— ์—ฌ๋Ÿฌ ๊ตฌ์„ฑ ์š”์†Œ ์ธ์Šคํ„ด์Šค๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ† ๋ก ์˜ ๋ชฉํ‘œ๋Š” ์ค‘์ฒฉ๋œ ๊ตฌ์„ฑ ์š”์†Œ์˜ ์ž„์˜ ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํฅ๋ฏธ๋กญ์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์˜ ์•ฑ์—์„œ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค.

๊ทธ ์™ธ์— ํŠน์ • ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ผ๋ฐ˜์ ์œผ๋กœ ์Šคํƒ ์˜ค๋ฒ„ํ”Œ๋กœ๊ฐ€ ์งˆ๋ฌธํ•˜๊ธฐ์— ์ข‹์€ ๊ณณ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ Discord์˜ Reactiflux ์ปค๋ฎค๋‹ˆํ‹ฐ์—๋Š” React ๋ฐ ๊ด€๋ จ ๊ธฐ์ˆ ์— ๋Œ€ํ•œ ํ† ๋ก  ์ „์šฉ ์ฑ„ํŒ… ์ฑ„๋„์ด ์žˆ์œผ๋ฉฐ, ํ•ญ์ƒ ๋Œ€ํ™”ํ•˜๊ณ  ๋„์›€์„ ์ฃผ๊ณ ์ž ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค.

@markerikson ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. Discord์—์„œ Reactiflux์˜ ๋„์›€์„ ๋ฐ›์œผ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ด์— ๋Œ€ํ•œ ํ›„์† ์กฐ์น˜:
ํ”„๋กœ์ ํŠธ์—์„œ ๋ฆฌ๋“€์„œ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋Š” ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. ๋ฆฌ๋“€์„œ ๋‚ด์—์„œ ๋ฆฌ๋“€์„œ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋„ค์ž„์ŠคํŽ˜์ด์Šค ํŒจ๋Ÿฌ๋‹ค์ž„

๋งŽ์€ "๋ถ€๋ถ„" ๋ฆฌ๋“€์„œ ์ค‘ ํ•˜๋‚˜์— ์ž‘์šฉํ•˜๋Š” ์•ก์…˜์ด โ€‹โ€‹๋™์ผํ•œ ์•ก์…˜ type (์˜ˆ https://github.com/reactjs/redux/issues/822#issuecomment-172958967)

๊ทธ๋Ÿฌ๋‚˜ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๋ณด์œ ํ•˜์ง€ ์•Š๋Š” ์ž‘์—…์€ ์—ฌ์ „ํžˆ โ€‹โ€‹๋‹ค๋ฅธ ๋ฆฌ๋“€์„œ ๋‚ด์˜ ๋ชจ๋“  ๋ถ€๋ถ„ ๋ฆฌ๋“€์„œ๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค.

๋ถ€๋ถ„ ๊ฐ์†๊ธฐ๊ฐ€ A A(undefined, {}) === Sa ์ด๊ณ  ์ดˆ๊ธฐ ์ƒํƒœ๊ฐ€ B(undefined, {}) === { a1: Sa, a2: Sa } ์ด๊ณ  ํ‚ค๊ฐ€ a1 ์ธ ๊ฐ์†๊ธฐ B ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. a1 ๋ฐ a2 ๋Š” A ์˜ ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค.

['a1'] ๋„ค์ž„์ŠคํŽ˜์ด์Šค๊ฐ€ ์žˆ๋Š” ์ž‘์—…(* ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋Š” ํ•ญ์ƒ ๋ถ€๋ถ„ ๋ฆฌ๋“€์„œ์— ๋Œ€ํ•œ ์ƒํƒœ ํ‚ค์™€ ์œ ์‚ฌํ•œ ๋ฌธ์ž์—ด ๋ฐฐ์—ด๋กœ ์ •๋ ฌ๋จ) B ์— ์บ์ŠคํŒ…๋˜๋ฉด ๋‹ค์Œ ๊ฒฐ๊ณผ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

const action = {
  type: UNIQUE_ID,
  namespace: ['a1']
};

B(undefined, action) == { a1: A(undefined, action*), a2: Sa }

๊ทธ๋ฆฌ๊ณ  ๋„ค์ž„์ŠคํŽ˜์ด์Šค๊ฐ€ ์—†๋Š” ์•ก์…˜์˜ ๋ฐ˜๋Œ€ ์˜ˆ

const action = {
  type: UNIQUE_ID
};


B(undefined, action) == { a1: A(undefined, action), a2: A(undefined, action) }

์ฃผ์˜ ์‚ฌํ•ญ

  • A ๊ฐ€ ์ฃผ์–ด์ง„ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด(๋ฆฌ๋“€์„œ๋ฅผ ์†Œ์ง„) ๋™์ผํ•œ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” p ์ž‘์—…์— ๋Œ€ํ•ด A ์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. B(undefined, p) { a1: Sa, a2: Sa } ํ•˜๋ฉฐ ์ด๋Š” ์šฐ์—ฐํžˆ B ์˜ ์ดˆ๊ธฐ ์ƒํƒœ์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.
  • ๋ถ€๋ถ„ ๊ฐ์†๊ธฐ๋กœ ์ „๋‹ฌ๋˜๋Š” ์ž‘์—…(์œ„์—์„œ action* ์œผ๋กœ ํ‘œ์‹œ)์€ ๋ฒ”์œ„๋ฅผ ์ขํžˆ๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ œ๊ฑฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ B ์— ์ „๋‹ฌ๋œ ์ž‘์—…์ด { type: UNIQUE_ID, namespace: ['a1'] } ์ด๋ฉด A ์— ์ „๋‹ฌ๋œ ์ž‘์—…์€ { type: UNIQUE_ID, namespace: [] } ์ž…๋‹ˆ๋‹ค.
  • ์ด ๊ตฌ์กฐ๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์ €์žฅ์†Œ ์˜ ๋ชจ๋“  ๋ฆฌ๋“€์„œ ๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ง€์ ์ด ์ถฉ์กฑ๋˜๋ฉด ์‚ฌ์šฉํ•˜๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ˆ˜์™€ ์ค‘์ฒฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„ ๋ฆฌ๋“€์„œ ์ˆ˜์— ์ œํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์„ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๋‚˜๋Š” ๋‹น์‹ ์˜ ๋ฆฌ๋“€์„œ์—์„œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ์˜์‚ฌ์ฝ”๋“œ๋ฅผ ์ƒ๊ฐํ•ด ๋ƒˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์ž‘๋™ํ•˜๋ ค๋ฉด ๋ฆฌ๋“€์„œ๊ฐ€ ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€์™€ ๋ฆฌ๋“€์„œ์— ์กด์žฌํ•˜๋Š” ๋ถ€๋ถ„ ๋ฆฌ๋“€์„œ์˜ ์–‘์„ ๋ฏธ๋ฆฌ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

(state = initialState, { ...action, namespace = [] }) => {
    var partialAction = { ...action, namespace: namespace.slice(1) };
    var newState;
    if (reducerCanHandleAction(reducer, action) and namespaceExistsInState(namespace, state)) {
        // apply the action to the matching partial reducer
        newState = {
            ...state,
            [namespace]: partialReducers[namespace](state[namespace], partialAction)
        };
    } else if (reducerCantHandleAction(reducer, action) {
        // apply the action to all partial reducers
        newState = Object.assign(
            {},
            state,
            ...Object.keys(partialReducers).map(
                namespace => partialReducers[namespace](state[namespace], action)
            )
        );
    } else {
        // can't handle the action
        return state;
    }

    return reducer(newState, action);
}

๋ฆฌ๋“€์„œ๊ฐ€ ์ž‘์—…์„ ๋ฏธ๋ฆฌ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋‹ฌ๋ ค ์žˆ์Šต๋‹ˆ๋‹ค. ์ €๋Š” ์ž‘์—… ์œ ํ˜•์ด ํ‚ค์ด๊ณ  ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๊ฐ€ ๊ฐ’์ธ ๊ฐ์ฒด ๋งต์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ฒŒ์ž„์— ์กฐ๊ธˆ ๋Šฆ์—ˆ์ง€๋งŒ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ๋Š” ๋ฒ”์šฉ ๊ฐ์†๊ธฐ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.
https://gist.github.com/crisu83/42ecffccad9d04c74605fbc75c9dc9d1

๋‚˜๋Š” mutilreducer ๊ฐ€ ํ›Œ๋ฅญํ•œ ๊ตฌํ˜„์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

@jeffhtli multireducer๋Š” ์ •์˜๋˜์ง€ ์•Š์€ ์–‘์˜ ๊ฐ์†๊ธฐ๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ข‹์€ ์†”๋ฃจ์…˜์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋Œ€์‹  ์ •์  ๊ฐ์†๊ธฐ ๋ชฉ๋ก์„ ์ž‘์„ฑํ•˜๋„๋ก ๋ฏธ๋ฆฌ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
๋Œ€์‹  ๊ฐ ๊ตฌ์„ฑ ์š”์†Œ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•œ UUID์™€ ๊ฐ UUID์— ๋Œ€ํ•œ ๊ณ ์œ  ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
https://github.com/eloytoro/react-redux-uuid

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰