React: componentDidReceiveProps 请

创建于 2015-02-27  ·  94评论  ·  资料来源: facebook/react

我想谦虚地请求一个 componentDidReceiveProps 钩子,很多时候我想在 componentWillMount 和 componentWillReceiveProps 上做一些事情,但是因为this.props还没有设置,我被迫传递道具而不是直接从this.props

新钩前

componentWillMount() {
  this.setup(this.props.id);
}

componentWillReceiveProps(next) {
  this.setup(next.id);
}

setup(id) {
  UserActions.load(id);
}

新钩后

componentWillMount() {
  this.setup();
}

componentDidReceiveProps() {
  this.setup();
}

setup() {
  UserActions.load(this.props.id);
}

在这个简单的例子中,它可能看起来是一件小事,但很多时候 props 的传递很深入,而不是方便地引用 this.props,而是被迫在整个组件中探查 props。

请考虑添加componentDidReceiveProps作为钩子,以利用在 componentWillMount 中利用的相同代码,而无需强制两者都在整个组件中探测 props。

最有用的评论

@syranide问题是当设置需要调用也需要道具的方法时,它需要调用也需要道具的方法......最终你的整个组件都在道具周围。

所有94条评论

为什么不是setup(props)

查看我自己的项目,我可以看到我已经在我有类似需求的地方做了这个(另一个是基于 props 派生一大块状态),只是在需要时传入一组不同的 props,所以你只需要传递一些额外的东西重用时,无需重复了解需要哪些道具:

setup(props) {
  props = props || this.props
  UserActions.load(props.id)
}

@syranide问题是当设置需要调用也需要道具的方法时,它需要调用也需要道具的方法......最终你的整个组件都在道具周围。

+1 似乎在应用程序中使用当前模式进行了一堆不必要的接线。 可能是解决问题的简洁标准化方法。 看到很多人对 ComponentWillReceiveProps 中的 this.props 感到厌烦,这清楚地表明它不直观。

+1,我也觉得这令人沮丧。

不再认为这是一个问题。

+1,我也发现自己经常传递道具。 与上面的@insin类似,我使用了一段时间的默认参数:

setup(props = this.props) {
  doSomething(props);
}

但决定它是一个反模式,因为如果你忘记传递 newProps,它可能会导致微妙的错误。

+1

我认为它不可用的原因是因为this.propsthis.state _always_ 对应于渲染值。 没有办法实现componentDidReceiveProps在不破坏此约束的情况下不会触发另一个渲染。

大多数时候,如果你使用componentWillReceiveProps ,你实际上想要一个更高阶的组件a la Relay ,或者像observe为 React 0.14 提议的钩子

+1
此外,您可以componentDidReceiveProps不更改this.propsthis.state情况下实现componentDidReceiveProps this.state 。 如果您需要做的只是从这些中读取,那么您将不会触发另一个渲染。 如果你在这个提议的函数调用中写入 props 或 state,那么你就是在给自己开枪,但对于生命周期中的所有其他方法来说,情况都是一样的。

+1

我希望能够在shouldComponentUpdate返回false时响应新的 props 事件,所以在我的情况下我不能使用componentDidUpdate

+1

+1

componentWillMountcomponentWillUpdate @iammerrick 怎么样?

我希望能够在 shouldComponentUpdate 返回 false 时响应新的 props 事件,所以在我的情况下我不能使用 componentDidUpdate。

使用componentWillReceiveProps吗?

+1

+1 这有助于 DRY 代码和简化逻辑。

我愿意在初始设置时调用 componentDidReceiveProps 以使启动此线程的示例只是一个函数:

componentDidReceiveProps() {
  UserActions.load(this.props.id);
}

想法?

componentWillReceiveProps() {
  setTimeout(()=> {
    if(this.isMounted()) {
      this.componentDidReceiveProps();
    }
  });
}
componentDidReceiveProps() {
  UserActions.load(this.props.id);
}

你怎么认为?

@YourDeveloperFriend我认为这取决于它相对于其他生命周期挂钩的工作方式和由于嵌套组件上的级联超时而导致的渲染延迟。

这些类型的生命周期钩子应该在保证在渲染之前调用的传递中同步调用。 我没有研究过 React 的代码库,但我假设没有在超时时调用渲染。

最有可能的,最好的解决方案是注册预渲染钩子或标记已更改其 props 的组件,以便渲染逻辑在调用 render 之前调用 componentDidReceiveProps。

+1 请。 这么难看。

不,我很好。 有更好的解决方案。

+1

componentDidReceiveProps()已经存在:它被称为render() 。 但是,可能存在一种情况(如@iammerrick的示例),您需要在执行渲染之前加载/触发/等等。 这意味着一件事,也是唯一一件事:你做错了什么。

当你做这样的事情时

setup(id) {
    UserActions.load(id);
}

您在组件中或(更糟)外部引入了状态。 为什么每次组件收到 props 时都需要load()数据(它们甚至不能保证是新的)? 如果你做延迟加载,正确的方法是在render()方法中请求数据:

render() {
    var actions = UserActions.load(id);
    if (actions) // render
    else // show spinner or return null, etc.
}

UserActions.load = function(id) {
    if (data) return data;
    else // request the data, emit change on success
}

@robyoder ,将参数传递给函数并不难看。 它可能看起来过于冗长,但在您选择的编程语言中很自然。 通过添加备用生命周期方法来减少冗长的 API 确实很丑陋。

所以告诉我,@me-andre,为什么我们同时拥有componentWillUpdatecomponentWillReceiveProps

在我看来,这是因为它们服务于不同的目的,并且两者都有帮助。 就像这两个不一样, render与假设的componentDidReceiveProps不一样,因为它是出于新道具以外的原因调用的; 此外,您无权访问以前的道具。

前面的 props 的好处是你不必每次都加载数据; 它可以根据道具以及它们是否发生变化而有条件。

显然,“[丑陋] 在旁观者眼中”,因为添加适当的生命周期方法对我来说似乎是一个更清晰的解决方案。

也许有另一种方式来看待这个......

假设这里的主要目标是:

(1) 减少人为错误 - 我忘记传递参数或再次使用props = props || this.props
(2) 减少非增值样板代码——为什么要写一些不必要的东西
(3) 减少尝试决定使用哪种生命周期方法的认知负担 - 为什么我需要考虑这类事情,为什么我会根据当天的感受继续使用略有不同的方法,等等

所以也许解决方案空间是围绕着简化 React 的使用和 React API 本身来实现这些目标的。

如果你考虑到这些目标的问题,也许可以合并一些生命周期方法,如果你有兴趣知道它是初始化还是更新,你可以通过调用签名/参数来判断。 例如: beforeRender(prevProps, prevState)update(prevProps, prevState)

就个人而言,感觉生命周期方法太多了,如果更一致地调用它们(在初始传递和更新时不使用或使用 prevProps / prevState),它可以简化我的代码并提高我的生产力。 另外,生命周期方法名称很长(我倾向于复制/粘贴它们而不是键入它们)并且很难记住哪些将/确实存在,这让我认为它们可以/应该被简化。

@robyoder ,为什么我们同时拥有componentWillUpdatecomponentDidReceiveProps的简短答案是它们之间存在巨大差异。 它们当然是相似的,但主要是以componentWill为前缀。

| | 状态已更改 | 组件没有更新|
| --- | --- | --- |
| componentWillUpdate() | 是 | 没有|
| componentWillReceiveProps() | 没有| 是 |

正如您可能已经注意到的那样,存在生命周期点/条件,其中一种方法被调用而另一种方法没有。 这就是为什么我们有 2 个不同的生命周期方法。

