10分鐘瞭解 react 引入的 Hooks

hooks

「你們好,我是谷阿莫,今天要將的是一個...」,哈哈哈,看到這個題我就想到這個開頭。最近react 官方在 2018 ReactConf 大會上宣佈 React v16.7.0-alpha(內測) 將引入 Hooks。因此咱們有必要了解 Hooks,以及由此引起的疑問。html

固然,學習的最好、最直接的方法就是看文檔,因此我也很是建議你們去看文檔學習,並且仍是官方的文檔而不是中文版的文檔。本文也是樓主在學習事後的一些總結與思考,樓主會把最近學習到的由淺入深,按部就班,儘量簡潔的分享給你們,但願對你們有幫助。不足之處可在評論區補充,本文講從如下幾個大方面來展開:react

  1. 爲何引入Hooks
  2. Hooks使用和注意事項
  3. Hooks的如何解決了已存在的問題
  4. 引入Hooks引起的疑問

爲何引入Hooks?

react官方給出的動機是用來解決長時間使用和維護react過程當中遇到的一些難以免的問題。好比:api

  1. 難以重用和共享組件中的與狀態相關的邏輯
  2. 邏輯複雜的組件難以開發與維護,當咱們的組件須要處理多個互不相關的 local state 時,每一個生命週期函數中可能會包含着各類互不相關的邏輯在裏面。
  3. 類組件中的this增長學習成本,類組件在基於現有工具的優化上存在些許問題。
  4. 因爲業務變更,函數組件不得不改成類組件等等。

在進一步瞭解以前,咱們須要先快速的瞭解一些基本的 Hooks 的用法。數組

快速瞭解 Hooks 的使用

Hooks讓咱們的函數組件擁有了相似類組件的特性,好比local state、lifecycle,並且還解決了上面提到的一系列問題,它是如何解決這些問題的,下面會在一一指出。首先來快速的看看Hoos的使用,這裏講最主要的兩個 Hooks :useState 和 useEffect。先看一個你可能看過不少遍的例子app

import { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
      <p> {count} </p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
  );
}

useState

useState 這個方法能夠爲咱們的函數組件帶來 local state,它接收一個用於初始 state 的值,返回一對變量ide

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

// 等價於
var const = useState(0)[0]; // 該state
var setConst = useState(0)[1]; // 修改該state的方法

useEffect

useEffect 能夠利用咱們組件中的 local state 進行一些帶有反作用的操做函數

useEffect(() => {
  document.title = `You clicked ${count} times`;
});

useEffect 中還能夠經過傳入第二個參數來決定是否執行裏面的操做來避免一些沒必要要的性能損失,只要第二個參數數組中的成員的值沒有改變,就會跳過這次執行。若是傳入一個空數組 [ ],那麼該 effect 只會在組件 mount 和 unmount 時期執行。工具

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 若是count沒有改變,就跳過這次執行

useEffect 中還能夠經過讓函數返回一個函數來進行一些清理操做(clean up),好比取消訂閱等性能

useEffect(() => {
  api.subscribe(theId);
  return () => {
      api.unsubscribe(theId)    //clean up
  }
});

useEffect 何時執行? 它會在組件 mount 和 unmount 以及每次從新渲染的時候都會執行,也就是會在 componentDidMount、componentDidUpdate、componentWillUnmount 這三個時期執行。學習

清理函數(clean up)何時執行? 它會在前一次 effect執行後,下一次 effect 將要執行前,以及 Unmount 時期執行

注意事項

咱們只能在 函數組件 中使用 Hooks,咱們也能夠在一個組件中使用多組 Hooks。好比:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    API.subscribe(props.friend.id);
    return () => {
      API.unsubscribe(props.friend.id);
    };
  });

  return isOnline
}

可是這裏有一點須要咱們注意的就是 咱們只能在頂層代碼(Top Level)中調用 Hooks,不能在循環或判斷語句等裏面調用,這樣是爲了讓咱們的 Hooks 在每次渲染的時候都會按照 相同的順序 調用,由於這裏有一個跟關鍵的問題,那就是 useState 須要依賴參照第一次渲染的調用順序來匹配對於的state,不然 useState 會沒法正確返回它對於的state。

Hooks 解決的問題

好了,知道了 Hooks 基本使用後,咱們就能夠來了解 Hooks 是怎麼解決 react 長期存在的問題的。

如何解決 狀態有關的邏輯(stateful logic) 的重用和共享問題。

