web-dev-qa-db-fra.com

Comment mettre des méthodes sur les objets à l'état Redux?

Selon docs l'état de l'application react doit être quelque chose de sérialisable. Et les cours alors?

Disons que j'ai une application ToDo. Jusqu'à présent, chacun des éléments Todo a des propriétés comme name, date etc. Maintenant, je veux avoir des méthodes sur des objets qui ne sont pas sérialisables. C'est à dire. Todo.rename() qui renommerait todo et ferait beaucoup d'autres choses.

Pour autant que je sache, je peux avoir une fonction déclarée quelque part et faire rename(Todo) ou peut-être passer cette fonction via les accessoires this.props.rename(Todo) au composant.

J'ai 2 problèmes avec la déclaration de .rename() quelque part: 1) Où? En réducteur? Il serait difficile de trouver toutes les méthodes would be instance Quelque part dans les réducteurs autour de l'application. 2) Passer cette fonction. Vraiment? dois-je le passer manuellement à partir de tous les composants de niveau supérieur via Et chaque fois que j'ai plus de méthodes, ajouter une tonne de passe-partout pour le transmettre? Ou faites toujours et espérez que je n'ai jamais qu'une seule méthode de renommage pour un type d'objet. Pas Todo.rename()Task.rename() et Event.rename()

Cela me semble idiot. L'objet doit savoir ce qui peut lui être fait et de quelle manière. N'est-ce pas?

Qu'est-ce qui me manque ici?

46
Yan

Dans Redux, vous n'avez pas vraiment de modèles personnalisés. Votre état doit être des objets simples (ou des enregistrements immuables). Ils ne devraient pas avoir de méthodes personnalisées.

Au lieu de mettre des méthodes sur les modèles (par exemple TodoItem.rename) vous devez écrire des réducteurs qui gèrent les actions . C'est tout l'intérêt de Redux.

// Manages single todo item
function todoItem(state, action) {
  switch (action.type) {
    case 'ADD':
      return { name: action.name, complete: false };

    case 'RENAME':
      return { ...state, name: action.name };

    case 'TOGGLE_COMPLETE':
      return { ...state, complete: !state.complete };

    default:
      return state;
  }
}

// Manages a list of todo items
function todoItems(state = [], action) {
  switch (action.type) {
    case 'ADD':
      return [...state, todoItem(undefined, action)];

    case 'REMOVE':
      return [
        ...state.slice(0, action.index),
        ...state.slice(action.index + 1)
      ];

    case 'RENAME':
    case 'TOGGLE_COMPLETE':
      return [
        ...state.slice(0, action.index),
        todoItem(state[action.index], action),
        ...state.slice(action.index + 1)
      ];
  }
}

Si cela n'a toujours pas de sens, veuillez lire le Tutoriel de base de Redux car vous semblez avoir une mauvaise idée de la structure des applications Redux.

36
Dan Abramov

La réponse de Dan est bien sûr correcte en ce qui concerne la façon dont vous devez utiliser redux lors de l'écriture d'une application à partir de zéro. Ma réponse est basée sur une situation de motivation non idéale qui peut être similaire à la situation des PO et sur la façon dont je pense y faire face.

Je me retrouve à essayer de créer une application redux qui dépend de bibliothèques tierces au sein du réducteur redux qui effectuent des opérations sur des objets d'entrée avec des méthodes, et je voulais garder ces objets d'entrée dans l'état redux. Voici ma situation motivante en pseudo-code, et comment je la "résout" en utilisant la méthode d'affectation de lodash:

// Library A is a large 3rd party library I don't want to change
// Library A expects instances of MyClass to have a method 'complexOperation', and also to have property 'a'
LibraryA = class {
  static reliesOnComplexOperation(myClassInstance) {
    if (myClassInstance.complexOperation() && myClassInstance.a < 100) {
      return true;
    } else {
      return false;
    }
  }
}

// MyClass is my own class that I control, and I want to store it's state and make changes to it using the redux reducer.
MyClass = class {
  constructor() {
    this.a = 0;
  }
  myComplexOperation() {
    return a > 10;
  }
}

//and here is my redux reducer, making use of Library A
function reducer(state, action) {
  switch (action.type) {
    case CHANGE_MY_CLASS:
      const myClassInstancePlain = state.myClassInstance;
      // redux has converted myClassInstance into a plain object with no methods, so we need to create a new instance of MyClass, and assign property values from the plain object to the new instance. Using the assign function from lodash library to achieve this - likely not the most efficient way
      const myClassInstance = _.assign(new MyClass(), myClassInstancePlain);

      // now I can pass myClassInstance off to LibraryA, and the complexOperation method will be available
      if (LibraryA.reliesOnComplexOperation(myClassInstance)) {
        myClassInstance.a = 50;
      }
      // I can return myClassInstance as part of the new state, and redux will convert it to a plain object
      return {
        ...state,
        myClassInstance
      };
    default:
      return state;
  }
}

Cet exemple montre donc une façon d'incorporer des objets avec des méthodes à l'état redux en tant qu'objets simples, puis de rajouter les méthodes afin qu'elles puissent être utilisées dans le réducteur redux. J'adorerais entendre les pensées de Dan ou de quelqu'un d'autre sur ce modèle ou comment il pourrait être amélioré. J'ai vu Dan poster à plusieurs endroits que le stockage d'objets avec des méthodes est une mauvaise idée dans redux, donc le but ici est de trouver un moyen de stocker l'objet comme un objet simple et d'attacher les méthodes lorsque vous en avez besoin de manière efficace .

5
adailey

Je ne suis pas sûr que ce soit exactement pertinent (le contexte utilise Redux avec React), mais j'ai trouvé cette page en cherchant quelque chose de similaire, donc j'écris ceci au cas où cela serait intéressant.

Ma motivation est, j'ai des objets d'état où je voudrais exposer une représentation de cet état plutôt que de l'état lui-même. Ainsi, par exemple (en utilisant Immutable.js), j'ai un état (un schéma) qui comprend un ou plusieurs champs, chacun identifié par un identifiant unique et stocké dans une carte , plus une liste d'identifiants qui donne l'ordre des champs.

Je ne veux vraiment pas écrire des trucs comme

state.get('field').get(state.get('order').get(nth))

pour obtenir le nième champ ailleurs que près du réducteur, afin que la représentation interne soit masquée et puisse être modifiée facilement.

Ce que je fais actuellement (et c'est un peu une expérience), c'est d'ajouter une fonction dans le même fichier que le réducteur:

schema.mapVisibleState = function (state) {
    return {
        getOrderedFields: () => state.get('order').map((fid) => state.get('fields').get(fid)
    }
}

Ceci est utilisé dans les composants React correspondants (avec une fonction schema.bindActionCreators similaire):

export const Schema = connect(
    (state) => schema.mapVisibleState (state),
    (dispatch) => schema.bindActionCreators (dispatch)
)(_Schema) ;

Maintenant, je peux accéder aux champs, dans l'ordre correct, à l'intérieur du composant simplement comme this.props.getOrderedFields () et ne pas me soucier de la représentation sous-jacente. Il possède également la propriété Nice supplémentaire qui permet d'injecter facilement les dépendances de la fonction getOrderedFields ().

De plus, comme j'ai une structure similaire pour l'état du champ, je peux étendre ceci:

schema.mapVisibleState = function (state) {
    return {
        getOrderedFields: () => state.get('order').map((fid) => field.mapVisibleState(state.get('fields').get(fid)),
        getNthField (nth) => field.mapVisibleState(state.get('fields').get(state.get('order').get(nth)))
    }
}
4
Mike Richardson