我應該將資料更改程式碼放在函數中還是僅放在操作中?
P粉410239819
P粉410239819 2024-01-10 18:11:13
0
1
386

我目前正在編寫一個公車時刻表應用程序,它使用物件類型來對時刻表「文件」進行建模。

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 );
    },
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板