简介
typescript 简介
1.简介 TypeScript 是 JavaScript 的超集,这意味着他支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展。 ts 在创建前的编译阶段经过类型系统的检查,就可以避免很多线上的错误 。
typescript 安装 和 编译
练习 ts
cnpm i typescript -g
tsc helloworld.ts // 编译ts
在 vue 中
-
安装
yarn add typescript yarn add @vue/cli-plugin-typescript / vue add @vue/typescript

- 在项目根目录新建 tsconfig.json
- 根目录下新建 shims-vue.d.ts,让 ts 识别 *.vue 文件
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
- 修改入口文件后缀为 ts .vue 文件 中使用
<script lang="ts">...</script>
关于@vue/cli-plugin-typescript 更多介绍,请移步 @vue/cli-plugin-typescript
好用的插件传送门
- vue-class-component
- vue-property-decorator 在 vue-class-component 上增强更多的结合 Vue 特性的装饰。
数据类型
boolean, number, string, undefined, null
let bool_a: boolean = true,
number_a: number = 123,
str_a: string = '123',
u: undefined = undefined,
n: null = null,
u1: number = undefined,
n1: number = null, //undefined和null 能否赋值给其他类型的变量取决于strictNullChecks配置,strictNullChecks:true,null和undefined只能赋值给void和它们各自。 strictNullChecks:false ,null和undefined是所有类型的子类型
c1: number | null | undefined // 这种叫联合类型
数组
两种写法
let num_arr1: number[] = [4, 5, 6]
let num_arr2: Array<number> = [7, 8, 9]
元组
元组( Tuple )一个特殊数组, 它的类型和数量是已知的
let tuple_a: [string, number]
tuple_a = ['yangyang', 20]
let tuple_b: [string, number, string] = ['yangyang', 20, 'sex']
可以使用对应元素的属性和方法
console.log(tuple_a[0].substr(1)) // OK
console.log(tuple_a[1].substr(1)) // Error, 'number' does not have 'substr'
当访问一个越界的元素,会使用联合类型替代:
tuple_a[3] = 'world' // OK, 字符串可以赋值给(string | number)类型
console.log(tuple_a[5].toString()) // OK, 'string' 和 'number' 都有 toString
tuple_a[6] = true // Error, 布尔不是(string | number)类型
枚举
某一变量的所有可能值, 比如性别(男、女) 关系(本人、父母、爱人、子女) 学历(专科、本科、硕士、博士) 好处:语义话,方便阅读 //需求: 当投被保人关系为本人时,投保人中已填写的项,被保人不再填写 //0:本人,1:父母,2:爱人,3:子女
if(friendship==0){
...
}
采用默认值,默认情况下从 0 开始为元素编号
enum Friendship {self,parent,lover,children} // 0 本人、1 父母、2 爱人、3 子女
if(friendship === Friendship.self){
...
}
编译过后
var Friendship
;(function(Friendship) {
Friendship[(Friendship['self'] = 0)] = 'self'
Friendship[(Friendship['parent'] = 1)] = 'parent'
Friendship[(Friendship['lover'] = 2)] = 'lover'
Friendship[(Friendship['children'] = 3)] = 'children'
})(Friendship || (Friendship = {}))
其实相当于
var Friendship = {
self: 0,
0: 'self',
parent: 1,
1: 'parent',
lover: 2,
2: 'lover',
children: 3,
3: 'children'
}
指定初始值,从初始值递增
全部手动赋值
enum Sex {
boy = 'M',
girl = 'F'
}
let sex = Sex.boy
any 任意类型
####适用情况 第三方库没有提供类型文件时 类型转换遇到困难时 数据结构太复杂难以定义
let root: HTMLElement | null = document.getElementById('root') // 这种叫联合类型
//root.style.color = 'red' // Object is possbily null
root!.style.color = 'red' // ! 确定不为 Null,可以使用!强行忽略警告
let root1: any = document.getElementById('root')
void 没有任何类型,
当函数没有返回值时,ts 认为返回类型为 void。 返回 null 和 undefined 认为是 void
function greeting(name: string) {
return
}
never 永远不知道是什么类型, 不可能的值
一个函数永远不会返回,返回值类型为 never 比如定时任务 就是一个死循环
function sum() {
while (true) {}
}
如果函数一定抛出错误,那么它也永远不会正常结束,返回类型也是 never
function error(message: string): never {
throw new Error(message)
}
function double(x: string | number) {
if (typeof x == 'number') {
console.log(x)
} else if (x == 'string') {
console.log(x)
} else {
console.log(x) //never
}
}
类型推论
let x = 10 //ts 推断出 x 为 number 类型
x = 'yangyang' //Type "'a'" is not assignable to type 'number'
断言
强行告诉 ts 是一个什么类型
let rr: string | number
console.log((rr as string).length)
console.log(rr as number)
字面量类型
变量可能的取值有哪些
let yycc: 1 | 2 | 'r' | 'i' = 1
type y1 = 10
type y2 = 'yangyang'
let yycc1: y1 | y2 = 10
函数
函数定义
js 写法
function add(x, y) {
return x + y
}
let myAdd = function(x, y) {
return x + y
}
ts 写法
function add(x: number, y: number): number {
return x + y
}
let myAdd = function(x: number, y: number): number {
return x + y
}
涉及到的 tsconfig.json 的配置
noImplicitAny:true , //不允许默认类型 为 true 时不显示声明类型时不会报错
let getName= function(firstname,lastname){} //报错 必须显示声明类型
let getName: GetFunction = function(
firstname: string,
lastname: string
): string {
return firstname + lastname
}
书写完整函数类型
定义类型,用来约束函数表达式
let myAdd: (x: number, y: number) => number = function(
x: number,
y: number
): number {
return x + y
}
type AddFn = (x: number, y: number) => number //注意:这里不是箭头函数,只是写法像,用箭头分割参数列表和返回值
//type AddFn = (baseValue: number, increment: number) => number //注:行参可以任意命名
let myAdd1: AddFn = function(x: number, y: number): number {
return x + y
}
有关参数
编译器会严格检查传递给一个函数的参数个数和类型, 必须与期望的一致,多传少传都不行。 当函数传参不固定时,我们可以做如下定义
- 可选参数 可选参数必须放到参数列表的最后
function print(name: string, age?: number): void {
console.log(name, age)
}
print('ii', 10)
print('ii')
- 默认参数 默认参数放到哪里都行, 如果默认参数在前面,想使用参数的默认值则传入参数 undefined 即可
function ajax(url: string, method: string = 'Get') {}
function add(num1: number = 1, num2: number): number {
return num1 + num2
}
alert(add(undefined, 3))
- 剩余参数 传入的参数个数不定
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ')
}
let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie')
函数的重载
java 中, 指两个函数,方法名是一样的,但参数的数量或者类型不一样 在 TS 里,仅仅指为一个函数提供多个函数定义
let obj = { name: 'yangyang', age: 10 }
function info(val: number | string) {
if (typeof val == 'string') {
obj.name = val
} else if (typeof val == 'number') {
obj.age = val
}
}
info('yanyang')
info(20)
注: 函数的实现和定义要一起写哦,中间不可以穿插其他代码
// 我给 val any 类型,但要限制 val 只能是 string 或 number. 则可以用函数重载
function attr1(val: any) {
if (typeof val == 'string') {
obj.name = val
} else if (typeof val == 'number') {
obj.age = val
}
}
function attr1(val: string): void
function attr1(val: number): void
class
tsconfig.js 设置 "strictPropertyInitialization": true / 启用类属性初始化的严格检查/ true 时,属性必须初始化,如下代码会报错
class Person {
name: string
getName(): void {
console.log(this.name)
}
}
定义类
属性初始化的写法如下:
- 方式一:直接赋值
class Person1 {
name: string = 'yangyang'
getName(): void {
console.log(this.name)
}
}
方式二:构造函数中赋值 构造函数 主要用于初始化类的成员变量属性 类的对象创建时自动调用执行 没有返回值
class Person2 {
name: string
constructor() {
this.name = name
}
getName(): void {
console.log(this.name)
}
}
//方式三
class Person3 {
name!: string // !表示name不为空
constructor() {
this.name = name
}
getName(): void {
console.log(this.name)
}
}
和 es6 类的比较
class Person2 {
constructor(name) {
this.name = name;
}
getName(){
console.log(this.name)
}
}
存取器
getter 和 setter
class User {
myname: string
constructor(myname: string) {
this.myname = myname
}
get name() {
return this.myname + 'yy'
}
set name(newname: string) {
this.myname = newname + 'yyy'
}
}
let u = new User('yangyang')
alert(u.name) //yangyangyy
u.name = 'yeyangyang'
alert(u.name) //yeyangyangyyyyy
继承
子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑 super 可以调用父类上的方法和属性
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`)
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!')
}
}
const dog = new Dog()
dog.bark()
dog.move(10)
dog.bark()
派生类包含了一个构造函数,它 必须调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问 this 的属性之前,我们 一定要调用 super()。 这个是 TypeScript 强制执行的一条重要规则。 这个例子演示了如何在子类里可以重写父类的方法。 Snake 类和 Horse 类都创建了 move 方法,它们重写了从 Animal 继承来的 move 方法,使得 move 方法根据不同的类而具有不同的功能。
class Animal {
name: string
constructor(theName: string) {
this.name = theName
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`)
}
}
class Snake extends Animal {
constructor(name: string) {
super(name)
}
move(distanceInMeters = 5) {
console.log('Slithering...')
super.move(distanceInMeters)
}
}
class Horse extends Animal {
constructor(name: string) {
super(name)
}
move(distanceInMeters = 45) {
console.log('Galloping...')
super.move(distanceInMeters)
}
}
let sam = new Snake('Sammy the Python')
let tom: Animal = new Horse('Tommy the Palomino')
sam.move()
tom.move(34)
修饰符
readonly、protected、private、public
class Anmial {
public readonly num: number
protected age: number = 14
private money: number
constructor(num: number) {
this.num = num
}
}
readonly 修饰符
最简单判断该用 readonly 还是 const 的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const,若做为属性则使用 readonly。
let ayy = new Anmial()
ayy.num = 9 //这样是不行的
class Dog extends Anmial {
static className = 'dog' //直接加在 Dog 上,Dog.className , Dog.getClassName 而不是加在 Dog 的原型上
static getClassName(): void {}
getNum() {
console.log(this.num) //子类
console.log(this.age) //子类
console.log(this.money) //子类 //private 在子类中不能访问
}
}
let ayy = new Anmial(13)
ayy.num // 外面
ayy.age //age 是受保护的,外面不能访问
静态属性 静态方法 static
实例成员,那些仅当类被实例化的时候才会被初始化的属性 static 创建的元素的存在于类本身上面而不是类的实例上 可以看下下面代码的编译结果
class Dog extends Anmial {
static className = 'dog' //直接加在 Dog 上,Dog.className , Dog.getClassName 而不是加在 Dog 的原型上
static getClassName(): void {}
getNum() {
console.log(this.num) //子类
console.log(this.age) //子类
console.log(this.money) //子类 //private 在子类中不能访问
}
}
abstruct
抽象类, 只能继承不能 new ,用来给子类封装一些公共属性和方法 不同于接口,抽象类可以包含成员的实现细节
abstract class Animal {
name!: string
abstract speak(): void
}
抽象类的抽象方法被继承时必须被实现
// class Cat extends Animal{
// //Non-abstract class 'Cat' does not implement inherited abstract member 'speak' from class 'Animal'
// }
class Cat extends Animal {
speak(): void {
console.log()
}
}
接口
接口里的方法都是抽象的
interface Flying {
fly(): void
}
interface Eating {
eat(): void
}
// 一个子类可以继承一个父类和多个接口
class Cat1 extends Animal implements Flying, Eating {
fly() {}
eat() {}
speak() {}
}
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 在 TypeScript 里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。 接口能够描述 JavaScript 中对象拥有的各种各样的外形。 可以描述
- 带有属性的普通对象
- 描述函数
- 描述可索引的对象
- 描述类
除了描述带有属性的普通对象外,接口也可以描述函数类型。
下面通过一个简单示例来观察接口是如何工作的:
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label)
}
类型检查器会查看 printLabel 的调用。 printLabel 有一个参数,并要求这个对象参数有一个名为 label 类型为 string 的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配
重写上面的例子,这次使用接口来描述
interface LabelledValue {
label: string
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label)
}
let myObj = { size: 10, label: 'Size 10 Object' }
printLabel(myObj)
let myObj = { size: 10, label: 'Size 10 Object' }
printLabel(myObj)
- 描述对象
interface Speakable {
speak(): void;
name?: string; //?表示可选属性
}
let speak: Speakable = {
name: '22',
speak() {}
}
let speak2: Speakable = {
speak() {}
}
interface Rectangle {
width number, //⚠️ 这里的分割符用, ;都可以
height: number
}
let rect: Rectangle = {
width: 10,
height: 10
}
需求: 一个变量对应的接口类型必须有 id 和 name,其他属性任意
interface Person {
readonly id: string | number
name: string
[propName: string]: any //可以这样写任意类型。 key 为 string, value 为 any
}
let p1: Person = {
id: 10,
name: 'yangyang',
age: 17,
sex: 'nan'
}
- 描述行为 接口可以在面向对象编程中表示为行为的抽象
interface Speakable {
speak(): void
sayHello(): void
}
interface Eatable {
eat(): void
}
//接口可以继承另一个接口
interface persontable extends persontable {
sleep(): void
}
//一个类可以实现多个接口
class Person implements Speakable, Eatable {
speak() {
console.log('Person说话')
}
eat() {}
}
用接口规范或定义函数
- 描述函数 它就像是一个只有参数列表和返回值类型的函数定义
interface DiscountInterface {
(price: number): number
// (aaa: number): number 这里只是行参,名字任意
}
let discount: DiscountInterface = function(price: number): number {
return price * 0.8
}
interface sumInterface {
(...args: number[]): number
}
let sum: sumInterface = function(...args: number[]): number {
return args.reduce((val, item) => val + item, 0)
}
sum(1, 2, 3)
### 可索引接口 描述那些能够“通过索引得到”的类型,比如 a[10]或 ageMap["daniel"] 对数组和对象进行约束 TypeScript 支持两种索引签名:字符串和数字
interface Arr {
[index: number]: number | string
}
let arr: Arr = ['yy', 'aa'] //数组也是对象,有索引值
let obj: Arr = { 1: 'hh', 3: 8888 }
interface UserInterface3 {
[index: string]: number | string
}
let obj2: UserInterface3 = { name: 'yyy', age: 'uu' }
约束类
与 C#或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。
interface ClockInterface {
currentTime: Date
setTime(d: Date)
}
// 通过implements 约束类
class Clock implements ClockInterface {
currentTime: Date
setTime(d: Date) {
this.currentTime = d
}
constructor(h: number, m: number) {}
}
接口约束构造函数类型 在 TypeScript 中,我们可以用 interface 来描述类 同时也可以使用 interface 里特殊的 new()关键字来描述类的构造函数类型
interface WithNameClass {
new (name: string): Animal //类的构造函数是类的属性
age: number
getAge(): number
}
class Animal {
constructor(public name: string) {}
static age: number //要用 static 定义成类的属性
static getAge(): number {
return this.age
}
}
function createAnimal(clazz: WithNameClass, name: string) {
return new clazz(name)
}
let a = createAnimal(Animal, 'cat')
console.log(a.name)
// 使用接口的变量需要实现接口的所有方法
class Girl implements Speakable1 {
eat() {}
walk() {}
speak() {}
}
一个类可以实现多个接口
class Person implements Speakable1, Eatable {
speak() {
console.log('Person 说话')
}
eat() {}
}
class TangDuck implements Speakable {
speak() {
console.log('TangDuck 说话')
}
eat() {}
}
泛型
泛型(用的特别特别多) 宽泛的类型反义词是不具体 我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值
function createArray(length: number, value: any): any[] {
let result: any[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
let a3 = createArray(3, 'yyy')
value 的值和返回的 result 的每一项值类型肯定是相同的, 这种可以用 泛型约束。 定义时,并不知道 value 的类型,使用的时候才知道 使用,函数名后面跟一个 ,表明有一个类型(type), 后面同类型的都用 T 表示 泛型并不只能用 T, 这块是可以随意写的
function createArray1<N>(length: number, value: N): N[] {
let result: N[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
let a4 = createArray1<number>(3, 99)
let a5 = createArray1<string>(3, 'yy')
类数组,比如比如 arguments
function sum3() {
let args: IArguments = arguments // IArguments 是ts里面自己定义的
for (let i = 0; i < args.length; i++) {
console.log(args[i])
}
}
sum3()
let root = document.getElementById('root')
let children1: HTMLCollection = root!.children
let childNodes1: NodeListOf<ChildNode> = root!.childNodes
let children: HTMLCollection = (root as HTMLElement).children
children.length
let nodeList: NodeList = (root as HTMLElement).childNodes
nodeList.length
泛型类 (用到了泛型的类)
class MyArray<T> {
private list: T[] = []
add(value: T) {
this.list.push(value)
}
getMax(): T {
let result = this.list[0]
for (let i = 0; i < this.list.length; i++) {
if (this.list[i] > result) {
result = this.list[i]
}
}
return result
}
}
let arr3 = new MyArray()
arr3.add(1)
arr3.add(2)
arr3.add(3)
let ret = arr3.getMax()
console.log(ret)
泛型接口 # 泛型接口可以用来约束函数
interface Calculate {
<T>(a: T, b: T): T //定义类型T, 参数a,b均为T类型,返回为T类型
}
let add: Calculate = function<T>(a: T, b: T): T {
return a
}
add<number>(1, 2)
多个泛型, 不借助中间变量交换两个变量的值
function swap<A, B>(tuple: [A, B]): [B, A] {
return [tuple[1], tuple[0]]
}
swap([1, 'a'])
默认泛型
function createArray2<N = number>(length: number, value: N): N[] {
let result: N[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray2(3, 2)
泛型约束 在函数中使用泛型的时候,由于预先并不知道泛型的类型,所以不能随意访问相应类型的属性或方法。
function logger<T>(val: T) {
console.log(val.length) //直接访问会报错
}
可以让泛型继承一个接口
interface LengthWise {
length: number
}
function logger2<T extends LengthWise>(val: T) {
console.log(val.length) //直接访问会报错
}
logger2('hello')
泛型接口 # 定义接口的时候也可以指定泛型
interface Cart<T> {
list: T[]
}
let c: Cart<number> = {
list: [2, 3]
}
let cart: Cart<{ name: string; price: number }> = {
list: [{ name: 'yangyang', price: 10 }]
}
console.log(cart.list[0].name, cart.list[0].price)
泛型类型别名 # 泛型类型别名可以表达更复杂的类型
type Cart2<T> = { list: T[] } | T[]
let c1: Cart2<string> = { list: ['1'] }
let c2: Cart2<number> = [1]
let root: HTMLElement | null = document.getElementById('root') //root.style.color = 'red' // Object is possbily null root!.style.color = 'red' // ! 确定不为 Null,可以使用!强行忽略警告 let root1: any = document.getElementById('root') // 这种叫联合类型
一切类型报错都能解决 any @ts-ignore 忽略类型检查
let obj: any
//@ts-ignore
let a6 = createArray1<string>(3, 111)
重写和重载 重写是指子类重写继承自父类中的方法
class Animal1 {
speak(word: string): string {
return '动作叫:' + word
}
}
class Cat2 extends Animal1 {
speak(word: string): string {
return '猫叫:' + word
}
}
let cat = new Cat2()
console.log(cat.speak('hello'))
重载是指为同一个函数提供多个类型定义
function double1(val:number):number
function double1(val:string):string
function double1(val:any):any{
if(typeof val == 'number'){
return val \*2;
}
return val + val;
}
let r = double(1);
console.log(r);
抽象类 vs 接口 -不同类之间公有的属性或方法,可以抽象成一个接口(Interfaces) -而抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现 -抽象类本质是一个无法被实例化的类,其中能够实现方法和初始化属性,而接口仅能够用于描述,既不提供方法的实现,也不为属性进行初始化 -一个类可以继承一个类或抽象类,但可以实现(implements)多个接口 -抽象类也可以实现接口
abstract class Animal {
name: string
constructor(name: string) {
this.name = name
}
abstract speak(): void
}
interface Flying {
fly(): void
}
class Duck extends Animal implements Flying {
speak() {
console.log('汪汪汪')
}
fly() {
console.log('我会飞')
}
}
let duck = new Duck('zhufeng')
duck.speak()
duck.fly()
7.11 泛型接口 vs 泛型类型别名 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名 类型别名不能被 extends 和 implements,这时我们应该尽量使用接口代替类型别名 当我们需要使用联合类型或者元组类型的时候,类型别名会更合适
type 和 interface 的区别
// type 定义别名 // interface 是一个真正的类型