TypeScript实战指南,让你快速掌握TypeScript

1,071 阅读9分钟

TypeScript是由微软开发的自由和开源的编程语言,它是JavaScript的超集,扩展了JavaScript的语法,因此现有的JavaScript代码可与TypeScript一起工作无需任何修改。TypeScript通过类型注解提供编译时的静态类型查询,这使得代码更加可读、可维护和更易于调试。本文会从ts的配置说起,了解ts的配置可以让你开发事半功倍。

tsconfig.json配置详解

tsconfig.json 是用来配置 TS 编译选项的,通常位于项目的根目录位置。

tsconfig.json的顶层配置

  • compilerOptions:编译器相关的选项。

    大部分编译配置都会放在这里;

  • files:指定需要被编译的文件列表。

    只能指定文件,不能是目录

  • include:指定需要编译的文件列表或匹配模式。

    include 可以通过通配符指定目录,如 "src/**/*" 表示 src 下的所有文件。

  • exclude:在 include 圈定的范围内,排除掉一些文件。

    多用于排除编译输出目录、测试文件目录、一些生成文件的脚本等文件

  • extends:继承另一个 ts 配置文件。

  • reference:引用。

实用的编译器配置

baseUrl

用于设置基础 url

示例:

比如原来要写import { button} from "./src/button/index",设置 baseUrl 为 ./src后,则可以写成import { button} from "button/index";

如果想使用相对项目根目录的路径,则将 baseUrl 设置为 . 。

module

编译后的 JS 使用哪种模块系统。

模块系统常用的有两种:ESModule 和 CommonJS。

  • ESModule 是 ES 的标准(使用了 import 关键字)

  • CommonJS是 Nodejs 的使用的模块系统(使用了 require)。

    此外还有 AMD、UMD 等。

支持的值有:none、commonjs、amd、umd、system、es6/es2015、es2020、es2022、esnext、node16、nodenext。

target

指定 TS 最后编译出来的 ES 版本,默认值是 ES3。

target 支持的值有:es3、es5、es6(也叫 es2015)、es2016 一直到 es2022、然后还有 esnext。

  • 没有 es7 ,需要用 es2016。
  • esnext 指的是当前版本的 TS 编译器支持的最高版本。

lib

用于指定编译器需要包含的库文件。

TypeScript 包含了一组默认的库文件,这些库文件包含了 JavaScript 的内置 API,比如 Math、String、Array 等等。此外,TypeScript 还包含了一些针对浏览器环境的 API,比如 DOM、Web Worker 等等。如果想要使用某个库文件,但是没有在 lib 配置项中指定,那么在编译时就会报错。

lib 有高层级的:ES5、ES2015、DOM 、ESNEXT、WebWorker、ScriptHost 等。或是低层级模块的 DOM.Iterable、ES2015.Core、ES2017.TypedArrays、 ES2018.Intl 等。

示例:如下配置指定了编译器需要包含 ES2015 和 DOM 两个库文件,代表可以在代码中使用 ES2015 和 DOM 的所有 API。

{
  "compilerOptions": {
    "lib": ["es2015", "dom"]
  }
}

strict

启用严格模式,能够更能保证类型检测的正确。

esModuleInterop

将 ES6 模块转换为 CommonJS 模块。

在 ES6 模块中,导入和导出的方式与 CommonJS 模块不同。如果你在 TypeScript 中使用 ES6 模块,并且需要导入一个 CommonJS 模块,那么就需要使用 require() 函数来导入这个模块。启用 esModuleInterop 选项,可以直接使用 import 语句来导入这个模块。

allowSyntheticDefaultImports

允许 TypeScript 在编译时生成一个默认导出。

在 ES6 模块中,如果一个模块没有默认导出,那么在导入这个模块时,就需要使用 import * as moduleName from ‘module’ 的方式来导入这个模块。而启用 allowSyntheticDefaultImports 选项,则可以直接使用 import moduleName from ‘module’ 的方式来导入这个模块。

  • jsx ,在 TypeScript 中,想要使用 JSX 语法,需要启用 jsx 选项

    jsx 选项值有:

    • preserve:保留 JSX 语法,不进行转换。
    • react:将 JSX 转换为 React.createElement 调用。
    • react-jsx:将 JSX 转换为 _jsx 调用。
    • react-jsxdev:将 JSX 转换为 _jsx 调用,并添加调试信息。

