三、React Hooks
1. useState
默认情况下,React会为根据设置的state的初始值来自动推导state以及更新函数的类型:
如果已知state 的类型,可以通过以下形式来自定义state的类型:
const [count, setCount] = useState<number>(1)
复制代码
如果初始值为null,需要显式地声明 state 的类型:
const [count, setCount] = useState<number | null>(null);
复制代码
如果state是一个对象,想要初始化一个空对象,可以使用断言来处理:
const [user, setUser] = React.useState<IUser>({} as IUser);
复制代码
实际上,这里将空对象{}断言为IUser接口就是欺骗了TypeScript的编译器,由于后面的代码可能会依赖这个对象,所以应该在使用前及时初始化 user 的值,否则就会报错。
下面是声明文件中 useState 的定义:
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
// convenience overload when first argument is omitted
* Returns a stateful value, and a function to update it.
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usestate
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
* An alternative to `useState`.
* `useReducer` is usually preferable to `useState` when you have complex state logic that involves
* multiple sub-values. It also lets you optimize performance for components that trigger deep
* updates because you can pass `dispatch` down instead of callbacks.
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usereducer
复制代码
可以看到,这里定义两种形式,分别是有初始值和没有初始值的形式。
2. useEffect
useEffect的主要作用就是处理副作用,它的第一个参数是一个函数,表示要清除副作用的操作,第二个参数是一组值,当这组值改变时,第一个参数的函数才会执行,这让我们可以控制何时运行函数来处理副作用:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
[props.source]
复制代码
当函数的返回值不是函数或者effect函数中未定义的内容时,如下:
useEffect(
() => {
subscribe();
return null;
复制代码
TypeScript就会报错:
来看看useEffect在类型声明文件中的定义:
// Destructors are only allowed to return void.
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);
// TODO (TypeScript 3.0): ReadonlyArray<unknown>
type DependencyList = ReadonlyArray<any>;
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
// NOTE: this does not accept strings, but this will have to be fixed by removing strings from type Ref<T>
* `useImperativeHandle` customizes the instance value that is exposed to parent components when using
* `ref`. As always, imperative code using refs should be avoided in most cases.
* `useImperativeHandle` should be used with `React.forwardRef`.
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useimperativehandle
复制代码
可以看到,useEffect的第一个参数只允许返回一个函数。
3. useRef
当使用 useRef 时,我们可以访问一个可变的引用对象。可以将初始值传递给 useRef,它用于初始化可变 ref 对象公开的当前属性。当我们使用useRef时,需要给其指定类型:
const nameInput = React.useRef<HTMLInputElement>(null)
复制代码
这里给实例的类型指定为了input输入框类型。
当useRef的初始值为null时,有两种创建的形式,第一种:
const nameInput = React.useRef<HTMLInputElement>(null)
nameInput.current.innerText = "hello world";
复制代码
这种形式下,ref1.current是只读的(read-only),所以当我们将它的innerText属性重新赋值时会报以下错误:
Cannot assign to 'current' because it is a read-only property.
复制代码
那该怎么将current属性变为动态可变得的,先来看看类型声明文件中 useRef 是如何定义的:
function useRef<T>(initialValue: T): MutableRefObject<T>;
// convenience overload for refs given as a ref prop as they typically start with a null value
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
* (`initialValue`). The returned object will persist for the full lifetime of the component.
* Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
* value around similar to how you’d use instance fields in classes.
* Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type
* of the generic argument.
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useref
复制代码
这段代码的第十行的告诉我们,如果需要useRef的直接可变,就需要在泛型参数中包含'| null',所以这就是当初始值为null的第二种定义形式:
const nameInput = React.useRef<HTMLInputElement | null>(null);
复制代码
这种形式下,nameInput.current就是可写的。不过两种类型在使用时都需要做类型检查:
nameInput.current?.innerText = "hello world";
复制代码
那么问题来了,为什么第一种写法在没有操作current时没有报错呢?因为useRef在类型定义式具有多个重载声明,第一种方式就是执行的以下函数重载:
function useRef<T>(initialValue: T|null): RefObject<T>;
// convenience overload for potentially undefined initialValue / call with 0 arguments
// has a default to stop it from defaulting to {} instead
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
* (`initialValue`). The returned object will persist for the full lifetime of the component.
* Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
* value around similar to how you’d use instance fields in classes.
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useref
复制代码
从上useRef的声明中可以看到,function useRef的返回值类型化是MutableRefObject,这里面的T就是参数的类型T,所以最终nameInput 的类型就是React.MutableRefObject。
注意,上面用到了HTMLInputElement类型,这是一个标签类型,这个操作就是用来访问DOM元素的。
4. useCallback
先来看看类型声明文件中对useCallback的定义:
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
* `useMemo` will only recompute the memoized value when one of the `deps` has changed.
* Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in
* the second argument.
* ```ts
* function expensive () { ... }
* function Component () {
* const expensiveResult = useMemo(expensive, [expensive])
* return ...
* ```
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usememo
复制代码
useCallback接收一个回调函数和一个依赖数组,只有当依赖数组中的值发生变化时才会重新执行回调函数。来看一个例子:
const add = (a: number, b: number) => a + b;
const memoizedCallback = useCallback(
(a) => {
add(a, b);
复制代码
这里我们没有给回调函数中的参数a定义类型,所以下面的调用方式都不会报错:
memoizedCallback("hello");
memoizedCallback(5)
复制代码
尽管add方法的两个参数都是number类型,但是上述调用都能够用执行。所以为了更加严谨,我们需要给回调函数定义具体的类型:
const memoizedCallback = useCallback(
(a: number) => {
add(a, b);
复制代码
这时候如果再给回调函数传入字符串就会报错了:
所有,需要注意,在使用useCallback时需要给回调函数的参数指定类型。
5. useMemo
先来看看类型声明文件中对useMemo的定义:
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
* `useDebugValue` can be used to display a label for custom hooks in React DevTools.
* NOTE: We don’t recommend adding debug values to every custom hook.
* It’s most valuable for custom hooks that are part of shared libraries.
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usedebugvalue
复制代码
useMemo和useCallback是非常类似的,但是它返回的是一个值,而不是函数。所以在定义useMemo时需要定义返回值的类型:
let a = 1;
setTimeout(() => {
a += 1;
}, 1000);
const calculatedValue = useMemo<number>(() => a ** 2, [a]);
复制代码
如果返回值不一致,就会报错:
const calculatedValue = useMemo<number>(() => a + "hello", [a]);
// 类型“() => string”的参数不能赋给类型“() => number”的参数
复制代码
6. useContext
useContext需要提供一个上下文对象,并返回所提供的上下文的值,当提供者更新上下文对象时,引用这些上下文对象的组件就会重新渲染:
const ColorContext = React.createContext({ color: "green" });
const Welcome = () => {
const { color } = useContext(ColorContext);
return <div style={{ color }}>hello world</div>;
复制代码
在使用useContext时,会自动推断出提供的上下文对象的类型,所以并不需要我们手动设置context的类型。当前,我们也可以使用泛型来设置context的类型:
interface IColor {
color: string;
const ColorContext = React.createContext<IColor>({ color: "green" });
复制代码
下面是useContext在类型声明文件中的定义:
function useContext<T>(context: Context<T>/*, (not public API) observedBits?: number|boolean */): T;
* Returns a stateful value, and a function to update it.
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usestate
复制代码
7. useReducer
有时我们需要处理一些复杂的状态,并且可能取决于之前的状态。这时候就可以使用useReducer,它接收一个函数,这个函数会根据之前的状态来计算一个新的state。其语法如下:
const [state, dispatch] = useReducer(reducer, initialArg, init);
复制代码
来看下面的例子:
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
const Counter = () => {
const initialState = {count: 0}
const [state, dispatch] = useReducer(reducer, initialState);
return (
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
复制代码
当前的状态是无法推断出来的,可以给reducer函数添加类型,通过给reducer函数定义state和action来推断 useReducer 的类型,下面来修改上面的例子:
type ActionType = {
type: 'increment' | 'decrement';
type State = { count: number };
const initialState: State = {count: 0}
const reducer = (state: State, action: ActionType) => {
// ...
复制代码
这样,在Counter函数中就可以推断出类型。当我们视图使用一个不存在的类型时,就会报错:
dispatch({type: 'reset'});
// Error! type '"reset"' is not assignable to type '"increment" | "decrement"'
复制代码
除此之外,还可以使用泛型的形式来实现reducer函数的类型定义:
type ActionType = {
type: 'increment' | 'decrement';
type State = { count: number };
const reducer: React.Reducer<State, ActionType> = (state, action) => {
// ...
复制代码
其实dispatch方法也是有类型的:
可以看到,dispatch的类型是:React.Dispatch,上面示例的完整代码如下:
import React, { useReducer } from "react";
type ActionType = {
type: "increment" | "decrement";
type State = { count: number };
const Counter: React.FC = () => {
const reducer: React.Reducer<State, ActionType> = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
const initialState: State = {count: 0}
const [state, dispatch] = useReducer(reducer, initialState);
return (
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
export default Counter;