「React 进阶」 React 全部 Hooks 使用大全 (包含 React v18 版本 )(上)

简介: 全面介绍 React 所有自定义 hooks

React Hooks.png

一 前言

React hooks是react16.8 以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。本章节笔者将介绍目前 React 提供的所有 hooks ,介绍其功能类型和基本使用方法。

创作不易,希望屏幕前的你能给笔者赏个,以此鼓励我继续创作前端硬文。🌹🌹🌹

1.1 技术背景

react hooks 解决了什么问题?

先设想一下,如果没有 Hooks,函数组件能够做的只是接受 Props、渲染 UI ,以及触发父组件传过来的事件。所有的处理逻辑都要在类组件中写,这样会使 class 类组件内部错综复杂,每一个类组件都有一套独特的状态,相互之间不能复用,即便是 React 之前出现过 mixin 等复用方式,但是伴随出 mixin 模式下隐式依赖,代码冲突覆盖等问题,也不能成为 React 的中流砥柱的逻辑复用方案。所以 React 放弃 mixin 这种方式。

类组件是一种面向对象思想的体现,类组件之间的状态会随着功能增强而变得越来越臃肿,代码维护成本也比较高,而且不利于后期 tree shaking。所以有必要做出一套函数组件代替类组件的方案,于是 Hooks 也就理所当然的诞生了。

所以 Hooks 出现本质上原因是:

  • 让函数组件也能做类组件的事,有自己的状态,可以处理一些副作用,能获取 ref ,也能做数据缓存。
  • 解决逻辑复用难的问题。
  • 放弃面向对象编程,拥抱函数式编程。

为什么要使用自定义 Hooks ?

自定义 hooks 是在 React Hooks 基础上的一个拓展,可以根据业务需求制定满足业务需要的组合 hooks ,更注重的是逻辑单元。通过业务场景不同,到底需要React Hooks 做什么,怎么样把一段逻辑封装起来,做到复用,这是自定义 hooks 产生的初衷。

自定义 hooks 也可以说是 React Hooks 聚合产物,其内部有一个或者多个 React Hooks 组成,用于解决一些复杂逻辑。

1.2 技术愿景

目前 hooks 已经成为 React 主流的开发手段,React 生态也日益朝着 hooks 方向发展,比如 React Router, React Redux 等, hooks 也更契合 React 生态库的应用。

随着项目功能模块越来越复杂,一些公共逻辑不能有效的复用,这些逻辑需要和业务代码强关联到一起,这样会让整体工程臃肿,功能不能复用,如果涉及到修改逻辑,那么有可能牵一发动全身。

所以有必要使用自定义 hooks 的方式,hooks 可以把重复的逻辑抽离出去,根据需要创建和业务功能绑定的业务型 hooks ,或者是根据具体功能创建的功能型 hooks 。

1.3 功能概览

在 React 的世界中,不同的 hooks 使命也是不同的,我这里对 React hooks 按照功能分类,分成了 数据更新驱动状态获取与传递,执行副作用,状态派生与保存,和工具类型, 具体功能划分和使用场景如下:

WechatIMG32.png

二 hooks 之数据更新驱动

2.1 useState

useState 可以使函数组件像类组件一样拥有 state,函数组件通过 useState 可以让组件重新渲染,更新视图。

useState 基础介绍:

const [ ①state , ②dispatch ] = useState(③initData)

① state,目的提供给 UI ,作为渲染视图的数据源。

② dispatchAction 改变 state 的函数,可以理解为推动函数组件渲染的渲染函数。

③ initData 有两种情况,第一种情况是非函数,将作为 state 初始化的值。 第二种情况是函数,函数的返回值作为 useState 初始化的值。

useState 基础用法:

const DemoState = (props) => {
   
   
   /* number为此时state读取值 ,setNumber为派发更新的函数 */
   let [number, setNumber] = useState(0) /* 0为初始值 */
   return (<div>
       <span>{
   
    number }</span>
       <button onClick={
   
    ()=> {
   
   
         setNumber(number+1)
         console.log(number) /* 这里的number是不能够即使改变的  */
       } } ></button>
   </div>)
}

useState 注意事项:

① 在函数组件一次执行上下文中,state 的值是固定不变的。

