自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

ReactHooks由淺入深:所有 hooks 的梳理、匯總與解析

開發(fā)
Vue 中的指令、React 中的 hooks 都是框架的核心知識(shí)點(diǎn)。但是對(duì)于很多同學(xué)來說,因?yàn)槿粘9ぷ髦虚_發(fā)的局限性,所以對(duì)這些 指令 或 hooks 認(rèn)知的并不全面,一旦在面試的時(shí)候被問到不熟悉的 指令 或者 hooks 可能就會(huì)吃虧。

Vue 中的指令、React 中的 hooks 都是框架的核心知識(shí)點(diǎn)。但是對(duì)于很多同學(xué)來說,因?yàn)槿粘9ぷ髦虚_發(fā)的局限性,所以對(duì)這些 指令 或 hooks 認(rèn)知的并不全面,一旦在面試的時(shí)候被問到不熟悉的 指令 或者 hooks 可能就會(huì)吃虧。

所以說,咱們今天就先來整理一下 React 中的 hooks,整理的過程會(huì) 由淺入深 同時(shí)配合一些代碼進(jìn)行說明。爭取讓哪怕不是很熟悉 react 的同學(xué),也可以在本文章中有一定的收獲。

一、React 基礎(chǔ)普及

1、什么是 react 中的組件

在 React 16.8 之后,react 使用 函數(shù) 表示組件,稱為 函數(shù)式組件。

例如以下代碼,就是兩個(gè)基礎(chǔ)函數(shù)式組件(App 與 Greeting):

import React from 'react';

// 函數(shù)組件
function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// 使用函數(shù)組件
function App() {
  return (
    <div>
      <Greeting name="Alice" />
      <Greeting name="Bob" />
      <Greeting name="Charlie" />
    </div>
  );
}

export default App;

2、什么是 react hooks

在 React 中 以 use 開頭的函數(shù) 就被稱之為 hooks。

React 默認(rèn)提供一些 hooks。同樣的,我們也可以自定義一些函數(shù)(以 use)開頭,那么該函數(shù)就可以被稱為是 hooks。

import { useState } from 'react';

// 最簡單的自定義Hooks
function useMyCustomHook() {
  // 在這個(gè)例子中,我們只是返回一個(gè)固定的值
  const [value] = useState("Hello, World!");

  return value;
}

export default useMyCustomHook;

3、hooks 解決了什么問題?

如果沒有Hooks,函數(shù)組件的功能相對(duì)有限,只能接受 Props、渲染UI,以及響應(yīng)來自父組件的事件。

因此,Hooks的出現(xiàn)主要是為了解決是哪個(gè)問題:

  • 強(qiáng)化函數(shù)式組件:讓函數(shù)組件具備類組件(16.8之前)的功能,可以有自己的狀態(tài)、處理副作用、獲取ref,以及進(jìn)行數(shù)據(jù)緩存。
  • 提高復(fù)用性: hooks本質(zhì)上是以 use 開頭的函數(shù),通過函數(shù)封裝邏輯,解決邏輯復(fù)用的問題。
  • 函數(shù)式編程:react 推薦函數(shù)式編程,摒棄面向?qū)ο缶幊獭?/li>

二、React Hooks 劃分

React Hooks 根據(jù)性能可以劃分為 5 大類:

1.1 狀態(tài)處理:useState

useState是React提供的一個(gè)Hook,它讓函數(shù)組件也能像類組件一樣擁有狀態(tài)。通過useState,你可以讓組件在內(nèi)部管理一些數(shù)據(jù),并在數(shù)據(jù)更新時(shí)重新渲染視圖。

在使用useState時(shí),你會(huì)得到一個(gè)包含兩個(gè)值的數(shù)組:

  1. state:當(dāng)前狀態(tài)的值,它用于提供給UI,作為渲染視圖的數(shù)據(jù)源。
  2. dispatch:一個(gè)函數(shù),用于改變state的值,從而觸發(fā)函數(shù)組件的重新渲染。

useState的基礎(chǔ)用法如下:

