Redux: ์ค‘์ฒฉ ๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ๋•Œ ์ƒ์šฉ๊ตฌ๋ฅผ ์ž๋ฅด๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

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

๊ทธ๋ž˜์„œ ๋‚ด ์ƒํƒœ ์•„๋ž˜์— ์ค‘์ฒฉ ๋œ ๊ตฌ์กฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

state = {
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe1'},{title: 'exe2'}]}
   ]
}

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

์˜ˆ๋ฅผ ๋“ค์–ด ์ƒˆ๋กœ์šด ๋นˆ ์šด๋™์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๊ธฐ์กด ์šด๋™์„ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด๋ฉ๋‹ˆ๋‹ค.

state.plans[planIdx].exercises.push({})

state.plans[planIdx].exercises[exerciseIdx] = exercise

ํ•˜์ง€๋งŒ์ด ์ค‘์ฒฉ ๊ตฌ์กฐ์—์„œ ๋™์ผํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ผ๊นŒ์š”? Redux ๋ฌธ์„œ์™€ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ถ€๋ถ„์„ ์ฝ์—ˆ์ง€๋งŒ ๊ฐ€์žฅ ๋จผ ๊ณณ์—์„œ ๊ณ„ํš์„ ์—…๋ฐ์ดํŠธํ–ˆ์Šต๋‹ˆ๋‹ค.

case 'UPDATE_PLAN':
    return {
      ...state,
      plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
      ]
    };

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

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

docs question

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

์˜ˆ, ๋ฐ์ดํ„ฐ๋ฅผ ์ •๊ทœํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒํ•˜๋ฉด "๊นŠ์ด"๊ฐˆ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋™์ผํ•œ ์ˆ˜์ค€์— ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‹น์‹ ์˜ ์ƒํƒœ๋Š”

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

๊ฐ์†๊ธฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

๊ทธ๋ž˜์„œ ์—ฌ๊ธฐ์„œ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? ๋จผ์ € ์ƒํƒœ๊ฐ€ ์ •๊ทœํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ ์•ˆ์— ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ID๋กœ ์„œ๋กœ๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ผ๋ถ€ ๊ฐœ์ฒด๊ฐ€ ๋ณ€๊ฒฝ ๋  ๋•Œ๋งˆ๋‹ค ์—…๋ฐ์ดํŠธํ•ด์•ผํ•˜๋Š” ๋‹จ์ผ ์œ„์น˜ ๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‘˜์งธ, plans ๊ฐ์†๊ธฐ์— ์ ์ ˆํ•œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  currentPlans ๊ฐ์†๊ธฐ์— ID๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ CREATE_PLAN ์— ์–ด๋–ป๊ฒŒ ๋ฐ˜์‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฑด ์ค‘์š”ํ•˜๋‹ค. ๋” ๋ณต์žกํ•œ ์•ฑ์—์„œ๋Š” ๊ด€๊ณ„๊ฐ€์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด plans ๊ฐ์†๊ธฐ๋Š” ๊ณ„ํš ๋‚ด์˜ ๋ฐฐ์—ด์— ์ƒˆ ID๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ADD_EXERCISE_TO_PLAN ๋ฅผ ์ฒ˜๋ฆฌ ํ•  ์ˆ˜ โ€‹โ€‹์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์šด๋™ ์ž์ฒด๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด _ID๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ plans ๊ฐ์†๊ธฐ๊ฐ€์ด๋ฅผ ์•Œ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค _.

์…‹์งธ, ์—”ํ‹ฐํ‹ฐ ๊ฐ์†๊ธฐ ( plans ๋ฐ exercises )์—๋Š” action.entities ๊ฐ์‹œํ•˜๋Š” ํŠน์ˆ˜ ์ ˆ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ ๋ฐ˜์˜ํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ ์žํ•˜๋Š” "์•Œ๋ ค์ง„ ์ง„์‹ค"์„ ๊ฐ€์ง„ ์„œ๋ฒ„ ์‘๋‹ต์ด์žˆ๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ž‘์—…์„ ์ „๋‹ฌํ•˜๊ธฐ ์ „์— ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋น„ํ•˜๋ ค๋ฉด normalizr ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Redux repo์˜ "real world"์˜ˆ์ œ์—์„œ ์‚ฌ์šฉ ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์•„, ๊ทธ๋ฆฌ๊ณ  { ...a, ...b } ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. Babel 2 ๋‹จ๊ณ„์—์„œ ES7 ์ œ์•ˆ์œผ๋กœ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. "๊ฐ์ฒด ํ™•์‚ฐ ์—ฐ์‚ฐ์ž"๋ผ๊ณ ํ•˜๋ฉฐ Object.assign({}, a, b) ๋ฅผ ์“ฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ฒฝ์šฐ Lodash๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ˆ๋ฅผ ๋“ค์–ด merge({}, a, b} ๋Š” ์ •ํ™•ํ•˜์ง€๋งŒ merge(a, b) ์€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Œ), updeep , react-addons-update ๋˜๋Š” ๋‹ค๋ฅธ ๊ฒƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‹ฌ์ธต ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ƒํƒœ ํŠธ๋ฆฌ๊ฐ€ ์ถฉ๋ถ„ํžˆ ํ‰ํ‰ํ•˜์ง€ ์•Š๊ณ  ๊ธฐ๋Šฅ ๊ตฌ์„ฑ์„ ์ถฉ๋ถ„ํžˆ ํ™œ์šฉํ•˜์ง€ ์•Š์•˜ ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์˜ˆ๋„ :

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

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

