引入useRef的动机
在任意一次渲染中 props和state 是始终保持不变的,如果props和state在不同的渲染中是相互独立的,那么使用到任何一个值同样也是独立的,下面以点赞按钮LikeButton组件为例:
import React, { useState } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
function handleClickAlert() {
setTimeout(()=>{
alert('you click on' + like)
},3000)
}
return (
<>
<button onClick={() => { setLike(like + 1) }}>
{like}👍
</button>
<button onClick={() => { handleClickAlert}}>
Alert!
</button>
</>
)
}
export default LikeButton
上例中,我们添加了一个Alert按钮,绑定了一个3的延时器,先按下Alert Button,再按下点赞按钮,即可看到 like 当前的值 与 弹框的值不同,弹框的值为3秒之前的like值。事件处理函数都陷入了闭包之中。
useRef的使用
作用一
props与state每次渲染都是相对独立的,那么想要在几次渲染中产生关系,就需要用到 useRef。
在TS中的类型定义文件 index.d.ts 中我们可以看到 useRef 返回的是MutableRefObject
function useRef<T>(initialValue: T): MutableRefObject<T>;
而接口MutableRefObject的定义为
interface MutableRefObject<T> {
current: T;
}
可以看到 useRef 里面就只有一个属性current,Ref对current 在所有的render里保持唯一的引用。因此所有对Ref的赋值和取值都是最终状态,不会在不同的render之间存在隔离。
现在我们将用到useRef修改之前的LikeButton:
import React, { useState, useRef } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
const likeRef = useRef(0)
function handleClickAlert() {
setTimeout(() => {
alert('you click on' + likeRef.current)
}, 3000)
}
return (
<>
<button onClick={() => { setLike(like + 1); likeRef.current++ }}>
{like}👍
</button>
<button onClick={() => { handleClickAlert }}>
Alert!
</button>
</>
)
}
export default LikeButton
这里其实仅仅改动了三处:添加了const likeRef = useRef(0),在setLike之后添加了likeRef.current++,以及将handleClickAlert中的 alert('you click on' + likeRef.current)
作用二:
虽然使用函数型组件要弱化生命周期的需求,但有时我们还需要在组件首次渲染或者组件更新的时候进行一些操作,这时我们也需要useRef来帮忙。
const didMountRef = useRef(false)
useEffect(() => {
if (didMountRef.current) {
console.log('this is updated')
} else {
didMountRef.current = true
}
})
在组件首次渲染时,didMountRef.current 为false,之后渲染时 didMountRef.current 为true,所以可以根据didMountRef.current值来进行相关操作。
访问DOM节点
useRef 返回的值传递给组件或DOM的ref属性,就可以通过Ref。current 的值访问组件或真实的DOM节点,从而可以对DOM进行一些操作。
我们现在添加一个 input,通过useRef添加focus的效果
const domRef = useRef<HTMLInputElement>(null)
useEffect(() => {
// domRef存在并且domRef.current也存在
if (domRef && domRef.current) {
// dom节点保存在domRef.current里面,Dom节点调用focus方法
domRef.current.focus()
}
})
return (
<input type='text' ref={domRef} />
)