1. 程式人生 > 程式設計 >react中hook介紹以及使用教程

react中hook介紹以及使用教程

前言

最近由於公司的專案開發,就學習了在react關於hook的使用,對其有個基本的認識以及如何在專案中去應用hook。在這篇部落格中主要從以下的幾個點進行介紹:

  • hook簡介
  • hook中常用api的使用
  • hook在使用過程中需要去注意的地方
  • hook中怎樣去實現class元件中的宣告周期函式

hook

首先介紹關於hook的含義,以及其所要去面對的一些場景

含義:Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。簡單來說就是可以使用函式元件去使用react中的一些特性

所要解決的問題:

  • 解決元件之間複用狀態邏輯很難得問題,hook能解決的就是在你無需修改之前元件結構的情況下複用狀態邏輯,在不使用hook的情況下,需要使用到一些高階的用法如高階元件、provider、customer等,這種方式對於新手來說不太友好,可能在理解上就比較的困難
  • 對於複雜元件可以去拆分其邏輯,例如在你使用生命週期函式時,不同的生命週期需要在不同的時刻進行,因此在此時對於複雜的元件來說,有的生命週期函式中就存在大量的邏輯,在可讀性上面就大打折扣。當使用hook時,就可以進行元件邏輯的劃分,將相同的邏輯給整合在一起,這樣就大大增加可讀性也在一方面利於維護
  • 不需要對於class元件的理解,當你在最初去學習時,你不得不去理解this這個關鍵字,在當前元件所表示的含義,但是在hook中就不需要。能夠解決你在不使用class元件的情況下去體現react的特性
  • 需要注意的一點就是hook和class元件是不能夠同時使用的,在實際的使用過程中一定要注意,否則就會出現報錯

那麼接下來所要介紹的部分就是如何去使用hook

state hook

對於使用過class元件的同學,相信對於state肯定有很深的印象,對於一些需要用到的全域性變數,在class元件中我們常常採用的方式是this.state = {},但是在hook中我們採用的方式就是使用useState這個hook,然後就可以對這種全域性變數進行引用,在引用時只需要用其變數名即可,這裡就拿官網的例子來舉例:
import React,{ useState } from 'react';

import React,{ useState } from 'react';

function Example() {
 // 宣告一個叫 "count" 的 state 變數
 const [count,setCount] = useState(0);

 return (
 <div>
  <p>You clicked {count} times</p>
  <button onClick={() => setCount(count + 1)}>
  Click me
  </button>
 </div>
 );
}

在上面的這個例子中,我們設定變數方式採用的就是const [count,setCount] = useState(0)這種方式,其中的0就是給count賦初值為0,如果想要給count賦值為一個空物件,那麼只需要const [count,setCount] = useState({}),這樣的方式就行了,那麼這樣你在用count時,此時獲取到的值就為一個空物件。

作用:返回一個state,以及更新state的函式

  1. 函式式更新:新的state需要通過使用先前的state計算得出,將函式傳遞給setState,該函式將接收先前的state,並返回一個更新後的值
  2. 惰性初始state,initialState引數只會在元件的初始渲染中起作用,如果初始化state需要通過一個複雜計算來獲取,則可以傳入一個函式,在函式中計算並返回初始的state,此函式只在初始渲染時被掉用,如下所示:
 const [state,setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
 })

在hook中如何給全域性變數設定值

在class元件中我們給放在state中的變數賦值時,通常採用的方式就是this.setState()這種方式,那麼在hook中所要採用的就是set+變數名這種方式,如

const [count,setCount] = useState(0)

在這裡通過上面我們已經知道的就是count能夠獲取到值,那麼其所對應的setCount(值),這種賦值的方式就是給count變數賦值的,然後通過count就能夠獲取到值。

  • 為什麼要採用這種方式呢?
  • 原因:是因為react中的單向資料來源,這樣的話,能夠保證你的資料來源流向會更加的清楚,這也是react所區別於vue中雙向資料來源繫結的一點

hook中設定多個全域性變數的方式

在hook中,如果我們需要去設定多個類似於上面所說的count,那麼就需要多次使用useState這個hook,當然你也可以設定一個變數在hook的最外部,即在hook這個函式元件的外部。需要注意的是別在整個hook這個函式的全域性設定,因此在hook的執行機制中,在每次載入時,都會從新去載入裡面的變數,因此你是不能夠去獲取到在整個函式內部中使用該變數所改變的值的,能夠獲取到的就只是這個變數的初始值*