https://github.com/gaearon/normalizr ๊ณผ ๊ฐ™์ด ์ค‘์ฒฉ ๋œ JSON์„ ์ •๊ทœํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์˜ˆ, ๋ฐ์ดํ„ฐ๋ฅผ ์ •๊ทœํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒํ•˜๋ฉด "๊นŠ์ด"๊ฐˆ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋™์ผํ•œ ์ˆ˜์ค€์— ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‹น์‹ ์˜ ์ƒํƒœ๋Š”

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

๊ฐ์†๊ธฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

๊ทธ๋ž˜์„œ ์—ฌ๊ธฐ์„œ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ? ๋จผ์ € ์ƒํƒœ๊ฐ€ ์ •๊ทœํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ ์•ˆ์— ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ID๋กœ ์„œ๋กœ๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ผ๋ถ€ ๊ฐœ์ฒด๊ฐ€ ๋ณ€๊ฒฝ ๋  ๋•Œ๋งˆ๋‹ค ์—…๋ฐ์ดํŠธํ•ด์•ผํ•˜๋Š” ๋‹จ์ผ ์œ„์น˜ ๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‘˜์งธ, plans ๊ฐ์†๊ธฐ์— ์ ์ ˆํ•œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  currentPlans ๊ฐ์†๊ธฐ์— ID๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ CREATE_PLAN ์— ์–ด๋–ป๊ฒŒ ๋ฐ˜์‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฑด ์ค‘์š”ํ•˜๋‹ค. ๋” ๋ณต์žกํ•œ ์•ฑ์—์„œ๋Š” ๊ด€๊ณ„๊ฐ€์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด plans ๊ฐ์†๊ธฐ๋Š” ๊ณ„ํš ๋‚ด์˜ ๋ฐฐ์—ด์— ์ƒˆ ID๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ADD_EXERCISE_TO_PLAN ๋ฅผ ์ฒ˜๋ฆฌ ํ•  ์ˆ˜ โ€‹โ€‹์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์šด๋™ ์ž์ฒด๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด _ID๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ plans ๊ฐ์†๊ธฐ๊ฐ€์ด๋ฅผ ์•Œ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค _.

์…‹์งธ, ์—”ํ‹ฐํ‹ฐ ๊ฐ์†๊ธฐ ( plans ๋ฐ exercises )์—๋Š” action.entities ๊ฐ์‹œํ•˜๋Š” ํŠน์ˆ˜ ์ ˆ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ ๋ฐ˜์˜ํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ ์žํ•˜๋Š” "์•Œ๋ ค์ง„ ์ง„์‹ค"์„ ๊ฐ€์ง„ ์„œ๋ฒ„ ์‘๋‹ต์ด์žˆ๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ž‘์—…์„ ์ „๋‹ฌํ•˜๊ธฐ ์ „์— ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋น„ํ•˜๋ ค๋ฉด normalizr ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Redux repo์˜ "real world"์˜ˆ์ œ์—์„œ ์‚ฌ์šฉ ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์•„, ๊ทธ๋ฆฌ๊ณ  { ...a, ...b } ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. Babel 2 ๋‹จ๊ณ„์—์„œ ES7 ์ œ์•ˆ์œผ๋กœ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. "๊ฐ์ฒด ํ™•์‚ฐ ์—ฐ์‚ฐ์ž"๋ผ๊ณ ํ•˜๋ฉฐ Object.assign({}, a, b) ๋ฅผ ์“ฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ฒฝ์šฐ Lodash๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์˜ˆ๋ฅผ ๋“ค์–ด merge({}, a, b} ๋Š” ์ •ํ™•ํ•˜์ง€๋งŒ merge(a, b) ์€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Œ), updeep , react-addons-update ๋˜๋Š” ๋‹ค๋ฅธ ๊ฒƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‹ฌ์ธต ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ƒํƒœ ํŠธ๋ฆฌ๊ฐ€ ์ถฉ๋ถ„ํžˆ ํ‰ํ‰ํ•˜์ง€ ์•Š๊ณ  ๊ธฐ๋Šฅ ๊ตฌ์„ฑ์„ ์ถฉ๋ถ„ํžˆ ํ™œ์šฉํ•˜์ง€ ์•Š์•˜ ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์˜ˆ๋„ :

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

