开发环境搭建
基本类型
webpack打包ts
数组
TypeScript 像 JavaScript 一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上[],表示由此类型元素组成的一个数组:
let list1: number[] = [1, 2, 3]
第二种方式是使用数组泛型,Array<元素类型>:
let list2: Array<number> = [1, 2, 3]
前面Array 表示此数据类型
<number> 表示子元素类型
多维数组
let data:number[][] = [[1,2], [3,4]];
arguments类数组
function Arr(...args:any): void {
console.log(arguments)
//ts内置对象IArguments 定义
let arr:IArguments = arguments
}
Arr(111, 222, 333)
枚举
enum 类型是对 JavaScript 标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字。
enum Color {
Red,
Green,
Blue
}
// 枚举数值默认从0开始依次递增
// 根据特定的名称得到对应的枚举数值
let myColor: Color = Color.Green // 1
console.log(myColor, Color.Red, Color.Blue)
默认情况下,从 0 开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1 开始编号:
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green
或者,全部都采用手动赋值:
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为 2,但是不确定它映射到 Color 里的哪个名字,我们可以查找相应的名字:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]
console.log(colorName) // 'Green'
any
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any 类型来标记这些变量:
let notSure: any = 4
notSure = 'maybe a string'
notSure = false // 也可以是个 boolean
在对现有代码进行改写的时候,any 类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。并且当你只知道一部分数据的类型时,any 类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:
let list: any[] = [1, true, 'free']
list[1] = 100
注意:
元祖 Tuple
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string 和 number 类型的元组。
let t1: [string, number]
t1 = ['hello', 10] // OK
t1 = [10, 'hello'] // Error
当访问一个已知索引的元素,会得到正确的类型:
console.log(t1[0].substring(1)) // OK
console.log(t1[1].substring(1)) // Error, 'number' 不存在 'substring' 方法
注意:元组必须是一一对应,
接口
对象接口
interface Iperson{
name:string,
age?:number,
readonly sex:string
}
//? 表示该属性可有可无
const obj:Iperson={
name:'as',
age:20,
sex:'女'
}
console.log(obj);
// const 外面作为变量使用
// readonly 里面作为属性使用
interface Iperson{
name:string,
age?:number,
readonly sex:string
}
const obj:Iperson ={
name:'as',
sex:'女',
}
console.log(obj); 这样是没有问题的
interface Iperson{
name:string,
age?:number,
readonly sex:string
}
interface Iperson{
hibiy:string
}
//2个相同的接口,默认合并的
const obj:Iperson={
name:'as',
age:20,
sex:'女',
hibiy:'打排球'
}
console.log(obj);
interface Iperson{
name:string,
age?:number,
readonly sex:string
}
interface son extends Iperson {
hibiy:string
}
//接口还可以继承接口
const obj:son={
name:'as',
age:20,
sex:'女',
hibiy:'打排球'
}
console.log(obj);
任意属性 [propName: string]
interface Person {
b?: string,
readonly a: string,
[propName: string]: any;
}
const person: Person = {
a: "213",
c: "123"
}
//应为我们定义了[propName: string]: any;
//允许添加新的任意属性
函数接口
interface search{
(a:string ,b:string) :boolean
}
// fn以search这个接口来写
const fn:search=function(a:string,b:string):boolean{
return a.search(b)>-1
}
console.log(fn("abc" ,"a"));
数组接口
一般用来描述类数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
//表示:只要索引的类型是数字时,那么值的类型必须是数字
类实现接口
与 C# 或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约
/*
类类型: 实现接口
1. 一个类可以实现多个接口
2. 一个接口可以继承多个接口
*/
interface Alarm {
alert(): any;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
一个类可以实现多个接口
class Car2 implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
接口继承接口 和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里
interface LightableAlarm extends Alarm, Light {
}
注意:接口与接口之间叫继承(使用extends关键字),接口与类之间叫实现(使用的是implements)
promise
this???????????
类
// 定义类
class person{
name:string
age:number
sex:string
// 可以写默认值
constructor(name:string ='zs',age:number,sex:string){
this.name=name
this.age=age
this.sex=sex
}
say(str:string){
console.log(`我的是名字是${this.name}${str}`);
}
}
// 继承
class stu extends person {
constructor(name:string,age:number,sex:string){
super(name,age,sex)
// super调用父类的构造函数
}
say(str: string){
super.say("你的名字呢")
// 这里是用super调用父类的方法
}
}
const p= new person('ss',20,'男')
const s=new stu('go',10,'女')
补充知识( 来源于es6,阮一峰)
ES2022 为类的实例属性,又规定了一种新写法。实例属性现在除了可以定义在constructor()方法里面的this上面,也可以定义在类内部的最顶层。
// 原来的写法
class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
上面示例中,实例属性_count定义在constructor()方法里面的this上面
现在的新写法是,这个属性也可以定义在类的最顶层,其他都不变
class IncreasingCounter {
_count = 0;
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
上面代码中,实例属性_count与取值函数value()和increment()方法,处于同一个层级。这时,不需要在实例属性前面加上this。
注意,新写法定义的属性是实例对象自身的属性,而不是定义在实例对象的原型上面。
公共,私有与受保护的修饰符
class person{
private name:string
constructor(name:string){
this.name=name
}
say(){
console.log(this.name);
}
}
class dog extends person{
constructor(name:string){
super(name)
}
say() {
// console.log(this.name); 报错
// 子类的类内部也不能使用
}
}
const p=new person('ss')
const d=new dog('gg')
// console.log(p.name); 报错
// private 只能是类内部使用
// console.log(d.name);报错
private 只能是该类的内部使用
public 公共成员,类内部及外部均可以使用
protected 只能在该类的内部使用及子类的内部使用
抽象类
抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。
/*
抽象类
不能创建实例对象,
可以包含未实现的抽象方法
为了让子类来进行实例化及实现内部的抽象方法
*/
abstract class Animal {
abstract cry ()
run () {
console.log('run()')
}
}
class Dog extends Animal {
cry () {
console.log(' Dog cry()')
}
}
const dog = new Dog()
dog.cry()
dog.run()
函数
// 第一种声明式写法
function fn(a:string,b:string):string {
return a+b
}
const res:string =fn('123','456')
console.log(res);
// 第二种表达式
const say =function(a:number,b:number):number{
return a+b
}
const result:number =say(123,456)
console.log(result);
// 完整写法
let myAdd2: (x: number, y: number) => number =
function(x: number, y: number): number {
return x + y
}
console.log(myAdd2(123,456));
// 接口
interface F{
(a:number ,b:number): number
}
// 按接口F格式来写
const add:F =function(a:number,b:number):number{
return a+b
}
console.log(add(1,1));
可选参数和默认参数
function buildName(firstName: string='A', lastName?: string): string {
if (lastName) {
return firstName + '-' + lastName
} else {
return firstName
}
}
console.log(buildName('C', 'D'))
console.log(buildName('C'))
console.log(buildName())
剩余参数
在 TypeScript 里,你可以把所有参数收集到一个变量里:
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...)后面给定的名字,你可以在函数体内使用这个数组
function info(x: string, ...args: string[]) {
console.log(x, args)
}
info('abc', 'c', 'b', 'a')
函数重载 函数重载: 函数名相同, 而形参不同的多个函数
在JS中, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的 但在TS中, 与其它面向对象的语言(如Java)就存在此语法
/*
函数重载: 函数名相同, 而形参不同的多个函数
需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也可以接收2个number类型的参数进行相加
*/
// 重载函数声明
function add (x: string, y: string): string
function add (x: number, y: number): number
// 定义函数实现
function add(x: string | number, y: string | number): string | number {
// 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add(1, 2))
console.log(add('a', 'b'))
// console.log(add(1, 'a')) // error
泛型
指在定义函数、接口或类的时候,不确定具体的类型,而在使用的时候再指定具体类型的一种特性。
function createArray(value: string, count: number): string[] {
const arr: string[] = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const res=createArray('SS',3)
// 上面的第一个参数传入的是字符串,有用,但当我要传入数字呢,又失效了
function createArray2(value: number, count: number): number[] {
const arr: number[] = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const res2=createArray2(10,3)
// 上面的第一个参数传入的是数字,有用,但当我要传入别的类型呢,又失效了
function createArray3(value: any, count: number): any[] {
const arr: any[] = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const res3=createArray3('aaa',3)
res3[0].split('')
// 好像这样写没有问题,但是我要调用元素的方法,就没有任何的提示或错误等信息,
// 这时引入泛型
function createArray4<T>(value: T, count: number): T[] {
const arr: Array<T> = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const res4 =createArray4<string>('ssss',3)
const res5=createArray4<number>(10,3)
// 调用时传入类型,再也不用重写函数
res4[0].split('')
// 此时元素调用方法就有提示
多个泛型参数的函数 一个函数可以定义多个泛型参数
function swap <K, V> (a: K, b: V): [K, V] {
return [a, b]
}
const result = swap<string, number>('abc', 123)
console.log(result[0].length, result[1].toFixed())
泛型类
在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
class GenericNumber<T> {
zeroValue: T
add: (x: T, y: T) => T
}
let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function(x, y) {
return x + y
}
let myGenericString = new GenericNumber<string>()
myGenericString.zeroValue = 'abc'
myGenericString.add = function(x, y) {
return x + y
}
console.log(myGenericString.add(myGenericString.zeroValue, 'test'))
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12))
泛型约束
如果我们直接对一个泛型参数取 length 属性, 会报错, 因为这个泛型根本就不知道它有这个属性
// 没有泛型约束
function fn <T>(x: T): void {
// console.log(x.length) // error
}
我们可以使用泛型约束来实现
interface Lengthwise {
length: number;
}
// 指定泛型约束
function fn2 <T extends Lengthwise>(x: T): void {
console.log(x.length)
}
我们需要传入符合约束类型的值,必须包含必须 length 属性:
fn2('abc')
// fn2(123) // error number没有length属性
泛型语法
参考上面的图片,当我们调用 identity<Number>(1) ,Number 类型就像参数 1 一样,它将在出现 T 的任何位置填充该类型。图中 <T> 内部的 T 被称为类型变量,它是我们希望传递给 identity 函数的类型占位符,同时它被分配给 value 参数用来代替它的类型:此时 T 充当的是类型,而不是特定的 Number 类型。
其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:
- K(Key):表示对象中的键类型;
- V(Value):表示对象中的值类型;
- E(Element):表示元素类型。
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
对于上述代码,编译器足够聪明,能够知道我们的参数类型,并将它们赋值给 T 和 U,而不需要开发人员显式指定它们。
12.4 泛型工具类型
为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等。出于篇幅考虑,这里我们只简单介绍 Partial 工具类型。不过在具体介绍之前,我们得先介绍一些相关的基础知识,方便读者自行学习其它的工具类型。
1.typeof
在 TypeScript 中,typeof 操作符可以用来获取一个变量声明或对象的类型。
interface Person {
name: string;
age: number;
}
const sem: Person = { name: 'semlinker', age: 33 };
type Sem= typeof sem; // -> Person
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
2.keyof
keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型
interface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
3.in
in 用来遍历枚举类型:
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
4.extends
有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3); // Error, number doesn't have a .length property
这时我们需要传入符合约束类型的值,必须包含必须的属性:
loggingIdentity({length: 10, value: 3});
5.Partial
Partial<T> 的作用就是将某个类型里的属性全部变为可选项 ?。
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "Learn TS",
description: "Learn TypeScript",
};
const todo2 = updateTodo(todo1, {
description: "Learn TypeScript Enum",
});
在上面的 updateTodo 方法中,我们利用 Partial<T> 工具类型,定义 fieldsToUpdate 的类型为 Partial<Todo>,即:
{
title?: string | undefined;
description?: string | undefined;
}
Required
把 可缺省的属性,变更为必须要求的属性
内置对象
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
- ECMAScript 的内置对象
Boolean
Number
String
Date
RegExp
Error
/* 1. ECMAScript 的内置对象 */
let b: Boolean = new Boolean(1)
let n: Number = new Number(true)
let s: String = new String('abc')
let d: Date = new Date()
let r: RegExp = /^1/
let e: Error = new Error('error message')
b = true
// let bb: boolean = new Boolean(2) // error
2. BOM 和 DOM 的内置对象
Window
Document
HTMLElement
DocumentFragment
Event
NodeList
const div: HTMLElement = document.getElementById('test')
const divs: NodeList = document.querySelectorAll('div')
document.addEventListener('click', (event: MouseEvent) => {
console.dir(event.target)
})
const fragment: DocumentFragment = document.createDocumentFragment()
类型别名
type 关键字(可以给一个类型定义一个名字)多用于复合类型
定义类型别名
type str = string
let s:str = "我是小满"
console.log(s);
定义函数别名
type str = () => string
let s: str = () => "我是小满"
console.log(s);
定义联合类型别名
type str = string | number
let s: str = 123
let s2: str = '123'
console.log(s,s2);
定义值的别名
type value = boolean | 0 | '213'
let s:value = true
//变量s的值 只能是上面value定义的值
type 和 interface 还是一些区别的 虽然都可以定义类型
- interface可以继承 type 只能通过 & 交叉类型合并
- type 可以定义 联合类型 和 可以使用一些操作符 interface不行
- interface 遇到重名的会合并 type 不行
详情请看(来源于尚硅谷) 课程指南 | Vue3+TS 快速上手 (24kcs.github.io)
import type 和import的区别
官方:import type 是用来协助进行类型检查和声明的,在运行时是完全不存在的。
import type 是 TypeScript 2.9 版本引入的一种新的类型导入语法。
虽然在某些情况下 import 和 import type 可以互换使用,但它们并不完全等价。
具体来说,import type 只能用于导入类型,而 import 则可以用于导入任何类型的值(变量、函数、类等)
import type 仅仅引入类型信息,而不会引入实际的 JavaScript 对象。这意味着在编译后,import type 引入的代码不会出现在生成的 JavaScript 文件中。而 import 会引入实际的 JavaScript 对象。
通常情况下,建议尽可能使用 import type 来导入类型,因为它可以帮助减小生成的 JavaScript 文件的大小,并且更加明确地表达你的意图。但是如果你需要同时导入类型和值,或者需要动态导入模块,那么就必须使用 import。