メインコンテンツにスキップ

フック構文

フック構文は、Reactフックのためのファーストクラスの構文と型チェックサポートです。フックを、構文的および意味的に通常の関数とは異なる独自のエンティティとしてReact言語に取り込み、Flowを使用してReactのルールの違反がないことを強制します。

基本的な使い方

関数とフックを書く際の主な違いは、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}

フックは通常の関数と同じように呼び出すことができます。

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}

フックは通常の関数と同じようにエクスポートできます。

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

フック型注釈

値をフックの型として定義したい場合があります。関数型とフック型は互換性がないため(詳細は後述します)、フック型注釈の新しい構文も導入します。これは、既存の関数型注釈の前に`hook`を付けるだけです。

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

フック構文によるReactのルールの適用

フック構文を使用すると、フックと非フックを構文的に明確に区別できるようになりました。Flowはこの情報を使用して、フックのルールとReactのルール全般を強制します。

安全でない変更の防止

Reactのルールによると、コンポーネントのレンダリング中はrefの読み取りまたは書き込みは許可されておらず、他のフック(特に`useState`)の戻り値は直接安全に変更できません。Flowにフックをファーストクラスの概念として認識させることで、これらの問題を多くの場合に検出し、テストに頼ることなく早期にエラーを発生させることができます。

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.dokyumento.jp/reference/react/useRef). [react-rule-unsafe-ref]

Flowは現在、コンポーネント内でコンポーネントpropsが変更されるのを防いでいます。フック構文により、このチェックをフックに拡張することができ、フック宣言内で不正な変更が発生した場合にエラーを検出して発生させることができます。

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]

条件付きフック呼び出しの防止

フックのルールでは、フックを条件付きで呼び出すことを禁止しています。これは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]

フックと関数の混同の防止

フックと通常の関数の区別は、Flow型システムに反映されます。フックと関数が従わなければならないプロパティが異なるため、フックとして定義された値を関数型を期待する位置に渡すこと、および通常のJavaScript関数をフックを期待する位置に渡すことは、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は、フックとコンポーネント内のフックのような名前の呼び出し先が実際にフックであることを強制します。また、通常の関数定義内の呼び出し先がフックにならないようにします。

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]