1. 程式人生 > >10分鐘了解 react 引入的 Hooks

10分鐘了解 react 引入的 Hooks

amp fault set follow 長時間 優化 重新 party 初始

“大家好,我是谷阿莫,今天要將的是一個...”,哈哈哈,看到這個題我就想到這個開頭。最近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) 的重用和共享問題。

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

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

原文地址:https://segmentfault.com/a/1190000016886795

10分鐘了解 react 引入的 Hooks