React 16.4 - 手动表单输入填写及其来自 getDerivedStateFromProps 的更新?

React 16.4 - manual form input fill along with its updates from getDerivedStateFromProps?

更新 React 16.4 后我遇到了一个问题,其中我们对 getDerivedStateFromProps 逻辑进行了一些重大更改。现在它会在 incomingown 组件的道具上的每个组件更新时触发。

所以,我已经阅读了文档和手册,但仍然无法弄清楚表单输入字段应该基于传入的 props (controlled component) 的情况,同时,可以由用户自己输入修改吗?

我也试过这个post,但它只涵盖了一次性更新的情况,而不是手动输入的情况: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

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

setState 安排更新组件的状态对象。当状态改变时,组件响应 re-rendering

getDerivedStateFromProps 在调用 render 方法之前被调用,无论是在初始安装还是在后续更新中。它应该 return 一个对象来更新状态,或者 null 不更新任何内容。

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

更新:

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

该方法的工作方式是,它会在每次 newPropssetState 调用发生时被调用。

因此,行为如下:

  1. 您使用处理程序更改值。
  2. getDerivedStateFromProps get 被调用,它将从 parent 组件中获取 currentLevel 值,它仍然没有被修改,因为我们没有在那里做任何更改,因此,它将使用未修改的 parent 组件中存在的值覆盖调用您的处理程序的新值。

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

所以:

  1. 向您的 parent 组件添加一个 handleCurrentLevelChange 方法,它将只有一个参数 e.target.value,它的工作是修改 [=37] 处的 currentLevel =]parent状态.
  2. 将您创建的 handleCurrentLevelChange 传递给您的 NameEditor 您想要的名称,可能是相同的名称。
  3. 修改你的child的 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 属性 和处理程序的新行为将允许更改同时发生在您的 child 和 parent。

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

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

您可能需要根据传入的 prop 为其提供密钥,从而在每次外部 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

P.S。 这可能是一个 anti-pattern(希望 Dan 永远不会看到这个),但我现在无法在脑海中找到更好的解决方案。

解决方案 #3:

参见 Sultan H. post 下的内容,关于带有来自包装器组件的显式回调的受控逻辑。