初学者简单理解 TypeScript 类型系统

简介: TypeScript 是一种强类型的编程语言,具有强大的类型系统。了解和掌握 TypeScript 的类型系统是成为高效 TypeScript 开发者的关键。本文将深入探讨 TypeScript 类型系统的核心概念和用法。

TypeScript

TypeScript 是一种强类型的编程语言,具有强大的类型系统。了解和掌握 TypeScript 的类型系统是成为高效 TypeScript 开发者的关键。本文将深入探讨 TypeScript 类型系统的核心概念和用法。

学习路线

  1. 类型注解和类型推断
  2. 基本类型和字面量类型
  3. 接口和类型别名
  4. 泛型和泛型约束
  5. 类型保护和类型推断
  6. 类型兼容性与类型断言
  7. 高级类型:交叉类型、联合类型和条件类型
  8. 映射类型和索引签名
  9. 类型声明和模块导入

安装

执行命令:

npm install typescript -g

检查是否安装成功:

tsc

看到这个,说明成功了👇🏻

image.png

Hello World

本地新建文件hello.ts,开始写代码

function sayHelloWorld(person: string) {
   
   
    return name + ' hello world'
}
console.log(sayHelloWorld("Alice"))

执行tsc hello.ts进行编译,会出现一个新的文件hello.js,此时我们执行如下命令:

node hello.js

正常输出 Alice hello world,成功!

上述的Ts栗子中有一个点,就是:指定类型。

注意: Ts只会在编译与书写代码的过程中给你相关的警告,在Js的执行中并不会有这样的警告,编译完的Js代码也不会插入相关的校验代码。

如果我们在调用sayHelloWorld的时候,传入一个123,会出现以下情况:

情况一:编辑器会有提示,这时候看到提示,我们就可以修改代码。

image.png

情况二:如果没有修改代码,强制编译,过程中会抛出这个错误


Argument of type 'number' is not assignable to parameter of type 'string'.
类型“number”不能分配给类型"string"的参数.

但是js文件依旧会编译成功,如果想要报错终止编译,那么需要配置tsconfig.json中的noEmitOnError

tsconfig.json

这个文件是Ts的编译选项配置文件,具体配置可以参考这里

不带任何输入文件的情况下调用tsc,编译器会从当前目录开始去查找tsconfig.json文件,逐级向上搜索父目录。

生成tsconfig.json

在项目根目录执行


tsc --init

即可生成一个tsconfig.json文件,里面有好多配置,我们来测试一个

image.png

我在这打开了删除注释的配置,然后在hello.ts中新增一行注释

function sayHelloWorld(name: string) {
   
   
  return name + "hello world111";
}

console.log(sayHelloWorld(1231212));
// 我是一行注释

执行tsc编译,hello.js文件如下👇🏻
image.png

同样的,我们刚刚说的那个noEmitOnError也是可以生效的,成功!

这里放一个配置表:

"compilerOptions": {
   
   
  "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
  "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
  "diagnostics": true, // 打印诊断信息 
  "target": "ES5", // 目标语言的版本
  "module": "CommonJS", // 生成代码的模板标准
  "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
  "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
  "allowJS": true, // 允许编译器编译JSJSX文件
  "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
  "outDir": "./dist", // 指定输出目录
  "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
  "declaration": true, // 生成声明文件,开启后会自动生成声明文件
  "declarationDir": "./file", // 指定生成声明文件存放目录
  "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
  "sourceMap": true, // 生成目标文件的sourceMap文件
  "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
  "declarationMap": true, // 为声明文件生成sourceMap
  "typeRoots": [], // 声明文件目录,默认时node_modules/@types
  "types": [], // 加载的声明文件包
  "removeComments":true, // 删除注释 
  "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
  "noEmitOnError": true, // 发送错误时不输出任何文件
  "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
  "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
  "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
  "strict": true, // 开启所有严格的类型检查
  "alwaysStrict": true, // 在代码中注入'use strict'
  "noImplicitAny": true, // 不允许隐式的any类型
  "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
  "strictFunctionTypes": true, // 不允许函数参数双向协变
  "strictPropertyInitialization": true, // 类的实例属性必须初始化
  "strictBindCallApply": true, // 严格的bind/call/apply检查
  "noImplicitThis": true, // 不允许this有隐式的any类型
  "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
  "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
  "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
  "noImplicitReturns": true, //每个分支都会有返回值
  "esModuleInterop": true, // 允许export=导出,由import from 导入
  "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
  "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
  "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
  "paths": {
   
    // 路径映射,相对于baseUrl
    // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
    "jquery": ["node_modules/jquery/dist/jquery.min.js"]
  },
  "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
  "listEmittedFiles": true, // 打印输出文件
  "listFiles": true// 打印编译的文件(包括引用的声明文件)
}