const DemoState = (props) => {
   // number是當(dāng)前state的值,setNumber是用于更新state的函數(shù)
   let [number, setNumber] = useState(0) // 0為初始值
   return (
       <div>
           <span>{number}</span>
           <button onClick={() => {
             setNumber(number + 1)
             console.log(number) // 這里的number是不能夠即時(shí)改變的
           }}>增加</button>
       </div>
   )
}

在使用useState時(shí)需要注意:

  1. 在同一個(gè)函數(shù)組件的執(zhí)行過程中,state的值是固定不變的。比如,如果你在一個(gè)定時(shí)器中改變state的值,組件不會(huì)重新渲染。
  2. 如果兩次dispatch傳入相同的state值,那么組件就不會(huì)更新,因?yàn)镽eact會(huì)認(rèn)為狀態(tài)沒有改變。
  3. 當(dāng)你在當(dāng)前執(zhí)行上下文中觸發(fā)dispatch時(shí),無法立即獲取到最新的state值,只有在下一次組件重新渲染時(shí)才能獲取到。

1.2 狀態(tài)處理:useReducer

useReducer是React Hooks提供的一個(gè)功能,類似于Redux的狀態(tài)管理工具。

在使用useReducer時(shí),你會(huì)得到一個(gè)包含兩個(gè)值的數(shù)組:

  1. state:state是更新后的狀態(tài)值
  2. dispatch:dispatch是用于派發(fā)更新的函數(shù),與useState中的dispatch函數(shù)類似

基礎(chǔ)用法如下:

const DemoUseReducer = () => {
    // number為更新后的state值, dispatchNumbner為當(dāng)前的派發(fā)函數(shù)
    const [number, dispatchNumber] = useReducer((state, action) => {
        const { payload, name } = action;
        // 根據(jù)不同的action類型來更新state
        switch (name) {
            case 'add':
                return state + 1;
            case 'sub':
                return state - 1;
            case 'reset':
                return payload;
            default:
                return state;
        }
    }, 0);

    return (
        <div>
            當(dāng)前值:{number}
            {/* 派發(fā)更新 */}
            <button onClick={() => dispatchNumber({ name: 'add' })}>增加</button>
            <button onClick={() => dispatchNumber({ name: 'sub' })}>減少</button>
            <button onClick={() => dispatchNumber({ name: 'reset', payload: 666 })}>賦值</button>
            {/* 把dispatch和state傳遞給子組件 */}
            <MyChildren dispatch={dispatchNumber} state={{ number }} />
        </div>
    );
};

在useReducer中,你需要傳入一個(gè)reducer函數(shù),這個(gè)函數(shù)接受當(dāng)前的state和一個(gè)action作為參數(shù),并返回新的state。如果新的state和之前的state指向的是同一個(gè)內(nèi)存地址,那么組件就不會(huì)更新。

1.3 狀態(tài)處理:useSyncExternalStore

useSyncExternalStore的出現(xiàn)與React版本18中更新模式下外部數(shù)據(jù)的撕裂(tearing)密切相關(guān)。它允許React組件在并發(fā)模式下安全有效地讀取外部數(shù)據(jù)源,并在組件渲染過程中檢測數(shù)據(jù)的變化,以及在數(shù)據(jù)源發(fā)生變化時(shí)調(diào)度更新,以確保結(jié)果的一致性。

基礎(chǔ)介紹如下:

useSyncExternalStore(
    subscribe,
    getSnapshot,
    getServerSnapshot
)
  1. subscribe是一個(gè)訂閱函數(shù),當(dāng)數(shù)據(jù)發(fā)生變化時(shí)會(huì)觸發(fā)該函數(shù)。useSyncExternalStore會(huì)使用帶有記憶功能的getSnapshot來判斷數(shù)據(jù)是否發(fā)生變化,如果有變化,則強(qiáng)制更新數(shù)據(jù)。
  2. getSnapshot可以看作是帶有記憶功能的選擇器。當(dāng)數(shù)據(jù)源變化時(shí),通過getSnapshot生成新的狀態(tài)值,該狀態(tài)值可作為組件的數(shù)據(jù)源使用。getSnapshot能夠檢查訂閱的值是否發(fā)生變化,一旦發(fā)生變化,則觸發(fā)更新。
  3. getServerSnapshot用于hydration模式下的getSnapshot。

