【譯】什麼是React Hooks

原文:What are React Hooks?
作者:Robin Wieruch
譯者:博軒

react

React Hooks2018年10月的React Conf 中引入,作爲在 React 函數組件中使用狀態和生命週期的一種方法。雖然函數組件之前被稱爲 無狀態組件(FSC) ,但是 React Hooks 的出現,使得這些函數組件可以使用狀態。因此,現在許多人將它們視爲功能組件。

在這篇文章中,我會解釋這些 Hooks 背後的動機,React 會發生什麼改變,爲什麼我們不應該恐慌,以及如何在函數式組件中使用常見的 React Hooks,比如 state生命週期

譯註:長文預警 🤓

爲什麼使用 React Hooks

React Hooks 是由 React 團隊發明的,用於在函數組件中引入狀態管理生命週期方法。如果我們希望一個 React 函數組件可以擁有 狀態管理生命週期方法,我不需要再去將一個 React 函數組件重構成一個 React 類組件。React Hooks 讓我們可以僅使用函數組件就可以完成一個 React 應用

不必要的組件重構

以前,只有 React 類組件可以使用 本地狀態管理生命週期方法 。後者對於在 React 類組件中引入副作用(如監聽DOM事件,異步加載數據)至關重要。

import React from 'react';

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

export default Counter;

只有在您不需要狀態生命週期方法時,纔會考慮使用 React 無狀態組件(FSC)。而且因爲 React 函數組件更輕便(更優雅),人們已經使用了大量的函數組件。每次,當這些 函數組件 需要狀態生命週期方法時,都需要將 React 函數組件升級成 React 類組件(反之亦然)。

import React, { useState } from 'react';