useEffect hook

對於useEffect hook,其用途類似於class元件中的生命週期函式,用來處理在一些特定時刻需要去做的事情,這種事情常被叫做副作用。在使用useEffect這個hook時,需要注意的一點就是其不能夠被包含在迴圈,判斷語句中,否則專案會出現報錯,這也是hook的一種設定機制

  • 副作用的劃分:
    • 不需要清除的: 在React更新DOM之後執行一些額外的程式碼:如:傳送網路請求,手動變更DOM,記錄日誌等
    • 需要清除的:當使用外部資料來源時,需要去清除資料,如:定時器,需要我們在結束的時候去清除
  • 渲染時機:在使用useEffect這個hook時,需要注意的就是其渲染的時機,預設情況下會在第一次渲染和每一次更新時去執行。對於如何去控制這個渲染時機,在下面的一個部分會有詳細的介紹
  • 作用:告訴元件在渲染之後執行某些操作
  • useEffect放在元件內部呼叫的原因:可以在effect中直接訪問state中的變數
  • effect返回函式:effect可選的清除機制,每個effect都可以返回一個清除函式
  • 接收內容:一個包含命令式、並且可能有副作用程式碼的函式
  • 清除effect:實現方式,effect函式需要返回一個清除函式
  • effect執行時機:在瀏覽器完成佈局和繪製之後,傳給useEffect的函式會延遲呼叫,因此不應該在函式中執行足賽瀏覽器更新螢幕的操作。
  • 預設條件執行:會在每輪元件渲染完成後執行,因而一旦effect的依賴發生變化,他就會被重新建立。要改變其執行時機,需要給useEffect傳遞第二個引數,只有當第二個引數值發生改變才會重新建立訂閱。如果要使用這個優化的方式,需要確保陣列包含了所有外部作用域中會發發生變化,且在effect中使用的變數。如果只想執行一次effect,可以傳遞一個空陣列作為第二個引數。

對於useEffect的初步認識只需要瞭解上面的即可。接下來就來介紹一個官網的例項,來說明useEffect

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

function Example() {
 const [count,setCount] = useState(0);

 // Similar to componentDidMount and componentDidUpdate:
 useEffect(() => {
  // Update the document title using the browser API
  document.title = `You clicked ${count} times`;
 });

 return (
  <div>
   <p>You clicked {count} times</p>
   <button onClick={() => setCount(count + 1)}>
    Click me
   </button>
  </div>
 );
}

在上面的這段程式碼中,就使用到了useEffect這個hook,在每次count值改變時,就會在頁面中去列印“You clicked ${count} times”這段文字,當然count肯定對應的就是其所對應的值。

useEffect去取代calss中的生命週期函式的方式

react中有狀態元件中,其生命週期函式的各個階段

  1. 在Mounting階段
    1. constructor()
    2. static getDerivedStateFromProps()
    3. render()
    4. componentDidMount()
  2. Updating
    1. static getDerivedStateFormProps
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate()
    5. componentDidUpdate()
  3. UnMouting
    1. componentWillUnmount()

使用hook去代替生命週期函式的方式

這裡就介紹了關於useEffect這個hook的使用,有一些生命週期函式就是通過該hook來實現的,這裡推薦一篇文章https://blog.logrocket.com/guide-to-react-useeffect-hook/,可以參考下。這裡是在參考了一些文章後寫的,具體介紹如下:

constructor: 可以通過useState來初始化state

componentDidMount(),在hook中需要使用下面的這種方式去取代,在useEffect中傳遞第二個引數,該引數為一個空陣列,只會去執行一次,如下面所示

useEffect(() => {

},[])

componentDidUpdate(),有兩種方式去解決

在每次渲染的時候都去呼叫hooks,解決的方式如下面所示

  useEffect(() => {

  })

用一個特殊變數的去觸發hook,如下面所示,count指的就是這個特殊的變數,該hook觸發,只會是count的值改變時

  useEffect(() => {

  },[count])

componentWillUnmount(),用hook來代替,需要去return一個callback(回撥函式),如下面的形式所示

  useEffect(() => {
    return () => {
      //執行的為componentWillUnmount
    }
  },[])

