React Hooks API 入門

已發表: 2022-03-10
快速總結 ↬在本教程中,您將學習和理解什麼是 React Hooks、可用的基本 React Hooks 以及如何為您的 React 應用程序編寫它們的示例。 在此過程中,您還將了解 React 16.8 附帶的一些附加鉤子,以及如何編寫自己的自定義 React Hooks。

當 React 16.8 於 2019 年 2 月初正式發佈時,它附帶了一個額外的 API,讓您無需編寫類即可在 React 中使用狀態和其他功能。 這個額外的 API 稱為Hooks ,它們在 React 生態系統中越來越流行,從開源項目到用於生產應用程序。

React Hooks 是完全可選的,這意味著不需要重寫現有代碼,它們不包含任何重大更改,並且可以與 React 16.8 一起使用。 一些好奇的開發者甚至在 Hooks API 正式發布之前就已經在使用它,但當時它並不穩定,只是一個實驗性功能。 現在它已經穩定並推薦給 React 開發者使用。

注意我們一般不會談論 React 或 JavaScript。 在學習本教程時,對 ReactJS 和 JavaScript 的良好了解將派上用場。

什麼是 React Hooks?

React Hooks 是內置函數,允許 React 開發人員在功能組件中使用狀態和生命週期方法,它們還可以與現有代碼一起工作,因此可以很容易地被採用到代碼庫中。 Hooks 向公眾推銷的方式是它們允許開發人員在功能組件中使用狀態,但在底層,Hooks 比這更強大。 它們允許 React 開發人員享受以下好處:

  • 改進的代碼重用;
  • 更好的代碼組合;
  • 更好的默認值;
  • 使用自定義掛鉤共享非可視化邏輯;
  • 靈活地上下移動components樹。

使用 React Hooks,開發人員可以使用功能組件來完成他們需要做的幾乎所有事情,從渲染 UI 到處理狀態和邏輯——這非常簡潔。

React Hooks 發布背後的動機

根據 ReactJS 官方文檔,React Hooks 發布的動機如下:

  • 在組件之間重用有狀態邏輯是很困難的。
    使用 Hooks,您可以在組件之間重用邏輯,而無需更改其架構或結構。
  • 複雜的組件可能難以理解。
    當組件變得更大並執行許多操作時,從長遠來看,它變得難以理解。 Hooks 解決了這個問題,允許您根據這個分離的組件的相關部分(例如設置訂閱或獲取數據)將特定的單個組件分成各種較小的函數,而不是必鬚根據生命週期方法強制拆分。
  • 課程相當混亂。
    類是正確學習 React 的障礙; 你需要了解this在 JavaScript 中是如何工作的,這與其他語言不同。 React Hooks 通過允許開發人員使用 React 的最佳功能而無需使用類來解決這個問題。
跳躍後更多! 繼續往下看↓

鉤子規則

正如 React 核心團隊在鉤子提案文檔中概述的那樣,有兩個主要規則必須嚴格遵守。

  • 確保不要在循環、條件或嵌套函數中使用 Hook;
  • 僅在 React 函數內部使用 Hooks。

基本的反應鉤子

React 16.8 附帶了 10 個內置鉤子,但基本(常用)鉤子包括:

  • useState()
  • useEffect()
  • useContext()
  • useReducer()

這些是 React 開發人員常用的 4 個基本鉤子,它們已將 React Hooks 應用到他們的代碼庫中。

useState()

useState()鉤子允許 React 開發人員更新、處理和操作功能組件內部的狀態,而無需將其轉換為類組件。 讓我們使用下面的代碼片段是一個簡單的年齡計數器組件,我們將使用它來解釋useState()掛鉤的功能和語法。

 function App() { const [age, setAge] = useState(19); const handleClick = () => setAge(age + 1) return <div> I am {age} Years Old <div> <button onClick={handleClick}>Increase my age! </button> </div> </div> }