基礎(chǔ)用法示例如下:

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;
    }
}

/* 注冊(cè)reducer */
const rootReducer = combineReducers({ number: numberReducer });
/* 創(chuàng)建 store */
const store = createStore(rootReducer, { number: 1 });

function Index() {
    /* 訂閱外部數(shù)據(jù)源 */
    const state = useSyncExternalStore(store.subscribe, () => store.getState().number);
    console.log(state);
    return (
        <div>
            {state}
            <button onClick={() => store.dispatch({ type: 'ADD' })}>點(diǎn)擊</button>
        </div>
    );
}

當(dāng)點(diǎn)擊按鈕時(shí),會(huì)觸發(fā)reducer,然后會(huì)觸發(fā)store.subscribe訂閱函數(shù),執(zhí)行g(shù)etSnapshot得到新的number,判斷number是否發(fā)生變化,如果有變化,則觸發(fā)更新。

1.4 狀態(tài)處理:useTransition

在React v18中,引入了一種新概念叫做過渡任務(wù)。這些任務(wù)與立即更新任務(wù)相對(duì)應(yīng),通常指的是一些不需要立即響應(yīng)的更新,比如頁面從一個(gè)狀態(tài)過渡到另一個(gè)狀態(tài)。

舉個(gè)例子,當(dāng)用戶點(diǎn)擊tab從tab1切換到tab2時(shí),會(huì)產(chǎn)生兩個(gè)更新任務(wù):

  1. 第一個(gè)任務(wù)是hover狀態(tài)從tab1變成tab2。
  2. 第二個(gè)任務(wù)是內(nèi)容區(qū)域從tab1內(nèi)容變換到tab2內(nèi)容。

這兩個(gè)任務(wù)中,用戶通常希望hover狀態(tài)的響應(yīng)更迅速,而內(nèi)容的響應(yīng)可能需要更長時(shí)間,比如請(qǐng)求數(shù)據(jù)等操作。因此,第一個(gè)任務(wù)可以視為立即執(zhí)行任務(wù),而第二個(gè)任務(wù)則可以視為過渡任務(wù)。

useTransition的基礎(chǔ)介紹如下:

import { useTransition } from 'react';

/* 使用 */
const [isPending, startTransition] = useTransition();

useTransition會(huì)返回一個(gè)數(shù)組,其中包含兩個(gè)值:

  1. 第一個(gè)值是一個(gè)標(biāo)志,表示是否處于過渡狀態(tài)。
  2. 第二個(gè)值是一個(gè)函數(shù),類似于上述的startTransition,用于將更新任務(wù)轉(zhuǎn)換為過渡任務(wù)。

在基礎(chǔ)用法中,除了切換tab的場景外,還有很多其他場景適合產(chǎn)生過渡任務(wù),比如實(shí)時(shí)搜索并展示數(shù)據(jù)。這種情況下,有兩個(gè)優(yōu)先級(jí)的任務(wù):第一個(gè)是受控表單的實(shí)時(shí)響應(yīng),第二個(gè)是輸入內(nèi)容改變后數(shù)據(jù)展示的變化。

