TS在React项目中的应用实践(附项目源码)

4,205 阅读5分钟

作者:宇文涛

前置阅读:TypeScript 中文网

目录

  • React项目集成TS
    • 集成流程概览
    • 安装依赖
    • 为什么不使用ts-loader
    • bebel.config.json修改
    • webpack修改
    • .eslintrc修改
    • package.json配置
    • 声明文件的作用
  • TS在项目中应该怎么去写?
    • 应用到哪些地方?
    • 怎么应用 ?(附todoList项目源码)

React项目集成TS

集成流程概览

image-20210705172133448.png

安装依赖

@babel/preset-typescript 是babel转义ts

@types/react @types/react-dom 是react依赖的类型库

@types/webpack-env 是webpack的全局属性类型库

yarn add typescript @babel/preset-typescript @types/react @types/react-dom @types/webpack-env -D 

为什么不使用ts-loader

当然是有缺点的,但是缺点可以避免,总收益是大的所以就用babel

  • 无法实时完成项目的类型检查,检查提示错误是IDE的提示,这个优化方法 就是每次开发完成后 执行一下类型检查 tsc -noEmit

bebel.config.json修改

{
  "presets": ["@babel/env", "@babel/preset-react", "@babel/preset-typescript"], // 新增
  "plugins": ["react-hot-loader/babel"]
}

webpack修改

全部react文件更名为TSX格式 原因是tsx语义更强

module.exports = (env, args) => {
// prd 模式
  const cssTypePrd = args.env.css === 'prd'

  return {
    entry: './app.tsx', // 入口文件
    module: {
        rules: [ // 配置加载器
        {
          test: /\.(jsx|tsx|js|ts)?$/,// 处理es6语法以及jsx语法
          loader: 'babel-loader',
          include: [
            path.resolve(__dirname, 'src'), // 使用目录
            path.resolve(__dirname, 'app.tsx'), // 使用文件
          ],
        },
        ]
    },
    resolve: { // 新增因为现在的文件变为了 tsx 和ts后缀名所以需要增加两个后缀名 tsx和ts
      extensions: ['.tsx', '.ts', '.json', '.js'], // 尝试按顺序解析这些后缀名。如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。
    },
  }
}

.eslintrc修改

{
    "extends": [
        "eslint-config-ali",
        "eslint-config-ali/react",
        "eslint-config-ali/typescript", // 修改项
        "eslint-config-ali/typescript/react" // 修改项
    ],
    "rules": {
        "semi": [2, "never"],
        "no-console": "off",
        "react/no-array-index-key": "off",
        "react/prop-types":"off"
    },
    "globals": { // 修改项
        "GLOBAL_ENV": true
    },
    "ignorePatterns": [ // 忽略文件夹
        "dist",
        "node_modules",
        "*.d.ts"
    ]
}

ts-config.json 文件

虽然不需要ts-loader来进行转译,但是需要ts-config的配置和IDE协作

{
    "compilerOptions": { // 编译配置
        "jsx": "react",
        "types": ["node", "react", "react-dom", "webpack-env"], // 获取node_modules下的@types下的ts依赖,不写就算add type也没用
        "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查
        "noEmit": true,  // 不生成文件,只做类型检查  
    },
}
// 参考链接 https://segmentfault.com/a/1190000021421461 
// https://juejin.cn/post/6844904052094926855#heading-17
// 这里没用ts-loader所以tsconfig的作用只有给IDE提示

package.json配置

    "typeCheck": "tsc --noEmit" // 新增 ts类型检查命令 不生产输出文件

声明文件的作用

在完成这些后会发现less 还有 img文件的import会标红,因为这些文件都需要类型声明 还有全局变量

新建一个 externals.d.ts

declare module '*.less' { // less报错

    const classes: { [className: string]: string };
  
    export default classes;
  
   }

// 文件报错声明
declare module '*.svg'

declare module '*.png'

declare module '*.jpg'

declare module '*.jpeg'

declare module '*.gif'

declare module '*.bmp'

declare module '*.tiff'

// 全局变量
declare var GLOBAL_ENV: string;


TS在项目中应该怎么去写?

TS的目的是为了更安全高效的类型提示和检查

应用到哪些地方?

image-20210705172224295.png

基础知识是哪些?

函数类型接口以及类型别名

泛型

  • 函数泛型

    function test<T>(name:T) => {
    	console.log(name)
    }
    
    test<string>('小明')
    
  • class泛型

    class Test<T>{
        constructor(params:T) {
            console.log(params)
        }
    }
    
    new Test<Number>(1)
    
  • interface泛型

    interface Test<T> {
    	name: string,
        age: number,
        info: T
    }
    
    const test:Test<string> = {
        name: '小米',
        age:11,
        info: '我是一个学生'
    }
    

枚举

  • 维护常量

    • 建议写法
    enum Test {
        NAME = 'name',
        AGE = 'age',
    }
    

typeof 与 keyof 的用法

  • typeof

    • 给枚举以及 定义类型的值用

      const a: number = 3
      
      // 相当于: const b: number = 4
      const b: typeof a = 4
      
  • keyof

    • 只能给类型用

      interface Point {
          x: number;
          y: number;
      }
      
      // type keys = "x" | "y"
      type keys = keyof Point;
      

怎么应用 ?

写一个简单的todoList

项目地址

  • 函数组件定义
  • 函数组件的props定义
  • hooks的定义
  • 自己方法的定义

注意的坑有?

  • class组件的defaultprops 无法和props的类型相关联,但是函数组件可以 ,参考链接, (可以看一下当前的 react的定义类型文件。)
  • chilren的定义 需要 写 React.ReactNode ,其他 ReactNodeArray 与 ReactChild 无法容错
  • 函数组件如果使用React.FC类型无法直接返回 Arr.map 结构,因为Arr.map有可能返回为[]

扩展阅读

TS的内置函数

TS的lib 声明文件 有一些内置的工具函数可以用

  • Partial

    // 让对象属性的所有属性都是可选的
    interface User {
        name: string,
        age: number
    }
    
    /*
    * {
     name?: string,
     age?:string
    }
    */
    type Test = Partial<User>
    
    
  • Required

    // 全部属性转为必需属性
    interface User {
        name?: string,
        age?: number
    }
    
    /*
    * {
     name: string,
     age:string
    }
    */
    type Test = Required<User>
    
  • Readonly

    // 全部属性转为只读属性
    interface User {
        name: string,
        age: number
    }
    
    /*
    * {
     readonly name: string,
     readonly age:string
    }
    */
    type Test = Readonly<User>
    
  • Pick

    // 获取全部对象类型某个属性
    interface User {
        name: string,
        age: number
    }
    
    /*
    * {
     	name: string,
     }
    */
    type Test = Pick<User, 'name'>
    
  • Record

    // 对象类型中赋给某个新的对象类型
    interface User {
        name: string,
        age: number
    }
    
    /*
    * {
     	test: User,
     }
    */
    type Test = Record<'test', User>
    
  • Omit

    // 对象类型排除某个属性
    // 对象类型中赋给某个新的对象类型
    interface User {
        name: string,
        age: number
    }
    
    /*
    * {
        name: string,
     }
    */
    type Test = Omit<User, 'age'>
    

TS的?操作符用法

TypeScript中的问号 ? 与感叹号 ! 是什么意思?

参考链接