但是,本主题中描述的componentDidReceiveProps()不代表任何组件状态或条件,并且它不提供对componentWillReceiveProps()不具有的任何访问权限。 它只是添加了一个轻微的语法糖,这可能有用,也可能看起来有用或更容易——这是个人喜好,而不是技术要求。 这正是不应该添加它的原因:它是主观的。 你说传递参数是丑陋的 - 但你也说“[丑陋]在旁观者的眼中”。 对我来说,你似乎已经回答了自己。

@me-andre 你可能会同时回复我 (@kmalakoff) 和@robyoder 。 或者也许不是,但我会借此机会推进讨论......

我所提出的是,也许通过以上三个目标来思考当前的 API 可以为人们如何简化 API 以实现这些目标提供新的见解或观点。 当然,经过练习之后,我们可能会在同一个地方结束。

让我们通过练习......

让我们假设这个主题正在被关注并被 +1 编辑,因为它有一些重要的东西,假设添加componentDidReceiveProps不是一个好主意,因为它增加了 API 表面积。

牢记我提出的表格和 3 个目标,有没有人有任何想法来简化 API 和/或另一种方式来给这个线程中的人他们想要的东西而不是扩展 API(甚至可能减少/简化它) )?

@kmalakoff ,您所说的 3 点仅与 API 相关,因为您在遇到它们时会使用 API。 它们不是由糟糕的设计引起的。

您谈论的第一个问题是生命周期方法采用不同的参数。 嗯,这很自然——它们有不同的用途。 componentWillReceiveProps获取 props 不是因为添加此方法时月亮是满的 - 而是因为它是关于接收尚未分配给组件的 props。 props仅在它们(可能)与this.props不同的方法中传递。 这实际上是一个提示,而不是问题。
问题 3 是您很难决定使用哪种回调/方法。 好吧,除非我上面讨论的方法具有相同的签名,否则这并不是真正的问题。 一旦您同时拥有statethis.statepropsthis.props在大多数方法中都是相等的(读起来毫无意义),那么您就会陷入备用选项和备择方案。
问题#2 是关于样板代码……嗯,React 是一个瘦库——正因为如此,它很漂亮、简单和严格。 如果您想制作一个包装器来简化我们的生活并减少我们每天编写的代码量 - 为什么不这样做呢? 将它发布为您自己的npm ,这取决于react ,您就完成了。

关于“生命周期方法太多” - 如果您停止请求没有技术需求的新回调,现在还不会并且永远不会。 技术需求是当你实现一个复杂的组件并且在工作的过程中你明白没有一些丑陋的黑客是绝对没有办法做到的。 它是关于“为了触发一个棘手的 DOM 方法,我需要在组件执行 X 时调用一些东西,但没有这样的方法,我无法添加一个,所以我使用 setTimeout 并希望它不会比我需要的更早着陆”。 不是当你说“哦,我厌倦了写 props = this.props”。

还有一件事情...
在设计良好的 React 应用程序中,您不需要我们谈论的大多数方法,或者您很少使用它们。 getInitialStatecomponentWillMountcomponentWillUnmount 99% 的情况下就足够了。 在我当前的项目中,这是一个相对较大的商业应用程序, componentWillReceiveProps在整个应用程序中使用了两次。 我根本不会使用它,但环境(读取浏览器)是不完美的 - 操纵某些“自身状态”的东西,例如scrollTop或依赖于未来render的布局计算需要在props转换和 DOM 更改之间手动同步。 然而,在大多数“正常”情况下,您只是不需要知道props转换何时发生。

@me-andre 根据我使用 React 的经验,我相信有改进/简化 API 的空间。 我并不孤单,这就是为什么这个问题首先被提出并被+1-ed。

如果我通过提供对您的分析的反馈来回答,我认为它不会真正推动这一点(这就是我到目前为止一直避免这样做的原因),因为它有点偏向于现状和证明当前 API 的合理性,但是我很乐意提供有关潜在解决方案的反馈! 参与此问题的人们正在寻求探索改进想法和潜在的 API 更改,以解决通过他们使用 React 的经验提出的问题(就像我上面概述的那样)。

也许你是对的,社区应该进行创新以推动 API 向前发展,也许让它回滚到核心,或者也许当前的 API 是完美的,但我认为这是一个很好的机会来考虑变化可能会是什么样子反应团队。

也许您像现在这样提出论点的原因是因为我们只是该库的用户,对当前 API 和其中的决策了解较少。 也许你可以邀请其他一些和你一样有经验和知识的 React 团队成员,看看我们是否有不同的观点,甚至可能提出一个很好的解决方案,或者达成共识,即这已经是最好的了?

@kmalakoff ,想象一下 React 是一个引擎和 4 个轮子。 现在,每次你想去某个地方时,你都会建造汽车的其余部分,这最终开始变得烦人。 否则,您会抱怨需要记住低级发动机细节并且用手转动前轮感觉不正确。
我的建议是要么获得一个完整的工具(一个成熟的框架),要么以您可以随着时间的推移重用它们的方式构建所需的组件。
我在这个帖子中看到的是发动机用户抱怨发动机没有液压系统也没有车内灯。 我的感觉是,如果我们将这些功能添加到它们不属于它们的地方,我们会得到一个完整的废话。
React 不是一个框架:它更像是一个 diff 驱动的渲染引擎,它公开了一个强大的组件系统。 它具有低级和简约的 API,但功能强大到足以让您在其上构建任何东西。
如果您觉得我需要澄清一些事情,请不要犹豫,通过电子邮件与我联系 - 我不希望此线程成为教程。

@me-andre 我相信我们现在非常了解您的立场和您的论点。 谢谢! 您可能对 API 已经达到预期的效果是正确的,但也有可能对其进行改进。

有人可以提出更改 API 的理由吗? 例如。 提供各种选项的对比分析(添加 componentDidReceiveProps vs 合并/简化 APIs vs 无变化)

React 团队一直在关注这个问题,我们会在内部进行讨论。 原则上,我总是倾向于消除 API 表面积,所以我发现自己倾向于 @me-andre 的论点。 但是,我们的团队有时会亲切地将componentDidReceiveProps称为“缺少的生命周期方法”,并且就是否可能添加它进行了认真的讨论。 重要的是我们在不催生不良实践的情况下启用最佳实践。

对我们(或至少对我而言)最有用的是深入了解人们为什么需要这种生命周期方法。 在其他生命周期方法没有充分涵盖的生命周期中,您想做什么? 为什么有人想要在UserActions.load(id);内执行componentDidReceiveProps而不是在渲染内执行https://github.com/facebook/react/issues/3279#issuecomment -163875454 (我可以想到一个原因,但我很好奇你的原因是什么)? 除了基于 props 启动数据加载之外,还有其他用例吗?

@jimfb我相信componentDidReceiveProps正是我所需要的,其他方法不合适,但我可能是错的。 我会很高兴地解释我的用例。

我的react-router中有一条路线,如下所示:

    <Route path="/profile/:username" component={ProfilePage} />

我需要从外部来源获取配置文件的数据,为了正确执行此操作,我需要在componentDidMount方法中发出 HTTP 请求。

不幸的是,当我从一个配置文件导航到另一个配置文件时,React Router 不会再次调用构造函数方法或componentDidMount方法(当然这可能是一个错误,但我假设现在不是) .

我想通过使用理论上的componentDidReceiveProps来解决这个问题。 不幸的是它还不存在,而且componentWillReceiveProps不能满足我的需求。

对此的任何指示将不胜感激。

我的预感是componentDidReceiveProps正是我所需要的。

@shea256你能详细说明为什么componentWillReceiveProps不能满足你的需求吗?