下面是一個(gè)基本使用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') //立即響應(yīng)的任務(wù),立即更新任務(wù)
  const [ renderData, setRenderData ] = React.useState(tab[active]) //不需要立即響應(yīng)的任務(wù),過渡任務(wù)
  const [ isPending,startTransition  ] = React.useTransition() 
  const handleChangeTab = (activeItem) => {
     setActive(activeItem) //立即更新
     startTransition(()=>{ //startTransition里面的任務(wù)優(yōu)先級(jí)低
       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>
}

以上示例中,當(dāng)切換tab時(shí),會(huì)產(chǎn)生兩個(gè)優(yōu)先級(jí)任務(wù):第一個(gè)任務(wù)是setActive控制tab的active狀態(tài)的改變,第二個(gè)任務(wù)是setRenderData控制渲染的長列表數(shù)據(jù)(在實(shí)際場景中,這可能是一些數(shù)據(jù)量大的可視化圖表)。

1.5 狀態(tài)處理:useDeferredValue

在React 18中,引入了useDeferredValue,它可以讓狀態(tài)的更新滯后于派生。useDeferredValue的實(shí)現(xiàn)效果類似于transition,在緊急任務(wù)執(zhí)行后,再得到新的狀態(tài),這個(gè)新的狀態(tài)就稱之為DeferredValue。

useDeferredValue基礎(chǔ)介紹:

useDeferredValue和前面提到的useTransition有什么異同呢?

相同點(diǎn): useDeferredValue和useTransition本質(zhì)上都是標(biāo)記為過渡更新任務(wù)。

不同點(diǎn): useTransition將內(nèi)部的更新任務(wù)轉(zhuǎn)換為過渡任務(wù)transition,而useDeferredValue則是通過過渡任務(wù)得到新的值,這個(gè)值作為延遲狀態(tài)。一個(gè)是處理一段邏輯,另一個(gè)是生成一個(gè)新的狀態(tài)。

useDeferredValue接受一個(gè)參數(shù)value,通常是可變的state,返回一個(gè)延遲狀態(tài)deferredValue。

const deferredValue = React.useDeferredValue(value)

useDeferredValue基礎(chǔ)用法:

下面將上面的例子改用useDeferredValue來實(shí)現(xiàn)。

export default function Index(){
  const [ active, setActive ] = React.useState('tab1') //需要立即響應(yīng)的任務(wù),立即更新任務(wù)
  const deferredActive = React.useDeferredValue(active) // 將狀態(tài)延遲更新,類似于過渡任務(wù)
  const handleChangeTab = (activeItem) => {
     setActive(activeItem) // 立即更新
  }
  const renderData = tab[deferredActive] // 使用延遲狀態(tài)
  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是正常改變的狀態(tài),deferredActive是延遲的active狀態(tài)。我們使用正常狀態(tài)來改變tab的active狀態(tài),而使用延遲狀態(tài)來更新視圖,從而提升了用戶體驗(yàn)。

2.1 副作用執(zhí)行:useEffect

React hooks提供了API,用于彌補(bǔ)函數(shù)組件沒有生命周期的不足。主要利用了hooks中的useEffect、useLayoutEffect和useInsertionEffect。其中,最常用的是useEffect?,F(xiàn)在我們來看一下useEffect的使用。

useEffect基礎(chǔ)介紹:

useEffect(() => {
    return cleanup;
}, dependencies);

useEffect的第一個(gè)參數(shù)是一個(gè)回調(diào)函數(shù),返回一個(gè)清理函數(shù)cleanup。cleanup函數(shù)會(huì)在下一次回調(diào)函數(shù)執(zhí)行之前調(diào)用,用于清除上一次回調(diào)函數(shù)產(chǎn)生的副作用。

第二個(gè)參數(shù)是一個(gè)依賴項(xiàng)數(shù)組,里面可以包含多個(gè)依賴項(xiàng)。當(dāng)依賴項(xiàng)發(fā)生變化時(shí),會(huì)執(zhí)行上一次callback返回的cleanup函數(shù),并執(zhí)行新的effect回調(diào)函數(shù)。

對(duì)于useEffect的執(zhí)行,React采用了異步調(diào)用的處理邏輯。對(duì)于每個(gè)effect的回調(diào)函數(shù),React會(huì)將其放入任務(wù)隊(duì)列中,類似于setTimeout回調(diào)函數(shù)的方式,等待主線程任務(wù)完成、DOM更新、JS執(zhí)行完成以及視圖繪制完成后才執(zhí)行。因此,effect回調(diào)函數(shù)不會(huì)阻塞瀏覽器的視圖繪制。

useEffect基礎(chǔ)用法:

/* 模擬數(shù)據(jù)交互 */
function getUserInfo(a){
    return new Promise((resolve)=>{
        setTimeout(()=>{ 
           resolve({
               name:a,
               age:16,
           }) 
        },500)
    })
}

const Demo = ({ a }) => {
    const [userMessage, setUserMessage] = useState({});
    const div = useRef();
    const [number, setNumber] = useState(0);

    /* 模擬事件監(jiān)聽處理函數(shù) */
    const handleResize = () => {};

    /* useEffect使用 */
    useEffect(() => {
       /* 請(qǐng)求數(shù)據(jù) */
       getUserInfo(a).then(res => {
           setUserMessage(res);
       });

       /* 定時(shí)器 延時(shí)器等 */
       const timer = setInterval(() => console.log(666), 1000);

       /* 操作dom */
       console.log(div.current); /* div */

       /* 事件監(jiān)聽等 */
       window.addEventListener('resize', handleResize);

       /* 此函數(shù)用于清除副作用 */
       return function() {
           clearInterval(timer);
           window.removeEventListener('resize', handleResize);
       };
    /* 只有當(dāng)props->a和state->number改變的時(shí)候, useEffect副作用函數(shù)重新執(zhí)行,如果此時(shí)數(shù)組為空[],證明函數(shù)只有在初始化的時(shí)候執(zhí)行一次,相當(dāng)于componentDidMount */
    }, [a, number]);

    return (
        <div ref={div}>
            <span>{userMessage.name}</span>
            <span>{userMessage.age}</span>
            <div onClick={() => setNumber(1)}>{number}</div>
        </div>
    );
};

上述代碼中,在useEffect中做了以下功能:

  1. 請(qǐng)求數(shù)據(jù)。
  2. 設(shè)置定時(shí)器、延時(shí)器等。
  3. 操作DOM,在React Native中可以通過ref獲取元素位置信息等內(nèi)容。
  4. 注冊(cè)事件監(jiān)聽器,在React Native中可以注冊(cè)NativeEventEmitter。
  5. 清除定時(shí)器、延時(shí)器、解綁事件監(jiān)聽器等。

2.2 副作用執(zhí)行:useLayoutEffect

useLayoutEffect基礎(chǔ)介紹:

useLayoutEffect和useEffect的不同之處在于它采用了同步執(zhí)行的方式。那么它和useEffect有什么區(qū)別呢?

① 首先,useLayoutEffect在DOM更新之后、瀏覽器繪制之前執(zhí)行。這使得我們可以方便地修改DOM、獲取DOM信息,從而避免了不必要的瀏覽器回流和重繪。相比之下,如果將DOM布局修改放在useEffect中,那么useEffect的執(zhí)行是在瀏覽器繪制視圖之后進(jìn)行的,接著再去修改DOM,可能會(huì)導(dǎo)致瀏覽器進(jìn)行額外的回流和重繪。由于兩次繪制,可能會(huì)導(dǎo)致視圖上出現(xiàn)閃現(xiàn)或突兀的效果。

② useLayoutEffect回調(diào)函數(shù)中的代碼執(zhí)行會(huì)阻塞瀏覽器的繪制。

useLayoutEffect基礎(chǔ)用法:

const DemoUseLayoutEffect = () => {
    const target = useRef();

    useLayoutEffect(() => {
        /* 在DOM繪制之前,移動(dòng)DOM到指定位置 */
        const { x, y } = getPositon(); // 獲取要移動(dòng)的x,y坐標(biāo)
        animate(target.current, { x, y });
    }, []);

    return (
        <div>
            <span ref={target} className="animate"></span>
        </div>
    );
};

2.3 副作用執(zhí)行:useInsertionEffect

useInsertionEffect基礎(chǔ)介紹:

useInsertionEffect是React v18新增的hooks之一,其用法與useEffect和useLayoutEffect相似。那么這個(gè)hooks用于什么呢?

在介紹useInsertionEffect用途之前,先來看一下useInsertionEffect的執(zhí)行時(shí)機(jī)。

React.useEffect(() => {
    console.log('useEffect 執(zhí)行');
}, []);

React.useLayoutEffect(() => {
    console.log('useLayoutEffect 執(zhí)行');
}, []);

React.useInsertionEffect(() => {
    console.log('useInsertionEffect 執(zhí)行');
}, []);

打印結(jié)果為:useInsertionEffect執(zhí)行 -> useLayoutEffect執(zhí)行 -> useEffect執(zhí)行。

可以看到,useInsertionEffect的執(zhí)行時(shí)機(jī)要比useLayoutEffect提前。在useLayoutEffect執(zhí)行時(shí),DOM已經(jīng)更新了,但是在useInsertionEffect執(zhí)行時(shí),DOM還沒有更新。useInsertionEffect主要是解決CSS-in-JS在渲染中注入樣式的性能問題。這個(gè)hooks主要適用于這個(gè)場景,在其他場景下React不建議使用這個(gè)hooks。

useInsertionEffect模擬使用:

export default function Index() {
    React.useInsertionEffect(() => {
        /* 動(dòng)態(tài)創(chuàng)建style標(biāo)簽插入到head中 */
        const style = document.createElement('style');
        style.innerHTML = `
            .css-in-js {
                color: red;
                font-size: 20px;
            }
        `;
        document.head.appendChild(style);
    }, []);

    return <div className="css-in-js">hello, useInsertionEffect</div>;
}

上述代碼模擬了useInsertionEffect的使用。

3.1 狀態(tài)傳遞:useContext

useContext基礎(chǔ)介紹

可以使用useContext來獲取父級(jí)組件傳遞過來的context值,這個(gè)值是最近的父級(jí)組件Provider設(shè)置的value值。useContext的參數(shù)通常是由createContext方式創(chuàng)建的context對(duì)象,也可以是父級(jí)上下文context傳遞的(參數(shù)為context)。useContext可以代替context.Consumer來獲取Provider中保存的value值。

const contextValue = useContext(context);

useContext接受一個(gè)參數(shù),一般是context對(duì)象,返回值是context對(duì)象內(nèi)部保存的value值。

useContext基礎(chǔ)用法:

/* 用useContext方式 */
const DemoContext = () => {
    const value = useContext(Context);
    /* my name is alien */
    return <div> my name is {value.name}</div>;
}

/* 用Context.Consumer方式 */
const DemoContext1 = () => {
    return (
        <Context.Consumer>
            {/* my name is alien */}
            {value => <div> my name is {value.name}</div>}
        </Context.Consumer>
    );
}

export default () => {
    return (
        <div>
            <Context.Provider value={{ name: 'alien', age: 18 }}>
                <DemoContext />
                <DemoContext1 />
            </Context.Provider>
        </div>
    );
}

3.2 狀態(tài)傳遞:useRef

useRef基礎(chǔ)介紹:

useRef可以用來獲取元素,緩存狀態(tài)。它接受一個(gè)初始狀態(tài)initState作為初始值,并返回一個(gè)ref對(duì)象cur。cur對(duì)象上有一個(gè)current屬性,該屬性就是ref對(duì)象需要獲取的內(nèi)容。

const cur = React.useRef(initState);
console.log(cur.current);

useRef基礎(chǔ)用法:

獲取DOM元素: 在React中,可以利用useRef來獲取DOM元素。在React Native中雖然沒有DOM元素,但是同樣可以利用useRef來獲取組件的節(jié)點(diǎn)信息(Fiber信息)。

const DemoUseRef = () => {
    const dom = useRef(null);
    const handleSubmit = () => {
        console.log(dom.current); // <div>表單組件</div> DOM節(jié)點(diǎn)
    }
    return (
        <div>
            {/* ref標(biāo)記當(dāng)前DOM節(jié)點(diǎn) */}
            <div ref={dom}>表單組件</div>
            <button onClick={handleSubmit}>提交</button>
        </div>
    );
}

保存狀態(tài): 可以利用useRef返回的ref對(duì)象來保存狀態(tài),只要當(dāng)前組件不被銷毀,狀態(tài)就會(huì)一直存在。

const status = useRef(false);
/* 改變狀態(tài) */
const handleChangeStatus = () => {
    status.current = true;
}

3.3 狀態(tài)傳遞:useImperativeHandle

useImperativeHandle基礎(chǔ)介紹:

useImperativeHandle配合forwardRef可以自定義向父組件暴露的實(shí)例值。對(duì)于函數(shù)組件,如果我們想讓父組件能夠獲取子組件的實(shí)例,就可以使用useImperativeHandle和forwardRef來實(shí)現(xiàn)。

useImperativeHandle接受三個(gè)參數(shù):

  1. 第一個(gè)參數(shù)ref:接受forwardRef傳遞過來的ref。
  2. 第二個(gè)參數(shù)createHandle:處理函數(shù),返回值作為暴露給父組件的ref對(duì)象。
  3. 第三個(gè)參數(shù)deps:依賴項(xiàng)deps,當(dāng)依賴項(xiàng)改變時(shí)形成新的ref對(duì)象。

useImperativeHandle基礎(chǔ)用法:

我們通過一個(gè)示例來說明,使用useImperativeHandle使得父組件能夠控制子組件中的input自動(dòng)聚焦并設(shè)置值。

function Son(props, ref) {
    const inputRef = useRef(null);
    const [inputValue, setInputValue] = useState('');

    useImperativeHandle(ref, () => {
        const handleRefs = {
            onFocus() {
                inputRef.current.focus();
            },
            onChangeValue(value) {
                setInputValue(value);
            }
        };
        return handleRefs;
    }, []);

    return (
        <div>
            <input
                placeholder="請(qǐng)輸入內(nèi)容"
                ref={inputRef}
                value={inputValue}
            />
        </div>
    );
}

const ForwardSon = forwardRef(Son);

class Index extends React.Component {
    inputRef = null;

    handleClick() {
        const { onFocus, onChangeValue } = this.inputRef;
        onFocus();
        onChangeValue('let us learn React!');
    }

    render() {
        return (
            <div style={{ marginTop: '50px' }}>
                <ForwardSon ref={(node) => (this.inputRef = node)} />
                <button onClick={this.handleClick.bind(this)}>操控子組件</button>
            </div>
        );
    }
}

4.1 性能優(yōu)化:useMemo

useMemo基礎(chǔ)介紹:

useMemo 可以在函數(shù)組件的渲染過程中同步執(zhí)行一個(gè)函數(shù)邏輯,并將其返回值作為一個(gè)新的狀態(tài)進(jìn)行緩存。這個(gè) hooks 的作用在于優(yōu)化性能,避免不必要的重復(fù)計(jì)算或渲染。

基本語法:

const cachedValue = useMemo(create, deps)
  • create: 第一個(gè)參數(shù)是一個(gè)函數(shù),函數(shù)的返回值將作為緩存值。
  • deps: 第二個(gè)參數(shù)是一個(gè)數(shù)組,包含當(dāng)前 useMemo 的依賴項(xiàng)。當(dāng)這些依賴項(xiàng)發(fā)生變化時(shí),會(huì)重新執(zhí)行 create 函數(shù)以獲取新的緩存值。
  • cachedValue: 返回值,為 create 函數(shù)的返回值或上一次的緩存值。

基本用法:

1. 派生新狀態(tài):

function Scope() {
    const keeper = useKeep()
    const { cacheDispatch, cacheList, hasAliveStatus } = keeper
   
    const contextValue = useMemo(() => {
        return {
            cacheDispatch: cacheDispatch.bind(keeper),
            hasAliveStatus: hasAliveStatus.bind(keeper),
            cacheDestory: (payload) => cacheDispatch.call(keeper, { type: ACTION_DESTORY, payload })
        }
      
    }, [keeper])
    return (
        <KeepaliveContext.Provider value={contextValue}>
        </KeepaliveContext.Provider>
    )
}

在上面的示例中,通過 useMemo 派生出一個(gè)新的狀態(tài) contextValue,只有 keeper 發(fā)生變化時(shí),才會(huì)重新生成 contextValue。

2. 緩存計(jì)算結(jié)果:

function Scope(){
    const style = useMemo(()=>{
      let computedStyle = {}
      // 大量的計(jì)算
      return computedStyle
    },[])
    return <div style={style} ></div>
}

在這個(gè)例子中,通過 useMemo 緩存了一個(gè)計(jì)算結(jié)果 style,只有當(dāng)依賴項(xiàng)發(fā)生變化時(shí),才會(huì)重新計(jì)算 style。

3. 緩存組件,減少子組件重渲染次數(shù):

function Scope ({ children }){
   const renderChild = useMemo(()=>{ children()  },[ children ])
   return <div>{ renderChild } </div>
}

通過 useMemo 緩存了子組件的渲染結(jié)果 renderChild,只有當(dāng) children 發(fā)生變化時(shí),才會(huì)重新執(zhí)行子組件的渲染。

4.2 性能優(yōu)化:useCallback

useCallback基礎(chǔ)介紹:

useCallback 和 useMemo 接收的參數(shù)類似,都是在其依賴項(xiàng)發(fā)生變化后才執(zhí)行,都返回緩存的值。但是它們的區(qū)別在于,useMemo 返回的是函數(shù)運(yùn)行的結(jié)果,而 useCallback 返回的是一個(gè)經(jīng)過處理的函數(shù)本身。它主要用于優(yōu)化性能,避免不必要的函數(shù)重新創(chuàng)建,特別是在向子組件傳遞函數(shù)時(shí),避免因?yàn)楹瘮?shù)重新創(chuàng)建而導(dǎo)致子組件不必要的重新渲染。

基本用法:

const cachedCallback = useCallback(callbackFunction, deps)
  • callbackFunction: 第一個(gè)參數(shù)是一個(gè)函數(shù),需要進(jìn)行緩存的函數(shù)。
  • deps: 第二個(gè)參數(shù)是一個(gè)數(shù)組,包含當(dāng)前 useCallback 的依賴項(xiàng)。當(dāng)這些依賴項(xiàng)發(fā)生變化時(shí),會(huì)重新創(chuàng)建新的緩存函數(shù)。
  • cachedCallback: 返回值,為經(jīng)過處理的緩存函數(shù)。

基礎(chǔ)用法示例:

const DemoChildren = React.memo((props)=>{
    console.log('子組件更新')
    useEffect(()=>{
        props.getInfo('子組件')
    },[])
    return <div>子組件</div>
})

const DemoUseCallback=({ id })=>{
    const [number, setNumber] = useState(1)
    
    const getInfo  = useCallback((sonName)=>{
        console.log(sonName)
    }, [id]) // 只有當(dāng) id 發(fā)生變化時(shí),才會(huì)重新創(chuàng)建 getInfo 函數(shù)
    
    return (
        <div>
            <button onClick={ ()=>setNumber(number+1) }>增加</button>
            <DemoChildren getInfo={getInfo} />
        </div>
    )
}

在上面的示例中,getInfo 函數(shù)通過 useCallback 進(jìn)行了緩存,只有當(dāng) id 發(fā)生變化時(shí),才會(huì)重新創(chuàng)建 getInfo 函數(shù)。這樣可以避免因?yàn)楹瘮?shù)重新創(chuàng)建而導(dǎo)致子組件不必要的重新渲染。

