前端必备typescript基础知识教程(vue+typescript项目实战)

TypeScript 知识全总结

注:typescript 为 javascript 的超集, 支持所有 javascript 语法, 同时扩展了强类型约束。编译后为 es5 代码

一、TypeScript中的数据类型

typescript中为了使编写的代码更规范,更有利于维护,增加了类型校验,在typescript中主要给我们提供了以下数据类型

ts 定义变量代码 必须指定类型

类型推论:在有些没有明确指出类型的地方,类型推论会帮助提供类型

let x = 3; // 初始化赋值数字, 那么 x 就只能是数字类型。
let x = [0, 1, null]; // 系统会自动给出兼容的类型
  • 布尔类型(boolean)
  • 数字类型(number)
  • 字符串类型(string)
  • 数组类型(array)
  • 元组类型(tuple)
  • 枚举类型(enum)
  • 任意类型(any)
  • null 和 undefined
  • void类型
  • never类型
  • Object 非原始类型

1、布尔类型(boolean)

es5的正确写法, ts中是错误写法

var flag = true;
flag = 456;

typescript中为了使编写的代码更规范,更有利于维护,增加了类型校验

var flag: boolean = true;
// flag = 123;  // 错误
flag = false;  // 正确
console.log(flag); // false

2、数字类型(number)

不区分整型和浮点型数据统一称为数字类型

var num: number = 123;
num = 456;
console.log(num);  // 正确 : 456
num = 'str';    // 错误

3、字符串类型(string)

var str:string = 'this is ts';
str = 'haha';  // 正确
str = true;  // 错误

4、数组类型(array)

ts中定义数组有两种方式 :

  1. var arr: number[] = [11, 22, 33]
  2. var arr: Array<number> = [11, 22, 33] 泛型约束 数组数据<类型约束>
// var arr=['1','2'];  // es5定义数组
var arr: number[] = [11, 22, 33];
console.log(arr); // [11, 22, 33]
var arr: Array<number> = [11, 22, 33];
console.log(arr); // [11, 22, 33]

5、 元组类型(tuple)

元组类型是属于数组的一种, 可以指定数组中每一个元素的类型。
正常的数组只能指定一种类型, 元组可以指定多种数据类型

// 正常数组
var arr: Array<number> = [11, 22, 33];
console.log(arr);  // [11, 22, 33]
// 元祖类型
let arr: [number, string] = [123, 'this is ts'];
console.log(arr); // [ 123, 'this is ts' ]
let arr1: [number, string, boolean] = [123, 'this is ts', true];
console.log(arr1); // [123, 'this is ts', true]

6、枚举类型(enum)

枚举类型主要的作用通俗点说就是当我们定义一些状态值和类型值的时候通常是使用数字。
但是过一段时间再回看代码就对数字状态概念会变模糊。
枚举就用这种对应的方式解释了每一种枚举属性值的概念。
直接调用属性就获得当前属性的状态值

注意:如果标识符没有赋值, 它的值就是索引下标

   用法:
   enum 枚举名{ 
         标识符[=整型常数], 
         标识符[=整型常数], 
         ... 
         标识符[=整型常数], 
     } ;     
enum Flag { success = 1, error = 2 };
let s: Flag = Flag.success;
console.log(s); // 1

enum Color { blue , red, 'orange' };
var c: Color = Color.red;
 console.log(c);   // 1  

取元素索引值, 例如 orange 前面的索引为3, 他的它的值为递增后的值

enum Color { blue, red = 3, 'orange' };
var c: Color = Color.red;
var b: Color = Color.blue;
var o: Color = Color.orange;
console.log(c, b, o);   // 3 0 4 
enum Err { 'undefined' = -1, 'null' = -2, 'success' = 1 };
var e: Err = Err.success;
console.log(e);

7、任意类型(any)

就像 es5 不指定类型一致, 处理不确定数据类型的数据

var num: any = 123;
num = 'str';
num = true;
console.log(num)
// 任意类型的用处
var oBox: any = document.getElementById('box');
oBox.style.color = 'red';

8、null 和 undefined

nullundefined 为其他数据类型(never类型)的子类型

var num: number; // 定义变量未赋值其实就是 undefined
console.log(num)  // 输出:undefined   但是会报警告
var num: undefined;
console.log(num)  // 输出:undefined  //正确
var num: number | undefined;  // 用这种方式处理如果没有赋值则赋值 undefined
num = 123;
console.log(num); // 123
// 定义没有赋值就是 undefined
var num: number | undefined;
console.log(num); // undefined
var num: null; // 指定空类型不能赋值其他类型
num = null; // 正确
num = 123; // 报错
//一个元素可能是 number类型 可能是null 可能是undefined
var num: number | null | undefined;
num = 1234;
console.log(num) // 1234

9、void类型

typescript 中的 void 表示没有任何类型,一般用于定义方法的时候方法没有返回值。
表示方法 没有返回任何类型

// es5的定义方法
function run() {
	console.log('run')
}
run();
// 正确写法
function run(): void {
    console.log('run')
}
run();
// 错误写法
function run(): undefined {
   console.log('run')
}
run();

如果又返回类型则直接定义类型

// 正确写法
function run(): number {
	return 123;
}
run();

10、never类型

是其他类型包括 nullundefined,代表从不会出现的值

例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。
never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never。

// 这意味着声明never的变量只能被never类型所赋值。
var a: undefined;
a = undefined;

var b: null;
b = null;
var a: never;
// a = 123; // 错误的写法
a = (() => {
     throw new Error('错误');
})()

11、Object 类型

object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。
使用object类型,就可以更好的表示像Object.create这样的API

  
function func1(x: object): void {
    console.log(x)
}
func1(null); // err Argument of type 'null' is not assignable to parameter of type 'object'.
func1({name: 'test'}); // OK
func1('klljsldkf'); // err Argument of type 'null' is not assignable to parameter of type 'object'.
func1(123); // err Argument of type '123' is not assignable to parameter of type 'object'.

12、类型兼容性了解

x 类型包含 name: string , y 具备这个属性满足兼容性可以赋值
相反 x却不具备location: string 不满足类型兼容
比较过程是递归进行的,检查每个成员及子成员

interface Named {
    name: string;
}

let x: Named;
// y 的推断类型是 { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y; // 正确
y = x; // 报错 Property 'location' is missing in type 'Named' but required in type '{ name: string; location: string; }'.

这里要检查y是否能赋值给x,编译器检查x中的每个属性,看是否能在y中也找到对应属性。 在这个例子中,y必须包含名字是namestring类型成员。y满足条件,因此赋值正确

比较两个函数

比较函数的参数列表, 不看参数名,只看参数类型。
赋值规则和对象类型一样,主要是看是否满足需要的参数。满足则可以兼容

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // Error

二、TypeScript中的函数

  • 函数的定义
  • 可选参数
  • 默认参数
  • 剩余参数
  • 函数重载
  • 箭头函数 es6

1、函数的定义

es5定义函数的方法

// 函数声明法
function run() {
    return 'run';
}
// 函数表达式
var run2 = function() {
    return 'run2';
}

ts中定义函数的方法

//函数声明法 
function run(): string {
     return 'run';
}

//错误写法 - 不一致的返回类型
function run1(): string {
    return 123;
}

// 函数表达式
var fun2 = function(): number {
     return 123;
}
fun2(); /* 调用方法 */

ts中定义方法传参

返回值类型约束。
参数类型约束

function getInfo(name: string, age: number): string {
     return `${name} --- ${age}`;
}
getInfo('zhangsan', 20); // zhangsan --- 20
var getInfo = function(name: string, age: number): string {
     return `${name} --- ${age}`;
}
getInfo('zhangsan', 40); // zhangsan --- 40

没有返回值的方法

function run(): void {
     console.log('run')
}
run();

2、方法可选参数

es5里面方法的实参和行参可以不一样,但是ts中必须一样,如果不一样就需要配置可选参数

参数后加 ? : 指定可选参数

注意: 可选参数必须配置到参数的最后面, 可选参数在前面会报警告。

// 正确写法
function getInfo(name: string, age?: number): string {
   if(age) {
       return `${name} --- ${age}`;
   }else{
       return `${name} ---年龄保密`;
   }
}
getInfo('zhangsan'); // zhangsan ---年龄保密
getInfo('zhangsan',123); // zhangsan --- 12

// 错误写法
function getInfo2(name?: string, age: number): string {
    if(age) {
        return `${name} --- ${age}`;
    }else{
        return `${name} ---年龄保密`;
    }
 }

getInfo2('zhangsan'); // 校验参数会报错  不能把可选参数放在前面

3、默认参数 可选参数

es5 里面没法设置默认参数,es6 和 ts 中都可以设置默认参数

function getInfo(name: string, age: number = 20): string {
   if(age) {
       return `${name} --- ${age}`;
   }else{
       return `${name} ---年龄保密`;
   }
}
getInfo('张三'); // 张三 --- 20
getInfo('张三',30); // 张三 --- 30

4、剩余参数

function sum(a: number, b: number, c: number, d: number): number {
	return a + b + c + d;
}
sum(1, 2, 3, 4); // 10

扩展运算符 , 接受新参传过来的值。用法同ES6用法

function sum(...result: number[]): number {
	var sum = 0;
	for(var i = 0; i < result.length; i++){
	    sum += result[i];  
	}
	return sum;
}
sum(1, 2, 3, 4, 5, 6); // 21
function sum(a: number, b: number, ...result: number[]): number {
   var sum = a + b;
   for(var i = 0;i< result.length; i++){
       sum += result[i];  
   }
   return sum;
}
sum(1, 2, 3, 4, 5, 6);

5、ts函数重载

java中方法的重载:重载指的是两个或者两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。

typescript中的重载:通过为同一个函数提供多个函数类型定义来试下多种功能的目的。
ts为了兼容es5 以及 es6 重载的写法 和 java中有区别。
es5中出现同名方法,下面的会替换上面的方法

// es5方法覆盖
function css(config) {
}

function css(config, value) {
}

ts中的重载

如下用法定义两个同名函数并且确定方法的参数类型和返回类型

按照如下示例定义一个同名逻辑方法, 此时的逻辑方法会校验参数是否合格。这就相当于重载前面两个函数把前面两个函数的规则声明进当前的最后的函数中。

function getInfo(name: string): string;
function getInfo(age: number): string;
function getInfo(str: any): any {
	if(typeof str === 'string') {
         return '我叫:' + str;
     }else{
        return '我的年龄是' + str;
     }
 }
getInfo('张三');    // 正确
getInfo(20);		// 正确
getInfo(true);		// 错误写法
function getInfo(name: string): string;
function getInfo(name: string, age: number): string;
function getInfo(name: any,age?: any): any {
	if(age) {
	 	return '我叫:'+ name + '我的年龄是' + age;
	} else {
	 	return '我叫:' + name;
	}
}
getInfo('zhangsan');  // 正确    	我叫:zhangsan
getInfo(123);  // 错误
getInfo('zhangsan', 20); // 正确     我叫:zhangsan我的年龄是20

6、箭头函数 es6

this 指向的问题 , 箭头函数里面的this指向上下文。
ts 同样具备这些功能没什么可说的

setTimeout(function() {
    console.log('run')
}, 1000)

setTimeout(() => {
    console.log('run')
}, 1000)

三、TypeScript中的类

  • 类的定义
  • 继承
  • 类里面的修饰符
  • 静态属性 静态方法
  • 抽象类 继承 多态

1、ts中类的定义

es5 中的类可以在构造函数和原型链上定义属性和方法

