10分鐘瞭解react引入的hooks

?holyZhengs發表於2019-03-04

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

當然,學習的最好、最直接的方法就是看文件,所以我也非常建議大家去看文件學習,而且還是官方的文件而不是中文版的文件。本文也是樓主在學習過後的一些總結與思考,樓主會把最近學習到的由淺入深,循序漸進,儘可能簡潔的分享給大家,希望對大家有幫助。不足之處可在評論區補充,本文講從以下幾個大方面來展開:

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

為什麼引入Hooks?

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

  1. 難以重用和共享元件中的與狀態相關的邏輯
  2. 邏輯複雜的元件難以開發與維護,當我們的元件需要處理多個互不相關的 local state 時,每個生命週期函式中可能會包含著各種互不相關的邏輯在裡面。
  3. 類元件中的this增加學習成本,類元件在基於現有工具的優化上存在些許問題。
  4. 由於業務變動,函式元件不得不改為類元件等等。

在進一步瞭解之前,我們需要先快速的瞭解一些基本的 Hooks 的用法。

快速瞭解 Hooks 的使用

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

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 的值,返回一對變數

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) 的重用和共享問題。

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

  1. Render Props 通過props接受一個返回react element的函式,來動態決定自己要渲染的結果;
<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>
複製程式碼
  1. 還有就是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讓我們的函式元件的功能得到了擴充,擁有了和類元件相似的功能,甚至避免了類元件存在的各種問題,那麼就會出現各種的疑問,比如

  1. 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的生態會繼續完善,請期待。

  1. 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 瞭解。如果有什麼不足或要補充的地方,歡迎評論區提出。

相關文章