@shea256另外,为什么您的组件需要发出 HTTP 请求? 该http请求包含什么? 如果是数据,为什么不在数据回调返回时异步更新组件?

@jimfb对我来说,最常见的情况是当我们必须异步加载某些东西以响应 prop 更改时,这涉及设置一些状态以指示正在加载,然后在加载数据时设置该状态。

挂载和接收新的 props 都应该触发相同的加载周期,所以componentDidMountcomponentWillReceiveProps是调用更新函数的地方。 render没有关于它是否是新道具的信息,无论如何,来自渲染的 setState 是一个禁忌。

所以,我有一个执行加载的函数。 但是我必须将props作为参数传入,并小心避免使用this.props ,这对于componentWillReceiveProps已经过时了。 这不可避免地最终会导致错误,因为您必须记住使用传入的道具,否则在更改到来时会出现微妙的落后错误。 并且所有涉及的方法的签名都更加复杂,因为需要传入 props。

是的,可以用当前的api来完成。 但它会导致更笨拙、容易出错的代码,而这正是 React 通常擅长避免的。

@jimfb我需要在routeParam获取新用户名,而componentWillReceiveProps我还没有。

我需要发出 HTTP 请求才能从外部源加载配置文件数据(数据未存储在本地)。

我可以在渲染方法中更新我的组件,但感觉有点脏:

constructor(props) {
  super(props)

  this.state = {
    id: this.props.routeParams.id,
    profile: {}
  }
}

componentDidMount() {
  this.getProfile(this.state.id)
}

render() {
  if (this.props.routeParams.id !== this.state.id) {
    this.getProfile(this.props.routeParams.id)
  }

  return (
    <div>
      <div className="name">
       {this.state.profile.name}
      </div>
    </div>
  )
}

@grassick写道:

对我来说,常见的情况是当我们必须异步加载某些东西以响应 prop 更改时,这涉及设置一些状态以指示正在加载,然后在加载数据时设置该状态。

是的,这正是我的用例。

挂载和接收新的 props 都应该触发相同的加载周期,所以 componentDidMount 和 componentWillReceiveProps 是调用更新函数的地方。

说得好。

@shea256这不能用componentWillReceiveProps来完成吗?

constructor(props) {
  super(props)

  this.state = {
    id: this.props.routeParams.id,
    profile: {}
  }
}

componentDidMount() {
  this.getProfile(this.state.id)
}

componentWillReceiveProps(nextProps) {
  let { id } = nextProps.params
  if(id !== this.state.id) {
    this.getProfile(id, (profile) => {
      this.setState({ id: id, profile: profile })
    })
  }
}

render() {
  return (
    <div>
      <div className="name">
       {this.state.profile.name}
      </div>
    </div>
  )
}

@grassick写道:

对我来说,常见的情况是当我们必须异步加载某些东西以响应 prop 更改时,这涉及设置一些状态以指示正在加载,然后在加载数据时设置该状态。

@grassick写道:

render 没有关于它是否是新道具的信息,无论如何,来自 render 的 setState 是一个禁忌。

只是在这里吐槽:如果componentDidUpdate在初始渲染时被调用(除了随后的渲染),这能解决你的用例吗? 您可以检查 props 是否更改并在componentDidUpdate加载所有数据,对吗? 在渲染中,如果this.state.profileName != this.props.profileName ,你就会知道你正在加载数据。 该替代方案是否足以满足您的用例?


是否有任何用例不涉及基于道具加载数据的组件?

@calmdev嗯,我相信你是对的。 我会试试的。

@jimfb也许,虽然我尝试使用componentDidUpdate并且我认为它不起作用。 我可以再看看。

听起来我完全可以检查this.state.profileName != this.props.profileName但这似乎也是一个黑客,不是吗? 在这一点上,如果componentWillReceiveProps(nextProps)最终工作,我只是喜欢那样。 现在,困扰我的是componentDidMount缺乏对称性。 我可以用componentWillMount代替componentDidMount吗?

我只是觉得整个生命周期可以更清洁。

@shea256 ,我有一个问题要问你……你读过 React 的README吗?
我不喜欢这么说,但如果不是,您可能不应该为您不熟悉的工具请求新功能。 对我来说,这甚至听起来……荒谬。
“我可以用componentWillMount代替componentDidMount吗?
不,您不能,因为正如自述文件中所述,在您的组件到达 DOM 之前调用componentWillMount并且在此之后调用componentDidMount 。 好吧,您绝对可以用另一种方法替换一种方法,这只会破坏您的代码。 我们这里有两种方法的原因不是美学(阅读“对称”)。 这是因为我们可能需要一种方法在渲染之前进行准备工作,而另一种方法是在第一次渲染之后进行一些初始 DOM 查询/操作。 但即使对于普通的 React 组件来说,这也是奇特的。 通常,您不需要手动访问 DOM。
“听起来我完全可以检查 this.state.profileName != this.props.profileName 但这似乎也是一个黑客,不是吗?”
是的,你的整个方法都是一个黑客。 谁告诉你componentDidReceiveProps会保证props已经改变了? 除非您定义shouldComponentUpdate ,否则不会这样做。
这就是 React 的工作方式。

@me-andre 谢谢你的参与,但你对我的口味来说有点太粗鲁了。 另外我不相信你理解我的问题。 我非常熟悉componentWillMountcomponentDidMount作用。 我会等待@jimfb的回应。

@shea256
“还有,我不相信你明白我的问题。”
能否请您指出我在回答您的问题时没有说到的地方?
另外,您能否澄清一下您要问的问题。
此外,我们在这里不是讨论个性或品味。 这不是私人谈话,也不是技术支持。 这甚至不是一个堆栈交换网站,您可以期望在其中指导您了解某些知识领域。
我经常在本地聚会和国际会议上谈论 React(不仅如此),而且我对知识共享非常开放——在合适的时间和地点。 您可以随时通过电子邮件或 Skype 直接与我联系。

