React 中使用 TS

发布于:2024-05-05 ⋅ 阅读:(22) ⋅ 点赞:(0)

1,搭建项目

选用 create-react-app@5.0.1 版本。

集成 typescript 的方法,create-react-app 文档中有介绍。运行如下命令即可:

npx create-react-app my-app --template typescript

2,tsconfig.json 相关配置项

compilerOptions 是编译选项,include 是编译目录。

{
  "compilerOptions": {
    "target": "es5", // 编译后的 js 版本。
    "lib": [ // 开发(写TS)时允许使用的库。
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true, // 允许在 ts 中引入 js 文件。
    "skipLibCheck": true, // 跳过对声明文件的类型检查。
    "esModuleInterop": true, // 启用 es 模块化交互,可以引入那些使用非 es 导出的模块。
    "allowSyntheticDefaultImports": true, // 和上面是匹配出现的。
    "strict": true, // 开启严格模式(统一将 strictNullChecks 等严格检查配置项都开启。)
    "forceConsistentCasingInFileNames": true, // 强制检查导入文件时,文件名的大小写需一致。
    "noFallthroughCasesInSwitch": true,
    "module": "esnext", // 设置编译结果中,使用的模块化标准。esnext 就是最新版
    "moduleResolution": "node", // 模块解析策略(查找文件的步骤)
    "resolveJsonModule": true, // 允许导入 json 文件,将其作为对象使用。
    "isolatedModules": true, // 将每一个文件都当做一个模块(每个文件中必须得有导出或导入语句,语法受 module 影响),
    "noEmit": true, // 不生成 js 文件,在内存中完成 ts-->js 的过程(因为后面还有 babel 处理)。
    "jsx": "react-jsx" // 如何处理 jsx 语法。
  },
  "include": [
    "src"
  ]
}

esModuleInteropmoduleResolution 的含义都可以看这篇文章

1, lib

1,dom,允许使用 DOM 环境下的对象,比如 document 对象

2,dom.iterable,允许使用 dom 对象的迭代器。

es6 之后,新增了一个内置对象 Symbol,它是基本数据类型。同时还有一些静态属性,其中之一就是 Symbol.iterator

for…of 就是用于循环可迭代的对象。换句话说,只要对象有迭代器才能使用 for…of 循环。比如常见的:字符串,ArrayMapSet ,DOM 对象等。

但 ts 限制的更加严格,默认情况下,for...of 只能循环字符串和Array,如果想循环其他的对象,需要增加配置选项:"downlevelIteration": true

3,esnext,允许使用最新版的 js。

2,module

注意,脚手架搭建的项目中,是使用 webpack + ts + babel 来联合处理的:

1,webpack 加载 ts --> 2,typescript 编译 ts 为 js --> 3,babel 将高版本 js 编译为低版本的。

module 选项影响的是第2步。所以项目最终打包的结果中 js 并不是 module 指定的最新版 js。

3,jsx

表示 ts 如何处理 jsx 语法。注意处理完成后的 js 文件还是会交给 babel 处理。

ts官方文档

Mode Input Output Output File Extension
preserve <div /> <div /> .jsx
react <div /> React.createElement("div") .js
react-native <div /> <div /> .js
react-jsx <div /> _jsx("div", {}, void 0); .js
react-jsxdev <div /> _jsxDEV("div", {}, void 0, false, {...}, this); .js

3,在 React 中使用 TS

3.1,TS 可以解决的问题

  1. 组件有哪些属性可以传递,属性的类型是什么;
  2. 事件传递时的参数;
  3. 运行时的错误。
    虽然可以通过 propTypes 约束属性的类型,但这是在运行时才能发现。而 TS 是一套静态的类型系统,所以在开发时就能发现错误。

3,2,函数式组件

interface IProps {
    num: number;
    onChange?: (num: number) => void;
}

export const CompCount: React.FC<IProps> = function (props) {
    return (
        <div>
            <button
                onClick={() => {
                    if (props.onChange) {
                        props.onChange(props.num - 1);
                    }
                }}
            >
                减一
            </button>
            <span>{props.num}</span>
        </div>
    );
};

其中 React.FC 是一个类型别名,用于约束函数式组件的,泛型 P 是 Props 的类型。

type FC<P = {}> = FunctionComponent<P>;

所以也可以直接写:

import { FunctionComponent } from "react";
export const CompCount: FunctionComponent<IProps> = function (props) {}

或者,用TS基础的类型约束方式:

export function CompCount(props:IProps) {}

3.3,类组件

import React from "react";

interface IProps {
    num: number;
    onChange?: (num: number) => void;
}

interface IState {
    msg: string;
    desc: string;
}

export class CompCount extends React.Component<IProps, IState> {
    state = {
        msg: "init",
        desc: "",
    };
    render() {
        return (
            <div>
                <button
                    onClick={() => {
                        if (this.props.onChange) {
                            this.props.onChange(this.props.num - 1);
                        }
                    }}
                >
                    减一
                </button>
                <span>{this.props.num}</span>
            </div>
        );
    }
}

其中 React.Component 是类组件的接口,用于约束类组件。泛型 P 是 Props 的类型,S 是 State 的类型。

interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> {}

3.4,Props 的默认值

方法1

因为 Props 通过接口约束了,可接口无法指定字段的默认值。可通过组件的 defaultProps 属性来指定。

FunctionComponent 接口定义的字段:

interface FunctionComponent<P = {}> {
  (
      props: P,
      deprecatedLegacyContext?: any,
  ): ReactNode;
 
  propTypes?: WeakValidationMap<P> | undefined;
 
  contextTypes?: ValidationMap<any> | undefined;

  defaultProps?: Partial<P> | undefined;

  displayName?: string | undefined;
}

步骤:

1,把要赋默认值的字段改成可选的。
2,,设置组件的 defaultProps 属性,指定默认值即可。

interface IProps {
    num?: number;
}

export const CompCount: React.FC<IProps> = function (props) {
    return (
        <div>{props.num! - 1}</div>
    );
};

CompCount.defaultProps = {
    num: 2,
};

此时会有一个问题,这个 num 可能为 undefined,所以需要使用非空断言 !

num! // 表示 num 一定不是 null 或 undefined

// 或重新定义一个变量来使用。
const _num = props.num as number

方法2

其他通过指定 defaultProps 的方式,React 已经不推荐了(当前测试版本 v18.3.1),更推荐使用解构赋值的方式,直接指定默认值。

interface IProps {
    num?: number; // 注意同样得设置为可选属性。
    onChange?: (num: number) => void;
}

export const CompCount: React.FC<IProps> = function ({num = 2, onChange}) {
    return (
        <div>{num - 1}</div>
    );
};

以上。