# JS 转 TS | 一文浅析入门__第一版

  • 微软开发的开源编程语言
  • 所有内容仅涉及TS语法基础使用的探究,更深度的场景使用经验在阅读完成后需自行多多实验
  • 目前文章内容不保证绝对准确性,记得之后会持续更新修正
  • 在线练习 TypeScript Playground (opens new window)
  • 笔者:【SAM9029】
  • 更新时间:【2024/03/03】

# 与 JS 的区别

  • 有新的数据结构类型
  • 定义变量时要注解类型(严格建议写类型注解)
  • TS 有上下文推断来判断变量的类型,没有类型注解的会自动推断,(JS 也有类型自动判断区别如后:)TS 若推断不出来就会设置变量为any类型
  • TS 要通过 TS 编译器编译为 JS 代码来执行

这段话理解很不错, 建议看完教程后再回看该段话

在编写 TS 代码时,时刻记住 类型

学习 TypeScript 需要分清楚“值”(value)和“类型”(type)。

“类型”是针对“值”的,可以视为是后者的一个元属性。每一个值在 TypeScript 里面都是有类型的。比如,3 是一个值,它的类型是 number。

TypeScript 代码只涉及类型,不涉及值。所有跟“值”相关的处理,都由 JavaScript 完成。

这一点务必牢记。TypeScript 项目里面,其实存在两种代码,一种是底层的“值代码”,另一种是上层的“类型代码”。前者使用 JavaScript 语法,后者使用 TypeScript 的类型语法。

它们是可以分离的,TypeScript 的编译过程,实际上就是把“类型代码”全部拿掉,只保留“值代码”。

编写 TypeScript 项目时,不要混淆哪些是值代码,哪些是类型代码。

# 安装与使用

# 基本数据结构类型

数据结构的定义就是 TS 最主要区别与 JS 的地方,在 TS 中类型声明伴随变量的定义,后称为类型注解

# any

  • 该类型的变量可以赋予任意类型的值
  • 指定 any 的类型变量在编译时不会进行类型检查!!!(也就意味着 JS 的写法了)
  • 使用 const 将不能设置变量类型为 any(因为必须初始化值)
let notSure: any = 4;
let notSureArr: any[] = [1, "hi"];
let notSureArr: Array<any> = [1, "hi"];
1
2
3
  • 污染问题: 參考下面的 y 值
let x: any = "hello";
let y: number;

y = x; // 不报错

y = y * 123; // 不报错
y = y.toFixed(); // 不报错
1
2
3
4
5
6
7

# unknown (TS 3.0 引入)

  • 严格版 any, 解决 any 污染问题
  • UN https://wangdoc.com/typescript/any#unknown-%E7%B1%BB%E5%9E%8B

# string number boolean

let str: string = "hi";
let str: number = 100;
let str: boolean = true;
let msg2: "hello there!" | "sa"; // 字面量写法
msg2 = "sa"; // [[OK]]
// msg2 = 'sasa' // [[error]:不能将类型“"sasa"”分配给类型“"hello there!" | "sa"”]
1
2
3
4
5
6

# null undefined

let u: undefined = undefined;
let n: null = null;
1
2
  • undefined 和 null 的特殊性: undefined和null既是值,又是类型。 作为值,它们有一个特殊的地方:任何其他类型的变量都可以赋值为 undefined 或 null。
let age: number = 24;

age = null; // 正确
age = undefined; // 正确
1
2
3
4

# 数组

// 两种都可
let Arr: Array<number> = [1, 2]; // 数组泛型定义,Array<元素类型>
let Arr: number[] = [1, 2];
let Arr: Array<string> = ["hi"];
let Arr: string[] = ["hi"];
let Arr44: ReadonlyArray<string> = ["hi"]; // 只读数组
let Arr5: [1, 2]; // // 字面量写法
1
2
3
4
5
6
7

# 元组 Tuple

// !!!元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
// 说白了就是数组,但是已知长度和每个元素的数据类型,且各元素数据类型不一致
let x: [string, number] = ["hello", 10];
1
2
3

# Object(重点)

# 常规

const obj: {
  x: number;
  y: number;
} = { x: 1, y: 1 };
1
2
3
4

# 大写的 Object 类型

  • 代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是 Object 类型,这囊括了几乎所有的值, 除了 undefined 和 null。
let obj: Object;

obj = true;
obj = "hi";
obj = 1;
obj = { foo: 123 };
obj = [1, 2];
obj = (a: number) => a + 1;
1
2
3
4
5
6
7
8

# 小写的 object 类型

  • 只包含对象、数组和函数,不包括原始类型的值
let obj: Object;

obj = { foo: 123 };
obj = [1, 2];
obj = (a: number) => a + 1;
obj = true; // 报错
obj = "hi"; // 报错
obj = 1; // 报错
1
2
3
4
5
6
7
8

# 以 interface 接口和 type 类的方式定义