基础

数据类型

布尔值

ts
let isOk: boolean = true

数值

let age: number = 18
let notANumber: number = NaN

字符串

let name: string = "Alice"
let age: number = 18
let sentence: string = `I from China Beijing, my name is ${name}, age ${age}`

空值

Js中没有Void的概念,Ts中可以用void表示没有任何返回值的函数:

function alertHello(): void {
   
   
    alert('hello')
}

null & undefiled

let u: undefined = undefined
let n: null = null

注意

有一个点需要注意一下

let isOk: boolean = new Boolean('')

这样也会抛出错误不能将类型“Boolean”分配给类型“boolean”。,因为new T返回的是一个T对象,👇🏻

image.png

任意值

Any表示你的变量可以是任何值,Ts -> Js。。。

let anyVar: any = '121'
anyVar = true

完全ok,如果是👆🏻上面说的声明定式的类型,那么是不行的:

let name: string = 'alice'
name = true

抛出错误不能将类型“number”分配给类型“string”。

类型推论

如果你的变量没有赋值,那么TypeScript会看你后面的值是啥类型,那你这个变量就是啥类型

let age = 12 // === let age: number = 12
age = '12'

抛出错误不能将类型“string”分配给类型“number”。

需要注意📢的是,如果你没有初始化变量,那就是any类型

let age;
age = 1
age = true

这样完全Ok,并不会抛出错误,这样写类似于:

let age: any;

类型兼容性与类型断言

类型兼容性是指在赋值操作或函数调用中,一个类型是否可以被另一个类型替代的规则。在 TypeScript 中,类型兼容性遵循结构类型系统,即只要两个类型的内部结构兼容,它们就是兼容的。以下是类型兼容性的一些重要原则:

  • 如果源类型包含目标类型的所有必需属性,则源类型是目标类型的子类型,它们是兼容的。
  • TypeScript 中的类型兼容性是双向的,即源类型和目标类型互相兼容,那么它们是可互换的。
  • 函数兼容性遵循参数列表的协变性,即目标函数的每个参数类型都必须兼容源函数的对应参数类型。
interface Animal {
   
   
  name: string;
}

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

let animal: Animal = {
   
    name: "Animal" };
let cat: Cat = {
   
    name: "Cat", age: 2 };

animal = cat; // 可以将 Cat 类型赋值给 Animal 类型,因为 Cat 具有 Animal 的所有必需属性
// cat = animal;  // 无法将 Animal 类型赋值给 Cat 类型,因为 Cat 需要额外的 age 属性

类型断言允许开发者手动指定一个值的类型,即告诉编译器“我知道这个值的实际类型比它声明的类型更准确”。类型断言有两种形式:使用尖括号(<类型>值)和使用as关键字(值 as 类型)。

let someValue: any = "This is a string";
let strLength: number = (<string>someValue).length; // 使用尖括号进行类型断言
let strLength2: number = (someValue as string).length; // 使用 as 关键字进行类型断言

console.log(strLength); // 输出:16
console.log(strLength2); // 输出:16

高级类型:交叉类型、联合类型和条件类型

  • 交叉类型(Intersection Types):交叉类型表示同时具有多个类型的值,通过使用 & 运算符实现。交叉类型的结果是两个类型的并集。

示例代码:


interface Car {
   
   
  brand: string;
  color: string;
}

interface Flyable {
   
   
  fly(): void;
}

type FlyingCar = Car & Flyable;

const myCar: FlyingCar = {
   
   
  brand: "Tesla",
  color: "red",
  fly() {
   
   
    console.log("Flying...");
  },
};
  • 联合类型(Union Types):联合类型表示一个值可以是多个类型之一,通过使用 | 运算符实现。联合类型的结果是多个类型的并集。

示例代码:

type Status = "success" | "error" | "loading";

function handleStatus(status: Status) {
   
   
  if (status === "success") {
   
   
    console.log("Operation succeeded");
  } else if (status === "error") {
   
   
    console.log("An error occurred");
  } else {
   
   
    console.log("Loading...");
  }
}

handleStatus("success"); // 输出:Operation succeeded
handleStatus("error"); // 输出:An error occurred
handleStatus("loading"); // 输出:Loading...
  • 条件类型(Conditional Types):条件类型是 TypeScript 中的一种高级类型,它根据一个条件来选择两种可能的类型中的一种进行映射。

示例代码:

type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // A 的类型为 true
type B = IsString<number>; // B 的类型为 false

在上述示例中,使用条件类型 IsString<T> 来判断泛型 T 是否是字符串类型,如果是,则结果类型为 true,否则为 false

联合类型

