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

How does a redux connected component know when to re-render?

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

这是我的理解。
在一个简单的 React 组件中,我们有 states & props。使用 setState 更新 state 会重新呈现整个组件。 props 大部分是只读的,更新它们没有意义。

在订阅 redux 存储的 React 组件中,通过类似 store.subscribe(render),它显然会在每次更新存储时重新呈现。

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

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

但是据了解 setState 对于 TodoListComponent 对 redux 状态树更改(重新渲染)做出反应至关重要,我找不到任何 statesetState相关代码在TodoList组件文件中。它是这样写的:

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

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

P.S 我正在关注与 redux package.

捆绑在一起的待办事项列表示例

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

Dan Abramov 在 (connect.js) that illustrates the basic idea, although it doesn't show any of the optimization work. I also have links to a number of articles on Redux performance 上写了一个很棒的 connect 简化版本,讨论了一些相关的想法。

更新

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

作为其中的一部分,我写了一篇 post 来解释 connect API 及其内部结构如何工作,以及它们如何随时间发生变化:

Idiomatic Redux: The History and Implementation of React-Redux

我的回答有点偏题。它阐明了导致我出现此 post 的问题。在我的例子中,应用程序似乎没有重新渲染,即使它收到了新的道具。
React 开发人员对这个经常被问到的问题给出了一个答案,即如果 (store) 发生了变化,99% 的情况下这就是 React 不会重新渲染的原因。 然而,与其他 1% 无关。突变不是这里的情况。


TLDR;

componentWillReceiveProps is how the state can be kept synced with the new props.

边缘情况:一旦 state 更新,然后 应用程序 重新渲染!


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

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

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

使用 redux,获取了数据,更新了存储,connect编辑了组件,但应用程序没有反映这些更改。仔细一看,收到了 props,但应用程序没有更新。 state 没有更新。

嗯,props 会更新和传播,但 state 不会。 您需要明确告诉 state 更新。

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

componentWillReceiveProps 是您更新 state 属性的地方,这些属性取决于已更改的 prop 值。

用法示例:

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

我必须对这篇文章大声疾呼,它启发了我许多其他 posts、博客和 repos 未能提及的解决方案。任何其他难以找到这个明显晦涩问题的答案的人,这里是:

ReactJs component lifecycle methods — A deep dive

componentWillReceiveProps is where you'll update state to keep in sync with props updates.

Once state updates, then fields depending on state do re-render !

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

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

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

从 props 派生状态是所有形式的 anti-pattern。包括使用较旧的 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 });
  }

  // ...
}