如果您已經註意到,我們的組件看起來非常簡單、簡潔,現在它是一個函數式組件,並且沒有類組件所具有的複雜程度。

useState()鉤子接收初始狀態作為參數,然後返回,通過使用 JavaScript 中的數組解構,可以將數組中的兩個變量命名為什麼。 第一個變量是實際狀態,而第二個變量是一個函數,用於通過提供新狀態來更新狀態。

我們完成的 React 應用程序(大預覽)

這就是我們的組件在 React 應用程序中呈現時的外觀。 通過單擊“增加我的年齡”按鈕,年齡的狀態將發生變化,並且組件將像具有狀態的類組件一樣工作。

useEffect()

useEffect()鉤子接受一個包含有效代碼的函數。 在功能組件中,不允許將諸如突變、訂閱、計時器、日誌記錄和其他效果等效果放置在功能組件中,因為這樣做會導致 UI 渲染時出現很多不一致以及令人困惑的錯誤。

在使用useEffect()鉤子時,傳遞給它的效果函數將在渲染顯示在屏幕上後立即執行。 效果基本上是窺探構建 UI 的命令式方式,這與 React 的函數式方式完全不同。

默認情況下,效果主要在渲染完成後執行,但您也可以選擇在某些值更改時觸發它們。

useEffect()鉤子主要用於副作用,這些副作用通常用於與瀏覽器/DOM API 或類似外部 API 的數據獲取或訂閱的交互。 此外,如果您已經熟悉 React 生命週期方法的工作原理,您還可以將useEffect()鉤子視為組件安裝更新卸載——所有這些都結合在一個函數中。 它讓我們可以在功能組件中復制生命週期方法。

我們將使用下面的代碼片段來解釋使用useEffect()掛鉤的最基本方法。

第 1 步:定義應用程序的狀態

