Heim  >  Fragen und Antworten  >  Hauptteil

Soll ich den Datenänderungscode in eine Funktion oder nur in eine Aktion einfügen?

Ich schreibe gerade eine Busfahrplananwendung, die Objekttypen zur Modellierung von Fahrplandokumenten verwendet.

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

Zusätzlich zu den Typen habe ich viele Funktionen, und wenn ich Mutationen verwende, schreibe ich sie normalerweise als Methoden in die Klasse. Ich verwende hauptsächlich Immer, sodass ich nicht viel erweiterte Syntax schreiben muss. Zum Beispiel

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

Um den Status zu verwalten, verwende ich Zustand und Immer, aber ich habe das Gefühl, dass mein Problem dasselbe wäre, wenn ich Redux verwenden würde. In meinem Shop habe ich ein Array von Timetable-Objekten und eine Operation, die Immer auch verwendet, um das aktuell ausgewählte Timetable-Objekt neu zuzuweisen:

        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
          })
        },

Dann rufe ich die Datenänderungsfunktion in der React-Komponente auf und rufe den Aktualisierungsvorgang auf:

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

Das funktioniert, aber ich bin mir nicht sicher, ob ich es richtig mache. Ich habe jetzt zwei Schichten von Immer-Aufrufen, meine Komponente verfügt jetzt über Datenänderungsfunktionen, die direkt in ihren Ereignishandlern aufgerufen werden, und mir gefällt das Aussehen der „Methoden“ nicht wirklich, auch wenn es insgesamt ein kleiner Fehler ist.

Ich habe überlegt:

In der Dokumentation für Flux, Zustand oder Immer wird meist die erste Option angezeigt, und nur gelegentlich ist keine Anwendung so einfach wie counter = counter + 1. Was ist der beste Weg, eine Anwendung mit der Flux-Architektur zu erstellen?

P粉410239819P粉410239819283 Tage vor348

Antworte allen(1)Ich werde antworten

  • P粉576184933

    P粉5761849332024-01-11 16:52:03

    (我不熟悉 ZustandImmer,但也许我可以帮忙......)

    总是有不同的方法,我在这里建议我最喜欢的一种。

    明确区分“调度”动作和状态的实际“突变”。 (也许在两者之间添加另一个级别)。

    特定的“突变”功能

    我建议创建特定“突变”函数,而不是通用函数,即:

    • 而不是 updateThisTt:() => { ...,
    • 使用诸如 addStop: () => { ... 之类的函数。

    根据需要创建许多突变函数,每个函数都有一个目的。

    在“变异”函数中构建新状态

    从概念上讲,仅在突变函数内使用 immer 生成器。
    (我的意思是关于商店。当然,您仍然可以将immer用于其他目的)

    根据这个官方示例

    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();

    在组件内部,您现在可以调用“mutator”函数。

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

    建设新国家

    如果构建新状态变得复杂,您仍然可以提取一些“构建器”函数。 但首先考虑下一节“大文件和重复”。

    例如您的 addStop 函数也可以在 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;
        });
    }

    大文件和重复

    当然应该避免代码重复,但总是需要权衡。

    我不会建议具体的方法,但请注意,代码通常阅读多于编写有时我认为值得多写几封信,例如类似 state.timetables[index] 多次, 如果它使代码的目的更加明显。你需要自己判断。

    无论如何,我建议将您的突变函数放入一个不执行任何其他操作的单独文件中, 这样,它看起来可能比您想象的更容易理解。

    如果您有一个很大的文件,但完全只专注于修改状态, 并且结构一致,即使您必须滚动,也很容易阅读 几页。

    例如如果它看起来像这样:

    // ...
    
    //-- 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 ...
       
    },
    
    // ...

    另请注意,这些变元函数是完全独立的(或者它们是吗?我希望 Zustand 在这里使用纯函数,不是吗?)

    这意味着,如果它变得复杂,你甚至可以拆分多个突变函数 分成文件夹内的多个单独文件 就像/store/mutators/timeTable.js

    但您以后随时都可以轻松做到这一点。

    构建“有效负载”(“操作调用者”)

    您可能会觉得需要另一个“级别” 事件处理程序变异函数之间。 我通常有这样一个层,但我没有一个好的名字。 我们暂时将其称为“操作调用者”。

    有时很难决定什么属于“变异函数”,什么又属于“变异函数” “动作调用者”。

    无论如何,您可以在“操作调用者”内部“构建一些数据”,但您应该 此处不进行任何类型的状态操作,即使使用immer也不行。

    转换状态与创建有效负载

    这是一个微妙的区别,您可能不应该对此过于担心(并且可能有例外),但作为一个例子:

    可以在“动作调用者”内使用旧状态的某些部分,例如:

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

    但是您不应该将旧状态映射(或转换)到新状态,例如:

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

    另一个例子

    这里不提出具体建议,只是举个例子:

    将许多值传递给变异器可能会变得混乱,例如:

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

    在事件处理程序中,您希望专注于您真正想做的事情,例如“添加新项目”, 但不要考虑所有的论点。此外,您可能还需要该新物品来做其他事情。

    或者你可以写:

    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 );
        },

    Antwort
    0
  • StornierenAntwort