Dois-je mettre le code de modification des données dans une fonction ou simplement dans une action ?
P粉410239819
P粉410239819 2024-01-10 18:11:13
0
1
381

J'écris actuellement une application d'horaires de bus qui utilise des types d'objets pour modéliser les "documents" d'horaires.

interface Timetable {
  name: string
  stops: string[]
  services: string[][]
}

En plus des types, j'ai de nombreuses fonctions, et si je dois utiliser des mutations, je les écris généralement sous forme de méthodes sur la classe. J'utilise principalement Immer donc je n'ai pas besoin d'écrire beaucoup de syntaxe étendue. Par exemple,

const addStop = (timetable: Timetable, stopName: string): Timetable => {
  return produce(timetable, (newTimetable) => {
    newTimetable.stops.push(stopName)
  })
}

Pour gérer l'état, j'utilise Zustand et Immer, mais j'ai l'impression que si j'utilisais Redux, mon problème serait le même. Dans mon magasin, j'ai un tableau d'objets Timetable et une opération qui utilise également Immer pour réaffecter l'objet Timetable actuellement sélectionné :

        updateTt: (tt, index) => {
          set((state) => {
            state.timetables[index] = tt
          })
        },
        updateThisTt: (timetable) => {
          set((s) => {
            if (s.selectedTtIdx === null) {
              throw new Error("no selected timetable")
            }
            s.timetables[s.selectedTtIdx] = timetable
          })
        },

Ensuite j'appelle la fonction de changement de données dans le composant React et j'appelle l'opération de mise à jour :

const onAddStop = (name) => {
  updateThisTt(addStop(timetable, name))
}

Cela fonctionne, mais je ne suis pas sûr de le faire correctement. J'ai maintenant deux couches d'appels Immer, mon composant a désormais des fonctions de modification de données qui sont appelées directement dans ses gestionnaires d'événements, et je n'aime pas vraiment l'apparence des "méthodes" même s'il s'agit globalement d'un bug mineur.

J'ai considéré :

  • Convertissez toutes les modifications de données en opérations. Cela semble être plus difficile à maintenir et à comprendre car il y a beaucoup de duplication dans l'indexation de la gamme d'objets du magasin.
  • Créez une action pour chaque fonction de modification de données ; puis appelez ces actions depuis mon gestionnaire d'événements. Cela semble créer une certaine duplication, ne serait-ce qu'en termes de noms.
  • Placez mon Timetable 类型转换为一个类,将数据修改函数重写为变异方法,并设置 [immerable] = true et laissez Immer faire tout le travail de mes actions. Je l'ai fait, mais je préfère m'en tenir au mode d'enregistrement immuable.

Pour ce que ça vaut, la documentation de Flux, Zustand ou Immer a tendance à afficher la première option, et seulement occasionnellement, aucune application n'est aussi simple que counter = counter + 1. Quelle est la meilleure façon de créer une application en utilisant l'architecture Flux ?

P粉410239819
P粉410239819

répondre à tous(1)
P粉576184933

(Je ne connais pas Zusand et Immer, mais je peux peut-être vous aider...)

Il existe toujours différentes façons et je vous suggère ici ma préférée.

Distinguez clairement les actions de « programmation » et la « mutation » réelle de l'état. (Peut-êtreajoutez un autre niveau entre les deux).

Fonction spécifique « Mutation »

Je recommande de créer des fonctions de "mutation" spécifiques plutôt que génériques, c'est à dire :

  • au lieu de updateThisTt:() => { ...,
  • Utilisez des fonctions comme addStop: () => { ....

Créez autant de fonctions de mutation que nécessaire, chacune avec un objectif.

Construisez de nouveaux états dans la fonction "mutation"

Conceptuellement, n'utilisez que des générateurs immer à l'intérieur des fonctions de mutation.
(Je veux dire à propos du magasin. Bien sûr, vous pouvez toujours utiliser immer à d'autres fins) .

Basé sur cet exemple officiel  :

import { produce } from 'immer'

const useLushStore = create((set) => ({
  lush: { forest: { contains: { a: 'bear' } } },
  clearForest: () =>
    set(
      produce((state) => {  // <----- create the new state here
        state.lush.forest.contains = null
      })
    ),
}));

const clearForest = useLushStore((state) => state.clearForest);
clearForest();

À l'intérieur du composant, vous pouvez maintenant appeler la fonction "mutateur".

const onAddStop = (name) => {
  updateThisTt( timetable, name );
}

Construire un nouveau pays

Si la construction d'un nouvel état devient compliquée, vous pouvez toujours extraire certaines fonctions de "constructeur". Mais considérons d’abord la section suivante, « Fichiers volumineux et duplication ».

Par exemple, votre fonction addStop peut également être appelée à l'intérieur d'une mutation Zustand :

updateThisTt: ( timetable: Timetable, stopName: string ) => {
    const newTimeTable = addStop( timetable, stopName );
    set( ( s ) => {
        if( s.selectedTtIdx === null ){
            throw new Error( "no selected timetable" )
        }
        s.timetables[ s.selectedTtIdx ] = newTimeTable;
    });
}

Fichiers volumineux et doublons

La duplication de code doit certainement être évitée, mais il y a toujours des compromis.

Je ne suggérerai pas d'approche spécifique, mais sachez que le code est souvent plus lu qu'écrit. ParfoisJe pense que cela vaut la peine d'écrire quelques lettres supplémentaires, comme quelque chose comme state.timetables[index] plusieurs fois, Si cela rend le but du code plus évident. Vous devez juger par vous-même.

Quoi qu'il en soit, je recommande de mettre votre fonction de mutation dans un fichier séparé qui ne fait rien d'autre, De cette façon, cela peut sembler plus facile à comprendre que vous ne le pensez.

Si vous avez un fichier très volumineux mais êtes entièrement concentré uniquement sur la modification de l'état , Et la structure est cohérente, ce qui la rend facile à lire même si vous devez faire défiler Quelques pages.

Par exemple si cela ressemble à ceci :

// ...

//-- Add a "stop" to the time table 
addStop: ( timetable: Timetable, stopName: string ) => {

   // .. half a page of code ...
   
},

//-- Do some other specific state change
doSomethingElse: ( arg1 ) => {

   // .. probably one full page of code ...
   
},

//-- Do something else again ... 
doSomethingAgain: () => {

   // .. another half page of code ...
   
},

// ...

Notez également que ces fonctions variadiques sont complètement indépendantes (ou le sont-elles ? J'aurais aimé que Zustand utilise des fonctions pures ici, non ?) .

Cela signifie que si cela devient compliqué, vous pouvez même diviser plusieurs fonctions de mutation Divisé en fichiers séparés dans un dossier Comme /store/mutators/timeTable.js.

Mais vous pouvez facilement le faire à tout moment plus tard.

Créez la "charge utile" ("l'appelant à l'action")

Vous ressentirez peut-être le besoin d'un autre "niveau" Entre gestionnaire d'événements et mutateur. J'ai généralement un calque comme celui-ci, mais je n'ai pas de bon nom pour lui. Appelons-le temporairement « Operation Caller ».

Parfois, il est difficile de décider ce qu'est un « variogramme » et ce qu'est un « variogramme » "Appelant à l'action".

Quoi qu'il en soit, vous pouvez "créer des données" dans "l'appelant d'action", mais vous devriez Aucune manipulation d'État d'aucune sorte n'est effectuée ici , pas même avec immer.

Transformer l'état et créer une charge utile

C'est une différence subtile et vous ne devriez probablement pas trop vous en soucier (et il peut y avoir des exceptions) , mais à titre d'exemple :

Vous pouvez utiliser certaines parties de l'ancien état dans un "action caller", par exemple :

// OK:
const { language } = oldState;
const newItem = {  // <-- This is ok: build a new item, use some data for that.
    id: 2,
    language: language,
};
addNewItemMutator( newItem ):

Mais vous ne devriez pas ne pas mapper (ou transformer) l'ancien état vers le nouvel état, par exemple :

// NOT OK:
const { item } = oldState;
const newItem = {  // <-- This kind of update belongs inside the mutator function.   
    ...item,  
    updatedValue: 'new',
};
addNewItemMutator( newItem ):

Autre exemple

Aucune suggestion spécifique n'est faite ici, juste un exemple :

Transmettre de nombreuses valeurs aux mutateurs peut prêter à confusion, par exemple :

const myComponent = ( props ) =>{
    const { name } = props;
    cosnt value = useValue();
    
    // ...
    
    const onAddNewItem: () => {
        const uniqueId = createUUID();
        addNewItemMutator( name, value, uniqueId, Date.now() );
    },

Dans le gestionnaire d'événements, vous souhaitez vous concentrer sur ce que vous voulez vraiment faire, comme "ajouter un nouvel élément", Mais ne considérez pas tous les arguments. De plus, vous aurez peut-être besoin du nouvel élément pour d’autres choses.

Ou vous pouvez écrire :

const callAddItemAction = function( name, value ){

    const newItem = {
        name:        name,
        value:       value,
        uniqueId:    createUUID(),
        dateCreated: Date.now(),
    };
    
    // sendSomeRequest( url, newItem ); // maybe some side effects
    
    addNewItemMutator( newItem );
}

const myComponent = ( props ) =>{
    const { name } = props;
    cosnt value = useValue();
    
    // ...
    
    const onAddNewItem: () => {
        callAddItemAction( name, value );
    },
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal