ReactHooks(完结)

上期戳here

ReactHooks[三]

  • [一.memo 函数](#一.memo 函数)
    • [1.1 语法格式](#1.1 语法格式)
  • [二. useMemo](#二. useMemo)
    • [2.1 问题引入](#2.1 问题引入)
    • [2.2 语法格式](#2.2 语法格式)
    • [2.3 使用 useMemo 解决刚才的问题](#2.3 使用 useMemo 解决刚才的问题)
  • 三.useCallback
    • [3.1 useMemo和useCallback区别](#3.1 useMemo和useCallback区别)
    • [3.2 语法格式](#3.2 语法格式)
  • 四.useTransition
    • [4.1 问题引入](#4.1 问题引入)
    • [4.2 语法格式](#4.2 语法格式)
    • [4.3 使用 isPending 展示加载状态](#4.3 使用 isPending 展示加载状态)
    • [4.4 注意事项](#4.4 注意事项)
  • 五.useDeferredValue
    • [5.1 问题引入](#5.1 问题引入)
    • [5.2 语法格式](#5.2 语法格式)
    • [5.3 延迟一个值与防抖和节流之间有什么不同](#5.3 延迟一个值与防抖和节流之间有什么不同)
    • [5.4 表明内容已过时](#5.4 表明内容已过时)

一.memo 函数

当父组件被重新渲染的时候,也会触发子组件的重新渲染,这样就多出了无意义的性能开销。使用 React.memo() 可以将组件进行缓存。

1.1 语法格式

const 组件 = React.memo(函数式组件)

typescript 复制代码
import React, { useEffect, useState } from 'react'

// 父组件
export const Father: React.FC = () => {
  // 定义 count 和 flag 两个状态
  const [count, setCount] = useState(0)
  const [flag, setFlag] = useState(false)

  return (
    <>
      <h1>父组件</h1>
      <p>count 的值是:{count}</p>
      <p>flag 的值是:{String(flag)}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>+1</button>
      <button onClick={() => setFlag((prev) => !prev)}>Toggle</button>
      <hr />
      <Son num={count} />
    </>
  )
}

// 子组件:依赖于父组件通过 props 传递进来的 num
//React.memo 包裹的子组件,只有props变化了,才会被重新渲染
export const Son: React.FC<{ num: number }> = React.memo(({ num }) => {
  useEffect(() => {
    console.log('触发了子组件的渲染')
  })
  return (
    <>
      <h3>子组件 --- {num}</h3>
    </>
  )
})

二. useMemo

如果点击 +1 按钮,发现count自增,flag值没有发生变化,但是tips函数也会重新执行。

2.1 问题引入

typescript 复制代码
// 父组件
export const Father: React.FC = () => {
  // 定义 count 和 flag 两个状态
  const [count, setCount] = useState(0)
  const [flag, setFlag] = useState(false)

  // 根据布尔值进行计算,动态返回内容
  const tips = () => {
    console.log('触发了 tips 的重新计算')
    return flag ? <p>哪里贵了,不要睁着眼瞎说好不好</p> : <p>这些年有没有努力工作,工资涨没涨</p>
  }

  return (
    <>
      <h1>父组件</h1>
      <p>count 的值是:{count}</p>
      <p>flag 的值是:{String(flag)}</p>
      {tips()}
      <button onClick={() => setCount((prev) => prev + 1)}>+1</button>
      <button onClick={() => setFlag((prev) => !prev)}>Toggle</button>
      <hr />
      <Son num={count} />
    </>
  )
}

我们希望如果 flag 没有发生变化,则避免 tips 函数的重新计算,从而优化性能。此时需要用到 React Hooks 提供的 useMemo API。

2.2 语法格式

typescript 复制代码
const memorizedValue = useMemo(cb, array)
const memoValue = useMemo(() => {
  return 计算得到的值
}, [value]) // 表示监听 value 的变化
  • cb:这是一个函数,用户处理计算的逻辑,必须使用 return 返回计算的结果;
  • array:这个数组中存储的是依赖项,只要依赖项发生变化,都会触发 cb 的重新执行。
    • 不传数组,每次更新都会重新计算
    • 空数组,只会计算一次
    • 依赖对应的值,对应的值发生变化时会重新执行 cb

2.3 使用 useMemo 解决刚才的问题

typescript 复制代码
//导入 useMemo:
import React, { useEffect, useState, useMemo } from 'react'
//在 Father 组件中,使用 useMemo 对 tips 进行改造:
// 根据布尔值进行计算,动态返回内容
const tips = useMemo(() => {
  console.log('触发了 tips 的重新计算')
  return flag ? <p>哪里贵了,不要睁着眼瞎说好不好</p> : <p>这些年有没有努力工作,工资涨没涨</p>
}, [flag])

三.useCallback

3.1 useMemo和useCallback区别

名称 返回值
useMemo 变量
useCallback 函数

3.2 语法格式

const memoCallback = useCallback(cb, array)

  • cb 是需要被缓存的函数
  • array 是依赖项列表,当 array 中的依赖项变化时才会重新执行 useCallback。
    • 如果省略 array,则每次更新都会重新计算
    • 如果 array 为空数组,则只会在组件第一次初始化的时候计算一次
    • 如果 array 不为空数组,则只有当依赖项的值变化时,才会重新计算
typescript 复制代码
import React, { useState, useCallback } from 'react'

// 用来存储函数的 set 集合
const set = new Set()

export const Search: React.FC = () => {
  const [kw, setKw] = useState('')

  const onKwChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setKw(e.currentTarget.value)
  }, [])

  // 把 onKwChange 函数的引用,存储到 set 集合中
  set.add(onKwChange)
  // 打印 set 集合中元素的数量
  console.log('set 中函数的数量为:' + set.size)

  return (
    <>
      <input type="text" value={kw} onChange={onKwChange} />
      <hr />
      <p>{kw}</p>
    </>
  )
} 

四.useTransition

4.1 问题引入

typescript 复制代码
import React, { useState } from 'react'

export const TabsContainer: React.FC = () => {
  // 被激活的标签页的名字
  const [activeTab, setActiveTab] = useState('home')

  // 点击按钮,切换激活的标签页
  const onClickHandler = (tabName: string) => {
    setActiveTab(tabName)
  }

  return (
    <div style={{ height: 500 }}>
      <TabButton isActive={activeTab === 'home'} onClick={() => onClickHandler('home')}>
        首页
      </TabButton>
      <TabButton isActive={activeTab === 'movie'} onClick={() => onClickHandler('movie')}>
        电影
      </TabButton>
      <TabButton isActive={activeTab === 'about'} onClick={() => onClickHandler('about')}>
        关于
      </TabButton>
      <hr />

      {/* 根据被激活的标签名,渲染对应的 tab 组件 */}
      {activeTab === 'home' && <HomeTab />}
      {activeTab === 'movie' && <MovieTab />}
      {activeTab === 'about' && <AboutTab />}
    </div>
  )
}

// Button 组件 props 的 TS 类型
type TabButtonType = React.PropsWithChildren & { isActive: boolean; onClick: () => void }
// Button 组件
const TabButton: React.FC<TabButtonType> = (props) => {
  const onButtonClick = () => {
    props.onClick()
  }

  return (
    <button className={['btn', props.isActive && 'active'].join(' ')} onClick={onButtonClick}>
      {props.children}
    </button>
  )
}

// Home 组件
const HomeTab: React.FC = () => {
  return <>HomeTab</>
}

// Movie 组件
const MovieTab: React.FC = () => {
  const items = Array(100000)
    .fill('MovieTab')
    .map((item, i) => <p key={i}>{item}</p>)
  return items
}

// About 组件
const AboutTab: React.FC = () => {
  return <>AboutTab</>
} 

4.2 语法格式

typescript 复制代码
import { useTransition } from 'react';

function TabContainer() {
  const [isPending, startTransition] = useTransition();
  // ......
}
  • 参数:
    • 调用 useTransition 时不需要传递任何参数
  • 返回值(数组):
    • isPending 布尔值:是否存在待处理的 transition【是否存在待渲染的组件】,如果值为 true,说明页面上存在待渲染的部分,可以给用户展示一个加载的提示;
    • startTransition 函数:调用此函数,可以把状态的更新 标记为低优先级的,不阻塞 UI 对用户操作的响应;
typescript 复制代码
import React, { useState, useTransition } from 'react'

export const TabsContainer: React.FC = () => {
  // 被激活的标签页的名字
  const [activeTab, setActiveTab] = useState('home')
  const [, startTransition] = useTransition()

  // 点击按钮,切换激活的标签页
  const onClickHandler = (tabName: string) => {
    startTransition(() => {
      setActiveTab(tabName)
    })
  }
  // 省略其它代码...
} 

4.3 使用 isPending 展示加载状态

typescript 复制代码
//调用 useTransition 期间,接收 isPending 参数:
const [isPending, startTransition] = useTransition()
// 将标签页的渲染,抽离到 renderTabs 函数中:
// 用于渲染标签页的函数
const renderTabs = () => {
  if (isPending) return <h3>Loading...</h3>
  switch (activeTab) {
    case 'home':
      return <HomeTab />
    case 'movie':
      return <MovieTab />
    case 'about':
      return <AboutTab />
  }
} 

4.4 注意事项

  • 传递给 startTransition 的函数必须是同步的。React 会立即执行此函数,并将在其执行期间发生的所有状态更新标记为 transition。如果在其执行期间,尝试稍后执行状态更新,这些状态更新不会被标记为 transition。
  • 标记为 transition 的状态更新将被其他状态更新打断。
  • transition 更新不能用于控制文本输入。

五.useDeferredValue

5.1 问题引入

transition更新不能用于控制文本输入,会导致中间状态丢失

typescript 复制代码
import React, { useState, useTransition } from 'react'

// 父组件
export const SearchBox: React.FC = () => {
  const [kw, setKw] = useState('')
  // 1. 调用 useTransition 函数
  const [, startTransition] = useTransition()

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    // 2. 将文本框状态更新标记为"低优先级",会导致中间的输入状态丢失
    startTransition(() => {
      setKw(e.currentTarget.value)
    })
  }

  return (
    <div style={{ height: 500 }}>
      <input type="text" value={kw} onChange={onInputChange} />
      <hr />
      <SearchResult query={kw} />
    </div>
  )
}

// 子组件,渲染列表项
const SearchResult: React.FC<{ query: string }> = (props) => {
  if (!props.query) return
  const items = Array(40000)
    .fill(props.query)
    .map((item, i) => <p key={i}>{item}</p>)

  return items
}

5.2 语法格式

typescript 复制代码
import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [kw, setKw] = useState('');
  // 根据 kw 得到延迟的 kw
  const deferredKw = useDeferredValue(kw);
  // ...
}

useDeferredValue 的返回值为一个延迟版的状态

  • 在组件首次渲染期间,返回值将与传入的值相同
  • 在组件更新期间,React 将首先使用旧值重新渲染 UI 结构,这能够跳过某些复杂组件的 rerender,从而提高渲染效率。随后,React 将使用新值更新 deferredValue,并在后台使用新值重新渲染是一个低优先级的更新。这也意味着,如果在后台使用新值更新时 value 再次改变,它将打断那次更新。
    注意:需要配合React.memo对子组件缓存使用。
typescript 复制代码
// 1. 按需导入 useDeferredValue 这个 Hooks API
import React, { useState, useDeferredValue } from 'react'

// 父组件
export const SearchBox: React.FC = () => {
  const [kw, setKw] = useState('')
  // 2. 基于 kw 的值,为其创建出一个延迟版的 kw 值,命名为 deferredKw
  const deferredKw = useDeferredValue(kw)

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setKw(e.currentTarget.value)
  }

  return (
    <div style={{ height: 500 }}>
      <input type="text" value={kw} onChange={onInputChange} />
      <hr />
      {/* 3. 将延迟版的 kw 值,传递给子组件使用 */}
      <SearchResult query={deferredKw} />
    </div>
  )
}

// 子组件,渲染列表项
// 4. 子组件必须使用 React.memo() 进行包裹,这样当 props 没有变化时,会跳过子组件的 rerender
const SearchResult: React.FC<{ query: string }> = React.memo((props) => {
  if (!props.query) return
  const items = Array(40000)
    .fill(props.query)
    .map((item, i) => <p key={i}>{item}</p>)

  return items
})

5.3 延迟一个值与防抖和节流之间有什么不同

面试可能会问到哦~延迟一个值与防抖和节流之间有什么不同?

  • 防抖: 在用户停止输入一段时间之后再更新列表。【搜索框常用】
  • 节流: 是指每隔一段时间更新列表。

5.4 表明内容已过时

typescript 复制代码
// 1. 按需导入 useDeferredValue 这个 Hooks API
import React, { useState, useDeferredValue } from 'react'

// 父组件
export const SearchBox: React.FC = () => {
  const [kw, setKw] = useState('')
  // 2. 基于 kw 的值,为其创建出一个延迟版的 kw 值
  const deferredValue = useDeferredValue(kw)

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setKw(e.currentTarget.value)
  }

  return (
    <div style={{ height: 500 }}>
      <input type="text" value={kw} onChange={onInputChange} />
      <hr />
      {/* 3. 将延迟版的 kw 值,传递给子组件使用 */}
      <div style={{ opacity: kw !== deferredValue ? 0.3 : 1, transition: 'opacity 0.5s ease' }}>
        <SearchResult query={deferredValue} />
      </div>
    </div>
  )
} 

ReactHooks 到这就完结啦,感谢大家的喜欢❥(^_-)

相关推荐
百万蹄蹄向前冲39 分钟前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5811 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路1 小时前
GeoTools 读取影像元数据
前端
ssshooter2 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友2 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry3 小时前
Jetpack Compose 中的状态
前端
dae bal3 小时前
关于RSA和AES加密
前端·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog4 小时前
低端设备加载webp ANR
前端·算法
LKAI.4 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi