ts篇-vue-cli3 + ts + Decorator + AOP

TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。

ts

优点

  • 静态类型检查
  • IDE 智能提示
  • 代码重构
  • 可读性

基础

let test: boolean = true;
let test: string = 'hello';
let test: number = 1;

let test: number[] = [1,2,3];
let test: Array<number>= [1,2,3];

let test: object = { a:1 };

function test(n:number): string {
    return n+'hello';
}

重点

  • 类型断言

有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。

// “尖括号”语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// as语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
  • 接口 interface

TypeScript的核心原则之一是对值所具有的结构进行类型检查

// 规范首字母大写以 ”I“ 开头
interface ITest {
    readonly x:number;
    color?: string;
    height: number;
    width?: number;
    [propName: string]: any;
}

// 对象检查
let test: ITest = { 
  x: 10,  
  color: 'red',
  height: 100,
  myProp: 'hello world'
};
test.x = 10; // 报错,只读属性不能赋值

// 继承
interface ITest2 extends ITest {
    other: string
}

  • type
    type 与 interface 的用法很类似

  • interface,type两者的区别
    相同点:
    都可以描述一个对象

interface IUser {
  name: string
  age: number
}
// ------
type User = {
  name: string
  age: number
};

都允许拓展(extends)

interface IName { 
  name: string; 
}
interface User extends IName { 
  age: number; 
}
// ------
type Name = { 
  name: string; 
}
type User = Name & { age: number  };

不同点:
type 可以而 interface 不行

// 基本类型别名
type Name = string
 
// 联合类型
interface Dog {
    wang: string
}
interface Cat {
    miao: string
}
 
type Pet = Dog | Cat

interface 可以而 type 不行


interface User {
  name: string
  age: number
}
 
interface User {
  sex: string
}
 
/*
同名的接口会被默认合并
User的接口为 {
  name: string
  age: number
  sex: string 
}
*/

总结:
如果不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type。先考虑用interface。

  • 泛型 <T>
// 例子
interface IResult<T> {
    code: number;
    data: T;
    message: string;
 }
const {
  code,
  data,
  message
}: IResult<{
  list: Array<{ id: number; name: string }>;
}> = await this.$request.get('/getList');

// 这样子ide就会有智能提示了
data.list[0].id
data.list[0].name
  • 枚举 enum
// 首字母大写
enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}
// 如上,我们定义了一个数字枚举, Up使用初始化为 1。 其余的成员会从 1开始自动增长。 换句话说, Direction.Up的值为 1, Down为 2, Left为 3, Right为 4。

ts 一些符号解释

“&” 表示且

type Test = { id: number } & { name: number  };

“|” 表示或

type Test = string | number;

type Test = 'test' | 'per' | 'prod';

“?” 表示可选

interface IUser { 
  name?: string; 
}

“!” 非空断言

// 例子
function fun(str: string | null): number {
    return str!.length;
}
// 如果编译器不能够去除 null或 undefined,你可以使用类型断言手动去除。 语法是添加 !后缀: identifier!从 identifier的类型里去除了 null和 undefined

vue + ts

vue-class-component VS vue-property-decorator

vue-class-component 是 vue 的官方库,作用是用类的方式编写组件。
这种编写方式可以让.vue文件的js域结构更扁平,并使vue组件可以使用继承、混入等高级特性。

vue-property-decorator 是一个非官方库,是 vue-class-component 的很好的补充。它可以让vue的某些属性和方法,通过修饰器的写法让它也写到vue组件实例的类里面。
比如 @Prop @Watch @Emit ...

一个较完整的demo

<template>
  <div class="test">
    <Comp></Comp>
  </div>
</template>

<script lang="ts">
// 类型约束定义,规范:写在顶部
interface IUser {
  id: number;
  name: string;
}

import { Component, Vue, Watch } from 'vue-property-decorator';
import { State, Getter, Action, Mutation, namespace } from 'vuex-class';

// 引入子组件
import Comp from '@/components/FormItem';

/* 接口路径常量定义 */
const GET_LIST = 'xxx/getList';

// vuex 模块定义,命名规范moduleXxx,Xxx为实际的模块名
const moduleTest = namespace('test');

@Component({
  // 子组件
  components: { Comp }
})

// 类名用大写,与router的name一致,这里的name将作为组件的name
export default class Test extends Vue {
  // vuex
  @moduleTest.State('user')
  user!: IUser;

  @moduleTest.Action('getUser')
  // by the way,方法返回值为空一般这样子写约束 () => {};
  getUser!: () => {};

  @moduleTest.Mutation('setUser')
  setUser!: (user: IUser) => {};
  
  // prop
  @Prop({
    type: String,
    default: ''
  })
  value!: string;
  @Prop({
    type: Boolean,
    default: false
  })
  value2!: boolean;


