溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

react中一共有多少個(gè)hooks

發(fā)布時(shí)間:2021-11-25 15:07:17 來(lái)源:億速云 閱讀:177 作者:小新 欄目:web開(kāi)發(fā)

這篇文章主要介紹了react中一共有多少個(gè)hooks,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

react共有9個(gè)hooks:useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect。

本教程操作環(huán)境:Windows7系統(tǒng)、react17.0.1版、Dell G3電腦。

React Hooks(總共9個(gè))

在 React 的世界中,有容器組件和 UI 組件之分,在 React Hooks 出現(xiàn)之前,UI 組件我們可以使用函數(shù),無(wú)狀態(tài)組件來(lái)展示 UI,而對(duì)于容器組件,函數(shù)組件就顯得無(wú)能為力,我們依賴于類(lèi)組件來(lái)獲取數(shù)據(jù),處理數(shù)據(jù),并向下傳遞參數(shù)給 UI 組件進(jìn)行渲染。在我看來(lái),使用 React Hooks 相比于從前的類(lèi)組件有以下幾點(diǎn)好處:

  • 代碼可讀性更強(qiáng),原本同一塊功能的代碼邏輯被拆分在了不同的生命周期函數(shù)中,容易使開(kāi)發(fā)者不利于維護(hù)和迭代,通過(guò) React Hooks 可以將功能代碼聚合,方便閱讀維護(hù)

  • 組件樹(shù)層級(jí)變淺,在原本的代碼中,我們經(jīng)常使用 HOC/render props 等方式來(lái)復(fù)用組件的狀態(tài),增強(qiáng)功能等,無(wú)疑增加了組件樹(shù)層數(shù)及渲染,而在 React Hooks 中,這些功能都可以通過(guò)強(qiáng)大的自定義的 Hooks 來(lái)實(shí)現(xiàn)

React 在 v16.8 的版本中推出了 React Hooks 新特性,雖然社區(qū)還沒(méi)有最佳實(shí)踐如何基于 React Hooks 來(lái)打造復(fù)雜應(yīng)用(至少我還沒(méi)有),憑借著閱讀社區(qū)中大量的關(guān)于這方面的文章,下面我將通過(guò)十個(gè)案例來(lái)幫助你認(rèn)識(shí)理解并可以熟練運(yùn)用 React Hooks 大部分特性。

1、useState 保存組件狀態(tài)

在類(lèi)組件中,我們使用 this.state 來(lái)保存組件狀態(tài),并對(duì)其修改觸發(fā)組件重新渲染。比如下面這個(gè)簡(jiǎn)單的計(jì)數(shù)器組件,很好詮釋了類(lèi)組件如何運(yùn)行:

import React from "react";
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      name: "alife"
    };
  }
  render() {
    const { count } = this.state;
    return (
      <p>
        Count: {count}
        <button onClick={() => this.setState({ count: count + 1 })}>+</button>
        <button onClick={() => this.setState({ count: count - 1 })}>-</button>
      </p>
    );
  }
}

一個(gè)簡(jiǎn)單的計(jì)數(shù)器組件就完成了,而在函數(shù)組件中,由于沒(méi)有 this 這個(gè)黑魔法,React 通過(guò) useState 來(lái)幫我們保存組件的狀態(tài)。

import React, { useState } from "react";
function App() {
  const [obj, setObject] = useState({
    count: 0,
    name: "alife"
  });
  return (
    <p className="App">
      Count: {obj.count}
      <button onClick={() => setObject({ ...obj, count: obj.count + 1 })}>+</button>
      <button onClick={() => setObject({ ...obj, count: obj.count - 1 })}>-</button>
    </p>
  );
}

通過(guò)傳入 useState 參數(shù)后返回一個(gè)帶有默認(rèn)狀態(tài)和改變狀態(tài)函數(shù)的數(shù)組。通過(guò)傳入新?tīng)顟B(tài)給函數(shù)來(lái)改變?cè)镜臓顟B(tài)值。值得注意的是 useState 不幫助你處理狀態(tài),相較于 setState 非覆蓋式更新?tīng)顟B(tài),useState 覆蓋式更新?tīng)顟B(tài),需要開(kāi)發(fā)者自己處理邏輯。(代碼如上)

