Шаблон для обновления нескольких частей состояния Redux

Форма моего состояния Redux выглядит следующим образом:

{
  user: {
    id: 123,
    items: [1, 2]
  },
  items: {
    1: {
      ...
    },
    2: {
      ...
    }
  }
}

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

Если я хочу добавить элемент, я могу вызвать 2 редуктора, первый добавит новый объект в items, а второй добавит идентификатор в массив user.items.

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

12 голосов | спросил Guy 30 WedEurope/Moscow2015-12-30T17:38:40+03:00Europe/Moscow12bEurope/MoscowWed, 30 Dec 2015 17:38:40 +0300 2015, 17:38:40

1 ответ


0

Я думаю, что то, что вы делаете, на самом деле правильно!

При отправке действия, начиная с корневого редуктора, будет вызываться каждый «субредуктор», передавая соответствующее «субсостояние» и действие следующему уровню субредукторов. Вы можете подумать, что это нехороший шаблон, поскольку каждый «субредуктор» вызывается и распространяется вплоть до каждого конечного узла дерева состояний, но на самом деле это не так!

Если действие определено в случае переключения, то «подредуктор» изменит только принадлежащую ему часть «подсостояния» и, возможно, передаст действие следующему уровню, но если действие не определено в «субредукторе» он ничего не делает и возвращает текущее «субсостояние», которое останавливает распространение.

Давайте рассмотрим пример с более сложным деревом состояний!


Допустим, вы используете redux-simple-router, и я расширил ваш случай, сделав его более сложным (с данными нескольких пользователей), чем ваше дерево состояний может выглядеть примерно так:

 {
  currentUser: {
    loggedIn: true,
    id: 123,
  },
  entities: {
    users: {
      123: {
        id: 123,
        items: [1, 2]
      },
      456: {
        id: 456,
        items: [...]
      }
    },
    items: {
      1: {
        ...
      },
      2: {
        ...
      }
    }
  },
  routing: {
    changeId: 3,
    path: "/",
    state: undefined,
    replace:false
  }
}

Как вы уже можете видеть, в дереве состояний есть вложенные слои, и для решения этой проблемы мы используем reducer composition, и концепция заключается в том, чтобы использовать combineReducer() для каждого слоя в дереве состояний.

Итак, ваш редуктор должен выглядеть примерно так: (Чтобы проиллюстрировать концепцию слой за слоем, это снаружи, так что порядок обратный)

первый слой:

 import { routeReducer } from 'redux-simple-router'

function currentUserReducer(state = {}, action) {
  switch (action.type) {...}
}

const rootReducer = combineReducers({
  currentUser: currentUserReducer,
  entities: entitiesReducer, // from the second layer
  routing: routeReducer      // from 'redux-simple-router'
})

второй уровень (часть сущностей):

 function usersReducer(state = {}, action) {
  switch (action.type) {
  case ADD_ITEM:
  case TYPE_TWO:
  case TYPE_TREE:
    return Object.assign({}, state, {
      // you can think of this as passing it to the "third layer"
      [action.userId]: itemsInUserReducer(state[action.userId], action)
    })
  case TYPE_FOUR:
    return ...
  default:
    return state
  }
}

function itemsReducer(...) {...}

const entitiesReducer = combineReducers({
  users: usersReducer,
  items: itemsReducer
})

третий уровень (entity.users.items):

 /**
 * Note: only ADD_ITEM, TYPE_TWO, TYPE_TREE will be called here,
 *       no other types will propagate to this reducer
 */
function itemsInUserReducer(state = {}, action) {
  switch (action.type) {
  case ADD_ITEM:
    return Object.assign({}, state, {
      items: state.items.concat([action.itemId])
      // or items: [...state.items, action.itemId]
    })
  case TYPE_TWO:
    return DO_SOMETHING
  case TYPE_TREE:
    return DO_SOMETHING_ELSE
  default:
    state:
  }
}

когда отправляется действие

redux будет вызывать каждый субредуктор из rootReducer,
передача:
currentUser: {...} подсостояние и все действие в currentUserReducer
entities: {users: {...}, items: {...}} и действие для entitiesReducer
routing: {...} и действие для routeReducer
и ...
entitiesReducer передаст users: {...} и действие для usersReducer,
и items: {...} и действие для itemsReducer

почему это хорошо?

Итак, вы упомянули, что есть способ заставить корневой редуктор обрабатывать различные части состояния, вместо того, чтобы передавать их в отдельные субредукторы. Но если вы не используете композицию редуктора и пишете огромный редуктор для обработки каждой части состояния, или просто вкладываете свое состояние в глубоко вложенное дерево, тогда ваше приложение становится более сложным (скажем, у каждого пользователя есть [friends] массив, или элементы могут иметь [tags] и т. Д.) , это будет безумно сложно, если не невозможно, выяснить каждый случай.

Кроме того, разбиение редукторов делает ваше приложение чрезвычайно гибким, вам просто нужно добавить любой case TYPE_NAME в редуктор, чтобы отреагировать на это действие ( до тех пор, пока ваш родительский редуктор его пропустит).

Например, если вы хотите отслеживать, посещает ли пользователь какой-либо маршрут, просто добавьте case UPDATE_PATH к вашему переключателю редуктора!

ответил DaxChen 30 WedEurope/Moscow2015-12-30T21:53:35+03:00Europe/Moscow12bEurope/MoscowWed, 30 Dec 2015 21:53:35 +0300 2015, 21:53:35

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132