shouldComponentUpdata(),常使用React.memo來代替,在預設情況下,它將對props物件中的複雜物件進行淺層比較,如果想要去控制比較,可以去提供一個自定義的比較函式作為第二個引數。代替hook的方式如下所示

  import React from 'react'
  function areEqual(prevProps,nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,otherwise return false
  */
  }
  const Weather = ({weather}) => {
    return (<div>
        <p>{weather.city}</p>
        <p>{weather.temperature}</p>
        {console.log('Render')}
        </div>
    )
  }

  export default React.memo(Weather,areEqual)

自定義hook

通常在實際的專案開發中少不了使這種自定義的hook,前提是在整個專案中使用了hook的情況下。通常情況下就是去使用useState,useEffect這種系統已經定義好的hook去實現,在呼叫時你就可以直接呼叫當你自定義好的hook來實現你所需要的功能。下面就以自定義useReducer這個hook為例

import React,{ useEffect } from 'react'

function useReducer(reducer,initialState) {
 const [state,setState] = useState(initialState);

 function dispatch(action) {
  const nextState = reducer(state,action);
  setState(nextState);
 }

 return [state,dispatch];
}
export default useReducer

在上面的這個實際例子中,我們封裝了一個自定義的useReducerhook,我們可以呼叫這個hook去完成與reducer一樣的功能了,在呼叫是就需要我們去傳入兩個引數,一個就是reducer,另外一個就是initialState,然後就能夠取得state,以及dispatch方法。注意這裡的返回值使用的是一個數組,這樣的好處就是我們在獲取其返回值時,可以採用陣列結構這種方式來獲取。具體關於陣列的結構可以去看看es6中的部分,就能夠明白了。那麼接下來就是使用這個自定義好的useReducer。使用方式如下

import useReducer form '你封裝useRecuer的元件中'
function Todos() {
const todosReducer = ( state,dispatch) => {
	if(dispatch.type == "") { //type值為什麼時去執行
		const newState == "" //執行一些操作,去更新state
		return newState //返回新的neState
	}
}
 const [todos,dispatch] = useReducer(todosReducer,[]);
 function handleAddClick(text) {
  dispatch({ type: 'add',text });
 }

 return (
	<div></div>
)
}

這裡並沒有把實際的使用情況給寫完,剩餘的可以自己去補充,其使用方式就和redux的使用方式相同。這就是整個自定義hook以及去使用的過程,在實際的開發中可以去體驗體驗。

額外的hook

useReducer,能給那些會出發深更新的元件做效能優化,因為可以向子元件去傳遞dispatch而不是回撥

useReducer這個hook的封裝,整個封裝的方法如下:

//reducer hook封裝
  import { useState } from 'react';
  export default useReducer function(reducer,initialState) {
    const [state,setState] = useState(initialState);
    function dispatch(action){
      const nextState = reducer(state,action);
      return setState(nextState);
    }
    return [state,dispatch]
  }
//實際例子使用
  import useReducer from '';
  const initialState = {count: 0};
  function reducer(state,action) {
  switch (action.type) {
    case 'increment':
    return {count: state.count + 1};
    case 'decrement':
    return {count: state.count - 1};
    default:
    throw new Error();
  }
  }
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'devrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </div>
  )

useReducer的惰性初始化,可以選擇惰性地建立初始化state。因此需要設定一個初始化函式作為useReducer的第三個引數傳入,這樣初始化state將設定為init(initialArg),如下所示,就是一個實際的案例在useReducer中去傳遞第三個引數

function init(initialCount) {
 return {count: initialCount};
}

function reducer(state,action) {
 switch (action.type) {
  case 'increment':
   return {count: state.count + 1};
  case 'decrement':
   return {count: state.count - 1};
  case 'reset':
   return init(action.payload);
  default:
   throw new Error();
 }
}

function Counter({initialCount}) {
 const [state,dispatch] = useReducer(reducer,initialCount,init);
 return (
  <>
   Count: {state.count}
   <button
    onClick={() => dispatch({type: 'reset',payload: initialCount})}>
    Reset
   </button>
   <button onClick={() => dispatch({type: 'decrement'})}>-</button>
   <button onClick={() => dispatch({type: 'increment'})}>+</button>
  </>
 );
}