์ด๊ฒƒ์„ ์กฐ๋ฆฌ๋ฒ•์œผ๋กœ ๋ฐ”๊พธ๋ฉด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด์ƒ์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” ๊ฐ„ํŒ ๋ณด๋“œ ์˜ˆ์ œ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค.
"๋ ˆ์ธ"๋‚ด๋ถ€์— "์นด๋“œ"๊ฐ€์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ค‘์ฒฉ ๋œ ์—”ํ‹ฐํ‹ฐ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

@ andre0799 ๋˜๋Š” Immutable.js๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค))

์ด์ƒ์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” ๊ฐ„ํŒ ๋ณด๋“œ ์˜ˆ์ œ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ํ•˜๋‚˜๋ฅผ ์ผ๋‹ค. ์•„๋งˆ๋„ ๋‹น์‹ ์€ ๊ทธ๊ฒƒ์„ ํฌํฌํ•˜๊ณ  ์›ํ•˜๋Š”๋Œ€๋กœ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Immutable.js๊ฐ€ ํ•ญ์ƒ ์ข‹์€ ํ•ด๊ฒฐ์ฑ…์€ ์•„๋‹™๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ ํ•œ ๋…ธ๋“œ์—์„œ ์‹œ์ž‘ํ•˜์—ฌ ์ƒํƒœ์˜ ๋ชจ๋“  ์ƒ์œ„ ๋…ธ๋“œ์˜ ํ•ด์‹œ๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๋ฉฐ ์ด๋Š” ํŠน์ • ๊ฒฝ์šฐ์— ๋ณ‘๋ชฉ ํ˜„์ƒ์ด๋ฉ๋‹ˆ๋‹ค (์ด๊ฒƒ์€ ๋งค์šฐ ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค). ๋”ฐ๋ผ์„œ ์ด์ƒ์ ์œผ๋กœ๋Š” Immutable.js๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํ†ตํ•ฉํ•˜๊ธฐ ์ „์— ๋ช‡ ๊ฐ€์ง€ ๋ฒค์น˜ ๋งˆํฌ๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.

๊ท€ํ•˜์˜ ๋‹ต๋ณ€, ํ›Œ๋ฅญํ•œ ์„ค๋ช…์— ๋Œ€ํ•ด @gaearon ์—๊ฒŒ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!

๋”ฐ๋ผ์„œ CREATE_PLAN ์„ ์ˆ˜ํ–‰ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๊ธฐ๋ณธ ์šด๋™์„ ์ƒ์„ฑํ•˜๊ณ  ์ถ”๊ฐ€ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์€ ๊ฒฝ์šฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? ๊ทธ๋Ÿฐ ๋‹ค์Œ 3 ๊ฐœ์˜ ์ž‘์—…์„ ์—ฐ์†์œผ๋กœ ํ˜ธ์ถœํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? CREATE_PLAN, CREATE_EXERCISE, ADD_EXERCISE_TO_PLAN ๊ฒฝ์šฐ ์–ด๋””์—์„œ ์ „ํ™”๋ฅผํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ?

๋”ฐ๋ผ์„œ CREATE_PLAN์„ ์ˆ˜ํ–‰ ํ•  ๋•Œ ๊ธฐ๋ณธ ์šด๋™์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ  ์ถ”๊ฐ€ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์€ ๊ฒฝ์šฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ?

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

Redux Thunk ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‘˜ ๋‹ค ํ˜ธ์ถœํ•˜๋Š” ์•ก์…˜ ์ƒ์„ฑ์ž๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function createPlan(title) {
  return dispatch => {
    const planId = uuid();
    const exerciseId = uuid();

    dispatch({
      type: 'CREATE_EXERCISE',
      id: exerciseId,
      exercise: {
        id: exerciseId,
        title: 'Default'
      }
    });

    dispatch({
      type: 'CREATE_PLAN',
      id: planId,
      plan: {
        id: planId,
        exercises: [exerciseId],
        title
      }
    });
  };
}

๊ทธ๋Ÿฐ ๋‹ค์Œ Redux Thunk ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

store.dispatch(createPlan(title));

