React Hooks 的最佳實踐
已發表: 2022-03-10 React Hooks 是 React 16.8 中的新增功能,它讓您無需編寫class
組件即可使用狀態和其他 React 功能。 換句話說,Hook 是讓你從函數組件中“掛鉤”到 React 狀態和生命週期特性的函數。 (它們在class
組件中不起作用。)
React 提供了一些內置的 Hooks,比如useState
。 您還可以創建自己的 Hooks 以在不同組件之間重用有狀態行為。 下面的示例顯示了一個計數器,其狀態使用useState()
掛鉤進行管理。 每次單擊按鈕時,我們都會使用setCount()
將count
的值更新1
。
此示例呈現一個值為0
的計數器。 當您單擊按鈕時,它會將值增加1
。 組件的初始值是使用useState
定義的。
const [count, setCount] = useState(0)
如您所見,我們將其設置為0
。 然後當我們想要增加值時,我們使用onClick()
方法調用setCount
。
<button onClick={() => setCount(count + 1)}> Click me </button>
在 React Hooks 發布之前,這個例子會使用更多的代碼行,因為我們不得不使用一個class
組件。
React Hooks 的規則
在深入探討最佳實踐之前,我們需要了解 React Hooks 的規則,這也是本文介紹的實踐的一些基本概念。
React Hooks 是 JavaScript 函數,但是在使用它們時需要遵循兩個規則。
- 在頂層調用 Hooks;
- 僅從 React 組件調用 Hooks。
注意:這兩個規則是在 React Hooks 中引入的,而不是 JavaScript 本身的一部分。
讓我們更詳細地了解這些規則。
在頂層調用 Hooks
不要在循環、條件或嵌套函數中調用 Hook。 始終在 React 函數的頂層使用 Hooks。 通過遵循此規則,您可以確保每次渲染組件時都以相同的順序調用 Hook。 這就是允許 React 在多個useState
和useEffect
調用之間正確保留 Hooks 狀態的原因。
讓我們創建一個有兩種狀態的Form
組件:
-
accountName
-
accountDetail
這些狀態將具有默認值,我們將使用useEffect
掛鉤將狀態保存到瀏覽器的本地存儲或文檔的標題。
現在,如果該組件在多次調用useState
和useEffect
之間保持相同,則該組件可能會成功管理其狀態。
function Form() { // 1. Use the accountName state variable const [accountName, setAccountName] = useState('David'); // 2. Use an effect for persisting the form useEffect(function persistForm() { localStorage.setItem('formData', accountName); }); // 3. Use the accountDetail state variable const [accountDetail, setAccountDetail] = useState('Active'); // 4. Use an effect for updating the title useEffect(function updateStatus() { document.title = accountName + ' ' + accountDetail; }); // ... }
如果我們的 Hooks 的順序發生了變化(在循環或條件中調用它們時可能發生這種情況),React 將很難弄清楚如何保存我們組件的狀態。
// ------------ useState('David') // 1. Initialize the accountName state variable with 'David' useEffect(persistForm) // 2. Add an effect for persisting the form useState('Active') // 3. Initialize the accountdetail state variable with 'Active' useEffect(updateStatus) // 4. Add an effect for updating the status // ------------- // Second render // ------------- useState('David') // 1. Read the accountName state variable (argument is ignored) useEffect(persistForm) // 2. Replace the effect for persisting the form useState('Active') // 3. Read the accountDetail state variable (argument is ignored) useEffect(updateStatus) // 4. Replace the effect for updating the status // ...
這就是 React 調用我們的鉤子的順序。 由於順序保持不變,它將能夠保留我們組件的狀態。 但是如果我們在一個條件中加入一個 Hook 調用會發生什麼呢?
// We're breaking the first rule by using a Hook in a condition if (accountName !== '') { useEffect(function persistForm() { localStorage.setItem('formData', accountName); }); }
accountName !== ''
條件在第一次渲染時為true
,所以我們運行這個 Hook。 但是,在下一次渲染時,用戶可能會清除表單,從而使條件為false
。 現在我們在渲染過程中跳過了這個 Hook,Hook 調用的順序變得不同了:
useState('David') // 1. Read the accountName state variable (argument is ignored) // useEffect(persistForm) // This Hook was skipped! useState('Active') // 2 (but was 3). Fail to read the accountDetails state variable useEffect(updateStatus) // 3 (but was 4). Fail to replace the effect
React 不知道第二個useState
Hook 調用返回什麼。 React 期望這個組件中的第二個 Hook 調用對應於persistForm
效果,就像在之前的渲染中一樣——但它不再是了。 從那時起,在我們跳過的Hook
調用之後的每個下一個 Hook 調用也會移動一個 - 導致錯誤。
這就是為什麼必須在我們組件的頂層調用 Hooks。 如果我們想有條件地運行一個效果,我們可以把這個條件放在我們的 Hook 中。
注意:查看 React Hook 文檔以閱讀有關此主題的更多信息。
僅從 React 組件調用 Hooks
不要從常規 JavaScript 函數調用 Hooks。 相反,您可以從 React 函數組件中調用 Hooks。 下面我們來看看 JavaScript 函數和 React 組件的區別:
JavaScript 函數
import { useState } = "react"; function toCelsius(fahrenheit) { const [name, setName] = useState("David"); return (5/9) * (fahrenheit-32); } document.getElementById("demo").innerHTML = toCelsius;
這裡我們從 React 包中導入useState
鉤子,然後聲明我們的函數。 但這是無效的,因為它不是 React 組件。
反應函數
import React, { useState} from "react"; import ReactDOM from "react-dom"; function Account(props) { const [name, setName] = useState("David"); return <p>Hello, {name}! The price is <b>{props.total}</b> and the total amount is <b>{props.amount}</b></p> } ReactDom.render( <Account total={20} amount={5000} />, document.getElementById('root') );
儘管兩者的主體看起來相似,但當我們將 React 導入文件時,後者成為了一個組件。 這就是我們可以在內部使用 JSX 和 React 鉤子之類的東西的原因。
如果你碰巧在沒有導入 React 的情況下導入了你喜歡的鉤子(這使它成為一個常規函數),你將無法使用你導入的 Hook,因為 Hook 只能在 React 組件中訪問。
從自定義 Hooks 調用 Hooks
自定義 Hook 是一個 JavaScript 函數,其名稱以use
開頭,並且可以調用其他 Hook。 例如, useUserName
在調用useState
和useEffect
掛鉤的自定義 Hook 下方使用。 它從 API 獲取數據,遍歷數據,如果它收到的特定用戶名存在於 API 數據中,則調用setIsPresent()
。
export default function useUserName(userName) { const [isPresent, setIsPresent] = useState(false); useEffect(() => { const data = MockedApi.fetchData(); data.then((res) => { res.forEach((e) => { if (e.name === userName) { setIsPresent(true); } }); }); }); return isPresent; }
然後我們可以繼續在應用程序中需要的其他地方重用這個鉤子的功能。 在這樣的地方,除非需要,我們不必再調用useState
或useEffect
了。
通過遵循此規則,您可以確保組件中的所有有狀態邏輯從其源代碼中清晰可見。
ESLint 插件
名為eslint-plugin-react-hooks
的 ESLint 插件強制執行上述規則。 在處理項目時,這在執行規則時非常方便。 我建議您在處理項目時使用此插件,尤其是在與他人合作時。 如果您想嘗試,可以將此插件添加到您的項目中:
// Your ESLint configuration { "plugins": [ // ... "react-hooks" ], "rules": { // ... "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies } }
此插件默認包含在 Create React App 中。 因此,如果您使用 Create-React-App 引導您的 React 應用程序,則不需要添加它。
思考鉤子
在深入探討幾個 Hooks 最佳實踐之前,讓我們簡要了解一下class
組件和功能組件(使用 Hooks)。
在 React 中定義組件的最簡單方法是編寫一個返回 React 元素的 JavaScript 函數:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
Welcome
組件接受props
,它是一個包含數據並返回 React 元素的對象。 然後我們可以在另一個組件中導入並渲染這個組件。
class
組件使用一種稱為封裝的編程方法,這基本上意味著與類組件相關的所有內容都將存在於其中。 生命週期方法( constructors
、 componentDidMount()
、 render
等)為組件提供了可預測的結構。
封裝是OOP (面向對象編程)的基礎之一。 它指的是在對數據進行操作的方法中捆綁數據,並用於隱藏類中結構化數據對象的值或狀態——防止未經授權的方直接訪問它們。
使用 Hooks,組件的組合從生命週期 Hooks 的組合變為最終帶有一些渲染的功能。
功能組件
下面的示例展示瞭如何在功能組件中使用自定義 Hooks(不展示主體是什麼)。 但是,它所做的或可以做的並不受限制。 它可以是實例化狀態變量、使用上下文、為組件訂閱各種副作用——或者如果您使用自定義鉤子,則可以是以上所有!
function { useHook{...}; useHook{...}; useHook{...}; return (
...); }
類組件
class
組件要求您從React.Component
擴展並創建一個返回 React 元素的render
函數。 這需要更多代碼,但也會給您帶來一些好處。
class { constructor(props) {...} componentDidMount() {...} componentWillUnmount() {...} render() {...} }
在 React 中使用函數式組件有一些好處:
- 分離容器組件和展示組件會變得更容易,因為如果您無法訪問組件中的
setState()
,則需要更多地考慮組件的狀態。 - 函數式組件更容易閱讀和測試,因為它們是沒有狀態或生命週期鉤子的純 JavaScript 函數。
- 你最終得到的代碼更少。
- React 團隊提到,在未來的 React 版本中,功能組件的性能可能會有所提升。
這導致了使用 React Hooks 時的第一個最佳實踐。
Hooks 最佳實踐
1. 簡化你的鉤子
保持 React Hooks 簡單將使您能夠有效地控制和操縱組件在其整個生命週期中發生的事情。 盡可能避免編寫自定義 Hooks ; 您可以內聯useState()
或useEffect()
而不是創建自己的鉤子。
如果您發現自己使用了一堆功能相關的自定義 Hook,您可以創建一個自定義 Hook 來充當這些 Hook 的包裝器。 下面我們來看看兩個不同的帶有鉤子的功能組件。
功能組件 v1
function { useHook(...); useHook(...); useHook(...); return( <div>...</div> ); }
功能組件 v2
function { useCustomHook(...); useHook(...); useHook(...); return( <div>...</div> ); }
v2 是一個更好的版本,因為它使鉤子保持簡單,並且所有其他useHook
都相應地內聯。 這使我們能夠創建可以在不同組件之間重用的功能,也使我們能夠更有效地控制和操作我們的組件。 您應該使用 v2,而不是採用我們的組件中到處都是 Hooks 的 v1,這將使調試變得容易並且您的代碼更清晰。
2. 組織和構建你的 Hooks
React Hooks 的優點之一是能夠編寫更少且易於閱讀的代碼。 在某些情況下, useEffect()
和useState()
的數量仍然可能令人困惑。 當您保持組件井井有條時,它將有助於提高可讀性並保持組件流的一致性和可預測性。 如果您的自定義 Hooks 太複雜,您可以隨時將它們分解為子自定義 Hooks。 將組件的邏輯提取到自定義 Hook 中,以使您的代碼可讀。
3. 使用 React Hooks 片段
React Hooks Snippets 是一個 Visual Studio Code 擴展,可讓 React Hooks 更輕鬆、更快速。 目前,支持五個鉤子:
-
useState()
-
useEffect()
-
useContext()
-
useCallback()
-
useMemo()
還添加了其他片段。 我嘗試過使用這些 Hooks,這是我個人在使用它們時使用的最佳實踐之一。
有兩種方法可以將 React Hooks 片段添加到項目中:
- 命令
啟動 VS Code 快速打開( Ctrl + P ),粘貼ext install ALDuncanson.react-hooks-snippets
並按Enter 。 - 擴展市場
啟動“VS Code Extension Marketplace”( Ctrl + Shift + X )並蒐索“React Hook Snippets”。 然後,尋找“Alduncanson”圖標。
我推薦第一個片段。 在此處閱讀有關片段的更多信息或在此處查看最新的 Hooks 片段。
4. 考慮 Hooks 規則
努力在使用 React Hooks 時始終考慮我們之前學到的 Hooks 的兩條規則。
- 只在頂層調用你的 Hooks。 不要在循環、條件或嵌套函數中調用 Hook。
- 始終從 React 函數組件或自定義 Hooks 調用 Hooks,不要從常規 JavaScript 函數調用 Hooks。
名為eslint-plugin-react-hooks
的 ESlint 插件強制執行這兩個規則,如果您願意,可以將此插件添加到您的項目中,正如我們在上面的鉤子規則部分中解釋的那樣。
最佳實踐尚未完全解決,因為 Hooks 仍然相對較新。 因此,在採用任何早期技術時都應謹慎行事。 考慮到這一點,Hooks 是 React 未來的發展方向。
結論
我希望你喜歡這個教程。 我們已經了解了 React Hooks 的兩個最重要的規則以及如何在 Hooks 中有效地思考。 我們研究了功能組件和一些以正確有效的方式編寫 Hooks 的最佳實踐。 儘管規則很簡短,但在編寫規則時讓它們成為您的指南針很重要。 如果你容易忘記它,你可以使用 ESLint 插件來強制它。
我希望你能在下一個 React 項目中吸取這裡學到的所有經驗教訓。 祝你好運!
資源
- “介紹 Hooks”,React 文檔
- “React 中的函數式組件與類組件”,David Joch,Medium
- “ Mixins 被認為是有害的,”Dan Abramov,React 博客
- “React Hooks:最佳實踐和心態轉變”,Bryan Manuele,Medium
- “React Hooks Snippets For VS Code”,Anthony Davis,Visual Code Marketplace