import {useRef} from 'react';
在函数式组件中,useRef 是一个返回可变引用对象的函数。该对象.current
属性的初始值为useRef传入的参数initialVale
。
返回的对象将在组件整个生命周期中持续存在。
const ref = useRef(initialValue);
通常情况下,useRef有两种用途,
访问DOM节点或React元素
尽管使用React时,我们推荐大家仅仅只关注数据,但也存在一些场景,我们需要去访问DOM节点才能达到目的。例如下面这个例子。
import React, {Component, createRef} from "react";
export default class Demo extends Component {
textInput = createRef();
focusTextInput = () => {
if (this.textInput.current) {
this.textInput.current.focus();
}
}
render() {
return (
<>
>
);
}
}
案例很简单,通过点击别的按钮,让input组件获得焦点。
在函数组件中,我们可以通过useRef达到同样的目的。
import React, {useRef} from "react";
export default function Demo() {
const inputRef = useRef<HTMLInputElement>(null);
const focusTextInput = () => {
if (inputRef.current) {
inputRef.current.focus();
}
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={focusTextInput}>点击我让input组件获得焦点</button>
</>
);
}
这个例子使用useRef替换掉了createRef。
接下来思考一个问题,默认支持的input组件拥有.focus
方法,调用该方法,input组件就能够获得焦点。那如果我们自己要封装一个Input组件,并且也希望该Input组件能够拥有.focus
和.blur
方法,我们应该怎么办?
利用React提供的 api forwardRef
就能够达到这个目的。forwardRef方法能够传递ref引用,具体使用如下
// 官网的案例
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
我们也可以使用同样的方式,自定义Input组件。
import React, {forwardRef, useState, ChangeEvent} from 'react';
export interface InputProps {
value?: string,
onChange?: (value: string) => any
}
function Input({value, onChange}: InputProps, ref: any) {
const [_value, setValue] = useState(value || '');
const _onChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setValue(value);
onChange && onChange(value);
}
return (
<div>
自定义Input组件
<input value={_value} onChange={_onChange} ref={ref} />
</div>
);
}
export default forwardRef(Input);
如果我们想要给.focus
改个名字,或者返回其他额外的属性或者方法,我们可以使用useImperativeHandle
。
useImperativeHandle
可以让我们在使用ref时自定义暴露给父组件的实例值。
import React, {useRef, useImperativeHandle, forwardRef, Ref, useState, ChangeEvent} from 'react';
export interface InputProps {
value?: string,
onChange?: (value: string) => any
}
export interface XInput {
focus: () => void;
blur: () => void;
sayHi: () => void
}
function Input({value, onChange}: InputProps, ref: Ref<XInput>) {
const inputRef = useRef<HTMLInputElement>(null);
const [_value, setValue] = useState(value || '');
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current && inputRef.current.focus()
},
blur: () => {
inputRef.current && inputRef.current.blur()
},
sayHi: () => {
console.log('hello, world!');
}
}));
const _onChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
console.log(value);
setValue(value);
onChange && onChange(value);
}
return (
<div>
自定义Input组件
<input value={_value} onChange={_onChange} ref={inputRef} />
</div>
);
}
export default forwardRef(Input);
使用一下这个Input组件。
import React, { useRef, useState } from "react";
import Input from './components/Input';
import { Button } from "antd-mobile";
const Demo = () => {
const textInput = useRef<any>(null);
const [text, setText] = useState('')
const focusTextInput = () => {
if (textInput.current) {
textInput.current.focus();
textInput.current.sayHi();
}
}
return (
<>
<Input ref={textInput} onChange={setText} value={text} />
<Button onClick={focusTextInput}>点击我,input组件获得焦点</Button>
<div>{text}</div>
</>
);
}
export default Demo;
在函数组件中,因为每次re-render
就意味着函数重新执行一次,因此在函数内部保持变量引用是一件我们需要思考的事情。
在前面学习useState时我们知道,使用useState定义变量,可以做到这样的事情,同样的,利用ref的.current
,也可以。
一个很常见的应用场景就是对于定时器的清除。我们需要确保setInterval
的执行结果timer的引用,才能准确的清除对应的定时器。
import React, { useRef, useEffect } from 'react';
export default function Timer() {
const timerRef = useRef<NodeJS.Timeout>();
useEffect(() => {
timerRef.current = setInterval(() => {
console.log('do something');
}, 1000);
// 组件卸载时,清除定时器
return () => {
timerRef.current && clearInterval(timerRef.current);
}
}, []);
return (
<div>
// ...
</div>
)
}
和useState不同,如果一个状态或者数据会影响DOM的渲染结果,一定要避免使用useRef来保持引用
通过ref访问DOM节点,除了配合useRef之外,仍然可以使用回调的形式获取。
<input type="text" ref={(node) => input = node} />
但是在函数组件中,由于我们还要思考如何使用一个引用稳定的变量来关联节点,这会比直接使用useRef更麻烦。因此,函数组件中推荐优先使用useRef。
本系列文章的所有案例,都可以在下面的地址中查看
https://github.com/advance-course/react-hooks
本系列文章为原创,请勿私自转载,转载请务必私信我