似乎有個(gè) useState 后,函數(shù)組件也可以擁有自己的狀態(tài)了,但僅僅是這樣完全不夠。

2、useEffect 處理副作用

函數(shù)組件能保存狀態(tài),但是對(duì)于異步請(qǐng)求,副作用的操作還是無(wú)能為力,所以 React 提供了 useEffect 來(lái)幫助開(kāi)發(fā)者處理函數(shù)組件的副作用,在介紹新 API 之前,我們先來(lái)看看類(lèi)組件是怎么做的:

import React, { Component } from "react";
class App extends Component {
  state = {
    count: 1
  };
  componentDidMount() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
    this.timer = setInterval(() => {
      this.setState(({ count }) => ({
        count: count + 1
      }));
    }, 1000);
  }
  componentDidUpdate() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
  }
  componentWillUnmount() {
    document.title = "componentWillUnmount";
    clearInterval(this.timer);
  }
  render() {
    const { count } = this.state;
    return (
      <p>
        Count:{count}
        <button onClick={() => clearInterval(this.timer)}>clear</button>
      </p>
    );
  }
}

在例子中,組件每隔一秒更新組件狀態(tài),并且每次觸發(fā)更新都會(huì)觸發(fā) document.title 的更新(副作用),而在組件卸載時(shí)修改 document.title(類(lèi)似于清除)

從例子中可以看到,一些重復(fù)的功能開(kāi)發(fā)者需要在 componentDidMount 和 componentDidUpdate 重復(fù)編寫(xiě),而如果使用 useEffect 則完全不一樣。

import React, { useState, useEffect } from "react";
let timer = null;
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = "componentDidMount" + count;
  },[count]);

  useEffect(() => {
    timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
    // 一定注意下這個(gè)順序:
    // 告訴react在下次重新渲染組件之后,同時(shí)是下次執(zhí)行上面setInterval之前調(diào)用
    return () => {
      document.title = "componentWillUnmount";
      clearInterval(timer);
    };
  }, []);
  return (
    <p>
      Count: {count}
      <button onClick={() => clearInterval(timer)}>clear</button>
    </p>
  );
}

我們使用 useEffect 重寫(xiě)了上面的例子,useEffect 第一個(gè)參數(shù)接收一個(gè)函數(shù),可以用來(lái)做一些副作用比如異步請(qǐng)求,修改外部參數(shù)等行為,而第二個(gè)參數(shù)稱之為dependencies,是一個(gè)數(shù)組,如果數(shù)組中的值變化才會(huì)觸發(fā) 執(zhí)行useEffect 第一個(gè)參數(shù)中的函數(shù)。返回值(如果有)則在組件銷(xiāo)毀或者調(diào)用函數(shù)前調(diào)用。

  • 1.比如第一個(gè) useEffect 中,理解起來(lái)就是一旦 count 值發(fā)生改變,則修改 documen.title 值;

  • 2.而第二個(gè) useEffect 中傳遞了一個(gè)空數(shù)組[],這種情況下只有在組件初始化或銷(xiāo)毀的時(shí)候才會(huì)觸發(fā),用來(lái)代替 componentDidMount 和 componentWillUnmount,慎用;


    1. 還有另外一個(gè)情況,就是不傳遞第二個(gè)參數(shù),也就是useEffect只接收了第一個(gè)函數(shù)參數(shù),代表不監(jiān)聽(tīng)任何參數(shù)變化。每次渲染DOM之后,都會(huì)執(zhí)行useEffect中的函數(shù)。

基于這個(gè)強(qiáng)大 Hooks,我們可以模擬封裝出其他生命周期函數(shù),比如 componentDidUpdate 代碼十分簡(jiǎn)單

function useUpdate(fn) {
    // useRef 創(chuàng)建一個(gè)引用
    const mounting = useRef(true);
    useEffect(() => {
      if (mounting.current) {
        mounting.current = false;
      } else {
        fn();
      }
    });
}

現(xiàn)在我們有了 useState 管理狀態(tài),useEffect 處理副作用,異步邏輯,學(xué)會(huì)這兩招足以應(yīng)對(duì)大部分類(lèi)組件的使用場(chǎng)景。