resolveJsonModule

用来告诉 TypeScript 是否应该支持导入 JSON 模块。

如果你的项目中使用了 JSON 文件,并且你想要在 TypeScript 中使用它们,那么你需要把这个选项设置为 true

比如说,假设你有一个名为 data.json 的 JSON 文件,其中包含一些数据。你想在 TypeScript 中导入这个JSON,import * as data from './data.json';

如果你没有在 tsconfig.json 文件中配置 resolveJsonModule 选项,TypeScript 就会报错,因为它不认识这个 JSON 文件。

noUnusedLocals

用于检测未使用的局部变量。

假设你有一个函数如下

function greet(name: string) {
  const greeting = `Hello, ${name}!`;
  console.log(greeting);
}

在这个函数中,greeting 是一个局部变量,但实际上在这个函数中没有被使用。如果你把 noUnusedLocals 设置为 true,TypeScript 就会给出一个警告,提示你这个变量是未使用的。

strictNullChecks

用于检查空值和未定义的值。

例如:有这样一个变量

let name: string;

如果你把 strictNullChecks 设置为 true,TypeScript 就会给出一个警告,提示你这个变量可能为未定义值。如果你想在这个变量之前使用它,你需要先进行空值检查或者为它赋一个默认值。

forceConsistentCasingInFileNames

强制执行文件名大小写一致性

如果你把它设置为 true,TypeScript 编译器会在编译时检查你的代码中的文件名,如果发现文件名大小写不一致,就会给出警告或错误提示。

这个选项的主要作用是避免因为文件名大小写不一致而导致一些问题。

skipLibCheck

用于跳过检查声明文件中的类型。

例如,假设你的项目中使用了许多第三方库,每个库都有一个声明文件,这些声明文件是用来描述库中的类型和接口的。如果你把 skipLibCheck 设置为 true,TypeScript 就会跳过对这些声明文件的检查,从而加快编译过程。

paths

用于配置模块解析路径

如果项目中有许多模块,而且它们都存放在不同的文件夹中,那么就可能需要使用 paths 来配置模块的解析路径,以便更方便地引用这些模块。

例如,假设有一个文件夹结构如下

- src/
    --app/
        ---index.ts
    --shared/
        ---utils.ts

想在 index.ts 中引用 utils.ts,通常需要使用相对路径

import { someUtil } from '../shared/utils';

如果你在 tsconfig.json 中配置了 paths,那么就可以使用一个别名来引用这个模块

