React-Hooks API 簡介

1、什么是Hooks

在不編寫 class 的情況下使用 state 以及其他的 React 特性(生命周期)

規(guī)則

  • 只在最頂層使用Hook
    不要在循環(huán)、條件或是嵌套函數(shù)中調(diào)用Hook;因為React在為組件每次渲染時,保存和讀取數(shù)據(jù)是依賴Hook的執(zhí)行按順序來進行的。Hooks會記錄下調(diào)用的次序以及入?yún)⒑统鰠ⅲ坏╉樞虺鲥e,那么在再次渲染時對應(yīng)的返回值就會出現(xiàn)問題。
  • 只在React函數(shù)中調(diào)用Hook
    不要在普通的JS函數(shù)中調(diào)用Hook。Hook只能在React的函數(shù)組件中調(diào)用,或是在自定義Hook中調(diào)用其他的Hook。
  • 自定義Hooks使用 use 為開頭進行聲明

State Hooks

函數(shù)組件中,使用class組件的state類似功能。進行函數(shù)式組件內(nèi)部狀態(tài)管理,以及組件更新。
是一個異步更新

  import React, { useState } from "react";

  export default function UseState() {
    const [count, setCount] = useState(0);
    return (
      <div>
        <p> useState</p>
        <h2>點擊次數(shù):{count}</h2>
        <button
          onClick={() => {
            setCount(count + 1);
          }}
        >
          點擊
        </button>
      </div>
    );
  }

useState 做了什么

通過useState進行注冊的“state”以及變更方法,會被React保留,不會隨著函數(shù)組件退出而被銷毀。

useState 入?yún)?/h3>

入?yún)ⅲ骸皊tate”初始化時需要的值|或是一個純函數(shù)返回初始化的值

useState 出參

出參:是一個數(shù)組。

  • 第一個值:是“state”初始化的賦值結(jié)果(類似于 this.state.xxx),用于后續(xù)組件或函數(shù)中的計算以及展示。
  • 第二個值:是修改這個"state"的方法(類似于 this.setstate)。

修改state的setState方法有兩種使用方式

  • 方法一:利用原有的state進行更新
    setState(state+1)
  • 方法二:利用函數(shù)式更新,這樣可以避免在某些無法直接獲取state準確值時進行使用
    例如在useEffect中,不想指定state作為依賴,又需要修改state時,這時候因為state被閉包,永遠拿不到準確值
    setState(s=>s+1)
// 連續(xù)多次調(diào)用方法一,只會執(zhí)行一次。但是方法二是每次都生效。
  const [count,setCount] = useState(0);
  if (way === "newState") {
      setCount(count + 1);
      setCount(count + 1);
      setCount(count + 1);
      setCount(count + 1);
      setCount(count + 1);
      // 執(zhí)行結(jié)束后 count = count + 1
    } else if (way === "func") {
      setCount((s) => s + 1);
      setCount((s) => s + 1);
      setCount((s) => s + 1);
      setCount((s) => s + 1);
      setCount((s) => s + 1);
      // 執(zhí)行結(jié)束后 count = count + 5
    }

如何使用多個state變量

  • 方法一:
    多個state,多個聲明。因為變量與函數(shù)是成對出現(xiàn)的,可以方便區(qū)分。
    const [state1,setState1] = useState(0);
    const [state2,setState2] = useState(2);
    const [state3,setState3] = useState(3);

  • 方法二:
    將相互關(guān)連的數(shù)據(jù),可以放在一個對象中進行管理。但是不建議將所有數(shù)據(jù)都放在一個對象中。因為useState不會幫你合并,只會直接替換。
    const initState = {
    state1:'haha',
    state2:'hehe'
    }
    const [mixState,setMinState] = useState(initState);
    setMixState({state1:'heihei'}); // 這樣就沒有state2了
    setMixState({...mixState,state1:'heihei'});

    所以盡量將相關(guān)聯(lián)的數(shù)據(jù)放到一個state,但是相關(guān)性不大的數(shù)據(jù),放在一起,尤其是在復(fù)雜數(shù)據(jù)結(jié)構(gòu)進行數(shù)據(jù)更新時會顯得很麻煩。

Effect Hooks

函數(shù)組件中,使用class組件中生命周期(componentDidmount/componentUpdate)等生命周期,方便在函數(shù)組件進行渲染的時候
可以在渲染過程中進行 訂閱/取消訂閱 消息 等操作。
UseEffect是不會阻塞瀏覽器更新屏幕,是異步的。如果需要測量屏幕之類的同步Effect,可以使用useLauoutEffect。
當出現(xiàn)多個useEffect時,會根據(jù)聲明順序,依次調(diào)用多個useEffect。

  import React, { useState, useEffect } from "react";

  export default function UseEffect() {
    const [count, setCount] = useState(0);

    // 相當于 componentDidMount 和 componentDidUpdate:
    useEffect(() => {
      // 使用瀏覽器的 API 更新頁面標題
      document.title = `You clicked ${count} times`;
    });

    return (
      <div>
        <p>點擊次數(shù):{count}</p>
        <button onClick={() => setCount(count + 1)}>點擊</button>
      </div>
    );
  }

