钩子语法
钩子语法是 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://reactjs.ac.cn/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 类型系统中。由于钩子和函数必须遵守不同的属性,因此将定义为钩子的值传递到期望函数类型的职位中是 Flow 错误,而将普通 JavaScript 函数传递到期望钩子的职位中也是错误。
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]