首頁>技術>

前言

下面我談一下我認為的react hooks的優缺點,優缺點通過和傳統的React.Component進行對比得出。

優點一、更容易複用程式碼

這點應該是react hooks最大的優點,它通過自定義hooks來複用狀態,從而解決了類元件有些時候難以複用邏輯的問題。hooks是怎麼解決這個複用的問題呢,具體如下:

每呼叫useHook一次都會生成一份獨立的狀態,這個沒有什麼黑魔法,函式每次呼叫都會開闢一份獨立的記憶體空間。雖然狀態(from useState)和副作用(useEffect)的存在依賴於元件,但它們可以在元件外部進行定義。這點是class component做不到的,你無法在外部宣告state和副作用(如componentDidMount)。上面這兩點,高階元件和renderProps也同樣能做到。但hooks實現起來的程式碼量更少,以及更直觀(程式碼可讀性)。

舉個例子,我們經常使用的antd-table,我們經常需要寫一些狀態:pagination={current:xxx,pageSize:xxx,total:xxx}。但許多場景只是簡單的表格,我們希望封裝一個高階元件,自帶這些狀態,並可以自動呼叫server去獲取remote data。

用高階元件來實現這樣它:

import { Table } from 'antd'import server from './api'@useTable(server)class App extends Component{  render(){    // useTable和tableProps的程式碼是分離的,但高階元件一多,程式碼會變得較難閱讀,   // 你難以區分這個props是來自哪個高階元件,或者還是來自業務的父元件。    const { tableProps } = this.props;    return (      <Table         columns={[...]}      // tableProps包含pagination, onChange, dataSource等屬性        {...tableProps}      />    )  }}
用hooks來實現的話,會是:
import { Table } from 'antd'import server from './api'function App {    const { tableProps } = useTable();    return (      <Table         columns={[...]}      // tableProps包含pagination, onChange, dataSource等屬性        {...tableProps}      />    )}/*相對比高階元件“祖父=>父=>子”的層層巢狀,hooks是這樣的:  const { brother1 } = usehook1; const { brother2} = usehook2;*/
實現有以下幾點優勢:

把"useTable"和輸出的值寫到一起,結構更清晰,更容易閱讀和維護。對比高階元件,程式碼量更少。

二、清爽的程式碼風格

函數語言程式設計風格,函式式元件、狀態儲存在執行環境、每個功能都包裹在函式中,整體風格更清爽,更優雅。另外,對比類元件,函式元件裡面的unused狀態和unused-method更容易被發現。

三、程式碼量更少

向props或狀態取值更加方便,函式元件的取值都從當前作用域直接獲取變數,而類元件需要先訪問例項引用this,再訪問其屬性或者方法,多了一步。更改狀態也變得更加簡單, this.setState({ count:xxx })變成 setCount(xxx)。因為減少了很多模板程式碼,特別是小元件寫起來更加省事,人們更願意去拆分元件。而元件粒度越細,則被複用的可能性越大。所以,hooks也在不知不覺中改變人們的開發習慣,提高專案的元件複用率。

缺點一、響應式的useEffect

寫函式元件時,你不得不改變一些寫法習慣。你必須清楚程式碼中useEffect和useCallback等api的第二個引數“依賴項陣列”的改變時機,並且掌握上下文的useEffect的觸發時機。當邏輯較複雜的時候,useEffect觸發的次數,可能會被你預想的多。對比componentDidmount和componentDidUpdate,useEffect帶來的心智負擔更大。

二、狀態不同步

這絕對是最大的缺點。函式的執行是獨立的,每個函式都有一份獨立的作用域。函式的變數是儲存在執行時的作用域裡面,當我們有非同步操作的時候,經常會碰到非同步回撥的變數引用是之前的,也就是舊的(這裡也可以理解成閉包)。

import React, { useState } from "react";const Counter = () => {  const [counter, setCounter] = useState(0);  const onAlertButtonClick = () => {    setTimeout(() => {      alert("Value: " + counter);    }, 3000);  };  return (    <div>      <p>You clicked {counter} times.</p>      <button onClick={() => setCounter(counter + 1)}>Click me</button>      <button onClick={onAlertButtonClick}>        Show me the value in 3 seconds      </button>    </div>  );};export default Counter;

當你點選Show me the value in 3 seconds的後,緊接著點選Click me使得counter的值從0變成1。三秒後,定時器觸發,但alert出來的是0(舊值),但我們希望的結果是當前的狀態1。

這個問題在class component不會出現,因為class component的屬性和方法都存放在一個instance上,呼叫方式是:this.state.xxx和this.method()。因為每次都是從一個不變的instance上進行取值,所以不存在引用是舊的問題。

其實解決這個hooks的問題也可以參照類的instance。用useRef返回的immutable RefObject(current屬性是可變的)來儲存state,然後取值方式從counter變成了:counterRef.current。如下:

import React, { useState, useRef, useEffect } from "react";const Counter = () => {  const [counter, setCounter] = useState(0);  const counterRef = useRef(counter);  const onAlertButtonClick = () => {    setTimeout(() => {      alert("Value: " + counterRef.current);    }, 3000);  };  useEffect(() => {    counterRef.current = counter;  });  return (    <div>      <p>You clicked {counter} times.</p>      <button onClick={() => setCounter(counter + 1)}>Click me</button>      <button onClick={onAlertButtonClick}>        Show me the value in 3 seconds      </button>    </div>  );};export default Counter;

結果如我們所期待,alert的是當前的值1。

我們可以把這個過程封裝成一個custom hook,如下:

import { useEffect, useRef, useState } from "react";const useRefState = <T>(initialValue: T): [T, React.MutableRefObject<T>,    React.Dispatch<React.SetStateAction<T>>] => {  const [state, setState] = useState<T>(initialValue);  const stateRef = useRef(state);  useEffect(() => {    stateRef.current = state;  }, [state]);  return [state, stateRef, setState];};export default useRefState;

儘管這個問題被巧妙地解決了,但它不優雅、hack味道濃,且丟失了函式程式設計風格。

怎麼避免react hooks的常見問題不要在useEffect裡面寫太多的依賴項,劃分這些依賴項成多個單一功能的useEffect。其實這點是遵循了軟體設計的“單一職責模式”。如果你碰到狀態不同步的問題,可以考慮下手動傳遞引數到函式。如:
// showCount的count來自父級作用域 const [count,setCount] = useState(xxx); function showCount(){   console.log(count) } // showCount的count來自引數 const [count,setCount] = useState(xxx); function showCount(c){   console.log(c) }

但這個也只能解決一部分問題,很多時候你不得不使用上述的useRef方案。

重視eslint-plugin-react-hooks外掛的警告。複雜業務的時候,使用Component代替hooks。感想

目前,我通常更偏向於用hooks來寫元件,但在複雜業務中,我會更傾向於用class Component或者兩者結合的方式。hooks會是未來的主流元件編寫方式,但目前來說它還不成熟。

  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 想要打通Windows和Linux的任督二脈,少不了API這層功力