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

Posted

技术标签:

【中文标题】更新深层不可变状态属性时,Redux 不更新组件【英文标题】:Redux not updating components when deep Immutable state properties are updated 【发布时间】:2016-06-11 03:47:43 【问题描述】:

我的问题:为什么在我的不可变状态(映射)中更新数组中对象的属性不会导致 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 中,它按预期显示为操作。但是,我的组件都没有更新!将新项目添加到 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')
);

最后,我的&lt;Root/&gt; 组件如下所示:

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 不会更新我的组件:

 
    UploaderReducer: 
        files: [progress: 0]
    
 

这是为什么?我认为使用 Immutable.js 的整个想法是更容易比较修改后的对象,无论您更新它们的深度如何?

似乎通常让 Immutable 与 Redux 一起工作并不像看起来那么简单: How to use Immutable.js with redux? https://github.com/reactjs/redux/issues/548

但是,使用 Immutable 的吹捧好处似乎值得这场战斗,我很想弄清楚我做错了什么!

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

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

这很好用! :)

【问题讨论】:

当你说“组件没有被更新”时,你的意思是它根本没有从 redux 接收新的 props 吗? mapStateToProps 是否没有收到更新的进度?那么reducer呢,action在payload中的进度是否正确? UploaderContainer mapStateToProps 函数根本不会触发,尽管在 DevTools 中看到状态已更新为预期值。我有单元测试检查我正在做的事情是否应该工作,所以我觉得在 Redux 级别有一些我不理解的东西,但我不知道它是什么! 你能在 JSBin 上获得一个在线工作示例吗?解决这个问题会更容易。 您是否在UploaderReducer files List 中混合了不可变映射和普通对象?调度操作时是否调用了 updateFilePropsAtIndex 函数? 【参考方案1】:

首先有两个一般想法:

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]) 的更新程序回调将返回与您作为参数获得的完全相同的文件对象。根据 https://facebook.github.io/immutable-js/docs/#/Map 的 Immutable.js 文档:

如果更新程序函数返回与调用它相同的值,则不会发生任何更改。如果提供了 notSetValue,这仍然是正确的。

是的。您返回的值与您获得的相同,您对它的直接突变显示在 DevTools 中,因为该对象仍然存在,但由于您返回了相同的对象 Immutable.js 实际上并没有进一步返回任何修改过的对象层次结构。因此,当 Redux 对***对象进行检查时,它发现没有任何变化,也没有通知订阅者,因此您的组件的 mapStateToProps 永远不会运行。

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

(一个相当晚的答案,但我刚刚看到了这个问题,而且它似乎仍然是开放的。希望你现在真的把它修好了......)

【讨论】:

嘿,抱歉,我没有对此作出回应。我目前正在升级 React/Redux 和其他模块。这个周末我会试着做这个,然后回复你。感谢您的详细回答! 我开始实施您指出的更改,它解决了所有问题。谢谢你的帮助! :) @ChrisPaton 你能分享你的相关更改吗? @Sag1v 如果您查看我的问题末尾的更新,我将我的最终更改发布到 updateFilePropsAtIndex。这就是我必须更改的所有内容,并且效果很好! @ChrisPaton updatein() 是 immutablejs 函数对吗?在没有它的情况下不改变这种状态就很难更新吗?

以上是关于更新深层不可变状态属性时,Redux 不更新组件的主要内容,如果未能解决你的问题,请参考以下文章

当 redux 状态更新时,React 组件不更新

javascript 更新状态React Redux不可变

更新 Redux 存储时反应组件不重新渲染

我想以不可变的方式更新状态对象,以便react和redux正常工作?

Redux/Firebase - 在状态中收到 uid 后组件不更新

动作调度不更新 Redux/React 中的状态