// how to use the state hook in a React function component
function Counter() {
  const [count, setCount] = useState(0);

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

export default Counter;

有了 Hooks ,就沒有必要進行這種重構。狀態生命週期方法React 函數組件中變得可用。這也讓 無狀態組件功能組件 的重塑變得切實可行。

副作用邏輯

React 類組件中,副作用主要在生命週期方法中引入 (例如 componentDidMountcomponentDidUpdatecomponentWillUnmount) 。副作用可能是React 中獲取數據,或 Browser API 進行交互 。通常這些副作用會伴隨着設置和清理階段。例如,如果您忘記了去刪除監聽器,就會遇到一些 React 性能問題

// side-effects in a React class component
class MyComponent extends Component {
  // setup phase
  componentDidMount() {
    // add listener for feature 1
    // add listener for feature 2
  }

  // clean up phase
  componentWillUnmount() {
    // remove listener for feature 1
    // remove listener for feature 2
  }

  ...
}

// side-effects in React function component with React Hooks
function MyComponent() {
  useEffect(() => {
    // add listener for feature 1 (setup)
    // return function to remove listener for feature 1 (clean up)
  });

  useEffect(() => {
    // add listener for feature 2 (setup)
    // return function to remove listener for feature 2 (clean up)
  });

  ...
}

現在,如果您在 React 類組件的生命週期方法中引入多個副作用,所有副作用將按生命週期方法分組,而不會按副作用的功能來分組。這就是 React Hooks 帶來的改變,將每個副作用通過一個鉤子函數進行封裝,而每個鉤子函數都會處理各自的副作用,並提供這個副作用的設置和清理。稍後您將在本篇教程中看到如何在 React Hook 中添加和刪除監聽器來實現這一點。

React 抽象地獄

React 中可以通過 高階組件Render Props 來實現抽象和可重用性。還有 React Context及其Provider和消費者組件 所提供的另一個層次的抽象。React 中的所有這些高級模式都使用了所謂的包裝組件。對於正在創建更大的 React 應用程序的開發人員來說,以下組件的實現應該並不陌生。

import { compose } from 'recompose';
import { withRouter } from 'react-router-dom';

function App({ history, state, dispatch }) {
  return (
    <ThemeContext.Consumer>
      {theme =>
        <Content theme={theme}>
          ...
        </Content>
      }
    </ThemeContext.Consumer>
  );
}

export default compose(
  withRouter,
  withReducer(reducer, initialState)
)(App);

Sophie Alpert 將之稱爲 React 中的 “包裝地獄” 。您不僅可以在組件實現時看到它,還可以在瀏覽器中檢查組件時看到它。由於使用 Render Props 組件(包括 React's Context 提供的消費者組件)和高階組件,很容易產生了幾十個包裝組件。由於所有抽象邏輯都被其他 React 組件所隱藏,我們的應用變成了一棵沒有可讀性的組件樹🌲。而那些可見的組件也很難在瀏覽器的 DOM 中進行跟蹤。那麼,如果這些抽象的邏輯在函數組件中被一些封裝好的副作用所代替,這些額外的組件將不再需要。

function App() {
  const theme = useTheme();
  const history = useRouter();
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <Content theme={theme}>
      ...
    </Content>
  );
}

export default App;

這就是 React Hooks 的魅力所在。所有副作用都可以直接在組件中使用,業務組件爲了使用這些副作用,也不需要再引入其他組件所爲容器。容器組件消失,邏輯只存在於作爲函數的 React Hooks 中。

Andrew Clark 已經在他的鵝妹子嚶高階組件庫:recompose 中發表了一篇關於贊成 React Hooks聲明

混亂的 JavaScript

JavaScript 很好地混合了兩個世界:面向對象編程(OOP)函數式編程(FP)React 將許多的開發者帶到了這兩個世界。一方面,React (和 Redux )向人們介紹了 函數編程(FP) 的功能組合,一些函數式編程的通用編程概念(例如高階函數,JavaScript 內置方法,如 mapreducefilter ),以及一些函數式編程的術語,如 不變性副作用React 本身沒有真正介紹這些東西,因爲它們是語言或編程範式的特性,但它們在 React 中被大量使用,這使得每個 React 開發人員都會自動成爲一個更好的 JavaScript 開發人員

另一方面,在 React 中,可以使用 JavaScript 類作爲定義 React 組件的一種方法。類只是聲明,而組件的實際用法是它的實例。它會創建一個類實例,而類實例的 this 對象可以用於調用類的方法(例如 setStateforceUpdate ,以及其他自定義類方法)。但是,對於不是來自 OOP 背景的 React 初學者來說,學習曲線會更陡峭。這就是爲什麼類綁定,this 對象和繼承可能令人困惑的原因。對於初學者來說,這一直是 React 最令人困惑的事情,我的 React書 中也只有幾章在講這方面的知識。

// I THOUGHT WE ARE USING A CLASS. WHY IS IT EXTENDING FROM SOMETHING?
class Counter extends Component {
  // WAIT ... THIS WORKS???
  state = { value: 0 };

  // I THOUGH IT'S THIS WAY, BUT WHY DO I NEED PROPS HERE?
  // constructor(props) {
  //  SUPER???
  //  super(props);
  //
  //  this.state = {
  //    value: 0,
  //  };
  // }

  // WHY DO I HAVE TO USE AN ARROW FUNCTION???
  onIncrement = () => {
    this.setState(state => ({
      value: state.value + 1
    }));
  };

  // SHOULDN'T IT BE this.onDecrement = this.onDecrement.bind(this); in the constructor???
  // WHAT'S this.onDecrement = this.onDecrement.bind(this); DOING ANYWAY?
  onDecrement = () => {
    this.setState(state => ({
      value: state.value - 1
    }));
  };

  render() {
    return (
      <div>
        {this.state.value}

        {/* WHY IS EVERYTHING AVAILABLE ON "THIS"??? */}
        <button onClick={this.onIncrement}>+</button>
        <button onClick={this.onDecrement}>-</button>
      </div>
    )
  }
}

現在,有許多人在爭論 React 不應該移除 JavaScript classes, 儘管他們不理解這些類的概念。畢竟,這些概念都來自於語言自身。但是,引入 Hooks API 的假設之一就是讓初學者在第一次編寫 React 應用時可以不用使用類組件,從而提供更加平滑的學習曲線。

React Hooks 會發生什麼變化?

每次引入新功能時,人們都會關注它。一些人對這一變化感到欣喜若狂,而另外一些人對這一變化感到擔憂。我聽說 React Hooks 最常見的問題是:

  • 一切都變了!莫名的感到恐慌......
  • ReactAngular 一樣變得臃腫!
  • Hooks 沒有用,classes 很好
  • 這是魔法!

讓我在這裏解決這些問題:

一切都在變化?

React Hooks 將改變我們將來編寫 React 應用程序的方式。但是,目前沒有任何變化。您仍然可以使用本地狀態和生命週期方法編寫類組件,並部署高級模式,例如高階組件或 Render Props 組件。沒有人會把這些知識從你身邊帶走。瞭解我如何將所有開源項目從舊版本升級到 React 16.6 。這些項目都沒有問題。他們正在使用 HOCRender Props ,甚至是較爲古老的 context API(如果我錯了,請糾正我)。這些年來我所學到的一切仍然有效。React 團隊確保 React 保持向後兼容。它與 React 16.7 保持相同。

react

ReactAngular 一樣變得臃腫

React 作爲一個第三方庫,其 API 總是會給人一種短小精幹的感覺 。現在是這樣,將來也是如此。但是,考慮到幾年前基於組件所構建的應用,在升級的時候不被其他更加先進的庫所取代, React 引入了有利於舊 API 的變更。如果 React 是9012年發佈的,也許他的功能只會保留函數組件和 Hooks。但 React 幾年前就已經發布,它在發佈新的特性的同時,還需要兼容之前的版本。可能會在幾年棄用類組件和其生命週期方法,轉而使用 React 函數組件和 Hooks,但目前,React 團隊仍將 React 類組件保留在了他們的工具庫中。畢竟, React 團隊希望利用新的特性 Hooks 來陪伴 React 贏得一場馬拉松,而不是一場短跑。顯然,React HooksReact 添加了另一個 API,但它有利於在未來簡化 React 的新 API。我喜歡這種轉變,而不是擁有 "React 2",讓一切都不同。

譯註:這是在吐槽 Angular 嘛?😂

Hooks 沒有用,classes 很好 ?

想象一下,你將從零開始學習 React ,你會被介紹給 React with Hooks 。也許 create-react-app 不會以 React 類組件開始,而是使用 React 函數組件。您需要了解組件的所有內容都是 React Hooks 。它們可以管理狀態和副作用,因此您只需要知道 state hookeffect hook。這是 React 類組件之前爲包含的一切。React 初學者學習 React 會更簡單,而不需要 JavaScript 類(繼承,thisbindingssuper,...)帶來的所有其他開銷。想象一下 React Hooks 作爲一種編寫 React 組件的新方法 - 這是一種全新的思維方式。我自己是一個持懷疑態度的人,但是一旦我用 React Hooks 寫了幾個更簡單的場景,我確信這是最簡單的 React 寫作方式,同樣也學習 React 最簡單的方式。作爲一個做了很多 React workshops 的人,我認爲它消除了那些令 React 初學者所沮喪的 classes

這是魔法?

衆所周知,React 是用 JavaScript 來實現的。當有人問我:“我爲什麼要學習 React ?”時,最好的解釋就是:編寫 React 應用程序會讓你成爲一個更好的 JavaScript 開發人員 。無論未來是否會使用新的庫,每個人都可以在使用 React 的過程中,磨練他們的 JavaScript 技能和一些通用的編程技巧。這是 Redux 經常做的事情,同樣也是 React 做的事情:流行,但是沒有魔力,它只是普通的 JavaScript 。現在 React Hooks 出現了,在以前經常使用的純函數組件中引入了一些有狀態的東西,同時還引入了一些不容易讓人接受的規則,這使得很多人都不明白底層發生了什麼。但是這樣考慮一下:在React 中的一個函數組件不僅僅是一個函數。您仍然必須將 React 作爲庫導入到源代碼文件中。它對你的函數做了一些事情,使得函數在 React 的世界中變成了函數組件。此函數組件的實現始終是隱式的。如何在將 React Hooks 引入之前使用函數實現的函數組件呢?人們也接受了它,即使它有點像是一個魔法。現在,唯一改變的東西(也許它之前已經是這樣)是這些函數組件帶有一個額外的隱藏對象,可以跟蹤的 Hooks。引用 [Dan Abramov
](https://twitter.com/dan_abramov)他的關於 Hooks 文章的話:“也許你想知道 React 在哪裏爲 Hooks 保持狀態。答案是它保存在 React 爲類保持狀態的完全相同的位置。無論你如何定義你的組件,React 都會在內部維護一個更新隊列”

最後,試一下這種思考方式

基於組件的解決方案(如 AngularVueReact)正在推動每個版本的 Web 開發的界限。它們建立在二十多年前發明的技術之上。他們的出現是爲了讓2018年的開發者更加輕鬆,而不是1998。他們瘋狂地優化自身以滿足現在和現在的需求。我們正在使用組件而不是 HTML模板 構建 Web 應用程序。雖然還沒有實現,但我想象一個未來,我們坐在一起,爲瀏覽器發明一個基於組件的標準。AngularVueReact 只是這一運動的先鋒。

在下文中,我想通過示例深入介紹一些受歡迎的 React Hooks ,以幫助您加速瞭解這一變化。所有示例都可以在此GitHub存儲庫中找到。

React useState Hook

上文中,您已經在一個典型的計數器示例的代碼片段中看到過 useState Hook。它用於管理函數組件中的本地狀態。讓我們在一個更詳細的例子中使用 Hook,我們將管理一系列項目。在我的另一篇文章中,您可以瞭解有關在React中將數組作爲狀態進行管理的更多信息,但這次我們使用 React Hook 進行操作。讓我們開始吧:

import React, { useState } from 'react';

const INITIAL_LIST = [
  {
    id: '0',
    title: 'React with RxJS for State Management Tutorial',
    url:
      'https://www.robinwieruch.de/react-rxjs-state-management-tutorial/',
  },
  {
    id: '1',
    title: 'A complete React with Apollo and GraphQL Tutorial',
    url: 'https://www.robinwieruch.de/react-graphql-apollo-tutorial',
  },
];

function App() {
  const [list, setList] = useState(INITIAL_LIST);

  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

useState Hook 接受一個初始狀態作爲參數,並通過使用數組解構返回兩個可以命名的變量,您可以爲它們取需要的名字。第一個變量是實際狀態,而第二個變量是一個可以設置並返回新狀態的函數。

這個示例的下一步是從列表中刪除子元素。爲了實現它,我們爲列表中的每個子元素都設置了一個可單擊按鈕。單擊處理程序會使用一個內聯函數實現,因爲稍後會在內聯函數中使用 list 以及 setList 。 因此,您不需要將這些變量傳遞給處理程序,因爲它們已經可以從組件的外部作用域中獲得。

function App() {
  const [list, setList] = useState(INITIAL_LIST);

  function onRemoveItem() {
    // remove item from "list"
    // set the new list in state with "setList"
  }

  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
          <button type="button" onClick={onRemoveItem}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
}

因爲一些原因,我們需要從列表中刪除的子元素。使用高階函數,我們可以將子元素的標識符傳遞給處理函數。否則,我們將無法從列表中刪除的這些子元素。

function App() {
  const [list, setList] = useState(INITIAL_LIST);

  function onRemoveItem(id) {
    // remove item from "list"
    // set the new list in state with "setList"
  }

  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
          <button type="button" onClick={() => onRemoveItem(item.id)}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
}

最後,使用數組的內置方法過濾列表,刪除包含標識符的子元素。它返回一個新列表,用於設置列表的新狀態。

function App() {
  const [list, setList] = useState(INITIAL_LIST);

  function onRemoveItem(id) {
    const newList = list.filter(item => item.id !== id);
    setList(newList);
  }

  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
          <button type="button" onClick={() => onRemoveItem(item.id)}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
}

此時應該已經完成了。您可以根據傳遞給處理程序的標識符從列表中刪除子元素。然後,處理函數過濾列表並使用 setList 函數設置列表的新狀態。

useState Hook 爲您提供了在函數組件中管理狀態所需的一切:初始狀態,最新狀態和狀態更新功能。其他一切都是 JavaScript 。此外,您不需要像以前一樣在類組件中使用淺合併來保持 state 的更新。相反,您使用 useState 封裝一個域(例如列表),但如果您需要另一個狀態(例如計數器),則只需使用另一個 useState 封裝此域。您可以在 React 的文檔中閱讀有關 useState Hook 的更多信息

React useEffect Hook

讓我們轉到下一個名爲 useEffectHook。如前所述,功能組件應該能夠使用 Hook 管理狀態和副作用。上面我們已經使用 useState Hook 展示了管理狀態。現在將 useEffect Hook 用於副作用,這些副作用通常用於與 Browser / DOM API 或外部 API(如數據獲取)的交互。讓我們看一下如何通過實現一個簡單的秒錶,將 useEffect HookBrowser API 相結合。您可以在GitHub 倉庫中查看使用 React 類組件需要如何完成。

import React, { useState } from 'react';

function App() {
  const [isOn, setIsOn] = useState(false);

  return (
    <div>
      {!isOn && (
        <button type="button" onClick={() => setIsOn(true)}>
          Start
        </button>
      )}

      {isOn && (
        <button type="button" onClick={() => setIsOn(false)}>
          Stop
        </button>
      )}
    </div>
  );
}

export default App;

現在還沒有秒錶。但現在至少有條件渲染顯示 “開始”“停止” 按鈕。並且由 useState hook 來管理布爾標誌的狀態。

接下來讓我們加入副作用:用 useEffect 來註冊一個 interval 定時器。定時器函數每秒會向您的瀏覽器控制檯發出一條記錄。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    setInterval(() => console.log('tick'), 1000);
  });

  return (
    <div>
      {!isOn && (
        <button type="button" onClick={() => setIsOn(true)}>
          Start
        </button>
      )}

      {isOn && (
        <button type="button" onClick={() => setIsOn(false)}>
          Stop
        </button>
      )}
    </div>
  );
}