//配置
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@shared/*": ["src/shared/*"]
    }
  }
}

//使用
import { someUtil } from '@shared/utils';

types

用于指定应该包含哪些类型声明文件。

如果项目中使用了许多第三方库,这些库可能会有自己的类型声明文件。如果想在代码中使用这些类型声明文件,需要将它们手动添加到项目中。但是,如果你在 tsconfig.json 中设置了 types,TypeScript 编译器会自动为你加载这些类型声明文件。

例如,假设项目中使用了 lodash 库,需要手动将它的类型声明文件添加到的项目

npm install --save-dev @types/lodash

如果在 tsconfig.json 中配置了types,TypeScript 编译器就会自动加载 lodash 的类型声明文件

//配置
{
  "compilerOptions": {
    "types": [
      "lodash"
    ]
  }
}

一份ts配置

{
  "compilerOptions": {
    "baseUrl": ".",
    "module": "ESNext",
    "target": "ESNext",
    "lib": ["DOM", "ESNext"],
    "strict": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "jsx": "preserve",
    "resolveJsonModule": true,
    "noUnusedLocals": true,
    "strictNullChecks": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "paths": {
      "@/*": ["./src/*"]
    },
    "types": ["vite/client", "node", "naive-ui/volar"]
  },
  "exclude": ["node_modules", "dist", "service"]
}

常用的TS类型定义

原始数据类型(可省略)

写法

TypeScript 对原始数据的类型定义和JS相比,就是转为全小写即可!

原始数据类型JavaScriptTypeScript
字符串Stringstring
数值Numbernumber
布尔值Booleanboolean
大整数BigIntbigint
符号Symbolsymbol
不存在Nullnull
未定义Undefinedundefined

示例

// 字符串
const str: string = 'Hello World'

// 数值
const num: number = 1

// 布尔值
const bool: boolean = true

实际的编程过程中,原始数据类型的类型定义可以省略,因为 TypeScript 会根据声明变量时赋值的类型,自动推导变量类型,也就是和平时写 JavaScript 一样

// 这样也不会报错,因为 TS 会推导它们的类型
const str = 'Hello World'
const num = 1
const bool = true

数组

写法

数组有两种写法(两种方式定义出来的类型一样)

  • 1)追加方括号 [] ,如string[] (常用)
  • 2)基于 TS 的泛型 Array<T> 写法
数组里的数据类型写法 1类型写法 2
字符串string[]Array
数值number[]Array
布尔值boolean[]Array
大整数bigint[]Array
符号symbol[]Array
不存在null[]Array
未定义undefined[]Array

示例

// 字符串数组
const strs: string[] = ['Hello World', 'Hi World']

// 数值数组
const nums: number[] = [1, 2, 3]

// 布尔值数组
const bools: boolean[] = [true, true, false]

数组对象

定义数组对象,可以先使用接口定义出对象,在用定义数组的写法。

interface Person {
  name: string;
  age: number;
  readonly id: string;
  sayHello?: () => void;
}

const person: Person[] = [
    {
      name: 'Alice',
      age: 30,
      id: '1234',
      sayHello: () => {
        console.log(`Hello, my name is ${person.name}`);
      }
    },
  ]

对象

写法

对象的类型定义有两个语法支持:

  • type
  • interface ,对象的类型 interface 也叫做接口,用来描述对象的结构
type UserInfo = {
  // ...
}

interface UserInfo {
  // ...
}

对象的类型定义通常采用 Upper Camel Case 大驼峰命名法,也就是每个单词的首字母大写,例如 ImportMetaEnvUserInfo

示例

定义一个用户对象类型(即描述一个用户)

// 定义接口
interface UserInfo {
  name: string
  age: number
}

// 在声明变量使用
const zhangsan: UserInfo = {
  name: '张三',
  age: 18,
}

可选属性

可选写法,当属性不是必须时,可以通过添加 ? 将其设置为可选属性。

如果定义的时候不是可选属性,又不存在,则会报错

// 比如增加一个用户昵称属性,但是它不是必须的
interface UserInfo {
  name: string
  age: number
  nickName?: string
}

自己调用自己

如果子属性的结构和自己一致,可以直接引用自己来定义,比如,用户信息里面还有一个朋友列表

interface UserInfo {
  name: string
  age: number
  friendList: UserInfo[]   // 这个属性引用了本身的类型
}

const zhangsan: UserItem = {
  name: '张三',
  age: 18,
  friendList: [
    {
      name: '李四',
      age: 19,
      friendList: [],
    },
    {
      name: '王五',
      age: 20,
      friendList: [],
    }
  ],
}

接口继承

//定义一个动物(Animal)接口
interface Animal {
  name: string;
  age: number;
  color: string;
}

//定义一个Dog接口,继承Animal接口,并且添加了一个breed属性
interface Dog extends Animal {
  breed: string;
}


const myDog: Dog = {
  name:'小白',
  age:3,
  breed:'拉布拉多犬'
  color:'white'
}

部分继承

如上示例,加入只想继承Animal的部分属性,比如只想继承name属性,可以这样写

选取部分属性Pick (选取)

这代表,只继承Animal属性里的name属性

...

//定义一个Dog接口,继承Animal接口,并且添加了一个breed属性
interface Dog extends Pick<Animal, "name"> {
  breed: string
}

const myDog: Dog = {
  name:'小白',
  breed:'拉布拉多犬'
}

Pick 是 TypeScript 中的一个辅助类型,用于从一个类型中选取部分属性,返回一个新的类型。它的语法如下:Pick<T, K>

  • T 表示原始类型(即要被选择属性的类型)
  • K 表示需要【选取】的属性名,可以是一个字符串数组或者一个字符串联合类型。

删除不想要的属性Omit (排除)

...

// 这里在继承 Animal 类型的时候,删除了两个多余的属性
interface Dog extends Omit<Animal, 'age' | 'color'> {
  permissionLevel: number
}

const myDog: Dog = {
  name:'小白',
  breed:'拉布拉多犬'
}

Omit 是 TypeScript 中的一个辅助类型,它的作用是从一个类型中剔除某些属性,然后返回一个新类型。Omit 的定义如下: Omit<T, K >

  • T 表示原始类型(即要剔除属性的类型)
  • K 表示需要【剔除】的属性名

函数

基本写法

函数的传参类型定义,是把类型写在参数后面,

  • (x: number, y: number),表示两个传参接收的都是数字

函数返回值类型定义,是写在圆括号后面,

  • sum1(x: number, y: number): number,最后加的这个number,表示这个函数return返回的是数字

函数声明

function sum1(x: number, y: number): number {
  return x + y
}

函数表达式

const sum2 = function(x: number, y: number): number {
  return x + y
}

箭头函数

const sum3 = (x: number, y: number): number => x + y

对象上的方法

const obj = {
  sum4(x: number, y: number): number {
    return x + y
  }
}

解构函数

如果想要给解构的参数定义类型,应该怎么写呢?

这样写是错误的

function f({ x: number }) {
  // Error, x is not defined?
  console.log(x);
}

正确的写法应该是

function f({ x }: { x: number }) {
  // OK
  console.log(x);
}

或者可以直接通过赋初始值的方式让ts自行推导

function f({ x = 0 }) {
  // x: number
  console.log(x);
}

无返回值函数

使用 void 来定义它的返回,也就是空。

// 注意这里的返回值类型
function sayHi(name: string): void {
  console.log(`Hi, ${name}!`)
}

sayHi('Petter') // Hi, Petter!

注意: voidnullundefined 不可以混用,如果的函数返回值类型是 null ,那么是真的需要 return 一个 null

// 只有返回 null 值才能定义返回类型为 null
function sayHi(name: string): null {
  console.log(`Hi, ${name}!`)
  return null
}

异步函数的返回值

异步函数的返回值类型通常是 Promise。Promise 是一个泛型类型,它接受一个类型参数,表示异步操作的结果类型。

例如,如果我们有一个异步函数 fetchUser,它返回一个 Promise,那么 fetchUser 的返回值类型就是 Promise。

interface User {
  name: string;
  age: number;
}

async function fetchUser(): Promise<User> {
  const response = await fetch('/user');
  const user = await response.json();
  return user;
}

函数重载

TypeScript 函数重载(Function Overloading)是指通过为同一个函数提供多个不同的签名(参数类型和数量),以便根据实际传入的参数类型和数量,在编译时选择正确的函数声明进行调用。

函数重载通常用于实现多态,即同一个函数在不同的上下文中有不同的行为。

示例:

function add(a: number, b: number): number;  
function add(a: string, b: string): string;
function add(a: any, b: any): any {
  return a + b;
}

在这个示例中,我们定义了一个函数 add,它有三个签名:

  • 第 1 行是函数的 TS 类型,告知 TypeScript ,当入参为 number 类型时,返回值也是 number

    使用场景,比如当传入两个数字时,add 函数返回它们的和

  • 第2 行是函数的 TS 类型,告知 TypeScript ,当入参为 string 类型时,返回值也是 string

    使用场景,比如当传入两个字符串时,add 函数返回它们的拼接

  • 第3行是函数的 TS 类型,告知 TypeScript ,当入参为 any 类型时,返回值也是 any

    使用场景,比如当传入其他类型的参数时,add 函数返回它们的相加结果。

类型断言

TypeScript的类型断言是一种告诉编译器变量类型的方法,它可以让开发者手动指定变量的类型,从而避免类型检查错误。

TypeScript支持两种类型断言语法:

  • 尖括号( <> )语法
  • as语法。

例如:将变量x强制转换为字符串类型,并将结果赋值给变量y。

let x: any = "hello";

let y: string = <string>x;
// 或者
let y: string = x as string;

实例:比如有这样一个方法,

虽然知道此时应该是 string[],但 TypeScript 还是会认为这是 string | string[],导致无法使用 join 方法

image-20230530180328592

这个时候,就可以用断言将其指定为 string[]

function demo(value: string | string[]): string | string[] {
  return value
}

const result = demo(['hi', '早上好']) as string[]  //使用断言

const resultSentence = result.join(' ')

参考资料

tsconfig.json 是什么


往期推荐

👉 Vite基础入门

👉 15 个JavaScript数组实用技巧

👉 JavaScript数组方法看这一篇就够了

👉 JS内置日期对象Date的使用指南