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

Javascript 更新深层不可变状态属性时,Redux不更新组件,javascript,reactjs,redux,immutable.js,Javascript,Reactjs,Redux,Immutable.js,我的问题:为什么在我的不可变状态(Map)下更新数组中对象的属性不会导致Redux更新我的组件 我正在尝试创建一个小部件,将文件上载到我的服务器,我的初始状态(从我的UploaderEducer中,您将在下面看到)对象如下所示: let initState = Map({ files: List(), displayMode: 'grid', currentRequests: List() }); onProgress: (data) => { dispatch(fi

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

我正在尝试创建一个小部件,将文件上载到我的服务器,我的初始状态(从我的UploaderEducer中,您将在下面看到)对象如下所示:

let initState = Map({
  files: List(),
  displayMode: 'grid',
  currentRequests: List()
});
onProgress: (data) => {
    dispatch(fileUploadProgressUpdated({
      index,
      progress: data.percentage
    }));
  } 
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);
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')
);
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>
    );
  }
}
我有一个thunk方法,它可以在事件发生时(例如进度更新)启动上载和分派操作。例如,onProgress事件如下所示:

let initState = Map({
  files: List(),
  displayMode: 'grid',
  currentRequests: List()
});
onProgress: (data) => {
    dispatch(fileUploadProgressUpdated({
      index,
      progress: data.percentage
    }));
  } 
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);
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')
);
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>
    );
  }
}
我正在使用
redux actions
来创建和处理我的操作,因此该操作的缩减器如下所示:

let initState = Map({
  files: List(),
  displayMode: 'grid',
  currentRequests: List()
});
onProgress: (data) => {
    dispatch(fileUploadProgressUpdated({
      index,
      progress: data.percentage
    }));
  } 
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);
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')
);
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>
    );
  }
}
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开发工具中,它显示为预期的操作。但是,我的任何组件都不会更新!将新项目添加到
文件
数组会使用添加的新文件重新呈现我的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);  
UploadWidget
基本上是一个拖放div和一个打印在屏幕上的
文件
数组

正如我在GitHub上的许多帖子中看到的那样,我尝试使用
redux immutablejs
来提供帮助,但我不知道它是否有帮助。。。这是我的根减速机:

import { combineReducers } from 'redux-immutablejs';
import { routeReducer as router } from 'redux-simple-router';
import UploaderReducer from './modules/UploaderReducer';

export default combineReducers({
  UploaderReducer,
  router
});
我的应用程序入口点如下所示:

let initState = Map({
  files: List(),
  displayMode: 'grid',
  currentRequests: List()
});
onProgress: (data) => {
    dispatch(fileUploadProgressUpdated({
      index,
      progress: data.percentage
    }));
  } 
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);
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')
);
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>
    );
  }
}
为什么会这样?我认为使用Immutable.js的整个想法是比较修改过的对象更容易,不管更新的深度如何

使用Redux似乎变得一成不变并不像看上去那么简单:

然而,使用Immutable的好处似乎值得一战,我很想知道我做错了什么

2016年4月10日更新 选择的答案告诉了我做错了什么,为了完整起见,我的
updateFilePropsAtIndex
函数现在只包含以下内容:

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

这很好用!:)

首先有两个总体思路:

  • 是的,Immutable.js可能很有用,但是您可以在不使用它的情况下完成相同的数据不变处理。有许多库可以帮助使不可变数据更新更易于读取,但仍然可以在普通对象和数组上操作。我在我的Redux相关库repo页面上列出了很多
  • 如果React组件看起来没有更新,几乎总是因为reducer实际上在修改数据。Redux常见问题解答对此主题有一个答案,位于
现在,考虑到您使用的是Immutable.js,我承认数据的变异似乎有点不太可能。也就是说。。。减速机中的
file[prop]=fileProps[prop]
行看起来非常奇怪。你到底希望那里发生什么事?我要好好看看那部分

事实上,现在我看到了。。。我几乎100%确定您正在修改数据。更新程序回调到
state.updateIn(['files',index])
返回的文件对象与作为参数得到的文件对象完全相同。根据Immutable.js文档,位于:

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

所以是的。您返回的值与给定的值相同,您对该值的直接变异会显示在DevTools中,因为该对象仍然挂起,但由于您返回了相同的对象Immutable.js,因此实际上不会返回层次结构上任何修改过的对象。因此,当Redux检查顶级对象时,它不会看到任何更改,也不会通知订阅者,因此组件的
MapStateTrops
永远不会运行

清理减速器并从更新程序中返回一个新对象,所有这些都应该可以正常工作


(这是一个迟来的回答,但我刚才看到了这个问题,它似乎仍然是开放的。希望你现在真的修复了它…

当你说“组件没有更新”时,你的意思是它根本没有收到redux的新道具吗?MapStateTops是否未收到更新的进度?减速器如何,操作在有效负载中的进度是否正确?UploaderContainer mapStateToProps函数根本不会启动,尽管在DevTools中看到状态使用预期值更新。我有单元测试来检查我正在做的工作是否有效,所以我觉得在Redux级别有一些我不理解的东西,但我不知道它是什么!你能在JSBin上在线获得一个工作示例吗?解决这个问题会更容易。您是否在
UploaderReducer
文件中混合了不可变映射和普通对象?分派操作时是否调用了您的
updateFilePropsAtIndex
函数?抱歉,我没有对此做出响应。我目前正在升级React/Redux和其他模块。这个周末我会试着去做,然后再打给你。谢谢你的详细回答!我抽出时间来实施你指给我的改变,它解决了所有问题。谢谢你的帮助!:)@ChrisPaton您能分享您的相关更改吗?@Sag1v如果您查看我问题末尾的更新,我将我的最终更改发布到updateFilePropsAtIndex。这就是我要改变的一切,而且效果很好@ChrisPaton
updatein()
immutablejs函数正确吗?要更新这种状态而不在没有它的情况下对其进行变异是否如此困难?