react 受控表单必须初始化

本文最后更新于:2019年3月26日 晚上

react 受控表单必须初始化

场景

这些天在学习 React 的时候遇到了一个奇怪的问题,明明受控表单的双向绑定已经成功了,然而控制台还是会出现 react 的警告:

Warning: A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.

代码很简单,仅仅只是一个登录表单

/**
 * 用户类
 */
export class User {
  constructor({ username, password } = {}) {
    this.username = username
    this.password = password
  }
}
import React, { Component } from 'react'
import { User } from './User'

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      user: new User(),
    }
  }
  change = e => {
    const el = e.target
    const k = el.name
    const v = el.value
    const user = { ...this.state.user }
    user[k] = v
    this.setState({
      user,
    })
  }
  submit = () => {}
  reset = () => {
    this.setState({
      user: new User(),
    })
  }
  render() {
    const { username, password } = this.state.user
    return (
      <div>
        <div>
          <label htmlFor="username">用户名: </label>
          <input name="username" value={username} onChange={this.change} />
        </div>
        <div>
          <label htmlFor="password">密码: </label>
          <input name="password" value={password} onChange={this.change} />
        </div>
        <div>
          <button onClick={this.submit}>登录</button>
          <button onClick={this.reset}>重置</button>
        </div>
      </div>
    )
  }
}

export default App

在 App 组件的 constructor 中明明已经通过 new User() 初始化了 user 属性,然而在输入的时候,还是会出现警告。

注:此时在输入框中输入值,确实会影响到 react state 中的 user 属性,反之亦然。只有一点,当重置表单,即使用 this.setState({user: new User()}) 重置 user 对象无法影响到页面上输入框的值。

此处出现了两个问题

  1. 为什么在输入的时候会出现警告
  2. 为什么重置之后输入框的值没有变化

解决

最终,吾辈在 StackOverflow 上找到了答案。
很重要的一句话:对于要控制的输入,其值必须与状态变量的值相对应。
最初并未满足这个条件,值为 nullstate 属性会被 react 视为未定义,导致表单最初是不受控制的。但是,当 onChange 第一次被触发的时候,this.state.user.username 就被设置了。此时,满足了条件,从非受控表单转换为了受控表单并导致了控制台的警告。
同理,当使用 this.setState({user: new User()}) 重置的时候,又变成了非受控表单,所以这里的绑定再次失效了。

注: react 使用 == 而非 === 比较是否为 null,而 null == undefined 的值为 true,所以。。。

那么,知道问题了之后,我们只要保证初始值 val != null 即可。
例如上面的代码可以修改 User.js

/**
 * 用户类
 */
export class User {
  constructor({ username = '', password = '' } = {}) {
    this.username = username
    this.password = password
  }
}

那么,关于 react 中的受控表单初始化的问题便到此为止了。可想而知,react 的坑还有很多没有踩完呢