export default App;

爲了在組件卸載的時候移除定時器(以及每次渲染更新之後),您可以在 useEffect 中返回一個函數,以便清理任何內容。對於上面的例子,當組件不再存在時,不應該留下任何內存泄漏。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => console.log('tick'), 1000);

    return () => clearInterval(interval);
  });

  ...
}

export default App;

現在,您需要在安裝組件時設置副作用,並在卸載組件時清除副作用。如果您需要記錄函數的調用次數,您會看到每次組件狀態發生變化時它都會設置一個新的定時器(例如,單擊“開始”/“停止”按鈕)。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    console.log('effect runs');
    const interval = setInterval(() => console.log('tick'), 1000);

    return () => clearInterval(interval);
  });

  ...
}

export default App;

爲了僅在組件的 mountunmount 時響應 ,可以將空數組作爲第二個參數傳遞給它。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => console.log('tick'), 1000);

    return () => clearInterval(interval);
  }, []);

  ...
}

export default App;

但是,由於在每次渲染之後都會清除定時器,我們也需要在我們的業務週期中設置定時器。我們也可以告訴 effect 僅在 isOn 變量發生變化時運行。僅當數組中的一個變量發生更改時,effect 纔會在更新週期中運行。如果將數組保持爲空, effect 將僅在 mountunmount 時運行,因爲沒有要檢查的變量是否再次運行副作用。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => console.log('tick'), 1000);

    return () => clearInterval(interval);
  }, [isOn]);

  ...
}

