React Hook上車(一)


React Hook 是 v16.8 的新功能,自誕生以來,受到廣泛的好評,在 React 版本更新中具有里程碑的意義。現在都2020年了,再不上車 React Hook 就真的 out 了...

Hook 動機

本着“存在即合理”的原則,我們先來康康 Hook 為我們解決了哪些問題?Hook 有哪些優勢呢?

在編寫 React 組件時,我們更喜歡函數組件,而不是 class 組件。

因為函數組件代碼更少,結構更清晰,不容易產生 bug。但是,函數組件沒辦法使用狀態,只能作為展示組件(就是個花瓶...哎)。

有了 Hook,我們就能在函數組件中使用狀態了**。毫不誇張的說,以后的組件都可以用函數組件 + Hook 來寫。

class 組件的問題:

  • 狀態邏輯難復用:在組件之間復用狀態邏輯很難,一般會用到 render props (渲染屬性)或者 HOC高階組件)。但無論是渲染屬性,還是高階組件,都會在原先的組件外包裹一層父容器(一般都是 div 元素),導致層級冗余
  • 生命周期
    • 多而善變的生命周期:用 React 開發的你一定被那些雜亂多變的生命周期惡心過,聲明周期多就不說了,隨着版本的變動會經常變化,導致升級 React 版本時很煩
    • 生命周期邏輯混亂:在生命周期函數中混雜不相干的邏輯(如:在 componentDidMount 中注冊事件以及其他的邏輯,在 componentWillUnmount 中卸載事件,這樣分散不集中的寫法,很容易寫出 bug )
    • class 組件難以拆分:class 組件中到處都是對狀態的訪問和處理,導致組件難以拆分成更小的組件
  • this 指向問題:class 組件中的 this 指向問題絕對讓人頭疼,需要我們手動小心翼翼地去綁定 this,一不小心就會出現 bug。


Hook 優勢:

  • 優化 class 組件的問題
  • 能在無需修改組件結構的情況下復用狀態邏輯(自定義 Hook )
  • 能將組件中相互關聯的部分拆分成更小的函數(比如設置訂閱或請求數據)
  • 副作用的關注點分離副作用指那些沒有發生在數據向視圖轉換過程中的邏輯,如 ajax 請求、訪問原生dom 元素、本地持久化緩存、綁定/解綁事件、添加訂閱、設置定時器、記錄日志等。以往這些副作用都是寫在類組件生命周期函數中的。而 useEffect 在全部渲染完畢后才會執行,useLayoutEffect 會在瀏覽器 layout 之后,painting 之前執行

Hook 規則

Hook 可以讓你在不編寫 class 組件的情況下使用 state 以及其他的 React 特性。但是,有些規則是我們需要准守的:

  • 只能在函數內部的最外層調用 Hook
    不要在循環,條件或嵌套函數中調用 Hook,
     確保總是在你的 React 函數的最頂層調用他們。遵守這條規則,你就能確保 Hook 在每一次渲染中都按照同樣的順序被調用。這讓 React 能夠在多次的 useState 和 useEffect 調用之間保持 hook 狀態的正確
  • 只能在 React 的函數組件(非 class組件)中調用 Hook
    不要在普通的 JavaScript 函數或 class 組件中調用 Hook。
    你可以:
    • 在 React 的函數組件中調用 Hook
    • 在自定義 Hook 中調用其他 Hook


ok,到此為止,我們已經了解 Hook 有哪些優勢了。

下面,我們開始認識最常用的兩個 Hook API—— useState、useEffect。

這兩個API很好理解,而且很實用,弄懂能處理80%的業務場景。本文不會涉及太復雜的操作,僅僅作為入門。

useState

先來看一段簡單的 Hook 代碼:

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

我們將通過將這段代碼與一個等價的 class 示例進行比較來開始學習 Hook。

等價的 class 組件示例:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

很簡單的一個加1計數器,Hook 寫法比 class 組件是不是簡潔了很多。

下面,我們來分析如何使用 useState Hook...

// 第一步:從 react 庫中引入 useState Hook
import React, { useState } from 'react';