5.1 工具類:useId

在組件的頂層調(diào)用 useId 生成唯一 ID:

import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  // ...

5.2 工具類:useDebugValue

在你的 自定義 Hook 的頂層調(diào)用 useDebugValue,以顯示可讀的調(diào)試值:

import { useDebugValue } from 'react';

function useOnlineStatus() {
  // ...
  useDebugValue(isOnline ? 'Online' : 'Offline');
  // ...
}


責(zé)任編輯:華軒 來源: 程序員Sunday
相關(guān)推薦

2020-03-16 10:25:49

前端React Hooks響應(yīng)式布局

2024-08-30 08:53:24

2016-08-31 00:34:51

AR技術(shù)VR技術(shù)3D技術(shù)

2022-08-21 09:41:42

ReactVue3前端

2022-07-18 09:01:58

React函數(shù)組件Hooks

2022-04-03 19:21:18

部署架構(gòu)存儲(chǔ)

2019-08-20 15:16:26

Reacthooks前端

2020-07-02 15:15:22

JavaScript面試前端

2022-06-23 09:04:14

ReactHooks項(xiàng)目

2023-07-14 22:36:42

Node.jsStorage

2023-11-06 08:00:00

ReactJavaScript開發(fā)

2020-10-28 09:12:48

React架構(gòu)Hooks

2023-12-18 09:39:13

PreactHooks狀態(tài)管理

2011-04-13 11:27:28

EIGRP路由

2020-12-02 08:30:27

Java Synchroniz并發(fā)

2010-03-25 17:20:00

CentOS入門

2009-10-26 13:45:39

linux Makef

2025-04-22 11:00:00

網(wǎng)絡(luò)協(xié)議通信網(wǎng)絡(luò)

2022-03-31 17:54:29

ReactHooks前端

2012-08-16 15:56:33

XML
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)