export default App;

現在,無論 isOn 布爾值是 true 還是 false ,定時器都在運行。接下來,我們希望定時器僅在秒錶啓動的時候運行。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(() => console.log('tick'), 1000);
    }

    return () => clearInterval(interval);
  }, [isOn]);

  ...
}

export default App;

現在在功能組件中引入另一個狀態來跟蹤秒錶的計時器。它用於更新計時器,但僅在秒錶被激活時使用。

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

function App() {
  const [isOn, setIsOn] = useState(false);
  const [timer, setTimer] = useState(0);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(
        () => setTimer(timer + 1),
        1000,
      );
    }

    return () => clearInterval(interval);
  }, [isOn]);

  return (
    <div>
      {timer}

      {!isOn && (
        <button type="button" onClick={() => setIsOn(true)}>
          Start
        </button>
      )}

      {isOn && (
        <button type="button" onClick={() => setIsOn(false)}>
          Stop
        </button>
      )}
    </div>
  );
}

export default App;

代碼中仍然存在一個錯誤。當定時器運行時,它會將 timer 每秒加 1 。但是,它始終依賴於計時器的初始狀態在累加。只有當 inOn 布爾標誌改變時,狀態纔會正常顯示。爲了在計時器在運行時,始終接收最新的狀態,您可以使用函數代替代替狀態,是的每次更新的時候都可以拿到最新的狀態。

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

