跳至主要內容

Hook 語法

Hook 語法是 React hook 的一級語法和類型檢查支援,將 hook 帶入 React 語言中,作為與一般函式在語法和語意上不同的實體,並使用 Flow 來強制執行 React 規則,避免違反這些規則。

基本用法

撰寫函式和 hook 之間的主要差異在於 hook 關鍵字

1import {useState, useEffect} from 'react';2
3hook useOnlineStatus(initial: boolean): boolean {4  const [isOnline, setIsOnline] = useState(initial);5  useEffect(() => {6    // ...7  }, []);8  return isOnline;9}

Hook 可以像一般函式一樣呼叫

1import * as React from 'react';2
3hook useOnlineStatus(): boolean {4    return true;5}6
7component StatusBar() {8  const isOnline = useOnlineStatus();9  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;10}

Hook 可以像一般函式一樣匯出

1export hook useNamedExportedHook(): boolean {2    return true;3}4
5export default hook useDefaultExportedHook(): boolean {6    return true;7}

Hook 類型註解

在某些情況下,您可能希望將值定義為 hook 的類型。由於函式類型和 hook 類型不相容(詳見下文!),我們也推出 hook 類型註解的新語法,這只不過是現有的函式類型註解,但前面加上 hook。

export const useGKOnlineStatus: hook (boolean) => boolean = 
experiment('show_online_status')
? useOnlineStatus
: useAlwaysOnlineStatus

使用 Hook 語法強制執行 React 規則

有了 hook 語法,我們現在可以在語法上明確區分 hook 和非 hook。Flow 會使用這些資訊來強制執行多項 hook 規則和 React 規則

防止不安全的變異

根據React 規則,在元件渲染時不允許讀取或寫入 ref,而且其他 hook(特別是 `useState`)的回傳值根本無法安全地直接變異。透過讓 Flow 將 hook 視為一級概念,我們現在可以在許多情況下偵測這些問題並及早提出錯誤,而不是依賴測試來找出它們。

1import {useState, useEffect, useRef} from 'react';2import * as React from 'react';3
4component MyComponent() { 5  const ref = useRef<?number>(null);6  const [state, setState] = useState<{ val: number }>({val: 0});7
8  state.val = 42; // Flow error: cannot mutate return value of hook
9 10 return (11 <div>12 {ref.current /* Flow error: cannot read ref during rendering */}
13 </div>14 );15}
8:9-8:11: Cannot assign `42` to `state.val` because property `val` is not writable. The return value of a React hook [1] cannot be written to. [react-rule-hook-mutation]
12:8-12:10: Cannot read `current` from `ref` [1] because `ref` values may not be read during render. (https://react.dev.org.tw/reference/react/useRef). [react-rule-unsafe-ref]

Flow 目前會防止元件屬性在元件內部被修改。hook 語法讓我們可以將此檢查擴充到 hook,並讓我們可以在 hook 宣告中發生非法變異時偵測並提出錯誤。

1hook useIllegalMutation(values: Array<number>) {2  values[0] = 42; // Flow error: mutating argument to hook
3 // ...4}
2:3-2:11: Cannot assign `42` to `values[0]` because read-only arrays cannot be written to. React hook arguments [1] and their nested elements cannot be written to. [react-rule-unsafe-mutation]

防止條件式 hook 呼叫

hook 規則禁止有條件地呼叫 hook。這由React 的 ESLint 外掛程式涵蓋,但現在 Flow 也會檢查這些違規情況。

1hook useOnlineStatus(): boolean {2    return true;3}4
5component StatusBar(shouldShowOnlineStatus: boolean) {6  if (shouldShowOnlineStatus) {7    const onlineStatus = useOnlineStatus();
8 }9 10 return null;11}
7:26-7:42: Cannot call hook [1] because React hooks cannot be called in conditional contexts. [react-rule-hook]

防止混淆 hook 和函式

hook 和一般函式之間的區別反映在 Flow 型別系統中。由於 hook 和函式必須遵守不同的屬性,因此將定義為 hook 的值傳遞到預期函式型別的位置,以及將一般 JavaScript 函式傳遞到預期 hook 的位置,都是 Flow 錯誤。

1import {useState, useEffect} from 'react';2
3hook useMultiplier(x: number): number {4  const [y, setY] = useState(1);5  useEffect(() => { setY(0) })6  return x * y;7}8
9component Mapper(args: Array<number>) {10  const multArgs = args.map(useMultiplier);
11 12 return multArgs;13}
10:29-10:41: Cannot call `args.map` with `useMultiplier` bound to `callbackfn` because function [1] is a React hook but function type [2] is not a hook. React hooks and other functions are not compatible with each other, because hooks cannot be called conditionally. [react-rule-hook-incompatible]

此外,Flow 強制執行 hook 和元件內部具有類似 hook 名稱的被呼叫者確實是 hook。我們也確保一般函式定義內的被呼叫者永遠不會是 hook。

1hook useHook() { return null }2
3function regularJavascript() {4  const x = useHook(); // Flow error: cannot call a hook outside of a component or hook
5}6 7component Component() { 8 const renamedHook = useHook;9 renamedHook(); // Flow error: cannot call a hook whose name does not begin with `use`
10 11 return null;12}
4:13-4:21: Cannot call hook [1] because React hooks can only be called within components or hooks. [react-rule-hook]
9:3-9:15: Cannot call hook because callee [1]'s name does not conform to React hook rules. Hook names must begin with `use` followed by a capitalized letter. [react-rule-hook]