import React, {useState} from 'react'; function App() { //Define State const [name, setName] = useState({firstName: 'name', surname: 'surname'}); const [title, setTitle] = useState('BIO'); return( <div> <h1>Title: {title}</h1> <h3>Name: {name.firstName}</h3> <h3>Surname: {name.surname}</h3> </div> ); }; export default App

就像我們在上一節中討論的如何使用useState()鉤子來處理功能組件內部的狀態一樣,我們在代碼片段中使用它來設置呈現我全名的應用程序的狀態。

第 2 步:調用 useEffect Hook

 import React, {useState, useEffect} from 'react'; function App() { //Define State const [name, setName] = useState({firstName: 'name', surname: 'surname'}); const [title, setTitle] = useState('BIO'); //Call the use effect hook useEffect(() => { setName({FirstName: 'Shedrack', surname: 'Akintayo'}) }, [])//pass in an empty array as a second argument return( <div> <h1>Title: {title}</h1> <h3>Name: {name.firstName}</h3> <h3>Surname: {name.surname}</h3> </div> ); }; export default App

我們現在已經導入了useEffect鉤子,並且還使用了useEffect()函數來設置我們的 name 和 surname 屬性的狀態,這非常簡潔。

您可能已經註意到第二個參數中的useEffect鉤子,它是一個空數組; 這是因為它包含對setFullName的調用,該調用沒有依賴項列表。 傳遞第二個參數將阻止無限的更新鏈( componentDidUpdate() ),它還將允許我們的useEffect()掛鉤充當componentDidMount生命週期方法並渲染一次,而無需在樹中的每次更改時重新渲染。

我們的 React 應用程序現在應該如下所示:

使用useEffect Hook 反應應用程序(大預覽)

我們還可以通過調用setTitle()函數在useEffect()函數中更改應用程序的title屬性,如下所示:

 import React, {useState, useEffect} from 'react'; function App() { //Define State const [name, setName] = useState({firstName: 'name', surname: 'surname'}); const [title, setTitle] = useState('BIO'); //Call the use effect hook useEffect(() => { setName({firstName: 'Shedrack', surname: 'Akintayo'}) setTitle({'My Full Name'}) //Set Title }, [])// pass in an empty array as a second argument return( <div> <h1>Title: {title}</h1> <h3>Name: {name.firstName}</h3> <h3>Surname: {name.surname}</h3> </div> ); }; export default App

現在,在我們的應用程序重新渲染後,它現在顯示了新標題。

我們完成的項目(大預覽)

useContext()

useContext()鉤子接受一個上下文對象,即從React.createContext返回的值,然後它返回該上下文的當前上下文值。

這個鉤子使功能組件可以輕鬆訪問您的 React 應用程序上下文。 在引入useContext鉤子之前,您需要設置一個contextType或一個<Consumer>來訪問從類組件中的某個提供者傳遞下來的全局狀態。

基本上, useContext鉤子與 React Context API 一起使用,這是一種在整個應用程序中深度共享數據的方法,而無需手動將應用程序道具向下傳遞到各個級別。 現在, useContext()使使用 Context 變得更容易了。

下面的代碼片段將展示 Context API 的工作原理以及useContext Hook 如何使其變得更好。

使用上下文 API 的常規方式

import React from "react"; import ReactDOM from "react-dom"; const NumberContext = React.createContext(); function App() { return ( <NumberContext.Provider value={45}> <div> <Display /> </div> </NumberContext.Provider> ); } function Display() { return ( <NumberContext.Consumer> {value => <div>The answer to the question is {value}.</div>} </NumberContext.Consumer> ); } ReactDOM.render(<App />, document.querySelector("#root"));

現在讓我們分解代碼片段並解釋每個概念。

下面,我們正在創建一個名為NumberContext的上下文。 它旨在返回一個具有兩個值的對象: { Provider, Consumer }

 const NumberContext = React.createContext();

然後我們使用從我們創建的NumberContext返回的Provider值來使所有子級都可以使用特定的值。

 function App() { return ( <NumberContext.Provider value={45}> <div> <Display /> </div> </NumberContext.Provider> ); }

有了這個,我們可以使用從我們創建的NumberContext返回的Consumer值來獲取我們為所有孩子提供的值。 如果你注意到了,這個組件沒有得到任何道具。

 function Display() { return ( <NumberContext.Consumer> {value => <div>The answer to the question is {value}.</div>} </NumberContext.Consumer> ); } ReactDOM.render(<App />, document.querySelector("#root"));

請注意,我們如何通過將我們的內容包裝在NumberContext.Consumer中並使用 render props 方法來檢索值並呈現它,從而將App組件中的值獲取到Display組件中。

一切正常,我們使用的 render props 方法是處理動態數據的一個非常好的模式,但從長遠來看,如果你不習慣它,它確實會引入一些不必要的嵌套和混亂。

使用 useContext 方法

為了解釋useContext方法,我們將使用 useContext 鉤子重寫Display組件。

 // import useContext (or we could write React.useContext) import React, { useContext } from 'react'; // old code goes here function Display() { const value = useContext(NumberContext); return <div>The answer is {value}.</div>; }

這就是我們需要做的所有事情來展示我們的價值。 很整潔,對吧? 您調用useContext()鉤子並傳入我們創建的上下文對象,然後我們從中獲取值。

注意:不要忘記傳遞給 useContext 掛鉤的參數必須是上下文對象本身,並且任何調用 useContext 的組件都會在上下文值更改時重新渲染。

useReducer()

useReducer鉤子用於處理複雜的狀態和狀態轉換。 它接受一個reducer函數和一個初始狀態輸入; 然後,它通過數組解構返回當前狀態以及一個dispatch函數作為輸出。

下面的代碼是使用useReducer鉤子的正確語法。

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

它是useState鉤子的一種替代方案; 當您有與多個子值有關的複雜狀態邏輯或下一個狀態依賴於前一個狀態時,通常最好使用useState

其他可用的 React Hooks

useCallback 這個鉤子返回一個被記憶的回調函數,只有當依賴樹中的一個依賴改變時才會改變。
useMemo 這個鉤子返回一個記憶值,你可以傳入一個“create”函數和一個依賴數組。 如果依賴關係樹中的依賴關係之一發生變化,它返回的值只會再次使用記憶值。
useRef 這個鉤子返回一個可變的 ref 對象,其.current屬性被初始化為傳遞的參數( initialValue )。 返回的對象將在組件的整個生命週期內可用。
useImperativeHandle 這個鉤子用於自定義在 React 中使用 refs 時可用於父組件的實例值。
useLayoutEffect 這個鉤子類似於useEffect鉤子,但是,它在所有 DOM 突變後同步觸發。 它也以與componentDidUpdatecomponentDidMount相同的方式呈現。
useDebugValue 這個鉤子可用於在 React 開發工具中顯示自定義鉤子的標籤。 它對於使用 React 開發工具進行調試非常有用。

自定義反應鉤子

“自定義 Hook”是一個 JavaScript 函數,其名稱以use為前綴,可用於調用其他 Hook。 它還允許您將組件邏輯提取到可重用的函數中; 它們是普通的 JavaScript 函數,可以利用其中的其他 Hook,並且還包含可以在多個組件中使用的通用有狀態邏輯。

下面的代碼片段演示了一個用於實現無限滾動的自定義 React Hook 示例(作者 Paulo Levy):

 import { useState } from "react"; export const useInfiniteScroll = (start = 30, pace = 10) => { const [limit, setLimit] = useState(start); window.onscroll = () => { if ( window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight ) { setLimit(limit + pace); } }; return limit; };

這個自定義 Hook 接受兩個參數,即startpace 。 start 參數是要渲染的元素的起始數量,而 pace 參數是要渲染的後續元素的數量。 默認情況下, startpace參數分別設置為3010 ,這意味著您實際上可以在沒有任何參數的情況下調用 Hook,並且將使用這些默認值。

因此,為了在 React 應用程序中使用這個 Hook,我們將它與返回“假”數據的在線 API 一起使用:

 import React, { useState, useEffect } from "react"; import { useInfiniteScroll } from "./useInfiniteScroll"; const App = () => { let infiniteScroll = useInfiniteScroll(); const [tableContent, setTableContent] = useState([]); useEffect(() => { fetch("https://jsonplaceholder.typicode.com/todos/") .then(response => response.json()) .then(json => setTableContent(json)); }, []); return ( <div style={{ textAlign: "center" }}> <table> <thead> <tr> <th>User ID</th> <th>Title</th> </tr> </thead> <tbody> {tableContent.slice(0, infiniteScroll).map(content => { return ( <tr key={content.id}> <td style={{ paddingTop: "10px" }}>{content.userId}</td> <td style={{ paddingTop: "10px" }}>{content.title}</td> </tr> ); })} </tbody> </table> </div> ); }; export default App;

上面的代碼將呈現一個假數據列表( userIDtitle ),它們利用無限滾動掛鉤在屏幕上顯示初始數據數量。

結論

我希望你喜歡學習本教程。 你總是可以從下面的參考資料中閱讀更多關於 React Hooks 的信息。

如果您有任何問題,您可以在評論部分留下它們,我很樂意為您一一解答!

本文的支持 repo 可在 Github 上找到。

資源和進一步閱讀

  • “Hooks API 參考”,React.js 文檔
  • “什麼是 React Hooks?”,Robin Wieruch
  • useContext掛鉤如何工作,”戴夫·塞迪亞
  • “React Hooks:如何使用useEffect() ”,Hossein Ahmadi,Medium
  • “編寫你自己的自定義 React Hooks”,Aayush Jaiswal,Medium
  • “易於理解的 React Hook 食譜”,Gabe Ragland,useHooks()