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

2022年05月10日 阅读数:8
这篇文章主要向大家介绍前端必备typescript基础知识教程(vue+typescript项目实战),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

TypeScript 知识全总结

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

1、TypeScript中的数据类型

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

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

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

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

一、布尔类型(boolean)

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

var flag = true;
flag = 456;

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

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

二、数字类型(number)

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

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

三、字符串类型(string)

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

四、数组类型(array)

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

  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]

五、 元组类型(tuple)

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

// 正常数组
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]

六、枚举类型(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);

七、任意类型(any)

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

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

八、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

九、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();

十、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('错误');
})()

十一、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'.

十二、类型兼容性了解

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

2、TypeScript中的函数

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

一、函数的定义

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();

二、方法可选参数

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'); // 校验参数会报错  不能把可选参数放在前面

三、默认参数 可选参数

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

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

四、剩余参数

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);

五、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

六、箭头函数 es6

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

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

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

3、TypeScript中的类

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

一、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();

二、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();

三、类里面的修饰符

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'.

四、静态属性 静态方法

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

五、多态、抽象类、继承

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

//多态属于继承
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();

4、typeScript中的接口

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

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

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

定义接入口规范。全部想经过这个接口访问任何东西都须要按照这个接口规则应用

一、属性接口

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代码');

5、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)    // 正确

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

一、定义个类
二、把类做为参数来约束数据传入的类型

定义一个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表增长数据

// 一、定义一个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增长数据

// 二、定义一个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方法
注意:约束统一的规范、以及代码重用
解决方案:须要约束规范因此要定义接口 ,须要代码重用因此用到泛型
一、接口:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动做的规范
二、泛型 通俗理解:泛型就是解决 类 接口 方法的复用性

// 泛型接口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);

6、命名空间

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

同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();

7、装饰器

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

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

一、 类装饰器: 普通装饰器(没法传参)

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

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方法 

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

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

三、 类装饰器

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

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(); // 我是修改后的数据---- 

四、属性装饰器

属性装饰器表达式会在运行时看成函数被调用,传入下列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

五、方法装饰器

它会被应用到方法的 属性描述符上,能够用来监视,修改或者替换方法定义。
方法装饰会在运行时传入下列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 类型, 相似于拦截器
        // 一、保存当前的方法
        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里面的方法 

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

参数装饰器表达式会在运行时看成函数被调用,传入下列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 

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

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

一、 如何建立一个ts 项目?

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

二、已经开发的项目转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 那样声明组件

三、应用

一个基本的 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>