关于加载配置文件数据的问题。 您正在尝试解决将经典命令式方法应用于面向功能的框架的问题。 React 中的视图是道具、状态和环境的函数。 就像function(state, props) { return // whatever you've computed from it } (但在现实世界中事情会变得稍微复杂一些 - 否则 React 根本不存在)。 尽管在0.14我们得到的是纯函数组件,但对于大多数组件,这个入口函数是render()

这意味着您从render()开始编写,并且您假设您的 props 始终是最新的,并且您不关心props是否已更改,更改了多少次以及在何处更改。 您的案例可以通过以下方式实施:

// profile-view.js

var React = require('react');

module.exports = React.createClass({
    contextTypes: {
        app: React.PropTypes.object.isRequired
        // another option is var app = require('app')
        // or even var profiles = require('stores/profiles')
    },
    componentWillMount() {
        var {app} = this.context; // another option is to require('app')
        app.profiles.addListener('change', this.onStoreChange);
    },
    componentWillUnmount() {
        var {app} = this.context; // another option is to require('app')
        app.profiles.removeChangeListener('change', this.onStoreChange);
    },
    onStoreChange() {
        this.forceUpdate();
    },
    render() {
        var {app} = this.context;
        var profile = app.profiles.read(this.props.routeParams.id);
        if (profile) { // profile's been loaded
            return <div>{profile.name}</div>;
        } else { // not yet
            return <div>Loading...</div>;
        }
    }
});

// profiles-store.js
// app.profiles = new Profiles() on app initialization

var EventEmitter = require('events');
var {inherits} = require('util');

module.exports = Profiles;

function Profiles() {
    this.profiles = {};
}

inherits(Profiles, EventEmitter);

Profiles.prototype.read = function(id) {
    var profile = this.profiles[id];
    if (!profile) {
        $.get(`profiles/${id}`).then(profile => {
            this.profiles[id] = profile;
            this.emit('change');
        });
    }
    return profile;
};

很简单。
而且你不需要componentDidReceiveProps 。 你甚至不需要componentWillReceiveProps和类似的钩子。 如果你觉得你需要它们来处理一个微不足道的案例,那么你就缺少对如何在 React 中做事的理解。 为了获得它,使用适当的资源,不要只是丢弃存储库的问题部分。 对我的口味来说,感觉有点太粗糙了。 甚至感觉你不尊重别人的时间。

@me-andre 可能值得稍微降低您的语言,因为即使您只是想提供帮助,它也会让人觉得有点对抗。 我们希望为每个人创建一个受欢迎的社区; 我们曾经都是新手。 尽管您对 API/设计的某些观点是正确的,但如此多的人对这个问题 +1 的事实表明有些地方出了问题,因此我们应该尝试了解人们想要这个的原因/原因生命周期。 也许问题是我们没有充分沟通如何正确编写组件(文档),或者 React 的 API 需要改变 - 无论哪种方式,都值得理解这里的抱怨/问题/评论。

@shea256 this.state.profileName != this.props.profileName正在检查内部状态(组件正在呈现的内容)是否与父级要求组件呈现的状态相匹配。 这几乎是“组件正在更新”或“组件是最新的”的定义,所以我不认为它是hacky。 至少,没有比“当道具更改时触发数据更新请求并在回调中执行设置状态”的想法更hacky 的想法。

@shea256需要明确的是:这个提议的生命周期不会让你做任何你今天无法用当前生命周期做的事情。 它只会使它潜在地更方便。 正如其他人所提到的,您尝试做的事情在当前生命周期中是可能的(有多种生命周期组合可以让您实现目标)。 如果您想“让它发挥作用”,那么 StackOverflow 将是一个更好的讨论场所。 这个 github 问题试图了解componentDidReceiveProps因为它与开发人员的人体工程学有关。

谁告诉你 componentDidReceiveProps 可以保证 props 已经改变了?

@me-andre 在这里是正确的。 大多数生命周期方法实际上并不能保证某些事情已经改变; 它们只表明某些东西_可能_已经改变。 出于这个原因,如果您要执行诸如发出 http 请求之类的操作,则始终需要检查是否previous === next 。 这个线程中的一群人似乎假设他们的价值仅仅因为生命周期方法被触发而改变。

@me-andre 写道:

您正在尝试解决将经典命令式方法应用于面向功能的框架的问题。

我认为这可能是人们想要这种新的生命周期方法的根本原因,但我仍在努力弄清楚人们为什么/如何使用这种方法。 当前的生命周期语义完全有可能与人们通常想要做的事情略有偏差。

全部:除了“异步加载数据以响应 prop 更改”之外,是否还有componentDidReceiveProps用例?

抄送@grassick @iammerrick

@jimfb我搜索了我的代码,我还使用componentWillReceiveProps来构建渲染所需的昂贵的创建对象。 这种情况遇到了同样的问题,它必须传递 props 并注意不要在代码中使用this.props

@jimfb写道:

@me-andre 可能值得稍微降低您的语言,因为即使您只是想提供帮助,它也会让人觉得有点对抗。 我们希望为每个人创建一个受欢迎的社区; 我们曾经都是新手。 尽管您对 API/设计的某些观点是正确的,但如此多的人对这个问题 +1 的事实表明有些地方出了问题,因此我们应该尝试了解人们想要这个的原因/原因生命周期。 也许问题是我们没有充分沟通如何正确编写组件(文档),或者 React 的 API 需要改变 - 无论哪种方式,都值得理解这里的抱怨/问题/评论。

谢谢你提到这一点。 确保您拥有一个热情的社区并且我与您的交流非常愉快,这一点非常重要。

@jimfb写道:

@shea256 this.state.profileName != this.props.profileName 正在检查内部状态(组件正在呈现的内容)是否与父级要求组件呈现的状态相匹配。 这几乎是“组件正在更新”或“组件是最新的”的定义,所以我不认为它是hacky。 至少,没有比“当道具更改时触发数据更新请求并在回调中执行设置状态”的想法更hacky 的想法。

是的,这似乎是合理的。

@jimfb写道:

@shea256需要明确的是:这个提议的生命周期不会让你做任何你今天无法用当前生命周期做的事情。 它只会使它潜在地更方便。 正如其他人所提到的,您尝试做的事情在当前生命周期中是可能的(有多种生命周期组合可以让您实现目标)。 如果您想“让它发挥作用”,那么 StackOverflow 将是一个更好的讨论场所。 这个 github 问题试图了解对 componentDidReceiveProps 的需求,因为它与开发人员的人体工程学有关。

我非常同意这一点。 我没有发布我的特定用例来获得帮助。 我发帖是为了深入了解为什么我认为componentDidReceiveProps会有用。

我和其他十几个明显发帖的人本能地使用这样的东西(这意味着可能有数百或数千个),这向我表明 API 并不像它应该的那样直观。

@jimfb写道:

@me-andre 在这里是正确的。 大多数生命周期方法实际上并不能保证某些事情已经改变; 它们只表明某些事情可能已经改变。 出于这个原因,如果您要执行诸如发出 http 请求之类的操作,则始终需要检查是否为前一个 === 下一个。 这个线程中的一群人似乎假设他们的价值仅仅因为生命周期方法被触发而改变。

我不是在做这个假设。 我确实省略了支票,但我现在正在使用支票。 但在最坏的情况下,会触发额外的请求。

@jimfb写道:

我认为这可能是人们想要这种新的生命周期方法的根本原因,但我仍在努力弄清楚人们为什么/如何使用这种方法。 当前的生命周期语义完全有可能与人们通常想要做的事情略有偏差。

也许。 我会考虑更多。

componentDidReceiveProps对我拥有的特定用例会有所帮助。

我正在使用 ReactRouter 和 Flux 架构。 当我的组件被实例化时,我将我的状态设置为商店中的一个项目。 当该存储发出更改事件时,我使用相同的检索查询更新我的状态。

当我的道具因为我在同一路线中导航到不同的商品 ID 而发生变化时,我需要再次查询我的商店,否则我的组件将停留在商店中上一个商品的状态。

目前,当componentWillReceiveProps被调用时,我会检查我的路由参数是否发生了变化,如果发生了变化,我会调用 updateItemState。 但是,因为道具实际上还没有改变,我现在必须将道具传递到我的方法中。

this.updateItemState( nextProps );

updateItemState( props ) {
    props = props || this.props;

    this.setState( {
        item: this.getItemState( props )
    } );
}

getItemState( props ) {
    props = props || this.props;

    return this.ItemStore.get( props.params[ this.paramName ] );
}

只会变成

this.updateItemState();

updateItemState() {
    this.setState( {
        item: this.getItemState()
    } );
}

getItemState() {
    return this.ItemStore.get( this.props.params[ this.paramName ] );
}

我觉得这是一个合理的用例。

@akinnee-gl 等人:如果除了更新之外,在初始挂载后 componentDidUpdate 被触发,这能解决您的用例吗?

@kinnee-gl ,如果您使用 Flux,则不需要从商店设置状态。 除非绝对不可能,否则您应该将状态保持在一处。 说到 Flux,那个地方就是商店本身。 当商店发出零钱时,您只需forceUpdate() ,然后在render()read()您的商店。

“当我的道具因为我在同一路线中导航到不同的商品 ID 而发生变化时,我需要再次查询我的商店,否则我的组件将停留在商店中上一个商品的状态。”

绝不。

看一看:您需要从商店渲染特定项目,并从props决定要渲染的项目。 如果这是您的要求,则应以与文字相同的方式在代码中表达:

    var item = this.props.store.read(this.props.id);
    return <div>{item.name}</div>;

这是您的组件,仅此而已。
为了使用 Flux,您可以编写一个可重用的组件来进行存储绑定/解除绑定:

<FluxWrapper store={store} component={Component} id={this.props.routeParams.id} />

var FluxWrapper = React.createClass({
    componentWillMount() {
        this.props.store.addListener('change', this.onStoreChange);
    },
    componentWillUnmount() {
        this.props.store.removeListener('change', this.onStoreChange);
    },
    onStoreChange() {
        this.forceUpdate();
    },
    render() {
        var Component = this.props.component;
        return <Component {...this.props} />;
    }
});

var Component = React.createClass({
    render() {
        var item = this.props.store.read(this.props.id);
        return <div>{item.name}</div>;
    }
});

看看如果你正确使用 React,你的组件会变得多么小。

但是,如果您的商店很重,并且您不能在每个render()上只使用read()它们,那么您需要一个缓存中间件。 这可以是一个可重用的组件(类似于FluxWrapper )或一个中间代理存储,它隐藏了原始的read()方法。 代理存储既简单又酷——它们不仅可以缓存读取,还可以在父存储更改对案例不重要或不重要时抑制更改。
这称为组合。 您应该更喜欢组合而不是继承或扩展功能或其他任何东西,因为组合可扩展
一旦遇到使用一个组件难以解决的问题,请考虑使用 2 个或更多组件,而不是将复杂性引入现有组件。

@jimfb ,关于我的语气:我不是母语为英语的人,甚至没有经常练习口语,所以我有时可能会选错词——我只是不觉得它们在情感上听起来如何。

有趣的方法。 我会记住的。 React 的所有官方文档都说要保持渲染函数的纯净(意味着你应该只访问渲染方法中的 props 和 state)。 所有 Flux 示例都显示了 store 中的设置状态。

@jimfb componentDidUpdate的问题在于它是由于道具更改以外的其他原因而被调用的。 此外,它需要我等到组件已经更新,然后调用 setState 再次更新它! 看起来效率很低。

@kinnee-gl componentWillReceiveProps也可能因道具更改以外的原因而被调用(请参阅:https://github.com/facebook/react/issues/3279#issuecomment-164713518),因此您必须检查以查看如果道具实际上发生了变化(无论您选择哪个生命周期)。 此外,如果您实际上是在执行异步数据,那么您将在回调被触发之前等待下一个事件循环,所以我认为如果您的函数在您之前/之后立即被调用并不重要渲染功能,对吧?

@jimfb实际上,如果道具引用的项目已经在商店中,则无需再次渲染。 如果它还没有在商店里,我们可能想显示一个加载屏幕。 所以渲染两次仍然没有意义。 我明白你在说什么。

@me-andre 我真的很喜欢你在渲染过程中从商店中读出的方法。 它似乎确实简化了代码。 我担心这样做会降低您的效率。 你失去了通过 shouldComponentUpdate 控制更新的能力,它需要你添加你提到的额外的 FluxWrapper 组件。 对此有什么想法吗?

我一直在关注讨论,我想重新提出第三个选项:(1) API 很棒,(2) 它需要 componentDidReceiveProps,以及 (3) 也许有一个更简单的 API。

我将问题视为一个机会,看看我们是否可以通过集思广益对 API 进行更深入的更改来解决此问题提出的潜在需求,而不是将讨论限制在 (1) 和 (2) 中的解决方案空间。

简化的 API 可以基于以下原则:

(A) 简化代码结构/减少样板
(B) 保持道具(输入)与状态(内部)分开

(A) 的主要原因是我发现自己在编写非增值样板。 例子:

componentWillMount() { this.actuallyCheckThePropsAndMaybeDoSomething(); }
componentWillReceiveProps(nextProps) { this.actuallyCheckThePropsAndMaybeDoSomething(nextProps); }

actuallyCheckThePropsAndMaybeDoSomething(props) {
  props = props || this.props;

  let relatedProps1 = _.pick(props, KEYS1);
  if (!shallowEqual(this.relatedProps1, relatedProps1) { // changed
   this.relatedProps1 = relatedProps1;

   // do something
  }

  let relatedProps2 = _.pick(props, KEYS2);
  if (!shallowEqual(this.relatedProps1, relatedProps2) { // changed
   this.relatedProps2 = relatedProps2;

   // do something else
  }
}

我真的宁愿执行以下操作,而不是第一次更改时没有单独的代码路径:

propsUpdated(prevProps) {
  if (!shallowEqual(_.pick(prevProps || {}, KEYS1), _.pick(this.props, KEYS1)) { // changed
   // do something
  }

  if (!shallowEqual(_.pick(prevProps || {}, KEYS2), _.pick(this.props, KEYS2)) { // changed
   // do something
  }
}

至于(B),componentDidUpdate 的主要问题就像刚刚提到的那样,如果由于属性更改而设置状态,则可以触发重复的方法调用,因为它同时被用于道具(输入)和(内部)状态。 这些代码路径似乎更好地解耦,因为道具是从外部提供的,状态是在内部设置的,例如。 我已经尝试/考虑了updated(prevProps, prevState)的第四种可能性(componentDidUpdate 的简化名称,因为更少的生命周期方法可以允许更短的命名)使用 componentDidUpdate 来减少样板,但我发现自己对潜在的冗余第二个有点不满意update 调用,并且逻辑在实践中似乎非常独立。

基于从设计原则开始(我确定还有更多!),我想知道propsUpdatedstateUpdated是否可以作为本次讨论的潜在第三种选择?

@kmalakoff你总是需要检查 props 是否改变了,因为我们永远无法明确地告诉你 props 是否改变了/没有改变(因为 Javascript 没有值类型并且确实具有可变性)。

在那一点上,也许它可以调用 shouldComponentUpdate 来判断它是否应该调用 propsDidChange 或其他什么。 虽然是单独的问题。

@jimfb没有准确遵循您所指的语言限制以及它与简化 API 的总体关系。 你能解释得更详细一点或举个例子吗? 我需要这个解释简化/扩展一点来理解......

我认为他的意思是没有内置的快速方法来检查this.props === nextProps因为它们可能是也可能不是两个单独的对象。

{ a: 1 } === { a: 1 }产生错误,因为它们是两个独立的对象,但是

var a = { a: 1 };
var b = a;
a === b

产生真,因为它们实际上都是对同一个对象的引用。

我们可以递归地检查每个属性是否相等,但这可能会很慢。 这就是为什么由我们来实现shouldComponentUpdate

@kmalakoff我不想离题,但这里有一个要点: https : //gist.github.com/jimfb/9ef9b46741efbb949744

TLDR:@akinnee-gl 的解释完全正确(谢谢!)。 有一个小的修正,我们不能总是做递归检查(即使我们想要,而且性能不是问题),因为没有办法递归检查作为 prop 传入的回调函数。

不过,让我们保持这个主题的主题:P。

谢谢你们俩! 嗯,仍然不完全清楚为什么简化 API 不是解决此问题的选项。 我将在要点中添加评论......

如果有人想参与更广泛的解决方案,请加入我们!

@jimfb @calmdev我已经尝试过你的建议,现在我完全理解了为什么componentDidReceiveProps没有真正添加任何新的东西,不应该作为附加功能添加。 虽然我之前相信你的话,但现在对我来说为什么会这样很直观。

为了说明我是如何实现这一点的,我想分享我的最新代码。 也许这也能帮助其他人。

看看我的个人资料页面组件:

class ProfilePage extends Component {
  static propTypes = {
    fetchCurrentProfile: PropTypes.func.isRequired,
    currentProfile: PropTypes.object.isRequired
  }

  constructor(props) {
    super(props)
    this.state = { currentProfile: {} }
  }

  componentHasNewRouteParams(routeParams) {
    this.props.fetchCurrentProfile(routeParams.id)
  }

  componentWillMount() {
    this.componentHasNewRouteParams(this.props.routeParams)
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.routeParams.id !== this.props.routeParams.id) {
      this.componentHasNewRouteParams(nextProps.routeParams)
    }
    this.setState({
      currentProfile: nextProps.currentProfile
    })
  }

  render() {
    var profile = this.state.currentProfile
    return (
      <div>
        <h1>{ profile.name ? profile.name : null }</h1>
      </div>
    )
  }
}

挂载组件时,它没有配置文件数据,需要从外部源加载配置文件。

所以...我让它调用fetchCurrentProfile函数(包装在componentHasNewIdProp ),它从外部源获取数据,然后向 Redux 发送更新操作。 reducer 接收这个动作并更新应用程序状态,然后更新ProfilePage组件的 props。

现在,由于 props 已经更新, componentWillReceiveProps函数被调用。 但是我们不知道 prop change 事件的上下文,所以我们需要弄清楚哪些 props(如果有)发生了变化。 我们看到ID是一样的,所以不需要再次调用fetchCurrentProfile 。 然后我们更新state.currentProfile以便组件知道用新的配置文件数据重新渲染(是的,我可以用道具来做到这一点,但还有另一个我不想在这里讨论的原因)。 这时候,我们可以在更新状态之前检查props.currentProfile是否发生了变化,但这不是一个昂贵的操作,所以我们是否检查并不重要。

然后...假设我们想从一个配置文件导航到另一个配置文件。 我们单击一个链接,路由器触发路由更改。 我们现在有了新的路由参数并且componentWillReceiveProps函数被触发。 我们看到id已更改,因此我们再次调用fetchCurrentProfile (通过componentHasNewIdProp )。

当新的 fetch 返回并调度 redux action 时, currentProfile prop 会再次更新,然后状态会更新,并且组件会使用新视图重新渲染。

这个例子说明了当组件挂载和组件接收新的 props 时需要有不同的行为。 它还演示了如何在后一种情况下检查 props 的更改以及如何仅在某些 prop 更改时调用某些函数。

回顾过去,本质上我想要的是一个仅在routeParams更改时更新的函数。 但是,没有必要添加另一个函数,因为我们可以使用现有函数做我们想做的任何事情,而且我们不想使生命周期复杂化。

不过,在这里可能有帮助的是更多关于生命周期、工作示例等的文档。

谢谢你们的帮助。 如果我在这里做错了什么,请告诉我。 我希望这对将来偶然发现此线程的任何人都有帮助。

@shea256你的例子似乎是一个合理的例子,因为不需要新的 API 就能在没有新 API 的情况下实现你需要的东西。

我在: https :

想象一下,api 允许您编写相同的功能,但样板文件更少:

class ProfilePage extends Component {
  static propTypes = {
    fetchCurrentProfile: PropTypes.func.isRequired,
    currentProfile: PropTypes.object.isRequired
  }
  state = {currentProfile: {}}; // not material to example (babel-stage-1)

  // called both on init and update
  willReceiveProps(nextProps) {
    if (!this.props || (this.props.routeParams.id !== nextProps.routeParams.id)
      nextProps.fetchCurrentProfile(nextProps.routeParams.id)
  }

  render() {
    var profile = this.props.currentProfile;
    return (
      <div>
        <h1>{ profile.name ? profile.name : null }</h1>
      </div>
    )
  }
}

我希望能够考虑的想法是减少 API 表面积以减少代码路径和样板的数量。 我想知道,如果讨论锚固在初始请求添加的东西时,可能从不同的角度看它会打开新的道路。

这只是一种选择(我的偏好是 receivedProps(prevProps) 因为理想情况下,只有一种与 props 相关的方法可以使用 this.props),但重要的部分是有一个代码路径用于初始化组件和任何 prop 发生变化,而不是我们现在拥有不同 API 签名的不同代码路径。 无论人们同意哪种解决方案都更好。

减少样板文件是我希望这个问题可以实现的目标(以澄清我的需求/问题!),我想就该解决方案的外观进行更广泛的头脑风暴(考虑取消 API,更改生命周期方法显着,缩短方法名称,因为 ES6 广泛可用,组件似乎是前进的方向,等等)。

@kinnee-gl ,有一个由许多微小组件组成的应用程序比几个沉重的组件要好得多,原因如下:

  1. 这种方法与“单一责任原则”相联系
  2. 您可以清楚地看到哪个组件做了什么
  3. 您可以通过包装轻松组合功能和扩展组件
  4. 方法和属性不冲突
  5. 由于一个组件封装了一个功能,因此扩展功能更容易

关于您注意到在每次渲染时从商店读取可能效率低下的问题,我制作了一个要点,其中展示了如何实现用于缓存的中间件组件的基本思想: https :

我已经对可能导致改进 API 的原则/目标进行了另一次传递(希望基于本期建议的接收到的 props 方法):

(A) 简化代码结构/减少样板
(B) 保持道具(输入)与状态(内部)分开
(C) 允许一种方法来控制组件的渲染
(D) 基于组件子类化的假设简化方法命名
(E) 将初始化和更改的 props 方法的数量减少到一个
(F) props 方法应该有 this.props 的最新值

要点中有对其中一些的澄清。

@kmalakoff根据要点,我们不要扩大这些问题的范围。 这个问题已经足够复杂了,将谈话离题到一个完整的 API 重新设计是不容易处理的。 欢迎您继续思考这些问题,但请将此主题保持在主题上。

@kmalakoff您提到的许多要点都是其他问题的主题。 例如(D)由https://github.com/reactjs/react-future/issues/40#issuecomment -142442124 和https://github.com/reactjs/react-future/issues/40#issuecomment回答 - 153440651。 这些是进行该讨论的适当主题。 如果您正在寻找更一般/整体设计/API 的讨论,正确的媒体可能是https://discuss.reactjs.org/

@jimfb在您建议我将其移至要点之后,我对此进行了一些思考,实际上并不相信这一行分析是离题的……我对围绕收到的道具的这个问题感兴趣的原因是我相信有收到的道具生命周期方法将允许我减少样板。

请听我说……减少样板是这个问题中的生命周期方法可以为我解决的问题。

我相信这也可能是人们对这种生命周期方法感兴趣的一个重要原因,因为当前的 API 比我们希望的更多地鼓励样板。

也就是说,我很乐意尝试将 API 改进解耦,例如,只专注于探索围绕改进与本期 props 相关的 API 的解决方案空间。

@kmalakoff该线程的主题由标题和第一篇文章定义。 标题是componentDidReceiveProps Please而不是lots of general ways to reduce boilerplate 。 这个线程是关于通过引入一个非常具体的生命周期方法来减少样板的。 仅此讨论就已经足够细致了,没有引入其他问题中已经讨论过的其他主题(例如重命名所有函数)。

您的观点很有价值,我鼓励您进行讨论,请将其移至其他线程,因为我们希望 github 线程专注于手头的主题。 否则讨论很难跟踪/管理。 您提出的大多数主题确实存在主题。

@jimfb为什么人们要componentDidReceiveProps ? 我对它感兴趣的原因是减少与 props 相关的样板。 我不确定如何更清楚地说明这一点,以便我可以听到和理解。

您已经询问了问题和用例,我已经将其说明为我的问题,并在上​​面展示了一个用例(响应 @shea256),它似乎是主题,在范围内,并且从我的角度来看,对于解决问题很重要这个问题。

我已同意推迟将范围扩展到一般 API 问题/改进和意愿。 我承诺! :眨眼:

仅供参考:正如我之前所说,我开始围绕这个问题进行思想实验的原因是因为这些论点似乎过于狭隘(例如,我们已经可以用当前的 API 做我们需要的一切),而不是确定背后的潜在动机问题。 如您所知,有时您需要退后一步,然后专注于,然后从不同的角度查看,然后重新审视假设,调查请求背后的根本原因等,以迭代解决问题的最佳解决方案。 希望我帮助提请注意这一点,我的参与将有助于让我们满意地解决这个问题! (当然,我也希望 React API 会变得更加易用,并将在其他地方继续讨论……感谢您提供链接)

@kmalakoff ,好的,为了减少样板,您只需创建可重用的类或组件,从中子类化或组合另一个组件。 没有人会阻止您为应用程序中的所有组件定义通用方法。 React 确实提供了出色的可扩展性:您可以将组件定义为对象、函数或类。 您可以通过子类化、mixin 或工厂来扩展组件。

@me-andre 如果此问题不会导致 API 改进,那绝对是一个选择。

提出这个问题是为了促进围绕对 API 进行更改以改进它的愿望的讨论。 应该考虑明确探索客户端代码中的替代方案,但如果有充分的理由表明应该更改 API,则不需要这些变通方法。 为了反驳,还需要针对改进的 API 应该是什么,然后根据当前的 API 进行评估以对其进行评估。

例如,如果您要证明当前没有定义自定义超类的 API 可以以减少样板的方式使用(例如,我们使用的 API 不正确,或者当前 API 中的某些功能可用于实现类似程度的样板减少等)或证明为什么没有改进的可能(例如,不存在通用解决方案,因为我们的 API 改进选项不支持一个主要用例),等等,您可以API 足够好的情况。

如果 API 要求人们为基本和常见的控制流模式编写自定义超类,这就强化了 API 需要改进的论点。

好奇 - 在挂载前没有触发componentWillReceiveProps的理由是什么? 仔细想想,组件在初始化的时候真的是在接收props。 它只是没有收到新的道具。

您只想在接收新道具时(而不是在初始道具接收时)触发某些事情的用例是什么?

我可能在这里遗漏了一些明显的东西,但只是试图调和各方的观点。

在任何情况下,如果componentWillReceiveNewProps在某些情况下很重要,仍然可以通过检查传入的道具来使用修改后的componentWillReceiveProps模拟它。

@kmalakoff如果componentWillReceiveProps第一次触发,那是否符合您的 API 简化标准?

componentWillReceiveProps未在 mount 上触发并不是人们要求componentDidReceiveProps 。 人们要求componentDidReceiveProps是因为他们编写了所有访问this.props 。 当componentWillReceiveProps被调用时,nextProps 被传递,但是this.props还没有改变,这意味着我们必须将nextProps传递给所有被调用以响应componentWillReceiveProps ,而不是保持原样。 我们最终得到了大量的props = props || this.props和大量的props传递给我们调用的每个函数。

@shea256好点。 初始化与更改的不同代码路径是 prop 样板的根本原因之一。 另一个根本原因是使用不同的签名来处理像@kinnee-gl 指出的道具。

这就是为什么我试图扩大正在考虑的解决方案空间(例如,还调用 init),因为实际上可能有更好的解决方案来减少道具样板。

我希望我们能够走得更远:

新钩前

componentWillMount() {
  this.setup(this.props.id);
}

componentWillReceiveProps(next) {
  this.setup(next.id);
}

setup(id) {
  UserActions.load(id);
}

新钩后(修订版)

componentDidReceiveProps(prevProps) {
  UserActions.load(this.props.id);
}

或者如果 UserActions.load 无法检查当前加载的 ID:

componentDidReceiveProps(prevProps) {
  if (!prevProps || (prevProps.id !== this.props.id))
    UserActions.load(this.props.id);
}

@kmalakoff ,我要说的是 API 改进现在绝对可供您使用:您可以创建自己的组件工厂,然后与我们分享(以及用例示例)。 这将使您的建议和理由更加清晰一百倍。 由于所有生命周期点已经有适当的回调,您可以在任何生命周期点/组件状态引入任何新方法。 您甚至可以将事件发射器混合到您的组件中,并可以为状态更改附加多个处理程序。

@shea256componentWillReceiveProps在第一次渲染之前没有被触发的一个可能原因是当时没有this.props这样的东西。 您通常在componentWillReceiveProps所做的是将this.props[propName]newProps[propName] 。 在第一次渲染之前触发该方法将使您还必须检查this.props 。 此外,整个组件在初始渲染之前收到props时完全未初始化,它甚至没有state

@kmalakoff ,我已经两次发布了代码示例,这些示例展示了如何以不需要任何setup或类似技巧的方式组织 React 组件。 你能告诉我为什么你仍然努力改变 React 组件的行为而不是调整你的代码以便它与 React 集成吗? 如果您只是指出我的样本不适合您的用例,那就太好了。

@kinnee-gl ,不引入仅在更新时访问this.props的新方法的原因是我们有这样的方法 - 它称为render() 。 它甚至在检查shouldComponentUpdate()后被调用 - 这通常是您执行this.props !== newProps_.isEqual(this.props, newProps)等的地方。
如果你觉得你应该有一个单独的方法来进行一些设置,为什么不只是继承一个 React 组件并定义一个如下的render()方法

this.setup(this.props);
return this._render();

我只是没有看到它如何简化 React 生态系统中的事情,但这就是您不断要求的。

@me-andre 这个问题的前提不是我们不能用当前的 API 实现我们想要的,也不是我们不能在客户端代码中解决当前的 API。 这个问题的前提是当前的 React API 是次优的,需要改进; 例如,如果您想出最佳 API 的外观原则(就像我在上面尝试过的那样),当前的 React API 将得分在中等范围内,因为它在许多领域都不是最佳的/有缺陷的。

不幸的是,在客户端代码中提供解决 React API 的方法并不能解决问题的根本原因,将争论从解决底层问题转移开,也不会引发围绕改进 React API 的潜在解决方案的争论。

简而言之,我已经有了对我有用的变通方法,因为我有一堆 React 应用程序在生产中,因此最佳实践对于博客文章来说可能很棒,但是在这个问题上使用它们作为争论的焦点只会使我们偏离关于这个问题的真正目的的真正辩论:如何改进 React API(在这个问题中,仅限于道具用例和样板)。

React 1.0 应该瞄准顶部,而不是中间。 主要版本的增加可能会破坏向后兼容性,所以让我们根据这些年来使用 0.x 版本所学到的知识,寻找可能的最佳 API。

(仅供参考:我认为人们可能不会像您希望的那样参与您的示例,因为他们来这里不是为了了解当前的 API,而是因为他们正在寻找/希望对 API 进行改进,例如。他们可以被认为是出于好意,但略有偏差)

我认为人们可能不会像您希望的那样参与您的示例,因为他们来这里不是为了学习当前的 API,而是因为他们正在寻找/希望对 API 进行改进

好的,您提出了 API 改进建议,但随后您展示的代码与“React 最佳实践”相去甚远。 有些事情看起来很像最坏的做法。 然后你说:“这就是改变的原因”。 是的,这就是改变的原因,但不是在 React 代码库中。
我向你展示了如何重新组织代码以使其与 React 一起工作,但只是忽略正确的使用展示并继续坚持。 这似乎不是一场建设性的辩论。

不幸的是,在客户端代码中提供解决 React API 的方法并没有解决问题的根本原因

当您将低级 API 与高级 API 包装在一起时,这不是一种解决方法,而是一种常见做法。 许多出色的框架都遵循这个想法。
我一直在说的是将现有的丑陋 API 与您想使用的内容打包并与我们分享。 连同使用示例和解释为什么它变得更好,这将是令人难以置信的。
再说一次,我只是看不出你为什么不这样做的理由。 因为如果您所谈论的是一个常见问题,那么这对许多人来说将是一个很大的帮助。

我有一堆 React 应用程序在生产中,因此最佳实践对于博客文章可能非常有用,但是在这个问题中使用它们作为辩论的重点只会使我们偏离真正的辩论

在架构/设计 API 时,您通常做的是预测问题、假设用例等。人们假设的原因并不是他们试图从现实世界中抽象出来以寻找理想。 这只是缺乏经验——你不能“提前”获得经验。

你说你有这样的经验,你见过真正的问题,你有一些可以分享的解决方法。 这正是使这场辩论变得“真实”和“有效”的原因。 React 本身是建立在真正的问题和挑战之上的——这就是为什么它解决了真正的架构问题,而不仅仅是“如何用 3 行代码编写 hello world”。

@me-andre 我听到你的声音,你的论点很明确。

你是对的,我的论点的核心是,如果我们基于我们使用当前 React API 的集体经验建立更好的原则,并展开辩论以包括可能以某些基本方式改变 API 的非教条式解决方案,我们可以而且应该抓住机会改进 React API,使其比现在更好。 当有改进的空间时,我们不要满足于现状!

您如何评价当前围绕 props 的 React API? (假设使用字母等级:F 到 A+),为什么,如果它低于 A+,你会怎么做来改进它?

@me-andre 你有机会准备你的排名和分析吗? 我很想听听您认为当前 API 的优势、劣势和机会。

+1

+1 请

有解决方法吗? 我需要 ComponentDidReceiveProps

我在一年前提出了这个问题,从那以后每天都在使用 React。 我不再认为 componentDidReceiveProps 有一个用例可以证明增加 API 是合理的。

@AlexCppns你想做什么?

@iammerrick ,实际上没关系,我只是误解了 componentWillReceiveProps 的使用方式。

相关讨论: https :

我遇到过几次,我最终做的是:

componentWillReceiveProps(nextProps) {
  if (this.props.foo !== nextProps.foo) this.needsUpdate = true;
}
componentDidUpdate() {
  if (this.needsUpdate) {
    this.needsUpdate = false;
    // update the dom
  }
}

还不错。

@brigand ,不需要标志 - 您可以在componentDidUpdate()内比较道具:

componentDidUpdate(prevProps) {
    let needsUpdate = prevProps.foo !== this.props.foo;
    // ...whatever
}

此外,您的解决方案很容易被shouldComponentUpdate()破坏,这可能会阻止重新渲染。

哦酷,谢谢!

@jimfb我想我在下面有一个同步用例。 我认为componetDidReceiveProps将是完美的。

  componentDidMount() {
    this._selectAll()
  }

  componentDidUpdate(prevProps) {
    let shouldUpdateSelected = (prevProps.recordTypeFilter !== this.props.recordTypeFilter) ||
      (prevProps.statusFilter !== this.props.statusFilter) ||
      (prevProps.list !== this.props.list)

    if (shouldUpdateSelected) { this._selectAll() }
  }

  _selectAll() {
    this.setState({ selectedIds: this._getFilteredOrders().map((order) => ( order.id )) })
  }

  _getFilteredOrders() {
    let filteredOrders = this.props.list

    // recordTypeFilter
    let recordTypeFilter = this.props.recordTypeFilter
    filteredOrders = _.filter(filteredOrders, (order) => {
        // somelogic
    })

    // statusFilter
    let statusFilter = this.props.statusFilter
    filteredOrders = _.filter(filteredOrders, (order) => {
        // somelogic
    })

    return filteredOrders
  }

@chanakasan ,您的示例缺少render()方法,这对于理解您的示例和提出更好的解决方案至关重要。
其次,您的代码连接到一些自定义业务逻辑并且不容易阅读。 不要犹豫解释,不要只是把复制粘贴扔给别人。
但是,我已经阅读了您的示例,并想分享以下想法:

  1. 这是componentWillReceiveProps的用例,而不是componentDidUpdate 。 如果您切换到componentWillReceiveProps ,您的组件将重新渲染两倍,同时保留相同的逻辑。 离开这个讨论一年后,我仍然看到componentDidUpdate的零用例,除了对 DOM 更改做出反应。
  2. 更好的是,完全避免挂钩并将所有逻辑移至render()方法/迁移到无状态组件,因为this.state.selectedIds实际上不是状态。 它纯粹是从道具计算出来的。

嗨@me-andre,感谢您抽出时间回复。 我已阅读此页面上的讨论,感谢您的参与。

是的,不需要componentDidReceiveProps ,但是如果没有它,事情似乎很神秘。
如果我使用componentWillReceiveProps您已经说过your component would re-render twice as less componentWillReceiveProps 。 这对我来说仍然是个谜。

我确实考虑并尝试了您之前建议的两件事,但失败了。

  1. componentWillReceiveProps 将不起作用,因为从_selectAll()调用的_getFilteredOrders()函数需要 newProps。
  2. 我想不出一种方法来导出selectedIds而不将其存储在状态中。

我刚刚从我的用例中创建了一个完整的示例。 我已经尽可能地容易理解。

如果可以,请看一看并指出我正确的方向。 如果我能够正确理解这一点,我将在博客文章中分享此示例以帮助其他人。

@chanakasan ,来吧,这看起来像一个生产代码。 我不会阅读所有内容并免费帮助您完成项目。

但是,我可以为您指出正确的方向:

  1. componentWillReceiveProps()componentDidUpdate()都可以访问 prev 和 next 道具。 从官方 React 文档中可以清楚地看到这一点。
  2. 从您的代码的完整复制粘贴中,现在很明显您使用 state 来存储用户可以切换的选定 id。 那时可以使用状态,但是componentWillReceiveProps()仍然会触发重新渲染的次数减少两倍。 (因为无论如何都会在componentWillReceiveProps()之后进行渲染, setState()只会为即将到来的渲染更新状态)。
  3. 请查看文档。 节省您的时间并尊重他人。

@me-andre 我想我理解你在文档中使用这一行关于componentWillReceiveProps观点:

如果您只是调用 this.setState(),则不会调用 componentWillReceiveProps()

但是使用componentWillReceiveProps的警告是我必须将 nextProps 传递给函数。

我会尽量听从你的建议。 谢谢,我很感激。

顺便说一句,我不想​​让你免费帮助我的制作项目:)。 这是我之前的简短示例中更完整的示例,用于填充有关我的用例的任何空白。

如果我们将它与 shouldComponentUpdate 结合使用会怎样?
我们不希望更新组件状态或重新渲染,
但是我们需要 this.props 在收到 props 后使用最新的 props 做一些手动脚本工作

我的解决方法是在运行所需的功能之前将 this.props 设置为新的道具

拥有 ComponentDidMount 或一些钩子真的很好。 为什么? 因为,有时,我们需要进行其他不依赖于 React 生命周期的计算。

例如:我有一个可以调整大小的父组件。 子组件负责渲染一个 OpenLayer 地图,该地图具有一个负责调整地图大小的函数。 但是,它应该发生在子级从父级获得道具并在 React 生命周期内提交其他计算之后。

此页面是否有帮助?
0 / 5 - 0 等级