需要清除的Effect

如同ComponentWillUnmount一樣,綁定的監(jiān)聽事件或是定時器,需要在組件卸載時進行取消,防止內(nèi)存泄漏。那么useEffect也
允許通過在傳入的函數(shù)中,返回一個可執(zhí)行的函數(shù),作為清除Effect時被調(diào)用。

  useEffect(()=>{
      console.log('每次渲染都執(zhí)行');
      return ()=>{
          console.log('卸載時執(zhí)行');
      }
  });

注意:
effect在每次渲染時候都會執(zhí)行。但在執(zhí)行當前effect時,會主動對上一個effect進行清除,主動執(zhí)行函數(shù)中返回的可執(zhí)行函數(shù)。
因為每一次的渲染執(zhí)行effect時可能都會進行一個綁定,那么如果沒有取消上一個監(jiān)聽,會導(dǎo)致監(jiān)聽錯亂,甚至重復(fù)監(jiān)聽。

  例如:模擬一個函數(shù)組件渲染更新過程
  //Mount FuncComponent
  執(zhí)行effect,添加一個監(jiān)聽

  // update FuncComponent
  執(zhí)行清除Effect函數(shù),移除上一個添加的監(jiān)聽
  執(zhí)行effect,添加一個監(jiān)聽

如果沒有清除這一步,那么頁面上同樣一個組件在每一次更新都會重新注冊,卻不刪除原有監(jiān)聽。這樣會出現(xiàn)bug!

同樣的如果我們的useEffect執(zhí)行了并不需要重復(fù)調(diào)用的操作,那么我們可以通過第二個參數(shù)進行跳過。
類似于 componentDidUpdate一樣,可以通過檢查prevState,prevProps來確定是否執(zhí)行更新。
useEffect,也可一將需要比較的參數(shù)組成數(shù)組,作為第二個可選參數(shù),通知React,是否需要在渲染中再次執(zhí)行。

// 父組件將更新次數(shù)傳遞給下層組件,下層組件每5次進行一個執(zhí)行一次useEffect
useEffect(()=>{ 
      console.log('counter變?yōu)榱耍?,props.updateTimes) 
      },[parseInt(props.updateTimes/5)]);

如果想執(zhí)行只運行一次的 effect(僅在組件掛載和卸載時執(zhí)行),可以傳遞一個空數(shù)組([])作為第二個參數(shù)。
這就告訴 React 你的 effect 不依賴于 props 或 state 中的任何值,所以它永遠都不需要重復(fù)執(zhí)行。
這并不屬于特殊情況 —— 它依然遵循依賴數(shù)組的工作方式。

自定義Hooks

如果函數(shù)的名字以 “use” 開頭并調(diào)用其他 Hook,我們就說這是一個自定義 Hook。
每一個函數(shù)組件中調(diào)用同一個自定義Hooks,但每個函數(shù)組件中自定義Hooks返回的值是獨立的,不會共享。
相當于將一組Hooks封裝在了一起,然后直接加以調(diào)用。這樣與直接在函數(shù)組件中調(diào)用沒有區(qū)別。

// 自定義一個模擬useReducer的自定義組件
function useCustomReducer(reducer, initState) {
  const [state, setState] = useState(initState);
  function dispatch(action, payload) {
    const newState = reducer(action, state, payload);
    setState(newState);
  }
  return [state, dispatch];
}

其他Hooks

useContext

  const value = useContext(MyContext);

接收一個 context 對象(React.createContext 的返回值)并返回該 context 的當前值。
當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider> 的 value prop 決定。

當組件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發(fā)重渲染,
并使用最新傳遞給 MyContext provider 的 context value 值。
即使祖先使用 React.memo 或 shouldComponentUpdate,也會在組件本身使用 useContext 時重新渲染。

useReducer

  const [state,dispatch] = useReducer(reducer,initialArg,initState)

參數(shù):

  • reducer 純函數(shù),接收一個action(字符串,用于處理究竟執(zhí)行哪個更新操作),一個變更值,返回一個新的state
  • initialArg 純函數(shù) 接收 initstate,并返回一個操作后的state,最終作為初始化state
  • initState 默認state,若沒有第二個參數(shù),只傳initstate,則直接作為初始化state

useMemo

  const memoizedValue = useMemo(() =>   
           computeExpensiveValue(a, b), [a, b]);

返回一個 通過第一個函數(shù)計算得出的 memoized 值,就是要求第一個參數(shù)是一個純函數(shù),且必須有返回值。

把“創(chuàng)建”函數(shù)和依賴項數(shù)組作為參數(shù)傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。
這種優(yōu)化有助于避免在每次渲染時都進行高開銷的計算。

記住,傳入 useMemo 的函數(shù)會在渲染期間執(zhí)行。
請不要在這個函數(shù)內(nèi)部執(zhí)行與渲染無關(guān)的操作,諸如副作用這類的操作屬于 useEffect 的適用范疇,而不是 useMemo。

如果沒有提供依賴項數(shù)組,useMemo 在每次渲染時都會計算新的值。

useCallBack

  const memoizedCallback = useCallback(
    () => {
      doSomething(a, b);
    },
    [a, b],
  );

返回一個 memoized 回調(diào)函數(shù),使用還是同樣的使用,只是對這個函數(shù),以及對應(yīng)的依賴項進行了一個緩存。
只要函數(shù)依賴項不發(fā)生改變,那么傳遞給子組件時,不會因為父組件的更新,子組件接收這個函數(shù)是一個新對象,而導(dǎo)致子組件的不必要更新。

把內(nèi)聯(lián)回調(diào)函數(shù)及依賴項數(shù)組作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本,該回調(diào)函數(shù)僅在某個依賴項改變時才會更新。
當你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時,它將非常有用。
useCallback(fn, deps) 相當于 useMemo(() => fn, deps)。

對比useMemo,useMemo緩存的是一個值,useCallback緩存的是一個函數(shù),是對一個單獨的props值進行緩存
memo緩存的是組件本身,是站在全局的角度進行優(yōu)化

useRef

useRef可以接受一個默認值,并返回一個含有current屬性的可變對象;
使用場景:
1、獲取子組件的實例(子組件需為react類繼承組件);

    function parent(){
      const childRef = useRef(null);
      return (<>
                     <Child ref={childRef} />
                     <button onClick={()=>{console.log(childRef.current)}} >
                        獲取Child組件
                     </button>
             </>)
    }

    class child extends Components{
      return <>haha</>
    }

2、獲取組件中某個DOM元素;

const inputRef = useRef(null);
<input ref={inputRef} type="text" />

3、用做組件的全局變量。
useRef返回對象中含有一個current屬性,該屬性可以在整個組件色生命周期內(nèi)不變,不會因為重復(fù)render而重復(fù)申明,
類似于react類繼承組件的屬性this.xxx一樣。

// 存儲普通變量
const isClick = useRef(false);
console.log(isClick.current); // 輸出 false 
// 存儲創(chuàng)建開銷較高的對象
const complexObjectRef = useRef(null);
function getComplexOebjct{
  if(!complexObjectRef.current){
    complexObjectRef.current = new ComplexObject();
  }
  return complexObjectRef.current;
}
const complexObject = getComplexOebjct();

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps]);

