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

scq000發表於2019-03-05

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

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

微信掃描二維碼,關注我的公眾號
最後,歡迎大家關注我的公眾號,一起學習交流。

相關文章