function Index(){
   
   
    const [ number, setNumber ] = React.useState(0)
    const handleClick = () => setInterval(()=>{
   
   
        // 此时 number 一直都是 0
        setNumber(number + 1 ) 
    },1000)
    return <button onClick={
   
    handleClick } > 点击 {
   
    number }</button>
}

② 如果两次 dispatchAction 传入相同的 state 值,那么组件就不会更新。

export default function Index(){
   
   
    const [ state  , dispatchState ] = useState({
   
    name:'alien' })
    const  handleClick = ()=>{
   
    // 点击按钮,视图没有更新。
        state.name = 'Alien'
        dispatchState(state) // 直接改变 `state`,在内存中指向的地址相同。
    }
    return <div>
         <span> {
   
    state.name }</span>
        <button onClick={
   
    handleClick }  >changeName++</button>
    </div>
}

③ 当触发 dispatchAction 在当前执行上下文中获取不到最新的 state, 只有再下一次组件 rerender 中才能获取到。

2.2 useReducer

useReducer 是 react-hooks 提供的能够在无状态组件中运行的类似redux的功能 api 。

useReducer 基础介绍:

const [ ①state , ②dispatch ] = useReducer(③reducer)

① 更新之后的 state 值。

② 派发更新的 dispatchAction 函数, 本质上和 useState 的 dispatchAction 是一样的。

③ 一个函数 reducer ,我们可以认为它就是一个 redux 中的 reducer , reducer的参数就是常规reducer里面的state和action, 返回改变后的state, 这里有一个需要注意的点就是:如果返回的 state 和之前的 state ,内存指向相同,那么组件将不会更新。

useReducer 基础用法:

const DemoUseReducer = ()=>{
   
   
    /* number为更新后的state值,  dispatchNumbner 为当前的派发函数 */
   const [ number , dispatchNumbner ] = useReducer((state,action)=>{
   
   
       const {
   
    payload , name  } = action
       /* return的值为新的state */
       switch(name){
   
   
           case 'add':
               return state + 1
           case 'sub':
               return state - 1 
           case 'reset':
             return payload       
       }
       return state
   },0)
   return <div>
      当前值:{
   
    number }
      {
   
    /* 派发更新 */ }
      <button onClick={
   
   ()=>dispatchNumbner({
   
    name:'add' })} >增加</button>
      <button onClick={
   
   ()=>dispatchNumbner({
   
    name:'sub' })} >减少</button>
      <button onClick={
   
   ()=>dispatchNumbner({
   
    name:'reset' ,payload:666 })} >赋值</button>
      {
   
    /* 把dispatch 和 state 传递给子组件  */ }
      <MyChildren  dispatch={
   
    dispatchNumbner } State={
   
   {
   
    number }} />
   </div>
}

2.3 useSyncExternalStore

useSyncExternalStore 的诞生并非偶然,和 v18 的更新模式下外部数据的 tearing 有着十分紧密的关联。useSyncExternalStore 能够让 React 组件在 concurrent 模式下安全地有效地读取外接数据源,在组件渲染过程中能够检测到变化,并且在数据源发生变化的时候,能够调度更新。当读取到外部状态发生了变化,会触发一个强制更新,来保证结果的一致性。

useSyncExternalStore 基础介绍:

useSyncExternalStore(
    subscribe,
    getSnapshot,
    getServerSnapshot
)

① subscribe 为订阅函数,当数据改变的时候,会触发 subscribe,在 useSyncExternalStore 会通过带有记忆性的 getSnapshot 来判别数据是否发生变化,如果发生变化,那么会强制更新数据。

② getSnapshot 可以理解成一个带有记忆功能的选择器。当 store 变化的时候,会通过 getSnapshot 生成新的状态值,这个状态值可提供给组件作为数据源使用,getSnapshot 可以检查订阅的值是否改变,改变的话那么会触发更新。

③ getServerSnapshot 用于 hydration 模式下的 getSnapshot。

useSyncExternalStore 基础用法:

import {
   
    combineReducers , createStore  } from 'redux'

/* number Reducer */
function numberReducer(state=1,action){
   
   
    switch (action.type){
   
   
      case 'ADD':
        return state + 1
      case 'DEL':
        return state - 1
      default:
        return state
    }
}

/* 注册reducer */
const rootReducer = combineReducers({
   
    number:numberReducer  })
/* 创建 store */
const store = createStore(rootReducer,{
   
    number:1  })

