it-swarm.com.ru

Более чистый/короткий способ обновить вложенное состояние в Redux?

Иногда редукторы становятся немного грязными:

const initialState = {
    notificationBar: {
        open: false,
    },
};

export default function (state = initialState, action) {
  switch (action.type) {
    case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
      return Object.assign({}, state, {
        // TODO: Find a cleaner way to do this!
        notificationBar: Object.assign({}, state.notificationBar, {
          open: true,
        }),
      });
    default:
      return state;
  }
}

Есть ли более лаконичный способ сделать это?

28
ffxsam

UPD: теперь это часть ES2018

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

return {
    ...state,
    notificationBar: {
        ...state.notificationBar,
        open: true,
    },
};
39
zerkms

Хотя можно использовать оператор распространения, существует множество других способов достижения того же результата, даже не требуя будущего компилятора JS для нестандартизированной функции. Вот некоторые другие варианты в произвольном порядке.

Вернуть литерал

Если вы уверены, что ваше состояние не будет расти, тогда вы можете просто вернуть все новое состояние в виде литерала.

return {
  notificationBar: {
    open: true
  }
}

Однако, это не часто будет уместно, потому что вряд ли ваше состояние будет таким простым.


Объединить Редукторы

Redux предоставляет вам полезный метод для объединения нескольких редукторов, которые работают с различными частями объекта состояния. В этом случае вы бы создали редуктор notificationBar, который обрабатывал бы только этот объект.

 createStore(combineReducers({
   notificationBar: function(state=initialNBarState, action) {
     switch (action.type) {
       case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
         return Object.assign({}, state, { open: true });
   }
 });

Это избавляет вас от необходимости беспокоиться о верхнем уровне свойств, так что вы можете избежать вложенных вызовов Object.assign.

Если ваше состояние может быть логически разбито на четко определенные разделы, то это, вероятно, самый идиоматический способ решения этой проблемы.


Используйте неизменные данные

Вы можете использовать постоянную библиотеку структур данных для создания структур данных, которые можно изменить для возврата копии.

Mori

Mori - это результат компиляции структур данных и функциональных API Clojure в JS.

import { hashMap, updateIn } from 'mori';

const initialState = hashMap(
  "notificationBar", hashMap(
    "open", false
  )
);

// ...

return updateIn(state, ['notificationBar', 'open'], true);

ImmutableJS

ImmutableJS - это более обязательный подход к переносу семантики попыток с отображением массивов хэша из постоянных структур данных Clojure в Javascript.

import { Map } from 'immutable';

const initialState = Map({
  notificationBar: Map({
    open: true
  });
});

// ...

return state.setIn(['notificationBar', 'open'], true);

Псевдоним Object.assign

Вы можете создать более дружественную версию Object.assign для написания более кратких версий кода выше. На самом деле, это может быть почти так же кратко, как оператор ....

function $set(...objects) {
  return Object.assign({}, ...objects);
}

return $set(state, {
  notificationBar: $set(state.notificationBar, {
    open: true,
  })
});

Используйте неизменных помощников

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

реагирующие-аддоны-обновления

У React уже давно есть встроенный набор помощников по неизменяемости. Они используют синтаксис, аналогичный запросам MongoDB.

import update from 'react-addons-update';

return update(state, {
  notificationBar: {
    open: { $set: true }
  }
});

дот-проп-неизменны

Эта библиотека позволяет вам использовать знакомые точки для указания обновлений (вложенных) свойств.

import dotProp from 'dot-prop-immutable';

return dotProp.set(state, 'notificationBar.open', true);

обновления в

Эта библиотека является оберткой вокруг react-addons-update и предоставляет более функциональный синтаксис для обновления (вложенных) свойств.

Вместо того, чтобы передавать новое значение, вы передаете функцию, которая принимает старое значение и возвращает новое.

import updateIn from 'update-in';

return updateIn(state, ['notificationBar', 'open'], () => true);

неизменная-путь

Для обновления свойств эта библиотека представляет собой нечто среднее между dot-prop-immutable и update-in.

import path from 'immutable-path';

return path.map(state, 'notificationBar.open', () => true);
45
Dan Prince

Вы можете использовать линзы.

import { set, makeLenses } from '@DrBoolean/lenses'

const L = makeLenses(['notificationBar', 'open']);
const notificationBarOpen = compose(L.notificationBar, L.open)
const setNotificationBarOpenTrue = set(notificationBarOpen, true)

const a = { notificationBar: { open: false } }
const b = setNotificationBarOpenTrue(a) 
// `a` is left unchanged and `b` is `{ notificationBar: { open: true } }`

Вы можете думать о линзах как о доступе/обновлении композиционных свойств.

Несколько хороших ресурсов о линзах:

Если вы хорошо читаете lisps, я бы также рекомендовал взглянуть на это превосходное введение в объективы из racket docs . Наконец, если вы хотите пойти глубже и можете читать haskell, вы можете посмотреть: Линзы - доступ к композиционным данным и манипулирование ими .

6
Safareli

Если вы используете Immutable.js , вы можете посмотреть в разделе Nested Structures некоторые функции, которые могут вам помочь, я лично использую mergeDeep:

prevState.mergeDeep({ userInfo: {
  username: action.payload.username,
} }),
1
Ricardo Mutti

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

Например, я создал библиотеку , где я попытался решить эту проблему следующим образом: я получил тип модуля (так называемый «тайл»), функцию для выполнения операций (как асинхронных, так и синхронизированных) и желаемое вложение на основе переданных параметров. Итак, для вашего случая это будет что-то вроде:

import { createSyncTile } from 'redux-tiles';
const uiTile = createSyncTile({
  type: ['ui', 'elements'],
  fn: ({ params }) => params,
  // type will be `notificationBar`
  nesting: ({ type }) => [type],
});

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

0
Bloomca

В дополнение к тому, что было сказано ранее, здесь есть функциональный способ с Ramda :

import { assocPath } from 'ramda';

const o1 = { a: { b: { c: 1 }, bb: { cc: 22 } } };
const o2 = assocPath(['a', 'b', 'c'])(42)(o1);
console.log(o1 !== o2, o1.a !== o2.a); // new copies of "changed" objects
console.log(o1.a.bb === o2.a.bb); // deep unchanged properties are copied by reference
0
aeldar