受控组件 & 非受控组件

January 28, 2021

前言

虽然在日常工作中经常使用 React 框架,但是对受控组件和非受控组件的概念十分模糊。

受控组件(Controlled Component)

一句话总结:当 input 拥有 value 属性时,他就是一个 受控组件

export default class App extends React.Component {
  render() {
    return <input type="text" value="hello!" />;
  }
}

任何用户输入都不会被渲染,因为 React 已经显式声明了 value 为 “hello!”,不会再变更。

我们需要手动在 onChange event handler 中更新 value,即调用 setState().

export default class App extends React.Component {
  state = { val: "hello!" };

  handleChange(e) {
    this.setState({ val: e.target.value });
  }

  render() {
    return (
      <div className="App">
        <input
          type="text"
          value={this.state.val}
          onChange={e => this.handleChange(e)}
        />
      </div>
    );
  }
}

总而言之,受控组件不维护自己内部的值,它只会依据 props(this.state.val) 进行渲染

非受控组件(UnControlled Component)

一句话总结:当 input 没有 value 属性时 ,它就是一个 非受控组件

export default class App extends React.Component {
  render() {
    return <input type="text" />;
  }
}

该 input 初始化会渲染空的值,任何用户输入都会被立即渲染,并且同样可以使用 onChange 事件监听其变化。需要注意的是,input 会在内部维护自己的 value,不由外界决定

默认值(Default Value)

如果想给非受控组件一个初始值,可以使用 defaultValue 属性,该属性只会在初次渲染起作用。

export default class App extends React.Component {
  render() {
    return <input type="text" defaultValue="hello!" />;
  }
}

有了默认值的存在,非受控组件在功能上媲美受控组件。

为什么选择 “受控组件”

现在让我们脱离 React 框架的约束,回归到最传统的 HTML 中:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body>
    <input type="text" id="input" value="hello!" />
    <script>
      const input = document.getElementById("input");
      input.oninput = function(e) {
        console.log("attribute - value: ", this.getAttribute("value"));
        console.log("target - value: ", e.target.value);
      };
    </script>
  </body>
</html>

这显然对应 React 中的非受控组件,input 的值由自身维护,当改变输入框的值时,虽然 e.target.value 会及时更新,但 getAttribute('value') 始终为 “hello!”。

让我们回到 React 中,使用 inputRef 获取 input 的 DOM reference,并为 input 设置一个 defaultValue,使它具备初始值,并在 onChange event handler 中监听值的变化。

export default class App extends React.Component {
  inputRef = React.createRef();

  handleChange() {
    console.log(
      "attribute - value: ",
      this.inputRef.current.getAttribute("value")
    );
    console.log("target - value: ", this.inputRef.current.value);
  }

  render() {
    return (
      <div className="App">
        <input
          ref={this.inputRef}
          type="text"
          defaultValue="hello!"
          onChange={() => this.handleChange()}
        />
      </div>
    );
  }
}

很遗憾,this.inputRef.current.value 会实时更新,但 this.inputRef.current.getAttribute("value") 永远为 defaultValue.

你可以点此链接,看实际的 demo:https://codesandbox.io/s/controlled-uncontrolled-26nue?file=/src/App.js

这也就是为什么 React 始终推荐使用受控组件,因为受控组件完全可以避免这个问题。

export default class App extends React.Component {
  state = { val: "hello!" };

  handleChange(e) {
    this.setState({ val: e.target.value }, () => {
      console.log("attribute - value: ", e.target.getAttribute("value"));
      console.log("target - value: ", e.target.value);
    });
  }

  render() {
    return (
      <div className="App">
        <input
          type="text"
          value={this.state.val}
          onChange={e => this.handleChange(e)}
        />
      </div>
    );
  }
}

当改用受控组件的写法后,input 的值由 props(this.state.val)决定,val 会随着 setState() 更新,所以 e.target.getAttribute('value') 也会随之更新。

参考资料

https://zhenyong.github.io/react/docs/forms.html


Written by B2D1(包邦东)