// 两种方式
// 使用接口(interface)时,你通常是在定义一个对象的结构,这个结构可以被类或其他对象实现。
// 使用类型别名(type)时,你是在创建一个新的类型名称,这个新类型可以是已经存在的类型,或者是一个复杂的类型。

// 接口(Interfaces): 接口是一种强大的工具,用于描述对象的结构。它定义了一组属性和方法的类型,但不提供具体的实现。
interface Person {
  name: string;
  age?: number; // ?可选属性
}
let person: Person = { name: "张三", age: 30 };
let person2: Person = { name: "张三" };

// 类型别名(Type Aliases): 类型别名允许你为已有的类型创建一个新的名称。
type Person = {
  name: string;
  age: number;
};
let person: Person = { name: "张三", age: 30 };

// 函数中接受参数
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 枚举 enum

// 作用 (增强代码的可读性; 避免魔法数字: 魔法数字是指直接出现在代码中的硬编码数字,它们没有解释,难以理解其含义)
enum Color {
  Red,
  Green,
  Blue,
} // 相当于 { '0': 'Red', '1': 'Green', '2': 'Blue', Red: 0, Green: 1, Blue: 2 }
// 还可以指定 索引
enum ColorTest {
  Red = 1,
  Green,
  Blue = 4,
} // 相当于 { '1': 'Red', '2': 'Green', '4': 'Blue', Red: 1, Green: 2, Blue: 4 }
let c1: Color = Color.Green;
let c2: String = Color[1];
// // dev-log >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
console.log(`[Dev_Log][${"c1"}_]_>>>`, c1); // [Dev_Log][c1_]_>>> 1
console.log(`[Dev_Log][${"c2"}_]_>>>`, c2); // [Dev_Log][c2_]_>>> Green
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# void

  • 一般用于表示一个函数没有返回值
// 某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void:
function warnUser(): void {
  console.log("This is my warning message");
}
// 声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:
let unusable: void = undefined;
1
2
3
4
5
6

# never

  • 不能赋给它任何值,否则会报错