// es5
function Person(name){
	this.name = name;
	this.run = function() {
		console.log(this.name)
	}
}
var p = new Person('张三'); // 实例化
p.run() // 张三
Person.prototype.sex = '男'
console.log(p.sex); // 男 
// ts中定义类
class Person{
    name: string;   //属性  前面省略了public关键词
    constructor(n: string){  //构造函数   实例化类的时候触发的方法
        this.name = n;
    }
    run(): void {
        alert(this.name);
    }
}
var p = new Person('张三');
p.run()
class Person{
    name: string; 
    constructor(name: string) {  //构造函数   实例化类的时候触发的方法
        this.name = name;
    }
    getName(): string{
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}
var p = new Person('张三');
p.getName();
p.setName('李四');
p.getName();

2、ts中实现继承 extends、 super

es5:
call/ apply 不能继承原型链上的方法和属性, 可以给父类传参
原型链既可以继承构造函数上的属性方法,也可以继承原型上的属性和方法
原型链继承无法给父类传参

class Person{
    name: string;
    constructor(name: string){
        this.name = name;
    }
    run(): string {
        return `${this.name}在运动`
    }
}
var p = new Person('王五');
p.run();

class Web extends Person{
    constructor(name:string){
       super(name);  /*初始化父类的构造函数  跟es一样 */
    }
}
var w = new Web('李四');
w.run();

ts中继承的探讨 父类的方法和子类的方法一致

class Person{
    name: string;
    constructor(name: string){
        this.name = name;
    }
    run(): string {
        return `${this.name}在运动`
    }
}
var p = new Person('王五');
p.run()

子类不仅可以继承父类的方法和属性, 还可以重写父类同名的方法并且扩展自己的方法

class Web extends Person{
    constructor(name: string) {
        super(name);  /*初始化父类的构造函数*/
    }
    run(): string { // 重写 run 方法
        return `${this.name}在运动-子类`
    }
    work() {
        alert(`${this.name}在工作`)
    }
}
var w = new Web('李四');
w.work();
w.run();

3、类里面的修饰符

typescript里面定义属性的时候给我们提供了 三种修饰符:

public :公有 在当前类里面、 子类 、类外面都可以访问
protected:保护类型 在当前类里面、子类里面可以访问 ,在类外部没法访问
private :私有 在当前类里面可以访问,子类、类外部都没法访问
属性如果不加修饰符 默认就是 公有 (public)

具体使用请看下面的示例代码:
public 外部和子类访问都正常

class Person{
    public name:string;  /*公有属性*/
    constructor(name:string){
        this.name=name;
    }
    run():string{
        return `${this.name}在运动`
    }
}
var p=new Person('王五');
p.run()


class Web extends Person{
    constructor(name: string) {
    super(name);  /*初始化父类的构造函数*/
}
run(): string {
    return `${this.name}在运动-子类`
}
work() {
    console.log(`${this.name}在工作`)
}
}
var w = new Web('李四');
w.work(); // 李四在工作
console.log(w.name); // 李四

类外部访问公有属性

class Person{
    public name: string;  /*公有属性*/
    constructor(name: string) {
        this.name = name;
    }
    run(): string {
        return `${this.name} is sporting`
    }
}
var  p = new Person('hahhahah');
console.log(p.run()); // hahhahah is sporting
console.log(p.name); // hahhahah

protected 子类显示正常 , 但是外部访问则出现语法异常提示

类和子类内部使用的属性

class Person{
    protected name: string;  /* 保护类型 */
    constructor(name: string) {
        this.name=name;
    }
    run():string{
        return `${this.name} is sport`
    }
}
var p = new Person('wangwu');
console.log(p.run()); // wangwu is sport


class Web extends Person{
    constructor(name: string) {
        super(name);  /*初始化父类的构造函数*/
    }
    work(){
        console.log(`${this.name} is working`)
    }
}
var w = new Web('lisi');
w.work(); // lisi is working
console.log(w.run()); // lisi is sport
console.log(w.name)// lisi  报错警告 Property 'name' is protected and only accessible within class 'Person' and its subclasses.

类外外部没法访问保护类型的属性

class Person{
    protected name: string;  /* 保护类型 */
    constructor(name: string) {
        this.name = name;
    }
    run(): string {
        return `${this.name} 在运动`
    }
}

var  p = new Person('哈哈哈');
console.log(p.name); // 报异常: Property 'name' is protected and only accessible within class 'Person' and its subclasses

private 子类和外部调用都会报异常,

通常私有属性都会通过类内部定义可操作方法来实现对数据的更新和调用

class Person{
    private name: string;  /* 私有 */
    constructor(name: string) {
        this.name = name;
    }
    run(): string {
        return `${this.name}在运动`
    }
}

class Web extends Person{
    constructor(name: string){
        super(name)
    }
    work(){
    	// 异常: Property 'name' is private and only accessible within class 'Person'.
        console.log(`${this.name}在工作`) 
    }
} 

var w = new Web('lisa');
console.log(w.name); // 异常:Property 'name' is private and only accessible within class 'Person'.           
class Person{
    private name: string;  /* 私有 */
    constructor(name: string){
        this.name = name;
    }
    run(): string {
        return `${this.name} is sporting`
    }
}

var p = new Person('hahaha');
console.log(p.run()); // hahaha is sporting
console.log(p.name); // 报异常: Property 'name' is private and only accessible within class 'Person'.

4、静态属性 静态方法

typescript 中使用关键字 static 来定义静态属性和静态方法。 这里和es6没有区别

ES5定义静态方法

// 定义构造函数
function Person() {
    this.run1 = function() { // 实例方法
    }
}
Person.name = '哈哈哈';

Person.run2 = function() { //  静态方法
}
var p = new Person();
p.run1(); // 实例方法需要实例化通过实例化对象来调用
Person.run2(); // 静态方法由构造函数直接调用,不需要实例化

静态属性方法的好处:不需要重复创建实例, 直接调用更加快捷,并且节省空间
jquery 举例, 本身使用的就是静态方法方式。感兴趣的小伙伴可以看一下 jQuery 源码

function $(element) {
    return new Base(element)
}
$.get = function() {
}

function Base(element) {
    this.element = `获取dom节点`;
    this.css=function(arr, value){
        this.element.style.arr = value;
    }
}

$('#box').css('color', 'red')
$.get('url', function() {})

静态属性

为什么实例方法能调用静态属性, 而静态方法不能调用实例属性?
我是这么理解, 静态方法在运行程序的时候自动分配内存空间, 也就是构造函数已经存所有的静态属性和方法是挂载到构造函数上面。这个时候实例对象是没有被创建的, 如果这里来调用实例的属性是会存在问题的。
同样, 实例化对象存在构造函数依然可以调用, 所以这时候来调用静态属性并不冲突。
这么看, 其实就是一个先后的过程, 程序加载预编译过程构造函数已经存在, 而实例需要实例化后才成功创建。

class Per{
    public name: string;
    public age: number = 20;
    // 静态属性
    static sex = "man";
    constructor(name: string) {
        this.name = name;
    }
    run() {  /* 实例方法 可以获取静态属性 */
        console.log(`${this.name} is sporting`)
        console.log(`run fn get sex ${Per.sex}`) // run fn get sex man
    }
    work() {
        console.log(`${this.name} is working`)
    }
     static print() {  /* 静态方法  里面没法直接调用类里面的属性 */
        console.log('print fn ' + Per.sex);
        console.log('print fn get age ' + this.age + ` ---`); 
        // print fn get age undefined --- Property 'age' does not exist on type 'typeof Per'.
    }
}

var p = new Per('zhangsan');
p.run(); // zhangsan is sporting
Per.print(); // print fn man
console.log(Per.sex); // man

5、多态、抽象类、继承

多态: 父类定义一个方法不去实现,让继承它的子类去实现 每一个子类有不同的表现

//多态属于继承
class Animal {
    name: string; // 这是公共属性, 省略 public 
    constructor(name: string) {
        this.name = name;
    }
    eat(){   // 具体吃什么  不知道   ,  具体吃什么?继承它的子类去实现 ,每一个子类的表现不一样
        console.log('吃的方法')
    }
}

class Dog extends Animal{
    constructor(name: string){
        super(name) /*初始化父类的构造函数*/
    }
    eat() { // 实现父类的 eat 方法, 就是重写
        return this.name + '吃粮食'
    }
}


class Cat extends Animal{
    constructor(name: string){
        super(name)  /*初始化父类的构造函数*/
    }
    eat() { // 实现父类的 eat 方法, 就是重写
        return this.name + '吃老鼠'
    }
}

typescript中的抽象类:它是提供其他类继承的基类,不能直接被实例化

abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类(子类)中实现
abstract抽象方法只能放在抽象类里面
如果有抽象方法, 必须定义成抽象类

抽象类和抽象方法用来定义标准 。 标准:Animal 这个类要求它的子类必须包含eat方法

abstract class Animal{ // 抽象类
    public name: string;
    constructor(name: string) {
        this.name = name;
    }
    abstract eat(): any;  // 抽象方法必须实现
    run() { // 可以不实现的方法
        console.log('其他方法可以不实现')
    }
}

// var a = new Animal() /* 错误的写法, 不能实例化 */
class Dog extends Animal{
    // 抽象类的子类必须实现抽象类里面的抽象方法
    constructor(name: any){
        super(name)
    }
    eat() {
        console.log(this.name + '吃粮食')
    }
}
var d = new Dog('小花花');
d.eat();

class Cat extends Animal{
    //抽象类的子类必须实现抽象类里面的抽象方法
    constructor(name: any) {
        super(name)
    }
    run() {
    }
    eat() {
        console.log(this.name + '吃老鼠')
    }
}

var c = new Cat('小花猫');
c.eat();

四、typeScript中的接口

