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 是不会的。

Published under  on .

Last updated on .

pipihua

我是皮皮花,一个前后端通吃的前端攻城狮,如果感觉不错欢迎点击小心心♥(ˆ◡ˆԅ) star on GitHub!