1

一旦更新React 16.4getDerivedStateFromProps ,我就会遇到一个问题,我们在逻辑上有一些重大变化。现在,它会在传入自己组件的 props 上的每个组件更新时触发。

所以,我已经阅读了文档和手册,但仍然无法弄清楚表单输入字段应该基于传入的道具(controlled component)并且同时能够通过用户自己的输入进行修改的情况?

我也试过这篇文章,但它只涵盖了一次性更新的案例,而不是手动输入案例:Why getDerivedStateFromProps is called after setState?

这是我要重现的小代码:

import PropTypes from 'prop-types'
import React from 'react'

export class NameEditor extends React.Component {
  static propTypes = {
    currentLevel: PropTypes.number
  }

  static defaultProps = {
    currentLevel: 0
  }

  constructor(props) {
    super(props)

    this.state = {
      currentLevel: 0
    }
  }

  static getDerivedStateFromProps(nextProps) {
    return {
      currentLevel: nextProps.currentLevel
    }
  }

  _handleInputChange = e => {
    this.setState({
      currentLevel: e.target.value
    })
  }

  render() {
    const { currentLevel } = this.state

    return (
        <input
          placeholder={0}
          value={currentLevel}
          onChange={this._handleInputChange}
        />
    )
  }
}

export default NameEditor
4

3 回答 3

1

更新:

目前,_handleInputChange方法只会修改子组件的状态,子组件会调用getDerivedStateFromProps.

该方法的工作方式是,当每个newPropssetState调用发生时都会调用它。

因此,行为如下:

  1. 您使用处理程序更改值。
  2. getDerivedStateFromPropsget 被调用,它将currentLevel从父组件中获取值,由于我们没有在那里进行任何更改,因此它仍然没有被修改,因此,它将用父组件中存在的值覆盖来自调用处理程序的新值组件,未修改。

为了解决这个问题:我们需要一个来自父组件的回调函数,它的工作与 handleInputChange 相同。

所以:

  1. 向你的父组件添加一个handleCurrentLevelChange方法,它只有一个参数e.target.value,它的工作是修改currentLevel你的状态。
  2. 将您创建的名称传递handleCurrentLevelChange给您的NameEditor您想要的名称,可能是相同的名称。
  3. 修改你孩子的handlr如下:
  _handleInputChange = (e, cb) => {
    this.setState({
      currentLevel: e.target.value
    }, () => {
      cb && cb(e.target.value) //this makes the callback optional.
    });
  }
  1. 修改您的 onChange 属性以适应新的更新:
        <input
          placeholder={0}
          value={currentLevel}
          onChange={(e) => this._handleInputChange(e, handleCurrentLevelChange)}

属性和处理程序的新行为onChange将允许更改发生在您的孩子和您的父母身上。

这应该可以解决当前的问题。

于 2019-07-23T07:42:30.807 回答
1

解决方案#1(带钥匙并重新安装):

您可能需要根据传入的 prop: 为每个外部 props 更新提供一个密钥,从而使当前组件重新安装在每个外部 props 上currentLevel。它看起来像:

class Wrapper ... {
...

  render() {
    const { currentLevel } = this.props;

    return (
     <NameEditor key={currentLevel} {...currentLevel} />
    )
  }
}

export default Wrapper

...并通过告诉它对您的组件进行一些额外的更改以阻止派生的道具替换 - 它是否是第一次渲染(因为我们计划state仅从内部控制它,并且仅通过重新安装从外部控制它,当它真的如此时) :

import PropTypes from 'prop-types'
import React from 'react'

export class NameEditor extends React.Component {
  static propTypes = {
    currentLevel: PropTypes.number
  }

  static defaultProps = {
    currentLevel: 0
  }

  constructor(props) {
    super(props)

    this.state = {
      currentLevel: 0,
      isFirstRender: false
    }
  }

  static getDerivedStateFromProps(nextProps, prevProps) {
    if (!prevProsp.isFirstRender) {
      return {
        currentLevel: nextProps.currentLevel,
        isFirstRender: true
      };
    }

    return null;
  }

  _handleInputChange = e => {
    this.setState({
      currentLevel: e.target.value
    })
  }

  render() {
    const { currentLevel } = this.state

    return (
        <input
          placeholder={0}
          value={currentLevel}
          onChange={this._handleInputChange}
        />
    )
  }
}

export default NameEditor

因此,在这种情况下,您将有机会通过手动输入表单中的值来操纵组件状态。

解决方案#2(不按标志重新挂载)

尝试设置一些标志来分隔每次重新渲染时的外部 (getDerived...) 和内部 (Controlled Comp...) 状态更新。例如updateType

import PropTypes from 'prop-types'
import React from 'react'

export class NameEditor extends React.Component {
  static propTypes = {
    currentLevel: PropTypes.number
  }

  static defaultProps = {
    currentLevel: 0
  }

  constructor(props) {
    super(props)

    this.state = {
      currentLevel: 0,
      updateType: 'props' // by default we expecting update by incoming props
    } 
  }

  static getDerivedStateFromProps(nextProps, prevProps) {
    if (!prevState.updateType || prevState.updateType === 'props') {
      return {
        updateType: 'props',
        currentLevel: nextProps.currentLevel,
        exp: nextProps.exp
      }
    }

    if (prevState.updateType === 'state') {
      return {
        updateType: '' // reset flag to allow update from incoming props
      }
    }

    return null
  }

  _handleInputChange = e => {
    this.setState({
      currentLevel: e.target.value
    })
  }

  render() {
    const { currentLevel } = this.state

    return (
        <input
          placeholder={0}
          value={currentLevel}
          onChange={this._handleInputChange}
        />
    )
  }
}

export default NameEditor

PS这可能是一种反模式(希望丹永远不会看到这一点),但我现在无法在我的脑海中找到更好的解决方案。

解决方案#3

请参阅Sultan H.这篇文章下的帖子,关于带有来自包装器组件的显式回调的受控逻辑。

于 2019-07-23T07:53:31.387 回答
0

因为在设置状态后 React 调用渲染,但在渲染之前你总是调用getDerivedStateFromProps方法。

setState安排对组件状态对象的更新。当状态改变时,组件通过重新渲染来响应

getDerivedStateFromProps 在调用该render方法之前被调用,无论是在初始挂载时还是在后续更新时。它应该返回一个对象来更新状态,或者返回 null 来更新任何内容。

https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

于 2019-07-23T07:42:11.433 回答