  • 属性类接口
  • 函数类型接口
  • 可索引接口
  • 类类型接口
  • 接口扩展

接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。
接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。
typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。

现实中接口如图:
在这里插入图片描述
在这里插入图片描述

定义接入口规范。所有想通过这个接口访问任何东西都需要按照这个接口规则应用

1、属性接口

ts中定义方法

function printLabel(): void {
    console.log('printLabel');
}
printLabel();

ts 中定义方法传入参数

function printLabel(label: string): void {
   console.log('printLabel');
}
printLabel('hahah');

ts 中自定义方法传入参数, 对json进行约束

属性接口: 对json的约束

function printLabel(labelInfo: { label: string }): void {
    console.log('printLabel');
}
printLabel('hahah'); //错误写法
printLabel({ name: '张三' });  // 错误的写法
printLabel({ label: '张三' });  // 正确的写法

接口:行为和动作的规范,对批量方法进行约束

规范接口行为

// 就是传入对象的约束    属性接口
interface FullName{
    firstName: string;   //注意 ; 结束
    secondName: string;
}

function printName(name: FullName) {
    // 必须传入对象  firstName  secondName
    console.log(name.firstName + '--' + name.secondName );
    
    // 虽然会输出但是ts校验会异常: Property 'age' does not exist on type 'FullName'.
    console.log(`age :` + name.age);
}
// printName('1213');  // 错误

// Argument of type '{ age: number; firstName: string; secondName: string; }' is not assignable to parameter of type 'FullName'.   Object literal may only specify known properties, and 'age' does not exist in type 'FullName'.
printName({ 
    age: 20, // 错误
    firstName: 'zhang',
    secondName: 'san'
})
var obj = {   // 不建议这样使用虽然不会报错,但是 age 并不是接口属性,无形中提高BUG率
    age: 20,
    firstName: 'zhang',
    secondName: 'san'
};

printName(obj)
printName({ /* 传入的参数需要严格按照接口规则定义,  */
    firstName: 'zhang',
    secondName: 'san'
}) // zhang--san 

对批量方法传入参数进行约束

interface FullName{
    firstName: string;   //注意 ; 结束
    secondName: string;
}

function printName(name: FullName) { // 方法1
    // 必须传入对象  firstName  secondName
    console.log(name.firstName + ' -- ' + name.secondName);
}

function printInfo(info: FullName) { // 方法2
    // 必须传入对象  firstName  secondName
    console.log(info.firstName + ' ---- ' + info.secondName);
}

var obj = {   /* 必须传入 firstName  secondName */
    firstName: 'zhang',
    secondName: '3'
};
printName(obj); // zhang -- 3
printInfo({ 	// li ---- 4
    firstName: 'li',
    secondName: '4'
})

不严格要求参数的顺序

interface FullName{
    firstName: string;
    secondName: string;
}

function getName(name: FullName) {
    console.log(name)
}
// 参数的顺序可以不一样
getName({        
    secondName: 'secondName',
    firstName: 'firstName'
})

可选接口属性 用?修饰, 方式和可选参数一样

interface FullName{
    firstName: string;
    secondName?: string;
}

function getName(name: FullName) {
    console.log(name)
}  
getName({               
    firstName:'firstName'
})

简单举例说明

// 原生 js 封装的 ajax 
interface Config{ // 定义接口规则
    type: string;
    url: string;
    data?: string;
    dataType: string;
}

// ajax 函数
function ajax(config: Config) { 
   var xhr = new XMLHttpRequest();
   xhr.open(config.type, config.url, true);
   xhr.send(config.data);
   xhr.onreadystatechange = function() {
        if(xhr.readyState == 4 && xhr.status == 200) {
            console.log('chengong');
            if(config.dataType == 'json') {
                console.log(JSON.parse(xhr.responseText));
            } else {
                console.log(xhr.responseText)
            }
        }
   }
}

// 按照接口规则传入参数
ajax({ 
    type: 'get',
    data: 'name=zhangsan',
    url: 'http://baidu.com/api/productlist', //api
    dataType: 'json'
})

只读属性

readonly:一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性

interface Point {
    readonly key: number;
    readonly value: number;
}

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // 报异常

readonly vs const区分:变量使用 const,属性则使用readonly

函数类型接口: 对方法传入的参数 以及返回值进行约束批量约束

注意: 参数类型一旦指定, 必须严格按照接口规则定义参数类型

// 定义:函数类型接口直接定义参数规则
interface encrypt{ 
    (key: string, value: number): string;
}
// 用法1: 按照函数接口一模一样的参数形式
var en1: encrypt = function(key: string, value: number): string{
        return key + value ;
}
// 用法2: 参数名自定义
var en2: encrypt = function(k: string, v: number): string{
        return k + v;
}
// 用法3:类型系统会根据接口推断参数类型
var en3: encrypt = function(k, v) {
        return k + v;
}
// 错误用法: 只要类型不一致就报错
var en4: encrypt = function(key: string, value: string): string{
        return key + value;
}

加密的函数类型接口示例:

var md5: encrypt = function(k: string, v: number): string{
     return k + v;
}
console.log(md5('name', 3344));
var sha1: encrypt = function(key, value) {
    return key + '----' + value;
}
console.log(sha1('name', 2211));

可索引接口:
数组、对象的约束 (不常用)

// ts定义数组的方式
var arr: number[] = [ 2342, 235325 ]
var arr1: Array<string> = ['111', '222']
// 可索引接口 对数组的约束
interface UserArr{
    [index: number]: string  // 索引值为数字, 并且值为字符串
}
var arr: UserArr=['aaa', 'bbb'];
console.log(arr[0]); // aaa
var arr1: UserArr = [123, 'bbb'];  /* 错误 */
console.log(arr1[0]);

可索引接口:
对对象的约束

interface UserObj{
    [index: string]: string
}
var arr:UserObj = { name:'张三' };

类类型接口:
对类的约束 和 抽象类抽象有点相似
实现接口:implements 关键字

必须实现接口内的属性和方法

interface Animal{
    name: string;
    eat(str: string): void;
}

class Dog implements Animal{
    name: string; // 实现属性
    constructor(name: string){
        this.name = name;
    }
    // 实现方法
    eat() { 
        console.log(this.name + '吃粮食')
    }
    
