2016-07-23 149 views
1

爲什麼反應認爲當我傳遞一個新的對象時,我正在變異狀態?我錯過了什麼嗎? 這是我的簡化器,用於向測驗數組中的測驗對象添加問題。Redux狀態變異

case types.UPDATE_QUESTION_SUCCESS: { 
    let quizContext = state.filter(quiz => quiz.id === action.quizId); 
    let filteredQuestions = quizContext[0].questions.filter(question => 
    question.questionId !== action.question.questionId 
); 
    filteredQuestions.push(action.question); 
    quizContext[0].questions = filteredQuestions; 
    let quiz = quizContext[0]; 
    return [...state.filter(quiz => quiz.id !== action.quizId), Object.assign({}, quiz)]; 
} 

錯誤:

Invariant Violation: A state mutation was detected between dispatches, in the path `quizes.4.questions.1`. This may cause incorrect behavior. (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments) 
+0

請發佈整個減速機功能。 – Timo

回答

2

首先的一個嵌套的狀態,因爲你只關心quizContext[0]filter是矯枉過正。 只要做:

const quiz = state.find(quiz => quiz.id === action.quizId); 

達到相同的結果。

我不想在這裏做太多的代碼審查,但還有一件事。即使您應始終使用const,您也可以使用letconst只是意味着你不能重新分配變量,你仍然可以改變它。

然後你過濾問題添加一個,然後將其添加回剛剛找到的測驗。

quizContext[0].questions =是問題,因爲即使你的對象現在在一個新的數組中,它們仍然是相同的對象,所以如果你改變它們,你會改變狀態。

const quiz = Object.assign({}, state.find(quiz => quiz.id === action.quizId)); 

因此重寫你想最終像這樣完整的東西:

case types.UPDATE_QUESTION_SUCCESS: 
{ 
    const quiz = Object.assign({}, state.find(quiz => quiz.id === action.quizId)); 
    quiz.questions = quiz.questions.filter(question => 
    question.questionId !== action.question.questionId 
); 
    quiz.questions.push(action.question); 
    return [...state.filter(quiz => quiz.id !== action.quizId), quiz]; 
} 

但是請記住,大部分的時間你不wan't在過濾數據的減速機。相反,我建議使用reselect,以便您可以根據商店中的數據計算派生數據。

+0

'Object.assign'做了一個淺層合併,當這個狀態有一個嵌套結構時,這可能會導致進一步的突變,因爲超越第一層深的引用實際上是對舊狀態的引用。 – sasidhar

+0

@sasidhar在這種情況下不會產生任何效果,如果您擁有深度嵌套的結構,您可以添加處理深度的新減速器 - 其他任何事情都只是不好的做法。 – mash

0

你是變異的狀態,按您的減速器。在使用過濾器函數時,傳遞給過濾器的對象是原始對象的引用,因此您在此處未執行deepCopy。而這條線filterQuestions.push(action.question)實際上是變異的狀態。

試試這個: let quizContext = cloneDeep(state.filter(quiz => quiz.id === action.quizId));

雖然有可能是一個更好的解決方案在那裏,這是你要去哪裏錯了。被返回的過濾對象是對原始數組的引用,但不是新對象。所以你最終會改變狀態中的原始對象。

- 更多clarification--

cloneDeep是lodash功能,如果你正在使用Object.assign這是要幹什麼只是一個淺淺的合併,但不深的合併。因此,當狀態在更深的層次突變的引用仍然是原來的對象有通過由土豆泥是變異的狀態是行指出變異的原始狀態

--edit 2--

quizContext[0].questions = filteredQuestions;和使用Object.assign如果你確信你沒有在這種情況下,更多的減速是爲了

+0

推送本身不改變狀態!cloneDeep它不是一個函數,它也不是很好的練習或適當的,正如你在本例中已經注意到的那樣。 – mash

+0

@mash推送功能是引起突變的功能。沒有?它所具有的引用是在舊狀態下的原始對象,因此推送實際上會推送到原始對象,所以當你說「推動本身不會改變狀態!」時,我沒有明白你的意思。 ? – sasidhar

+0

不,它不像ECMA-262狀態那樣,過濾器不會直接改變它被調用的對象,但對象可能會被callbackfn.'的調用突變,所以推送到新的數組只會添加一個項目到新數組不是'quizContext [0] .questions'。一旦他重新分配,就會發生突變。 – mash