function Index(){
   
   
    /* 订阅外部数据源 */
    const state = useSyncExternalStore(store.subscribe,() => store.getState().number)
    console.log(state)
    return <div>
        {
   
   state}
        <button onClick={
   
   () => store.dispatch({
   
    type:'ADD' })} >点击</button>
    </div>
}

点击按钮,会触发 reducer ,然后会触发 store.subscribe 订阅函数,执行 getSnapshot 得到新的 number ,判断 number 是否发生变化,如果变化,触发更新。

2.4 useTransition

在 React v18 中,有一种新概念叫做过渡任务,这种任务是对比立即更新任务而产生的,通常一些影响用户交互直观响应的任务,例如按键,点击,输入等,这些任务需要视图上立即响应,所以可以称之为立即更新的任务,但是有一些更新不是那么急迫,比如页面从一个状态过渡到另外一个状态,这些任务就叫做过渡任务。 打个比方如下图当点击 tab 从 tab1 切换到 tab2 的时候,本质上产生了两个更新任务。

  • 第一个就是 hover 状态由 tab1 变成 tab2。

  • 第二个就是内容区域由 tab1 内容变换到 tab2 内容。

这两个任务,用户肯定希望 hover 状态的响应更迅速,而内容的响应有可能还需要请求数据等操作,所以更新状态并不是立马生效,通常还会有一些 loading 效果。所以第一个任务作为立即执行任务,而第二个任务就可以视为过渡任务

WechatIMG6496.jpeg

useTransition 基础介绍:

useTransition 执行返回一个数组。数组有两个状态值:

  • 第一个是,当处于过渡状态的标志——isPending。
  • 第二个是一个方法,可以理解为上述的 startTransition。可以把里面的更新任务变成过渡任务。
import {
   
    useTransition } from 'react' 
/* 使用 */
const  [ isPending , startTransition ] = useTransition ()

useTransition 基础用法:

除了上述切换 tab 场景外,还有很多场景非常适合 useTransition 产生的过渡任务,比如输入内容,实时搜索并展示数据,这本质上也是有两个优先级的任务,第一个任务就是受控表单的实时响应;第二个就是输入内容改变,数据展示的变化。那么接下来我们写一个 demo 来看一下 useTransition 的基本使用。

/* 模拟数据 */
const mockList1 = new Array(10000).fill('tab1').map((item,index)=>item+'--'+index )
const mockList2 = new Array(10000).fill('tab2').map((item,index)=>item+'--'+index )
const mockList3 = new Array(10000).fill('tab3').map((item,index)=>item+'--'+index )

const tab = {
   
   
  tab1: mockList1,
  tab2: mockList2,
  tab3: mockList3
}

export default function Index(){
   
   
  const [ active, setActive ] = React.useState('tab1') //需要立即响应的任务,立即更新任务
  const [ renderData, setRenderData ] = React.useState(tab[active]) //不需要立即响应的任务,过渡任务
  const [ isPending,startTransition  ] = React.useTransition() 
  const handleChangeTab = (activeItem) => {
   
   
     setActive(activeItem) // 立即更新
     startTransition(()=>{
   
    // startTransition 里面的任务优先级低
       setRenderData(tab[activeItem])
     })
  }
  return <div>
    <div className='tab' >
       {
   
    Object.keys(tab).map((item)=> <span className={
   
    active === item && 'active' } onClick={
   
   ()=>handleChangeTab(item)} >{
   
    item }</span> ) }
    </div>
    <ul className='content' >
       {
   
    isPending && <div> loading... </div> }
       {
   
    renderData.map(item=> <li key={
   
   item} >{
   
   item}</li>) }
    </ul>
  </div>
}

如上当切换 tab 的时候,产生了两个优先级任务,第一个任务是 setActive 控制 tab active 状态的改变,第二个任务为 setRenderData 控制渲染的长列表数据 (在现实场景下长列表可能是一些数据量大的可视化图表)。

2.5 useDeferredValue

React 18 提供了 useDeferredValue 可以让状态滞后派生。useDeferredValue 的实现效果也类似于 transtion,当迫切的任务执行后,再得到新的状态,而这个新的状态就称之为 DeferredValue。

useDeferredValue 基础介绍:

useDeferredValue 和上述 useTransition 本质上有什么异同呢?

\
相同点: useDeferredValue 本质上和内部实现与 useTransition 一样都是标记成了过渡更新任务。

