什么是ts
- ts就是typescript,是在js的基础上增加了一些限制,属于js的超集
- 因为js的规范很松散
使用ts
npm install typescript -g将ts下载到全局- 在使用目录下执行
tsc --init生成ts的配置文件tsconfig.json- ts是无法在浏览器直接运行的,需要进行编译
编译ts
tsc将目录下的所有ts都单独编译为对应的js后缀文件,比如less编译成css,会新建一个文件tsc --watch监听当前目录下所有的ts,如果修改就会自动编译tsc index.ts只编译index.ts
类型
- 给某个值使用类型限制,
在变量名后加:类型即可- 使用多个的话,
: 第一类 | 第二类,这样也称之为联合类型null和undefined可以为任意类型的子类型,也就是可以将他俩赋值给其他数据类型的变量,但是会爆红,这时候将tsconfig.json里的strictNullChecks改为false即可
string
- 字符串
number
- 数字
boolean
- 布尔
any
- 所有类型
HTMLElement
- dom节点
null
- 空
- 可以为其他类型的子类型
undefined
- 未定义
- 可以为其他类型的子类型
void
- 函数没有返回值,或者为return (null/undefined/不返回)
never
- 永远都不会出现的值,比如函数里面一直执行中途死循环或异常
// 在这里面可以看到,end永远不会执行,但是奇怪的是使用if则不行
function fn(): never {
console.log(1);
throw new Error('error');
console.log('end');
}
function fn(): never {
while(true){
console.log(1);
}
console.log('end');
}
字面量类型
- 在一个变量初始化时候,指定几个值,在后续赋值的时候,该变量只能赋值开始指定的值中的一个
- 使用方法 let 变量名: 值 | 值;
let Gender: '男' | '女';
Gender = '男';
Gender = '女';
Gender = '不男不女';//爆红
声明指定参数类型的数组
- 两种方法
- 方法1:
类型[]- 方法2:
Array<类型>
// 方法1
const ary: string[] = ['one','two'];
// 方法2
const ary2: Array<string> = ['one','two'];
声明元组
- 元组就是一个指定好长度和类型的数组,例如react的useState会返回固定的两个值
- 声明方法
[string, number]
const arg: [string, number] = ['文字', 1]
枚举 enum
- 用法:生成一个
可枚举的对象- 如果不赋值的话,会默认将索引赋值给属性名
- 可以通过属性值拿到属性名,也可以通过属性名拿到属性值
- 使用方法
enum 名字 {key=name,key=name},注意赋值的时候用=而不是:- 如果不同的属性赋值为一个值的话,那么我们通过他的值访问到的将会是最后一个属性名
enum Stu {
age,
name
};
console.log(stu[0], stu[1], stu['age'], stu['name']); // age name 0 1
// 编译结果,可以看出他是将一个键值对赋值两次,正着一次,反着一次
var stu;
(function (stu) {
stu[stu["age"] = 0] = "age";
stu[stu["name"] = 1] = "name";
})(stu || (stu = {}));
;
console.log(stu[0], stu[1], stu['age'], stu['name']);
声明一个类 type
- 可以使用type声明一个类,来减少重复的书写类似的限制
- 格式 type 类名 = (参数:类型,参数:类型)=>返回值的类型
- 注意的是,=>后如果为{},那是需要返回一个对象,而不是像箭头函数一样执行
- 如果是可选参数,那么就在声明的时候再:前加?
function fn(name: string, age?: number): { name: string } {
return { name: '' };
}
type Stu = (name: string, age?: number) => { name: '' };
let fn2: Stu = function (name, age) {
return { name: '' };
}
fn2('')
断言
非空断言,在变量名后使用!表示一定存在类型断言,将一个联合类型的变量,指定为一个具体的类型,但是不能指定为联合类型中不存在的类型,用法为(变量 as 类型)
// 因为dom可能拿不到,那么root会爆红,那么使用的时候在root后加上! 表示我知道他肯定存在就ok
let root: HTMLElement | null = document.querySelector('#root');
root.style.color = 'red';//爆红
root!.style.color = 'red';//OK
// 类型断言
let age: string | number = 1;
(age as number).toFixed(1);
函数重载
- 声明一个函数的参数以及返回值类型
- 用法,与函数保持同名,且中间不能有其他的js语句,否则会报错
const obj: any = {};
function fn(val: any) {
if (typeof val === 'string') {
obj['name'] = val;
}
if (typeof val === 'number') {
obj['age'] = val;
}
}
fn('name');
fn(21);
fn({});//不报错
// 使用函数重载
function fn(val: number): void;
function fn(val: string): void;
function fn(val: any) {
if (typeof val === 'string') {
obj['name'] = val;
}
if (typeof val === 'number') {
obj['age'] = val;
}
}
fn('name');
fn(21);
fn(null);//报错
// 也可以使用type或直接在参数上标记为联合类型
// 但是复杂需求可以使用这个,比如说传入number输出number,传入string输出string,使用(val: number | string): number | string 显然达不到需求效果,没有那么精准
class 类
- 内置了get与set方法,用的是Object.defineProperty来监听
- 在
属性名前使用readonly可以将值设置为只读- 可以使用在constructor的()中使用public将参数放在实例上
- 如果想在类里面初始化的时候只限制类型,但不赋值,会爆红,如(name:string;),解决方法:在tsconfig.json里将
strictPropertyInitialization改为false即可
class A {
// myname: string = '空';如果使用public的话,这句话就可以省略
readonly age: number = 1;
constructor(public myname: string = '空', public readonly money: number = 100) {
}
get name() {
return this.myname;
}
set name(val: string) {
this.myname = '名字' + name;
}
}
let newA = new A;
console.log(newA);
console.log(newA.name);
newA.name = 'name';
console.log(newA.name);
继承
- 在
属性名前使用public自己以及自己子类或孙等类都能访问- 在
属性名前使用protected受保护的,只有自己和自己的子类可以访问- 在
属性名前使用private私有的,只有自己能访问- 假设A类一个属性设置了pricate,子类B拿不到,但是可以在A类上声明一个方法,这个方法内部可以拿到这个属性,而这个方法不是私有的,那么B可以间接的拿到这个属性
class A {
// 公开的
public name: string;
// 受保护的
protected age: number;
// 私有的
private sexs: '男' | '女' = '男';
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
getSexs(){
console.log(this.sexs);
}
}
class B extends A {
money: number;
constructor(name: string, age: number, money: number) {
super(name, age);
this.money = money;
console.log(this.age,'儿子可以访问');
console.log(this.sexs,'儿子也不可以访问');//爆红,私有的
}
}
let newB = new B('大大泡泡', 21, 0);
newB.getSexs();
console.log(newB.sexs);//爆红,因为是私有的,孙子不可以访问
console.log(newB.age);//爆红,因为是受保护的,孙子不可以访问
console.log(newB.name);
newB.getSexs();
装饰器 @
- 使用@+名字来使用装饰器
- 使用前需要先声明一个函数
- 如果直接使用,那么紧贴需要使用的属性或方法,且不用加()
- 如果加()那么声明的函数就需要在内部进行return一个新的函数
- 执行顺序是谁距离目标近就先执行谁,然后将结果给到下一个
类装饰器
- 可以重构类,起到增加属性的作用
- target就是传递进来的类
function addA(target: any) {
console.log(target);//A
return class B extends target {
public name: string = 'cuicui';
money: number = 100;
}
}
@addA
class A {
}
let newA = new A;
console.log(newA);//{name: 'cuicui', money: 100}
属性装饰器
- 在class里面使用@名字,然后在类外面声明一个同名函数
- 如果作用在属性上,接受两个参数,第一个是target,第二个是name
- 如果作用在函数上,接受三个参数,第一个是target,第二个是name,第三个是config(代表的是他的配置,是一个对象)
- target代表的是prototype,name代表的是属性名
- 访问static静态属性的时候,target就是类本身,但是需要先在类本身上先声明属性,哪怕不赋值
- 一个属性装饰器可以被多个属性使用
function upperCase(target: any, propertyName: string) {
let value: string = target[propertyName];
const getter = () => value;
const setter = (newVal: string): void => {
value = newVal.toUpperCase();
};
delete target[propertyName];
Object.defineProperty(target, propertyName, {
get: getter,
set: setter
});
}
function getMethods(target: any, propertyName: string, ada: any) {
console.log(target,propertyName,ada,999);
}
class A {
@upperCase
name: string = 'cuicui';
money: string = 'money';
@getMethods
getMoney() {
console.log('get');
}
@upperCase
static age: string;
}
let newA = new A;
newA.name = 'haha';
console.log(newA.name);//'HAHA'
console.log(A.age);//age
A.age = 'age';
console.log(A.age);//Age
参数装饰器
- 三个参数:目标(prototype),函数名字,参数索引
function addAge(target: any, name: string, index: number) {
console.log(target, name, index);//A.prepertype 'login' 1
}
class A {
login(username: string, @addAge password: string) {
console.log(username, password);
}
}
let newA = new A;
newA.login('cuicui', 'password');
抽象类 abstract
- 在未定义的类和属性前加上abstract
abstract class All {
name: string;
abstract getName(): string;
}
class Cat extends All {
getName(): string {
return this.name;
}
}
let cat = new Cat;
cat.name = '猫';
console.log(cat.getName());//猫
let dog = new Cat;
dog.name = '狗';
console.log(dog.getName());//狗
接口 interface
- 可以当做一种抽象规范来限制
- 一个类可以实现多个接口,以,间隔即可,使用inmpements实现
//给对象限制数据结构
interface Stu {
name: string,
age: number
};
let cui: Stu = { name: 'cui', age: 21 };
// 给函数限制
interface fn {
(val: number): number;
}
let myFn: fn = function (age) {
return 1;
}
myFn(1);//正常
myFn('1');//爆红
// 类实现2个接口
interface FnOne {
getOne(): string
}
interface FnTwo {
getTwo(): void
}
class A implements FnOne, FnTwo {
getOne() {
return ''
};
getTwo() { };
}
// 限制构造函数
class A {
constructor(name: string) {
}
}
interface bindA {
// 这个new限制的是A的constructor
new(name: string): A
}
function creatA(clazz: bindA, name: string) {
return new clazz('');
}
interface常用方法
- 通常定义了一个接口,包含了a,b两个值,但后续赋值的时候,会有一些额外的值,会ts抛异常
interface I{
a: string
b: number
}
const test1:I = {a:'', b:1};//OK
const test1:I = {a:'', b:1, c:1};//异常
解决办法1
const test2 = {a:'', b:1, c:1};
test1 = test2;
解决办法2
interface I2 extends I{
c: number
}
const test2:I2 = {a:'', b:1, c:1};
解决办法3
//相加
const test2:I1 & {c: number} = {a:'', b:1, c:1};
解决办法4 快速
const test1:I = ({a:'', b:1, c:1}) as I;
解决办法5 推荐
//[key:string]:any 表示所有未知的key只要是string类型,且值是any都可以,所以可以有多个key,省事
interface I{
a: string
b: number
[key:string]:any
}
const test1:I = {a:'', b:1, c:1};
泛型
- 泛型就是广泛的类型
- 定义的时候,放在括号前面
- 使用方法是:在变量名字后面<自定义泛型名>,使用的时候就可以直接使用 泛型名了
- 可以理解为泛型是在变量调用的时候才传递过来的一个值
在函数中使用
//箭头函数定义泛型
const fn = <T>(props:T)=>{};
function createArray(len: number, val: any): Array<any> {
const result: Array<any> = [];
for (let i = 0; i < len; i++) {
result[i] = val;
}
return result;
}
createArray(2, 'x');
// 换成泛型 函数
function createArray1<T>(len: number, val: T): Array<T> {
const result: Array<T> = [];
for (let i = 0; i < len; i++) {
result[i] = val;
}
return result;
};
createArray1<string>(2, 'x');
createArray1<number>(2, 'x');//爆红
createArray1<number>(2, 9);
在类里面使用泛型
class A<T>{
constructor(name: T) {
}
num: T;
}
new A<string>('');
接口里使用泛型
- 接口中分内部声明与外部声明
- 在内部声明的话,那么泛型只针对声明泛型的自己
- 在外部声明针对的是整个接口
// 内部
interface Num {
<T>(a: T, b: T): T
};
let add: Num = function <T>(a: T, b: T): T {
return a;
};
add<number>(5, 5);
// 外部
interface Num<T> {
name: T,
age: T
};
let add: Num<number> = {
name: 1,
age: 2
}
多个泛型
- 以,间隔即可
function nums<A, B>(arg: [A, B]): [B, A] {
return [arg[1], arg[0]];
}
nums<number, string>([1, '22']);
赋默认值
- 其实如果不传递的话,会根据自己传递的参数来判断泛型
- 也可以像给形参赋默认值一样
泛型=类型
function nums1<A = number, B = string>(arg: [A, B]): [B, A] {
return [arg[1], arg[0]];
}
nums1([1, '22']);
泛型的约束
- 因为泛型在执行前还没定义,所以会导致我们无法使用未定义的值身上的方法,哪怕我们知道他的类型,但是ts不知道
- 解决方法:让泛型继承一个接口,接口内部声明我们需要使用的属性或方法
interface Len {
length: number
};
function args<T extends Len>(val: T) {
console.log(val.length);
}
泛型类型别名
- 配合type来使用
type List<T> = { list: T[] } | T[];
let list1: List<string> = { list: ['1'] };
let list2: List<string> = ['1'];
泛型接口和泛型类型别名的区别
- 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名
- 类型别名不能被extends和implements,这个时候我们应该使用接口来代替类型别名
- 当我们需要使用联合类型或元组类型的时候,类型别名会更合适
- type可以做 循环和逻辑判断,interface不可以
兼容性
基本数据类型的兼容性
- 可以在原型上找到就不报错
let tos: {
toString(): string
};
tos = {
toString() {
return '';
}
};//ok
tos = {
toString() {
return '';
},
x: ''
};//爆红,因为限制了只有一个
tos = '';//成功,因为string原型上有toString方法
接口的兼容性
interface A {
name: string,
age: number,
}
interface B {
name: string,
age: number,
money: number
}
function getName(a: A): string {
return a.name
}
getName({ name: '', age: 10, money: 10 });//爆红
let b: B = { name: '', age: 10, money: 10 };
getName(b);//不爆红了,一个新的接口,里面的内容包含他就ok
类的兼容
- 可以看出,只要是赋值的结果和new的结果结构一致即可
- 或者,找一个新的变量,其内部的值大于等于原有的值,也可以成功
lass A {
name: string;
}
class B extends A {
}
class C extends A {
age: number = 10;
}
let a: A;
let b: B;
let c: C;
a = new A;//ok
a = new B;//ok
b = new B;//ok
b = new A;//ok
c=new A;//爆红
c=new B;//爆红
c=new C;//ok
c={age:10,name:''};//ok
let d={age:10,name:'',money:10};
c=d;//ok
函数的兼容
- 形参可以少,但是不能多
- 返回值可以多,但是不能少
- 参数的类型可以多,但是不能少
type sumType = (name: string, age: number) => number;
let sum: sumType;
let fn1 = function (name: string, age: number, money: number): number {
return 1;
}
sum=fn1;//爆红
let fn2 = function (name: string, age: number): number {
return 1;
}
sum=fn2;//ok
let fn3 = function (name: string): number {
return 1;
}
sum=fn3;//ok
let fn4 = function (): number {
return 1;
}
sum=fn4;//ok
接口的兼容性
- 不同泛型,但值固定,可以相互赋值
- 不同泛型,值不固定,不可以相互赋值
interface A<T> {
data:T
}
let a:A<string>;
let b:A<string>;
let c:A<number>;
a=b;//ok
a=c;//爆红
interface B<T> {
data:1
}
let a:B<string>;
let b:B<string>;
let c:B<number>;
a=b;//ok
a=c;//ok
枚举的兼容
- 枚举对象内有,就可以赋值,否则不行
enum Color {
Red, Yellow
};
let c: Color;
c = Color.Red;//ok
c = 1;//ok
c = 0;//ok
c = Color.Yellow;//ok
c = '哈哈';//爆红
类型保护
- 使用判断,来更精准的识别数据类型或指定的类,这样就可以使用对应的方法
- 可使用typeof、instanceof、in逻辑判断来给ts进行明确的指引
type All = string | number | boolean;
function all(val: All) {
val.toFixed();//爆红
if(typeof val === 'number'){
val.toFixed();//ok
}
};
all(1);
//demo 2
class A {
public name: string = '1';
}
class B{
public age: number = 1;
}
function getName(a: A) {
a.age;//爆红
if(a instanceof B){
a.age;//ok
}
}
//------------
class A {
name: string
}
class B { }
function getVal(v: A | B) {
v//A|B
if (v instanceof A) {
v;//A
} else {
v;//B
}
};
function getVal2(v: A | B) {
if ('name' in v) {
v;//A
} else {
v;//B
}
}
链式写法
a?.b- 先判断a是否存在,如果为null或undefined就返回null或undefined,如果存在就找里面是否存在b,如果b存在就返回b的值,如果不存在,返回undefined
a?.b等价于a == null? a: a.b
let a;
console.log(a?.b);//undefined
a={b:1};
console.log(a?.b);//1
a=1;
console.log(a?.b);//undefined
a?.b();//如果b不是函数的话会抛出错误
自定义的类型保护
- 如果使用2个类来限制类型,可以通过定义一个函数来当前判断使用的是哪个类
- 函数返回值为布尔
interface A {
name: string
}
interface B {
age: number
}
// 判断函数,return的必须是一个布尔值
function isA(x: A | B): x is A {
return (x as A).name == '';
}
function getA(x: A | B) {
if(isA(x)){
return x.name;
}
return x.age;
}
交集&
- 可以使用&将两个类进行合并,相同的值,后面会覆盖前面
interface A {
name: string
money:number
}
interface B {
age: number
name: number
}
type All = A & B;
let p: All = {
name:'',//爆红
age:1,//ok
money:1,//ok
}
typeof
- ts对typeof进行了二次封装
- 在原来的基础上,还可以识别ts类,但
识别的ts类需要用type来接收(虽然使用let不报错),且接收后可以当做类型给其他值使用
interface A {
name: string
money: number
}
function a(x: A) {
type B = typeof x;
let a = typeof '';
console.log(a);//string 不影响旧的功能
console.log(B);//爆红,因为B是一个接口类,不能输出
let D: B = {
name: '',
money: 1
};//ok
}
a({ name: '', money: 1 });
Exclude
- 排除
type I = Exclude<number|string|boolean, number|boolean>;//string
//会将第一项的挨个去第二项找,有就排除,没有留下
Extract
- 筛选
- 查看参数2中是否存在参数1,存在就返回,没有就never
type R = Extract<string, number | string>;//string
索引操作符
- 可以拿到子级的类型给到其他地方用
interface A {
name: string,
job: { name: string }
}
let haha: A['name'] = '';//ok
let dada: A['name'] = 1;//爆红
let xiaoxiao: A['job'] = { name: '' };//ok
索引集合 keyof
- 可以通过
keyof 接口拿到当前接口内的所有集合,且把它当做一个类
interface A {
name: string,
age: number
}
type Akeys = keyof A;
// 此时的Akeys相当于 'name' | 'age'
let b: Akeys = 'age';
特殊记忆
keyof any; //symbol | number | string 代表任何能作为key的类型
映射类型
在定义的时候用in操作符去定义
interface A {
name: string,
age: number
}
type AKeyType = { [key in keyof A]?: A[key] };
let a: AKeyType = { name: '' };//ok
let b: AKeyType = { age: 1 };//ok
let c: AKeyType = { name: '', age: 1 };//ok
let d: AKeyType = { name: '', age: 1, money: 1 };//爆红
遍历
//声明了一个K来遍历交集的keys集合,取值的时候使用K来取,有点像js的key in {}
interface Btn {
size: "mini" | "large"
type: "primary" | "success"
}
interface Event {
click: (eventName: string) => void;
}
//type BtnType = Btn & Event;//鼠标悬浮BtnType,提示Btn & Event 不够清晰,声明的时候extends也是一样,提示不清晰
type Compute<T> = { [K in keyof T]: T[K] }
type BtnType = Compute<Btn & Event>;//有size、type、click 清晰
Partial
- 将所有参数变为可选的
- 内部还是遍历
//实现 type Partial<T> = { [K in keyof T]?: T[K] }
type I = Partial<{ a: string, b: number }>;// {a?:string,b?:string}
Required
- 将所有参数变为必填的
//实现 type Required<T> = { [K in keyof T]-?: T[K] }
type I2 = Required<{ a?: string, b: number }>;//{a:string,b:string}
Omit
- 用于object
- 移除某一项
//实现type Omit<T extends object, K extends keyof T> = { [X in Exclude<keyof T, K>]: T[X] };
//Compute只是为了看起来清晰
type I3 = {
name: string
age: number
address: string
}
type I3OPtions = Compute<Omit<I3, 'address'>>//{ name: string,age: number}
Pick
- 用于object
- 拿到object中的key,组成新的类
//实现type Pick<T, K extends keyof T> = {[P in K]: T[P];};
type I3 = {
name: string
age: number
address: string
}
type I4 = Pick<I3, 'name' | 'age'>;//{name: string, age: number}
条件限制
- 通过泛型和三元运算符来计算type
- 此时的
extends可以理解为是不是,而不是继承
interface A {
name: string
}
interface B {
age: number
}
interface C {
money: number
}
type D<T> = T extends A ? B : C;
let b: D<A> = {
name: ''
};//爆红
b = {
age: 1
};//ok
Record
- 限制对象的key和value类型
- Record要求第一个值只能是 number | string | symbol
let obj: Record<symbol, number> = { [Symbol(1)]: 1 }
命名控件 namespace
- 会形成一个单独的作用域,可以理解为是闭包
- 在内部可以使用export导出一些内容
- 两个namespace互不影响
namespace A {
console.log(1);
let c = 1;
export let a: number = 1;
}
console.log(A.c);//undefined
console.log(A);//{a:1}
声明 declare
declare namespace Jquery {
function ajax(path: 'string', config: any): any
let name: string
}
console.log(Jquery.ajax);//Jquery is not defined
declare let a: string = 1;//报错,因为只能声明
declare let myname: string;//ok
声明文件
- 以.d.ts为后缀的都是声明文件
- 在这里面我们可以将一些常用的方法和变量用declare声明,起到全局声明的效果
常见问题
引入第三方模块,爆红,显示缺少声明文件
- 比如juqery缺少@types/juqery
- 可以安装Types Auto Installer插件来自动补全@types,也可以手动安装对应的声明文件
判断一个参数是否可以被new
class C {}
//行间写法
function get<T>(clazz:new ()=> T) {
return new clazz();
}
get<C>(C);//OK
get(C);//OK ts自动进行类型推导,可以得到返回值是C的实例
//接口写法
interface Clazz<T> {
new ()=> T
}
function get<T>(clazz:Clazz<T>) {
return new clazz();
}
get<C>(C);//OK