React的Ref功能
1.String Ref
String Ref是个过时的API。因为String类型的Ref存在一些问题,将在未来的某个版本中被遗弃,不建议使用。
使用方式:this.refs.XXX获取DOM元素节点:
获取普通标签:
import React, { Component } from 'react';
class App extends Component {
componentDidMount() {
console.log('this.refs.XXX');
console.log(this.refs.h1Ref);
}
render() {
return <h1 ref='h1Ref'>Hello World!</h1>
}
}
export default App;
复制代码打印结果:
this.refs.xxx
<h1>Hello World!</h1>
获取react组件:此时可以调用组件上的方法
import React, { Component } from 'react';
class App extends Component {
componentDidMount() {
console.log(this.refs.childRef);
this.refs.childRef.handleLog(); // Child Component }
render() {
return (
<div>
<h1>Hello World!</h1>
<Child ref='childRef' count='1' />
</div>
)
}
}
class Child extends Component {
handleLog = () => {
console.log('Child Component');
}
render() {
const { count } = this.props;
return <h2>count: { count }</h2>
}
}
export default App
// 结果
组件的引用,包括上面挂载的函数和组件props等
2.Callback Ref
Callback Ref能助你更精细地控制何时 refs
被设置和解除,传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数。
React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。
使用方式:ref={element => (this.eleref = element)}
获取DOM元素节点:
import React, { Component } from "react";
class App extends Component {
componentDidMount() {
console.log("Callback Ref");
console.log(this.h1Ref);
}
render() {
return (
<div>
<h1 ref={element => (this.h1Ref = element)}>Hello World!</h1>
</div>
);
}
}
export default App;
// 打印结果
Callback Ref
<h1>Hello World!</h1>
获取子组件实例:【同上可调用方法】
import React, { Component } from "react";
class App extends Component {
componentDidMount() {
console.log("Callback Ref");
console.log(this.childRef);
this.childRef.handleLog();
}
render() {
return (
<div>
<h1>Hello World!</h1>
<Child ref={component => (this.childRef = component)} count="1" />
</div>
);
}
}
class Child extends Component {
handleLog = () => {
console.log("Child Component");
};
render() {
const { props } = this;
return <h1>count: {props.count}</h1>;
}
}
export default App;
3.Create Ref
该功能是React16.3中发布的,并且在类组件中推荐使用
使用React.createRef()
创建Refs,通过ref附加到组件中,对该节点的引用通过ref的current属性访问
React在组件挂载时给current传入DOM元素,并在组件卸载时传入null,ref会在生命周期函数前更新完成
使用方法和前两种类似,这里就不一一列举了
import React, { Component, createRef} from "react";
class App extends Component {
constructor(props) {
super(props);
this.h1Ref = createRef();
}
componentDidMount() {
console.log("React.createRef()");
console.log(this.h1Ref.current);
}
render() {
return <h1 ref={this.h1Ref}>Hello World!</h1>;
}
}
export default App;
4.useRef
当在函数组件中使用前三种ref,会抛出以下错误:Uncaught Invariant Violation: Function components cannot have refs. Did you mean to use React.forwardRef()?
原因:函数组件和class组件根本区别是,函数组件没有实例,所以无法使用实例对象,取而代之的为useRef,或使用forwardRef
作用:
获取DOM节点
获取组件实例
渲染周期之间共享数据存储(state修改会触发重新渲染,所以不能跨周期共享)
使用方法:与createRef类似,挂在current上
import React, { useEffect, useRef } from 'react';
function App() {
const h1Ref = useRef();
useEffect(() => {
console.log('useRef')
console.log(h1Ref.current)
}, [])
return <h1 ref={h1Ref}>Hello World!</h1>
}
export default App;
5 不同渲染周期之间的数据共享
该情况主要解决一类问题:类组件中,函数组件中的属性无法跨渲染周期进行数据共享,因为每次保存都会刷新数据重新渲染
示例程序:(其中的timer无法跨渲染周期,每次执行App函数,其都会被重置)
import React, { useState, useEffect, useRef } from "react";
function App() {
const [count, setCount] = useState(0);
// 把定时器设置成全局变量使用useRef挂载到current上
const timer = useRef();
// 首次加载useEffect方法执行一次设置定时器
useEffect(() => {
timer.current = setInterval(() => {
setCount(count => count + 1); // 此处必须传入函数,否则拿到的都是组将挂载时的初值!!
}, 1000);
}, []);
// count每次更新都会执行这个副作用,当count > 5时,清除定时器
useEffect(() => {
if (count > 5) {
clearInterval(timer.current);
}
});
return <h1>count: {count}</h1>;
}
export default App;
6 各方式使用总结
ref方式 | 定义 | 获取元素引用 | 执行挂载在元素内的函数 | 备注 |
---|---|---|---|---|
string ref | 标签内ref=‘xxx’ | this.refs.xxx | this.refs.xxx.handle() | 已过时 |
callback ref | 标签内ref={(element)=>{this.xxx=element}} | this.xxx | this.xxx.handle() | 精准控制ref |
create ref | 构造中this.xxx=createRef() 标签中ref={this.xxx} | this.xxx.current | this.xxx.current.handle() | 推荐使用 |
useRef() | 函数组件中const 函数标签中xxx=useRef() ref={xxx} | xxx.current | xxx.current.handle() | 函数组件中的ref替代方法 |
7 Ref转发
有时候需要传递ref
给子组件,需要用到React.forwardRef
函数,因为常规函数组件和 class 组件不接收 ref
参数,且 props 中也不存在 ref
。
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
// 此时的ref即React.forwardRef的参数之一
<FancyButton ref={ref}>Click me!</FancyButton>;
这个例子展示了,将ref转发到了最终的button标签上,其实Ref 转发不仅限于 DOM 组件,你也可以转发 refs到 class组件实例中。
HOC Ref转发
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
// 然后它就可以被挂载到被 LogProps 包裹的子组件上。
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
这时候logProps如何调用,怎么同时把Component和ref传进去
猜测:
const Test = logProps(<button>123</button>)
const ref = React.createRef()
<Test ref={ref}/>