不同点: useTransition 是把 startTransition 内部的更新任务变成了过渡任务transtion,而 useDeferredValue 是把原值通过过渡任务得到新的值,这个值作为延时状态。 一个是处理一段逻辑,另一个是生产一个新的状态。

useDeferredValue 接受一个参数 value ,一般为可变的 state , 返回一个延时状态 deferrredValue。

const deferrredValue = React.useDeferredValue(value)

useDeferredValue 基础用法:

接下来把上边那个例子用 useDeferredValue 来实现。

export default function Index(){
   
   
  const [ active, setActive ] = React.useState('tab1') //需要立即响应的任务,立即更新任务
  const deferActive = React.useDeferredValue(active) // 把状态延时更新,类似于过渡任务
  const handleChangeTab = (activeItem) => {
   
   
     setActive(activeItem) // 立即更新
  }
  const renderData = tab[deferActive] // 使用滞后状态
  return <div>
    <div className='tab' >
       {
   
    Object.keys(tab).map((item)=> <span className={
   
    active === item && 'active' } onClick={
   
   ()=>handleChangeTab(item)} >{
   
    item }</span> ) }
    </div>
    <ul className='content' >
       {
   
    renderData.map(item=> <li key={
   
   item} >{
   
   item}</li>) }
    </ul>
  </div>
  }

如上 active 为正常改变的状态,deferActive 为滞后的 active 状态,我们使用正常状态去改变 tab 的 active 状态,使用滞后的状态去更新视图,同样达到了提升用户体验的作用。

总结

在下一文中,我们将继续介绍 React Hooks

相关文章
|
7月前
|
前端开发 JavaScript UED
使用React Hooks优化前端应用性能
本文将深入探讨如何使用React Hooks来优化前端应用的性能,重点介绍Hooks在状态管理、副作用处理和组件逻辑复用方面的应用。通过本文的指导,读者将了解到如何利用React Hooks提升前端应用的响应速度和用户体验。
|
7月前
|
前端开发 开发者
探索前端框架的新趋势:React Hooks的应用与实践
本文将深入探讨前端开发中的新趋势,重点介绍React Hooks的应用与实践。通过学习和使用React Hooks,开发者可以更高效地构建可维护、可扩展的前端应用程序。本文将详细介绍React Hooks的原理、优势以及如何在实际项目中运用Hooks来提高开发效率并改善代码结构。无论你是刚入门前端开发还是经验丰富的工程师,本文都将对你有所启发。
|
7月前
|
前端开发
【第31期】一文学会用React Hooks组件编写组件
【第31期】一文学会用React Hooks组件编写组件
82 0
|
7月前
|
缓存 前端开发 JavaScript
【第28期】一文学会使用React Hooks
【第28期】一文学会使用React Hooks
60 0
|
7月前
|
前端开发
利用React Hooks优化前端状态管理
本文将深入介绍如何利用React Hooks优化前端状态管理,包括Hooks的概念、使用方法以及与传统状态管理方式的对比分析,帮助前端开发人员更好地理解和应用这一现代化的状态管理方案。
|
7月前
|
存储 缓存 前端开发
【React】Hooks面试题集锦
本文集合一些React的Hooks面试题,方便读者以后面试查漏补缺。作者给出自认为可以让面试官满意的简易答案,如果想要了解更深刻,可以点击链接查看对应的详细博文。在此对链接中的博文作者非常感谢🙏。
224 1
|
7月前
|
前端开发 开发者
React Hooks:提升前端开发效率和代码可维护性
传统的 React 类组件在处理状态管理和生命周期函数时存在一些限制,而引入的 React Hooks 技术可以帮助前端开发者更高效地管理组件状态和逻辑,提升代码的可读性和可维护性。本文将介绍 React Hooks 的基本用法及其在实际项目中的应用场景。
|
7月前
|
前端开发 JavaScript 测试技术
React Hooks之useState、useRef
React Hooks之useState、useRef
|
7月前
|
存储 前端开发 JavaScript
React Hooks的useState、useRef使用
React Hooks的useState、useRef使用
72 2
|
7月前
|
存储 前端开发 JavaScript
探索前端框架React Hooks的魅力
【2月更文挑战第2天】本文深入探讨了前端框架React Hooks的核心概念及其在现代Web开发中的重要性,分析了Hooks相较于传统class组件的优势所在,展示了它带来的便利和灵活性,为开发者提供了更加高效和优雅的解决方案。