1、什么是 useRef
-
使用:const refContainer = useRef(initialValue)
-
特点
- 和类组件的实例属性几乎完全一致
- 返回一个可变的 ref 对象 ( current 属性 )
- 跨越渲染周期存储数据
- 组件的更新,不会更改 current 的值
- 更新 current 值,不会导致组件的更新
- 值的引用,随时可以立刻拿到更新后最新的值( 类似于组件的this )
-
使用场景
- 定义一个类似 global 的变量、定时器(获取到最新值)
- 绑定 dom 元素、子组件
2、变量的"引用"
// 1、操作 - 使用 useState
// 1、点击第一个按钮 6 下
// 2、立刻点击 Alert 按钮
// 3、马上再点击第一个按钮4下(3s内完成点击)
// 4、等待控制台输出结果:6
import React, { useState } from 'react';
const LikeButton = () => {
const [like, setLike] = useState(0)
function handleAlertClick() {
setTimeout(() => {
// 形成闭包,所以弹出来的是当时触发函数时的like值
console.log(`you clicked on ${like}`)
}, 3000)
}
return (
<>
<button onClick={() => setLike(like + 1)}>{like}赞</button>
<button onClick={handleAlertClick}>Alert</button>
</>
)
}
export default LikeButton
// 2、// 操作 - 使用 useRef
// 1、点击第一个按钮 6 下
// 2、立刻点击 Alert 按钮
// 3、马上再点击第一个按钮4下(3s内完成点击)
// 4、等待控制台输出结果:10
import React, { useRef } from "react";
const LikeButton = () => {
let like = useRef(0); // 等同于声明 global 全局变量
function handleAlertClick() {
setTimeout(() => {
alert(`you clicked on ${like.current}`);
}, 3000);
}
return (
<>
// 更新 like 值时并不会 re-render
<button
onClick={() => {
like.current = like.current + 1;
}}>
{like.current}赞
</button>
// 显示更改后的值
<button onClick={handleAlertClick}>Alert</button>
</>
);
};
export default LikeButton;
3、绑定DOM、子组件
- 绑定DOM
- 绑定子组件(函数组件)
- forwardRef:转发 refs 到 DOM 组件
- useImperativeHandle:向外暴露自定义方法
// 1、绑定DOM元素(自动获取焦点)
import React, { MutableRefObject, useRef } from 'react'
const TextInputWithFocusButton = () => {
const inputEl = useRef(null) // 绑定DOM元素
const handleFocus = () => {
inputEl.current.focus()
}
return (
<div>
<input ref={inputEl} type="text" />
<button onClick={handleFocus}>Focus the input</button>
</div>
)
}
export default TextInputWithFocusButton
// 2、绑定子组件,hooks组件中(必须使用forwardRef)
import React, {useState, useRef} from 'react'
import Son1 from './Son1'
const About = () => {
const inputRef = useRef()
const cliBtn = () => {
inputRef.current.focus()
}
return (
<div>
{/* 将ref定义到父组件 */}
<Son1 ref={inputRef}></Son1>
<button onClick={cliBtn}>点击</button>
</div>
)
}
export default About
// 子组件
import React, {useRef, forwardRef} from 'react
// 1、子组件使用 forwardRef 包裹
// 2、此时子组件接受两个参数
// 参数1: props
// 参数2:父组件ref对象
// 3、缺点
// ref 绑定的 input 元素的所有方法都直接暴露给App
// App组件可以进行任意的操作
// 不能手动暴漏方法
const Son = (props, inputRef) => {
const inputEl = useRef(null) // 绑定DOM元素
console.log('props', props)
return (
<div>
<input ref={inputRef} type="text" />
</div>
)
}
export default forwardRef(Son)
// 3、绑定子组件,hooks组件中,向外暴露自定义方法(必须使用forwardRef、并配合useImperativeHandle)
import React, {useState, useRef} from 'react'
import Son1 from './Son1'
import Son2 from './Son2'
const About = () => {
const inputRef = useRef()
const cliBtn = () => {
inputRef.current.focus()
}
return (
<div>
{/* 将ref定义到父组件 */}
{/* <Son1 ref={inputRef}></Son1> */}
<Son2 ref={inputRef}></Son2>
<button onClick={cliBtn}>点击</button>
</div>
)
}
export default About
// 子组件
import React, {useRef, forwardRef, useImperativeHandle} from 'react'
inteface IRef {
focus: () => void
}
// 1、子组件使用 forwardRef 包裹
// 2、此时子组件接受两个参数
// 参数1: props
// 参数2:父组件ref对象
// 3、向外暴露自定义方法
const Son2 = (props, ref: Ref<IRef | undefined>) => {
const inputRef = useRef(null) // 绑定DOM元素
console.log('props', props)
// 向外暴露自定义方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
}
}))
return (
<div>
<input ref={inputRef} type="text" />
</div>
)
}
export default forwardRef(Son2)
4、useRef 与 createRef 的区别( 组件3个阶段)
- 第一:useRef 与 createRef 没有差别
- 第二:
- createRef 每次render都会返回个新的引用
- useRef 不会 随着组件的更新而重新创建
- 第三:两者都会销毁
import React, { useState, useRef, createRef } from 'react'
const RefDifference: React.FC = () => {
let [renderIndex, setRenderIndex] = useState(1)
let refFromUseRef = useRef<number>()
let refFromCreateRef = createRef()
// 首次为空,重新渲染后保留之前的值,不再重新赋值
if (!refFromUseRef.current) {
refFromUseRef.current = renderIndex
}
// 首次为空,重新渲染后还是为空,即每次赋新值
if (!refFromCreateRef.current) {
refFromCreateRef.current = renderIndex
}
// 任何data、props值的改变都会重新渲染组件
return (
<>
<p>{renderIndex}</p>
<p>{refFromUseRef.current}</p>
<p>{refFromCreateRef.current}</p>
<button onClick={() => setRenderIndex((prev) => prev + 1)}>
Cause re-render
</button>
</>
)
}
export default RefDifference
5、useRef 源码
// mount 阶段
function mountRef<T>(initialValue: T): {|current: T|} {
// 获取 hook 对象
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
hook.memoizedState = ref;
return ref;
}
// update 阶段
function updateRef<T>(initialValue: T): {|current: T|} {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}