联合类型就是一个变量可以有多个类型,举个🌰

ts

let isWhat: string | number | boolean;
isWhat = '1'
isWhat = 1
isWhat = true

完全Ok,但是如果这样:

isWhat = [1,2,3]

就会抛出错误不能将类型“number[]”分配给类型“string | number | boolean”

再举一个例子:

function getLength(something: string | number): number {
   
   
    return something.length
}

这样写就会抛出错误类型“number”上不存在属性“length”。length不是他们的共有属性,所以会报错,改成这样:

function getString(something: string | number): string {
   
   
    return something.toString()
}

完全Ok!

接口

基本定义

interface是对行为的抽象,举个🌰:

interface Person {
   
   
    name: string;
    age: number;
}
let alice: Person = {
   
   
    name: 'Alice',
    age: 18
}

上面的栗子,变量alice的结构必须与接口Person相一致,如果我们不写age,那么就会抛出错误类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性。

所以定义的变量的属性,比接口定义的少是不被允许的,当然!多了也是不允许的,必须一致!

比如我们在变量alice添加属性address,那么就会抛出错误不能将类型“{ name: string; age: number; address: string; }”分配给类型“Person”。\ 对象文字可以只指定已知属性,并且“address”不在类型“Person”中。

可选属性

如果我们需要某个属性不是必须一致,那么可以这么做,继续用上面的栗子,假设age为不必要属性:

interface Person {
   
   
    name: string;
    age?: number;
}
let alice: Person = {
   
   
    name: 'Alice'
}

完全Ok

任意属性

如果我们需要在变量中定义一些我们将来可能会添加的属性,有极大的不确定性的话,比如我们想要新增一个address属性,那么我们可以这样:

interface Person {
   
   
    name: string;
    age?: number;
    [propName: string]: any
}
let alice: Person = {
   
   
    name: 'Alice',
    address: 'China Beijing',
    gender: 0
}

完全OK!

只读属性

有时候我们需要一个属性不能再被修改,需要用到readonly定义属性,举个🌰

interface Person {
   
   
    readonly id: number;
    name: string;
    [propName: string]: any;
}
let alice: Person = {
   
   
    id: 1,
    name: 'Alice',
    gender: 0
}
alice.id = 2
// error: 无法分配到 "id" ,因为它是只读属性。

总结一下:

  • TypeScript的类型兼容性是基于结构类型系统的,只要两个类型的内部结构兼容,它们就是兼容的。如果一个类型包含目标类型的所有必需属性,则源类型是目标类型的子类型。
  • 类型断言允许开发者手动指定一个值的类型,使用尖括号或者as关键字进行类型断言。
  • TypeScript提供了交叉类型(Intersection Types)和联合类型(Union Types)来组合多个类型的特性。
  • 条件类型(Conditional Types)是一种高级类型,根据条件来选择映射到不同类型的类型。

本文同步我的技术文档

相关文章
|
8天前
|
存储 人工智能 开发框架
Eliza:TypeScript 版开源 AI Agent 开发框架,快速搭建智能、个性的 Agents 系统
Eliza 是一个开源的多代理模拟框架,支持多平台连接、多模型集成,能够快速构建智能、高效的AI系统。
92 8
Eliza:TypeScript 版开源 AI Agent 开发框架,快速搭建智能、个性的 Agents 系统
|
3月前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
69 0
|
3月前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧
|
2月前
|
设计模式 JavaScript 安全
TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等
本文深入探讨了TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等,旨在帮助开发者在保证代码质量的同时,实现高效的性能优化,提升用户体验和项目稳定性。
50 6
|
2月前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
49 2
|
2月前
|
JavaScript 安全 前端开发
TypeScript类型声明:基础与进阶
通过本文的介绍,我们详细探讨了TypeScript的基础与进阶类型声明。从基本数据类型到复杂的泛型和高级类型,TypeScript提供了丰富的工具来确保代码的类型安全和可维护性。掌握这些类型声明能够帮助开发者编写更加健壮和高效的代码,提高开发效率和代码质量。希望本文能为您在使用TypeScript时提供实用的参考和指导。
47 2
|
2月前
|
JavaScript 开发者
在 Babel 插件中使用 TypeScript 类型
【10月更文挑战第23天】可以在 Babel 插件中更有效地使用 TypeScript 类型,提高插件的开发效率和质量,减少潜在的类型错误。同时,也有助于提升代码的可理解性和可维护性,使插件的功能更易于扩展和升级。
|
3月前
|
JavaScript 前端开发
TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第11天】TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
3月前
|
JavaScript 前端开发 安全
TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第9天】TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
3月前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与最佳实践
【10月更文挑战第8天】深入理解TypeScript:类型系统与最佳实践