理解 React Hooks


歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐干貨哦~

本文由志航發表於雲+社區專欄

TL;DR

一句話總結 React Hooks 就是在 react 函數組件中,也可以使用類組件(classes components)的 state 和 組件生命周期,而不需要在 mixin、 函數組件、HOC組件和 render props 之間來回切換,使得函數組件的功能更加實在,更加方便我們在業務中實現業務邏輯代碼的分離和組件的復用。

本文將從以下幾個方面介紹 hooks

Hooks 在解決什么問題 Hooks 的 api 介紹 和如何使用 hooks Hooks 是怎么實現的

💡Hooks 在解決什么問題

React 一直在解決一個問題,如何實現分離業務邏輯代碼,實現組件內部相關業務邏輯的復用。

一般情況下,我們都是通過組件和自上而下傳遞的數據流將我們頁面上的大型UI組織成為獨立的小型UI,實現組件的重用。但是我們經常遇到很難侵入一個復雜的組件中實現重用,因為組件的邏輯是有狀態的,無法提取到函數組件當中。這在處理動畫和表單的時候,尤其常見,當我們在組件中連接外部的數據源,然后希望在組件中執行更多其他的操作的時候,我們就會把組件搞得特別糟糕:

  • 難以重用和共享組件中的與狀態相關的邏輯,造成產生很多巨大的組件
  • 邏輯復雜的組件難以開發與維護,當我們的組件需要處理多個互不相關的 localstate 時,每個生命周期函數中可能會包含着各種互不相關的邏輯在里面。
  • 復雜的模式,如渲染道具和高階組件。
  • 由於業務變動,函數組件不得不改為類組件。

這時候,Hooks就派上用場了。 Hooks 允許我們將組件內部的邏輯,組織成為一個可復用的隔離模塊。

借用 @Sunil Pai 的兩張圖來說明這個問題:

imgimage.png

imgimage.png

從 React Hooks 中體驗出來的是 React 的哲學在組件內部的實現,以前我們只在組件和組件直接體現 React 的哲學,就是清晰明確的數據流和組成形式。既可以復用組件內的邏輯,也不會出現 HOC 帶來的層層嵌套,更加不會出現 Mixin 的弊端

💡Hooks 的 api 介紹 和如何使用 hooks

@dan_abramov 在會議上給我們介紹了 hooks 的三個關鍵的api,分別是 State HooksEffect HooksCustom Hooks(自定義hooks)

📌state Hooks (useState)

useState 這個方法可以為我們的函數組件帶來 local state,它接收一個用於初始 state 的值,返回一對變量。 讓函數組件擁有自己的組件。

首先如果我們需要用 classes component 實現一個點擊按鈕 +1 組件應該怎么寫呢?

import React from 'react';

class Example extends React.Component {
    constructor(props) {
        super(props);
        this.state = {count: 0};
        this.clickBtn = this.clickBtn.bind(this);
    }
    clickBtn = () => {
        this.setState({
            count: this.state.count + 1;
        });
    }
    return (
        <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={this.clickBtn}>
                Click me
            </button>
        </div>
    );
}

那使用 useState 是怎么樣的呢? 可以看見非常清晰明了。

// 一個簡單的點擊計數
import { useState } from 'react';

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

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

📌Effect Hooks (useEffect)

Effect Hooks 用於處理一些帶有副作用的操作,下面通過監聽窗口寬度的變化代碼為例,說明 effect hooks 的使用fangfa

import { useState } from 'react';

function windowWidth() {
    const [width, setWithd] = useState(window.innerWidth);
    useEffect(() => {
        const handleResize = ()=>{
            setWidth(window.innerWidth);
        }
        window.addEventListener('resize', handleResize);
    });
    return (
        <p> window width is {width}</p>
    )
}

useEffect 可以傳入第二個操作來避免性能的損耗,如果第二個參數數組中的成員變量沒有變化則會跳過此次改變。如何傳入一個空數組 ,那么該 effect 只會在組件 mount 和 unmount 時期執行。

import { useState } from 'react';

function windowWidth() {
    const [width, setWithd] = useState(window.innerWidth);
    useEffect(() => {
    const handleResize = ()=>{
        setWidth(window.innerWidth);
    }
    window.addEventListener('resize', handleResize);
    }, [width]); // width 沒有變化則不處理
    return (
        <p> window width is {width}</p>
    )
}

useEffect 中還可以通過讓函數返回一個函數來進行一些取消兼容之類的清理操作,比如取消訂閱等

import { useState } from 'react';

function windowWidth() {
  const [width, setWithd] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = ()=>{
        setWidth(window.innerWidth);
    }
    window.addEventListener('resize', handleResize);

    return () => {
        // 取消監聽窗口的寬度變化
        window.removeEventListener('resize');
    }
  });
  return (
      <p> window width is {width}</p>
  )
}

如上所示,內置的 React Hooks 如 useState 和 useEffect 充當基本構建塊。 我們可以直接在組件中使用它們,或者我們可以將它們組合到自定義Hook中,例如useWindowWidth。使用自定義Hooks感覺就像使用React的內置API一樣。

📌Custom Hooks 自定義組件

