3.精通React-如何优雅地在React中使用TypeScript?-《前端知识进阶》

admin 2025-11-01 15:31:08 编程 来源:ZONE.CI 全球网 0 阅读模式
  • 一、组件声明
    • 1. 类组件
    • 2. 函数组件
  • 二、React内置类型
    • 1. JSX.Element
    • 2. React.ReactElement
    • 3. React.ReactNode
    • 4. CSSProperties
  • 三、React Hooks
    • 1. useState
    • 2. useEffect
    • 3. useRef
    • 4. useCallback
    • 5. useMemo
    • 6. useContext
    • 7. useReducer
  • 四、事件处理
    • 1. Event 事件类型
    • 2. 事件处理函数类型
  • 五、HTML标签类型
    • 1. 常见标签类型
    • 2. 标签属性类型
  • 六、工具泛型
    • 1. Partial
    • 2. Required
    • 3. Readonly
    • 4. Pick
    • 5. Record
    • 6. Exclude
    • 7. Omit
    • 8. ReturnType
  • 七、其他
    • 1. import React
    • 2. Types or Interfaces?
    • 3. 懒加载类型
    • 4. 类型断言
    • 5. 枚举类型

    一、组件声明

    在React中,组件的声明方式有两种:函数组件类组件,来看看这两种类型的组件声明时是如何定义TS类型的。

    1. 类组件

    类组件的定义形式有两种:React.Component<P, S={}>React.PureComponent<P, S={} SS={}>,它们都是泛型接口,接收两个参数,第一个是props类型的定义,第二个是state类型的定义,这两个参数都不是必须的,没有时可以省略:

    1. interface IProps {
    2. name: string;
    3. }
    4. interface IState {
    5. count: number;
    6. }
    7. class App extends React.Component<IProps, IState> {
    8. state = {
    9. count: 0
    10. };
    11. render() {
    12. return (
    13. <div>
    14. {this.state.count}
    15. {this.props.name}
    16. </div>
    17. );
    18. }
    19. }
    20. export default App;

    React.PureComponent<P, S={} SS={}> 也是差不多的:

    1. class App extends React.PureComponent<IProps, IState> {}

    React.PureComponent是有第三个参数的,它表示getSnapshotBeforeUpdate的返回值。

    那PureComponent和Component 的区别是什么呢?它们的主要区别是PureComponent中的shouldComponentUpdate 是由自身进行处理的,不需要我们自己处理,所以PureComponent可以在一定程度上提升性能。

    有时候可能会见到这种写法,实际上和上面的效果是一样的:

    1. import React, {PureComponent, Component} from "react";
    2. class App extends PureComponent<IProps, IState> {}
    3. class App extends Component<IProps, IState> {}

    那如果定义时候我们不知道组件的props的类型,只有在调用时才知道组件类型,该怎么办呢?这时泛型就发挥作用了:

    1. // 定义组件
    2. class MyComponent<P> extends React.Component<P> {
    3. internalProp: P;
    4. constructor(props: P) {
    5. super(props);
    6. this.internalProp = props;
    7. }
    8. render() {
    9. return (
    10. <span>hello world</span>
    11. );
    12. }
    13. }
    14. // 使用组件
    15. type IProps = { name: string; age: number; };
    16. <MyComponent<IProps> name="React" age={18} />; // Success
    17. <MyComponent<IProps> name="TypeScript" age="hello" />; // Error

    2. 函数组件

    通常情况下,函数组件我是这样写的:

    1. interface IProps {
    2. name: string
    3. }
    4. const App = (props: IProps) => {
    5. const {name} = props;
    6. return (
    7. <div className="App">
    8. <h1>hello world</h1>
    9. <h2>{name}</h2>
    10. </div>
    11. );
    12. }
    13. export default App;

    除此之外,函数类型还可以使用React.FunctionComponent<P={}>来定义,也可以使用其简写React.FC<P={}>,两者效果是一样的。它是一个泛型接口,可以接收一个参数,参数表示props的类型,这个参数不是必须的。它们就相当于这样:

    1. type React.FC<P = {}> = React.FunctionComponent<P>

    最终的定义形式如下:

    1. interface IProps {
    2. name: string
    3. }
    4. const App: React.FC<IProps> = (props) => {
    5. const {name} = props;
    6. return (
    7. <div className="App">
    8. <h1>hello world</h1>
    9. <h2>{name}</h2>
    10. </div>
    11. );
    12. }
    13. export default App;

    当使用这种形式来定义函数组件时,props中默认会带有children属性,它表示该组件在调用时,其内部的元素,来看一个例子,首先定义一个组件,组件中引入了Child1和Child2组件:

    1. import Child1 from "./child1";
    2. import Child2 from "./child2";
    3. interface IProps {
    4. name: string;
    5. }
    6. const App: React.FC<IProps> = (props) => {
    7. const { name } = props;
    8. return (
    9. <Child1 name={name}>
    10. <Child2 name={name} />
    11. TypeScript
    12. </Child1>
    13. );
    14. };
    15. export default App;

    Child1组件结构如下:

    1. interface IProps {
    2. name: string;
    3. }
    4. const Child1: React.FC<IProps> = (props) => {
    5. const { name, children } = props;
    6. console.log(children);
    7. return (
    8. <div className="App">
    9. <h1>hello child1</h1>
    10. <h2>{name}</h2>
    11. </div>
    12. );
    13. };
    14. export default Child1;

    我们在Child1组件中打印了children属性,它的值是一个数组,包含Child2对象和后面的文本:image.png使用 React.FC 声明函数组件和普通声明的区别如下:

    • React.FC 显式地定义了返回类型,其他方式是隐式推导的;
    • React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全;
    • React.FC 为 children 提供了隐式的类型(ReactElement | null)。

    那如果我们在定义组件时不知道props的类型,只有调用时才知道,那就还是用泛型来定义props的类型。对于使用function定义的函数组件:

    1. // 定义组件
    2. function MyComponent<P>(props: P) {
    3. return (
    4. <span>
    5. {props}
    6. </span>
    7. );
    8. }
    9. // 使用组件
    10. type IProps = { name: string; age: number; };
    11. <MyComponent<IProps> name="React" age={18} />; // Success
    12. <MyComponent<IProps> name="TypeScript" age="hello" />; // Error

    如果使用箭头函数定义的函数组件,直接这样调用时错误的:

    1. const MyComponent = <P>(props: P) {
    2. return (
    3. <span>
    4. {props}
    5. </span>
    6. );
    7. }

    必须使用extends关键字来定义泛型参数才能被成功解析:

    1. const MyComponent = <P extends any>(props: P) {
    2. return (
    3. <span>
    4. {props}
    5. </span>
    6. );
    7. }

    二、React内置类型

    1. JSX.Element

    先来看看JSX.Element类型的声明:

    1. declare global {
    2. namespace JSX {
    3. interface Element extends React.ReactElement<any, any> { }
    4. }
    5. }

    可以看到,JSX.Element是ReactElement的子类型,它没有增加属性,两者是等价的。也就是说两种类型的变量可以相互赋值。

    JSX.Element 可以通过执行 React.createElement 或是转译 JSX 获得:

    1. const jsx = <div>hello</div>
    2. const ele = React.createElement("div", null, "hello");

    2. React.ReactElement

    React 的类型声明文件中提供了 React.ReactElement<T>,它可以让我们通过传入<T/>来注解类组件的实例化,它在声明文件中的定义如下:

    1. interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    2. type: T;
    3. props: P;
    4. key: Key | null;
    5. }

    ReactElement是一个接口,包含type,props,key三个属性值。该类型的变量值只能是两种: null 和 ReactElement实例。

    通常情况下,函数组件返回ReactElement(JXS.Element)的值。

    3. React.ReactNode

    ReactNode类型的声明如下:

    1. type ReactText = string | number;
    2. type ReactChild = ReactElement | ReactText;
    3. interface ReactNodeArray extends Array<ReactNode> {}
    4. type ReactFragment = {} | ReactNodeArray;
    5. type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

    可以看到,ReactNode是一个联合类型,它可以是string、number、ReactElement、null、boolean、ReactNodeArray。由此可知。ReactElement类型的变量可以直接赋值给ReactNode类型的变量,但反过来是不行的。

    类组件的 render 成员函数会返回 ReactNode 类型的值:

    1. class MyComponent extends React.Component {
    2. render() {
    3. return <div>hello world</div>
    4. }
    5. }
    6. // 正确
    7. const component: React.ReactNode<MyComponent> = <MyComponent />;
    8. // 错误
    9. const component: React.ReactNode<MyComponent> = <OtherComponent />;

    上面的代码中,给component变量设置了类型是Mycomponent类型的react实例,这时只能给其赋值其为MyComponent的实例组件。

    通常情况下,类组件通过 render() 返回 ReactNode的值。

    4. CSSProperties

    先来看看React的声明文件中对CSSProperties 的定义:

    1. export interface CSSProperties extends CSS.Properties<string | number> {
    2. /**
    3. * The index signature was removed to enable closed typing for style
    4. * using CSSType. You're able to use type assertion or module augmentation
    5. * to add properties or an index signature of your own.
    6. *
    7. * For examples and more information, visit:
    8. * https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors
    9. */
    10. }

    React.CSSProperties是React基于TypeScript定义的CSS属性类型,可以将一个方法的返回值设置为该类型:

    1. import * as React from "react";
    2. const classNames = require("./sidebar.css");
    3. interface Props {
    4. isVisible: boolean;
    5. }
    6. const divStyle = (props: Props): React.CSSProperties => ({
    7. width: props.isVisible ? "23rem" : "0rem"
    8. });
    9. export const SidebarComponent: React.StatelessComponent<Props> = props => (
    10. <div id="mySidenav" className={classNames.sidenav} style={divStyle(props)}>
    11. {props.children}
    12. </div>
    13. );

    这里divStyle组件的返回值就是React.CSSProperties类型。

    我们还可以定义一个CSSProperties类型的变量:

    1. const divStyle: React.CSSProperties = {
    2. width: "11rem",
    3. height: "7rem",
    4. backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})`
    5. };

    这个变量可以在HTML标签的style属性上使用:

    1. <div style={divStyle} />

    在React的类型声明文件中,style属性的类型如下:

    1. style?: CSSProperties | undefined;

    三、React Hooks

    1. useState

    默认情况下,React会为根据设置的state的初始值来自动推导state以及更新函数的类型:image.png如果已知state 的类型,可以通过以下形式来自定义state的类型:

    1. const [count, setCount] = useState<number>(1)

    如果初始值为null,需要显式地声明 state 的类型:

    1. const [count, setCount] = useState<number | null>(null);

    如果state是一个对象,想要初始化一个空对象,可以使用断言来处理:

    1. const [user, setUser] = React.useState<IUser>({} as IUser);

    实际上,这里将空对象{}断言为IUser接口就是欺骗了TypeScript的编译器,由于后面的代码可能会依赖这个对象,所以应该在使用前及时初始化 user 的值,否则就会报错。

    下面是声明文件中 useState 的定义:

    1. function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
    2. // convenience overload when first argument is omitted
    3. /**
    4. * Returns a stateful value, and a function to update it.
    5. *
    6. * @version 16.8.0
    7. * @see https://reactjs.org/docs/hooks-reference.html#usestate
    8. */
    9. function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
    10. /**
    11. * An alternative to `useState`.
    12. *
    13. * `useReducer` is usually preferable to `useState` when you have complex state logic that involves
    14. * multiple sub-values. It also lets you optimize performance for components that trigger deep
    15. * updates because you can pass `dispatch` down instead of callbacks.
    16. *
    17. * @version 16.8.0
    18. * @see https://reactjs.org/docs/hooks-reference.html#usereducer
    19. */

    可以看到,这里定义两种形式,分别是有初始值和没有初始值的形式。

    2. useEffect

    useEffect的主要作用就是处理副作用,它的第一个参数是一个函数,表示要清除副作用的操作,第二个参数是一组值,当这组值改变时,第一个参数的函数才会执行,这让我们可以控制何时运行函数来处理副作用:

    1. useEffect(
    2. () => {
    3. const subscription = props.source.subscribe();
    4. return () => {
    5. subscription.unsubscribe();
    6. };
    7. },
    8. [props.source]
    9. );

    当函数的返回值不是函数或者effect函数中未定义的内容时,如下:

    1. useEffect(
    2. () => {
    3. subscribe();
    4. return null;
    5. }
    6. );

    TypeScript就会报错:image.png来看看useEffect在类型声明文件中的定义:

    1. // Destructors are only allowed to return void.
    2. type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
    3. // NOTE: callbacks are _only_ allowed to return either void, or a destructor.
    4. type EffectCallback = () => (void | Destructor);
    5. // TODO (TypeScript 3.0): ReadonlyArray<unknown>
    6. type DependencyList = ReadonlyArray<any>;
    7. function useEffect(effect: EffectCallback, deps?: DependencyList): void;
    8. // NOTE: this does not accept strings, but this will have to be fixed by removing strings from type Ref<T>
    9. /**
    10. * `useImperativeHandle` customizes the instance value that is exposed to parent components when using
    11. * `ref`. As always, imperative code using refs should be avoided in most cases.
    12. *
    13. * `useImperativeHandle` should be used with `React.forwardRef`.
    14. *
    15. * @version 16.8.0
    16. * @see https://reactjs.org/docs/hooks-reference.html#useimperativehandle
    17. */

    可以看到,useEffect的第一个参数只允许返回一个函数。

    3. useRef

    当使用 useRef 时,我们可以访问一个可变的引用对象。可以将初始值传递给 useRef,它用于初始化可变 ref 对象公开的当前属性。当我们使用useRef时,需要给其指定类型:

    1. const nameInput = React.useRef<HTMLInputElement>(null)

    这里给实例的类型指定为了input输入框类型。

    当useRef的初始值为null时,有两种创建的形式,第一种:

    1. const nameInput = React.useRef<HTMLInputElement>(null)
    2. nameInput.current.innerText = "hello world";

    这种形式下,ref1.current是只读的(read-only),所以当我们将它的innerText属性重新赋值时会报以下错误:

    1. Cannot assign to 'current' because it is a read-only property.

    那该怎么将current属性变为动态可变得的,先来看看类型声明文件中 useRef 是如何定义的:

    1. function useRef<T>(initialValue: T): MutableRefObject<T>;
    2. // convenience overload for refs given as a ref prop as they typically start with a null value
    3. /**
    4. * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
    5. * (`initialValue`). The returned object will persist for the full lifetime of the component.
    6. *
    7. * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
    8. * value around similar to how you’d use instance fields in classes.
    9. *
    10. * Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type
    11. * of the generic argument.
    12. *
    13. * @version 16.8.0
    14. * @see https://reactjs.org/docs/hooks-reference.html#useref
    15. */

    这段代码的第十行的告诉我们,如果需要useRef的直接可变,就需要在泛型参数中包含’| null’,所以这就是当初始值为null的第二种定义形式:

    1. const nameInput = React.useRef<HTMLInputElement | null>(null);

    这种形式下,nameInput.current就是可写的。不过两种类型在使用时都需要做类型检查:

    1. nameInput.current?.innerText = "hello world";

    那么问题来了,为什么第一种写法在没有操作current时没有报错呢?因为useRef在类型定义式具有多个重载声明,第一种方式就是执行的以下函数重载:

    1. function useRef<T>(initialValue: T|null): RefObject<T>;
    2. // convenience overload for potentially undefined initialValue / call with 0 arguments
    3. // has a default to stop it from defaulting to {} instead
    4. /**
    5. * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
    6. * (`initialValue`). The returned object will persist for the full lifetime of the component.
    7. *
    8. * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
    9. * value around similar to how you’d use instance fields in classes.
    10. *
    11. * @version 16.8.0
    12. * @see https://reactjs.org/docs/hooks-reference.html#useref
    13. */

    从上useRef的声明中可以看到,function useRef的返回值类型化是MutableRefObject,这里面的T就是参数的类型T,所以最终nameInput 的类型就是React.MutableRefObject。

    注意,上面用到了HTMLInputElement类型,这是一个标签类型,这个操作就是用来访问DOM元素的。

    4. useCallback

    先来看看类型声明文件中对useCallback的定义:

    1. function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
    2. /**
    3. * `useMemo` will only recompute the memoized value when one of the `deps` has changed.
    4. *
    5. * Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in
    6. * the second argument.
    7. *
    8. * ```ts
    9. * function expensive () { ... }
    10. *
    11. * function Component () {
    12. * const expensiveResult = useMemo(expensive, [expensive])
    13. * return ...
    14. * }
    15. *

    *

    • @version 16.8.0
    • @see https://reactjs.org/docs/hooks-reference.html#usememo */ useCallback接收一个回调函数和一个依赖数组,只有当依赖数组中的值发生变化时才会重新执行回调函数。来看一个例子:typescript const add = (a: number, b: number) => a + b;

    const memoizedCallback = useCallback( (a) => { add(a, b); }, [b] );

    1. 这里我们没有给回调函数中的参数a定义类型,所以下面的调用方式都不会报错:
    2. ```typescript
    3. memoizedCallback("hello");
    4. memoizedCallback(5)

    尽管add方法的两个参数都是number类型,但是上述调用都能够用执行。所以为了更加严谨,我们需要给回调函数定义具体的类型:

    1. const memoizedCallback = useCallback(
    2. (a: number) => {
    3. add(a, b);
    4. },
    5. [b]
    6. );

    这时候如果再给回调函数传入字符串就会报错了:image.png所有,需要注意,在使用useCallback时需要给回调函数的参数指定类型。

    5. useMemo

    先来看看类型声明文件中对useMemo的定义:

    1. function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
    2. /**
    3. * `useDebugValue` can be used to display a label for custom hooks in React DevTools.
    4. *
    5. * NOTE: We don’t recommend adding debug values to every custom hook.
    6. * It’s most valuable for custom hooks that are part of shared libraries.
    7. *
    8. * @version 16.8.0
    9. * @see https://reactjs.org/docs/hooks-reference.html#usedebugvalue
    10. */

    useMemo和useCallback是非常类似的,但是它返回的是一个值,而不是函数。所以在定义useMemo时需要定义返回值的类型:

    1. let a = 1;
    2. setTimeout(() => {
    3. a += 1;
    4. }, 1000);
    5. const calculatedValue = useMemo<number>(() => a ** 2, [a]);

    如果返回值不一致,就会报错:

    1. const calculatedValue = useMemo<number>(() => a + "hello", [a]);
    2. // 类型“() => string”的参数不能赋给类型“() => number”的参数

    6. useContext

    useContext需要提供一个上下文对象,并返回所提供的上下文的值,当提供者更新上下文对象时,引用这些上下文对象的组件就会重新渲染:

    1. const ColorContext = React.createContext({ color: "green" });
    2. const Welcome = () => {
    3. const { color } = useContext(ColorContext);
    4. return <div style={{ color }}>hello world</div>;
    5. };

    在使用useContext时,会自动推断出提供的上下文对象的类型,所以并不需要我们手动设置context的类型。当前,我们也可以使用泛型来设置context的类型:

    1. interface IColor {
    2. color: string;
    3. }
    4. const ColorContext = React.createContext<IColor>({ color: "green" });

    下面是useContext在类型声明文件中的定义:

    1. function useContext<T>(context: Context<T>/*, (not public API) observedBits?: number|boolean */): T;
    2. /**
    3. * Returns a stateful value, and a function to update it.
    4. *
    5. * @version 16.8.0
    6. * @see https://reactjs.org/docs/hooks-reference.html#usestate
    7. */

    7. useReducer

    有时我们需要处理一些复杂的状态,并且可能取决于之前的状态。这时候就可以使用useReducer,它接收一个函数,这个函数会根据之前的状态来计算一个新的state。其语法如下:

    1. const [state, dispatch] = useReducer(reducer, initialArg, init);

    来看下面的例子:

    1. const reducer = (state, action) => {
    2. switch (action.type) {
    3. case 'increment':
    4. return {count: state.count + 1};
    5. case 'decrement':
    6. return {count: state.count - 1};
    7. default:
    8. throw new Error();
    9. }
    10. }
    11. const Counter = () => {
    12. const initialState = {count: 0}
    13. const [state, dispatch] = useReducer(reducer, initialState);
    14. return (
    15. <>
    16. Count: {state.count}
    17. <button onClick={() => dispatch({type: 'increment'})}>+</button>
    18. <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    19. </>
    20. );
    21. }

    当前的状态是无法推断出来的,可以给reducer函数添加类型,通过给reducer函数定义state和action来推断 useReducer 的类型,下面来修改上面的例子:

    1. type ActionType = {
    2. type: 'increment' | 'decrement';
    3. };
    4. type State = { count: number };
    5. const initialState: State = {count: 0}
    6. const reducer = (state: State, action: ActionType) => {
    7. // ...
    8. }

    这样,在Counter函数中就可以推断出类型。当我们视图使用一个不存在的类型时,就会报错:

    1. dispatch({type: 'reset'});
    2. // Error! type '"reset"' is not assignable to type '"increment" | "decrement"'

    除此之外,还可以使用泛型的形式来实现reducer函数的类型定义:

    1. type ActionType = {
    2. type: 'increment' | 'decrement';
    3. };
    4. type State = { count: number };
    5. const reducer: React.Reducer<State, ActionType> = (state, action) => {
    6. // ...
    7. }

    其实dispatch方法也是有类型的:image.png可以看到,dispatch的类型是:React.Dispatch,上面示例的完整代码如下:

    1. import React, { useReducer } from "react";
    2. type ActionType = {
    3. type: "increment" | "decrement";
    4. };
    5. type State = { count: number };
    6. const Counter: React.FC = () => {
    7. const reducer: React.Reducer<State, ActionType> = (state, action) => {
    8. switch (action.type) {
    9. case "increment":
    10. return { count: state.count + 1 };
    11. case "decrement":
    12. return { count: state.count - 1 };
    13. default:
    14. throw new Error();
    15. }
    16. };
    17. const initialState: State = {count: 0}
    18. const [state, dispatch] = useReducer(reducer, initialState);
    19. return (
    20. <>
    21. Count: {state.count}
    22. <button onClick={() => dispatch({ type: "increment" })}>+</button>
    23. <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    24. </>
    25. );
    26. };
    27. export default Counter;

    四、事件处理

    1. Event 事件类型

    在开发中我们会经常在事件处理函数中使用event事件对象,比如在input框输入时实时获取输入的值;使用鼠标事件时,通过 clientX、clientY 获取当前指针的坐标等等。

    我们知道,Event是一个对象,并且有很多属性,这时很多人就会把 event 类型定义为any,这样的话TypeScript就失去了它的意义,并不会对event事件进行静态检查,如果一个键盘事件触发了下面的方法,也不会报错:

    1. const handleEvent = (e: any) => {
    2. console.log(e.clientX, e.clientY)
    3. }

    由于Event事件对象中有很多的属性,所以我们也不方便把所有属性及其类型定义在一个interface中,所以React在声明文件中给我们提供了Event事件对象的类型声明。

    常见的Event 事件对象如下:

    • 剪切板事件对象:ClipboardEvent
    • 拖拽事件对象:DragEvent
    • 焦点事件对象:FocusEvent
    • 表单事件对象:FormEvent
    • Change事件对象:ChangeEvent
    • 键盘事件对象:KeyboardEvent
    • 鼠标事件对象:MouseEvent
    • 触摸事件对象:TouchEvent
    • 滚轮事件对象:WheelEvent
    • 动画事件对象:AnimationEvent
    • 过渡事件对象:TransitionEvent

    可以看到,这些Event事件对象的泛型中都会接收一个Element元素的类型,这个类型就是我们绑定这个事件的标签元素的类型,标签元素类型将在下面的第五部分介绍。

    来看一个简单的例子:

    1. type State = {
    2. text: string;
    3. };
    4. const App: React.FC = () => {
    5. const [text, setText] = useState<string>("")
    6. const onChange = (e: React.FormEvent<HTMLInputElement>): void => {
    7. setText(e.currentTarget.value);
    8. };
    9. return (
    10. <div>
    11. <input type="text" value={text} onChange={this.onChange} />
    12. </div>
    13. );
    14. }

    这里就给onChange方法的事件对象定义为了FormEvent类型,并且作用的对象时一个HTMLInputElement类型的标签(input标签)

    可以来看下MouseEvent事件对象和ChangeEvent事件对象的类型声明,其他事件对象的声明形似也类似:

    1. interface MouseEvent<T = Element, E = NativeMouseEvent> extends UIEvent<T, E> {
    2. altKey: boolean;
    3. button: number;
    4. buttons: number;
    5. clientX: number;
    6. clientY: number;
    7. ctrlKey: boolean;
    8. /**
    9. * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
    10. */
    11. getModifierState(key: string): boolean;
    12. metaKey: boolean;
    13. movementX: number;
    14. movementY: number;
    15. pageX: number;
    16. pageY: number;
    17. relatedTarget: EventTarget | null;
    18. screenX: number;
    19. screenY: number;
    20. shiftKey: boolean;
    21. }
    22. interface ChangeEvent<T = Element> extends SyntheticEvent<T> {
    23. target: EventTarget & T;
    24. }

    在很多事件对象的声明文件中都可以看到 EventTarget 的身影。这是因为,DOM的事件操作(监听和触发),都定义在EventTarget接口上。EventTarget 的类型声明如下:

    1. interface EventTarget {
    2. addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
    3. dispatchEvent(evt: Event): boolean;
    4. removeEventListener(type: string, listener?: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
    5. }

    比如在change事件中,会使用的e.target来获取当前的值,它的的类型就是EventTarget。来看下面的例子:

    1. <input
    2. onChange={e => onSourceChange(e)}
    3. placeholder="最多30个字"
    4. />
    5. const onSourceChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    6. if (e.target.value.length > 30) {
    7. message.error('请长度不能超过30个字,请重新输入');
    8. return;
    9. }
    10. setSourceInput(e.target.value);
    11. };

    这里定义了一个input输入框,当触发onChange事件时,会调用onSourceChange方法,该方法的参数e的类型就是:React.ChangeEvent,而e.target的类型就是EventTarget:image.png在来看一个例子:

    1. questionList.map(item => (
    2. <div
    3. key={item.id}
    4. role="button"
    5. onClick={e => handleChangeCurrent(item, e)}
    6. >
    7. // 组件内容...
    8. </div>
    9. )
    10. const handleChangeCurrent = (item: IData, e: React.MouseEvent<HTMLDivElement>) => {
    11. e.stopPropagation();
    12. setCurrent(item);
    13. };

    这点代码中,点击某个盒子,就将它设置为当前的盒子,方便执行其他操作。当鼠标点击盒子时,会触发handleChangeCurren方法,该方法有两个参数,第二个参数是event对象,在方法中执行了e.stopPropagation();是为了阻止冒泡事件,这里的stopPropagation()实际上并不是鼠标事件MouseEvent的属性,它是合成事件上的属性,来看看声明文件中的定义:

    1. interface MouseEvent<T = Element, E = NativeMouseEvent> extends UIEvent<T, E> {
    2. //...
    3. }
    4. interface UIEvent<T = Element, E = NativeUIEvent> extends SyntheticEvent<T, E> {
    5. //...
    6. }
    7. interface SyntheticEvent<T = Element, E = Event> extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {}
    8. interface BaseSyntheticEvent<E = object, C = any, T = any> {
    9. nativeEvent: E;
    10. currentTarget: C;
    11. target: T;
    12. bubbles: boolean;
    13. cancelable: boolean;
    14. defaultPrevented: boolean;
    15. eventPhase: number;
    16. isTrusted: boolean;
    17. preventDefault(): void;
    18. isDefaultPrevented(): boolean;
    19. stopPropagation(): void;
    20. isPropagationStopped(): boolean;
    21. persist(): void;
    22. timeStamp: number;
    23. type: string;
    24. }

    可以看到,这里的stopPropagation()是一层层的继承来的,最终来自于BaseSyntheticEvent合成事件类型。原生的事件集合SyntheticEvent就是继承自合成时间类型。SyntheticEvent泛型接口接收当前的元素类型和事件类型,如果不介意这两个参数的类型,完全可以这样写:

    1. <input
    2. onChange={(e: SyntheticEvent<Element, Event>)=>{
    3. //...
    4. }}
    5. />

    2. 事件处理函数类型

    说完事件对象类型,再来看看事件处理函数的类型。React也为我们提供了贴心的提供了事件处理函数的类型声明,来看看所有的事件处理函数的类型声明:

    1. type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"];
    2. type ReactEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;
    3. // 剪切板事件处理函数
    4. type ClipboardEventHandler<T = Element> = EventHandler<ClipboardEvent<T>>;
    5. // 复合事件处理函数
    6. type CompositionEventHandler<T = Element> = EventHandler<CompositionEvent<T>>;
    7. // 拖拽事件处理函数
    8. type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
    9. // 焦点事件处理函数
    10. type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
    11. // 表单事件处理函数
    12. type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
    13. // Change事件处理函数
    14. type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
    15. // 键盘事件处理函数
    16. type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
    17. // 鼠标事件处理函数
    18. type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
    19. // 触屏事件处理函数
    20. type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
    21. // 指针事件处理函数
    22. type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
    23. // 界面事件处理函数
    24. type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
    25. // 滚轮事件处理函数
    26. type WheelEventHandler<T = Element> = EventHandler<WheelEvent<T>>;
    27. // 动画事件处理函数
    28. type AnimationEventHandler<T = Element> = EventHandler<AnimationEvent<T>>;
    29. // 过渡事件处理函数
    30. type TransitionEventHandler<T = Element> = EventHandler<TransitionEvent<T>>;

    这里面的T的类型也都是Element,指的是触发该事件的HTML标签元素的类型,下面第五部分会介绍。

    EventHandler会接收一个E,它表示事件处理函数中 Event 对象的类型。bivarianceHack 是事件处理函数的类型定义,函数接收一个 Event 对象,并且其类型为接收到的泛型变量 E 的类型, 返回值为 void。

    还看上面的那个例子:

    1. type State = {
    2. text: string;
    3. };
    4. const App: React.FC = () => {
    5. const [text, setText] = useState<string>("")
    6. const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
    7. setText(e.currentTarget.value);
    8. };
    9. return (
    10. <div>
    11. <input type="text" value={text} onChange={this.onChange} />
    12. </div>
    13. );
    14. }

    这里给onChange方法定义了方法的类型,它是一个ChangeEventHandler的类型,并且作用的对象时一个HTMLImnputElement类型的标签(input标签)。

    五、HTML标签类型

    1. 常见标签类型

    在项目的依赖文件中可以找到HTML标签相关的类型声明文件:image.png所有的HTML标签的类型都被定义在 intrinsicElements 接口中,常见的标签及其类型如下:

    1. a: HTMLAnchorElement;
    2. body: HTMLBodyElement;
    3. br: HTMLBRElement;
    4. button: HTMLButtonElement;
    5. div: HTMLDivElement;
    6. h1: HTMLHeadingElement;
    7. h2: HTMLHeadingElement;
    8. h3: HTMLHeadingElement;
    9. html: HTMLHtmlElement;
    10. img: HTMLImageElement;
    11. input: HTMLInputElement;
    12. ul: HTMLUListElement;
    13. li: HTMLLIElement;
    14. link: HTMLLinkElement;
    15. p: HTMLParagraphElement;
    16. span: HTMLSpanElement;
    17. style: HTMLStyleElement;
    18. table: HTMLTableElement;
    19. tbody: HTMLTableSectionElement;
    20. video: HTMLVideoElement;
    21. audio: HTMLAudioElement;
    22. meta: HTMLMetaElement;
    23. form: HTMLFormElement;

    那什么时候会使用到标签类型呢,上面第四部分的Event事件类型和事件处理函数类型中都使用到了标签的类型。上面的很多的类型都需要传入一个ELement类型的泛型参数,这个泛型参数就是对应的标签类型值,可以根据标签来选择对应的标签类型。这些类型都继承自HTMLElement类型,如果使用时对类型类型要求不高,可以直接写HTMLELement。比如下面的例子:

    1. <Button
    2. type="text"
    3. onClick={(e: React.MouseEvent<HTMLElement>) => {
    4. handleOperate();
    5. e.stopPropagation();
    6. }}
    7. >
    8. <img
    9. src={cancelChangeIcon}
    10. alt=""
    11. />
    12. 取消修改
    13. </Button>

    其实,在直接操作DOM时也会用到标签类型,虽然我们现在通常会使用框架来开发,但是有时候也避免不了直接操作DOM。比如我在工作中,项目中的某一部分组件是通过npm来引入的其他组的组件,而在很多时候,我有需要动态的去个性化这个组件的样式,最直接的办法就是通过原生JavaScript获取到DOM元素,来进行样式的修改,这时候就会用到标签类型。

    来看下面的例子:

    1. document.querySelectorAll('.paper').forEach(item => {
    2. const firstPageHasAddEle = (item.firstChild as HTMLDivElement).classList.contains('add-ele');
    3. if (firstPageHasAddEle) {
    4. item.removeChild(item.firstChild as ChildNode);
    5. }
    6. })

    这是我最近写的一段代码(略微删改),在第一页有个add-ele元素的时候就删除它。这里我们将item.firstChild断言成了HTMLDivElement类型,如果不断言,item.firstChild的类型就是ChildNode,而ChildNode类型中是不存在classList属性的,所以就就会报错,当我们把他断言成HTMLDivElement类型时,就不会报错了。很多时候,标签类型可以和断言(as)一起使用。

    后面在removeChild时又使用了as断言,为什么呢?item.firstChild不是已经自动识别为ChildNode类型了吗?因为TS会认为,我们可能不能获取到类名为paper的元素,所以item.firstChild的类型就被推断为ChildNode | null,我们有时候比TS更懂我们定义的元素,知道页面一定存在paper 元素,所以可以直接将item.firstChild断言成ChildNode类型。

    2. 标签属性类型

    众所周知,每个HTML标签都有自己的属性,比如Input框就有value、width、placeholder、max-length等属性,下面是Input框的属性类型定义:

    1. interface InputHTMLAttributes<T> extends HTMLAttributes<T> {
    2. accept?: string | undefined;
    3. alt?: string | undefined;
    4. autoComplete?: string | undefined;
    5. autoFocus?: boolean | undefined;
    6. capture?: boolean | string | undefined;
    7. checked?: boolean | undefined;
    8. crossOrigin?: string | undefined;
    9. disabled?: boolean | undefined;
    10. enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | undefined;
    11. form?: string | undefined;
    12. formAction?: string | undefined;
    13. formEncType?: string | undefined;
    14. formMethod?: string | undefined;
    15. formNoValidate?: boolean | undefined;
    16. formTarget?: string | undefined;
    17. height?: number | string | undefined;
    18. list?: string | undefined;
    19. max?: number | string | undefined;
    20. maxLength?: number | undefined;
    21. min?: number | string | undefined;
    22. minLength?: number | undefined;
    23. multiple?: boolean | undefined;
    24. name?: string | undefined;
    25. pattern?: string | undefined;
    26. placeholder?: string | undefined;
    27. readOnly?: boolean | undefined;
    28. required?: boolean | undefined;
    29. size?: number | undefined;
    30. src?: string | undefined;
    31. step?: number | string | undefined;
    32. type?: string | undefined;
    33. value?: string | ReadonlyArray<string> | number | undefined;
    34. width?: number | string | undefined;
    35. onChange?: ChangeEventHandler<T> | undefined;
    36. }

    如果我们需要直接操作DOM,就可能会用到元素属性类型,常见的元素属性类型如下:

    • HTML属性类型:HTMLAttributes
    • 按钮属性类型:ButtonHTMLAttributes
    • 表单属性类型:FormHTMLAttributes
    • 图片属性类型:ImgHTMLAttributes
    • 输入框属性类型:InputHTMLAttributes
    • 链接属性类型:LinkHTMLAttributes
    • meta属性类型:MetaHTMLAttributes
    • 选择框属性类型:SelectHTMLAttributes
    • 表格属性类型:TableHTMLAttributes
    • 输入区属性类型:TextareaHTMLAttributes
    • 视频属性类型:VideoHTMLAttributes
    • SVG属性类型:SVGAttributes
    • WebView属性类型:WebViewHTMLAttributes

    一般情况下,我们是很少需要在项目中显式的去定义标签属性的类型。如果子级去封装组件库的话,这些属性就能发挥它们的作用了。来看例子(来源于网络,仅供学习):

    1. import React from 'react';
    2. import classNames from 'classnames'
    3. export enum ButtonSize {
    4. Large = 'lg',
    5. Small = 'sm'
    6. }
    7. export enum ButtonType {
    8. Primary = 'primary',
    9. Default = 'default',
    10. Danger = 'danger',
    11. Link = 'link'
    12. }
    13. interface BaseButtonProps {
    14. className?: string;
    15. disabled?: boolean;
    16. size?: ButtonSize;
    17. btnType?: ButtonType;
    18. children: React.ReactNode;
    19. href?: string;
    20. }
    21. type NativeButtonProps = BaseButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement> // 使用 交叉类型(&) 获得我们自己定义的属性和原生 button 的属性
    22. type AnchorButtonProps = BaseButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement> // 使用 交叉类型(&) 获得我们自己定义的属性和原生 a标签 的属性
    23. export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps> //使用 Partial<> 使两种属性可选
    24. const Button: React.FC<ButtonProps> = (props) => {
    25. const {
    26. disabled,
    27. className,
    28. size,
    29. btnType,
    30. children,
    31. href,
    32. ...restProps
    33. } = props;
    34. const classes = classNames('btn', className, {
    35. [`btn-${btnType}`]: btnType,
    36. [`btn-${size}`]: size,
    37. 'disabled': (btnType === ButtonType.Link) && disabled // 只有 a 标签才有 disabled 类名,button没有
    38. })
    39. if(btnType === ButtonType.Link && href) {
    40. return (
    41. <a
    42. className={classes}
    43. href={href}
    44. {...restProps}
    45. >
    46. {children}
    47. </a>
    48. )
    49. } else {
    50. return (
    51. <button
    52. className={classes}
    53. disabled={disabled} // button元素默认有disabled属性,所以即便没给他设置样式也会和普通button有一定区别
    54. {...restProps}
    55. >
    56. {children}
    57. </button>
    58. )
    59. }
    60. }
    61. Button.defaultProps = {
    62. disabled: false,
    63. btnType: ButtonType.Default
    64. }
    65. export default Button;

    这段代码就是用来封装一个buttom按钮,在button的基础上添加了一些自定义属性,比如上面将button的类型使用交叉类型(&)获得自定义属性和原生 button 属性 :

    1. type NativeButtonProps = BaseButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>

    可以看到,标签属性类型在封装组件库时还是很有用的,更多用途可以自己探索~

    六、工具泛型

    在项目中使用一些工具泛型可以提高我们的开发效率,少写很多类型定义。下面来看看有哪些常见的工具泛型,以及其使用方式。

    1. Partial

    Partial 作用是将传入的属性变为可选项。适用于对类型结构不明确的情况。它使用了两个关键字:keyof和in,先来看看他们都是什么含义。keyof 可以用来取得接口的所有 key 值:

    1. interface IPerson {
    2. name: string;
    3. age: number;
    4. height: number;
    5. }
    6. type T = keyof IPerson
    7. // T 类型为: "name" | "age" | "number"

    in关键字可以遍历枚举类型,:

    1. type Person = "name" | "age" | "number"
    2. type Obj = {
    3. [p in Keys]: any
    4. }
    5. // Obj类型为: { name: any, age: any, number: any }

    keyof 可以产生联合类型, in 可以遍历枚举类型, 所以经常一起使用, 下面是Partial工具泛型的定义:

    1. /**
    2. * Make all properties in T optional
    3. * 将T中的所有属性设置为可选
    4. */
    5. type Partial<T> = {
    6. [P in keyof T]?: T[P];
    7. };

    这里,keyof T 获取 T 所有属性名, 然后使用 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值。中间的?就用来将属性设置为可选。

    使用示例如下:

    1. interface IPerson {
    2. name: string;
    3. age: number;
    4. height: number;
    5. }
    6. const person: Partial<IPerson> = {
    7. name: "zhangsan";
    8. }

    2. Required

    Required 的作用是将传入的属性变为必选项,和上面的工具泛型恰好相反,其声明如下:

    1. /**
    2. * Make all properties in T required
    3. * 将T中的所有属性设置为必选
    4. */
    5. type Required<T> = {
    6. [P in keyof T]-?: T[P];
    7. };

    可以看到,这里使用-?将属性设置为必选,可以理解为减去问号。适用形式和上面的Partial差不多:

    1. interface IPerson {
    2. name?: string;
    3. age?: number;
    4. height?: number;
    5. }
    6. const person: Required<IPerson> = {
    7. name: "zhangsan";
    8. age: 18;
    9. height: 180;
    10. }

    3. Readonly

    将T类型的所有属性设置为只读(readonly),构造出来类型的属性不能被再次赋值。Readonly的声明形式如下:

    1. /**
    2. * Make all properties in T readonly
    3. */
    4. type Readonly<T> = {
    5. readonly [P in keyof T]: T[P];
    6. };

    使用示例如下:

    1. interface IPerson {
    2. name: string;
    3. age: number;
    4. }
    5. const person: Readonly<IPerson> = {
    6. name: "zhangsan",
    7. age: 18
    8. }
    9. person.age = 20; // Error: cannot reassign a readonly property

    可以看到,通过 Readonly 将IPerson的属性转化成了只读,不能再进行赋值操作。

    4. Pick

    从T类型中挑选部分属性K来构造新的类型。它的声明形式如下:

    1. /**
    2. * From T, pick a set of properties whose keys are in the union K
    3. */
    4. type Pick<T, K extends keyof T> = {
    5. [P in K]: T[P];
    6. };

    使用示例如下:

    1. interface IPerson {
    2. name: string;
    3. age: number;
    4. height: number;
    5. }
    6. const person: Pick<IPerson, "name" | "age"> = {
    7. name: "zhangsan",
    8. age: 18
    9. }

    5. Record

    Record 用来构造一个类型,其属性名的类型为K,属性值的类型为T。这个工具泛型可用来将某个类型的属性映射到另一个类型上,下面是其声明形式:

    1. /**
    2. * Construct a type with a set of properties K of type T
    3. */
    4. type Record<K extends keyof any, T> = {
    5. [P in K]: T;
    6. };

    使用示例如下:

    1. interface IPageinfo {
    2. name: string;
    3. }
    4. type IPage = 'home' | 'about' | 'contact';
    5. const page: Record<IPage, IPageinfo> = {
    6. about: {title: 'about'},
    7. contact: {title: 'contact'},
    8. home: {title: 'home'},
    9. }

    6. Exclude

    Exclude 就是从一个联合类型中排除掉属于另一个联合类型的子集,下面是其声明的形式:

    1. /**
    2. * Exclude from T those types that are assignable to U
    3. */
    4. type Exclude<T, U> = T extends U ? never : T;

    使用示例如下:

    1. interface IPerson {
    2. name: string;
    3. age: number;
    4. height: number;
    5. }
    6. const person: Exclude<IPerson, "age" | "sex"> = {
    7. name: "zhangsan";
    8. height: 180;
    9. }

    7. Omit

    上面的Pick 和 Exclude 都是最基础基础的工具泛型,很多时候用 Pick 或者 Exclude 还不如直接写类型更直接。而 Omit 就基于这两个来做的一个更抽象的封装,它允许从一个对象中剔除若干个属性,剩下的就是需要的新类型。下面是它的声明形式:

    1. /**
    2. * Construct a type with the properties of T except for those in type K.
    3. */
    4. type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

    使用示例如下:

    1. interface IPerson {
    2. name: string;
    3. age: number;
    4. height: number;
    5. }
    6. const person: Omit<IPerson, "age" | "height"> = {
    7. name: "zhangsan";
    8. }

    8. ReturnType

    ReturnType会返回函数返回值的类型,其声明形式如下:

    1. /**
    2. * Obtain the return type of a function type
    3. */
    4. type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

    使用示例如下:

    1. function foo(type): boolean {
    2. return type === 0
    3. }
    4. type FooType = ReturnType<typeof foo>

    这里使用 typeof 是为了获取 foo 的函数签名,等价于 (type: any) => boolean。

    七、其他

    1. import React

    在React项目中使用TypeScript时,普通组件文件后缀为.tsx,公共方法文件后缀为.ts。在. tsx 文件中导入 React 的方式如下:

    1. import * as React from 'react'
    2. import * as ReactDOM from 'react-dom'

    这是一种面向未来的导入方式,如果想在项目中使用以下导入方式:

    1. import React from "react";
    2. import ReactDOM from "react-dom";

    就需要在tsconfig.json配置文件中进行如下配置:

    1. "compilerOptions": {
    2. // 允许默认从没有默认导出的模块导入。
    3. "allowSyntheticDefaultImports": true,
    4. }

    2. Types or Interfaces?

    我们可以使用types或者Interfaces来定义类型吗,那么该如何选择他俩呢?建议如下:

    • 在定义公共 API 时(比如编辑一个库)使用 interface,这样可以方便使用者继承接口,这样允许使用最通过声明合并来扩展它们;
    • 在定义组件属性(Props)和状态(State)时,建议使用 type,因为 type 的约束性更强。

    interface 和 type 在 ts 中是两个不同的概念,但在 React 大部分使用的 case 中,interface 和 type 可以达到相同的功能效果,type 和 interface 最大的区别是:type 类型不能二次编辑,而 interface 可以随时扩展:

    1. interface Animal {
    2. name: string
    3. }
    4. // 可以继续在原属性基础上,添加新属性:color
    5. interface Animal {
    6. color: string
    7. }
    8. type Animal = {
    9. name: string
    10. }
    11. // type类型不支持属性扩展
    12. // Error: Duplicate identifier 'Animal'
    13. type Animal = {
    14. color: string
    15. }

    type对于联合类型是很有用的,比如:type Type = TypeA | TypeB。而interface更适合声明字典类行,然后定义或者扩展它。

    3. 懒加载类型

    如果我们想在React router中使用懒加载,React也为我们提供了懒加载方法的类型,来看下面的例子:

    1. export interface RouteType {
    2. pathname: string;
    3. component: LazyExoticComponent<any>;
    4. exact: boolean;
    5. title?: string;
    6. icon?: string;
    7. children?: RouteType[];
    8. }
    9. export const AppRoutes: RouteType[] = [
    10. {
    11. pathname: '/login',
    12. component: lazy(() => import('../views/Login/Login')),
    13. exact: true
    14. },
    15. {
    16. pathname: '/404',
    17. component: lazy(() => import('../views/404/404')),
    18. exact: true,
    19. },
    20. {
    21. pathname: '/',
    22. exact: false,
    23. component: lazy(() => import('../views/Admin/Admin'))
    24. }
    25. ]

    下面是懒加载类型和lazy方法在声明文件中的定义:

    1. type LazyExoticComponent<T extends ComponentType<any>> = ExoticComponent<ComponentPropsWithRef<T>> & {
    2. readonly _result: T;
    3. };
    4. function lazy<T extends ComponentType<any>>(
    5. factory: () => Promise<{ default: T }>
    6. ): LazyExoticComponent<T>;

    4. 类型断言

    类型断言(Type Assertion)可以用来手动指定一个值的类型。在React项目中,断言还是很有用的,。有时候推断出来的类型并不是真正的类型,很多时候我们可能会比TS更懂我们的代码,所以可以使用断言(使用as关键字)来定义一个值得类型。

    来看下面的例子:

    1. const getLength = (target: string | number): number => {
    2. if (target.length) { // error 类型"string | number"上不存在属性"length"
    3. return target.length; // error 类型"number"上不存在属性"length"
    4. } else {
    5. return target.toString().length;
    6. }
    7. };

    当TypeScript不确定一个联合类型的变量到底是哪个类型时,就只能访问此联合类型的所有类型里共有的属性或方法,所以现在加了对参数target和返回值的类型定义之后就会报错。这时就可以使用断言,将target的类型断言成string类型:

    1. const getStrLength = (target: string | number): number => {
    2. if ((target as string).length) {
    3. return (target as string).length;
    4. } else {
    5. return target.toString().length;
    6. }
    7. };

    需要注意,类型断言并不是类型转换,断言成一个联合类型中不存在的类型是不允许的。

    再来看一个例子,在调用一个方法时传入参数:image.png这里就提示我们这个参数可能是undefined,而通过业务知道这个值是一定存在的,所以就可以将它断言成数字:data?.subjectId as number

    除此之外,上面所说的标签类型、组件类型、时间类型都可以使用断言来指定给一些数据,还是要根据实际的业务场景来使用。

    感悟:使用类型断言真的能解决项目中的很多报错~

    5. 枚举类型

    枚举类型在项目中的作用也是不可忽视的,使用枚举类型可以让代码的扩展性更好,当我想更改某属性值时,无需去全局更改这个属性,只要更改枚举中的值即可。通常情况下,最好新建一个文件专门来定义枚举值,便于引用。关于枚举类型的语法这里不在多介绍,可以参考文章:《TS入门篇 | 详解 TypeScript 枚举类型》。

    以太坊cppgolang区别 编程

    以太坊cppgolang区别

    以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
    progolang 编程

    progolang

    Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
    golangn个发送者 编程

    golangn个发送者

    Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
    golang技能图谱 编程

    golang技能图谱

    从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
    评论:0   参与:  8