關於React Hooks,你不得不知的事

React Hooks是React 16.8發佈以來最吸引人的特性之一。在開始介紹React Hooks之前,讓咱們先來理解一下什麼是hooks。wikipedia是這樣給hook下定義的:

In computer programming, the term hooking covers a range of techniques used to alter or augment the behaviour of an operating system, of applications, or of other software components by intercepting function calls or messages or events passed between software components. Code that handles such intercepted function calls, events or messages is called a hook.

通俗來說,Hook(鉤子)就是通過攔截軟件和系統內部函數調用和消息通信來增強原有功能的技術。而React Hooks想要增強哪些功能呢?設想你的項目中已經有一大堆組件,這些組件各自都擁有自己的狀態。那麼一旦你想重用某些特定的帶狀態邏輯,就得大幅度重構你的應用。現在有了React Hooks,你只需要抽離這些帶狀態的邏輯代碼,然後它們可以更好地進行重用, 而且獨立出來的代碼也更容易進行測試和管理。有了React Hooks後,你可以在函數式組件中實現之前在帶狀態組件中能做到的任何事,你能夠更靈活地實現你的應用代碼。

接下來,讓我們看看React Hooks在實際項目中到底怎麼使用。

狀態管理

對於業務性組件來說,狀態管理肯定是不可避免的。以前,我們通常寫Class組件來管理業務邏輯,或者使用redux來全局管理狀態。現在我們可以利用React Hooks新提供的State Hook來處理狀態,針對那些已經寫好的Class組件,我們也可以利用State Hook很好地進行重構, 先來看下面這段代碼:

import React from 'react';
class Person extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
          username: "scq000"
      };
  }
  
  render() {
      return (
        <div>
            <p>Welcome to homepage. {state.username}</p>
            <input type="text" placeholder="input a username" onChange={(event) => this.setState({ username: event.target.value)})}></input>
        </div>
      );
  }
}

接下來嘗試將它重構成函數式組件:

import React, {useState} from 'react';

export const Person = () => {
  const [state, setState] = useState({username: "scq000"});
  
  return (
    <div>
        <p>Welcome to homepage. {state.username}</p>
        <input type="text" placeholder="input a username" onChange={(event) => setState({username: event.target.value})}></input>
    </div>
  )
}

如上面這段代碼,我們首先使用useState api 來聲明一個內部狀態,接着聲明一個新的狀態變量state,以及它的setter方法。在這裏,爲了減少重構的工作量我特意選擇了state這個變量名,你也可以單獨將每個獨立的狀態提取出來使用, 比如使用代碼const [username, setUsername] = userState("scq000")。在隨後的組件內部,我們就可以利用這個內部狀態來處理業務邏輯了。由於是函數式組件的寫法,我們也能夠避免很多this綁定,而且這部分邏輯在後續使用過程中也可以抽離出來進行重用。不過這裏有個需要注意的點是:當你使用set方法的時候,舊狀態不會自動merge到新狀態中去,所以你如果提取的狀態是個對象,且有多個屬性時,需要使用如下語法進行狀態的更新:

setState({
    ...state,
    username: event.target.value
});

生命週期管理

我們都知道,組件的生命週期管理是整個react組件的靈魂所在。利用生命週期函數,我們可以控制整個組件的加載、更新和卸載。React Hooks中提供了Effect鉤子,使我們可以在函數式組件中實現這些功能。

爲了便於理解,接下來我將分別演示如何利用Effect鉤子實現原本在Class組件中的各個生命週期方法。下面這段代碼是我們熟悉的Class組件:

import React from 'react';
class Person extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
          username: "scq000"
      };
  }
  
  componentDidMount() {
      console.log('componentDidMount: 組件加載後')
  }
  
  componentWillUnmount() {
      console.log('componentWillUnmount: 組件卸載, 做一些清理工作')
  }
  
  componentDidUpdate(prevProps, prevState) {
      if(prevState.username !== this.state.username) {
          console.log('componentDidUpdate: 更新usernmae')
      }
  }
  
  render() {
      return (
        <div>
            <p>Welcome to homepage. {state.username}</p>
            <input type="text" placeholder="input a username" onChange={(event) => this.setState({ username: event.target.value)})}></input>
        </div>
      );
  }
}

現在我們利用Effect重構一下:

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

export const Person = () => {
  const [state, setState] = useState({username: "scq000"});
  
  useEffect(() => {
      console.log('componentDidMount: 組件加載後')
      return () => {
        console.log('componentWillUnmount: 組件卸載, 做一些清理工作')
      }
  }, []);
  
  useEffect(() => {
      console.log('componentDidUpdate: 更新usernmae')
  }, [state.username]);
  
  return (
    <div>
        <p>Welcome to homepage. {state.username}</p>
        <input type="text" placeholder="input a username" onChange={(event) => setState({username: event.target.value})}></input>
    </div>
  )
}

可以看到,我們利用副作用鉤子很好地實現了原本的生命週期方法。通常我們會利用組件的生命週期函數去獲取數據,操作DOM等,而這些操作都被稱作副作用(side effect)。這些副作用邏輯一般都比較複雜,也是bug頻發的地段。 所以我們可以針對每一段邏輯單獨使用一個Effect鉤子,便於後期維護和調試。

在使用過程中,useEffect方法需要傳入兩個參數,第一個參數是回調函數:這個回調函數會在每次組件渲染後執行,包括初始化渲染以及每次更新時。另一個參數,則是狀態依賴項(數組形式),一旦檢測到依賴項數據變動,組件會更新,並且回調函數都會被再次執行一遍,從而實現componentDidUpdate的功能。如果你傳入一個空依賴,就能實現原來componentDidMount的效果,即只會執行一次。回調函數中如果返回的是閉包,這個返回的閉包函數將會在組件重新渲染前執行,所以你可以在這個位置做一些清理操作,從而實現componentWillUnmount的功能。

還有要注意的是componentWillMountcomponentWillUpdate兩個生命週期方法在新版本的React中已經不推薦使用了,具體原因可以查看這裏

至此,我們就學會如何利用Effect鉤子在函數式組件中實現所有生命週期方法,從而管理我們的應用了。

自定義Hook

重用和抽象一直都是編程中要解決的問題。我們可以自己封裝想要的Hook, 從而實現代碼邏輯的重用和抽象。

封裝自定義hook其實很簡單,就是包裝一個自定義函數,然後根據功能將其狀態和對應的effect邏輯封裝進去:

export const useFetch = (url, dependencies) => {
    const [isLoading, setIsLoading] = useState(false);
    const [response, setResponse] = useState(null);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        setIsLoading(true);
        axios.get(url).then((res) => {
            setIsLoading(false);
            setResponse(res);
        }).catch((err) => {
            setIsLoading(false);
            setError(err);
        });
    }, dependencies)
    
    return [isLoading, response, error];
}

這裏我們簡單地封裝了一個請求數據的Hook,使用方法跟其他Hook類似,直接調用就可以了:

export const Person = () => {
  const [isLoading, response, error] = useFetch("http://example.com/getPersonInfo", []); 
  
  return (
    <div>
        {isLoading ? 
            <div>loading...</div>
            :
            (
                error ? <div> There is an error happened. {error.message} </div>
                      : <div> Welcome, {response.userName} </div>
            )
        }
    </div>
  )
}

注意事項

在使用Hooks的過程中,需要注意的兩點是:

  • 不要在循環,條件或嵌套函數中調用Hook,必須始終在React函數的頂層使用Hook。這是因爲React需要利用調用順序來正確更新相應的狀態,以及調用相應的鉤子函數。一旦在循環或條件分支語句中調用Hook,就容易導致調用順序的不一致性,從而產生難以預料到的後果。

  • 只能在React函數式組件或自定義Hook中使用Hook。

爲了避免我們無意中破壞這些規則,你可以安裝一個eslint插件:

npm install eslint-plugin-react-hooks --save-dev

並在配置文件中使用它:

{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error"
  }
}

這樣,一旦你違法上述這些原則,就會獲得相應的提示。

總結

本文介紹了React Hook的使用方式,並通過幾個簡單的例子演示瞭如何在函數式組件中進行狀態管理和生命週期管理。官方目前提供了很多基礎的Hook,如useContext, useReducer, useMemo等,大家可以酌情在項目中使用。

參考資料

https://reactjs.org/docs/hooks-reference.html

——本文首發於個人公衆號,轉載請註明出處———


最後,歡迎大家關注我的公衆號,一起學習交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章