我应该将数据更改代码放在函数中还是仅放在操作中?
P粉410239819
P粉410239819 2024-01-10 18:11:13
0
1
373

我目前正在编写一个公交车时刻表应用程序,它使用对象类型来对时刻表“文档”进行建模。

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

除了类型之外,我还有许多函数,如果我要使用突变,我通常会将它们编写为类上的方法。我主要使用 Immer,因此我不必编写大量扩展语法。例如,

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

为了管理状态,我使用 Zustand 和 Immer,但我觉得如果我使用 Redux,我的问题会是相同的。在我的商店中,我有一个 Timetable 对象数组,以及也使用 Immer 重新分配当前选定的时间表对象的操作:

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

然后我在 React 组件中调用数据更改函数,并调用更新操作:

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

这可行,但我不确定我做得是否正确。我现在有两层 Immer 调用,我的组件现在有直接在其事件处理程序中调用的数据修改函数,而且我不太喜欢“方法”的外观,即使从总体来看,这是一个小错误。

我考虑过:

  • 将所有数据修改转化为操作。这看起来会更难维护,也更难理解,因为在索引商店的对象数组方面有很多重复。
  • 为每个数据修改函数创建一个操作;然后,从我的事件处理程序调用这些操作。这似乎会造成一些重复,即使只是在名称方面。
  • 将我的 Timetable 类型转换为一个类,将数据修改函数重写为变异方法,并设置 [immerable] = true 并让 Immer 完成所有工作我的行动。我已经这样做了,但我宁愿坚持不可变的记录模式。

就其价值而言,Flux、Zustand 或 Immer 的文档往往会显示第一个选项,而且只是偶尔出现;没有哪个应用程序像 counter = counter + 1 那样简单。对于使用 Flux 架构的应用程序,构建它的最佳方法是什么?

P粉410239819
P粉410239819

全部回复(1)
P粉576184933

(我不熟悉 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 );
    },
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板