๋ฐฑ์—”๋“œ ์–ด๋”˜๊ฐ€์— ๊ทธ๋Ÿฌํ•œ ๊ด€๊ณ„ (๊ฒŒ์‹œ๋ฌผ, ์ž‘์„ฑ์ž, ํƒœ๊ทธ, ์ฒจ๋ถ€ ํŒŒ์ผ ๋“ฑ)๊ฐ€์žˆ๋Š” ๊ฒŒ์‹œ๋ฌผ ํŽธ์ง‘๊ธฐ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ • ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

currentPlans ํ‚ค ๋ฐฐ์—ด๊ณผ ์œ ์‚ฌํ•œ currentPosts ํ‘œ์‹œํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? currentPosts ๊ฐ ํ‚ค๋ฅผ mapStateToProps ํ•จ์ˆ˜์˜ entities.posts ์—์žˆ๋Š” ํ•ด๋‹น ๊ฐ์ฒด์— ๋งคํ•‘ํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? currentPosts ์ •๋ ฌ

์ด ๋ชจ๋“  ๊ฒƒ์ด ๊ฐ์†๊ธฐ ๊ตฌ์„ฑ์— ์†ํ•ฉ๋‹ˆ๊นŒ?

์—ฌ๊ธฐ ๋ญ”๊ฐ€ ๋น ์กŒ์–ด์š” ...

์›๋ž˜ ์งˆ๋ฌธ๊ณผ ๊ด€๋ จํ•˜์—ฌ React Immutability Helpers ๊ฐ€ ๊ทธ ๋ชฉ์ ์„ ์œ„ํ•ด ๋งŒ๋“ค์–ด ์กŒ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค

currentPlans ํ‚ค ๋ฐฐ์—ด๊ณผ ์œ ์‚ฌํ•œ currentPosts๋ฅผ ํ‘œ์‹œํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? currentPosts์˜ ๊ฐ ํ‚ค๋ฅผ mapStateToProps ํ•จ์ˆ˜์˜ entity.posts์—์žˆ๋Š” ํ•ด๋‹น ๊ฐ์ฒด์— ๋งคํ•‘ํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? currentPosts๋ฅผ ์ •๋ ฌํ•˜๋Š” ๊ฒƒ์€ ์–ด๋–ป์Šต๋‹ˆ๊นŒ?

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

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ์—์„œ ํŒŒ์ƒ ๋ฐ์ดํ„ฐ ๊ณ„์‚ฐ ์„ ์ฝ์€ ํ›„ ์ด๋ฏธ ์•„์ด๋””์–ด๋ฅผ ์–ป๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ ์ฝ์—ˆ์„ ๋•Œ ๋ฌด์Šจ ์ผ์ด ๋ฒŒ์–ด ์กŒ๋Š”์ง€ ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ฟก ๋นต๋€จ

๋ชจ๋“  connect() ed ๊ตฌ์„ฑ ์š”์†Œ์—๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ dispatch ๊ฐ€ prop์œผ๋กœ ์‚ฝ์ž…๋ฉ๋‹ˆ๋‹ค.

this.props.dispatch(createPlan(title));

