React Hooks (Proposal)

nanyang24發表於2018-11-12

原文連結:Github

React Hooks (Proposal)

在 React v16.7.0 alpha 版本里,提出了一個新的 Feature Proposal :Hooks ,對社群以及以後前端發展所帶來的影響是巨大的。

學習 Hooks 的知識需要對 React 生態有較深入的理解

What is the Hooks ?

Hooks 是 React 內部元件中的一系列特殊函式,直觀帶來的改變是引入state、生命週期函式、或者其他 React 功能,無需使用 classes 編寫元件(類語法帶來的問題有很多),背後為前端帶來更深入更普及的 functional programming 思想。

引入 Hooks 的動機

React 官方闡明瞭引入 Hooks 的動機,Hooks 出現前,我們編寫 React 元件 會經常遇到的問題:

  1. It’s hard to reuse stateful logic between components
    • React 沒有提供官方方案去解決 元件之間共享複用有狀態邏輯 ,元件間邏輯的複用和資料傳遞就變得十分困難(必須一層一層往下傳),所以我們使用 render propshigher-order components 來解決複用邏輯的同時引來了新的問題,一些無關 UI 的 wrapper 元件越來越多,巢狀元件越來越深,形成 wrapper hell ,雖然 React devTools 有過濾器來幫助我們更容易地除錯。
    • 使用 Hooks 可以在不改變元件層次結構的情況下複用有狀態邏輯。可以利用 custom hooks,複用包含狀態的邏輯,這些邏輯不再出現在元件樹中,而是形成一個獨立、可測試的單元,但仍然響應 React 在渲染之間的變化;社群之間分享 自定義hooks 更容易,hooks 就像外掛一樣。
  2. Complex components become hard to understand
    • 隨著專案深入,我們逐漸會編寫越來越複雜的邏輯在元件中,這導致了再生命週期函式內編寫的邏輯非常臃腫,例如 新增監聽器,我們需要在componentDidMountcomponentWillUnmount 中分別編寫新增與刪除監聽器的邏輯,而一般在 componentDidMount 中,我們也會編寫 請求資料 的邏輯。各種功能不相關聯的邏輯寫在一起,而且相同功能的邏輯散落在不同函式內,這帶來許多隱患以及除錯上的困難
    • 使用 Hooks 可以 將相關聯的邏輯code由元件拆分出來成更簡單直觀的函式(例如訂閱事件、請求資料)
  3. Classes confuse both people and machines
    • React 官方認為 JS 的 Class 語法的學習成本很高,使用類語法,要必須清楚 this 在 JS 的工作方式,例如我們需要 繫結事件處理程式 (以何種方式繫結這裡不是重點,個人推薦箭頭函式形式);另外一些重要實踐上,使用 Class 語法也帶來諸多問題,詳細參閱 classes-confuse-both-people-and-machines)
    • 使用 Hooks 可以 在無需編寫 Class 語法的情況下 引入state、生命週期函式、或者其他 React 功能

實際上引入 Hooks 並不會給現有的程式碼帶來問題

  1. 完全可選(將使用 Hooks 的選擇權交給開發者)
  2. 向後相容(不會有任何破壞性更改)
  3. 在可預見的未來內,不會從 React 中刪除 類語法
  4. Hooks 並沒有顛覆之前的 React 概念。相反,帶來更直觀的 API 實現相同的功能

編寫 Hooks

目前主要的 Hooks :

  1. State hooks
  2. Effect hooks
  3. Custom hooks (自定義 hooks 用來複用包含狀態的邏輯)

useState

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>
  );
}
複製程式碼

使用 state hooksfunction components中可以像上面程式碼這樣,等同於Class語法的程式碼就不貼了。

值得一提的是,在 Hooks 出現之前,我們通常叫這樣形式的元件為 stateless components or stateless function components ,但現在,有了 Hooks ,我們可以在這類元件中使用 state,所以改稱 function components

  1. useState 的引數是 我們需要定義的 state 名的初始值(不必像以前一樣,state 必須為 Object,如果我們想要建立兩個state,就呼叫兩次 useState)
  2. 返回值是包含兩個值的陣列,兩個值分別為 當前狀態更新它的函式 。(這裡我們使用 array destructuring 的方式將值取出來。)

建立多個 state 就像這樣

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

this.setState 不同,更新狀態總是替換它而不是合併它(也解決了很多之前合併帶來的問題)

Functional updates

如果新的 state 值是依賴上一個 state 值來計算的,我們可以給 setState 傳遞一個函式引數,這個函式的引數為上一個 state 的值,返回值是更新後的 state 值,例如:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}
複製程式碼

所以如果需要更新的 state 值為 Object,我們應該使用 object spread syntax

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});
複製程式碼

延遲初始化 state

如果初始化的值是需要大量計算得到的結果,可以使用函式代替,此函式只會在初始化階段執行

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});
複製程式碼

useEffect

Effect 其實就是 請求資料,操作DOM,以及訂閱事件等一系列 副作用/效果

而 useEffect 則是 之前 componentDidMountcomponentDidUpdatecomponentWillUnmount 的結合

React元件中有兩種常見的 Effect:需要清理和不需要清理的 Effect

不需要清理的 Effect

import { useState, useEffect } from 'react';

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

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
複製程式碼
  1. 將在每次渲染後執行 useEffect
  2. useEffect 寫在 函式內部是為了直接訪問到state值,利用了閉包的性質,不需要額外 API

需要清理的 Effect

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';
}
複製程式碼

需要單獨的 API 來執行清理邏輯。因為新增和刪除訂閱的邏輯是相關的,useEffect 旨在將其保持在一起。 如果 useEffect 返回一個函式,React 將在清理時執行它

清理的時機是 當元件解除安裝時,但,useEffect 會在每次渲染後執行而不僅僅是一次, 這就是 React 在下次執行 useEffect 之前還清除前一個 useEffect 的原因;Using the Effect Hook – React

如果要減少 useEffect 內並不是每次渲染都必要的邏輯,可以:

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

React 會比較兩次渲染的 count 值,如果一樣,就會跳過這次 useEffect

Custom Hooks

我們可以封裝在多個元件可重用的包含狀態的邏輯,例如

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;
}
複製程式碼

useFriendStatus 就是一個我們寫好的複用邏輯函式,供其他元件呼叫。

多個元件使用 相同自定義Hooks,它們的狀態和效果是 獨立隔離的,僅僅是邏輯的複用。因為本質是 呼叫 Custom Hooks 是呼叫 useStateuseEffect,它們在一個元件呼叫很多次,彼此產生的狀態也是完全獨立的。

詳細參見文件:Writing Custom Hooks – React

使用 Hooks 的規則:

務必遵守的規則: Rules of Hooks – React

Hooks API: Hooks API Reference – React

Conclusion

React Hooks 帶來的邊際效應可以說是巨大的,希望更加完善之後,可以看到開啟新窗的前端。

啟發 Hooks 的產生:Hooks FAQ – React

關於 Hooks 的討論:RFC: React Hooks by sebmarkbage · Pull Request #68 · reactjs/rfcs · GitHub

有趣的是,Vue的作者也很快建立了在 Vue 實驗 Hooks 的repo:GitHub - yyx990803/vue-hooks: Experimental React hooks implementation in Vue

原文連結:Github

相關文章