当深度不可变状态属性更新时,Redux不更新组件 [英] Redux not updating components when deep Immutable state properties are updated

查看:55
本文介绍了当深度不可变状态属性更新时,Redux不更新组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

MY QUESTION: Why doesn't updating a property of an object in an array in my Immutable state (Map) not cause Redux to update my component?

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

I'm trying to create a widget that uploads files to my server, and my initial state (from inside my UploaderReducer which you will see below) object looks like this:

let initState = Map({
  files: List(),
  displayMode: 'grid',
  currentRequests: List()
});

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

I have a thunk method that starts uploads and dispatches actions when an event occurs (such as a progress update). For example, the onProgress event looks like this:

onProgress: (data) => {
    dispatch(fileUploadProgressUpdated({
      index,
      progress: data.percentage
    }));
  } 

我正在使用redux-actions来创建和处理我的动作,所以该动作的减速器如下所示:

I'm using redux-actions to create and handle my actions, so my reducer for that action looks like this:

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中,它显示为预期的操作.但是,我的组件都没有更新!将新项目添加到files数组会使用添加的新文件重新呈现我的UI,因此Redux肯定不会出现问题...

So far, this all seems to work fine! In Redux DevTools, it shows up as an action as expected. However, none of my components update! Adding new items to the files array re-renders my UI with the new files added, so Redux certainly doesn't have a problem with me doing that...

我使用connect连接到商店的顶级组件如下:

My top level component that connects to the store using connect looks like this:

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创建的动作的对象.

uploadActions is an object with actions created using redux-actions.

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

A file object in the files array is basically this:

{
    name: '',
    progress: 0,
    status
}

UploadWidget基本上是一个拖动n拖放div和一个在屏幕上打印出的files数组.

The UploadWidget is basically a drag n drop div and a the files array printed out on the screen.

我尝试使用redux-immutablejs来提供帮助,正如我在GitHub上的许多帖子中所看到的那样,但我不知道它是否有帮助...这是我的root reducer:

I tried using redux-immutablejs to help out as I've seen in many posts on GitHub, but I have no idea if it helps... This is my root reducer:

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

export default combineReducers({
  UploaderReducer,
  router
});

我的应用程序入口点如下:

My app entry point looks like this:

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/>组件如下所示:

Lastly, my <Root/> component looks like this:

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

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

So, ultimately, if I try to update a 'progress' in the following state object, React/Redux does not update my components:

 {
    UploaderReducer: {
        files: [{progress: 0}]
    }
 }

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

Why is this? I thought the whole idea of using Immutable.js was that it was easier to compare modified objects regardless of how deeply you update them?

似乎与Redux一起使用变得不可变似乎并不像看起来那么简单: 如何在Redux中使用Immutable.js? https://github.com/reactjs/redux/issues/548

It seems generally getting Immutable to work with Redux is not as simple as it seems: How to use Immutable.js with redux? https://github.com/reactjs/redux/issues/548

但是,使用Immutable所吹捧的好处似乎值得这场战斗,我很想知道自己在做错什么!

However, the touted benefits of using Immutable seem to be worth this battle and I'd LOVE to figure out what I'm doing wrong!

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

UPDATE April 10 2016 The selected answer told me what I was doing wrong and for the sake of completeness, my updateFilePropsAtIndex function now contains simply this:

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

这很好用! :)

推荐答案

首先有两个一般思想:

  • Immutable.js is potentially useful, yes, but you can accomplish the same immutable handling of data without using it. There's a number of libraries out there that can help make immutable data updates easier to read, but still operate on plain objects and arrays. I have many of them listed on the Immutable Data page in my Redux-related libraries repo.
  • If a React component does not appear to be updating, it's almost always because a reducer is actually mutating data. The Redux FAQ has an answer on that topic, at http://redux.js.org/docs/FAQ.html#react-not-rerendering.

现在,考虑到您正在使用Immutable.js,我承认数据的突变似乎不太可能.就是说...减速器中的file[prop] = fileProps[prop]行似乎非常好奇.您究竟期望在那发生什么?我会仔细看一下.

Now, given that you are using Immutable.js, I'll admit that mutation of data seems a bit unlikely. That said... the file[prop] = fileProps[prop] line in your reducer does seem awfully curious. What exactly are you expecting to be going on there? I'd take a good look at that part.

实际上,现在我来看一下...我几乎100%确信您正在变异数据.您对state.updateIn(['files', index])的更新程序回调返回与参数完全相同的文件对象.根据 https://facebook.github.io/immutable- js/docs/#/Map :

Actually, now that I look at it... I am almost 100% certain that you are mutating data. Your updater callback to state.updateIn(['files', index]) is returning the exact same file object you got as a parameter. Per the Immutable.js docs at https://facebook.github.io/immutable-js/docs/#/Map:

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

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.

是的.您将返回给定的相同值,由于该对象仍在周围徘徊,因此在DevTools中将显示对其的直接突变,但是由于您返回了相同的对象,因此Immutable.js实际上并没有进一步返回任何修改过的对象层次结构.因此,当Redux对顶级对象进行检查时,它看不到任何变化,不会通知订阅者,因此组件的mapStateToProps永远不会运行.

So yeah. You're returning the same value you were given, your direct mutations to it are showing up in the DevTools because that object is still hanging around, but since you returned the same object Immutable.js isn't actually returning any modified objects further up the hierarchy. So, when Redux does a check on the top-level object, it sees nothing has changed, doesn't notify subscribers, and therefore your component's mapStateToProps never runs.

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

Clean up your reducer and return a new object from inside that updater, and it should all just work.

(一个相当迟来的答案,但是我刚才看到了这个问题,而且它似乎仍然是未解决的.希望您现在确实已经解决了该问题...)

(A rather belated answer, but I just now saw the question, and it appears to still be open. Hopefully you actually got it fixed by now...)

这篇关于当深度不可变状态属性更新时,Redux不更新组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