3分钟掌握hook在typescript中的姿势

  • 2020 年 1 月 14 日
  • 筆記

hook结合typescript可以说是很香了。本文主要介绍hook结合typescript 如何使用,享受ts带给我们的编辑器提示和类型约束

useState

useState如果初始值不是null/undefined的话,是具备类型推导能力的,根据传入的初始值推断出类型;初始值是 null/undefined的话则需要传递类型定义才能进行约束。一般情况下,还是推荐传入类型(通过useState的第一个泛型参数)。

// 这里ts可以推断 value的类型并且能对setValue函数调用进行约束  const [value, setValue] = useState(0);    interface MyObject {    foo: string;    bar?: number;  }    // 这里需要传递MyObject才能约束 value, setValue  // 一般情况下推荐传入类型  const [value, setValue] = useState<MyObject>(null);

useContext

useContext一般根据传入的Context的值就可以推断出返回值。不需要显示传递类型

type Theme = 'light' | 'dark';  // 我们在createContext就传了类型了  const ThemeContext = createContext<Theme>('dark');    const App = () => (    <ThemeContext.Provider value="dark">      <MyComponent />    </ThemeContext.Provider>  )    const MyComponent = () => {      // useContext根据ThemeContext推断出类型,这里不需要显示传    const theme = useContext(ThemeContext);    return <div>The theme is {theme}</div>;

useEffect useLayoutEffect

没有返回值,无需传递类型

useCallback useMemo

useMemo无需传递类型,根据函数的返回值就能推断出类型

useCallback无需传递类型,根据函数的返回值就能推断出类型。但是注意函数的入参需要定义类型,不然就是推断为any了!

const value = 10;  // 推断出result是number类型  const result = useMemo(() => value * 2, [value]);    const multiplier = 2;  // 推断出 (value: number) => number  // 注意函数入参value需要定义类型  const multiply = useCallback((value: number) => value * multiplier, [multiplier]);

useRef

useRef传非空初始值的时候可以推断类型,同样也可以通过传入第一个泛型参数来定义类型,约束ref.current的类型。

const MyInput = () => {    // 这里约束inputRef是一个html元素    const inputRef = useRef<HTMLInputElement>(null);    return <input ref={inputRef} />  }
// 自动推断出 myNumberRef.current 是number类型  const myNumberRef = useRef(0);  myNumberRef.current += 1;

useReducer

只需要对传入useReducer的reducer函数的入参stateaction进行类型约束就能够推断出来

interface State {    value: number;  }    type Action =    | { type: 'increment' }    | { type: 'decrement' }    | { type: 'incrementAmount'; amount: number };    const counterReducer = (state: State, action: Action) => {    switch (action.type) {      case 'increment':        return { value: state.value + 1 };      case 'decrement':        return { value: state.value - 1 };      case 'incrementAmount':        return { value: state.value + action.amount };      default:        throw new Error();    }  };    // 这里可以推断出state为State类型  const [state, dispatch] = useReducer(counterReducer, { value: 0 });    //能够约束传入dispatch的参数,符合Action类型  dispatch({ type: 'increment' });  dispatch({ type: 'decrement' });  dispatch({ type: 'incrementAmount', amount: 10 });    // TypeScript compilation error  dispatch({ type: 'invalidActionType' });

useImperativeHandle

useImperativeHandle一般比较少用,一般用来选择函数组件对外暴露ref属性被调用,需要配合forwardRef使用。

如下例子。需要定义对外暴露的接口MyInputHandles,函数组件使用React.RefForwardingComponent对外暴露的接口调用作为泛型参数。然后就会得到约束了

// MyInputHandles 需要给父组件的useRef作为类型使用 和 RefForwardingComponent作为泛型参数传入约束  export interface MyInputHandles {    focus(): void;  }    // 使用RefForwardingComponent 类型进行定义组件,第一个泛型参数是对外暴露的handle,第二个是Props  const MyInput: RefForwardingComponent<MyInputHandles, MyInputProps> = (    props,    ref  ) => {    const inputRef = useRef<HTMLInputElement>(null);      useImperativeHandle(ref, () => ({      // 这里的返回会自动使用MyInputHandles进行类型约束      focus: () => {        if (inputRef.current) {          inputRef.current.focus();        }      },    }));      return <input {...props} ref={inputRef} />;  };    // 函数组件必须使用forwardRef才能让外部组件使用该组件的ref  export default forwardRef(MyInput);
// 父组件  const Autofocus = () => {    // 能够约束 myInputRef.current的类型    const myInputRef = useRef<MyInputHandles>(null);      useEffect(() => {      if (myInputRef.current) {        myInputRef.current.focus();      }    });      return <MyInput ref={myInputRef} />  }

参考:

React Hooks in TypeScript