Redux 状态突变

Redux state mutation

为什么明明我传递的是一个新对象,但 React 却认为我正在改变状态?我错过了什么吗? 这是我在测验数组中的测验对象上添加问题的缩减器。

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)

您正在根据您的减速器改变状态。当您使用 filter 函数时,传递给过滤器的对象是原始对象的引用,您不是在此处执行 deepCopy。而这一行 filterQuestions.push(action.question) 实际上是在改变状态。

试试这个: let quizContext = cloneDeep(state.filter(quiz => quiz.id === action.quizId));

尽管可能有更优雅的解决方案,但这就是您出错的地方。返回的过滤对象是对原始数组的引用,而不是新对象。所以你最终改变了状态中的原始对象。

--更多说明--

cloneDeep 是一个 lodash 函数,如果你使用 Object.assign 它只会进行浅合并,而不是深度合并。因此,当状态在更深层次上发生变化时,通过改变原始状态,引用仍然是原始对象的引用

--编辑 2--

正如 mash 所指出的那样,改变你的状态的行是 quizContext[0].questions = filteredQuestions; 并使用 Object.assign 如果你确定你没有嵌套状态,在这种情况下更多的减速器是有序的

首先,既然你只关心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];
}

但请记住,大多数时候您根本不想在 reducer 中过滤数据。相反,我建议使用 reselect 以便您可以从商店中的数据计算派生数据。