์ด ์Šค๋ ˆ๋“œ์™€ ๊ด€๋ จ์ด์—†๋Š” ์‚ฌ์šฉ ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ์˜ˆ์ œ๋ฅผ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜ StackOverflow ์งˆ๋ฌธ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์ €๋Š” Dan์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ •๊ทœํ™”ํ•˜๊ณ  ๊ฐ€๋Šฅํ•œ ํ•œ ์ƒํƒœ ๊ตฌ์กฐ๋ฅผ ํ‰ํƒ„ํ™”ํ•˜๋Š” ๊ฒƒ์— ๋™์˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋‚˜์—๊ฒŒ ์•ฝ๊ฐ„์˜ ๋‘ํ†ต์„ ๋œ์–ด ์ฃผ์—ˆ์„ ๊ฒƒ์ด๋ฏ€๋กœ ๋ฌธ์„œ์— ๋ ˆ์‹œํ”ผ / ๋ชจ๋ฒ” ์‚ฌ๋ก€๋กœ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‚ด ์ƒํƒœ์—์„œ ์•ฝ๊ฐ„์˜ ๊นŠ์ด๋ฅผ ๊ฐ–๋Š” ์‹ค์ˆ˜๋ฅผํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Redux๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊นŠ์€ ์ƒํƒœ๋ฅผ ๊ฐœ์กฐํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ๋„์›€์ด๋˜๋„๋ก์ด lib๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. https://github.com/baptistemanson/immutable-path
์•„๋งˆ ๋ชจ๋“  ๊ฒƒ์ด ์ž˜๋ชป๋˜์—ˆ์ง€๋งŒ ๊ท€ํ•˜์˜ ์˜๊ฒฌ์— ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ˆ„๊ตฐ๊ฐ€๋ฅผ ๋„์šธ ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ๋„์›€์ด๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋‘์—๊ฒŒ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ œ ์˜ˆ์ œ์™€ ๋™์ผํ•œ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ„ํš์— ์šด๋™์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ์šด๋™์„ ์ถ”๊ฐ€ํ•˜๋ฉด planId ํ•„๋“œ์™€ ํ•จ๊ป˜ ์ƒˆ๋กœ ์ƒ์„ฑ ๋œ ์šด๋™ ํ•ญ๋ชฉ์ด ๋ฐ˜ํ™˜๋˜์—ˆ๋‹ค๊ณ  ๊ฐ€์ • ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ณ„ํš์— ๋Œ€ํ•œ ๊ฐ์†๊ธฐ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  ํ•ด๋‹น ๊ณ„ํš์— ์ƒˆ ์šด๋™์„ ์ถ”๊ฐ€ํ•˜๊ณ  ํŠน๋ณ„ํžˆ CREATE_EXERCISE ์ž‘์—…์„๋“ค์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์—ฌ๊ธฐ์— ํ›Œ๋ฅญํ•œ ํ† ๋ก ๊ณผ ์ •๋ณด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด ํ”„๋กœ์ ํŠธ์— normalizr์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€๋งŒ ์—…๋ฐ์ดํŠธ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์›๊ฒฉ ์„œ๋ฒ„์— ๋‹ค์‹œ ์ €์žฅํ•˜๋Š” ๊ฒƒ๊ณผ ๊ด€๋ จํ•˜์—ฌ ์งˆ๋ฌธ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ๋กœ, ์—…๋ฐ์ดํŠธ ํ›„ ์ •๊ทœํ™” ๋œ ๋ชจ์–‘์„ ์›๊ฒฉ API์—์„œ ์ œ๊ณตํ•˜๋Š” ์ค‘์ฒฉ ๋ชจ์–‘์œผ๋กœ ๋˜ ๋Œ๋ฆฌ๋Š” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ? ์ด๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณ€๊ฒฝ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์—…๋ฐ์ดํŠธ ์š”์ฒญ์˜ ํ˜•ํƒœ๋ฅผ ์ œ์–ด ํ•  ์ˆ˜์—†๋Š” ์›๊ฒฉ API๋กœ ํ”ผ๋“œ๋ฐฑํ•ด์•ผ ํ•  ๋•Œ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ : ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ค‘์ฒฉ ๋œ ์šด๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ด-> ํด๋ผ์ด์–ธํŠธ๊ฐ€์ด๋ฅผ ์ •๊ทœํ™”ํ•˜์—ฌ redux์— ์ €์žฅ-> ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ •๊ทœํ™” ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝ-> ์‚ฌ์šฉ์ž๊ฐ€ ์ €์žฅ์„ ํด๋ฆญ-> ํด๋ผ์ด์–ธํŠธ ์•ฑ์ด ์—…๋ฐ์ดํŠธ ๋œ ์ •๊ทœํ™” ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ค‘์ฒฉ ๋œ ํ˜•ํƒœ๋กœ ๋‹ค์‹œ ๋ณ€ํ™˜ ๊ทธ๋ž˜์„œ ๊ทธ๊ฒƒ์€ ์›๊ฒฉ ์„œ๋ฒ„์— ์ œ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค -> ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์ œ์ถœ

normalizr์„ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด ๊ตต์€ ๋‹จ๊ณ„์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ง€์ • ๋ณ€ํ™˜๊ธฐ๋ฅผ ์ž‘์„ฑํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด์ด๋ฅผ ์œ„ํ•ด ๊ถŒ์žฅ ํ•  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋˜๋Š” ๋„์šฐ๋ฏธ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ๋ชจ๋“  ๊ถŒ์žฅ ์‚ฌํ•ญ์„ ๋งŽ์ด ์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฐ์‚ฌ

https://github.com/gpbl/denormalizr ์ด๋ผ๋Š” ๊ฒƒ์ด ์žˆ์ง€๋งŒ normalizr ์—…๋ฐ์ดํŠธ๋ฅผ ์–ผ๋งˆ๋‚˜ ๋ฐ€์ ‘ํ•˜๊ฒŒ ์ถ”์ ํ•˜๋Š”์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์ž‘์—… ํ•œ ์•ฑ์— ๋Œ€ํ•ด ๋ช‡ ์‹œ๊ฐ„ ๋งŒ์— normalizr์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ํฌํฌํ•˜๊ณ  ๋น„์ •๊ทœ ํ™”๋ฅผ ์ถ”๊ฐ€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ๐Ÿ˜„.

๋ฉ‹์ง€๋‹ค. ๋น„์ •๊ทœ ํ™”๋ฅผ ์‚ดํŽด๋ณด๊ณ  ๋ฌด์–ธ๊ฐ€๊ฐ€ ์žˆ์œผ๋ฉด ํ”„๋กœ์ ํŠธ์— ๋‹ค์‹œ ๊ธฐ์—ฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ช‡ ์‹œ๊ฐ„ ๋™์•ˆ ๋Œ€๋‹จํ•œ ์ผ์„ํ–ˆ์Šต๋‹ˆ๋‹ค ;-) ๋‹ค์‹œ ์—ฐ๋ฝํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ๊นŠ์ด ์ค‘์ฒฉ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์— ์žˆ์ง€๋งŒ immutable.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘๋™ํ•˜๋Š” ์†”๋ฃจ์…˜์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์—์„œ ์†”๋ฃจ์…˜์— ๋Œ€ํ•œ ํ”ผ๋“œ๋ฐฑ์„ ์š”์ฒญํ•œ StackOverflow ๊ฒŒ์‹œ๋ฌผ์— ๋งํฌํ•ด๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๊นŒ?

์ง€๊ธˆ ๋งํฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด ๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜ ์—ฌ๊ธฐ์— ๋งํฌํ•˜๋Š” ๊ฒƒ์ด ๋ถ€์ ์ ˆํ•˜๋‹ค๊ณ  ๋ง ํ•ด์ฃผ์„ธ์š” :
http://stackoverflow.com/questions/37171203/manipulating-data-in-nested-arrays-in-redux-with-immutable-js

์ด ์ ‘๊ทผ ๋ฐฉ์‹์ด ๊ณผ๊ฑฐ์— ๊ถŒ์žฅ๋˜๋Š” ๊ฒƒ์„ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜์ด ๋ฐฉ๋ฒ•์€ ์ค‘์ฒฉ ๋œ ๊ฐœ์ฒด๋ฅผ ์ œ๊ฑฐํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๋ฆฌ๋“€์„œ๋Š” ๊ฐ์ฒด ์ž์ฒด๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ ์ „์— ๊ฐ์ฒด์— ๋Œ€ํ•œ ๋ชจ๋“  ์ฐธ์กฐ๋ฅผ ์‚ดํŽด๋ณด๊ณ ์ด๋ฅผ ์ œ๊ฑฐํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.์ด ์ž‘์—…์€ O (n)์ด๋ฉ๋‹ˆ๋‹ค. ๋ˆ„๊ตฌ๋“ ์ง€ ๋น„์Šทํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ํ•ด๊ฒฐ ํ–ˆ์Šต๋‹ˆ๊นŒ?

@ariofrio : ์•„ ...

๋‹น์‹ ์ด ๊ฐ€์ง€๊ณ ์žˆ๋Š” ํŠน์ •ํ•œ ์šฐ๋ ค ์‚ฌํ•ญ์ด ์žˆ์Šต๋‹ˆ๊นŒ, ์•„๋‹ˆ๋ฉด ๋‹น์‹ ์ด ๋‹ค๋ฃจ๊ณ ์žˆ๋Š” ์„ฑ๊ฐ€์‹  ์‹œ๋‚˜๋ฆฌ์˜ค๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์ œ๊ฐ€ ์˜๋ฏธํ•˜๋Š” ๋ฐ”๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ƒํƒœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 6]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
      5: {title: 'exe5'}
      6: {title: 'exe6'}
    }
  },
  currentPlans: [1, 2]
}

์ด ์˜ˆ์—์„œ ๊ฐ ์šด๋™์€ ํ•˜๋‚˜์˜ ๊ณ„ํš์—์„œ๋งŒ ์ฐธ์กฐ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ "์šด๋™ ์ œ๊ฑฐ"๋ฅผ ํด๋ฆญํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{type: "REMOVE_EXERCISE", payload: 2}

๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ๋งค๋‹ฌ๋ฆฐ ์ฐธ์กฐ๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋“  ๊ณ„ํš์„ ๋ฐ˜๋ณต ํ•œ ๋‹ค์Œ ๊ฐ ๊ณ„ํš ๋‚ด์˜ ๋ชจ๋“  ์—ฐ์Šต์„ ๋ฐ˜๋ณตํ•˜์—ฌ id 2๋กœ ์—ฐ์Šต์„ ์ฐธ์กฐํ•˜๋Š” ํ•˜๋‚˜๋ฅผ ์ฐพ์•„์•ผํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์ œ๊ฐ€ ๊ฑฑ์ •ํ–ˆ๋˜ O (n) ์ž‘์—…์ž…๋‹ˆ๋‹ค.

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

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