過去對於相似問題的解決方案主要有兩個:

  • Render Props 經過props接受一個返回react element的函數,來動態決定本身要渲染的結果;
<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>
  • 還有就是Higher-Order Components 以一種相似 工廠模式 的方式去生產出具備相同或相似邏輯的組件。
function getComponent(WrappedComponent) {

  return class extends React.Component {
    constructor(props) {
      super(props);
    }
    componentDidMount() {
      // doSomething
    }
    componentWillUnmount() {
      // doSomething
    }
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

可是不管是哪種方法都會形成組件數量增多,組件樹結構的修改,並且有可能出現組件嵌套地獄(wrapper hell)的狀況。如今 React 經過 custom Hooks 來解決這個問題

custom Hooks

custom Hooks 並非一個api,而是一個規則。具體實現就是經過一個函數來封裝跟狀態有關的邏輯(stateful logic),將這些邏輯從組件中抽取出來。在這個函數中咱們可使用其餘的 Hooks,也能夠單獨進行測試,甚至將它貢獻給社區。

import { useState, useEffect } from 'react';

function useCount() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  
  return count
}

好比上面的一個例子,他就是一個 custom Hooks,提取了對 count 的操做。這裏須要遵循一個約定,命名要用 use*,這是爲了方便咱們區分,利於咱們維護。能夠看到他其實就是一個函數,咱們能夠在現有的全部其餘組件中引用它

function CountStatus() {
  const count = useCount();
  return count;
}

這裏的核心概念就是將邏輯提取出來封裝在 custom Hooks,而後能夠在任何的其餘組件中共享這部分邏輯,也能夠貢獻給社區。因此我也預測在不久的未來,會出現不少的充滿想象力的各類用途的 custom Hooks 在社區中出現,極大的提升咱們的開發效率。

具備複雜邏輯的組件的開發和維護

前面咱們也提到,咱們的組件可能會隨着開發的進行變得愈來愈複雜,要處理愈來愈多的 local State,那麼在組件的生命週期函數中就會充斥着各類互不相關的邏輯,這裏須要引入官方的比較複雜的例子,先看基於之前類組件的狀況:

class FriendStatusWithCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }
  // ...

通過 Hook 改造後:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...
}

狀態和相關的處理邏輯能夠按照功能進行劃分,沒必要散落在各個生命週期中,大大下降了開發和維護的難度。除了這幾個hooks還有其餘額外的hooks,在此繼續瞭解 Hooks API Reference

伴隨 Hooks 的一些思考

hooks讓咱們的函數組件的功能獲得了擴充,擁有了和類組件類似的功能,甚至避免了類組件存在的各類問題,那麼就會出現各類的疑問,好比

  • Hooks 引進後, 函數組件 和 類組件 該如何選擇?官方關於相似的問題的答覆是:
Our goal is for Hooks to cover all use cases for classes as soon as possible. There are no Hook equivalents to the uncommon getSnapshotBeforeUpdate and componentDidCatch lifecycles yet, but we plan to add them soon.

It is a very early time for Hooks, so some integrations like DevTools support or Flow/TypeScript typings may not be ready yet. Some third-party libraries might also not be compatible with Hooks at the moment.

官方的目標是儘量快的讓 Hooks 去覆蓋全部的類組件案例,可是如今 Hooks 還處於一個很是早的階段,各類調試工具、第三方庫等都尚未作好對 Hooks 的支持,並且目前也沒有能夠取代類組件中 getSnapshotBeforeUpdate 和 componentDidCatch 生命作起的 Hooks,不過很快會加上他們。總的來時就是鼓勵你們在之後使用 Hooks,對於已存在的類組件沒必要大規模的去重寫,Hooks及Hooks的生態會繼續完善,請期待。

  • Hooks 是否能夠代替 render-props 和 higher-order components ?前面咱們也提到,hooks能夠解決後者帶來的各類問題,那麼 hooks 是否能夠代替後者呢?官方的回答:
Often, render props and higher-order components render only a single child. We think Hooks are a simpler way to serve this use case. There is still a place for both patterns (for example, a virtual scroller component might have a renderItem prop, or a visual container component might have its own DOM structure). But in most cases, Hooks will be sufficient and can help reduce nesting in your tree.

大概意思就是,在大多數案例下,hooks 足夠應付且更適合,因此優先考慮 hooks。

這是我看到 hooks 後比較關心的兩個問題,若是你們想了解更多的問題的話能夠到 Hooks FAQ 瞭解。若是有什麼不足或要補充的地方,歡迎評論區提出。

相關文章
相關標籤/搜索