Parfois, les réducteurs deviennent un peu salissants:
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;
}
}
Y a-t-il un moyen plus concis de faire cela?
UPD: il fait maintenant partie de l'ES2018
Il pourrait être légèrement amélioré via une syntaxe non normalisée, mais aussi par propagation des propriétés :
return {
...state,
notificationBar: {
...state.notificationBar,
open: true,
},
};
Bien qu'il soit possible d'utiliser l'opérateur spread, il existe de nombreuses autres façons d'obtenir le même résultat sans même avoir besoin d'un futur compilateur JS pour une fonctionnalité non normalisée. Voici quelques autres options sans ordre particulier.
Si vous êtes sûr que votre état ne va pas croître, vous pouvez simplement renvoyer le nouvel état entier en tant que littéral.
return {
notificationBar: {
open: true
}
}
Cependant, cela ne sera pas souvent approprié car il est peu probable que votre état soit aussi simple.
Redux vous fournit une méthode utilitaire pour combiner plusieurs réducteurs fonctionnant sur différentes parties de l'objet d'état. Dans ce cas, vous créez un réducteur notificationBar
qui gère cet objet seul.
createStore(combineReducers({
notificationBar: function(state=initialNBarState, action) {
switch (action.type) {
case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
return Object.assign({}, state, { open: true });
}
});
Cela vous évite d'avoir à vous soucier du niveau supérieur des propriétés, vous évitant ainsi d'imbriquer des appels à Object.assign
.
Si votre état peut logiquement être divisé en sections clairement définies, il s'agit probablement de la manière la plus idiomatique de résoudre ce problème.
Vous pouvez utiliser une bibliothèque de structures de données persistantes pour créer des structures de données modifiables pour renvoyer une copie.
Mori est le résultat de la compilation des structures de données et de l'API fonctionnelle de Clojure dans JS.
import { hashMap, updateIn } from 'mori';
const initialState = hashMap(
"notificationBar", hashMap(
"open", false
)
);
// ...
return updateIn(state, ['notificationBar', 'open'], true);
ImmutableJS est une approche plus impérative pour amener la sémantique de Hash Array Mapped Tries à partir des structures de données persistantes de Clojure vers Javascript.
import { Map } from 'immutable';
const initialState = Map({
notificationBar: Map({
open: true
});
});
// ...
return state.setIn(['notificationBar', 'open'], true);
Object.assign
Vous pouvez créer une version plus conviviale de Object.assign
pour écrire des versions plus simples du code ci-dessus. En fait, il peut être aussi concis que l'opérateur ...
.
function $set(...objects) {
return Object.assign({}, ...objects);
}
return $set(state, {
notificationBar: $set(state.notificationBar, {
open: true,
})
});
Un certain nombre de bibliothèques offrent également des aides à l'immuabilité pour apporter des modifications à des objets mutables réguliers.
React dispose depuis longtemps d’un ensemble d’aides à l’immutabilité. Ils utilisent une syntaxe similaire aux requêtes MongoDB.
import update from 'react-addons-update';
return update(state, {
notificationBar: {
open: { $set: true }
}
});
Cette bibliothèque vous permet d'utiliser des chemins de points familiers pour spécifier les mises à jour des propriétés (imbriquées).
import dotProp from 'dot-prop-immutable';
return dotProp.set(state, 'notificationBar.open', true);
Cette bibliothèque est un wrapper autour de react-addons-update
et fournit une syntaxe plus fonctionnelle pour la mise à jour des propriétés (imbriquées).
Plutôt que de transmettre une nouvelle valeur, vous transmettez une fonction qui prend l'ancienne valeur et en retourne une nouvelle.
import updateIn from 'update-in';
return updateIn(state, ['notificationBar', 'open'], () => true);
Pour la mise à jour des propriétés, cette bibliothèque est comme un croisement entre dot-prop-immutable
et update-in
.
import path from 'immutable-path';
return path.map(state, 'notificationBar.open', () => true);
Vous pouvez utiliser des lentilles.
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 } }`
Vous pouvez penser aux objectifs en tant qu'accès/mise à jour de propriété compositionnelle.
Quelques bonnes ressources sur les objectifs:
Si vous êtes d'accord avec la lecture de lisps, je vous recommande également de jeter un œil sur cet excellent introduction aux objectifs à partir de docs de la raquette . Enfin, si vous voulez aller plus loin et que vous pouvez lire avec haskell, vous pouvez regarder: Objectifs - accès aux données de composition et manipulation .
Si vous utilisez Immutable.js , vous pouvez regarder sous la rubrique Structures imbriquées des fonctions qui peuvent vous aider, j'utilise personnellement mergeDeep
:
prevState.mergeDeep({ userInfo: {
username: action.payload.username,
} }),
Tous les conseils ici sont excellents et valables, mais je voudrais proposer une autre solution. Le problème qui apparaît ici est certainement un motif commun, donc je pense qu’il est bien mieux d’écrire votre propre interface pour de telles mises à jour et de l’en tenir à l’intérieur des réducteurs et d’utiliser une fonction pour mettre à jour en profondeur tous vos réducteurs.
Par exemple, J'ai créé une bibliothèque , où j'ai essayé de résoudre ce problème de la manière suivante: je reçois le type du module (appelé "mosaïque"), fonction permettant d'effectuer des opérations (à la fois asynchrone et sync) et nidification souhaitée en fonction des paramètres passés. Donc, pour votre cas, ce sera quelque chose comme:
import { createSyncTile } from 'redux-tiles';
const uiTile = createSyncTile({
type: ['ui', 'elements'],
fn: ({ params }) => params,
// type will be `notificationBar`
nesting: ({ type }) => [type],
});
Et c'est tout - la mise à jour se fera correctement sur l'imbrication arbitraire. De plus, tile fournit des sélecteurs, pour que vous n'ayez pas à vous inquiéter personnellement de la localisation précise des données, vous pouvez simplement les utiliser .. Donc, je ne veux pas dire que c'est la meilleure solution, mais l'idée est belle. simple - n’ayez pas peur d’écrire votre propre implémentation, puis utilisez simplement factory pour résoudre ce problème.
En plus de ce qui a été dit précédemment, voici une manière fonctionnelle avec 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