DOM这块是非之地,React并不想涉足其中,所以更多时候会我们会使用受控组件。
但是,如果想蹚浑水,非操作DOM 不可,React也提供了ref
这一解决方法。
不过,ref
只能在 class组件上使用,不能用于function组件。
所谓受控组件,是指 React组件的state同步记录表单数据,表单数据由React控制;
所谓非受控组件,就是指 表单数据不受React控制,而是由DOM节点自己控制。
而React里获取DOM的方式正是通过ref
。
使用ref
ref是特殊的props,使用ref有两种方式。
ref是一个对象
//react.development.js
function createRef() {
var refObject = {
current: null
};
{
Object.seal(refObject);
}
return refObject;
}
React.createRef()
返回一个带current属性的对象,且current的初始值是null。
ref作用在HTML元素上,挂载完成后,current属性就是该元素所在DOM节点;
ref作用在React组件上,挂载完成后,current属性就是该组件实例。
import React from "react";
import ReactDOM from "react-dom";
class NameForm extends React.Component{
constructor(props){
super(props);
this.inputRef = React.createRef();
console.log(Object.assign({},this.inputRef));
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e){
console.log(this.inputRef.current.value);
e.preventDefault();
}
componentDidMount(){
console.log(Object.assign({},this.inputRef));
}
render(){
return (
<form onSubmit={this.handleSubmit}>
<label>名字:<input type='text' name='name' ref={this.inputRef} defaultValue='Nicholas'/></label>
<input type='submit' value='提交'/>
</form>
)
}
}
ReactDOM.render(
<NameForm/>,
document.getElementById('root')
)
ref是一个函数
ref是一个函数时,组件装载过程中会调用这个函数(在componentDidMount之前调用),且函数参数是ref所在DOM节点或组件实例。
import React from "react";
import ReactDOM from "react-dom";
class NameForm extends React.Component{
constructor(props){
super(props);
this.inputRef = this.inputRef.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e){
console.log(this.input.value);
e.preventDefault();
}
inputRef(input){
this.input = input;
}
render(){
return (
<form onSubmit={this.handleSubmit}>
<label>名字:<input type='text' name='name' ref={this.inputRef} defaultValue='Nicholas'/></label>
<input type='submit' value='提交'/>
</form>
)
}
}
ReactDOM.render(
<NameForm/>,
document.getElementById('root')
)
React的思想是UI=render(data)
,数据驱动视图,开发人员只需要关心要渲染成什么样子,不必关心怎么去操作DOM、如何实现渲染。
非受控组件则让我们重新回到了操作DOM的时代。
如果操作DOM是非必要的,尽量使用受控组件,让React全权在握,毕竟它具有这种能力。
转发ref
有两种方式。
第一种,通过props传递
import React from "react";
import ReactDOM from "react-dom";
class CustomTextInput extends React.Component{
render(){
return <input type='text' ref={this.props.inputRef} />
// return <input type='text' ref={this.props.ref} />
}
}
class Form extends React.Component{
constructor(props){
super(props);
this.inputElm = React.createRef();
}
componentDidMount(){
this.inputElm.current.focus();
}
render(){
return <CustomTextInput inputRef={this.inputElm} />
// return <CustomTextInput ref={this.inputElm} />
}
}
ReactDOM.render(
<Form />,
document.getElementById('root')
)
将ref作为props从父组件传递给子组件,但如果像下面这样,
class Form extends React.Component{
render(){
return <CustomTextInput ref={this.inputElm} />
}
}
class CustomTextInput extends React.Component{
render(){
return <input type='text' ref={this.props.ref} />
}
}
React会给出警告:
‘ref’ is not a prop. Trying to access it will result in ‘undefined’ being returned. If you need to access the same value within the child component, you should pass it as a different prop.
因为ref
是特殊的props,子组件无法获取,但可以使用其他名称,比如inputRef
来传递 。
第二种,React.forwardRef(render)
React.forwardRef(render)
render
,接收两个参数,返回一个JSX元素。
第一个参数:从父组件传递过来的props,该props不包含ref
;
第二个参数:从父组件传递过来的ref
。
- 使用render的第一个参数:props
import React from "react";
import ReactDOM from "react-dom";
const AutoFocusTextInput = React.forwardRef((props,ref) => {
//console.log(props);
//console.log(ref);
return <input type='text' ref={props.inputRef} />
})
class Form extends React.Component{
constructor(props){
super(props);
this.inputElm = React.createRef();
}
componentDidMount(){
this.inputElm.current.focus();
}
render(){
return <AutoFocusTextInput inputRef={this.inputElm} />
}
}
ReactDOM.render(
<Form />,
document.getElementById('root')
)
- 使用render的第二个参数:ref
import React from "react";
import ReactDOM from "react-dom";
const AutoFocusTextInput = React.forwardRef((props,ref) => {
//console.log(props);
//console.log(ref);
return <input type='text' ref={ref} />
})
class Form extends React.Component{
constructor(props){
super(props);
this.inputElm = React.createRef();
}
componentDidMount(){
this.inputElm.current.focus();
}
render(){
return <AutoFocusTextInput ref={this.inputElm} />
}
}
ReactDOM.render(
<Form />,
document.getElementById('root')
)
- React.forwardRef(render)在高阶组件中的应用
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./app.js";
ReactDOM.render(<App/>,document.querySelector('#root'));
//app.js
import React from 'react';
import FancyButton from "./fancyButton.js";
class App extends React.Component{
constructor(props){
super(props);
this.elm = React.createRef();
this.handleClick = () => {
console.log(this.elm.current);
}
}
render(){
return <FancyButton
label="click me"
ref={this.elm}
handleClick={this.handleClick}
/>
}
}
export default App;
//fancyButton.js
import React from "react";
import "./fancyButton.css";
const FancyButton = React.forwardRef((props,ref) => {
const {label,handleClick} = props;
return <button className='fancyButton' ref={ref} onClick={handleClick}>{label}</button>
})
export default FancyButton;
//fancyButton.css
.fancyButton{
outline:0;
background-color:lightsteelblue
}
点击click me,控制台打印出<button class="fancyButton">click me</button>
。
高阶组件其实就是一个函数,它接受一个组件作为参数,返回一个新组件。
修改fancyButton.js,其中,HOC(FancyButton)
,组合了 原组件FancyButton
,返回了新组件NewComponent
。
//fancyButton.js
import React from "react";
import "./fancyButton.css";
function HOC(WrappedComponent){
class NewComponent extends React.Component{
componentDidUpdate(props){
console.log(props,this.props);
}
render(){
return <WrappedComponent {...this.props} />
}
}
return NewComponent;
}
const FancyButton = React.forwardRef((props,ref) => {
const {label,handleClick} = props;
return <button className='fancyButton' ref={ref} onClick={handleClick}>{label}</button>
})
export default HOC(FancyButton);
点击click me,控制台打印的不是<button class="fancyButton">click me</button>
,而是NewComponent
。
怎么办?React.forwardRef(render)
可以帮上忙。
//fancyButton.js
import React from "react";
import "./fancyButton.css";
function HOC(WrappedComponent){
class NewComponent extends React.Component{
componentDidUpdate(props){
console.log(props,this.props);
}
render(){
//分离出theRef,除theRef外的其它props直接透传
const {theRef,...restProps} = this.props;
return <WrappedComponent ref={theRef} {...restProps} />
}
}
return React.forwardRef((props,ref) => {
return <NewComponent {...props} theRef={ref} />
});
}
const FancyButton = React.forwardRef((props,ref) => {
const {label,handleClick} = props;
return <button className='fancyButton' ref={ref} onClick={handleClick}>{label}</button>
})
export default HOC(FancyButton);