注意:如果reducer hook的返回值與當前state相同,react將跳過子元件的渲染及副作用的執行

useCallback

返回值:返回一個memoized回撥函式,該回調函式僅在某給依賴項改變時才會更新。

含義:把內聯回撥函式及其依賴項陣列作為引數傳入useCallback,它將返回該回調函式傳遞給經過優化的並使用引用相等性去避免非必要渲染

useCallBack(fn,deps)相當與useMemo(() => fn,deps)

useMemo

使用方式:const memoziedValue = useMemo(() => computeExpensiveValue(a,b),[a,b])

返回值:返回一個memoized值,把建立函式和依賴項陣列作為引數傳入useMemo,僅在某個依賴項改變時才重新計算memoized值。

好處:這種優化有助於避免在每次渲染時都進行高開銷的計算

渲染方式:傳入useMemo的函式會在渲染期間執行,不要在這個函式內部執行與渲染無關的操作,如屬於useEffect中的副作用。如果沒有,那麼新的值將會在每次渲染時被重新渲染

注意:依賴項陣列不會作為引數傳遞給函式,概述來說,就是每一個出現在函式中的引數也應該出現在依賴項的陣列中

useRef

使用方式: const refContainer = useref(initialValue);

返回值:返回一個可ref物件,其.current屬性被初始化為傳入的引數(initialValue)。這返回的的物件將在元件的整個生命週期中持續

含義: useRef就像是一個盒子可以將.current中得可變屬性給儲存起來

ref與useRef的區別在於,後者是建立的了一個普通的js物件,useRef和自建一個{current: …。}物件的唯一區別是,useRef會在每次渲染時,返回同一個ref物件

useImperativeHandle

作用:可以在使用ref時自定義暴露給賦元件的例項值,使用的形式如下:

useImperativeHandle(ref,createHandle,[deps])

useLayoutEffect

更新時機:在瀏覽器執行下一次繪製前去執行

與useEffect相同,會在所有的DOM變更之後同步呼叫effect

useDebugValue

作用:在react devTools中常被用於去當作展示標籤,作為客戶端的鉤子

hooks中的效能優化

在hook中,其效能優化的點很多,這個可以在一些https://react.docschina.org/docs/hooks-faq.html#performance-optimizations去學習,下面是我看的一部分。

如何在更新時去跳過effect,可以採用條件式方式,即在useEffect中去傳遞第二個引數

由於某些原因,無法將一個函式移動到effect內部時,可採用下面方式

  1. 嘗試將函式移動到當前元件的外部
  2. 如果所呼叫對策方法是一個純計算等,此時可以在effect外面去寫這個函式
  3. 如果要增加一個函式去依賴項,那麼要明確使用useCallback外部的hook,如下面的例子所示
function ProductPage({ productId }) {
// Wrap with useCallback to avoid change on every render
const fetchProduct = useCallback(() => {
  // ... Does something with productId ...
},[productId]); // All useCallback dependencies are specified

return <ProductDetails fetchProduct={fetchProduct} />;
}

function ProductDetails({ fetchProduct }) {
useEffect(() => {
  fetchProduct();
},[fetchProduct]); // All useEffect dependencies are specified
// ...
}

實現shouldComponentUpdate的方式

  const Button = React.memo((props) => {
  // your component
  });

如上面所示,這種實現方式並不是使用了hooks,它相當於純元件,但是僅僅能夠比較的是props。可以去增加第二個引數,採用一種函式的方式去拿到新老的props,如果結果返回true,就跳過更新階段

記住計算結果的方式

使用useMemo這個hook去記住之前的計算結果,從而在多個渲染之中快取計算

const memoizedValue = useMemo(() => computeExpensiveValue(a,b]);

上面的程式碼會呼叫computeExpensiveValue(a,b)這個函式,但是它們依賴的a,b沒有改變,那麼useMemo在直接去返回上一次結果的值

結語

對於hook的學習大概就如上面所說,對於hook其中的內容還很多所以對於hook的學習最好是去官網看看,連結如下https://react.docschina.org/docs/hooks-intro.html在官網中介紹的更加詳細,這裡的中文文件和英文文件內容都一樣,不過對於英文好的同學建議看看英文版本。

到此這篇關於react中hook的介紹以及使用的文章就介紹到這了,更多相關react中hook介紹及使用內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!