React 16.4 - 手动表单输入填写及其来自 getDerivedStateFromProps 的更新?
React 16.4 - manual form input fill along with its updates from getDerivedStateFromProps?
更新 React 16.4 后我遇到了一个问题,其中我们对 getDerivedStateFromProps
逻辑进行了一些重大更改。现在它会在 incoming 和 own 组件的道具上的每个组件更新时触发。
所以,我已经阅读了文档和手册,但仍然无法弄清楚表单输入字段应该基于传入的 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
.
该方法的工作方式是,它会在每次 newProps 或 setState 调用发生时被调用。
因此,行为如下:
- 您使用处理程序更改值。
getDerivedStateFromProps
get 被调用,它将从 parent 组件中获取 currentLevel
值,它仍然没有被修改,因为我们没有在那里做任何更改,因此,它将使用未修改的 parent 组件中存在的值覆盖调用您的处理程序的新值。
要解决这个问题:我们需要一个来自 parent 组件的回调函数,它与 handleInputChange 的工作相同。
所以:
- 向您的 parent 组件添加一个
handleCurrentLevelChange
方法,它将只有一个参数 e.target.value
,它的工作是修改 [=37] 处的 currentLevel
=]parent状态.
- 将您创建的
handleCurrentLevelChange
传递给您的 NameEditor 您想要的名称,可能是相同的名称。
- 修改你的child的 handlr如下:
_handleInputChange = (e, cb) => {
this.setState({
currentLevel: e.target.value
}, () => {
cb && cb(e.target.value) //this makes the callback optional.
});
}
- 修改您的 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 下的内容,关于带有来自包装器组件的显式回调的受控逻辑。
更新 React 16.4 后我遇到了一个问题,其中我们对 getDerivedStateFromProps
逻辑进行了一些重大更改。现在它会在 incoming 和 own 组件的道具上的每个组件更新时触发。
所以,我已经阅读了文档和手册,但仍然无法弄清楚表单输入字段应该基于传入的 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
.
该方法的工作方式是,它会在每次 newProps 或 setState 调用发生时被调用。
因此,行为如下:
- 您使用处理程序更改值。
getDerivedStateFromProps
get 被调用,它将从 parent 组件中获取currentLevel
值,它仍然没有被修改,因为我们没有在那里做任何更改,因此,它将使用未修改的 parent 组件中存在的值覆盖调用您的处理程序的新值。
要解决这个问题:我们需要一个来自 parent 组件的回调函数,它与 handleInputChange 的工作相同。
所以:
- 向您的 parent 组件添加一个
handleCurrentLevelChange
方法,它将只有一个参数e.target.value
,它的工作是修改 [=37] 处的currentLevel
=]parent状态. - 将您创建的
handleCurrentLevelChange
传递给您的 NameEditor 您想要的名称,可能是相同的名称。 - 修改你的child的 handlr如下:
_handleInputChange = (e, cb) => {
this.setState({
currentLevel: e.target.value
}, () => {
cb && cb(e.target.value) //this makes the callback optional.
});
}
- 修改您的 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 下的内容,关于带有来自包装器组件的显式回调的受控逻辑。