更新深度不可变状态属性时,Redux不更新组件

Chr*_*ton 7 javascript reactjs immutable.js redux

我的问题:为什么不在我的不可变状态(Map)中更新数组中对象的属性不会导致Redux更新我的组件?

我正在尝试创建一个将文件上传到我的服务器的小部件,我的初始状态(从我将在下面看到的UploaderReducer内部)对象看起来像这样:

let initState = Map({
  files: List(),
  displayMode: 'grid',
  currentRequests: List()
});
Run Code Online (Sandbox Code Playgroud)

我有一个thunk方法,可以在事件发生时启动上传和调度操作(例如进度更新).例如,onProgress事件如下所示:

onProgress: (data) => {
    dispatch(fileUploadProgressUpdated({
      index,
      progress: data.percentage
    }));
  } 
Run Code Online (Sandbox Code Playgroud)

我正在使用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);
Run Code Online (Sandbox Code Playgroud)

而且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;
  });
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,这一切似乎都运行良好!在Redux DevTools中,它显示为预期的动作.但是,我的组件都没有更新!向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);  
Run Code Online (Sandbox Code Playgroud)

uploadActions是使用创建的动作的对象redux-actions.

数组中的file对象files基本上是这样的:

{
    name: '',
    progress: 0,
    status
}
Run Code Online (Sandbox Code Playgroud)

UploadWidget基本上是一个拖累n个墨滴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
});
Run Code Online (Sandbox Code Playgroud)

我的app入口点如下所示:

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')
);
Run Code Online (Sandbox Code Playgroud)

最后,我的<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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,最终,如果我尝试更新以下状态对象中的"进度",则React/Redux不会更新我的组件:

 {
    UploaderReducer: {
        files: [{progress: 0}]
    }
 }
Run Code Online (Sandbox Code Playgroud)

为什么是这样?我认为使用Immutable.js的整个想法是,无论您更新它们有多深,都比较容易比较修改过的对象?

看起来一般使用Redux与Redux一起工作并不像看起来那么简单: 如何在REDx中使用Immutable.js? https://github.com/reactjs/redux/issues/548

然而,使用Immutable的吹捧好处似乎值得这场战斗,我很想知道我做错了什么!

更新2016年4月10日 选定的答案告诉我我做错了什么,为了完整起见,我的updateFilePropsAtIndex功能现在只包含这个:

return state.updateIn(['files', index], file =>
  Object.assign({}, file, fileProps)
);
Run Code Online (Sandbox Code Playgroud)

这非常有效!:)

mar*_*son 6

两个一般的想法第一:

  • Immutable.js 可能很有用,是的,但您可以在不使用它的情况下完成相同的不可变数据处理.有许多库可以帮助使不可变数据更新更容易阅读,但仍然可以在普通对象和数组上运行.我在Redux相关的库repo中的不可变数据页面上列出了很多.
  • 如果React组件似乎没有更新,那几乎总是因为reducer实际上是在改变数据.Redux FAQ在http://redux.js.org/docs/FAQ.html#react-not-rerendering上有关于该主题的答案.

现在,假设你正在使用Immutable.js,我承认数据的突变似乎有点不太可能.那说...... file[prop] = fileProps[prop]你的减速机中的线路似乎非常好奇.你究竟想要去那里的是什么?我会好好看看那一部分.

实际上,现在我看一下......我几乎100%肯定你在改变数据.您的updater回调state.updateIn(['files', index])函数将返回与参数相同的文件对象.根据https://facebook.github.io/immutable-js/docs/#/Map上的Immutable.js文档:

如果updater函数返回与调用的值相同的值,则不会发生任何更改.如果提供notSetValue,则仍然如此.

是的.你返回的是你给出的相同值,你的直接突变出现在DevTools中,因为那个对象仍然在闲逛,但是因为你返回了同一个对象,所以Immutable.js实际上并没有进一步返回任何修改过的对象层次结构.因此,当Redux检查顶级对象时,它看到没有任何更改,不通知订阅者,因此您的组件mapStateToProps永远不会运行.

清理你的减速器并从更新器内部返回一个新对象,它应该都可以正常工作.

(一个相当迟来的答案,但我刚刚看到了这个问题,它似乎仍然是开放的.希望你现在实际上已经解决了......)