function App() {
  const [isOn, setIsOn] = useState(false);
  const [timer, setTimer] = useState(0);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(
        () => setTimer(timer => timer + 1),
        1000,
      );
    }

    return () => clearInterval(interval);
  }, [isOn]);

  ...
}

export default App;

另一種方法是,在計時器改變時,運行 effect。然後 effect 將收到最新的計時器狀態。

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

function App() {
  const [isOn, setIsOn] = useState(false);
  const [timer, setTimer] = useState(0);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(
        () => setTimer(timer + 1),
        1000,
      );
    }

    return () => clearInterval(interval);
  }, [isOn, timer]);

  ...
}

export default App;

這是使用瀏覽器 API 實現的秒錶效果,如果您想繼續,您也可以通過提供 “重置” 按鈕來擴展示例。

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

function App() {
  const [isOn, setIsOn] = useState(false);
  const [timer, setTimer] = useState(0);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(
        () => setTimer(timer => timer + 1),
        1000,
      );
    }

    return () => clearInterval(interval);
  }, [isOn]);

  const onReset = () => {
    setIsOn(false);
    setTimer(0);
  };

  return (
    <div>
      {timer}

      {!isOn && (
        <button type="button" onClick={() => setIsOn(true)}>
          Start
        </button>
      )}

      {isOn && (
        <button type="button" onClick={() => setIsOn(false)}>
          Stop
        </button>
      )}

      <button type="button" disabled={timer === 0} onClick={onReset}>
        Reset
      </button>
    </div>
  );
}

