TypeScript学习文档一------常见类型
TypeScript学习第一章:TypeScript初识
1.1 TypeScript学习初见
TypeScript(TS)是由微软Microsoft由2012年推出的自由和开源的编程语言, 目前主流的三大框架React \ Vue 和 Angular这三大主流框架再加上最新的鸿蒙3.0都可以用TS进行开发.
可以说 TS 是 JS 的超集, 是建立在JavaScript上的语言. TypeScript把其他语言的一些精妙的语法, 带入到JavaScript中, 让JS达到了一个新的高度.
可以在TS中使用JS以外的扩展语法, 同时可以结局TS对面向对象和静态类型的良好支持, 可以让我们编写更健壮\ 更可维护的大型项目.
1.2 TypeScript介绍
因为TypeScript是JavaScript的超集, 所以要介绍TS, 不得不提一下JS, JS从在引入编程社区20多年以来, 已经成了有史以来应用最广泛的跨平台语言之一了, 从一开始为网页中添加一些微不足道的\交互性的小型的脚本语言发展到现在各种规模的前端和后端应用程序的首选语言了.
虽然我们用JS语言编写程序的大小\ 范围和复杂性呈指数级的增长, 但是JS语言表达不同代码单元之间的关系和能力却很弱, 使得JS成了一项难以大规模管理的任务, 而且也很难解决程序员经常出现的错误: 类型错误.
而TS语言可以很好的解决这个错误, 他的目标是成为JS程序的静态类型检查器, 可以在代码运行之前进行检查, 也就是静态编译, 并且呢, 可以确保我们程序的类型正确(即进行类型检查).
TS添加了可选的静态类型和基于类的面向对象编程等等, 是JS的语言扩展, 不是JS的替代品, 会让JS前进的步伐更坚实 更遥远.
1.3 JS 、TS 和 ES之间的关系
ES6又称为ECMAScript 2015, TypeScript 是 JS 的超集, 他包含Javascript的所有元素, 能运行Javascript代码, 并扩展了JS语法, 并添加了静态类型 类 模块 接口 类型注解等等方面的功能, 更加易于大项目的开发.
这张图表示TS不仅包含了JS和ES的最新内容, 还扩展了新的功能.
总的来说, ECMAScript是JS的标准, TS是JS的超集.
1.4 TS的竞争者有哪些?
1. ESLint
2. TSlint
1 和 2 都是和TypeScript一样来突出代码中可能出现的错误, 至少i没有为检查过程添加新的语法, 但是这两者都不打算最为IDE集成的工具来运行, 这两个的存在可以是TS做更少的检查, 但是这些检查并不适合于所有的代码库
3. CoffeeScript
CoffeeScript是想改进JS语言, 但是现在用的人少了, 因为他又成为了JS的标准, 属于是打不过JS了.
4.Flow
Vue2的源码的类型检查工具就是flow, 不过Vue3已经开始使用TS做类型检查了.
Flow更悲观的判断类型, 而TS更加乐观.
Flow是为了维护Facebook的代码库而建立的, 而TS是作为一种独立的语言而建立的, 其内部有独立的环境, 可以自由专注于工具的开发和整个生态系统的维护
TypeScript学习第二章:为什么使用TypeScript?
2.1 发现问题
JS中每个值都有一组行为, 我们可以通过运行不同的操作来观察:
// 在 'message' 上访问属性方法 'toLowerCase', 并调用它
message.toLowerCase();
// 调用 'message'
message();
我们尝试直接调用message, 但是假设我们不知道message, 我们就无法可靠的说出尝试运行任何的这些代码会得到什么结果, 每个操作的结果完全取决于我们最初给message的赋值. 我们编译代码的时候真的可以调用message()么, 也不一定有toLowerCase()这个方法, 而且也不知道他们的返回值是什么.
通常我们在编写js的时候需要对上面所述的细节牢记在心, 才能编写正确的代码.
假设我们知道了message 是什么, 如下所示, 但是第三行就会报错.
const message = 'Hello World'
message.toLowerCase(); // 输出hello world
message(); // TypeError: message is not a function
如果我们能避免这样的错误, 就完美了, 当我们运行我们的代码的时候, 选择做什么的方式, 是通过确定值得类型, 来确定他具有什么样的行为和功能的, TypeError 就暗指 字符串是不能作为函数来调用的. 对于某些值,比如string和number, 我们可以使用typeof来识别他们的类型.
但是对于像函数之类的其他的东西, 没有相应的运行时机制, 比如下面的代码, 运行是有条件的, 也就是说这个x是必须具有flip这个方法的, js只能在运行一下代码时才能知道这个x是提供了什么的, 我们如果能够使用静态类型系统, 在运行代码之前预测预期的代码,问题就解决了.
function fn(x) {
return x.flip()
}
2.2 静态类型检查
const message = 'hello'
message() // TypeError
上述这段代码会引起TypeError, 理想的情况下, 我们希望有一个工具可以在我们代码运行之前发现这些错误, TS就可以实现这些功能. 静态类型系统就描述了当前我们运行程序的时候, 值得形状和行为, 像TS这样的类型检查器, 会告诉我们什么时候代码会出现问题.
2.3 非异常故障
JS 在运行的时候会告诉我们他认为某些东西是没有意义的情况, 因为ECMA规范明确说明了JS在遇到某些意外情况下应该是如何表现得, 比如如下代码:
const user = {
name: "小千",
age:26,
};
user.location; // 返回undefined, 理应报错, 因为根本没有location这个属性
但是静态类型系统要求必须对调用哪些代码做系统的标记, 如果是在TS运行这段代码, 就会出现location未定义的错误, 如下图所示:
TS可以在开发过程中捕获很多类似于合法的错误, 比如说错别字, 未调用函数, 基本的逻辑错误等等:
拼写错误: 属性toLocaeleLowerCase在String类型中不存在, 你找的是否是toLocaleLowerCase属性?
未调用的函数检查: 运算符号 < 不能用在一个 '() => number' 和 number数字之间.
逻辑问题: value !== 'a' 和 value === 'b'逻辑重叠.
2.4 使用工具
- 安装VSCode
- 安装Node.js:使用命令
node -v
来检查nodejs版本 - 安装TypeScript编译器:
npm i typescript -g
然后我们要编译我们的TS, 因为TS是不能直接运行的, 我们必须把他编译成JS.
在终端中使用cls 或者 clear命令可以清屏
可以使用tsc命令来转换TS 成 JS: 例如 tsc hello.ts
, 就会生成对应的JS文件.
hello.ts:
// 你好, 世界
// console.log('Hello World')
// 会出现函数实现重复的错误
function greet(person, date) {
console.log(`Helo ${person}, today is ${date}`)
}
greet('xiaoqian','2021/12/04')
会出现函数实现重复的错误是因为hello.js也有这个greet的函数, 这是跟我们编译环境是矛盾的, 而且还需要我们重新编译ts, 所以我们需要进行优化编译过程.
2.5 优化编译
- 解决TS和JS冲突问题
tsc --init # 生成配置文件
- 自动编译
tsc --watch
- 发出错误
tsc --noEmitOnError hello.ts
TS文件编译成JS文件以后, 当出现函数名或者是变量名相同的时候, 会给我们提示重复定义的问题,可以通过 tsc --init
来生成一个配置文件来解决冲突问题. 先把严格模式strict关闭, 可解决未指定变量类型的问题.
当我们修改TS文件的时候, 我们需要重新的执行编译, 才能拿到最新的结果我们需要自动编译, 可以通过tsc --watch
来解决自动编译的问题.
当我们编译完之后, JS还是能正常运行的, 我们可以加一个noEmitOnError的参数来解决, 这样的话如果我们在TS中出现错误就可以让TS不编译成JS文件了.
最终的命令行指令是这样的:
tsc --watch --noEmitOnError
2.6 显式类型
刚才我们在tsconfig.json里把strict模式关闭了, 如果我们打开, 就会出现未指定变量类型的错误, 如果要解决这个问题, 我们就需要指定显式类型:
什么叫显式类型呢, 就是手工的给变量定义类型, 语法如下:
function greet(person: string, date: Date) {
console.log(`Helo ${person}, today is ${date.toDateString()}.`)
}
在TS中, 也不是必须指定变量的数据类型, TS会根据你的变量自动推断数据类型, 如果推断不出来就会报错.
2.7 降级编译
我们可以在tsconfig.json 就修改target来更改TS编译目标的代码版本.
{
"compilerOptions": {
......
"target": 'es5',
......
}
}
默认为es2016, 即es7, 建议以默认值就可以, 目前的浏览器都能兼容
2.8 严格模式
不同的用户使用TS在类型检查中希望检查的严格程度是不同的, 有的人喜欢更宽松的验证体验, 从而仅仅验证程序的某些部分, 并且仍然拥有不错的工具.
默认情况下:
{
"compilerOptions": {
......,
"strict": true, /* 严格模式: 启用所有严格的类型检查选项。*/
"noImplicitAny": true, /* 为隐含的'any'类型的表达式和声明启用错误报告。*/
"strictNullChecks": true, /* 当类型检查时,要考虑'null'和'undefined' */
......
}
}
一般来说使用TS就是追求的强立即验证, 这些静态检查设置的越严格, 越可能需要更多额外的编程工作, 但是从长远来说是值得的, 它会使代码更加容易维护. 如果可以我们应该始终打开这些类型检查.
启用strictNullChecks可以拦截null 和undefined 的错误, 启用noImplicitAny可以拦截any的错误, 启用strict可以拦截所有的严格类型检查选项, 包括前面两个的.
所以结论就是只需要开启"strict"为true即可。
TypeScript学习第三章: 常用类型
3.1 基元类型string number 和 boolean
- string: 字符串, 例子: 'Hello', 'World'.
- number: 数字, 例子: 42, -100.
- boolean: 布尔, 例子: true, false.
String Number Boolean 也是合法的, 在TS里专门指一些很少的, 出现在代码里的一些特殊的内置类型, 对于类型我们始终使用小写的string, number 和 boolean.
为了输出方便我们可以在tsconfig.json的rootDir里设置一个目录"./src"
, 设置outDir为"./dist"
.
let str: string = 'hello typescript'
let num: number = 100
let bool: boolean = true
3.2 数组
数组的定义方法有两种:
- type[]
- Array
Array
let arr: number[] = [1, 4, 6 ,8]
// arr = ['a']
let arr2: Array<number> = [1, 2, 3]
arr2 = []
值得注意的是, 数组可以被赋值为空数组[], 但是不能被赋值为规定类型以外的数组值.
3.3 any
any可以在不希望某个特定值导致类型检查错误, 就可以使用any.
当一个值是any的时候, 可以访问它的任何属性, 将它分配给任何类型的值, 或者几乎任何其它语法上的东西都是合法的. 但是运行的时候该报错还是报错, 所以我们不应该经常使用他.
let obj: any = {
x: 0
}
obj.foo() // js调用时就会报错
obj()
obj.bar = 100
obj = 'hello'
const n: number = obj
3.4 变量上的类型解释
let myName: string = "Felixlu"
采用(冒号:) + (类型string)的方式.
let my: string = "Hello World"
// 如果不声明, 会自动推断
let myName = "Bleak" // 将myName推断成string
myName = 100 // 报错, 不能将number分配给string.
3.5 函数
//
function greet (name: string): void {
console.log("Hello," + name.toUpperCase() + "!!!")
}
const greet2 = (name: string): string =>{
return "你好," + name
}
greet("Bleak")
console.log(greet2("黯淡"))
第一个name: string是参数类型注释, 第二个: void是返回值类型注释.
一般来说不用定义返回值类型, 因为会自动推断.
const names = ["xiaoqian", 'xiaoha', 'xiaoxi']
names.forEach(function(s) {
console.log(s.toUpperCase());
})
names.forEach(s => {
console.log(s.toLowerCase());
})
匿名函数与函数声明有点不同, 当一个函数出现在出现在TS可以确定它如何被调用的地方的时候, 这个函数的参数会自动的指定类型.
3.6 对象类型
function printCoord(pt: {x: number; y: number}) {
console.log("坐标的x值是: " + pt.x)
console.log("坐标的y值是: " + pt.y)
}
printCoord({x: 3, y: 7})
对于参数类型注释是对象类型的, 对象中属性的分割可以用 分号; 或者 逗号,
function printName(obj: {first: string, last?: string}) {
if(obj.last === undefined) {
console.log("名字是:" + obj.first)
} else {
console.log("名字是:" + obj.first + obj.last)
}
}
printName({
first: "Mr.",
last: "Bleak"
})
使用?可以指定对象中某个参数可以选择传入或者不传入, 不传入其值就是undefined.
如何在函数体内确定某个带?的参数是否传参了呢?可以使用两种方法
-
if(obj.last === undefined) {// 未传入时的方法体 } else {// 传入时的方法体 }
-
console.log(obj.last?.toUpperCase())
第二种方式更加优雅, 更推荐使用
3.7 联合类型
let id: number | string
TS的类型系统允许我们使用多种运算符, 从现有类型中构建新类型union.
联合类型是由两个或多个其他类型组成的类型. 表示可能是这些类型中的任何一种的值, 这些类型中的每一种被称为联合类型的成员.
function printId(id: number | string) {
console.log("当前Id为:" + id)
// console.log(id.toUpperCase())
if (typeof id === 'string') {
console.log(id.toUpperCase())
} else {
console.log(id)
}
}
printId(101)
printId('202')
如果需要调用一些参数的属性或者方法, 可以使用JS携带的typeof函数来进行判断并分情况执行代码.
function welcomePeople(x: string[] | string) {
if(Array.isArray(x)) {
console.log("Hello, " + x.join(' and '))
} else {
console.log("Welcome lone traveler " + x)
}
}
welcomePeople(["A", "B"])
welcomePeople('A')
根据分支来进行操作的函数.
// 共享的方法
function getFirstThree(x: number[] | string) {
return x.slice(0, 3)
}
都有的属性和方法, 可以直接使用.
3.8 类型别名
type Point = {
x: number
y: number
} // 对象类型
function printCoord(pt: Point) {
}
printCoord({x: 100, y: 200})
type ID = number | string // 联合类型
function printId(id: ID) {
}
printId(100)
printId('2333')
type UserInputSanitizedString = string // 基元类型
function sanitizedString(str: string): UserInputSanitizedString {
return str.slice(0, 2)
}
let userInput = sanitizedString('hello')
console.log(userInput)
type可以用来定义变量的类型, 如果是对象, 里面的属性和方法可以用逗号, 分号; 或直接不写来做间隔, 可以用来做一些平时经常会用到的类型来做复用, 其可以用于变量的类型指定上.
3.9 接口
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {
console.log("坐标x的值是: " + pt.x)
console.log("坐标y的值是: " + pt.y);
}
printCoord({ x: 100, y: 100 })
可以用接口来定义对象的类型, 几乎所有可以通过interface来定义的类型都可以用type来定义
类型别名type 和接口interface之间的区别:
- 扩展接口: 通过extends
// 扩展接口
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
const bear: Bear = {
name: 'winie',
honey: true
}
console.log(bear.name, bear.honey)
扩展类型别名: 通过 &
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
const bear: Bear = {
name: "winie",
honey: true
}
-
向现有的类型添加新字段
接口: 定义相同的接口, 其字段会合并.
interface MyWindow {
count: number
}
interface MyWindow {
title: string
}
const w: MyWindow = {
title: 'hello ts',
count: 10
}
类型别名: 类型别名创建的类型创建后是不能添加新字段的
3.10 类型断言
const myCanvas = document.getElementById("main_canvas") // 返回某种类型的HTMLElement
// 可以使用类型断言来指定
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement
const myCanvas = <HTMLCanvasElement>document.getElementById()
类型注释与类型断言一样, 类型断言由编译器来删除, 不会影响代码的运行时行为, 也就是因为类型断言在编译时被删除, 所以没有与类型断言相关联的运行时检查.
const x = ('hello' as unknown) as number
如上代码可以在我们不知道某些代码是什么类型的时候断言为一个差不多的类型.
3.11 文字类型
除了一般类型string
和number
, 还可以在类型位置引用特定的字符串和数字.
一种方法是考虑js如何以不同的方式声明变量. var
和let
两者都允许更改变量中保存的内容, const
不允许, 这反映在TS如何为文字创建类型上
let testString = "Hello World";
testString = "Olá Mundo";
// 'testString'可以表示任何可能的字符串,那TypeScript是如何在类型系统中描述它的
testString;
const constantString = "Hello World";
// 因为'constantString'只能表示1个可能的字符串,所以具有文本类型表示
constantString;
就其本身而言, 文字类型不是很有价值
let x: "hello" = "hello";
// 正确
x = "hello"
// 错误
x = "howdy"
拥有一个只能由一个值的变量并没有多大用处!
但是通过将文字组合成联合,你可以表达一个更有用的概念——例如,只接受一组特定已知值的函数:
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
数字文字类型的工作方式相同:
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
也可以将这些与非文字类型结合使用:
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
还有一种文字类型:布尔文字。只有两种布尔文字类型,它们是类型 true
和 false
。类型 boolean 本身实际上只是联合类型 union
的别名 true | false
。
文字推理
当你使用对象初始化变量时,TypeScript 假定该对象的属性稍后可能会更改值。例如,如果你写了这样的代码:
const obj = { counter: 0};
if(someCondtion) {
obj.counter = 1
}
TypeScript 不假定先前具有的字段值 0
,后又分配 1
是错误的。另一种说法是 obj.counter
必须有 number
属性, 而非是 0
,因为类型用于确定读取和写入行为。
这同样适合用于字符串:
function handleRequest(url: string, method: 'GET' | 'POST' | 'GUESS') {
// ...
}
const req = { url: 'https://example.com', method: 'GET' };
handleRequest(req.url, req.method);
在上面的例子 req.method
中推断是 string
,不是 "GET"
。因为代码可以在创建 req
和调用之间进行评估,TypeScript 认为这段代码有错误。
有两种方法可以解决这个问题:
- 可以通过在任一位置添加类型断言来更改推理:
// 方案 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// 方案 2
handleRequest(req.url, req.method as "GET");
方案1表示“我打算 req.method 始终拥有文字类型"GET"
”,从而防止之后可能分配"GUESS"
给该字段。
方案 2 的意思是“我知道其他原因req.method
具有"GET"
值”。
- 可以使用
as const
将整个对象转换为类型文字
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
该as const
后缀就像const
定义,确保所有属性分配的文本类型,而不是一个更一般的string
或 number
。
3.12 null
和undefined
JavaScript 有两个原始值用于表示不存在或未初始化的值: null
和 undefined
.
TypeScript有两个对应的同名类型。这些类型的行为取决于您是否在tsconfig.json
设置strictNullChecks
选择。
-
strictNullChecks
关闭使用false,仍然可以正常访问的值,并且可以将值分配给任何类型的属性。这类似于没有空检查的语言 (例如 C#、Java)的行为方式。缺乏对这些值的检查往往是错误的主要来源;如果在他们的代码库中这样做可行,我们总是建议大家打开。
-
strictNullChecks
开启使用true,你需要在对该值使用方法或属性之前测试这些值。就像在使用可选属性之前检查一样,我们可以使用缩小来检查可能的值:
function doSomething(x: string | null) {
if (x === null) {
// 做一些事
} else {
console.log("Hello, " + x.toUpperCase());
}
}
- 非空断言运算符(
!
后缀)
TypeScript 也有一种特殊的语法 null
, undefined
, 可以在不进行任何显式检查的情况下,从类型中移除和移除类型。 !
在任何表达式之后写入实际上是一种类型断言,即该值不是 null
or undefined
:
使用?
可以指定对象中某个参数可以选择传入或者不传入, 不传入其值就是undefined.
function liveDangerously(x?: number | null) {
// 正确
console.log(x!.toFixed());
}
就像其他类型断言一样,这不会更改代码的运行时行为,因此仅 !
当你知道该值不能是 null
或 undefined
时使用才是重要的。
3.13 枚举
枚举是 TypeScript 添加到 JavaScript 的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。与大多数 TypeScript 功能不同,这不是JavaScript 的类型级别的添加,而是添加到语言和运行时的内容。因此,你确定你确实需要枚举在做些事情,否则请不要使用。可以在Enum参考页中阅读有关 枚举的更多信息。
// ts源码
enum Direction {
Up = 1,
Down,
Left,
Right,
}
console.log(Direction.Up) // 1
// 编译后的js代码
"use strict";
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Left"] = 3] = "Left";
Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));
console.log(Direction.Up);
3.14 不太常见的原语
值得一提的是JavaScript中一些较新的原语, 它们在 TypeScript 类型系统中也实现了。我们先简单的看两个例子:
bigint
从 ES2020(ES11) 开始,JavaScript 中有一个用于非常大的整数的原语BigInt
:
// 通过bigint函数创建bigint
const oneHundred: bigint = BigInt(100);
// 通过文本语法创建BigInt
const anotherHundred: bigint = 100n;
你可以在TypeScript 3.2发行说明 中了解有关 BigInt 的更多信息。
symbol
JavaScript 中有一个原语 Symbol()
,用于通过函数创建全局唯一引用:
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
// 这里的代码不可能执行
}
此条件将始终返回 false
,因为类型typeof firstName
和typeof secondName
没有重叠。