入?yún)?/strong>

  • ref:定義 current 對象的 ref

  • createHandle:一個函數(shù),返回值是一個對象,即這個 ref 的 current

  • [deps]:即依賴列表,當監(jiān)聽的依賴發(fā)生變化,useImperativeHandle 才會重新將子組件的實例屬性輸出到父組件

    與React.forwadRef搭配,返回一個由子組件修改后的Ref,傳遞回父組件方便父組件查詢部分值的變化。

import React, { useRef, useState, useImperativeHandle } from "react";

const Child = React.forwardRef((props, ref) => {
  const inputRef = useRef(null);
  const globalAttr = useRef(0);
  const [clickTime, setClickTime] = useState(0);
  useImperativeHandle(ref, () => {
    return {
      globalAttr,
      addClickTime: () => {
        setClickTime(clickTime + 1);
      },
      focus: () => {
        inputRef.current.focus();
      },
    };
  });
  return (
    <div>
      <p>父組件點擊次數(shù)值:{clickTime}</p>
      <p>全局變量值:{globalAttr.current}</p>
      <input ref={inputRef} type="text" />
      <button
        onClick={() => {
          globalAttr.current += 1;
        }}
      >
        更新子組件全局變量
      </button>
    </div>
  );
});

export default function UseImperativeHandle() {
  const childRef = useRef(null);
  return (
    <>
      <Child ref={childRef} />
      <button
        onClick={() => {
          childRef.current.focus();
        }}
      >
        獲取子節(jié)點輸入框焦點
      </button>
      <button
        onClick={() => {
          childRef.current.addClickTime();
        }}
      >
        增加子組件添加值
      </button>
      <button
        onClick={() => {
          console.log(childRef.current.globalAttr);
        }}
      >
        輸出子組件全局變量
      </button>
    </>
  );
}

useLayoutEffect

使用方法與useEffect 是一致的。
useLayoutEffect里面的callback函數(shù)會在DOM更新完成后立即執(zhí)行,但是會在瀏覽器進行任何繪制之前運行完成,阻塞了瀏覽器的繪制。
但是它是阻塞瀏覽器渲染的更新,在繪制之前會執(zhí)行的副作用,一般用于有需要變更或是獲取DOM的操作。

useDebugValue

開發(fā)過程中使用,正式環(huán)境沒啥用
代碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容