React Hook上車

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

Hook 動機

本着「存在即合理」的原則,咱們先來康康 Hook 爲咱們解決了哪些問題?Hook 有哪些優點呢?

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

由於函數組件代碼更少,結構更清晰,不容易產生 bug。可是,函數組件沒辦法使用狀態,只能做爲展現組件(就是個花瓶...哎)。

有了 Hook,咱們就能在函數組件中使用狀態了**。絕不誇張的說,之後的組件均可以用函數組件 + Hook 來寫。

class 組件的問題:html

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


Hook 優點:java

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

Hook 規則

Hook 可讓你在不編寫 class 組件的狀況下使用 state 以及其餘的 React 特性。可是,有些規則是咱們須要準守的:
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%的業務場景。本文不會涉及太複雜的操做,僅僅做爲入門。
git

useState

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

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 組件示例:ajax

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徹底上手指南

相關文章
相關標籤/搜索