React Hooks之useRef

167 阅读3分钟

一、useRef

1、定义

当你想要记住某些信息,但又不想触发组件的重新渲染时,就可以使用ref
ref是一个普通的JS对象,具有可以被读取和修改的current属性;
该ref对象在React的整个生命周期中都存在;ref存储的是不影响组件视图的信息;
ref与state的区别就是:state更改会触发组件的重新渲染,但是更改ref不会;

2、语法与参数

const ref = useRef(initialValue); // useRef用于创建一个JS对象,每次渲染返回的都是同一个ref对象;

(1)initialValue
    useRef调用时赋值给ref对象的current属性的值,该值可以是任意类型,只在首次渲染时使用;

(2)返回值
    useRef函数调用返回一个只有一个current属性的对象;在后续过程中,useRef返回的都是同一个对象;
    修改ref的current属性时,React不会重新渲染组件;【ref是一个普通的JS对象,改变ref不会触发组件的重新渲染】

3、useRef的使用场景

  • 操作DOM节点;
  • 缓存存储信息,如定时器ID等;

4、自定义组件ref的获取

使用forwardRef包裹子组件;

import { forwardRef } from 'react'; 
const MyInput = forwardRef(({ value, onChange }, ref) => { 
    return (
        <input value={value} onChange={onChange} ref={ref} /> 
    ); 
}); 
export default MyInput;

5、【实践】useRef在原生DOM节点上的使用

(1)示例

// 类型声明:正确 
const imgRef = useRef<HTMLImageElement>(null); 
// 类型声明:错误(原因:类型不匹配【声明类型为HTMLImageElement | null,初始赋值为undefined】) 
/** 
 * 报错方式:
 * Type 'HTMLImageElement | null | undefined' is not assignable to type 'HTMLImageElement | null'. 
 * Type 'undefined' is not assignable to type 'HTMLImageElement | null'. 
 */ 
const imgRef = useRef<HTMLImageElement | null>(); 
<img ref={imgRef}/>

(2)在Input标签上的使用

// 与ts结合使用,注意声明ref类型:input 
const inputRef = useRef<Input>(null); 
// input标签的使用 
<input ref={inputRef} placeholder="请输入内容"/> 
// input标签获取焦点 
useEffect(() => { 
    inputRef.current?.focus(); 
    // 赋值 inputRef.current?.setValue('赋值'); 
}, []);

(3)自定义ref类型

// 自定义useRef类型: 
const ExampleRef = forwardRef<ref类型, Props>((_, ref) => { 
    React.useImperativeHandle(ref, () => ({ 
        name // 暴露给父组件的参数 
    }); 
}); 
// 在父组件中的使用 
const examRef = useRef<ref类型(自定义类型))>(null); 
<ExampleRef ref={examRef}/>

6、为什么依赖数组中可以省略ref?

    因为ref具有稳定的标识:React保证每次渲染中调用useRef产生的对象的引用都是相同的,它不会导致组件的重新渲染;

    但,如果ref是从组件传递的,则必须在依赖数组中指定它;

二、useRef源码

1、mountRef - 首次渲染

function mountRef<T>(initialValue: T): {current: T} { 
    const hook = mountWorkInProgressHook(); 
    const ref = {current: initialValue}; 
    hook.memoizedState = ref; // 将值缓存在memoizedState中 
    return ref; 
}

    可以看到,在首次渲染调用useRef时,React会创建一个具有current属性的对象ref,且current的值将会被initialValue赋值,然后会将这个对象ref存储在memoizedState中;

2、updateRef - 更新渲染

function updateRef<T>(initialValue: T): {current: T} { 
    const hook = updateWorkInProgressHook(); 
    return hook.memoizedState; // 直接返回我们之前缓存的对象 
}

    当更新时,React会直接返回memoizedState对象,这与首次渲染时的对象是同一个引用;这也就是为什么更改ref不会触发重新渲染。