React躬行記(15)——React Hooks

  Hook(鉤子)是React v16.8新引入的特性,能以鉤子的形式爲函數組件附加類組件的狀態、生命週期等特性。React的類組件有難以拆分、測試,狀態邏輯分散,難以複用等問題,雖然能夠經過渲染屬性(Render Props)和高階組件來提取狀態邏輯,但會造成層層嵌套,而使用Hook後的函數組件就能避免這些問題。html

  Hook本質上是一種特殊的JavaScript函數,名稱以use爲前綴,在使用它時須要遵循兩條規則,以下所列:react

  (1)在循環、條件語句或嵌套函數中調用Hook是不容許的,必須在函數的最頂層調用,確保Hook的調用順序。數組

  (2)只能在React的函數組件或自定義的Hook中調用Hook。瀏覽器

  這兩條規則能夠結合後文的分析慢慢體會,接下來會詳細講解幾個內置的Hook,而且會介紹如何自定義Hook,文中的示例來源於官網緩存

1、State Hook(狀態鉤子)

  先來看一個簡單的類組件,Btn組件會渲染出一個按鈕,每次點擊按鈕,其文本會加一。服務器

import React from "react"; class Btn extends React.Component { constructor() { super(); this.state = { count: 0 }; this.dot = this.dot.bind(this); } dot() { this.setState({ count: this.state.count + 1 }) } render() { return <button onClick={this.dot}>{this.state.count}</button>;
 } }

  而後將Btn組件改爲相同功能的函數形式,以下代碼所示,沒有了構造函數和render()方法,經過useState()爲函數組件附加狀態。異步

import { useState } from "react"; function Btn() { const [count, setCount] = useState(0); return (<button onClick={() => setCount(count + 1)}>{count}</button>);
}

  useState()是一個鉤子函數,它的參數是狀態的初始值,返回一個數組,包含兩個元素:當前狀態和更新狀態的函數。經過數組解構的方式聲明瞭一個名爲count的狀態變量和一個名爲setCount的函數,至關於類組件中的this.state.count和this.setState()。在點擊事件中讀取狀態或調用更新狀態的函數都不須要this。函數

  注意,useState()能夠被屢次調用,React會根據useState()的出現順序保證狀態的獨立性,而且與this.setState()不一樣的是,更新狀態是替換而不是合併。性能

2、Effect Hook(反作用鉤子)

  在React組件中有兩種常見的反作用:無需清除和須要清除,接下來會逐個講解。測試

1)無需清除

  在React更新DOM以後會運行一些無需清除的反作用,例如向服務器請求數據、變動DOM結構、記錄日誌等。在類組件中,這些反作用常在componentDidMount()和componentDidUpdate()生命週期方法中執行。以上一節的Btn組件爲例,在更新計數後,修改頁面標題,以下所示(只列出了核心代碼)。

class Btn extends React.Component { componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } }

  注意,兩個函數中的代碼是重複的,由於不少狀況下,在組件掛載和更新時會執行相同的操做,而React並未提供每次渲染以後可回調的函數。

  接下來用useEffect()鉤子函數實現相同功能,一樣只列出了核心代碼,以下代碼所示。useEffect()使得相同功能的反作用不用再分散到不一樣的生命週期中,即按照用途分離反作用。

import { useEffect } from "react"; function Btn() { useEffect(() => { document.title = `You clicked ${count} times`; }); }

  useEffect()可接收兩個參數,第一個參數是回調函數,叫作Effect,在每次渲染(包括第一次掛載和後續的DOM更新)以後Effect都會被執行,其中每次接收的Effect都是新的,不用擔憂狀態過時的問題;第二個參數是可選的數組(由Effect的依賴項組成),用於控制Effect的執行,而是否執行Effect將取決於數組中的元素是否發生了變化,例如將count變量做爲數組的元素(以下代碼所示),當count的值與從新渲染後的count的值同樣時,React會忽略這個Effect,優化性能。

useEffect(() => { document.title = `You clicked ${count} times`; }, [count]);

  當把一個空數組([])傳給useEffect()時,Effect只會運行一次,即僅在組件掛載和卸載時運行。因爲Effect不依賴state或props中的任意值,所以永遠都不須要重複執行。

  useEffect()至關於componentDidMount()、componentDidUpdate()和componentWillUnmount()三個生命週期方法的組合,但與componentDidMount()或componentDidUpdate()不一樣,使用useEffect()會異步執行反作用,可避免阻塞瀏覽器更新視圖。

2)須要清除

  有些反作用是必須清除的,例如訂閱的外部數據源,將其清除後,可防止內存泄露。在類組件中,一般會在componentDidMount()中設置訂閱,並在componentWillUnmount()中執行清除。

  假設有一個ChatAPI模塊,用於訂閱好友的在線狀態,以下所示(只有關鍵部分),其中componentDidMount()和componentWillUnmount()處理的是關聯的反作用。

class FriendStatus extends React.Component { componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } }

  接下來用函數組件實現相同的功能,一樣只有關鍵部分的代碼。因爲添加和移除訂閱的邏輯有很強的緊密性,所以useEffect()將它們組織在一塊兒。當Effect返回一個函數時,React將在執行清除操做時調用它,以下所示。

function FriendStatus(props) { useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); }

  注意,React會在執行當前Effect以前對上一個Effect進行清除,也就是說,反作用並不只在組件卸載時被執行。

3、自定義Hook

  自定義的Hook用於保存組件中可複用的邏輯,它的參數和返回值都沒有特殊要求,相似於一個普通的函數,但爲了遵循Hook的規則,其名稱必須以use開頭。接下來將以前的FriendStatus組件中訂閱好友在線狀態的邏輯抽離到自定義的useFriendStatus()中,其參數爲friendID,返回值爲好友當前的狀態,以下所示。

function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }

  在FriendStatus組件中調用自定義的Hook,其內部邏輯將變得很是簡潔,以下所示。

function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }

4、其它Hook

  除了上面所講解的兩個內置Hook,React還提供了其它功能的Hook,例如useContext()、useCallback()、useMemo()、useLayoutEffect()等,具體可參考官方的API索引

1)useContext()

  接收一個由React.createContext()建立的Context對象,返回該Context的當前值(即要傳送的數據)。調用了useContext()的組件會在Context值發生變化時從新渲染。

2)useCallback()

  包含兩個參數,第一個是回調函數,第二個是依賴項數組,返回回調函數的記憶版本。當某個依賴項發生改變時,會更新回調函數。注意,依賴項數組不會做爲參數傳給回調函數。

3)useMemo()

  包含回調函數和依賴項數組兩個參數,回調函數的返回值就是useMemo()的返回值,它會被緩存,而且僅在某個依賴項發生改變時才從新計算它。以前的useCallback(fn, deps)至關於useMemo(() => fn, deps)。

4)useLayoutEffect()

  函數簽名與useEffect()相同,但調用時機不一樣,它會在全部的DOM更新以後同步調用Effect,也就是在瀏覽器更新視圖以前調用Effect。

相關文章
相關標籤/搜索