์šด๋™์„ ์ œ๊ฑฐํ•˜๋ผ๋Š” ๋ฉ”์‹œ์ง€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{type: "REMOVE_EXERCISE", payload: {plan_index: 0, exercise_index: 1}}

๋ช‡ ๊ฐ€์ง€ ์ƒ๊ฐ :

  • ํ•ด๋‹น ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋‹จ์ˆœํ™” ํ•  ๊ณ„ํš์„ ์„ธ์šฐ๊ธฐ ์œ„ํ•ด ์—ฐ์Šต์˜ ์—ญ๋ฐฉํ–ฅ ์กฐํšŒ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Redux-ORM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ํ•˜๋Š” ์ผ์€ ๋งŽ์€ ์œ ํ˜•์˜ ๊ด€๊ณ„์— ๋Œ€ํ•ด "ํ…Œ์ด๋ธ”์„ ํ†ตํ•ด"์ž๋™ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ์ด ๊ฒฝ์šฐ์—๋Š” {id, planId, exerciseId} ํŠธ๋ฆฌํ”Œ๋ ›์ด ํฌํ•จ ๋œ "PlanExercise" "ํ…Œ์ด๋ธ”"์ด ์ƒ์ ์— ์žˆ์Šต๋‹ˆ๋‹ค. ํ™•์‹คํžˆ O (n) ์Šค์บ”์ด์ง€๋งŒ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.
  • O (n) ์—ฐ์‚ฐ์€ ๋ณธ์งˆ์ ์œผ๋กœ ๋‚˜์œ ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ์ „์ ์œผ๋กœ N์ด ์–ผ๋งˆ๋‚˜ ํฐ์ง€, ์šฉ์–ด ์•ž์˜ ์ƒ์ˆ˜ ์š”์†Œ, ๋ฐœ์ƒ ๋นˆ๋„ ๋ฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์ง„ํ–‰๋˜๋Š” ๋‹ค๋ฅธ ์ž‘์—…์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. 10 ๊ฐœ ๋˜๋Š” 15 ๊ฐœ์˜ ํ•ญ๋ชฉ ๋ชฉ๋ก์„ ๋ฐ˜๋ณตํ•˜๊ณ  ์‚ฌ์šฉ์ž ๋ฒ„ํŠผ ํด๋ฆญ์— ๋Œ€ํ•œ ๋™๋“ฑ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์€ 500ms๋งˆ๋‹ค ์‹œ์Šคํ…œ์— ๋“ค์–ด์˜ค๋Š” 1,000 ๋งŒ ํ•ญ๋ชฉ ๋ชฉ๋ก์„ ๋ฐ˜๋ณตํ•˜๊ณ  ๊ฐ๊ฐ์— ๋Œ€ํ•ด ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ๊ณผ๋Š” ์™„์ „ํžˆ ๋‹ค๋ฅธ ์ผ์ž…๋‹ˆ๋‹ค. ์•ˆ๊ฑด. ์ด ๊ฒฝ์šฐ ์ˆ˜์ฒœ ๊ฐœ์˜ ๊ณ„ํš์„ ํ™•์ธํ•˜๋Š” ๊ฒƒ์กฐ์ฐจ ์˜๋ฏธ์žˆ๋Š” ๋ณ‘๋ชฉ ํ˜„์ƒ์ด๋˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด๊ฒƒ์ด ์‹ค์ œ ์„ฑ๋Šฅ ๋ฌธ์ œ์ž…๋‹ˆ๊นŒ, ์•„๋‹ˆ๋ฉด ๊ฐ€๋Šฅํ•œ ์ด๋ก ์  ๋ฌธ์ œ๋ฅผ ์˜ˆ์ƒํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๊นŒ?

๊ถ๊ทน์ ์œผ๋กœ ์ค‘์ฒฉ ๋ฐ ์ •๊ทœํ™” ๋œ ์ƒํƒœ๋Š” ๋ชจ๋‘ ๊ทœ์น™์ž…๋‹ˆ๋‹ค. Redux์™€ ํ•จ๊ป˜ ์ •๊ทœํ™” ๋œ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์—๋Š” ์ข‹์€ ์ด์œ ๊ฐ€ ์žˆ์œผ๋ฉฐ ์ƒํƒœ๋ฅผ ์ •๊ทœํ™” ๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•ด์•ผํ•˜๋Š” ์ข‹์€ ์ด์œ ๊ฐ€์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์—๊ฒŒ ๋งž๋Š” ๊ฒƒ์„ ์„ ํƒํ•˜์‹ญ์‹œ์˜ค :)

๋‚ด ์†”๋ฃจ์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

function deepCombinReducer(parentReducer, subReducer) {
    return function (state = parentReducer(void(0) /* get parent reducer initial state */, {}) {
        let finalState = {...state};

        for (var k in subReducer) {
          finalState[k] = subReducer(state[k], action);
        }

       return parentReducer(finalState, action);
    };
}

const parentReducer = function(state = {}, action) {
    return state;
}

const subReducer = function(state = [], action) {
    state = Immutable.fromJS(state).toJS();
    switch(action.type) {
       case 'ADD':
          state.push(action.sub);
           return state;
       default:
          return state;
   }
}

export default combineReducers({
   parent: deepCombinReducer(parentReducer, {
       sub: subReducer
   })
})

๊ทธ๋Ÿฐ ๋‹ค์Œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒ์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{
    parent: {
       sub: []
    }
}

dispatch({
    type: 'ADD',
    sub: '123'
});

// the store will change to:
{
    parent: {
       sub: ['123']
    }
}

@smashercosmo immutable.js ๊นŠ์€ ์ค‘์ฒฉ ์ƒํƒœ? ์–ด๋–ป๊ฒŒ ๊ถ๊ธˆํ•ด

@gaearon

์šฐ๋ฆฌ๋Š” ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ ์•ˆ์— ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ดํ•ด๊ฐ€ ์•ˆ ๋ผ์š”. ์—ฌ๊ธฐ์—๋Š” ์ตœ์†Œํ•œ ์„ธ ๊ฐ€์ง€ ์ˆ˜์ค€์˜ ์ค‘์ฒฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

entities.plans[1] - three levels
entities.exercises[1] - three levels

์ด๊ฒƒ์€ ์ค‘์ฒฉ๋˜์ง€ ์•Š์€ ๊ฐœ์ฒด์ž…๋‹ˆ๋‹ค. ํ•œ ์ˆ˜์ค€ ๋งŒ.

{
   plans: [1,2, 3],
   exercises: [1,2,3],
   'so forth': [1,2,3]
}

@wzup : ์ฐธ๊ณ ๋กœ Dan์€ ์š”์ฆ˜ Redux ๋ฌธ์ œ๋ฅผ ๋ณด๋Š” ๋ฐ ๋งŽ์€ ์‹œ๊ฐ„์„ ์†Œ๋น„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Š” React์—์„œ ์ž‘์—…ํ•˜๋Š” ๋ฐ ์ถฉ๋ถ„ํ•œ ์‹œ๊ฐ„์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ "์ค‘์ฒฉ"์˜ ์˜๋ฏธ๋Š” ์ด์ „ ์Šค๋ ˆ๋“œ์˜ ๋‹ค์Œ ์˜ˆ์ œ์™€ ๊ฐ™์ด ๋ฐ์ดํ„ฐ ์ž์ฒด๊ฐ€ ์ค‘์ฒฉ ๋œ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

์ด ์˜ˆ์ œ์—์„œ ์—ฐ์Šต "exe6"์— ์•ก์„ธ์Šคํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ plans[1].exercises[2] ์™€ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ํŒŒํ—ค์น˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@tmonte ์˜ ์งˆ๋ฌธ์— ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

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

์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋งŽ์€ ๊ฒฝ์šฐ ๊ฐ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด ํ•˜๋‚˜์˜ ๊ฐ์†๊ธฐ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์ง€๋งŒ์ด ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ ํ•ด๊ฒฐ์ฑ…์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

๋” ๋งŽ์€ ์œ ์—ฐ์„ฑ์„ ์œ„ํ•ด merge ๋Œ€์‹  mergeWith ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

import mergeWith from 'lodash/mergeWith';

// Updates an entity cache in response to any action with `entities`.
function entities(state = {}, action) {
  // Here where we STORE or UPDATE one or many entities
  // So check if the action contains the format we will manage
  // wich is `payload.entities`
  if (action.payload && action.payload.entities) {
    // if the entity is already in the store replace
    // it with the new one and do not merge. Why?
    // Assuming we have this product in the store:
    //
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    //
    // We will updated with
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //   }
    // }
    //
    // The result if we were using `lodash/merge`
    // notice the rate `rateCategory` hasn't changed:
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    // for this particular use case it's safer to use
    // `lodash/mergeWith` and skip the merge
    return mergeWith({}, state, action.payload.entities, (oldD, newD) => {
      if (oldD && oldD.id && oldD.id === newD.id) {
        return newD;
      }
      return undefined;
    });
  }

  // Here you could register other handlers to manipulate 
  // the entities
  switch (action.type) {
    case ActionTypes.SOME_ACTION:
      // do something;
    default:
      return state;
  }
}

const rootReducer = combineReducers({
  entities,
});
export default rootReducer;
์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