    // 扩展方法
 	work() {
        console.log(`${this.name} is working`)
    }
}

var d = new Dog('小黑');
d.eat(); // 小黑吃粮食
d.work(); // 小黑 is working

class Cat implements Animal{
    name: string;
    constructor(name: string){
        this.name = name;
    }
    eat(food: string){
        console.log(this.name + '吃' + food);
    }
}

var c = new Cat('小花');
c.eat('老鼠');

接口扩展:接口可以继承接口

interface Animal{
    eat(): void;
}

interface Person extends Animal{
    work(): void;
}

class Web implements Person{
    public name: string;
    constructor(name: string) {
        this.name = name;
    }
    eat() {
        console.log(this.name + '喜欢吃馒头')
    }
    work() {
        console.log(this.name + '写代码');
    }
}

var w = new Web('小李');
w.eat();
interface Animal{
    eat(): void;
}

// 接口继承接口
interface Person extends Animal{ 
    work(): void;
}

class Programmer{
    public name: string;
    constructor(name: string) {
        this.name = name;
    }
    coding(code: string) {
        console.log(this.name + code)
    }
}

// 同时继承 Programmer 和实现接口
class Web extends Programmer implements Person{    
    constructor(name: string) {
       super(name)
    }
    // 必须实现 Animal 方法
    eat() { 
        console.log(this.name + '喜欢吃馒头')
    }
    // 必须实现 Person 方法
    work() {
        console.log(this.name + '写代码');
    }
}

var w = new Web('小李');
w.eat();
w.coding('写ts代码');

五、typeScript中的泛型

