React的受控组件与非受控组件的概念是相对于表单而言,如果一个 input 表单元素的值是由 React 控制,就其称为受控组件。非受控组件就像是运行在 React 体系之外的表单元素,当用户将数据输入到表单字段(例如 input,dropdown 等)时,React 不需要做任何事情就可以映射更新后的信息。然而,这也意味着,你无法强制给这个表单字段设置一个特定值。
受控组件
比如我们常用利用onChange
事件保存用户输入的信息,就是一个受控组件:
constructor() {
super();
this.state = {
value: "",
};
}
render() {
const { value } = this.state;
return (
<div>
<input
value={value}
onChange={(e) => this.setState({ value: e.target.value })}
/>
</div>
);
}
非受控组件
但是当使用input上传一个或多个文件时呢,因为它的值只能由用户设置,而不能通过代码控制。所以只能使用非受控组件。
ref的使用
创建非受控组件,我们一般借助ref
。使用ref
,有三种不同的方式。
1:使用字符串
我们将上面的代码改造一下:
render() {
const { value } = this.state;
return (
<div>
<p>{value}</p>
<input ref="input" />
<button onClick={this.btnClick}>获取input框中value的值</button>
</div>
);
}
btnClick = () => {
this.setState({
value: this.refs.input.value,
});
};
输入数值,点击按钮,就能看到value
值已经被我们获取到了。
由于设置的是字符串,每一次执行渲染时都会重新赋一次值,可能会发生内存泄露的隐患,因此控制台中会出现警告,官方不建议使用字符串的方式创建ref
2:使用回调函数的方式
使用回调函数创建ref
,回调函数中的参数就是使用ref
的元素。我们把这个参数保存一下,就可以使用ref
了。这种方法不会出现警告,在第三种方法出现之前,常用这种方式创建。
render() {
const { value } = this.state;
return (
<div>
<p>{value}</p>
<input ref={(input) => this.input = input} />
<button onClick={this.btnClick}>获取input框中value的值</button>
</div>
);
}
btnClick = () => {
this.setState({
value: this.input.value,
});
};
3:使用createRef
API
使用React.createRef()
创建一个ref
,生成一个对象,将使用到ref
的元素放在current
属性中
constructor() {
super();
this.state = {
value: "",
};
this.input = React.createRef();
}
render() {
const { value } = this.state;
return (
<div>
<p>{value}</p>
<input ref={this.input} />
<button onClick={this.btnClick}>获取input框中value的值</button>
</div>
);
}
btnClick = () => {
this.setState({
value: this.input.current.value,
});
};
注意,取到值需要this.input.current.value
,较比第二种方法,多了一个.current
。
当然了,如果使用hooks
,还有另一种方法,useRef
,由于是复习篇幅,hooks会在后续篇幅中呈现。
ref的转发
如果使用ref
,在父组件中想要获取子组件的元素怎么办呢?
这个时候就需要用到forwardRef
API了,它接收两个参数,一个是props
值,一个是声明好的ref
。较比于函数式组件,多了第二个参数。
我们现在父组件中创建好ref
,并传给子组件
export default class App extends React.Component {
constructor() {
super();
this.input = React.createRef();
}
render() {
return (
<div>
<Modal ref={this.input} />
<button onClick={this.btnClick}>显示value值</button>
</div>
);
}
btnClick = () => {
console.log('this.input.current.value :>> ', this.input.current.value);
}
}
接下来在组件中使用forwardRef
创建一个函数式组件,将ref
转发给需要用的的元素节点就行啦。
export default React.forwardRef((props, ref) => {
class Modal extends React.Component {
render() {
return (
<div>
<input ref={ref} defaultValue="11"></input>
</div>
);
}
}
return <Modal />;
});
上面代码中我们返回了一个class
的组件,当然你也可以直接写成函数式组件的形式。如果想要获取整个子组件,不用forwardRef
进行包裹就可以了。
结语
上面父组件获取子组件input
输入框的值显然不止这一种方法,也可以在父组件中定义好state
,写好改变state
的方法传递给子组件,这样也能拿到子组件元素中的值。那么合适适合使用ref呢?官网已经给出了答案:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
同样,不好的地方官方也进行了说明:
由于笔者还是个前端小萌新,难免会有纰漏错误,欢迎大佬们进行指点!