在 redux 中,为什么我们必须在 reducer 中保持状态不可变?
In redux, why do we have to keep the state immutable in reducers?
我想知道为什么我们应该在 reducer 中保持状态不可变。这是强制性的还是仅仅是推荐的方法? redux 是否利用不变性来做一些优化?
经过一些实验,我发现 redux 确实利用了这种不可变性的东西。一个证据是,当我们 return 从 reducer 对状态的相同引用时, UI 没有得到应有的更新,即使状态中的数据已经改变。
如下例所示:
const reducer = (state = [], action) => {
switch(action.type) {
case 'BRING_UP_NEXT_IMAGE':
state.push(state.shift());
break;
default:
return state;
}
return state;
};
UI 不会更新,因为 Redux 检测到相同的状态引用(对于它的价值)。
这解释了如果我们想要更新 UI,我们必须 return 对状态的新引用的必要性。
但是为什么 Redux 要求内部状态结构也应该是不可变的呢?据我所知, UI 是否更新完全取决于状态引用本身是否发生了变化。即使我改变了内部结构,应用程序也能正常工作,如:
const reducer = (state = [], action) => {
switch(action.type) {
case 'BRING_UP_NEXT_IMAGE':
state = state.concat(state.shift());
break;
default:
return state;
}
return state;
};
是的,我确实用 "state.shift()" 改变了以前的状态,但是由于 state.concat return 是对数组的新引用,状态本身是一个不同的引用,应用程序可以正常工作通常。
简而言之,我想知道状态是否应该严格保持不可变是使Redux正常运行的必要条件,或者是推荐选项,以便应用程序可以通过启用浅比较来获得更好的性能shouldComponentUpdate 绕过协调过程?
在 React 生命周期方法 componentWillReceiveProps
、shouldComponentUpdate
和 componentWillUpdate
中,如果你改变状态 nextProps 和 this.props 将是相同的。它会产生难以调试的问题。
如果您假设 "redux functions correctly" 当组件重新呈现它们所订阅的商店更改时,那么它只有在您 return 您的状态的新副本时才能正常运行。
redux 的连接方法已经实现了一个默认的 shouldComponentUpdate,它确实包括身份检查。
如果您直接修改状态而不创建新身份,您不仅会修改新状态,还会修改旧状态,因为两者都指向存储上的相同引用。具有相同的引用将导致 this.props.somePropFromStore === this.nextProps.somePropFromStore 始终为真。这将导致 redux 不再检测状态更改,除非您实现自定义行为。
所以我想说它不仅仅是 "recommended option",因为如果你绕过 redux 的基本原则 "never mutate data" 你可以用一个全局的类似存储的变量替换 redux 存储引擎保留所有信息。
redux 文档中也有一节解释它:http://redux.js.org/docs/faq/ReactRedux.html#why-isnt-my-component-re-rendering-or-my-mapstatetoprops-running
我在我的博客 post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent 中对此进行了深入讨论。引用摘要部分:
The core Redux createStore
function itself puts only two limitations on how you must write your code: actions must be plain objects, and they must contain a defined type
field. It does not care about immutability, serializability, or side effects, or what the value of the type
field actually is.
That said, the commonly used pieces around that core, including the Redux DevTools, React-Redux, React, and Reselect, do rely on proper use of immutability, serializable actions/state, and pure reducer functions. The main application logic may work okay if these expectations are ignored, but it's very likely that time-travel debugging and component re-rendering will break. These also will affect any other persistence-related use cases as well.
It's also important to note that immutability, serializability, and pure functions are not enforced in any way by Redux. It's entirely possible for a reducer function to mutate its state or trigger an AJAX call. It's entirely possible for any other part of the application to call getState()
and modify the contents of the state tree directly. It's entirely possible to put promises, functions, Symbols, class instances, or other non-serializable values into actions or the state tree. You are not supposed to do any of those things, but it's possible.
关于 Redux 如何以及为什么依赖不变性的细节也在 Immutable Data FAQ section.
的 Redux 文档中讨论。
我想知道为什么我们应该在 reducer 中保持状态不可变。这是强制性的还是仅仅是推荐的方法? redux 是否利用不变性来做一些优化?
经过一些实验,我发现 redux 确实利用了这种不可变性的东西。一个证据是,当我们 return 从 reducer 对状态的相同引用时, UI 没有得到应有的更新,即使状态中的数据已经改变。 如下例所示:
const reducer = (state = [], action) => {
switch(action.type) {
case 'BRING_UP_NEXT_IMAGE':
state.push(state.shift());
break;
default:
return state;
}
return state;
};
UI 不会更新,因为 Redux 检测到相同的状态引用(对于它的价值)。
这解释了如果我们想要更新 UI,我们必须 return 对状态的新引用的必要性。 但是为什么 Redux 要求内部状态结构也应该是不可变的呢?据我所知, UI 是否更新完全取决于状态引用本身是否发生了变化。即使我改变了内部结构,应用程序也能正常工作,如:
const reducer = (state = [], action) => {
switch(action.type) {
case 'BRING_UP_NEXT_IMAGE':
state = state.concat(state.shift());
break;
default:
return state;
}
return state;
};
是的,我确实用 "state.shift()" 改变了以前的状态,但是由于 state.concat return 是对数组的新引用,状态本身是一个不同的引用,应用程序可以正常工作通常。
简而言之,我想知道状态是否应该严格保持不可变是使Redux正常运行的必要条件,或者是推荐选项,以便应用程序可以通过启用浅比较来获得更好的性能shouldComponentUpdate 绕过协调过程?
在 React 生命周期方法 componentWillReceiveProps
、shouldComponentUpdate
和 componentWillUpdate
中,如果你改变状态 nextProps 和 this.props 将是相同的。它会产生难以调试的问题。
如果您假设 "redux functions correctly" 当组件重新呈现它们所订阅的商店更改时,那么它只有在您 return 您的状态的新副本时才能正常运行。
redux 的连接方法已经实现了一个默认的 shouldComponentUpdate,它确实包括身份检查。
如果您直接修改状态而不创建新身份,您不仅会修改新状态,还会修改旧状态,因为两者都指向存储上的相同引用。具有相同的引用将导致 this.props.somePropFromStore === this.nextProps.somePropFromStore 始终为真。这将导致 redux 不再检测状态更改,除非您实现自定义行为。
所以我想说它不仅仅是 "recommended option",因为如果你绕过 redux 的基本原则 "never mutate data" 你可以用一个全局的类似存储的变量替换 redux 存储引擎保留所有信息。
redux 文档中也有一节解释它:http://redux.js.org/docs/faq/ReactRedux.html#why-isnt-my-component-re-rendering-or-my-mapstatetoprops-running
我在我的博客 post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent 中对此进行了深入讨论。引用摘要部分:
The core Redux
createStore
function itself puts only two limitations on how you must write your code: actions must be plain objects, and they must contain a definedtype
field. It does not care about immutability, serializability, or side effects, or what the value of thetype
field actually is.That said, the commonly used pieces around that core, including the Redux DevTools, React-Redux, React, and Reselect, do rely on proper use of immutability, serializable actions/state, and pure reducer functions. The main application logic may work okay if these expectations are ignored, but it's very likely that time-travel debugging and component re-rendering will break. These also will affect any other persistence-related use cases as well.
It's also important to note that immutability, serializability, and pure functions are not enforced in any way by Redux. It's entirely possible for a reducer function to mutate its state or trigger an AJAX call. It's entirely possible for any other part of the application to call
getState()
and modify the contents of the state tree directly. It's entirely possible to put promises, functions, Symbols, class instances, or other non-serializable values into actions or the state tree. You are not supposed to do any of those things, but it's possible.
关于 Redux 如何以及为什么依赖不变性的细节也在 Immutable Data FAQ section.
的 Redux 文档中讨论。