3、useContext 減少組件層級(jí)

上面介紹了 useState、useEffect 這兩個(gè)最基本的 API,接下來(lái)介紹的 useContext 是 React 幫你封裝好的,用來(lái)處理多層級(jí)傳遞數(shù)據(jù)的方式,在以前組件樹(shù)種,跨層級(jí)祖先組件想要給孫子組件傳遞數(shù)據(jù)的時(shí)候,除了一層層 props 往下透?jìng)髦猓覀冞€可以使用 React Context API 來(lái)幫我們做這件事,舉個(gè)簡(jiǎn)單的例子:

const { Provider, Consumer } = React.createContext(null);
function Bar() {
  return <Consumer>{color => <p>{color}</p>}</Consumer>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <Provider value={"grey"}>
      <Foo />
    </Provider>
  );
}

通過(guò) React createContext 的語(yǔ)法,在 APP 組件中可以跨過(guò) Foo 組件給 Bar 傳遞數(shù)據(jù)。而在 React Hooks 中,我們可以使用 useContext 進(jìn)行改造。

const colorContext = React.createContext("gray");
function Bar() {
  const color = useContext(colorContext);
  return <p>{color}</p>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <colorContext.Provider value={"red"}>
      <Foo />
    </colorContext.Provider>
  );
}

傳遞給 useContext 的是 context 而不是 consumer,返回值即是想要透?jìng)鞯臄?shù)據(jù)了。用法很簡(jiǎn)單,使用 useContext 可以解決 Consumer 多狀態(tài)嵌套的問(wèn)題。

function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}

而使用 useContext 則變得十分簡(jiǎn)潔,可讀性更強(qiáng)且不會(huì)增加組件樹(shù)深度。

function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);
  return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}

4、useReducer

useReducer 這個(gè) Hooks 在使用上幾乎跟 Redux/React-Redux 一模一樣,唯一缺少的就是無(wú)法使用 redux 提供的中間件。我們將上述的計(jì)時(shí)器組件改寫(xiě)為 useReducer,

import React, { useReducer } from "react";
const initialState = {
  count: 0
};
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
        -
      </button>
    </>
  );
}

用法跟 Redux 基本上是一致的,用法也很簡(jiǎn)單,算是提供一個(gè) mini 的 Redux 版本。

5、useCallback 記憶函數(shù)

在類(lèi)組件中,我們經(jīng)常犯下面這樣的錯(cuò)誤:

class App {
    render() {
        return <p>
            <SomeComponent style={{ fontSize: 14 }} doSomething={ () => { console.log('do something'); }}  />
        </p>;
    }
}

這樣寫(xiě)有什么壞處呢?一旦 App 組件的 props 或者狀態(tài)改變了就會(huì)觸發(fā)重渲染,即使跟 SomeComponent 組件不相關(guān),由于每次 render 都會(huì)產(chǎn)生新的 style 和 doSomething(因?yàn)橹匦聄ender前后, style 和 doSomething分別指向了不同的引用),所以會(huì)導(dǎo)致 SomeComponent 重新渲染,倘若 SomeComponent 是一個(gè)大型的組件樹(shù),這樣的 Virtual Dom 的比較顯然是很浪費(fèi)的,解決的辦法也很簡(jiǎn)單,將參數(shù)抽離成變量。

const fontSizeStyle = { fontSize: 14 };
class App {
    doSomething = () => {
        console.log('do something');
    }
    render() {
        return <p>
            <SomeComponent style={fontSizeStyle} doSomething={ this.doSomething }  />
        </p>;
    }
}

在類(lèi)組件中,我們還可以通過(guò) this 這個(gè)對(duì)象來(lái)存儲(chǔ)函數(shù),而在函數(shù)組件中沒(méi)辦法進(jìn)行掛載了。所以函數(shù)組件在每次渲染的時(shí)候如果有傳遞函數(shù)的話都會(huì)重渲染子組件。

function App() {
  const handleClick = () => {
    console.log('Click happened');
  }
  return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}