  • 泛型的定义
  • 泛型函数
  • 泛型类
  • 泛型接口

泛型:软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

通俗理解:泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持(类型校验)

只能返回string类型的数据

function getData(value: string): string {
    return value;
}

怎么同时返回 string类型 和number类型 ?

实现没有问题,但是仔细审视代码会发现代码冗余比较突出

function getData1(value: string): string {
    return value;
}

function getData2(value: number): number {
    return value;
}

那么能不能同时返回 string类型 和number类型? 又不代码冗余 ?

any可以解决这个问题, 但是 any 不会约束任何数据, 包括输入的参数和返回值的类型不一致

function getData(value: any): any {
    return '哈哈哈';
}

getData(123);
getData('str');

any放弃了类型检查,传入什么 返回什么。
比如:传入number 类型必须返回number类型 传入 string类型必须返回string类型

传入的参数类型和返回的参数类型可以不一致

function getData(value: any): any {
    return '哈哈哈';
}

到这里我们想到了泛型, 既满足代码不冗余, 又校验参数与返回值为同类型, 并且还可以实现参数类型和返回值类型不一致

注意: 无法创建泛型枚举和泛型命名空间

泛型:可以支持不特定的数据类型
规则 :<字母> 可以是任意字母, 通常都用 T 来表示泛型
要求:传入的参数和返回的参数一致 , T表示泛型,具体什么类型是调用这个方法的时候决定的

/**
	要求传入和返回一种类型<T> 
	T 就指代当前传入的数据类型 如:传入 number 那么 所有的 T 都为 number 
*/
function getData<T>(value: T): T { 
    return value;
}
getData<number>(123); // 正确
getData<string>('1214231'); // 正确
getData<number>('2112');       /* 错误的写法 */  
// 参数和返回值可以不一致
function getData<T>(value: T): any { 
	return '2145214214';
}
getData<number>(123);  // 参数必须是number
getData<string>('这是一个泛型');

泛型类:比如有个最小堆算法,需要同时支持返回数字和字符串 a - z两种类型。 通过类的泛型来实现

不用泛型的方式, 定义 number 只支持数字

class MinClass{
    public list: number[] = [];
    add(num: number) {
        this.list.push(num)
        console.log(this.list)
    }
    min(): number {
        var minNum = this.list[0];
        for(var i = 0;i< this.list.length; i++) {
            if(minNum > this.list[i]) {
                minNum = this.list[i];
            }
        }
        return minNum;
    }
}

var m = new MinClass();
m.add(3); // [ 3 ]
m.add(22);// [ 3, 22 ]
m.add(23);// [ 3, 22, 23 ]
m.add(6);//  [ 3, 22, 23, 6 ]
m.add(7);//  [ 3, 22, 23, 6, 7 ]
console.log(m.min()); // 3

类的泛型:同时支持数字和字符串, 增加代码的复用性

class MinClass<T>{
    public list: T[] = [];
    add(value: T): void {
        this.list.push(value);
        console.log(this.list)
    }
    min(): T {
        var minNum = this.list[0];
        for(var i = 0;i < this.list.length; i++) {
            if(minNum > this.list[i]) { // 这里是隐式转ASCII码比较
                minNum = this.list[i];
            }
        }
        return minNum;
    }
}
/**
  实例化类 并且制定了类的T代表的类型是number
  其他类型回报类型错误
*/
var m1 = new MinClass<number>();  
m1.add(11); // [ 11 ]
m1.add(3);  // [ 11, 3 ]
m1.add(2);  // [ 11, 3, 2 ]
console.log(m1.min()); // 2
var m2 = new MinClass<string>();   /* 实例化类 并且制定了类的T代表的类型是string */
m2.add('c');// [ 'c' ]
m2.add('a');// [ 'c', 'a' ]
m2.add('v');// [ 'c', 'a', 'v' ]
console.log(m2.min());// a

函数类型接口

interface ConfigFn{
    (value1: string, value2: string): string;
}
var setData: ConfigFn = function(value1: string, value2: string): string {
    return value1 + value2;
}
setData('name', '张三');

泛型接口写法1:在参数位置加泛型约束

interface ConfigFn{
    <T>(value:T): T;
}
// 完整的函数接口
var getData: ConfigFn = function<T>(value: T): T {
    return value;
}
// 类型推断 + 自定义参数名函数接口
var getData2: ConfigFn = function(v) {
    return v;
}

getData<string>('张三');
getData<string>(1243);  // 错误  Argument of type '1243' is not assignable to parameter of type 'string'

getData2<string>('张三');
getData2<string>(true);  // 错误 Argument of type 'true' is not assignable to parameter of type 'string'.

泛型接口写法2:在接口名后加泛型约束

interface ConfigFn<T>{
    (value: T): T;
}
     
function getData<T>(value: T): T {
    return value;
}
// 官方用法定义一个函数赋值给需要的变量并且约束类型
var myGetData: ConfigFn<string> = getData;

// 匿名函数用法
var myGetData2: ConfigFn<number> = function<T>(value: T): T{
    return value;
};      
myGetData('20');  // 正确
myGetData(20)  	  // 错误

myGetData2('30'); // 错误
myGetData2(40)    // 正确

泛类:泛型可以帮助我们避免重复的代码以及对不特定数据类型的支持(类型校验)
下面我们看看把类当做参数的泛型类

1、定义个类
2、把类作为参数来约束数据传入的类型

定义一个User的类这个类的作用就是映射数据库字段 , 然后定义一个 MysqlDb的类这个类用于操作数据库
然后把User类作为参数传入到MysqlDb中

// 可能还有其他字段,目前只定义两个字段
class User{
    username: string | undefined; // undefined 屏蔽不赋值的警告
    password: string | undefined;
}

// mysql 操作类
class MysqlDb{
	// 增加数据方法,返回值为成功和失败
	// 参数为 User 类型,验证数据合法性
    add(user: User): boolean{ 
        console.log(user);
        return true;
    }
}

// 实例化后的类是一个对象, 给对象添加两个值
var user = new User()
user.username = '张三'
user.password = '123456'

var Db = new MysqlDb();
// 把 user 对象当参数传入
Db.add(user); // { "username": "张三", "password": "123456" } 

把类作为参数来约束数据传入的类型

// 定义一个文章数据库类
class ArticleCate{
    title: string | undefined;
    desc: string | undefined;
    status: number | undefined
}

// 一样的 mysql 操作类
class MysqlDb{
    add(info: ArticleCate): boolean{
        console.log(info);
        console.log(info.title);
        return true;
    }

}
var a = new ArticleCate();
a.title = "国内";
a.desc = "国内新闻";
a.status = 1;

var Db = new MysqlDb();
Db.add(a); // { "title": "国内", "desc": "国内新闻", "status": 1 } 

根据上面两个例子, 我们重复的使用了 MysqlDb 这个类
优化之后可以写成泛型类如下示例代码:

// 定义操作数据库的泛型类
class MysqlDb<T>{
    add(info: T): boolean {
        console.log(info);       
        return true;
    }
    updated(info: T, id: number): boolean {
        console.log('update-', info);  
        console.log('updateid-', id); 
        return true;
    }
}

跟之前一样的操作, 给User表增加数据

// 1、定义一个User类 和数据库进行映射
class User{
    username: string | undefined;
    password: string | undefined;
}
var u = new User();
u.username = '张三';
u.password = '123456';
var Db = new MysqlDb<User>(); // 用泛型来验证数据合法性
Db.add(u);  // { "username": "张三", "pasword": "123456" } 
Db.add('123'); // 报错

相关ArticleCate增加数据

// 2、定义一个ArticleCate类 和数据库进行映射
class ArticleCate{
    title: string | undefined;
    desc: string | undefined;
    status: number | undefined;
    constructor(params: {
        title: string | undefined,
        desc: string | undefined,
        status?: number | undefined
    }){
        this.title = params.title;
        this.desc = params.desc;
        this.status = params.status;
    }
}
// 增加操作
var a = new ArticleCate({
    title: '分类',
    desc: '1111',
    status: 1
});

// 类当做参数的泛型类
var Db = new MysqlDb<ArticleCate>();
Db.add(a); // { "title": "分类", "desc": "1111", "status": 1 }
//修改数据
var a = new ArticleCate({
    title: '分类111',
    desc: '2222'      
});
a.status = 0;
var Db = new MysqlDb<ArticleCate>();
Db.updated(a, 12);
//  update-  { "title": "分类111", "desc": "2222", "status": 0 }  
//  updateid-  12

案例展示 :

功能:定义一个操作数据库的库 支持 Mysql Mssql MongoDb
要求1:Mysql MsSql MongoDb功能一样 都有 add update delete get方法
注意:约束统一的规范、以及代码重用
解决方案:需要约束规范所以要定义接口 ,需要代码重用所以用到泛型
1、接口:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范
2、泛型 通俗理解:泛型就是解决 类 接口 方法的复用性

// 泛型接口DBI, 规定增删改查四个函数
interface DBI<T>{
    add(info: T): boolean;
    update(info: T, id: number): boolean;
    delete(id: number): boolean;
    get(id: number): any[];
}

定义一个操作mysql数据库的类

注意:要实现泛型接口 , 这个类也应该是一个泛型类

class MysqlDb<T> implements DBI<T>{
    constructor() {
        console.log('数据库建立连接');
    }
    add(info: T): boolean {
        console.log(info);
        return true;
    }    
    update(info: T, id: number): boolean {
        throw new Error("Method not implemented.");
    }
    delete(id: number): boolean {
        throw new Error("Method not implemented.");
    }
    get(id: number): any[] {
        var list = [
            {
                title:'xxxx',
                desc:'xxxxxxxxxx'
            },
            {
                title:'xxxx',
                desc:'xxxxxxxxxx'
            }
        ]
        return list;
    }
}

定义一个操作mssql数据库的类

class MsSqlDb<T> implements DBI<T>{
    constructor() {
        console.log('数据库建立连接');
    }
    add(info: T): boolean {
        console.log(info);
        return true;
    }    
    update(info: T, id: number): boolean {
        throw new Error("Method not implemented.");
    }
    delete(id: number): boolean {
        throw new Error("Method not implemented.");
    }
    get(id: number): any[] {
        var list = [
            {
                title:'xxxx',
                desc:'xxxxxxxxxx'
            },
            {
                title:'xxxx',
                desc:'xxxxxxxxxx'
            }
        ]
        return list;
    }
}

操作用户表 定义一个User类和数据表做映射

class User{
    username: string | undefined;
    password: string | undefined;
}
var u = new User();
u.username = '张三111';
u.password = '123456';
var oMysql = new MysqlDb<User>(); // 类作为参数来约束数据传入的类型 
oMysql.add(u);

class User{
    username: string | undefined;
    password: string | undefined;
}

var u = new User();
u.username = '张三2222';
u.password = '123456';

var oMssql = new MsSqlDb<User>();
oMssql.add(u);

//获取User表 ID=4的数据
var data = oMssql.get(4);
console.log(data);

六、命名空间

在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内

同Java的包、.Net的命名空间一样,TypeScript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象。
命名空间内的对象通过export关键字对外暴露。

// modules/animal.ts
export namespace A{
    interface Animal {
        name: string;
        eat(): void;
    }
    export class Dog implements Animal {
        name: string;
        constructor(theName: string) {
            this.name = theName;
        }

        eat() {
            console.log(`${this.name} 在吃狗粮。`);
        }
    }

    export class Cat implements Animal {
        name: string;
        constructor(theName: string) {
            this.name = theName;
        }

        eat() {
            console.log(`${this.name} 吃猫粮。`);
        }
    }   
}

export namespace B{
    interface Animal {
        name: string;
        eat(): void;
    }
    export class Dog implements Animal {
        name: string;
        constructor(theName: string) {
            this.name = theName;
        }

        eat() {
            console.log(`${this.name} 在吃狗粮。`);
        }
    }

    export class Cat implements Animal {
        name: string;
        constructor(theName: string) {
            this.name = theName;
        }

        eat() {
            console.log(`${this.name} 在吃猫粮。`);
        }
    }   
}

命名空间和模块的区别:

命名空间:内部模块,主要用于组织代码,避免命名冲突。编译完为一个自执行函数的域
模 块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间。

import { A, B } from './modules/animal'; // 用法跟模块类似, 命名空间内的类直接当做他的属性直接实例化
var d = new A.Dog('小黑');
d.eat();

var dog = new B.Dog('小花');
dog.eat();

七、装饰器

装饰器:装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。

通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
装饰器的写法:普通装饰器(无法传参) 、 装饰器工厂(可传参)
装饰器是过去几年中js最大的成就之一,已是Es7的标准特性之一

1、 类装饰器: 普通装饰器(无法传参)

类装饰器:类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 传入一个参数

function logClass(params: any) {
    // params 就是当前类
    console.log(params); // class HttpClient { constructor() { } getData() { } } 
    params.prototype.apiUrl = '动态扩展的属性';
    params.prototype.run = function() {
        console.log('我是一个run方法');
    }

}

@logClass
class HttpClient{
    constructor(){
    }
    getData(){

    }
}
var http:any=new HttpClient();
console.log(http.apiUrl); // 动态扩展的属性 
http.run(); // 我是一个run方法 

2、 类装饰器: 装饰器工厂(可传参)

function logClass(params: string) {
    return function(target: any) { // 工厂匿名函数携带当前类
        console.log(target); // class HttpClient { constructor() { } getData() { } } 
        console.log(params); //  http://www.baidu.com/api
        target.prototype.apiUrl = params;
    }
}

@logClass('http://www.baidu.com/api')
class HttpClient{
    constructor() {
    }

    getData(){
    }
}

var http: any = new HttpClient();
console.log(http.apiUrl); //  http://www.baidu.com/api

vue - ts 中实例显示 , 具体操作请继续向下看

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
import Son from './son.vue'
@Component({
  components: { Son }
})
export default class Test extends Vue {}
</script

3、 类装饰器

下面是一个重载构造函数的例子。
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

function logClass(target: any) { // 定义装饰器 重载构造函数或者方法
    console.log(target); // class HttpClient { constructor() { this.apiUrl = '我是构造函数里面的apiUrl'; } getData() { console.log(this.apiUrl); } } 
    return class extends target{ // 继承这个类可以直接修改属性和方法(必须重载属性和方法否则报错)
        apiUrl: any = '我是修改后的数据';
        getData() {
            this.apiUrl = this.apiUrl + '----';
            console.log(this.apiUrl);
        }
    }
}


@logClass
class HttpClient{
    public apiUrl: string | undefined;
    constructor() {
        this.apiUrl = '我是构造函数里面的apiUrl';
    }
    getData() {
        console.log(this.apiUrl);
    }
}

var http = new HttpClient();
http.getData(); // 我是修改后的数据---- 

4、属性装饰器

属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字。

// 类装饰器
function logClass(params: string) {
     return function(target: any) {
         console.log('logClass-', target); // logClass-  class HttpClient { constructor() { } getData() { console.log(this.url); } } 
         console.log('logClass-', params); // logClass-  xxxx    
     }
 }

// 属性装饰器
function logProperty(params: any) {
    return function(target: any, attr: any) {
        console.log('logProperty-', target); // logProperty- {} (类的原型对象)
        console.log('logProperty-', params); // logProperty-  http://baidu.com
        console.log('logProperty-', attr);   // logProperty-  url
        target[attr] = params;
    }
}
@logClass('xxxx')
class HttpClient{
    @logProperty('http://baidu.com')
    public url: any | undefined;
    constructor() {
    }
    getData() {
        console.log(this.url);
    }
}
var http = new HttpClient();
http.getData(); // http://baidu.com

5、方法装饰器

它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
方法装饰会在运行时传入下列3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字。
3、成员的属性描述符。

// 方法装饰器一
function get(params: any) {
    return function(target: any, methodName: any, desc: any) {
        console.log('get-', params);     // get- http://www.baidu,com 
        console.log('get-', target);     // get- {} (类原型对象)
        console.log('get-', methodName); // get- getData (装饰的函数名)
        console.log('get-', desc);       // get- { "writable": true, "enumerable": false, "configurable": true } (属性描述)
        // 扩展当前实例的属性和方法
        target.apiUrl = 'xxxx'; 
        target.run = function() {
            console.log('run');
        }
    }
}

class HttpClient{  
    public url: any | undefined;
    constructor() {
    }
    @get('http://www.baidu,com')
    getData() {
        console.log(this.url);
    }
}

var http:any = new HttpClient();
console.log(http.apiUrl); // xxxx 
http.run();               // run
// 方法装饰器二
function get(params: any) {
    return function(target: any, methodName: any, desc: any) {
        console.log(target);        // {} 
        console.log(methodName);    // getData
        console.log(desc.value);    // getData(...args) { console.log(args); console.log('我是getData里面的方法'); }   
        // 修改装饰器的方法:功能是要把装饰器方法里面传入的所有参数改为 string 类型, 类似于拦截器
        // 1、保存当前的方法
        var oMethod = desc.value;
        desc.value = function(...args: any[]) {                
            args = args.map((value) => {
                return String(value);
            })
            oMethod.apply(this, args);
        }
    }
}

class HttpClient{  
    public url: any | undefined;
    constructor() {
    }
    @get('http://www.baidu,com')
    getData(...args: any[]) {
        console.log(args);                   // [ "123", "xxx" ] 
        console.log('我是getData里面的方法'); // 我是getData里面的方法 
    }
}

var http = new HttpClient();
http.getData(123, 'xxx');	// [ "123", "xxx" ]    我是getData里面的方法 

6、方法参数装饰器(用的较少)

参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、参数的名字。
3、参数在函数参数列表中的索引。

function logParams(params:any){
    return function(target: any, methodName: any, paramsIndex: any) {
        console.log('logParams-', params);      // logParams  xxxxx 
        console.log('logParams-', target);      // logParams- {} 
        console.log('logParams-', methodName);  // logParams- getData
        console.log('logParams-', paramsIndex); // logParams- 0 
        target.apiUrl = params;
    }   
}

class HttpClient{  
      public url: any | undefined;
       constructor() {
      }           
      getData(@logParams('xxxxx') uuid: any) {               
          console.log(uuid);
      }
}
var http:any = new HttpClient();
http.getData(123456);       // 123456
console.log( http.apiUrl);  // xxxxx 

装饰器执行顺序:
属性 > 方法 > 方法参数 > 类
如果有多个同样的装饰器,它会先执行后面的

function logClass1(params: string) {
    return function(target: any) {
      console.log('类装饰器1')
    }
}

function logClass2(params: string) {
    return function(target: any) {
      console.log('类装饰器2')
    }
}

function logAttribute1(params?: string) {
    return function(target: any, attrName: any) {
      console.log('属性装饰器1')
    }
}

function logAttribute2(params?: string) {
    return function(target: any, attrName: any) {
      console.log('属性装饰器2')
    }
}

function logMethod1(params?: string) {
    return function(target: any, attrName: any, desc: any) {
      console.log('方法装饰器1')
    }
}

function logMethod2(params?: string) {
    return function(target: any, attrName: any, desc: any) {
      console.log('方法装饰器2')
    }
}

function logParams1(params?: string) {
    return function(target: any, attrName: any, desc: any) {
      console.log('方法参数装饰器1')
    }
}

function logParams2(params?: string) {
    return function(target: any, attrName: any, desc: any) {
      console.log('方法参数装饰器2')
    }
}

@logClass1('http://www.baidu.com/api')
@logClass2('xxxx')
class HttpClient{
    @logAttribute1()
    @logAttribute2()
    public apiUrl: string | undefined;
    constructor() {
    }