  // data 初始化
  test1: boolean = false;
  test2: Date = new Date();
  test3: string = '';

  // watch,方法命名 onXxxChanged,Xxx为需要监听的变量名
  @Watch('test')
  onTestChanged(val: string, oldVal: string) {}

  // computed
  get test4() {
    return this.test1 + this.test3;
  }

  // 生命周期
  created() {}

  mounted() {}

  // 方法,不需要methods包裹
  test(n: number): string {
    return n + 'hello world!!!';
  }
}
</script>

<style lang="less" scoped>
.test {
}
</style>

约束怎么写,要写在哪里?(局部约束,全局约束)

  • 局部约束,用在一个页面上只有【一处】才用到的约束
// ...
<script lang="ts">
const user: { id: number; name: string } = { id: 1, name: '张三' };
</script>
// ...
  • 局部约束,用在一个页面上有【多处】需要用到的约束
// ...
<script lang="ts">
// 类型约束定义,规范:写在<script lang="ts">顶部</script>

interface IUser {
  id: number;
  name: string;
}

// ...

const user: IUser = { id: 1, name: '张三' };

</script>
  • 全局约束
    出现好几个处都需要用到的约束,可以定义至全局 global.d.ts
    新增global.d.ts 文件放置项目根目录
declare global {
  interface Window {
    VM: any;
  }

  type Env = 'test' | 'pre' | 'prod';

  interface IRequest {
    get: (path: string, data?: object, config?: object) => any;
    post: (path: string, data?: object, config?: object) => any;
    put: (path: string, data?: object, config?: object) => any;
  }
}
  • 命名空间
    由于写了全局约束,可能需要用命名空间来进行优雅的编写,命名空间也可以让我们更加清楚约束是用来做什么的,[namespace统一用小写驼峰]
// 约束vuex的user模块相关
declare global {
  namespace myVuex {
    namespace user{
      interface IUser {
        id?: number;
        name?: string;
      }
      interface IState {
        user: IUser;
      }
    }
  }
}

其他地方就可以用

const user: myVuex.user.IUser = { id: 1, name: '张三' };

装饰器 Decorator & AOP编程

  • 什么是 AOP

面向切面编程(AOP, Aspect Orient Programming)主要实现的目的是针对业务处理过程中的切面进行提取,所面对的是处理过程中某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP是对OOP的一个横向的补充,主要作用是把一些业务无关的功能抽离,例如日志打印、统计数据、安全控制、异常处理等。这些功能都与核心业务无关,但又随处可见。将其抽离出来用动态插入的方式嵌入到各业务逻辑中。好处是业务模块可变得比较干净、不受污染,同时功能点能够得到很好的复用,给模块解耦。

这里只举环绕通知

  1. before(前置通知)
    场景:提交表单时,我想要先通过一系列的验证,然后再执行表单提交

  2. after(后置通知)

  3. around(环绕通知)
    场景:修饰方法,提交表单的时候,点击提交按钮。我想要在提交前把按钮禁用掉,然后提交成功后【ajax请求后】把按钮启用。

原生写法

/**
 * @desc 按钮节流开关,场景:用在异步提交表单的时候,防止用户重复提交
 * 1开 0关
 * @param prop
 */
const ThorttleButton = (prop: string) => (
  target: object,
  name: string,
  descriptor: any
): any => {
  const origin = descriptor.value;
  descriptor.value = async function(...args: any) {
    this[prop] = 0;
    await origin.apply(this, args);
    this[prop] = 1;
  };
};

export default ThorttleButton;

用 createDecorator 方法

import { createDecorator } from 'vue-class-component';
const ThorttleButton = (prop: string) =>
  createDecorator((options, key) => {
    const origin = options.methods![key];
    options.methods![key] = async function(...args: any) {
      (this as any)[prop] = 0;
      await origin.apply(this, args);
      (this as any)[prop] = 1;
    };
  });

export default ThorttleButton;

调用

import ThorttleButton from '@/utils/decorate/ThorttleButton';
// ...
postBtn: number = 1;
// ...

@ThorttleButton('postBtn')
async postForm() {
  await this.$request.post('/postData');
}

// ...

同一个方法有多个装饰器,装饰器怎么执行?

比喻:会像剥洋葱一样,先从外到内进入,然后由内向外执行。

具体描述:当多个装饰器应用于一个声明上,它们求值方式与复合函数相似。
在这个模型下,当复合 fg 时,复合的结果(fg)(x)等同于f(g(x))。
1.由上至下依次对装饰器表达式求值。
2.求值的结果会被当作函数,由下至上依次调用。

function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}

// f(): evaluated
// g(): evaluated
// g(): called
// f(): called
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容