为啥在 setState 之后调用 getDerivedStateFromProps?

Posted

技术标签:

【中文标题】为啥在 setState 之后调用 getDerivedStateFromProps?【英文标题】:Why getDerivedStateFromProps is called after setState?为什么在 setState 之后调用 getDerivedStateFromProps? 【发布时间】:2018-12-03 19:41:21 【问题描述】:

React 引入了新的静态方法getDerivedStateFromProps(props, state),它在每个渲染方法之前被调用,但是为什么呢?在道具更改后调用它对我来说是有意义的,但在 setState 之后它没有,也许我错过了一些东西。

我正在根据我公司的要求创建一个datePicker 组件,组件日期由道具控制。我在组件中有以下状态。

selectedDate: number;
selectedMonth: number;
selectedYear: number;
currentMonth: number;
currentYear: number;
view: string;

selected 代表选定的日期,它来自 date 属性,currentMonthcurrentYear 代表当前日历视图中的月份和年份。

如果 date 来自 prop 更改 selected*currentMonthcurrentYear 应相应更改。为此,我使用getDerivedStateFromProps,但假设用户单击月份名称,这会将日历视图切换到月份(而不是显示月份的日期名称),该函数使用 setState 更新currentMonth,但是日期道具与以前相同(包含上个月),但应该调用 getDerivedStateFromProps 并且 currentMonth 再次与以前相同而不是更改。

我在state 中创建了一个额外的变量来跟踪是否由于setState 而调用了getDerivedStateFromProps,但我认为这不是正确的方法。

要么我做错了什么或遗漏了什么,要么getDerivedStateFromProps 不应该在setState 之后调用。可能我做错了什么。

【问题讨论】:

getDerivedStateFromProps 不会在 setState 调用中调用。当父级重新渲染调用子级 getDerivedStateFromProps 以及组件安装时。可重现的演示或相关代码可能有助于指出错误。查看这个演示,证明 setState 不会触发 getDerivedStateFromProps codesandbox.io/s/k94z83mz6r getDerivedStateFromProps 在每个渲染方法之前调用,它仅用于在道具更改时调用一次,但他们在可能的 16.4 版本中更改了它 你能再次检查沙箱吗,我已经更新了反应和反应- dom版本 是的,你是对的,在最新版本中,每次重新渲染之前都会调用它 那么您是否找到了解决方法。每次重新渲染时都调用它似乎没用,因为它破坏了 setState 功能,并且无法区分状态更改和道具更改。真烦人…… 嗨@tylik,我正在从构造函数中的道具设置状态,并在需要时使用 componentDidUpdate 从道具更新状态。 【参考方案1】:

以下内容似乎对我很有效,而且很笼统。

在构造函数中:

this.state = 
    props,
    // other stuff

在 gDSFP 中:

static getDerivedStateFromProps(newProps, state) 
    if (state.props !== newProps)
        // Compute and return new state relevant to property change
    else
        return state;

一些实验证实“状态”不再自动是旧状态,而是在这个钩子实例中是新设置的状态与现有道具的组合。我想添加钩子是为了不必直接调用 gDSFP 来重复依赖于道具的复杂状态演变,例如

this.setState(ClassName.getDerivedStateFromProps(this.props, newState))

问题在于,这并不是一个重大变化,但如果旧代码在 getDerivedStateFromProps 中不必要地进行大量计算,则可能会导致效率低下。例如,我刚刚发现一个复杂的对象被三次构造,一次是真实的,另外两次只是因为这个新的钩子。

【讨论】:

【参考方案2】:

对于这种情况(根据道具变化更新状态),使用:

componentDidUpdate(prevProps) 
  // don't forget to compare props
  if (this.props.userID !== prevProps.userID) 
    this.fetchData(this.props.userID);
  

componentDidUpdate 将在每次更新后被调用(由于道具更改/状态更改)。所以你应该检查道具是否改变了(this.props.userID !== prevProps.userID)。

【讨论】:

【参考方案3】:

我做了这样的事情

constructor(props) 
    super(props);
    this.state = 
        expanded: props.expanded,
        ownUpdate: false
    


static getDerivedStateFromProps(props, state) 
    if (state.ownUpdate) 
        return 
            expanded: state.expanded,
            ownUpdate: false
        ;
     else if (props.expanded !== state.expanded) 
        return 
            expanded: props.expanded
        ;
    
    return null;


toggle() 
    this.props.onAftePress(this.state.expanded, this.props.index);
    this.setState(
        expanded: !this.state.expanded,
        ownUpdate: true
    )

【讨论】:

是否有任何其他解决方案来代替设置 ownUpdate:true? 这对我来说看起来不安全。 React 可以批量渲染,因此可能会调用 getDerivedStateFromProps,因为您的状态已更改并且道具已更改。在这种特殊情况下,您最好存储 props.expanded 值中的 prevExpanded 状态并与之比较以查看扩展的 prop 是否已更改。【参考方案4】:

每当收到 new propssetStateforceUpdate 时,getDerivedStateFromProps 挂钩的工作方式。

16.3 的版本中,无论何时使用setState,React 都不会影响getDerivedStateFromProps。但是他们在以16.4 开头的版本中对其进行了改进,因此每当setState 被调用时,getDerivedStateFromProps 就会被挂钩。

这是从React lifecycle diagram提取的图像:

16.3

^16.4


因此,何时通过正确检查道具和状态来挂钩 getDerivedStateFromProps 取决于您。这是一个例子:

static getDerivedStateFromProps (props, state) 
  // check your condition when it should run?
  if(props.currentMonth != state.currentMonth) 
    return 
      currentMonth: state.currentMonth
    
  
  // otherwise, don't do anything
  else 
   return null
  

【讨论】:

在 16.4 版中改进了吗?更像是他们在 16.4 版中破坏了它。新版本没用。 这是改进。你认为什么没用?【参考方案5】:

您的问题本身就有答案。 “在每个渲染方法之前调用”。每当您执行setState 时,都会调用render 方法。

我建议您将状态变量 currentMonthcurrentYear 提升到父组件,并将它们与其他三个一起作为 prop 传递。您还可以将更改处理程序作为道具传递并从子级调用它。

在初始render - currentMonthcurrentYear 可以设置为null,这样您就可以拥有显示默认内容的逻辑。当有人知道月份名称时,您可以从父级调用changeHandler,它将传递新的道具。现在在 getderivedstatefromprops 中,您不再将 currentMonthcurrentYear 设为 `null,因此您知道月份已更改。

【讨论】:

【参考方案6】:

我也遇到了这个问题。所以我设置了另一个变量来检查第一次收到的道具。

this.state=flag:true

getderivedstatefromprops

static getderivedstatefromprops(props, state)
   if(props.<*propName*> && flag)
      return( props.<*propName*>, flag:false)
   

如果你想使用多个 props 值,你需要相应地设置你的 if 语句(或任何其他逻辑)。

【讨论】:

以上是关于为啥在 setState 之后调用 getDerivedStateFromProps?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:状态类:为啥不在 setState 方法调用之前改变状态变量,而不是在 setState 方法中

在等待之后调用 setState 时,状态立即可用

在 dispose() 之后调用 setState()

为啥 React setState hook 没有立即更新? [复制]

即使在调用 setState 之后,React.js 子状态也不会重新渲染?

ReactJS:为啥我不应该改变嵌套状态?