export default App;

That’s it. 使用 useEffect hook 來管理 React 函數組件的副作用,比如 Browser / DOM API 或其他第三方 API 的交互(例如數據獲取)。您可以在 React 官方文檔 useEffect hook 部分 獲取更多信息。

自定義 React Hooks

最後同樣重要的是,在您瞭解了兩個最常用的在函數組件中引入狀態和副作用的 Hooks 之後,我還想告訴您最後一件事:自定義 Hooks 。沒錯,您可以實現自己的自定義 React Hooks,在您的應用程序中或提供給其他人中使用。讓我們看看接下來的示例:一個能夠檢測您的設備是在線還是離線的應用程序是如何工作的。

import React, { useState } from 'react';

function App() {
  const [isOffline, setIsOffline] = useState(false);

  if (isOffline) {
    return <div>Sorry, you are offline ...</div>;
  }

  return <div>You are online!</div>;
}

export default App;

接下來,爲副作用引入 useEffect hook。在這種情況下,effect 會添加和刪除檢查設備是聯機還是脫機的監聽器。兩個監聽器在 mount 時只設置一次,在 unmount 時清理一次(空數組作爲第二個參數)。每當調用其中一個監聽器時,它就會設置 isOffline 布爾值的狀態。

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

function App() {
  const [isOffline, setIsOffline] = useState(false);

  function onOffline() {
    setIsOffline(true);
  }

  function onOnline() {
    setIsOffline(false);
  }

  useEffect(() => {
    window.addEventListener('offline', onOffline);
    window.addEventListener('online', onOnline);

    return () => {
      window.removeEventListener('offline', onOffline);
      window.removeEventListener('online', onOnline);
    };
  }, []);

  if (isOffline) {
    return <div>Sorry, you are offline ...</div>;
  }

  return <div>You are online!</div>;
}

export default App;

現在,讓我們將這些功能封裝在一個 Effect 中。這是一個很棒的功能,我們希望其他地方也可以重用。這就是爲什麼我們可以提取功能,作爲一個自定義 Hook, 它遵循與其他 Hook 相同的命名約定。

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

function useOffline() {
  const [isOffline, setIsOffline] = useState(false);

  function onOffline() {
    setIsOffline(true);
  }

  function onOnline() {
    setIsOffline(false);
  }

  useEffect(() => {
    window.addEventListener('offline', onOffline);
    window.addEventListener('online', onOnline);

    return () => {
      window.removeEventListener('offline', onOffline);
      window.removeEventListener('online', onOnline);
    };
  }, []);

  return isOffline;
}

function App() {
  const isOffline = useOffline();

  if (isOffline) {
    return <div>Sorry, you are offline ...</div>;
  }

  return <div>You are online!</div>;
}

export default App;

將自定義 Hook 作爲函數提取出來並不是唯一的事情。您還必須將 isOffline 狀態從自定義 Hook 中返回,以便在應用程序中向用戶顯示是否離線的消息。否則,它應該呈現正常的應用程序。這就是自定義 Hook ,它可以檢測您是在線還是離線。您可以在 React 的文檔中閱讀有關自定義 Hook 的更多信息。

可重用的 React Hooks 最棒的地方,是它有可能發展自定義 React Hooks 的生態系統,我們可以從 npm 爲任何 React 應用程序安裝 Hooks。而且不僅僅是 React 應用程序。Vue的作者 Evan You (尤雨溪) 也被 Hooks 迷住了!。也許我們會看到兩個生態系統之間的橋樑,可以在 VueReact 之間共享 Hooks


如果您想深入瞭解 state hookeffect hook ,可以查看以下 React Hooks 教程:

查看 React 文檔中關於鉤子的官方FAQ規則,以瞭解有關其細粒度行爲的更多信息。此外,您還可以查看所有官方提供的React Hooks

對於仍然關注 React Hooks 的每個人:給自己一個機會。使用狀態和副作用實現幾個 React 函數組件。我們必須自己去理解它們是如何工作的,感受這種編程體驗的升級。我必須說使用它們感覺非常棒。

譯註:給我一個機會,重學 React ~
本文已經聯繫原文作者,並授權翻譯,轉載請保留原文鏈接
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章