Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript #7

Open
hex11o opened this issue Apr 20, 2021 · 6 comments
Open

TypeScript #7

hex11o opened this issue Apr 20, 2021 · 6 comments

Comments

@hex11o
Copy link
Owner

hex11o commented Apr 20, 2021

  1. 编译上下文
    通过tsconfig.json定义ts编译选项
{
  "compilerOptions": {

    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}
@hex11o
Copy link
Owner Author

hex11o commented Apr 20, 2021

  1. 指定编译的文件与编译包含文件与排除文件
{
  "files": [
    "./some/file.ts"
  ],
  "include": [
    "./folder"
  ],
  "exclude": [
    "./folder/**/*.spec.ts",
    "./folder/someSubFolder"
  ]
}

@hex11o
Copy link
Owner Author

hex11o commented Apr 20, 2021

  1. 声明空间
    分为两种,一种是变量声明空间,一种是类型声明空间
    变量声明空间很清楚,就是var , let, const 的 = 赋值
    类型声明空间则是用来当作类型注解的内容:
class Foo {}
interface Bar {}
type Bas = {};
// 类型注解
let foo: Foo;
let bas: Bas;
// 变量
const bar = Bar // Error: "cannot find name 'Bar'"

注意: class的类型声明,同时又提供了一个变量到变量声明空间

class Foo {}
const someVar = Foo;

@hex11o
Copy link
Owner Author

hex11o commented Apr 20, 2021

模块

全局模块

在一个新的ts文件写下代码,将会处于全局命名空间中

文件模块

当文件中含有import 或者export 时,文件中将创建一个文件作用域

模块路径

有两种不同的模块:

  • 相对路径模块:例如:./someFile
  • 动态查找模块: 例如:vue
    区别在于系统如何解析模块:
  • 相对路径:直接根据路径查找
  • 动态查找:基于Node模块解析策略

./node_modules/foo
../node_modules/foo
../../node_modules/foo

重写类型的动态查找

通过declare module 'somePath'声明一个全局模块,用来解决模块路径查找问题

declare module 'foo' {
  // some variable declarations
  export var bar: number;
}

@hex11o
Copy link
Owner Author

hex11o commented Apr 20, 2021

类型

泛型

不依赖对象的实际类型,但是在变量本身强制提供约束

interface Array<T> {
  reverse(): T[];
}

联合类型

属性为多种类型之一 : string | number

交叉类型

继承不同对象的功能

元祖类型

let nameNumber: [string, number];

类型别名

type SomeName = someValidTypeAnnotation

@hex11o
Copy link
Owner Author

hex11o commented Apr 21, 2021

interface 接口

接口通过声明进行扩展

// Lib a.d.ts
interface Point {
  x: number,
  y: number
}
declare const myPoint: Point

// Lib b.d.ts
interface Point {
  z: number
}

// Your code
myPoint.z // Allowed!

@hex11o
Copy link
Owner Author

hex11o commented Feb 23, 2023

前言

本次分享内容从TS的配置说明到写法实际案例优化,希望能对为实际写ts代码提供一点点🤏帮助,下面我们正式开始,首先为只是知道ts是什么的同学简单介绍一下ts能做什么以及成本:
由于js本身是动态语言,ts能够有很多优点

  • 更好的类型检查:TypeScript 为 JavaScript 引入了静态类型,能够在编码过程中发现更多的类型相关错误,包括拼写错误、类型不匹配等,减少运行时错误的可能性,提高代码的健壮性和可维护性。
  • 更好的可读性和可维护性:TypeScript 强制类型声明和接口的使用使得代码更加具有可读性,也能够更加明确地表达代码的含义和目的,有助于代码的可维护性。
  • 更好的代码提示和自动补全:TypeScript 可以提供更好的代码提示和自动补全,让开发者更加高效地编写代码。
  • 更好的重构能力:TypeScript 为重构提供了更好的支持,通过类型检查能够快速找到相关依赖和引用,减少了代码修改后引入错误的可能性。

综合来看,这些优点对业务和开发都是很有帮助的。

当然,引入新的东西往往也有很多成本:

  • 学习成本:相比于JavaScript,使用TS需要更多的学习成本,需要掌握TS的语法和类型系统,这对于一些开发者可能会有一定的难度。
  • 开发效率:由于需要编写类型定义和遵循一些类型检查的规则,开发效率可能会受到一定的影响。
  • 项目维护:尽管TS能够提高代码的可读性和可维护性,但是对于一些老旧的代码库或者缺乏类型定义的代码,引入TS可能需要耗费一定的时间和精力来进行重构和维护。
  • 编译时间:相对于JavaScript,TS需要编译成JavaScript才能在浏览器或者Node.js上运行,这可能会增加编译时间和构建时间,影响项目的整体性能。

ts的校验是有很大的兼容范围的,甚至可以简单到将后缀js改成ts,我们可以结合实际情况判断是否项目需要引用以及具体的严格程度要求,在具体的代码中则最好缩窄ts的限制,避免运行时错误

接下来从编译逻辑、ts配置帮助我们了解下还没写代码之前的打底知识,磨刀不误砍柴工,最后通过案例讲一下项目中经常出现但可以优化的地方;

如何工作与项目配置

TypeScript 的类型提示是通过 TypeScript 的语言服务实现的。TypeScript 的语言服务是一个独立的后台进程,用于为编辑器提供关于 TypeScript 代码的分析和提示信息。
在编辑器中打开一个 TypeScript 文件时,编辑器启动的 TypeScript 语言服务会读取 TypeScript 代码,并对其进行分析,生成对应的语法树和类型信息。语言服务会根据当前的光标位置和上下文信息,提供对应的代码补全、类型提示等功能。

compilerOptions

{
  "compilerOptions": {

    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 js 文件
    "checkJs": true,                       // 报告 js 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (ts pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 ts 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }

项目ts方法改进

  • 使用了太多any
    在 ts 中,使用 any 类型会破坏类型安全,使得类型检查失去作用,因为 any 类型可以接受任何类型的值。因此,如果过度地使用 any 类型,可能会导致程序出现难以排查的错误,并降低代码的可维护性和可读性。

    为了避免过度使用 any 类型,可以尽量使用具体的类型、使用泛型等方法。如果确实需要使用 any 类型,可以尝试将其限制在局部范围内,并且在使用时要非常谨慎,确保其不会对代码产生负面影响。

    以下是一些避免使用 any 类型的方法:

    1. 尽可能使用具体的类型,例如使用基本类型、自定义类型、接口等。
    2. 使用泛型类型,可以在编译时确定类型,从而避免在运行时出现类型错误。
    3. 使用 TypeScript 提供的类型工具,例如 unknown 类型和类型断言,来替代 any 类型。
    4. 如果需要使用 any 类型,可以在使用时进行类型检查和类型转换,确保其不会对代码产生负面影响。

    第一种就不用说了,不忙不懒的时候大家都能轻松做到=。=下面来说一下其他方法的应用场景来解决我们的项目实际问题

    // 2. 使用泛型类型
    function identity<T>(arg: T): T {
      return arg;
    }
    let result = identity<number>(42); // result 的类型是 number
    
    // 3. 使用 unknown 类型
    function handleValue(value: unknown) {
      if (typeof value === "string") {
        // 在这里可以安全地使用 value 的字符串方法
      }
    }
    // 在大多数情况下,范型已经可以满足我们的需求,只有在特定的情况下才需要使用 unknown
    
    // 4. 使用类型断言
    let inputValue: unknown = "Hello, TypeScript!";
    let inputLength = (inputValue as string).length; // inputLength 的类型是 number
    

    针对范型的情况举一个项目中的实际例子,接口请求数据的返回现在用IAjaxData承接,所有的数据全是any指向,另外就是变量声明时由于ts自带类型推断,数据本身被推断成了any, 导致赋值时两头any,改完IAjaxData的同时同时还要为变量声明做好数据定义

    练手: 1-generic

    tips: 大家快速生成接口数据的数据类型时,可以使用ts类型推断结果,快速复制粘贴,简单修改后就可以直接使用了

  • 类型范围太宽:虽然ts 类型非any,编译时并不会报错,但实际运行中会出现报错, 比如取值只能是特定枚举,但类型设定为string, 比如很多枚举下拉、请求参数为特定常量、对象取值

    练手:2-objvalue

  • 类型断言滥用:类型断言是将一个变量强制转换为指定类型的一种方式,但是滥用类型断言会增加代码出错的可能性。在使用类型断言时,应该尽量避免使用 as any,而是尽可能使用更具体的类型断言。

type Person = {
  age: number
  address: {
    city: string
    code: string
  }
}

// error
const person = {
  age: 1
} as Person

console.log(person.address.city)
// 编译不会报错,但运行会崩 
// cant read 'city' of undefined
  • 对象字面量的惰性初始化
// js
let foo = {};
foo.bar = 123;
foo.bas = 'Hello World';

// ts
let foo = {};
foo.bar = 123; // Error: Property 'bar' does not exist on type '{}'
foo.bas = 'Hello World'; // Error: Property 'bas' does not exist on type '{}'

最好就还是在定义阶段将变量写明,另外可以定义好数据类型,并做类型推断断言, 确保类型安全

let foo = {
  bar: 123,
  bas: 'abc'
}

// or
interface Foo {
  bar: number
  base: string
}

let foo = {} as Foo
  • 冗余的对象声明, 类型声明中的 number 是冗余的,因为 TypeScript 可以自动推断出变量 a 的类型。
// wrong
const a: number = 1;

// right
const a = 1;
  • 善用ts工具类型(utility types), 可以减少代码的重复书写重复定义,并能形成定义类型的合理化引用与溯源
    1. Partial types: 将某个type所有属性变为可选
      常见场景是我们已定义一个对象类型,在某些情况下,需要更新对象部分数据
      type Partial = {
      [P in keyof T]? : T[P] | undefined
      }
    2. Required types: 将可选属性全变为必选,比如定义更新和新增数据类型时,id经常为可选?, 但更新数据id实际为必选参数
    3. Record types: key, value分别为一个类型的的map, 比如instype险种对应的配置
      type Record<K extends string | number | symbol, T> = { [P in K]: T; }
    4. Pick types: 选取类型中某些特定属性形成新的类型
    5. Omit types: 选取类型中除开某些属性之后形成的新类型,与Pick相反
    6. Exclude types: 移除union 中的某些属性
    7. Awaited types: 为了模拟像async函数中的await或Promises上的.then()方法这样的操作, type A = Awaited<Promise>...

reference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant