Hooks核心03
useCallback:缓存回调函数(避免子组件重复渲染)
const cb = useCallback(fn,deps)
这里的 fn 是定义的回调函数,deps 是依赖的变量数组。只有当某个依赖变量发生变化时,才会重新声明 fn 这个回调函数。
在 react 函数组件中,每一次的 ui 变化,都是通过重新执行函数来完成了,和 class 组件不一样,没有一个直接的方式在多次渲染之间维持一个状态。
如下,每次创建新函数会让接收事件处理函数的组件重新渲染。
import React, { useState } from "react"
function Parent() {
const [state, setState] = useState(0)
const handleChirden = () => {
setState(state + 1)
}
// 每次创建新函数的方式会让接收事件处理函数的组件重新渲染
return <Children handler={handleChirden} />
}
所以 useCallback
可以通过定义依赖项:只有当deps
发生变化时,才需要重新定义一个回调函数
import React, { useState, useCallback } from "react"
function Parent() {
const [state, setState] = useState(0)
// 只有当state发生变化时,才会重新创建回调函数
const handleChirden = useCallback(() => {
setState(state + 1)
}, [state])
return <Children handler={handleChirden} />
}
useMemo:缓存计算的结果(避免重复计算、避免子组件重复渲染)
const result = useMemo(fn,deps)
这里的 fn 是产生所需数据的一个计算函数。通常来说,fn 会使用 deps 中声明的一些变量来生成一个结果没用来渲染出最终的 ui。
场景:如果某个数据时通过其他数据计算得到的,那么只有当用到的数据,也就是依赖项的数据发生变化时,才需要重新计算。
避免重复计算
举个 🌰:当你需要根据购物车筛选出你想要的商品时,你可能会如下实现:
import React, { useState } from "react"
export default const Demo = () => {
// 后端返回的购物车数据
const [shopCart,setShopCart] = useState([])
// 搜索关键词
const [keyWord,setKeyWord] = useState[""]
let goods = null
if(shopCart?.length>0 && keyWord){
// 每次都会重新过滤
goods = shopCart.filter(good => good.name.includes(keyWord))
}
return <div>{goods?.map(good => good.name)}</div>
}
在这个例子中,组件的每次渲染都会进行一次过滤操作,但你只需要在购物车数据和关键词某一个数据变化时,才重新计算需要展示的数据就行了。
//..
// 使用useMem缓存计算结果
goods = useMemo(() => {
return shopCart.filter(good => good.name.includes(keyWord))
}, [shopCart, keyWord])
//..
避免重复渲染
useCallback 的功能其实可以用 useMemo 来实现。
const cb = useMemo(() => {
// 返回一个函数作为缓存结果
return () => {
// 在这里进行事件处理
}
}, [deps])
总结:这两个 API 只是做了同一件事情:建立了一个绑定某个结果到依赖数据的关系。只有当依赖变了,这个结果才需要被重新计算得到
useRef:在多次渲染之间共享数据
const ref = useRef(initialValue)
我们可以把 useRef 看做是在函数组件之外创建的一个容器空间。在这个容器上,我们可以通过唯一的 current 属性设置一个值,从而在函数组件的多次渲染之间共享这个值。
在类组件可以定义类的成员变量(属性),以便能在对象上通过成员属性去保存一些数据。但是在函数组件中,是没有这样一个空间去保存数据的。
举个 🌰:保存计时器的引用,操作计时器的开始和暂停
const timer = useRef(null)
// 开始计时器
const handleStart = () => {
timer.current = setInterval(() => {
// 一些操作
}, 1000)
}
// 暂时计时器
const handlePause = () => {
clearInterval(timer.current)
timer.current = null
}
保存某个 DOM 节点的引用
function TextInputWithFocusButton() {
const inputEl = useRef(null)
const onButtonClick = () => {
// current属性指向了真实的input这个dom节点,从而可以调用focus方法
inputEl, current.focus()
}
return <input ref={inputEl} type="text" />
}
思考:useState 也可以在多少渲染之间共享数据,那么能否使用 state 来保存 useRef 例子中的 timer 呢?
当然可以,只是 useRef 更优。更新 state 会导致重新渲染,而 ref 是不会的。