接着上面的監聽窗口大小的代碼,我們接着講自定義 hooks, 證明 react hooks 是怎么使到組件內的邏輯可復用的。

Talk is cheap, show me the code.

// 一個顯示目前窗口大小的組件
function responsiveComponent(){
   // custom hooks
   const width = useWindowWidth(); 
   return (
       <p>當前窗口的寬度是 {width}</p>
   )
}

上面的代碼只有幾行,非常清晰明了說明了他的作用就是監聽當前窗口的變化,這就是Hooks的目標 - 使組件真正具有聲明性,即使它們包含狀態和副作用。

我們來看看如何實現這個自定義Hook。我們使用React本地狀態來保持當前窗口寬度,並在窗口調整大小時使用副作用來設置該狀態

import { useState, useEffect} from 'react';
// custom hooks to listen window width change
function useWindowWidth(){
    const [width, setWidth] = useState(window.innerWidth);

    useEffect(() => {
        const handleResize = ()=>{
            setWidth(window.innerWidth);
        }
        window.addEventListener('resize', handleResize);
    }, [width]); // width 沒有變化則不處理

    return width;
}

[在線編輯例子]

⚡ React Hooks 的規則

Hooks 是JavaScript函數,但它們強加了兩個額外的規則:

  • 只能在頂層調用Hooks。不要在循環,條件或嵌套函數中調用Hook。
  • 僅從React功能組件調用Hooks。不要從常規JavaScript函數中調用Hook。 (還有另一個地方可以調用Hooks——你自己的定制Hooks。)

🔌 其他 Hooks

這里有一些不常用的內置Hook。例如,useContext允許您訂閱React上下文而不引入嵌套:

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}

發現一個很有趣的倉庫,react-use, 包含了很多很有趣的自定義hooks

👀hooks 是如何工作的

以下內容翻譯自 react-hooks-not-magic-just-arrays.

react hooks 其實只是一個數組,並不是奇妙的魔法。

如何實現 useState() 方法

讓我們在這里通過一個例子來演示狀態 hooks 的實現如何工作。

首先讓我們從一個組件開始:

function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi");
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}

hooks API背后的想法是你可以使用一個setter函數作為hook函數中的第二個數組項返回,而setter將控制由hook管理的狀態。

那么React與此有什么關系呢?

讓我們了解這在React內部如何工作。 以下內容可在執行上下文中用於呈現特定組件。 這意味着此處存儲的數據位於正在渲染的組件之外。 此狀態不與其他組件共享,但它保留在可以隨后渲染特定組件的范圍內。

1)初始化

創建兩個空數組:settersstate

將光標設置為 0

imgimage.png

初始化:兩個空數組,Cursor為0

2) 首次渲染

首次運行組件功能。

每次useState()調用,當在第一次運行時,將setter函數(綁定到光標位置)推送到setter數組,然后將某個狀態推送到state數組。

imgimage.png

第一次渲染:作為光標增量寫入數組的項目。

3) 后續渲染

每個后續渲染都會重置光標,並且只從每個數組中讀取這些值。

imgimage.png

后續渲染:從數組中讀取的項目為光標增量

4) 事件處理

每個setter都有一個對它的光標位置的引用,因此通過觸發對任何setter的調用,它將改變狀態數組中該位置的狀態值。

imgimage.png

Setters“記住”他們的索引並根據它設置內存。

通過偽代碼實現 useState 功能

這是一個演示實現的代碼示例:

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;

function createSetter(cursor) {
  return function setterWithCursor(newVal) {
    state[cursor] = newVal;
  };
}

// useState的偽代碼實現
export function useState(initVal) {
  if (firstRun) {
    state.push(initVal);
    setters.push(createSetter(cursor));
    firstRun = false;
  }

  const setter = setters[cursor];
  const value = state[cursor];

  cursor++;
  return [value, setter];
}

// 模擬使用useState
function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
  const [lastName, setLastName] = useState("Yardley"); // cursor: 1

  return (
    <div>
      <Button onClick={() => setFirstName("Richard")}>Richard</Button>
      <Button onClick={() => setFirstName("Fred")}>Fred</Button>
    </div>
  );
}

// 模擬Reacts渲染周期
function MyComponent() {
  cursor = 0; //  重置光標的位置
  return <RenderFunctionComponent />; // render
}

console.log(state); // Pre-render: []
MyComponent();
console.log(state); // 首次渲染: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // 后續渲染: ['Rudi', 'Yardley']

// 點擊'Fred' 按鈕 

console.log(state); // 點擊后: ['Fred', 'Yardley']

總結

Hooks 還處於早期階段,但是給我們復用組件的邏輯提供了一個很好的思路,大家可以在 react-16.7.0-alpha.0 中體驗。

相關閱讀
【每日課程推薦】機器學習實戰!快速入門在線廣告業務及CTR相應知識

此文已由作者授權騰訊雲+社區發布,更多原文請點擊

搜索關注公眾號「雲加社區」,第一時間獲取技術干貨,關注后回復1024 送你一份技術課程大禮包!

海量技術實踐經驗,盡在雲加社區


免責聲明!

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



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