Hooks
Hooks 在 `@types/react` 版本 16.8 及以上版本中得到支持 。
useState
类型推断对于简单值非常有效
const [state, setState] = useState(false);
// `state` is inferred to be a boolean
// `setState` only takes booleans
如果您需要使用一个依赖于推断的复杂类型,请参阅 使用推断类型 部分。
但是,许多 Hooks 使用 nullish 默认值初始化,您可能想知道如何提供类型。显式声明类型,并使用联合类型
const [user, setUser] = useState<User | null>(null);
// later...
setUser(newUser);
如果状态在设置后很快初始化并且之后始终具有值,您也可以使用类型断言
const [user, setUser] = useState<User>({} as User);
// later...
setUser(newUser);
这暂时“欺骗”TypeScript 编译器,使 `{}` 成为 `User` 类型。您应该继续设置 `user` 状态 - 如果您不这样做,您的其余代码可能会依赖于 `user` 是 `User` 类型的事实,这可能会导致运行时错误。
useCallback
您可以像任何其他函数一样为 `useCallback` 指定类型。
const memoizedCallback = useCallback(
(param1: string, param2: number) => {
console.log(param1, param2)
return { ok: true }
},
[...],
);
/**
* VSCode will show the following type:
* const memoizedCallback:
* (param1: string, param2: number) => { ok: boolean }
*/
请注意,对于 React < 18,`useCallback` 的函数签名默认将类型化参数作为 `any[]` 处理。
function useCallback<T extends (...args: any[]) => any>(
callback: T,
deps: DependencyList
): T;
在 React >= 18 中,`useCallback` 的函数签名已更改为以下内容
function useCallback<T extends Function>(callback: T, deps: DependencyList): T;
因此,以下代码将在 React >= 18 中产生“`Parameter 'e' implicitly has an 'any' type.`”错误,但在 <17 中不会。
// @ts-expect-error Parameter 'e' implicitly has 'any' type.
useCallback((e) => {}, []);
// Explicit 'any' type.
useCallback((e: any) => {}, []);
useReducer
您可以使用 区分联合 来处理 Reducer 操作。不要忘记定义 Reducer 的返回类型,否则 TypeScript 会推断它。
import { useReducer } from "react";
const initialState = { count: 0 };
type ACTIONTYPE =
| { type: "increment"; payload: number }
| { type: "decrement"; payload: string };
function reducer(state: typeof initialState, action: ACTIONTYPE) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - Number(action.payload) };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "decrement", payload: "5" })}>
-
</button>
<button onClick={() => dispatch({ type: "increment", payload: 5 })}>
+
</button>
</>
);
}
与来自 `redux` 的 `Reducer` 一起使用
如果您使用 redux 库编写 Reducer 函数,它提供了一个方便的 `Reducer<State, Action>` 格式的辅助函数,它会为您处理返回类型。
因此,上面的 Reducer 示例变为
import { Reducer } from 'redux';
export function reducer: Reducer<AppState, Action>() {}
useEffect / useLayoutEffect
`useEffect` 和 `useLayoutEffect` 都用于执行**副作用**,并返回一个可选的清理函数,这意味着如果它们不处理返回值,则不需要类型。在使用 `useEffect` 时,注意不要返回除函数或 `undefined` 之外的任何内容,否则 TypeScript 和 React 都会报错。在使用箭头函数时,这可能会很微妙
function DelayedEffect(props: { timerMs: number }) {
const { timerMs } = props;
useEffect(
() =>
setTimeout(() => {
/* do stuff */
}, timerMs),
[timerMs]
);
// bad example! setTimeout implicitly returns a number
// because the arrow function body isn't wrapped in curly braces
return null;
}
上面示例的解决方案
function DelayedEffect(props: { timerMs: number }) {
const { timerMs } = props;
useEffect(() => {
setTimeout(() => {
/* do stuff */
}, timerMs);
}, [timerMs]);
// better; use the void keyword to make sure you return undefined
return null;
}
useRef
在 TypeScript 中,`useRef` 返回一个引用,该引用可以是 只读 或 可变 的,这取决于您的类型参数是否完全涵盖初始值。选择适合您用例的一个。
选项 1:DOM 元素 ref
访问 DOM 元素:仅提供元素类型作为参数,并使用 `null` 作为初始值。在这种情况下,返回的引用将具有一个由 React 管理的只读 `.current`。TypeScript 期望您将此 ref 提供给元素的 `ref` 属性
function Foo() {
// - If possible, prefer as specific as possible. For example, HTMLDivElement
// is better than HTMLElement and way better than Element.
// - Technical-wise, this returns RefObject<HTMLDivElement>
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Note that ref.current may be null. This is expected, because you may
// conditionally render the ref-ed element, or you may forget to assign it
if (!divRef.current) throw Error("divRef is not assigned");
// Now divRef.current is sure to be HTMLDivElement
doSomethingWith(divRef.current);
});
// Give the ref to an element so React can manage it for you
return <div ref={divRef}>etc</div>;
}
如果您确定 `divRef.current` 永远不会为 null,也可以使用非空断言运算符 `!`
const divRef = useRef<HTMLDivElement>(null!);
// Later... No need to check if it is null
doSomethingWith(divRef.current);
请注意,您在此处放弃了类型安全性 - 如果您忘记在渲染中将 ref 分配给元素,或者如果 ref-ed 元素是条件渲染的,您将遇到运行时错误。
提示:选择使用哪个 `HTMLElement`
选项 2:可变值 ref
拥有可变值:提供您想要的类型,并确保初始值完全属于该类型
function Foo() {
// Technical-wise, this returns MutableRefObject<number | null>
const intervalRef = useRef<number | null>(null);
// You manage the ref yourself (that's why it's called MutableRefObject!)
useEffect(() => {
intervalRef.current = setInterval(...);
return () => clearInterval(intervalRef.current);
}, []);
// The ref is not passed to any element's "ref" prop
return <button onClick={/* clearInterval the ref */}>Cancel timer</button>;
}
另请参阅
useImperativeHandle
基于此 Stackoverflow 答案
// Countdown.tsx
// Define the handle types which will be passed to the forwardRef
export type CountdownHandle = {
start: () => void;
};
type CountdownProps = {};
const Countdown = forwardRef<CountdownHandle, CountdownProps>((props, ref) => {
useImperativeHandle(ref, () => ({
// start() has type inference here
start() {
alert("Start");
},
}));
return <div>Countdown</div>;
});
// The component uses the Countdown component
import Countdown, { CountdownHandle } from "./Countdown.tsx";
function App() {
const countdownEl = useRef<CountdownHandle>(null);
useEffect(() => {
if (countdownEl.current) {
// start() has type inference here as well
countdownEl.current.start();
}
}, []);
return <Countdown ref={countdownEl} />;
}
另请参阅:
自定义 Hooks
如果您在自定义 Hook 中返回一个数组,您将希望避免类型推断,因为 TypeScript 会推断出一个联合类型(当您实际上想要数组每个位置的不同类型时)。相反,请使用 TS 3.4 常量断言
import { useState } from "react";
export function useLoading() {
const [isLoading, setState] = useState(false);
const load = (aPromise: Promise<any>) => {
setState(true);
return aPromise.finally(() => setState(false));
};
return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[]
}
这样,当您解构时,您实际上会根据解构位置获得正确的类型。
替代方案:断言元组返回类型
如果您遇到 常量断言问题,您也可以断言或定义函数返回类型
import { useState } from "react";
export function useLoading() {
const [isLoading, setState] = useState(false);
const load = (aPromise: Promise<any>) => {
setState(true);
return aPromise.finally(() => setState(false));
};
return [isLoading, load] as [
boolean,
(aPromise: Promise<any>) => Promise<any>
];
}
如果您编写了许多自定义 Hook,一个自动为元组指定类型的辅助函数也会很有帮助
function tuplify<T extends any[]>(...elements: T) {
return elements;
}
function useArray() {
const numberValue = useRef(3).current;
const functionValue = useRef(() => {}).current;
return [numberValue, functionValue]; // type is (number | (() => void))[]
}
function useTuple() {
const numberValue = useRef(3).current;
const functionValue = useRef(() => {}).current;
return tuplify(numberValue, functionValue); // type is [number, () => void]
}
但是,请注意,React 团队建议返回两个以上值的自定义 Hook 应该使用合适的对象而不是元组。
更多 Hooks + TypeScript 阅读:
- https://medium.com/@jrwebdev/react-hooks-in-typescript-88fce7001d0d
- https://fettblog.eu/typescript-react/hooks/#useref
如果您正在编写一个 React Hooks 库,请不要忘记您还应该公开您的类型以供用户使用。