受控组件 & 非受控组件

January 28, 2021

前言

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

受控组件(Controlled Component)

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

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

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

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

tsx
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>
);
}
}
tsx
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 属性时 ,它就是一个 非受控组件

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

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

默认值(Default Value)

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

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

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

为什么选择 “受控组件”

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

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>
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 中监听值的变化。

tsx
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>
);
}
}
tsx
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 始终推荐使用受控组件,因为受控组件完全可以避免这个问题。

tsx
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>
);
}
}
tsx
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


B2D1 (包邦东)

Written by B2D1 (包邦东)