typescript学习笔记-入门篇(小满zs+官方文档)
implements:使用implements来实现一些类共有方法属性的提取,interface提取,implements实现。因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。static属性加载在其他属性之前,static初始化的时候别的属性还不存在,调用不了。教程用例比较复杂,可以理解为在手写vue源码的一部分,虚拟dom渲染为真实dom。注意:断言只能欺骗一下ts编译器,实际传数
ts官方文档
小满主页
remake
很久没登CSDN了,没想到当时随便记录的一篇居然挺多人看的。更新一波把坑填了,顺便也回顾一下ts的相关内容
推荐一篇很nice的ts入门博客:typescript史上最强学习入门文章(2w字)
至少我看完这篇之后项目里基本随便用了,只不会做一些很难的类型体操(个人目前认为这完全是没有必要的,当然会是更好,而且也有面试会考相关的知识点)
小满zs的ts到目前,集成了一些es6特性和前端工程化相关的内容,实操性上更友好,比较适合作为新手上路的实践教程。不过我也是后面二刷了部分才理解到讲的一些点。
然后分享一些这么久以来使用ts的感受吧。一开始确实是恨的,毕竟本来唰唰两下写完的js,现在还要花额外的时间去标注各种各样的类型,一些复杂的类型写起来也很让人烦躁。
然而到现在习惯ts之后,拿到一个js的文件反而会有些晕了,而且项目中基本都会默认的先去思考类型再动手实现,并且尽量做到完整的类型标注。
个人向的优点:
代码即文档 & 智能提示。
这点真的非常难能可贵,尤其是针对具有完备类型提示的第三方库而言,在使用第三方函数的时候都是一个黑盒,然而我们可以通过vscode F12直接跳转到其类型定义,这样就能大概的清楚这个函数的作用以及如何使用(传参)。对于其提供的对象,.
一下就能看到他有的属性,这样也省去了记忆的时间。
可读性上,有的人认为优秀的代码是“自注释”的,我非常不认同这点。团队中每个人的技术栈、编码水平、编码风格都是不固定的,好的代码可读性会更好,但也不能因此否认注释的作用,毕竟每个人眼中的“好代码”也不尽相同(当然坏代码是加再多注释都没用的)。而ts就相当于提供了一个统一的规范,通过其类型检查,再加上eslint、lint-staged、husky等配置,就可以一定程度上减少协作开发时遇到💩代码的可能性。
1(由于小满zs视频更新,顺序已不对应)
安装ts
npm i typescript -g
tsc -v
查看版本号
调试
tsc --init
初始化ts,生成tsconfig.json
tsc -w
编译出一个js文件、
node index.js
node调试js文件
其他
NaN、Infinity也算number类型
还有一些进制也算 比如hex
void类型是null/undefined,一般用于函数
严格模式void不能被赋为null
严格模式在cfg里面"strict"里改
非严格模式下,undefined和null可以穿插赋值
2
安装库
npm i ts-node -g
配置镜像库:
npm i xmzs -g
其他命令
mmp ls
mmp -h
mmp use
生成package.json
npm init -y
生成配置文件node_modules
npm i @types/node -D
然后就可以直接使用 ts-node index.ts
来看到输出了
类型
顺序
- any unknown
- Object
- Number String Boolean
- number string boolean
- 1 “qwq” false
- never
其他
unknown 只能赋值给自身或者any
unknown 不能用.读到属性
3 Object、object、{}
Object
和any差不多
object
引用数据类型
{}
相当于new Object
但是赋值后无法修改
4 接口
特点
- 接口和类型必须一模一样
- 接口重名,重合
- 索引签名
[propName:string]:any
让其他属性取消强校验 ?
标志属性非必填- readonly 限制只读,常用于函数和id
- 继承 extend
- 函数接口,写对参数和返回值的约束
注意限制的位置
interface Fn {
(name: string): number[];
}
const a: Fn = function fn(name: string) {
return [1];
};
5 数组
number[] = Array<number>
number[][] = Array<Array<number>>
对象数组用interface
interface x {
name: string;
}
const arr: x[] = [{ name: "qwq" }];
6 函数
- 函数定义类型和返回值
返回值在()后定义 - 默认值和可选参不可一起用
- 参数是对象,interface对参数约束就好
- ts可以定义this的类型
js不能用
interface Obj {
user: number[];
add(this: Obj, num: number): void;
}
const obj: Obj = {
user: [1, 2, 3],
add(num) {
this.user.push(num);
},
};
obj.add(4)
console.log(obj);
- 函数重载
let user: number[] = [1, 2, 3];
//传number类型数组 添加
function findNum(add: number[]): number[];
//传id,单个查询
function findNum(id: number): number[];
//没有传,查全部
function findNum(): number[];
//实现
function findNum(ids?: number | number[]): number[] {
//判断类型是否为数组用Array的方法
if (Array.isArray(ids)) {
user.push(...ids);
return user;
}
//判断是否为number这类用typeof
else if (typeof ids == "number") {
return user.filter((v) => v == ids);
}
//最后也要有返回值
else {
return user;
}
}
findNum([4]);
console.log("查全部", findNum());
console.log("查单个", findNum(1));
6
| 联合
!!强转
& 交叉(将两个类型合起来)
类型断言:
(type as A)或者(<A>type)
注意:断言只能欺骗一下ts编译器,实际传数据传的啥类型还是啥类型
7 内置对象
第7节的代码雨还是很有意思的
https://www.bilibili.com/video/BV1wR4y1377K?p=8&vd_source=133a4c6b8765759be3947374e6336df7
ecma
Number Date RegExp Error XMLHttprequest
let num: Number = new Number(1);
dom
bom
自行查阅吧^^
8 class
和java/c++里学的类实现差不多
教程用例比较复杂,可以理解为在手写vue源码的一部分,虚拟dom渲染为真实dom
-
基本用法
extends:继承
implements:使用implements来实现一些类共有方法属性的提取,interface提取,implements实现 -
修饰符
readonly
public
private 只能内部
protected 给子类和内部
在外面都是不能用.访问到的 -
super调父类方法
实质为父类的prototype.constructor.call -
static
静态方法只能被类本身调用,实例无法调用
在static方法里的this也只能访问带static的东西。
原因:
static属性加载在其他属性之前,static初始化的时候别的属性还不存在,调用不了 -
getter setter
用get和set关键字
存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。
9 class
抽象类abstract
猫狗动物的例子挺好的hh
特点:
抽象方法只能描述不能实现
抽象类不能被实例化
类可以当作接口使用
类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。
自己写的一个很屑的案例
abstract class Animal {
type: string;
constructor(type: string) {
this.type = type;
}
abstract speak(): void;
}
class Cat extends Animal {
constructor(name: string) {
super("Cat");
this.name = name;
}
name: string;
speak(): void {
console.log(`${this.type} + ${this.name}`);
}
}
let c = new Cat("qwq");
c.speak();
不要整出例如这样的代码就可以了
//不能实例化
let d = new Animal()
//不能实现
abstract speak(): void{
console.log(this.type);
}
10 元组
const arr: [x: string, y: number] = ["123", 123];
越界后会推断为联合类型
arr.push(1234) 对
arr.push(true) 错
可用readonly修饰(const不能让元组只读)
可用?
使用typeof获取元组的类型
type first = typeof arr[0]
略显奇怪的用法
type first = typeof arr["length"]
可以得到元组的大小为2
实例:用于描述excel数据
11 枚举
enum关键字
可用可不用,相当于是一个对象
数字枚举
enum Color {
//默认从0开始
red = 1,
//为2
blue,
white = 6,
}
console.log(Color.blue);
字符串枚举
必须都初始化一下
异构枚举
混着写是可以的
接口枚举
在接口中写枚举,更优雅一些(有一条规范是不要出现任何常量的直接赋值)
enum Color {
red = "qwq",
blue = 1,
}
interface A {
red: Color.red;
}
let obj: A = {
red: Color.red,
};
const枚举
没啥应用场景
直接编译为常量
const enum Color {
red,
blue,
}
let code: number = 0;
if (code === Color.red) {
console.log("yes");
}
编译为js
var code = 0;
if (code === 0 /* Color.red */) {
console.log("yes");
}
不加const的编译:对象形式
var Color;
(function (Color) {
Color[Color["red"] = 0] = "red";
Color[Color["blue"] = 1] = "blue";
})(Color || (Color = {}));
var code = 0;
if (code === Color.red) {
console.log("yes");
}
反向映射
字符串不可以用这玩意
可以互相取挺方便的,value拿key就用这招
enum Color {
red,
blue,
}
let code: number = Color.red;
let key = Color[code];
console.log(code);//0
console.log(key);//red
11 类型推论 类型别名
类型推论
不写:的情况下,会自动推断变量的类型,默认any
别名
type关键字
type-interface
type不能用extends,可以使用&,|来定义交叉、联合类型
type重名不会合并
高级用法
type num = 1 extends never ? 1: 0
num为0,extends:包含
左边的值会作为右边类型的子类型
12 never类型
type a = number & string
a为never类型
然后对于函数,死函数/运行报错函数设置为never类型
function a():never{
while(true){
}
}
never处于类型的最底级,联合没用
一般用于switch语句中default的部分,通过never类型的错误找到逻辑错误
13 symbol类型
基础
唯一标识,在不同的内存空间
let a1: symbol = Symbol(1);
let a2: symbol = Symbol(1);
console.log(a1 == a2);//false
Symbol.for
for全局symbol是否注册过这个key,有就用,无才创建。但是需要传字符串值
let a1: symbol = Symbol.for("1");
let a2: symbol = Symbol.for("1");
console.log(a1 == a2);//true
用处:解决属性的key取重复的问题
因为对symbol而言,就算里面的值是一样的,也算不同的
let a1: symbol = Symbol(1);
let a2: symbol = Symbol(1);
let obj = {
[a1]: 123,
[a2]: 234,
//name: "qwq",
//name: "sayori",
//报错:对象文本不能具有多个名称相同的属性,在js中会下面的覆盖上面的
};
console.log(obj);
//{ [Symbol(1)]: 123, [Symbol(1)]: 234 }
问题:
for in 、Object.keys 、Object.getOwnPropertyNames都不能读到symbol
需要使用
Object.getOwnPropertySymbols,但是这个只能单独取symbol
使用Reflect.ownKeys才可以都取到
生成器 generator函数
function* gen() {
//同步/异步都可以
yield Promise.resolve("qwq");
yield "123";
yield "sayoriqwq";
}
const A = gen();
console.log(A.next());
console.log(A.next());
console.log(A.next());
console.log(A.next());
输出:
{ value: Promise { 'qwq' }, done: false }
{ value: '123', done: false }
{ value: 'sayoriqwq', done: false }
{ value: undefined, done: true }
done为true说明迭代完成了
迭代器
和生成器其实差不多,也是一个函数,用法也是一样的
let arr = [1, 2, 3];
console.log(arr[Symbol.iterator]().next().value);//1
set map
set自带去重
let set: Set<number> = new Set([1, 1, 2, 2]);
console.log(set);//Set(2) { 1, 2 }
map相关方法
let map: Map<any, any> = new Map();
let arr = [1, 2, 3];
map.set(arr, "qwq");
console.log(map.get(arr));//qwq
然后讲了一些伪数组
arguments,querySelectorAll
迭代器应用场景:支持遍历所有这些乱七八糟的数据类型
语法糖 for of
二者等效,也可以用来遍历map、array等实现了iterator接口的对象
let set: Set<number> = new Set([1, 1, 2, 3]);
const each = (value: any) => {
let It: any = value[Symbol.iterator]();
let next: any = { done: false };
while (!next.done) {
next = It.next();
if (!next.done) {
console.log(next.value);
}
}
};
for(let value of set){
console.log(value);
}
注意:for of对象不能用,对象身上没有iterator
原生object对象是默认没有部署Iterator接口,即object不是一个可迭代对象。但不是有{...obj}
的解构语法吗?
数组解构:通过模式匹配来完成。按照位置来分配值。(基于索引)
对象解构:通过属性名来匹配。会尝试寻找同名的属性,并将其值赋给对应的变量。(基于属性名)
使对象支持for of:手动实现iterator方法
let obj = {
max: 5,
current: 0,
//配置迭代器
[Symbol.iterator]() {
return {
//迭代到下一层之前把属性速记
max: this.max,
current: this.current,
next() {
//迭代完成
if (this.current == this.max) {
return {
value: undefined,
done: true,
};
}
//继续迭代
else {
return {
value: this.current++,
done: false,
};
}
},
};
},
};
for (let value of obj) {
console.log(value);
}
console.log([...obj]);
14 泛型
动态类型,ts中的核心概念,可以说学会了泛型就学会了入门级的ts
概念
function qwq<T>(a: T, b: T): T[] {
return [a, b];
}
//一样的,一般让ts自己推断类型
console.log(qwq(1, 2));
console.log(qwq<number>(1, 2));
类型泛型:
type A<T> = string | number | T;
let a: A<null> = null;
接口也可以用
相比于any,泛型可以推断类型,而any只能是any
泛型也可以支持多个
function add<T, K>(a: T, b: K): (T | K)[] {
return [a, b];
}
add(1,false)
可以自己写一下代码,就可以看到在写的时候ts对类型的推断了,不传的时候是unknown,传的时候推断为我们传的值的类型
实战
调接口的时候用
https://www.bilibili.com/video/BV1wR4y1377K/?p=17&spm_id_from=pageDriver&vd_source=133a4c6b8765759be3947374e6336df7
泛型约束
控制类型的范围
interface Len {
length: number;
}
function fn<T extends Len>(a: T) {
a: length;
}
fn(111);//错,number类型没有length属性
fn("1q213");
fn([1, 2, 3]);
keyof 约束对象中的key
let obj = {
name: "sayori",
age: 123,
};
//T限制为传引用类型,K限制为传T的key值
function fn<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
console.log(fn(obj, "name"));//sayori
console.log(fn(obj, "qwe"));//报错,qwe不是obj里的一个key
而且在写代码的时候也会有一个很智能的提示
keyof 高级(手写类型工具partial)
interface Data {
age: number;
name: string;
sex: string;
}
type Options<T extends object> = {
readonly [key in keyof T]: T[key];
};
type A = Options<Data>
对A
来说,就已经实现了Data里每一项都为只读
当然也可以用?
修饰使得Data项目都可选
15 cfg配置文件
tsc --init
生成
配置文件里有注释,也可以去翻官方文档
16 namespace
实质是包了一层function
可以嵌套
在最新版本中,推荐使用ES Modules
作为模块化的解决方案,而不是过度使用namespace
抽离命名空间
以文件形式export
,然后import
简化命名空间
用import newName = A.B.C.D
的形式为命名空间取别名
ts-node
现在认识它了
重名算到一起(和interface
一样)
17 声明文件
对第三方包想要提供/获取ts类型提示:
declare
关键字声明模块
.d.ts
文件
有的包自带,有的包是后面布丁上的,装包的时候可以尝试用@types/{包名}来装一下对应的类型,没有就是未提供。
案例:对express进行了类型注解
18 类型守卫
类型收缩:宽类型(any等)在内部如果进行了类型判断(typeof
instanceof
等),推导时则会收窄其类型。
const isArr = (arr: any) => arr instanceof Array
类型谓词:is
关键字实现。给上述返回布尔值的条件函数赋予类型守卫的能力
const isArr = (arr: any): arr is any[] => arr instanceof Array
19 协变、逆变(鸭子类型)
值协变,函数逆变
核心:可以多,但不可以少
直接上例子
interface A {
name: string
age: number
}
interface B {
name: string
age: number
sex: string
}
let a: A = {
name: 'a',
age: 1,
}
let b: B = {
name: 'b',
age: 2,
sex: 'man',
}
a = b (发生协变)
// b = a(不允许)
let fna = (param: A) => {}
fna(b) (发生逆变)
// let fnb = (param: B) => {}
// fnb(a) //错误
fnb = fna
// fna = fnb //错误
双向协变:要这个特性去ts配置文件将
strictFunctionTypes
置为 false
结
ts的基础部分到此也就差不多结束了,掌握以上内容即可在项目中实现基本的类型标注,并且享受ts带来的好处了。
如果想要进一步提高ts水平,建议是首先尝试改造自己原有的项目(逻辑会比较熟悉),尽可能多的为其带上类型,不懂的地方善用搜索引擎和gpt。期间可能就会接触到一些ts高级特性,然后选学即可。然后可以阅读一些框架的源码(例如Vue3 watch部分的类型,真的是非常的复杂),或者自己尝试写一个mini版本标注一下。
查看方式:在你的项目中对第三方库的函数直接F12跳转,就可以看到对应的声明文件啦
如果想要成为ts高手,那么类型体操是必不可少的一环。可以自行查找资料,进行相关习题的针对训练。
更多推荐
所有评论(0)