簡體   English   中英

如何防御性地防止Redux狀態突變?

[英]How to defensively prevent mutation of the Redux state?

由於我的團隊使用預先存在的代碼作為新更改的模板,因此我試圖為我的應用程序中的reducer編寫一些防御性代碼。 因此,我試圖涵蓋與不變性有關的所有潛在偏差。

假設您的初始狀態如下所示:

const initialState = {
  section1: { a: 1, b: 2, c: 3 },
  section2: 'Some string',
};

以及一個reducer處理這樣的動作:

export default function(state = initialState, action) {
  switch(action.type) {
    case SOME_ACTION:
      return { ...state, section1: action.payload.abc };
  }
  return state;
}

然后,您可以有一個執行以下任務的調度程序:

function foo(dispatch) {
  const abc = { a: 0, b: 0, c: 0 };
  dispatch({ type: SOME_ACTION, payload: { abc } });

  // The following changes the state without dispatching a new action
  // and does so by breaking the immutability of the state too.
  abc.c = 5;
}

在這種情況下,當reducer通過創建舊狀態的淺表副本並僅更改已更改的位來遵循不變性模式時,調度程序仍然可以訪問action.payload.abc並可以對其進行更改。

也許redux已經創建了整個動作的深層副本,但是還沒有找到任何提及這一點的資料。 我想知道是否有一種方法可以簡單地解決此問題。

應當注意,在您的示例中,如果僅基於對象執行適當的級別復制,則該突變不會引起任何問題。

對於看起來像{ a: 1, b: 2, c: 3 } abc { a: 1, b: 2, c: 3 }您可以進行淺表復制,而對於嵌套對象{ a: { name: 1 } } ,則必須進行深復制,但仍然可以明確地執行此操作,而無需任何庫或任何東西。

{
  ...state,
  a: {
    ...action.a
  }
}

另外,您可以使用eslint-plugin-immutable防止突變,這會迫使程序員不要編寫此類代碼。

正如您在上面的ESLint插件的說明中所看到的,是no-mutation規則

該規則與使用Object.freeze()防止Redux還原器中的突變一樣有效。 但是,此規則沒有運行時成本。 一個很好的替代對象變異的方法是使用ES2016中提供的對象傳播語法。


主動防止突變的另一種相對簡單的方法(不使用某些不變性庫)是在每次更新時凍結狀態。

export default function(state = initialState, action) {
  switch(action.type) {
    case SOME_ACTION:
      return Object.freeze({
        ...state,
        section1: Object.freeze(action.payload.abc)
      });
  }
  return state;
}

這是一個例子:

 const object = { a: 1, b: 2, c : 3 }; const immutable = Object.freeze(object); object.a = 5; console.log(immutable.a); // 1 

Object.freeze是一個淺層操作,因此您必須手動凍結其余對象或使用諸如deep-freeze之類的庫。

上面的方法將保護突變的責任帶給了您。

這種方法的一個缺點是,它通過明確顯示突變保護來增加程序員的必要認知工作量,因此更容易出現錯誤(尤其是在大型代碼庫中)。

深度凍結時可能還會有一些性能開銷,特別是如果使用用於測試/處理您的特定應用程序可能永遠不會引入的各種極端情況的庫,則尤其如此。


更具可擴展性的方法是將不變性模式嵌入到代碼邏輯中,這將自然地將編碼模式推向不變性操作。

一種方法是使用不可變JS ,其中數據結構本身的構建方式是,對它們的操作始終會創建一個新實例,並且永不變異。

import { Map } from 'immutable';

export default function(state = Map(), action) {
  switch(action.type) {
    case SOME_ACTION:
      return state.merge({ section1: action.payload.abc });
      // this creates a new immutable state by adding the abc object into it
      // can also use mergeDeep if abc is nested
  }
  return state;
}

另一種方法是使用immer ,該immer器將不可變性隱藏在幕后,並遵循寫時復制原理為您提供可變的api。

import produce from 'immer'

export default = (state = initialState, action) =>
  produce(state, draft => {
    switch (action.type) {
      case SOME_ACTION:
        draft.section1 = action.section1;
    })
}

如果您要轉換一個現有的應用程序,該應用程序可能包含許多執行變異的代碼,那么該庫可能對您來說效果很好。

不變性庫的缺點在於,它增加了不熟悉該庫的人進入代碼庫的障礙,因為現在每個人都必須學習它。

就是說,一致的編碼模式通過明確限制代碼的構建方式,減少了認知工作量(每個人都使用相同的模式),並減少了代碼混亂因素(防止人們始終發明自己的模式)。 這自然會減少錯誤並加快開發速度。

我是Redux的維護者。

Redux本身並沒有采取任何措施來防止狀態突變 部分原因是因為Redux不知道或不在乎實際狀態。 它可以是一個數字,普通的JS對象和數組,Immutable.js映射和列表或其他內容。

話雖如此, 現有一些開發附件可以捕獲偶然的突變

我將特別建議您嘗試使用我們的新redux-starter-kit軟件包 現在,當您使用configureStore()函數時,默認情況下它將在開發模式下將redux-immutable-state-invariant中間件添加到您的商店中,並且還檢查是否意外添加了不可序列化的值。 另外,它的createReducer()實用程序使您可以定義化簡器,以通過“改變”狀態來簡化不可變的更新邏輯,但是更新實際上是不可變的。

我相信在reducer中,如果您從action.payload.abc創建一個新對象,則可以確保原始對象的任何更改都不會影響redux存儲。

case SOME_ACTION:
      return { ...state, section1: {...action.payload.abc}};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM