redux 连接的组件如何知道何时重新渲染?

Posted

技术标签:

【中文标题】redux 连接的组件如何知道何时重新渲染?【英文标题】:How does a redux connected component know when to re-render? 【发布时间】:2017-03-16 03:07:41 【问题描述】:

我可能遗漏了一些非常明显的东西,并想澄清自己。

这是我的理解。 在一个幼稚的反应组件中,我们有statesprops。用setState 更新state 会重新渲染整个组件。 props 大多是只读的,更新它们没有意义。

在通过store.subscribe(render) 之类的方式订阅redux store 的react 组件中,它显然会在每次更新store 时重新渲染。

react-redux 有一个帮助器 connect() 将状态树的一部分(组件感兴趣)和 actionCreators 作为 props 注入到组件中,通常通过类似

const TodoListComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

但是了解setState对于TodoListComponent对redux状态树更改(重新渲染)做出反应是必不可少的,我在@中找不到任何statesetState相关代码987654336@ 组件文件。它的内容如下:

const TodoList = ( todos, onTodoClick ) => (
  <ul>
    todos.map(todo =>
      <Todo
        key=todo.id
        ...todo
        onClick=() => onTodoClick(todo.id)
      />
    )
  </ul>
)

有人可以指出我缺少什么的正确方向吗?

P.S 我正在关注与redux package 捆绑在一起的待办事项列表示例。

【问题讨论】:

【参考方案1】:

connect 函数生成一个订阅商店的包装器组件。当一个动作被调度时,包装组件的回调会被通知。然后它运行你的 mapState 函数,shallow-compares 这次的结果对象与上次的结果对象(所以如果你要重写 redux store具有相同值的字段,它不会触发重新渲染)。如果结果不同,则它将结果作为道具传递给您的“真实”组件。

Dan Abramov 在 (connect.js) 写了一个很棒的 connect 简化版本,说明了基本思想,尽管它没有显示任何优化工作。我还有一些关于Redux performance 的文章的链接,这些文章讨论了一些相关的想法。

更新

React-Redux v6.0.0 对连接组件从存储区接收数据的方式进行了一些重大的内部更改。

作为其中的一部分,我写了一篇文章,解释了connect API 及其内部的工作原理,以及它们如何随着时间的推移而发生变化:

Idiomatic Redux: The History and Implementation of React-Redux

【讨论】:

谢谢!您是前几天通过有用链接帮助我@HN - news.ycombinator.com/item?id=12307621 的同一个人吗?很高兴认识你:) 是的,就是我 :) 我花了太多时间在网上寻找有关 Redux 的讨论 - Reddit、HN、SO、Medium 和其他各种地方。乐意效劳! (仅供参考,我强烈推荐 Discord 上的 Reactiflux 聊天频道。很多人在那里闲逛并回答问题。我通常在美国东部标准时间晚上在那里在线。邀请链接位于 reactiflux.com。) 假设这回答了问题,介意接受答案吗? :) 是比较对象本身还是比较前后对象的属性?如果是后者,是不是只检查第一级,是不是你说的“浅”? 是的,“浅相等检查”意味着比较每个对象的第一级字段:prev.a === current.a &amp;&amp; prev.b === current.b &amp;&amp; .....。这假设任何数据更改都会导致新的引用,这意味着需要不可变的数据更新而不是直接突变。【参考方案2】:

我的回答有点离题。它揭示了导致我写这篇文章的一个问题。在我的情况下,应用似乎没有重新渲染,即使它收到了新的道具。 对于这个经常被问到的问题,React 开发人员有一个答案,如果 (store) 发生了变异,99% 的时间这就是 react 不会重新渲染的原因。 然而,剩下的 1% 却一无所获。突变不是这里的情况。


TLDR;

componentWillReceivePropsstate 与新的props 保持同步的方式。

极端情况:一旦state 更新,然后应用重新渲染!


事实证明,如果您的应用仅使用state 来显示其元素,props 可以更新,但state 不会,因此无需重新渲染。

我有 state 依赖于从 redux store 收到的 props。我需要的数据还没有在商店里,所以我从componentDidMount 获取它,这是正确的。当我的减速器更新存储时,我得到了道具,因为我的组件是通过 mapStateToProps 连接的。但是页面没有渲染,状态仍然是空字符串。

例如,用户从保存的 url 加载了“编辑帖子”页面。您可以从 url 访问 postId,但该信息尚未在 store 中,因此您可以获取它。您页面上的项目是受控组件 - 因此您显示的所有数据都在 state 中。

使用redux,获取了数据,更新了store,组件是connected,但是app没有反映变化。仔细一看,收到了props,但应用没有更新。 state 没有更新。

好吧,props 会更新和传播,但state 不会。 您需要专门告诉state 进行更新。

您不能在 render() 中执行此操作,并且 componentDidMount 已经完成了它的周期。

componentWillReceiveProps 是您更新依赖于更改的 prop 值的 state 属性的位置。

示例用法:

componentWillReceiveProps(nextProps)
  if (this.props.post.category !== nextProps.post.category)
    this.setState(
      title: nextProps.post.title,
      body: nextProps.post.body,
      category: nextProps.post.category,
    )
        

我必须为这篇文章大声疾呼,这篇文章启发了我解决其他数十篇文章、博客和存储库未提及的解决方案。其他任何无法找到这个明显晦涩问题的答案的人,这里是:

ReactJs component lifecycle methods — A deep dive

componentWillReceiveProps 是您更新state 以与props 更新保持同步的位置。

一旦state 更新,然后 字段取决于state 重新渲染!

【讨论】:

React 16.3 开始,您should 使用getDerivedStateFromProps 而不是componentWillReceiveProps。但事实上,你甚至不应该像here 那样做所有这些事情@ 它不起作用,每当状态发生变化时,componentWillReceiveProps 就会起作用,很多情况下我尝试这不起作用。特别是多级嵌套或复杂的道具,所以我必须为组件分配一个 ID 并为其触发更改并更新状态以重新渲染新数据【参考方案3】:

据我所知,redux 唯一的作用是,如果您的组件依赖于突变状态,则在更改商店状态时调用 componentWillRecieveProps,然后您应该强制您的组件更新 是这样的

1-store State change-2-call(componentWillRecieveProps(()=>3-component state change))

【讨论】:

【参考方案4】:

此答案是 Brian Vaughn 题为 You Probably Don't Need Derived State(2018 年 6 月 7 日)的文章的摘要。

从 props 派生状态是所有形式的反模式。包括使用旧的componentWillReceiveProps 和新的getDerivedStateFromProps

考虑以下解决方案,而不是从 props 派生状态。

两个最佳实践建议

建议 1. 完全受控的组件
function EmailInput(props) 
  return <input onChange=props.onChange value=props.email />;

建议 2. 带有密钥的完全不受控制的组件
// parent class
class EmailInput extends Component 
  state =  email: this.props.defaultEmail ;

  handleChange = event => 
    this.setState( email: event.target.value );
  ;

  render() 
    return <input onChange=this.handleChange value=this.state.email />;
  


// child instance
<EmailInput
  defaultEmail=this.props.user.email
  key=this.props.user.id
/>

如果出于某种原因,建议对您的情况不起作用,则有两种选择。

备选方案 1:使用 ID 属性重置不受控制的组件
class EmailInput extends Component 
  state = 
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  ;

  static getDerivedStateFromProps(props, state) 
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) 
      return 
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      ;
    
    return null;
  

  // ...

备选方案 2:使用实例方法重置不受控制的组件
class EmailInput extends Component 
  state = 
    email: this.props.defaultEmail
  ;

  resetEmailForNewUser(newEmail) 
    this.setState( email: newEmail );
  

  // ...

【讨论】:

以上是关于redux 连接的组件如何知道何时重新渲染?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 redux 让根组件在 react-native 中重新渲染(开源项目)

ReactJS表单应用程序:如何阻止父组件重新渲染? /使用redux共享状态的正确方法

React/Redux 存储更新但组件未重新渲染

如何在 redux 状态更新时自动刷新组件

反应父组件不重新渲染

子组件不会重新渲染,但父组件会重新渲染。如何让子组件重新渲染?