React v16.7 之 Hooks

Hooks are an upcoming feature that lets you use state and other React features without writing a class. They’re currently in React v16.7.0-alpha.

What

Hooks 是 React 函數組件內的一類特殊函數,使開發者能在 function component 里繼續使用 state 和 lifecycle。通過 Custom Hooks 可以復用業務邏輯,從而避免添加額外的components。

Why

過去我們使用React時,component 基本分為兩種:function component 和 class component 。其中function component就是一個 pure render component,不存在 state 和 lifecycle 。function component 使組件之間耦合度降低,但一旦需要 state 或 lifecycle ,就需要變成 class component 。而 class component 也會帶來一些缺點:

  • React 組件樹過于臃腫

單向數據流使組件間的通信必須一層一層往下傳,當有些狀態不適合使用 Redux 這種 global store 的情況下,此時組件之間的邏輯復用和溝通就會變得十分困難。為此,過去我們會使用各種 HOC(高階組件)來傳遞狀態。這就導致了當應用規模越來越龐大的時候,會多了很多無關 UI 的 wrapper 組件,也就使得 React 組件樹變得越來越臃腫,開發和調試效率隨之變低。

Write

了解了Hooks的基本知識,接下來就是如何去使用 Hooks API 了。Hooks 主要分為三種:

  • State Hook (在 function component 使用 state)
  • Effect Hook (在 function component 使用 lifecycle 和 side effect )
  • Custom Hook (通過自定義Hook來復用邏輯)
WX20181224-184244@2x.png

State Hook

官方 Example:

import { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

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

Hook 的本質實際上就是一個特殊的函數,通常以"use"開頭。這里的 useState 就是一個Hook,通過它可以實現在組件內使用state。useState 會返回一個pair:分別是當前 state 的值和修改這個 state 的函數。用法有點類似 class component 里的 this.setState,只是這里不會合并 state 對象,而且注意到沒,這里的 useState 的初始值是0,跟 this.state 不同在于它不需要是一個 Object。

官方有個例子對比 useState 與 this.state 的。(傳送門:https://reactjs.org/docs/hooks-state.html

多個 state 變量

function ExampleWithManyStates() {
  // Declare multiple state variables!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

你完全可以聲明多個 useState,一點問題都木有!這種做法帶來的好處是:我們的 state 將不會變得非常臃腫,每個 state 都非常直觀,獨立。

Effect Hook

我們經常在 React 組件中進行拉取數據、訂閱或操作DOM,這種行為被稱為 "side effects"(副作用),這種行為通常只能在生命周期中執行,而不能在 render 里。

React 通過 useEffect 來解決這樣的問題,具體怎么用我們看看例子:

import { useState, useEffect } from 'react';

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

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

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

這種做法相當于把 React classes 里的 componentDidMount ,componentDidUpdate,componentWillUnmount 合并成了一個。

由于 Hooks 是定義在最外層函數的,所以這里是能訪問到 props 和 state 的,它默認會在每一次 render 后都會被調用(包括第一次)。

同樣的,官方有個例子對比了 useEffect 和 class lifecycle 的。(傳送門:https://reactjs.org/docs/hooks-effect.html

cleanup

通常我們的大部分 effect 行為都是不需要清理,比如網絡請求、DOM操作或者日志記錄等。但如果我們在 effects 進行了類似外部數據訂閱這樣的操作,那么我們就需要在 Unmount 前取消訂閱。針對這種場景,可以通過在 useEffect 中返回一個函數的方式進行清理。此處應該有代碼:

import { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

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

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

乍一看,使用 Hook 的方式相對于 class component 只是代碼寫少點而已,但實際上它帶來的好處可不止這些。如果用 class component 的話,我們需要在 componentDidMount 訂閱 props.friend.id 的狀態,然后在 componentWillUnmount 中取消訂閱。

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

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

但這種做法可能會引發bug(試想一下,如果 friend prop 變了會怎樣?)。這種情況下,頁面上顯示的狀態就不是當前這個 friend 的啦~ 所以這是一個bug,解決的方式就是在 componentDidUpdate 中先進行 unsubscribe,再重新 subscribe

componentDidUpdate(prevProps) {
    // Unsubscribe from the previous friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // Subscribe to the next friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

在實際開發中,我們可能經常會沒考慮到這種情況。那么現在有了 Hook,你可以不用擔心了!

性能優化之 Skipping effects

每次 render 都會 cleanup 或執行 effect,這可能會導致性能問題。在 class component 中我們通常會在 componentDidUpdate 里進行對比判斷。而在 Hook 里,我們可以通過給 useEffect 傳遞第二個參數(數組形式)來選擇 Hook 的觸發時機,

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

同步的 useLayoutEffect

必須說明的是,useEffect 是異步的,即它不會阻塞瀏覽器渲染頁面,因為大多數情況下,這些 effects 都不需要同步執行。在極少數的場景下,可以選擇用 useLayoutEffect。它會在 re-render 后同步執行,阻塞瀏覽器進行渲染。

Custom Hook

過去我們在組件中復用邏輯的通常做法是使用高階函數 ( high-order component ) 和 render props。如今有了 Hooks,我們可以避免添加更多的組件到我們的組件樹中了。

我們把上面 FriendStatus 稍加改動。

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

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

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

它對外接受一個 friendId 作為參數,然后返回具體狀態。而在其他組件里引用也非常簡單:

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

這樣,復用了邏輯而又不需要引入新的 state,簡直完美!

其他 built-in Hooks

還好很多內置的 Hooks,如:

  • useContext(替代<Context.Consumer> 使用render props 的寫法)
function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}
  • useReducer (相當于組件自帶 redux reducer,負責 dispatch action 并更新 state)
function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);
  // ...

注意事項

  • 在最外層函數使用 Hooks ,不要在循環塊、條件塊或函數內調用 Hooks;
  • 只在 React function component 里使用 Hooks(除非是Custom Hooks,否則不要在普通函數中使用 Hooks)

最后總結一下

在 React 里,Hooks 就是一系列的特殊函數,在 function component 內部“勾住” 組件的 state 和 lifecycle。Hook 是向后兼容的,但官方不推薦大家將舊代碼的 class component 都改成 Hook,大家可以在新代碼中體驗一下這種寫法。針對 Hooks 新特性的官方文檔很詳細,這里限于篇幅,就不過多講了,推薦大家去看官方文檔。

掰掰~

參考文檔:https://reactjs.org/docs/hooks-intro.html

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • It's a common pattern in React to wrap a component in an ...
    jplyue閱讀 3,295評論 0 2
  • 原教程內容詳見精益 React 學習指南,這只是我在學習過程中的一些閱讀筆記,個人覺得該教程講解深入淺出,比目前大...
    leonaxiong閱讀 2,854評論 1 18
  • Learn from React 官方文檔 一、Rendering Elements 1. Rendering a...
    恰皮閱讀 2,685評論 2 3
  • 掛單碧水源漲停板,后取消;戰勝心理因素,堅決持有,等待市場發出賣出信號; 北新建材賣出后上漲,有后悔心理,因為之前...
    W大先生閱讀 103評論 0 0
  • 圖片發自簡書App 浣衣娘 人言蘇杭是天堂 玉景美人遮眼忙 我說...
    c45736e529f0閱讀 494評論 0 6