理解 React Hooks

歡迎你們前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~html

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

TL;DR

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

本文將從如下幾個方面介紹 hooksgithub

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

💡Hooks 在解決什麼問題

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

通常狀況下,咱們都是經過組件和自上而下傳遞的數據流將咱們頁面上的大型UI組織成爲獨立的小型UI,實現組件的重用。可是咱們常常遇到很難侵入一個複雜的組件中實現重用,由於組件的邏輯是有狀態的,沒法提取到函數組件當中。這在處理動畫和表單的時候,尤爲常見,當咱們在組件中鏈接外部的數據源,而後但願在組件中執行更多其餘的操做的時候,咱們就會把組件搞得特別糟糕:機器學習

  • 難以重用和共享組件中的與狀態相關的邏輯,形成產生不少巨大的組件
  • 邏輯複雜的組件難以開發與維護,當咱們的組件須要處理多個互不相關的 localstate 時,每一個生命週期函數中可能會包含着各類互不相關的邏輯在裏面。
  • 複雜的模式,如渲染道具和高階組件。
  • 因爲業務變更,函數組件不得不改成類組件。

這時候,Hooks就派上用場了。 Hooks 容許咱們將組件內部的邏輯,組織成爲一個可複用的隔離模塊。ide

借用 @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 送你一份技術課程大禮包!

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

相關文章
相關標籤/搜索