function Example() {
  /* 第二步:通過調用 useState Hook 聲明了一個新的 state 變量。
   * 它返回一對值(數組)解構到我們命名的變量上。
   * 第一個返回的是狀態 count,它存儲的是點擊次數。我們通過傳 0 作為 useState 唯一的參數來將其初始化 0。
   * 第二個返回的值本身就是一個函數。它讓我們可以更新 count 的值,所以我們叫它 setCount。
   */
  const [count, setCount] = useState(0);  // 聲明一個叫 "count" 的 state 變量

  return (
    <div>
      // 第三步:讀取 state,即count
      <p>You clicked {count} times</p>
      // 第四步:更新 state,通過 setCount()
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

通過上面的分析,我們可以看到使用 useState Hook 管理狀態簡直太爽了。不用寫繁瑣的 class 組件,不用擔心 this 指向,代碼是如此的清晰。

如何使用多個 state 變量:
將 state 變量聲明為一對 [something, setSomething] 也很方便,因為如果我們想使用多個 state 變量,它允許我們給不同的 state 變量取不同的名稱:

function ExampleWithManyStates() {
  // 聲明多個 state 變量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '學習 Hook' }]);
  ...

在以上組件中,我們有局部變量 agefruit 和 todos,並且我們可以單獨更新它們:

  function handleOrangeClick() {
    // 和 this.setState({ fruit: 'orange' }) 類似
    setFruit('orange');
  }

不必使用多個 state 變量。State 變量可以很好地存儲對象和數組,因此,你仍然可以將相關數據分為一組。然而,不像 class 中的 this.setState,更新 state 變量總是_替換_它而不是合並它。

注意
你可能想知道:為什么叫 useState 而不叫 createState?
“Create” 可能不是很准確,因為state 只在組件首次渲染的時候被創建。在下一次重新渲染時,useState 返回給我們當前的 state。否則它就不是 “state”了!這也是 Hook 的名字_總是_以 use開頭的一個原因。

useEffect

Effect Hook定義:useEffect 傳入一個 callback 函數

useEffect(effect: React.EffectCallback, deps?: ReadonlyArray<any> | undefined)


Effect Hook作用:處理函數組件中的副作用,如異步操作、延遲操作等,可以替代Class Component的componentDidMountcomponentDidUpdatecomponentWillUnmount等生命周期。

Effect Hook特性:

  • effect(副作用):指那些沒有發生在數據向視圖轉換過程中的邏輯,如 ajax 請求、訪問原生dom 元素、本地持久化緩存、綁定/解綁事件、添加訂閱、設置定時器、記錄日志等。
  • 副作用操作可以分兩類:需要清除的和不需要清除的。
  • useEffect 就是一個 Effect Hook,給函數組件增加了操作副作用的能力。它和 class 組件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不過被合並成了一個 API
  • useEffect 接收一個函數,該函數會在組件渲染到屏幕之后才執行。該函數有要求:要么返回一個能清除副作用的函數,要么就不返回任何內容
  • componentDidMountcomponentDidUpdate 不同,使用 useEffect 調度的 effect 不會阻塞瀏覽器更新屏幕,這讓你的應用看起來響應更快。大多數情況下,effect 不需要同步地執行。在個別情況下(例如測量布局),有單獨的 useLayoutEffect Hook 供你使用,其 API 與 useEffect 相同


useEffect 使用示例:

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

function Example() {
  const [count, setCount] = useState(0);
	
  // 類似 componentDidMount 和 componentDidUpdate
  useEffect(() => {
    // 使用瀏覽器 API 去更新 document 標題
    document.title = `You clicked ${count} times`;
  });
  
  // 類似 componentDidMount
  useEffect(() => {
    // 使用瀏覽器 API 去更新 document 標題
    document.title = `You clicked ${count} times`;
  }, []); // 慎用!監聽空數組,當 callback 使用到 state 或 props 時最好不要用,因為只能獲取初始化的數據
  
  // 返回一個函數用於清除操作
  useEffect(() => {
    document.title = `You clicked ${count} times`;
    
    window.addEventListener('load', loadHandle); // loadHandle 函數定義省略
    
    return () => {
      window.removeEventListener('load', loadHandle); // 執行清理:callback 下一次執行前調用
    };
  }, [count]); // 只有當count的值發生變化時,才會重新執行 callback 

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


useEffect 用法很簡單,但是有兩個地方需要特別注意:

  • deps 參數很重要
    • useEffect 可以接受第二個參數 deps,用於在 re-render 時判斷是否重新執行 callback
    • deps 數組項必須是 immutable 的,比如:不能也不必傳 useRef、dispatch 等
    • deps 的比較是淺比較(參閱源碼),傳入對象、函數是無意義
    • 作為最佳實踐,使用 useEffect 時請盡可能都傳 deps
  • 清除副作用
    • useEffect 傳入的 callback 要么返回一個清除副作用的函數,要么什么都不返回。所以,callback 不能用 async 函數(面試題:如何在 useEffect 中使用 async 函數)
    • useEffect 傳入的 callback 返回一個函數,在下一次執行 callback 前將會執行這個函數,從而達到清理 effect 的效果


useEffect 的用法大概就是這樣的,有一些坑和更復雜操作這里沒有涉及。當然,要深入理解的話需要去啃源碼了,這里不做過多的解釋。

Hook 核心知識點:閉包
當你在使用 Hook 遇到問題時,請先考慮是否由於閉包引起的。這將幫助你快速排查問題。

總結

Hook 讓我們可以在函數組件中使用狀態state,函數組件一統 React 的時代來了,這很棒。

Hook 可以讓我們摒棄那些繁瑣的生命周期、不用考慮 this 的指向、復用邏輯也不用寫HOC了,這很棒。

Hook 還有更多 API 等着我們去探索,同時也支持自定義 Hook。

Hook 發車啦,用過都說好...

參考:
Hook 官方文檔
30分鍾精通React Hooks
React Hooks完全上手指南


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM