Подтвердить что ты не робот

Как вы добавляете/удаляете хранилище редукции, сгенерированное с помощью normalizr?

Посмотрите примеры из README:

Учитывая "плохую" структуру:

[{
  id: 1,
  title: 'Some Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}, {
  id: 2,
  title: 'Other Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}]

Очень просто добавить новый объект. Все, что мне нужно сделать, это что-то вроде

return {
  ...state,
  myNewObject
}

В редукторе.

Теперь, учитывая структуру "хорошего" дерева, я понятия не имею, как мне подойти к нему.

{
  result: [1, 2],
  entities: {
    articles: {
      1: {
        id: 1,
        title: 'Some Article',
        author: 1
      },
      2: {
        id: 2,
        title: 'Other Article',
        author: 1
      }
    },
    users: {
      1: {
        id: 1,
        name: 'Dan'
      }
    }
  }
}

Каждый подход, о котором я думал, требует некоторого сложного манипулирования объектами, что заставляет меня чувствовать, что я не на правильном пути, потому что нормализация должна облегчать мою жизнь.

Я не могу найти какие-либо примеры онлайн для кого-то, кто работает с деревом normalizr таким образом. Официальный пример не добавляет и не удаляет, так что это тоже не помогло.

Может ли кто-нибудь дать мне знать, как правильно добавить/удалить из нормального дерева?

4b9b3361

Ответ 1

Ниже приведено сообщение от создателя redux/normalizr здесь:

Итак, ваше состояние будет выглядеть следующим образом:

{
  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
});

Итак, что здесь происходит? Во-первых, обратите внимание, что состояние нормировано. У нас никогда не было объектов внутри других объектов. Вместо этого они ссылаются друг на друга по идентификаторам. Поэтому, когда какой-либо объект изменяется, есть только одно место, где его нужно обновлять.

Во-вторых, обратите внимание на то, как мы реагируем на CREATE_PLAN, добавляя соответствующий объект в редуктор планов и добавляя его идентификатор к редуктору currentPlans. Это важно. В более сложных приложениях у вас могут быть отношения, например. Плановый редуктор может обрабатывать ADD_EXERCISE_TO_PLAN таким же образом, добавив новый идентификатор в массив внутри плана. Но если само обновление обновлено, нет необходимости в сокращении планов, чтобы знать, что, поскольку идентификатор не изменился.

В-третьих, обратите внимание, что редукторы объектов (планы и упражнения) имеют специальные предложения, отслеживающие действия. Это в случае, если у нас есть ответ сервера с "известной правдой", который мы хотим обновить всеми нашими сущностями, чтобы отразить. Чтобы подготовить данные таким образом перед отправкой действия, вы можете использовать normalizr. Вы можете видеть, что он используется в примере "реального мира" в репозитории Redux.

Наконец, обратите внимание на то, как редукторы объектов схожи. Возможно, вы захотите написать функцию для их создания. Это из-за моего ответа - иногда вам нужна большая гибкость, а иногда вы хотите меньше шаблонов. Вы можете проверить код разбивки на страницы в примерах редукторов реального мира для примера создания подобных редукторов.

О, и я использовал синтаксис {... a,... b}. Он был включен в Babel stage 2 в качестве предложения ES7. Он называется "оператор распространения объектов" и эквивалентен записи Object.assign({}, a, b).

Что касается библиотек, вы можете использовать Lodash (будьте осторожны, чтобы не мутировать, например, merge ({}, a, b} корректно, но слияние (a, b) отсутствует), updeep, update-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)
  };

Ответ 2

В большинстве случаев я использую normalizr для данных, которые я получаю от API, потому что у меня нет никакого контроля над (обычно) глубокими вложенными структурами данных. Пусть дифференцируются сущности и результат и их использование.

Объекты

Все чистые данные находятся в объекте объектов после его нормализации (в вашем случае articles и users). Я бы рекомендовал либо использовать редуктор для всех объектов, либо редуктор для каждого типа сущности. Редуктор (сущности) должен нести ответственность за синхронизировать ваши (серверные) данные и иметь единственный источник правды.

const initialState = {
  articleEntities: {},
  userEntities: {},
};

Результат

Результаты - это только ссылки на ваши сущности. Представьте себе следующий сценарий: (1) Вы извлекаете из рекомендованного API articles с помощью ids: ['1', '2']. Вы сохраняете объекты в редукторе сущности статьи. (2) Теперь вы получаете все статьи, написанные конкретным автором с помощью id: 'X'. Снова вы синхронизируете статьи в редукторе сущности статьи. Редуктор сущности статьи является единственным источником правды для всех ваших данных статьи - вот и все. Теперь вы хотите иметь другое место, чтобы отличать статьи ((1) рекомендуемые статьи и (2) статьи от автора X). Вы можете легко сохранить их в другом конкретном редукторе. Состояние этого редуктора может выглядеть так:

const state = {
  recommended: ['1', '2' ],
  articlesByAuthor: {
    X: ['2'],
  },
};

Теперь вы можете легко увидеть, что статья автора X также является рекомендуемой статьей. Но вы сохраняете только один источник истины в своем редукторе сущности статьи.

В вашем компоненте вы можете просто сопоставить сущности + рекомендуемые /articlesByAuthor, чтобы представить объект.

Отказ от ответственности: я могу порекомендовать запись в блоге, которую я написал, которая показывает, как приложение реального мира использует normalizr для предотвращения проблем в управлении государством: Redux Normalizr: улучшите свою Управление государством

Ответ 3

Я реализовал небольшое отклонение общего редуктора, который можно найти через Интернет. Он способен удалять элементы из кеша. Все, что вам нужно сделать, это убедиться, что при каждом удалении вы отправляете действие с удаленным полем:

export default (state = entities, action) => {
    if (action.response && action.response.entities)
        state = merge(state, action.response.entities)

    if (action.deleted) {
        state = {...state}

        Object.keys(action.deleted).forEach(entity => {
            let deleted = action.deleted[entity]

            state[entity] = Object.keys(state[entity]).filter(key => !deleted.includes(key))
                .reduce((p, id) => ({...p, [id]: state[entity][id]}), {})
        })
    }

    return state
}

пример использования в коде действия:

await AlarmApi.remove(alarmId)

dispatch({
    type: 'ALARM_DELETED',
    alarmId,
    deleted: {alarms: [alarmId]},
})

Ответ 4

В вашем редукторе сохраните копию ненормированных данных. Таким образом, вы можете сделать что-то вроде этого (при добавлении нового объекта в массив в состоянии):

case ACTION:
  return {
    unNormalizedData: [...state.unNormalizedData, action.data],
    normalizedData: normalize([...state.unNormalizedData, action.data], normalizrSchema),
  }

Если вы не хотите хранить ненормированные данные в своем магазине, вы также можете использовать denormalize