2016-07-23 3 views
1

왜 내가 반응을 보이면 분명히 새로운 객체를 전달할 때 상태를 변경한다고 생각합니까? 내가 놓친 게 있니? 이것은 퀴즈 배열 내의 퀴즈 개체에 질문을 추가하는 것을 줄이는 방법입니다.Redux state mutation

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

첫 번째에있는 경우 중첩 된 상태가없는 확신하는 경우 Object.assign를 사용는 filter은 과잉이다. 그냥 할 :

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

동일한 결과를 얻을 수 있습니다.

너무 많은 코드 리뷰를하고 싶지는 않지만 한 가지 더 있습니다. 항상 const을 사용해야한다고해도 let을 사용합니다. const은 변수를 재 지정할 수 없다는 것을 의미합니다.

그런 다음 질문을 필터링하여 추가 한 다음 방금 찾은 퀴즈에 다시 추가하십시오.

quizContext[0].questions =은 개체가 새 배열에 있어도 여전히 동일한 개체이므로 변경 한 개체를 변경하면 상태가 변경 될 수 있기 때문에 문제가됩니다.

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

그래서이 같은 끝낼 것 완전한 것은 재 작성 : 대부분의 시간은 당신이 데이터를 필터링 할 wan't하지 않는 것이

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]; 
} 

그러나 명심 당신의 감속기. 대신 reselect을 사용하여 상점의 데이터에서 파생 데이터를 계산할 수 있도록하는 것이 좋습니다.

+0

'Object.assign'는 얕은 병합을 수행합니다. 상태가 중첩 된 구조를 가졌을 때 첫 번째 레벨을 넘어선 참조가 실제로 이전 상태를 참조하므로 추가 변이가 발생할 수 있습니다. – sasidhar

+0

@sasidhar이 경우 효과가 없을 것입니다. 깊게 중첩 된 구조를 가지고 있다면 redux에서 깊이를 처리하는 새로운 감속기를 추가하십시오. 다른 모든 것은 나쁜 습관 일 것입니다. – mash

0

당신은 당신의 감속기에 따라 상태를 돌연변이 있습니다. 필터 함수를 사용할 때 필터에 전달 된 객체는 원래 객체의 참조이며 여기에서는 deepCopy를 수행하지 않습니다. 그리고이 라인 filterQuestions.push(action.question)은 실제로 상태를 돌연변이시키고 있습니다.

대신보십시오 : 거기에 더 우아한 해결책이 될 수 let quizContext = cloneDeep(state.filter(quiz => quiz.id === action.quizId));

있지만, 잘못가는 곳입니다. 반환되는 필터링 된 객체는 원래 배열에 대한 참조이지만 새 객체는 참조되지 않습니다. 그래서 당신은 결국 그 주에있는 원래의 사물을 돌연변이시킵니다.

는 --More는

cloneDeep

가 lodash 기능이며 Object.assign를 사용하는 경우에만 얕은 병합을 할 것, 그러나 깊은 병합되어 clarification--. 상태가 더 깊은 수준에서 돌연변이 때 그래서 참조하여 상태가 변이되어 줄 매쉬에 의해 언급 된 바와 같이 원래의 상태

--edit 2--

을 돌연변이에 의해이 원래 개체의 아직도 quizContext[0].questions = filteredQuestions; 당신은 당신이 약 quizContext[0]을 걱정하기 때문에 당신이 더 감속기를 위해 모든

+0

푸시 자체가 상태를 변경하지 않습니다!당신이 이미이 경우에 주목 한 것처럼 그것은 기능이 아니며 좋은 연습 또는 적절한 것입니다. – mash

+0

@mash push 함수는 돌연변이를 일으키는 함수입니다. 아니? 그것이 가지고있는 참조는 원래 상태의 원래 객체로 들어가므로 푸시가 실제로 원래 객체로 푸시됩니다. 그래서 "푸시 자체가 상태를 변경시키지 않는다"고 말할 때 당신이 의미하는 바를 얻지 못했습니다. ? – sasidhar

+0

ECMA-262는 '필터가 호출 된 객체를 직접 변경하지 않지만 callbackfn을 호출하여 객체가 변형 될 수 있습니다.'라고 말하면서 그렇지 않습니다. 따라서 새로운 배열로 푸시하면 항목이 추가됩니다. 새로운 배열은'quizContext [0] .questions'가 아닙니다. 돌연변이는 재 지정되면 발생합니다. – mash

관련 문제