這里多說(shuō)一句,一版把函數(shù)式組件理解為class組件render函數(shù)的語(yǔ)法糖,所以每次重新渲染的時(shí)候,函數(shù)式組件內(nèi)部所有的代碼都會(huì)重新執(zhí)行一遍。所以上述代碼中每次render,handleClick都會(huì)是一個(gè)新的引用,所以也就是說(shuō)傳遞給SomeComponent組件的props.onClick一直在變(因?yàn)槊看味际且粋€(gè)新的引用),所以才會(huì)說(shuō)這種情況下,函數(shù)組件在每次渲染的時(shí)候如果有傳遞函數(shù)的話都會(huì)重渲染子組件。

而有了 useCallback 就不一樣了,你可以通過(guò) useCallback 獲得一個(gè)記憶后的函數(shù)。

function App() {
  const memoizedHandleClick = useCallback(() => {
    console.log('Click happened')
  }, []); // 空數(shù)組代表無(wú)論什么情況下該函數(shù)都不會(huì)發(fā)生改變
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

老規(guī)矩,第二個(gè)參數(shù)傳入一個(gè)數(shù)組,數(shù)組中的每一項(xiàng)一旦值或者引用發(fā)生改變,useCallback 就會(huì)重新返回一個(gè)新的記憶函數(shù)提供給后面進(jìn)行渲染。

這樣只要子組件繼承了 PureComponent 或者使用 React.memo 就可以有效避免不必要的 VDOM 渲染。

6、useMemo 記憶組件

useCallback 的功能完全可以由 useMemo 所取代,如果你想通過(guò)使用 useMemo 返回一個(gè)記憶函數(shù)也是完全可以的。

useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).

所以前面使用 useCallback 的例子可以使用 useMemo 進(jìn)行改寫(xiě):

function App() {
  const memoizedHandleClick = useMemo(() => () => {
    console.log('Click happened')
  }, []); // 空數(shù)組代表無(wú)論什么情況下該函數(shù)都不會(huì)發(fā)生改變
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

唯一的區(qū)別是:**useCallback 不會(huì)執(zhí)行第一個(gè)參數(shù)函數(shù),而是將它返回給你,而 useMemo 會(huì)執(zhí)行第一個(gè)函數(shù)并且將函數(shù)執(zhí)行結(jié)果返回給你。**所以在前面的例子中,可以返回 handleClick 來(lái)達(dá)到存儲(chǔ)函數(shù)的目的。

所以 useCallback 常用記憶事件函數(shù),生成記憶后的事件函數(shù)并傳遞給子組件使用。而 useMemo 更適合經(jīng)過(guò)函數(shù)計(jì)算得到一個(gè)確定的值,比如記憶組件。

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b= />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

當(dāng) a/b 改變時(shí),child1/child2 才會(huì)重新渲染。從例子可以看出來(lái),只有在第二個(gè)參數(shù)數(shù)組的值發(fā)生變化時(shí),才會(huì)觸發(fā)子組件的更新。

7、useRef 保存引用值

useRef 跟 createRef 類(lèi)似,都可以用來(lái)生成對(duì) DOM 對(duì)象的引用,看個(gè)簡(jiǎn)單的例子:

import React, { useState, useRef } from "react";
function App() {
  let [name, setName] = useState("Nate");
  let nameRef = useRef();
  const submitButton = () => {
    setName(nameRef.current.value);
  };
  return (
    <p className="App">
      <p>{name}</p>

      <p>
        <input ref={nameRef} type="text" />
        <button type="button" onClick={submitButton}>
          Submit
        </button>
      </p>
    </p>
  );
}

useRef 返回的值傳遞給組件或者 DOM 的 ref 屬性,就可以通過(guò) ref.current 值訪問(wèn)組件或真實(shí)的 DOM 節(jié)點(diǎn),重點(diǎn)是組件也是可以訪問(wèn)到的,從而可以對(duì) DOM 進(jìn)行一些操作,比如監(jiān)聽(tīng)事件等等。

當(dāng)然 useRef 遠(yuǎn)比你想象中的功能更加強(qiáng)大,useRef 的功能有點(diǎn)像類(lèi)屬性,或者說(shuō)您想要在組件中記錄一些值,并且這些值在稍后可以更改。

利用 useRef 就可以繞過(guò) Capture Value 的特性。可以認(rèn)為 ref 在所有 Render 過(guò)程中保持著唯一引用,因此所有對(duì) ref 的賦值或取值,拿到的都只有一個(gè)最終狀態(tài),而不會(huì)在每個(gè) Render 間存在隔離。

React Hooks 中存在 Capture Value 的特性:

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      alert("count: " + count);
    }, 3000);
  }, [count]);

  return (
    <p>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>增加 count</button>
      <button onClick={() => setCount(count - 1)}>減少 count</button>
    </p>
  );
}

先點(diǎn)擊增加button,后點(diǎn)擊減少button,3秒后先alert 1,后alert 0,而不是alert兩次0。這就是所謂的 capture value 的特性。而在類(lèi)組件中 3 秒后輸出的就是修改后的值,因?yàn)檫@時(shí)候** message 是掛載在 this 變量上,它保留的是一個(gè)引用值**,對(duì) this 屬性的訪問(wèn)都會(huì)獲取到最新的值。講到這里你應(yīng)該就明白了,useRef 創(chuàng)建一個(gè)引用,就可以有效規(guī)避 React Hooks 中 Capture Value 特性。

function App() {
  const count = useRef(0);

  const showCount = () => {
    alert("count: " + count.current);
  };

  const handleClick = number => {
    count.current = count.current + number;
    setTimeout(showCount, 3000);
  };

  return (
    <p>
      <p>You clicked {count.current} times</p>
      <button onClick={() => handleClick(1)}>增加 count</button>
      <button onClick={() => handleClick(-1)}>減少 count</button>
    </p>
  );
}

只要將賦值與取值的對(duì)象變成 useRef,而不是 useState,就可以躲過(guò) capture value 特性,在 3 秒后得到最新的值。

8、useImperativeHandle 透?jìng)?Ref

通過(guò) useImperativeHandle 用于讓父組件獲取子組件內(nèi)的索引

import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";
function ChildInputComponent(props, ref) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => inputRef.current);
  return <input type="text" name="child input" ref={inputRef} />;
}
const ChildInput = forwardRef(ChildInputComponent);
function App() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <p>
      <ChildInput ref={inputRef} />
    </p>
  );
}

通過(guò)這種方式,App 組件可以獲得子組件的 input 的 DOM 節(jié)點(diǎn)。

9、useLayoutEffect 同步執(zhí)行副作用

大部分情況下,使用 useEffect 就可以幫我們處理組件的副作用,但是如果想要同步調(diào)用一些副作用,比如對(duì) DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用會(huì)在 DOM 更新之后同步執(zhí)行。

function App() {
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    const title = document.querySelector("#title");
    const titleWidth = title.getBoundingClientRect().width;
    console.log("useLayoutEffect");
    if (width !== titleWidth) {
      setWidth(titleWidth);
    }
  });
  useEffect(() => {
    console.log("useEffect");
  });
  return (
    <p>
      <h2 id="title">hello</h2>
      <h3>{width}</h3>
    </p>
  );
}

在上面的例子中,useLayoutEffect 會(huì)在 render,DOM 更新之后同步觸發(fā)函數(shù),會(huì)優(yōu)于 useEffect 異步觸發(fā)函數(shù)。

(1) useEffect和useLayoutEffect有什么區(qū)別?

簡(jiǎn)單來(lái)說(shuō)就是調(diào)用時(shí)機(jī)不同,useLayoutEffect和原來(lái)componentDidMount&componentDidUpdate一致,在react完成DOM更新后馬上同步調(diào)用的代碼,會(huì)阻塞頁(yè)面渲染。而useEffect是會(huì)在整個(gè)頁(yè)面渲染完才會(huì)調(diào)用的代碼。

官方建議優(yōu)先使用useEffect

However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.

在實(shí)際使用時(shí)如果想避免頁(yè)面抖動(dòng)(在useEffect里修改DOM很有可能出現(xiàn))的話,可以把需要操作DOM的代碼放在useLayoutEffect里。關(guān)于使用useEffect導(dǎo)致頁(yè)面抖動(dòng)。

不過(guò)useLayoutEffect在服務(wù)端渲染時(shí)會(huì)出現(xiàn)一個(gè)warning,要消除的話得用useEffect代替或者推遲渲染時(shí)機(jī)。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“react中一共有多少個(gè)hooks”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI