只读属性引起的 state 不随 setState 的触发而变化

这个是写某项目过程当中偶然遇到的,具体需求是随着数据的变化,实时改变一个 <input> 输入框的 value。因为用 React.js,自然很快就想到利用 React 组件的 state 特性就可以很方便的做到了。然而由此却引发了一些问题,就是我发现用 this.state.val 赋值,用 this.setState() 改变组件状态的时候,更新并没有实时展现出来(其实是根本没有展示出来),输入框里的内容并没有实时更新。

我们知道通过调用 React 组件的 setState() 方法改变组件的状态会触发组件重新渲染,则用 this.state 调用的 state 也会改变。所以说,输入框的值没有实时更新,很可能是因为组件根本没有重新渲染。一开始以为是回调的问题,于是使用 setState() 的第二个参数处理接下来的业务逻辑,可是发现并不是。最后发现,问题出现在对 input 的 value 属性的设置上。

考虑下面的这一段代码:

class App extends React.Component {
  
  constructor(props) {
    super(props);
    
    this.state = {
      val: ''
    };
    setTimeout(() => {
      this.setState({
        val: 'world!'
      });
    }, 1000);
  }
  
  render() {
    return (
      <div>
        <span>Hello</span>
        <input type='text' defaultValue={this.state.val} />
      </div>
    );
  }
  
}

如果正常,将 <App /> 渲染到页面上,你可以看到 Hello 。然后稍等 1s,你可以看到 Hello 。

这就是你说的正常显示?我读书少你别骗我!”

的确,如果单单读这段代码,一般人都应该能猜出想象中的结果——文本框最开始为空,但是一秒后它的值会变为 world。可是这里却并没有实现。文档并没有写错,this.setState() 过后会引起状态的变更,虽然是异步的但是迟早会做这一步的啊。为什么在这里无效呢。

问题的关键在于,我们为输入框赋值的时候用的属性是 defaultValue。这是一个只读属性。

“切,这就是你的锅了,好好的 value 属性不用,用个 poi 的 defaultValue!”

可是假如你把 defaultValue 改成 value,那么请你打开 console,那里已经有一条红色的警告消息等着你了。不信的话大家不妨试试:


React 要求我们设置这个 input 为只读,或者为这个 input 设置一个 onChange 事件,如果都不的话就用 defaultValue 代替 value。很多人为了省事就听了它的话用了 defaultValue(包括我),于是就踩进了坑。下面我们试着改一下上面那个组件的 render 方法好不好啊?吼啊!(((

javascript return (

Hello false} />
); ```

把 defaultValue 改成 value 并且绑定一个 onChange 事件。再次刷新页面你会发现出现了我们预期中的结果。至此我们可以大声讲出这个问题就是有这个只读的属性 defaultValue 引起的了。