更新深层不可变状态属性时,Redux 不更新组件
Redux not updating components when deep Immutable state properties are updated
我的问题:为什么在我的不可变状态(映射)中更新数组中对象的 属性 不会导致 Redux 更新我的组件?
我正在尝试创建一个将文件上传到我的服务器的小部件,我的初始状态(来自我的 UploaderReducer 内部,您将在下面看到)对象如下所示:
let initState = Map({
files: List(),
displayMode: 'grid',
currentRequests: List()
});
我有一个 thunk 方法,可以在事件发生时(例如进度更新)开始上传和分派操作。例如,onProgress 事件如下所示:
onProgress: (data) => {
dispatch(fileUploadProgressUpdated({
index,
progress: data.percentage
}));
}
我正在使用 redux-actions
来创建和处理我的操作,因此我的该操作的 reducer 如下所示:
export default UploaderReducer = handleActions({
// Other actions...
FILE_UPLOAD_PROGRESS_UPDATED: (state, { payload }) => (
updateFilePropsAtIndex(
state,
payload.index,
{
status: FILE_UPLOAD_PROGRESS_UPDATED,
progress: payload.progress
}
)
)
}, initState);
而 updateFilePropsAtIndex
看起来像:
export function updateFilePropsAtIndex (state, index, fileProps) {
return state.updateIn(['files', index], file => {
try {
for (let prop in fileProps) {
if (fileProps.hasOwnProperty(prop)) {
if (Map.isMap(file)) {
file = file.set(prop, fileProps[prop]);
} else {
file[prop] = fileProps[prop];
}
}
}
} catch (e) {
console.error(e);
return file;
}
return file;
});
}
到目前为止,这一切似乎 工作正常!在 Redux DevTools 中,它显示为预期的操作。但是,none 我的组件更新了!将新项目添加到 files
数组会重新呈现我的 UI 添加的新文件,所以 Redux 对我这样做肯定没有问题...
我使用 connect
连接到商店的顶级组件如下所示:
const mapStateToProps = function (state) {
let uploadReducer = state.get('UploaderReducer');
let props = {
files: uploadReducer.get('files'),
displayMode: uploadReducer.get('displayMode'),
uploadsInProgress: uploadReducer.get('currentRequests').size > 0
};
return props;
};
class UploaderContainer extends Component {
constructor (props, context) {
super(props, context);
// Constructor things!
}
// Some events n stuff...
render(){
return (
<div>
<UploadWidget
//other props
files={this.props.files} />
</div>
);
}
}
export default connect(mapStateToProps, uploadActions)(UploaderContainer);
uploadActions
是一个具有使用 redux-actions
创建的操作的对象。
files
数组中的一个file
对象基本上是这样的:
{
name: '',
progress: 0,
status
}
UploadWidget
基本上是拖放 div 和 files
数组打印在屏幕上。
我尝试使用 redux-immutablejs
来提供帮助,正如我在 GitHub 上的许多帖子中看到的那样,但我不知道它是否有帮助...这是我的根减速器:
import { combineReducers } from 'redux-immutablejs';
import { routeReducer as router } from 'redux-simple-router';
import UploaderReducer from './modules/UploaderReducer';
export default combineReducers({
UploaderReducer,
router
});
我的应用程序入口点如下所示:
const store = configureStore(Map({}));
syncReduxAndRouter(history, store, (state) => {
return state.get('router');
});
// Render the React application to the DOM
ReactDOM.render(
<Root history={history} routes={routes} store={store}/>,
document.getElementById('root')
);
最后,我的 <Root/>
组件如下所示:
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router';
export default class Root extends React.Component {
static propTypes = {
history: PropTypes.object.isRequired,
routes: PropTypes.element.isRequired,
store: PropTypes.object.isRequired
};
get content () {
return (
<Router history={this.props.history}>
{this.props.routes}
</Router>
);
}
//Prep devTools, etc...
render () {
return (
<Provider store={this.props.store}>
<div style={{ height: '100%' }}>
{this.content}
{this.devTools}
</div>
</Provider>
);
}
}
因此,最终,如果我尝试更新以下状态对象中的 'progress',React/Redux 不会更新我的组件:
{
UploaderReducer: {
files: [{progress: 0}]
}
}
这是为什么?我认为使用 Immutable.js 的整个想法是比较修改后的对象更容易,无论您更新它们的深度如何?
似乎通常让 Immutable 与 Redux 一起工作并不像看起来那么简单:
https://github.com/reactjs/redux/issues/548
然而,使用 Immutable 的吹捧好处似乎值得这场战斗,我很想弄清楚我做错了什么!
2016 年 4 月 10 日更新
所选答案告诉我我做错了什么,为了完整起见,我的 updateFilePropsAtIndex
函数现在仅包含以下内容:
return state.updateIn(['files', index], file =>
Object.assign({}, file, fileProps)
);
这非常有效! :)
首先有两个一般的想法:
- Immutable.js 可能 有用,是的,但是您可以在不使用它的情况下完成相同的数据不可变处理。有许多库可以帮助使不可变数据更新更易于阅读,但仍然对普通对象和数组进行操作。我在我的 Redux 相关库 repo 的 Immutable Data 页面上列出了其中的许多。
- 如果 React 组件似乎没有更新,那几乎总是因为 reducer 实际上在改变数据。 Redux 常见问题解答在 http://redux.js.org/docs/FAQ.html#react-not-rerendering.
处有关于该主题的答案
现在,鉴于您 正在 使用 Immutable.js,我承认数据突变似乎不太可能。那就是说......你的减速器中的 file[prop] = fileProps[prop]
行看起来非常奇怪。你到底期望在那里发生什么?我会好好看看那部分。
实际上,现在我看到它...我几乎 100% 确定您正在改变数据。您对 state.updateIn(['files', index])
的更新程序回调是 returning 您作为参数获得的完全相同的文件对象。根据 https://facebook.github.io/immutable-js/docs/#/Map 上的 Immutable.js 文档:
If the updater function returns the same value it was called with, then no change will occur. This is still true if notSetValue is provided.
是的。你 return 编辑了你被赋予的相同值,你对它的直接突变显示在 DevTools 中,因为该对象仍然存在,但由于你 return 编辑了同一个对象 Immutable.js 实际上并没有 return 任何修改过的对象在层次结构的更上层。因此,当 Redux 检查顶级对象时,它发现没有任何变化,不会通知订阅者,因此您的组件的 mapStateToProps
永远不会运行。
清理你的 reducer 和 return 更新器内部的一个新对象,它 应该 一切正常。
(一个相当迟来的答案,但我现在才看到这个问题,它似乎仍然是开放的。希望你现在真的已经解决了它......)
我的问题:为什么在我的不可变状态(映射)中更新数组中对象的 属性 不会导致 Redux 更新我的组件?
我正在尝试创建一个将文件上传到我的服务器的小部件,我的初始状态(来自我的 UploaderReducer 内部,您将在下面看到)对象如下所示:
let initState = Map({
files: List(),
displayMode: 'grid',
currentRequests: List()
});
我有一个 thunk 方法,可以在事件发生时(例如进度更新)开始上传和分派操作。例如,onProgress 事件如下所示:
onProgress: (data) => {
dispatch(fileUploadProgressUpdated({
index,
progress: data.percentage
}));
}
我正在使用 redux-actions
来创建和处理我的操作,因此我的该操作的 reducer 如下所示:
export default UploaderReducer = handleActions({
// Other actions...
FILE_UPLOAD_PROGRESS_UPDATED: (state, { payload }) => (
updateFilePropsAtIndex(
state,
payload.index,
{
status: FILE_UPLOAD_PROGRESS_UPDATED,
progress: payload.progress
}
)
)
}, initState);
而 updateFilePropsAtIndex
看起来像:
export function updateFilePropsAtIndex (state, index, fileProps) {
return state.updateIn(['files', index], file => {
try {
for (let prop in fileProps) {
if (fileProps.hasOwnProperty(prop)) {
if (Map.isMap(file)) {
file = file.set(prop, fileProps[prop]);
} else {
file[prop] = fileProps[prop];
}
}
}
} catch (e) {
console.error(e);
return file;
}
return file;
});
}
到目前为止,这一切似乎 工作正常!在 Redux DevTools 中,它显示为预期的操作。但是,none 我的组件更新了!将新项目添加到 files
数组会重新呈现我的 UI 添加的新文件,所以 Redux 对我这样做肯定没有问题...
我使用 connect
连接到商店的顶级组件如下所示:
const mapStateToProps = function (state) {
let uploadReducer = state.get('UploaderReducer');
let props = {
files: uploadReducer.get('files'),
displayMode: uploadReducer.get('displayMode'),
uploadsInProgress: uploadReducer.get('currentRequests').size > 0
};
return props;
};
class UploaderContainer extends Component {
constructor (props, context) {
super(props, context);
// Constructor things!
}
// Some events n stuff...
render(){
return (
<div>
<UploadWidget
//other props
files={this.props.files} />
</div>
);
}
}
export default connect(mapStateToProps, uploadActions)(UploaderContainer);
uploadActions
是一个具有使用 redux-actions
创建的操作的对象。
files
数组中的一个file
对象基本上是这样的:
{
name: '',
progress: 0,
status
}
UploadWidget
基本上是拖放 div 和 files
数组打印在屏幕上。
我尝试使用 redux-immutablejs
来提供帮助,正如我在 GitHub 上的许多帖子中看到的那样,但我不知道它是否有帮助...这是我的根减速器:
import { combineReducers } from 'redux-immutablejs';
import { routeReducer as router } from 'redux-simple-router';
import UploaderReducer from './modules/UploaderReducer';
export default combineReducers({
UploaderReducer,
router
});
我的应用程序入口点如下所示:
const store = configureStore(Map({}));
syncReduxAndRouter(history, store, (state) => {
return state.get('router');
});
// Render the React application to the DOM
ReactDOM.render(
<Root history={history} routes={routes} store={store}/>,
document.getElementById('root')
);
最后,我的 <Root/>
组件如下所示:
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router';
export default class Root extends React.Component {
static propTypes = {
history: PropTypes.object.isRequired,
routes: PropTypes.element.isRequired,
store: PropTypes.object.isRequired
};
get content () {
return (
<Router history={this.props.history}>
{this.props.routes}
</Router>
);
}
//Prep devTools, etc...
render () {
return (
<Provider store={this.props.store}>
<div style={{ height: '100%' }}>
{this.content}
{this.devTools}
</div>
</Provider>
);
}
}
因此,最终,如果我尝试更新以下状态对象中的 'progress',React/Redux 不会更新我的组件:
{
UploaderReducer: {
files: [{progress: 0}]
}
}
这是为什么?我认为使用 Immutable.js 的整个想法是比较修改后的对象更容易,无论您更新它们的深度如何?
似乎通常让 Immutable 与 Redux 一起工作并不像看起来那么简单:
然而,使用 Immutable 的吹捧好处似乎值得这场战斗,我很想弄清楚我做错了什么!
2016 年 4 月 10 日更新
所选答案告诉我我做错了什么,为了完整起见,我的 updateFilePropsAtIndex
函数现在仅包含以下内容:
return state.updateIn(['files', index], file =>
Object.assign({}, file, fileProps)
);
这非常有效! :)
首先有两个一般的想法:
- Immutable.js 可能 有用,是的,但是您可以在不使用它的情况下完成相同的数据不可变处理。有许多库可以帮助使不可变数据更新更易于阅读,但仍然对普通对象和数组进行操作。我在我的 Redux 相关库 repo 的 Immutable Data 页面上列出了其中的许多。
- 如果 React 组件似乎没有更新,那几乎总是因为 reducer 实际上在改变数据。 Redux 常见问题解答在 http://redux.js.org/docs/FAQ.html#react-not-rerendering. 处有关于该主题的答案
现在,鉴于您 正在 使用 Immutable.js,我承认数据突变似乎不太可能。那就是说......你的减速器中的 file[prop] = fileProps[prop]
行看起来非常奇怪。你到底期望在那里发生什么?我会好好看看那部分。
实际上,现在我看到它...我几乎 100% 确定您正在改变数据。您对 state.updateIn(['files', index])
的更新程序回调是 returning 您作为参数获得的完全相同的文件对象。根据 https://facebook.github.io/immutable-js/docs/#/Map 上的 Immutable.js 文档:
If the updater function returns the same value it was called with, then no change will occur. This is still true if notSetValue is provided.
是的。你 return 编辑了你被赋予的相同值,你对它的直接突变显示在 DevTools 中,因为该对象仍然存在,但由于你 return 编辑了同一个对象 Immutable.js 实际上并没有 return 任何修改过的对象在层次结构的更上层。因此,当 Redux 检查顶级对象时,它发现没有任何变化,不会通知订阅者,因此您的组件的 mapStateToProps
永远不会运行。
清理你的 reducer 和 return 更新器内部的一个新对象,它 应该 一切正常。
(一个相当迟来的答案,但我现在才看到这个问题,它似乎仍然是开放的。希望你现在真的已经解决了它......)