    @logMethod1()
    @logMethod2()
    getData() {
        return true;
    }

    setData(@logParams1() attr1: any, @logParams2() attr2: any,) {
    }
}

var http:any=new HttpClient();
//  属性装饰器2 
//  属性装饰器1 
//  方法装饰器2 
//  方法装饰器1 
//  方法参数装饰器2 
//  方法参数装饰器1 
//  类装饰器2 
//  类装饰器1 

八、使用typescript来写vue详细说明

学完typescript, 如何应用到vue中呢?

1、 如何创建一个ts 项目?

  • 在使用cli创建项目的时候勾选 typescript
  • 基于类的组件选项选择 - yes
  • 代码检查 tslint

2、已经开发的项目转ts 项目 ( 整个项目全部转成 ts ,包括vue文件和js 文件)

注意: 正式项目不要轻易执行
命令行输入:

vue add @vue/typescript
  • 使用vue-cli创建vue项目, cli 中默认安装 vue-property-decorator
  • Vue Property Decorator提供了7个装饰器:
  • @Emit、@Inject、@Model、@Prop、@Provide、@Watch、@Component
  • 实现像原生 JavaScript class 那样声明组件

3、应用

一个基本的 vue 组件模板

<template>
  <div class="test">
    test
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({})
export default class Test extends Vue {

}

</script>

声明响应式属性 data

<template>
  <div class="test">
    {{name}}
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({})
export default class Test extends Vue {
  private name: string = '李明';
}
</script>

这样的写法等同于之前的:

export default {
  name: 'App',
  data() {
    return {
      name: '李明'
    }
  }
}

计算属性 computed

<template>
  <div class="test">
    <button class="btn" @click="num-=1">-</button>

    <div>num:{{num}}</div>
    <div>age:{{age}}</div>

    <button class="btn" @click="num+=1">+</button>
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({})
export default class Test extends Vue {
  private num: number = 0;

  // 计算属性
  get age () :string {
    // 计算属性的get
    return `我的年龄是 ${this.num} 岁`
  }
  set age (value) {
    // 计算属性的set
    this.num -= 1
  }
}

</script>

侦听属性 watch

<template>
  <div class="test">
    <input type="text" v-model="name">
    <p>{{str}}</p>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
@Component({})
export default class Test extends Vue {
  private str: string = ''
  private name: string = 'jerry'

  // 监控name属性
  @Watch('name')
  changeName (newValue: string, oldValue: string) {
    // newValue:name改变以后得
    // oldValue:name改变之前的
    console.log('newValue')
    console.log(newValue)
    console.log('oldValue')
    console.log(oldValue)
    // 将name反转,最后赋值给str
    this.str = this.name.split('').reverse().join('')
  }
}

</script>

生命周期

<template>
  <div class="test">
  </div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';
@Component
export default class Test extends Vue {
  // 生命周期
  beforeCreate () {
    console.log('before create')
  }
  created () {
    console.log('created')
  }
  beforeMount () {
    console.log('before mount')
  }
  mounted () {
    console.log('mounted')
  }
}
</script>

组件注册与传递 Prop

<template>
  <div class="test">
    <Son mes="我是父组件传递过来的信息"></Son>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
import Son from './son.vue'
@Component({
  components: { Son }
})
export default class Test extends Vue {}
</script>

son.vue

<template>
  <div class="son">
    <h1>{{mes}}</h1>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Watch, Prop } from 'vue-property-decorator'
@Component
export default class Test extends Vue {
  @Prop({
  	type: Boolean,
	required: false,
	default: false // 默认属性的默认值
  })
  private mes!: string
  @Prop() private fatherKey: string[]; // 其他没有默认值的传值
}
</script>

父子组件通信 Emit

<template>
  <div class="test">
    <Son @sonMes='parentMes'></Son>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
import Son from './son.vue'
@Component({
  components: { Son }
})
export default class parent extends Vue {
  parentMes (val: string) {
    console.log(1)
    alert('子组件传递:' + val)
  }
}
</script>

son.vue

<template>
  <div class="son">
    <button @click='myClick'>传递给父组件</button>
    <div>输入的姓名:
      <input type="text" v-model="name">
    </div>
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class Test extends Vue {
  private name: string = ''
  myClick () {
    console.log(2)
    this.$emit('sonMes', this.name)
  }
}
</script>

son.vue或者使用@Emit来写

<template>
  <div class="son">
    <button @click='myClick'>传递给父组件</button>
    <div>输入的姓名:
      <input type="text" v-model="name">
    </div>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Emit } from 'vue-property-decorator'
@Component
export default class Test extends Vue {
  private name: string = ''
  @Emit('sonMes')
  myClick () {
    return this.name
  }
}
</script>

注意:这里的@Emit('sonMes')参数sonMes为父组件绑定的那个函数名,myClick返回值为传递过去的信息

main.ts中注册路由导航守卫并在组件中使用路由钩子函数
watch 监听 router 的变化

shims-vue.d.ts 的设置

// shims-vue.d.ts
import Vue from 'vue'
import VueRouter, {Route} from 'vue-router';
declare module 'vue/types/vue' {
	interface Vue {
		$router: VueRouter; // 这表示this下有这个东西
		$route: Route;
	}
}

main.ts 的设置

// main.ts
import { Component } from "vue-class-component";
Component.registerHooks([
  "beforeRouteEnter", //进入路由之前
  "beforeRouteLeave", //离开路由之前
  "beforeRouteUpdate"
]);

需要监听路由钩子的 SCF 组件:

<script lang="ts">
  // xxx.vue 的script标签内
  import { Component, Vue, Prop, Watch } from "vue-property-decorator";
  import { Route, RawLocation } from 'vue-router';
  // # 下边两段,看你需要什么了:
// 1/监听路由变化

@Watch('$route',{ immediate: true })

private changeRouter(route: Route){
	console.log(route)
}
// 2/定义路由钩子函数

private beforeRouteEnter(to: Route, from: Route, next: () => void): void {
	console.log('beforeRouteEnter', to, from, next)
	next(); // 没有next将不会进入路由内部,跟vue文档用法一致
}

private beforeRouteUpdate(to: Route, from: Route, next: () => void): void {
	console.log('beforeRouteUpdate'); // 暂时不生效,版本问题
	next();
}

private beforeRouteLeave(to: Route, from: Route, next: () => void): void {
	console.log('beforeRouteLeave');
	next();
}

</script>
  • 17
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值