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

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

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.html

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

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

狀態管理

對於業務性組件來講,狀態管理確定是不可避免的。之前,咱們一般寫Class組件來管理業務邏輯,或者使用redux來全局管理狀態。如今咱們能夠利用React Hooks新提供的State Hook來處理狀態,針對那些已經寫好的Class組件,咱們也能夠利用State Hook很好地進行重構, 先來看下面這段代碼:ios

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>
      );
  }
}

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

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到新狀態中去,因此你若是提取的狀態是個對象,且有多個屬性時,須要使用以下語法進行狀態的更新:shell

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

生命週期管理

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

爲了便於理解,接下來我將分別演示如何利用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重構一下:json

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

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

 

微信掃描二維碼,關注個人公衆號
最後,歡迎你們關注個人公衆號,一塊兒學習交流。
相關文章
相關標籤/搜索