// never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
  return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
  while (true) {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 函数添加类型注解(三种情况)

// (一)接受参数类型注解
function foo1(_opts: string) {}
function foo1(_opts: string | null) {} // 多项类型
function foo1(_opts: string | null | "HI") {} // 多项类型

// (二)返回值类型注解
function foo1(): string {
  return "hi";
}
function foo1(): void {}

// (三)匿名函数--接受参数--可以由TS自动进行上下文推断(contextual typing)
const names = ["Alice", "Bob", "Eve"]; // 这里是直接赋值,所以可以不用写类型注解
names.forEach(function (s) {
  // 该处 s 可以类型注解
  console.log(s.toUpperCase());
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 类型断言

官话:有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。 可以理解未这是一种情况来解决 变量在使用之前未知类型,但是在使用时知道类型时进行类型断言,TS 就不会在 check 此处该变量的类型

类型断言有两种形式

// 一 新语法,(建议使用第一种,JSX语法据支持该种)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

// 二 旧语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
1
2
3
4
5
6
7

# 类型注解类型断言 的区别(可看了 interfaces 再看)

暴力简单理解(待改进)

  • 使用 类型注解 一般伴随变量值的初始化或者函数的接收参数,且会在代码运行时会对变量类型进行检查
  • 使用 类型断言 不会伴随变量值的初始化,仅仅指定该变量应该是什么类型(且不会在代码运行时进行类型检查,仅在编译时检查)

如下示意

interface Shape {
  color: string;
}

// !!!类型注解
let square1: Shape = {
  color: 'blue'
};

// !!!类型断言,明确告诉 TypeScript square 应该是 Shape 类型
let square2 = {} as Shape;
// // or 尖脑壳语法(正确名称是尖括号语法)
// let square2 = <Shape>{};

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# interfaces接口语法的作用

  • 一般是为(你自己的代码或者第三方库的数据)Object定义数据结构类型

# (重点)对变量定义类型

  • 可定义可选属性
  • 可定义只读属性,(之后将不能改变变量值)
  • 可定义任意key任意类型属性, (不限制数量)
  • 属性没有顺序要求
interface SquareConfig {
  color: string;
  width?: number; // 可选
  readonly height?: number; // 只读且可选的
  [propName: string]: any; // 任意key任意类型属性
}

function createSquare(config: SquareConfig): { color: string; area: number } {
  let newSquare = {
    color: config.color,
    // 判断 width 是否存在
    area: config.width ? config.width * config.width : 1,
  };

  // 如果提供了height属性,你可以在这里使用它,但不能修改它
  if (config.height) {
    console.log(`The height of the square is ${config.height}`);
  }

  return newSquare;
}

let mySquare = createSquare({ color: "black" }); // 没有提供height属性
let mySquareWithHeight = createSquare({ color: "black", height: 200 }); // 提供了height属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 对函数定义类型

  • interfaces 也可以描述函数类型(主要是接受参数&&返回参数)
interface SearchFunc {
  // 接受参数:返回参数
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
};
mySearch("sas", "a");
1
2
3
4
5
6
7
8
9
10
11

# (重点)对类型注解

主要两个 - 不多讲了请自己理解概念

  • 实现 implements
  • 继承 extends

UN实现与继承还有很多其他知识目前不讲,之后探索

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

// 实现
class Clock implements ClockInterface {
  currentTime: Date;
  setTime(d: Date | null) {
    this.currentTime = d || new Date();
  }
  constructor(h: number, m: number) {}
}

// 继承
interface Shape {
  color: string;
}
interface PenStroke {
  penWidth: number;
}

// 单个继承
interface Square extends Shape {
  sideLength: number;
}
let square = {} as Square;
// let square = <Square>{}; 尖脑壳写法
square.color = "blue";
square.sideLength = 10;

// 多个继承
interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# (逻辑锻炼 ⭐)interface 混合类型

  • 将一个接口 既可以当对象又可以当函数使用
  • TypeScript 的一个特性,它允许接口合并不同的类型特性
interface Counter {
  (start: number): string; // 函数模式 (start:number):string , 收参number返参string
  interval: number; // 对象属性--string
  reset(): void; // 对象属性--function
}

// (一)getCounter 的返回值是 Counter 类型
function getCounter(): Counter {
  // (二)同时 counter 在申明时 可以用作 Counter 类型中的函数类型
  let counter = function (start: number) {} as Counter; // 作为函数调用
  // let counter = <Counter>function (start: number) { }; 函数模式

  counter.interval = 123; // 为函数 counter 设置interval属性(函数名可以设置属性,只是JS逻辑上不会用的,可自行实验)
  counter.reset = function () {}; // 为函数 counter 设置reset函数属性

  // (三)返回类型 counter 在getCounter和申明let counter的作用下也成为了混合类型
  return counter;
}

// (四) 得到混合类型的 c (又是函数又是对象)
let c = getCounter();
c(10); // 故而可以作为函数使用 (10即为 start:number 参数)
c.reset();
c.interval = 5.0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • 上面的代码是比较隐晦的,对于 function 来说其本质就是对象,所有的地址引用罢了,所以对于函数名来说依旧可以为其写属性

# 设置可索引的类型

# type 类型申明的作用

在 TypeScript 中,type关键字用于定义一个类型的别名。使用type可以给一个已存在的类型起一个新的名字,这样可以增加代码的可读性和可维护性。下面是type的一些常见用途:

  1. 基本类型别名: 使用type给基本类型起一个别名,例如:
    type UserID = string;
    type Age = number;
    let userId: UserID = "123";
    let userAge: Age = 25;
    
    1
    2
    3
    4
  2. 元组类型: 使用type定义一个元组类型,例如:
    type Pair = [string, number];
    let pair: Pair = ["hello", 42];
    
    1
    2
  3. 函数类型: 使用type定义一个函数类型,例如:
    type Add = (a: number, b: number) => number;
    let add: Add = function (x, y) {
      return x + y;
    };
    
    1
    2
    3
    4
  4. 联合类型: 使用type定义一个由两个或多个其他类型组成的联合类型,例如:
    type StringOrNumber = string | number;
    function formatId(id: StringOrNumber): string {
      return String(id);
    }
    
    1
    2
    3
    4
  5. 交叉类型: 使用type定义一个由两个或多个类型的所有属性组成的类型,例如:
    type Employee = {
      id: number;
      name: string;
    };
    type Manager = {
      department: string;
    };
    type ManagerEmployee = Employee & Manager;
    let manager: ManagerEmployee = {
      id: 1,
      name: "John Doe",
      department: "Engineering",
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  6. 索引签名类型: 使用type定义一个具有索引签名类型的对象,例如:
    type StringMap = {
      [key: string]: string;
    };
    let myMap: StringMap = {
      key1: "value1",
      key2: "value2",
    };
    
    1
    2
    3
    4
    5
    6
    7

# declare 申明语句的作用

参考:declare (opens new window)

declare 关键字用于声明一个变量、函数、类或模块,而不提供实际的实现

declare 关键字用来告诉编译器,某个类型是存在的,可以在当前文件中使用。它的主要作用,就是让当前文件可以使用其他文件声明的类型。 举例来说,自己的脚本使用外部库定义的函数,编译器会因为不知道外部函数的类型定义而报错,这时就可以在自己的脚本里面使用declare关键字,告诉编译器外部函数的类型。这样的话,编译单个脚本就不会因为使用了外部类型而报错。

-- UN

declare function alert(message: string): void;
1

# 泛型

  • UN

# 进阶

# 内置已定义的变量类型注解

  • 不知道是不是这样叫 如 date,HTMLElement 浏览器全局对象document

# 联合类型

  • UN

# 交叉类型

  • UN

# 类语法

  • UN

# 命名空间

# JSX

  • UN

1