美文网首页
vue + typeScript

vue + typeScript

作者: O蚂蚁O | 来源:发表于2021-10-20 13:59 被阅读0次

    一、TS 快速上手

    1. 关于TS

    TypeScript 是 JavaScript 的一个超集,可以编译成纯 JavaScript。TypeScript 在 JavaScript 的基础上添加了可选的静态类型基于类的面向对象编程

    TypeScript 提供最新的和不断发展的 JavaScript 特性,下图显示了 TypeScript 与 ES5、ES2015 和 ES2016 之间的关系:

    2. 基础

    2.1 基本语法
    :<TypeAnnotation>
    
    

    TypeScript的基本类型语法是在变量之后使用冒号进行类型标识,这种语法也揭示了TypeScript的类型声明实际上是可选的。

    (1) 原始值类型
    let bool: boolean = false;
    let num: number = 10;
    let str: string = 'sip';
    
    // var bool = false;
    // var num = 10;
    // var str = 'sip';
    
    
    (2) 特殊类型
    • Any

      任意值(Any)用来表示允许赋值为任意类型。 在任意值上访问任何属性 / 调用任何方法都是允许

      let notSure: any = 'any';
      notSure = 1;
      
      

    未声明类型的变量

    变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型: 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:

    let something;
    // 等同于 let something: any;
    
    
    • Void

      void 类型像是与 any 类型相反,它表示没有任何类型 ;在 TypeScript 中,可以用 void 表示没有任何返回值的函数:

      function alertName(): void {
          alert('My name is Tom');
      }
      
      

    声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefinednull

    let unusable: void = undefined;
    
    
    • Null 和 Undefined

      let u: undefined = undefined;
      let n: null = null;
      
      

    void 的区别是,undefinednull 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:

    // 这样不会报错
    let num: number = undefined;
    // 这样也不会报错
    let u: undefined;
    let num: number = u;
    
    

    void 类型的变量不能赋值给 number 类型的变量:

    let u: void;
    let num: number = u;
    
    // Type 'void' is not assignable to type 'number'.
    
    
    2.2 类型推论

    如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。

    以下代码虽然没有指定类型,但是会在编译的时候报错:

    let num = 'seven';
    num = 7;
    
    // error TS2322: Type '7' is not assignable to type 'string'.
    
    

    事实上,它等价于:

    let num: string = 'seven';
    num = 7;
    
    // error TS2322: Type '7' is not assignable to type 'string'.
    
    

    TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论

    如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查

    let num;
    num = 'seven';
    num = 7;
    
    
    2.3 联合类型

    联合类型(Union Types)表示取值可以为多种类型中的一种。

    let strNum: string | number;
    strNum = 'seven';
    strNum = 7;
    
    let strNum2: string | number;
    strNum2 = true;
    
    // error TS2322: Type 'true' is not assignable to type 'string | number'
    
    
    2.4 对象的类型——接口

    在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。

    • 例子

      // 接口 Person (接口一般首字母大写)
      interface Person {
          name: string;
          age: number;
      }
      
      let man: Person = {
          name: 'Tom',
          age: 25
      };
      
      

    定义的变量比接口多/少了一些属性都是不允许的,赋值的时候,变量的形状必须和接口的形状保持一致

    • (1) 可选属性

      可选属性的含义是该属性可以不存在,但不允许添加未定义的属性

      interface Person {
          name: string;
          age?: number;
      }
      
      let man: Person = {
          name: 'Tom'
      };
      
      let man2: Person = {
          name: 'Tom',
          age: 25
      };
      
      let man3: Person = {
          name: 'Tom',
          tel: '1370000000'
      };
      // error TS2322: Type '{ name: string; tel: string; }' is not assignable to type 'Person'. 
      // Object literal may only specify known properties, and 'tel' does not exist in type 'Person'.
      
      
    • (2) 任意属性

      interface Person {
          name: string;
          age?: number;
          [propName: string]: any;
      }
      
      let man: Person = {
          name: 'Tom',
          gender: 'male'
      };
      
      

      使用 [propName: string] 定义了任意属性取 string 类型的值。

      1. 一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
      1. 一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
    • (3) 只读属性

      interface Person {
          readonly id: number;
          name: string;
          age?: number;
          [propName: string]: any;
      }
      
      let man: Person = {
          id: 99999,
          name: 'Tom',
          gender: 'male'
      };
      
      man.id = 10000;
      // error TS2540: Cannot assign to 'id' because it is a read-only property.
      
      
    2.5 数组的类型
    • 类型 + 方括号

      数组的项中不允许出现其他的类型:

      let fibonacci: number[] = [1, '1', 2, 3, 5];
      
      // Type 'string' is not assignable to type 'number'.
      
      
    • 数组泛型

      let fibonacci: Array<number> = [1, 1, 2, 3, 5];
      
      
    • 用接口表示数组

      interface NumberArray {
          [index: number]: number;
      }
      let fibonacci: NumberArray = [1, 1, 2, 3, 5];
      
      
    • 类数组

      类数组(Array-like Object)不是数组类型,比如 arguments

      function sum() {
          let args: {
              [index: number]: any;
              length: number;
              callee: Function;
          } = arguments;
      }
      
      
    • any 在数组中的应用

      let list: any[] = ['1', 1, { key: 'value' }];
      
      
    2.6 函数的类型
    • 函数声明

      function sum(x: number, y: number): number {
          return x + y;
      }
      // 注意,输入多余的(或者少于要求的)参数,是不被允许的:
      sum(1, 2, 3);
      // error TS2554: Expected 2 arguments, but got 3.
      sum(1);
      // error TS2554: Expected 2 arguments, but got 1.
      
      
    • 函数表达式

      let mySum = function (x: number, y: number): number {
          return x + y;
      };
      
      

      上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。

    如果需要我们手动给 mySum 添加类型,则应该是这样:

    let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
        return x + y;
    };
    
    

    注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>

    在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

    • 用接口定义函数的形状

      interface SearchFunc {
          (source: string, subString: string): boolean;
      }
      
      let mySearch: SearchFunc;
      mySearch = function(source: string, subString: string) {
          return source.search(subString) !== -1;
      }
      
      

      采用函数表达式|接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。

    • 可选参数

      前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?

      与接口中的可选属性类似,我们用 ? 表示可选的参数:

      function buildName(firstName: string, lastName?: string) {
          if (lastName) {
              return firstName + ' ' + lastName;
          } else {
              return firstName;
          }
      }
      let tomcat = buildName('Tom', 'Cat');
      let tom = buildName('Tom');
      
      

    需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了

    function buildName(firstName?: string, lastName: string) {
        if (firstName) {
            return firstName + ' ' + lastName;
        } else {
            return lastName;
        }
    }
    let tomcat = buildName('Tom', 'Cat');
    let tom = buildName(undefined, 'Tom');
    
    // error TS1016: A required parameter cannot follow an optional parameter.
    
    
    • 参数默认值

      TypeScript 会将添加了默认值的参数识别为可选参数,此时就不受「可选参数必须接在必需参数后面」的限制

      function buildName(firstName: string = 'Tom', lastName: string) {
          return firstName + ' ' + lastName;
      }
      let tomcat = buildName('Tom', 'Cat');
      let cat = buildName(undefined, 'Cat');
      
      
    • 重载

      重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

    2.7 声明文件

    声明文件必需以 .d.ts 为后缀。

    一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件。

    • declare var 声明全局变量 (declare let 和 declare const)
    • declare function 声明全局方法
    • declare class 声明全局类
    • declare enum 声明全局枚举类型
    • declare namespace 声明(含有子属性的)全局对象
    • interface 和 type 声明全局类型
    • export 导出变量
    • export namespace 导出(含有子属性的)对象
    • export default ES6 默认导出
    • export = commonjs 导出模块
    • export as namespace UMD 库声明全局变量
    • declare global 扩展全局变量
    • declare module 扩展模块
    • /// <reference /> 三斜线指令

    3. 补充

    3.1 元组 Tuple

    元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

    let strNumberList: [string, number];
    
    strNumberList = ['hello', 10]; // OK
    strNumberList = [10, 'hello']; // webstorm会报红, 编译会出错
    
    
    3.2 枚举

    enum类型是对JavaScript标准数据类型的一个补充。

    enum Color {Red, Green, Blue}       // 默认从0开始编号
    let color: Color = Color.Green;
    
    // 编译后
    var Color;
    (function (Color) {
        Color[Color["Red"] = 0] = "Red";
        Color[Color["Green"] = 1] = "Green";
        Color[Color["Blue"] = 2] = "Blue";
    })(Color || (Color = {}));
    var color = Color.Green;
    
    // enum Color {Red = 1, Green, Blue}  指定从1开始编号
    // enum Color {Red = 1, Green = 2, Blue = 4}    手动赋值
    
    
    3.3 泛型

    泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

    二、TS 在 Vue2.x 的实践

    1. 构建

    通过官方脚手架构建安装 (Vue CLI 3)
    // 1\. 如果没有安装 Vue CLI 就先安装
    
    npm install --global @vue/cli
    
    // 2\. 创建一个新工程,并选择 "Manually select features (手动选择特性)" 选项
    
    vue create project-name
    
    

    然后,命令行会要求选择预设。使用箭头键选择 Manually select features。

    接下来,只需确保选择了 TypeScript 和 Babel 选项,然后配置其余设置,设置完成 vue cli 就会开始安装依赖并设置项目 。

    [图片上传失败...(image-7844bb-1608444165739)]

    2. 目录解析

    安装完成打开项目,目录结构如下:

    |-- project-name
        |-- .browserslistrc     # browserslistrc 配置文件 (用于支持 Autoprefixer)
        |-- .gitignore
        |-- .eslintrc.js        # eslint 相关配置
        |-- babel.config.js     # babel-loader 配置
        |-- package-lock.json
        |-- package.json        # package.json 依赖
        |-- postcss.config.js   # postcss 配置
        |-- README.md
        |-- tsconfig.json       # typescript 配置
        |-- vue.config.js       # vue-cli 配置
        |-- public              # 静态资源 (会被直接复制)
        |   |-- favicon.ico     # favicon图标
        |   |-- index.html      # html模板
        |-- src
        |   |-- App.vue         # 入口页面
        |   |-- main.ts         # 入口文件 加载组件 初始化等
        |   |-- shims-tsx.d.ts
        |   |-- shims-vue.d.ts
        |   |-- assets          # 主题 字体等静态资源 (由 webpack 处理加载)
        |   |-- components      # 全局组件
        |   |-- router          # 路由
        |   |-- store           # 全局 vuex store
        |   |-- styles          # 全局样式
        |   |-- views           # 所有页面
    
    

    ts构建的项目目录与之前用js构建的区别不大,区别主要是之前 js 后缀的现在改为了ts后缀,还多了tsconfig.jsonshims-tsx.d.tsshims-vue.d.ts这几个文件:

    • tsconfig.json: typescript配置文件,主要用于指定待编译的文件和定义编译选项
    • shims-tsx.d.ts: 允许.tsx 结尾的文件,在 Vue 项目中编写 jsx 代码
    • shims-vue.d.ts: 主要用于 TypeScript 识别.vue 文件,Ts 默认并不支持导入 vue 文件
    tsconfig.json`推荐配置
    // tsconfig.json
    {
      "compilerOptions": {
        // 与 Vue 的浏览器支持保持一致
        "target": "es5",
        // 这可以对 `this` 上的数据 property 进行更严格的推断
        "strict": true,
        // 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:
        "module": "es2015",
        "moduleResolution": "node"
      }
    }
    
    

    注意: 需要引入 strict: true (或者至少 noImplicitThis: true,这是 strict 模式的一部分) 以利用组件方法中 this 的类型检查,否则它会始终被看作 any 类型。

    3. 使用

    3.1 在 vue 中使用 typescript 常用的几个库
    • vue-class-component:

      是一个 Class Decorator,该库通过装饰器模式实现了 vue 的 ts 适配,也是官方推荐的使用 ts 方式

    • vue-property-decorator:

      是在 vue-class-component 基础上进行了修改与扩充

    • vuex-module-decorators:

      是在 typeScript 环境下中使用 vuex 的一种解决方案

    3.2 上手

    要让 TypeScript 正确推断 Vue 组件选项中的类型,我们需要使用 Vue.componentVue.extend 定义组件。

    3.2.1 vue-class-component

    vue-class-component 对 Vue 组件进行了一层封装,让 Vue 组件语法在结合了 TypeScript 语法之后更加扁平化

    <script>
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component({
      props: {
        propMessage: String
      }
    })
    export default class App extends Vue {
      // initial data
      msg = 123
    
      // use prop values for initial data
      helloMsg = 'Hello, ' + this.propMessage
    
      // lifecycle hook
      mounted () {
        this.greet()
      }
    
      // computed
      get computedMsg () {
        return 'computed ' + this.msg
      }
    
      // method
      greet () {
        alert('greeting: ' + this.msg)
      }
    }
    </script>
    
    
    3.2.2 vue-property-decorator

    vue-property-decorator 是在 vue-class-component 上增强了更多的结合 Vue 特性的装饰器,新增了这 7 个装饰器:

    • @Emit
    • @Inject
    • @Model
    • @Prop
    • @Provide
    • @Watch
    • @Component (从 vue-class-component 继承)
    (1) 组件声明
    • 引入组件:和原生写法一致,都需要先引入再注册
    <script lang="ts">
    import { Component, Vue } from 'vue-property-decorator';
    import HelloWorld from './components/HelloWorld.vue';
    
    @Component({
      components: {
        HelloWorld,
      },
    })
    export default class App extends Vue {
    
    }
    </script>
    
    
    (2) 响应式data 和 Prop声明
    • data

      类语法中可以直接定义为类的实例属性作为组件的响应式数据

    • prop

      类语法实现组件 props 定义是通过装饰器@Prop实现

    <script lang="ts">
    import { Component, Prop, Vue } from 'vue-property-decorator';
    
    @Component
    export default class HelloWorld extends Vue {
      private name: string;
        @Prop() private msg!: string;
    }
    
    
    • !: 表示一定存在,?: 表示可能不存在。这两种在语法上叫赋值断言
    • @Prop(options: (PropOptions | Constructor[] | Constructor) = {})
      • PropOptions,可以使用以下选项:type,default,required,validator
      • Constructor[],指定 prop 的可选类型
      • Constructor,例如 String,Number,Boolean 等,指定 prop 的类型
    (3) 生命周期函数

    生命周期钩子的使用和原先使用的区别:在类语法中直接将生命周期生命为方法(方法名称和生命周期名称一致)。

    public created(): void {   
       console.log('created'); 
    }  
    public mounted(): void {   
       console.log('mounted') 
    }
    
    
    (4) Watch 监听属性

    类语法实现响应式的数据监听,是由 vue-property-decorator 依赖提供 @Watch 装饰器来完成。

    • @Watch(path: string, options: WatchOptions = {})

    • 其中,options 包含两个属性

      • immediate?:boolean 侦听开始之后是否立即调用该回调函数
      • deep?:boolean 被侦听的对象的属性被改变时,是否调用该回调函数
    import { Vue, Component } from 'vue-property-decorator';
    
    @Component
    export default class Index extends Vue {
    
      //watch定义,其中Wacth装饰器第一个参数:响应式数据字符串(也可以定义为'a.b');
      //第二个参数options成员[immediate,deep]分别对应的是原生的用法
      @Watch('$route', { immediate: true })
      private changeRouter(val: Route, oldVal: Route) {
        console.log('$route watcher: ', val, oldVal);
      }
    }
    
    
    (5) computed 计算属性

    类语法中的计算属性的实现,是通过 get 取值函数。

    public get getName() {
      return 'computed ' + this.name;
    }
    
    

    getName 是计算后的值,name 是被监听的值

    (6) method

    在类语法实现原生 vue 的方法的方式,即通过直接定义类方法成员。

    public clickFunc(): void {
      console.log(this.name)
      console.log(this.msg)
    }
    
    
    (7) 事件触发

    ts 环境下 vue 的事件触发方式和 js 环境下是一致的,区别只是事件回调定义的地方不同(ts 定义为类的实例方法,js 定义在 methods 属性中)。

    (8) ref

    类语法中使用 ref 需要借助vue-property-decorator提供的@Ref装饰器

    (9) mixins

    类语法使用 mixins 需要继承vue-property-decorator提供的 Mixins 函数所生成的类。

    Mixins 函数的参数是 Vue 实例类

    (10) slots 和 scopedSlots

    slots 和 scopedSlots 的使用方式和原生 vue 保持一致。

    三、tsconfig.json

    概述

    如果一个目录下存在一个 tsconfig.json 文件,那么它意味着这个目录是TypeScript项目的根目录。

    tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项。 一个项目可以通过以下方式之一来编译:

    使用tsconfig.json
    • 不带任何输入文件的情况下调用tsc,编译器会从当前目录开始去查找tsconfig.json文件,逐级向上搜索父目录。
    • 不带任何输入文件的情况下调用tsc,且使用命令行参数--project(或-p)指定一个包含tsconfig.json文件的目录。

    当命令行上指定了输入文件时,tsconfig.json文件会被忽略。

    示例
    {
        // 编译选项
      "compilerOptions": {
        // 编译输出目标 ES 版本
        "target": "esnext",
        // 采用的模块系统
        "module": "es2015",
        // 以严格模式解析
        "strict": true,
        "jsx": "preserve",
        // 如何处理模块
        "moduleResolution": "node",
        // 启用装饰器
        "experimentalDecorators": true,
        // 允许从没有设置默认导出的模块中默认导入
        "allowSyntheticDefaultImports": true,
        // 定义一个变量就必须给它一个初始值
        "strictPropertyInitialization" : false,
        // 允许编译javascript文件
        "allowJs": true,
        // 是否包含可以用于 debug 的 sourceMap
        "sourceMap": true,
        // 忽略 this 的类型检查, Raise error on this expressions with an implied any type.
        "noImplicitThis": false,
        // 解析非相对模块名的基准目录 
        "baseUrl": ".",
        // 给错误和消息设置样式,使用颜色和上下文。
        "pretty": true,
        // 设置引入的定义文件
        "types": ["webpack-env", "mocha", "chai"],
        // 指定特殊模块的路径
        "paths": {
          "@/*": ["src/*"]
        },
        // 编译过程中需要引入的库文件的列表
        "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
      },
      // ts 管理的文件
      "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        "tests/**/*.ts",
        "tests/**/*.tsx"
      ],
      // ts 排除的文件
      "exclude": ["node_modules"]
    }
    
    

    "compilerOptions"可以被忽略,这时编译器会使用默认值。在这里查看完整的编译器选项列表。

    "files"指定一个包含相对或绝对文件路径的列表。

    "include""exclude"属性指定一个文件glob匹配模式列表。 支持的glob通配符有:

    • * 匹配0或多个字符(不包括目录分隔符)
    • ? 匹配一个任意字符(不包括目录分隔符)
    • **/ 递归匹配任意子目录

    链接:https://www.jianshu.com/p/04a3f9417074

    vue-property-decorator

    这个组件完全依赖于vue-class-component.它具备以下几个属性:

    @Component (完全继承于vue-class-component)
    @Emit
    @Inject
    @Provice
    @Prop
    @Watch
    @Model
    Mixins (在vue-class-component中定义);
    使用
    当我们在vue单文件中使用TypeScript时,引入vue-property-decorator之后,script中的标签就变为这样:

    <script lang="ts">
    import {Vue, Component} from 'vue-property-decorator';

    @Component({})
    export default class "组件名" extends Vue{
        ValA: string = "hello world";
        ValB: number = 1;
    }
    

    </script>
    等同于

    <script lang="es6">
    import Vue from 'vue';

    export default {
        data(){
            return {
                ValA: 'hello world',
                ValB: 1
            }
        }
    }
    

    </script>
    总结: 对于data里的变量对顶,我们可以直接按ts定义类变量的写法写就可以
    那么如果是计算属性呢? 这就要用到getter了.

    <script lang="ts">
    import {Vue, Component} from 'vue-property-decorator';

    @Component({})
    export default class "组件名" extends Vue{
        get ValA(){
            return 1;
        }
    }
    

    </script>
    等同于

    <script lang="es6">
    import Vue from 'vue';

    export default {
        computed: {
            ValA: function() {
                return 1;
            }
        }
    }
    

    </script>
    总结: 对于Vue中的计算属性,我们只需要将该计算属性名定义为一个函数,并在函数前加上get关键字即可.
    原本Vue中的computed里的每个计算属性都变成了在前缀添加get的函数.

    @Emit
    关于Vue中的事件的监听与触发,Vue提供了两个函数emit和on.那么在vue-property-decorator中如何使用呢?
    这就需要用到vue-property-decorator提供的@Emit属性.

    <script lang="ts">
    import {Vue, Component, Emit} from 'vue-property-decorator';

    @Component({})
    export default class "组件名" extends Vue{
        mounted(){
            this.$on('emit-todo', function(n) {
                console.log(n)
            })
    
            this.emitTodo('world');
        }
    
            @Emit()
        emitTodo(n: string){
            console.log('hello');
        }
    }
    

    </script>
    运行上面的代码会打印 'hello' 'world', 为什么呢? 让我们来看看它等同于什么

    <script lang="es6">
    import Vue from 'vue';

    export default {
        mounted(){
            this.$on('emit-todo', function(n) {
                console.log(n)
            })
    
            this.emitTodo('world');
        },
        methods: {
            emitTodo(n){
                console.log('hello');
                this.$emit('emit-todo', n);
            }
        }
    }
    

    </script>
    可以看到,在@Emit装饰器的函数会在运行之后触发等同于其函数名(驼峰式会转为横杠式写法)的事件, 并将其函数传递给$emit.
    如果我们想触发特定的事件呢,比如在emitTodo下触发reset事件:

    <script lang="ts">
    import {Vue, Component, Emit} from 'vue-property-decorator';

    @Component({})
    export default class "组件名" extends Vue{
    
        @Emit('reset')
        emitTodo(n: string){
    
        }
    }
    

    </script>
    我们只需要给装饰器@Emit传递一个事件名参数reset,这样函数emitTodo运行之后就会触发reset事件.

    总结:在Vue中我们是使用$emit触发事件,使用vue-property-decorator时,可以借助@Emit装饰器来实现.@Emit修饰的函数所接受的参数会在运行之后触发事件的时候传递过去.
    @Emit触发事件有两种写法
    @Emit()不传参数,那么它触发的事件名就是它所修饰的函数名.
    @Emit(name: string),里面传递一个字符串,该字符串为要触发的事件名.
    @Watch
    我们可以利用vue-property-decorator提供的@Watch装饰器来替换Vue中的watch属性,以此来监听值的变化.

    在Vue中监听器的使用如下:

    export default{
    watch: {
    'child': this.onChangeValue
    // 这种写法默认 immediatedeepfalse
    ,
    'person': {
    handler: 'onChangeValue',
    immediate: true,
    deep: true
    }
    },
    methods: {
    onChangeValue(newVal, oldVal){
    // todo...
    }
    }
    }
    那么我们如何使用@Watch装饰器来改造它呢?

    import {Vue, Component, Watch} from 'vue-property-decorator';

    @Watch('child')
    onChangeValue(newVal: string, oldVal: string){
    // todo...
    }

    @Watch('person', {immediate: true, deep: true})
    onChangeValue(newVal: Person, oldVal: Person){
    // todo...
    }
    总结: @Watch使用非常简单,接受第一个参数为要监听的属性名 第二个属性为可选对象.@Watch所装饰的函数即监听到属性变化之后的操作.
    @Prop
    我们在使用Vue时有时会遇到子组件接收父组件传递来的参数.我们需要定义Prop属性.

    比如子组件从父组件接收三个属性propA,propB,propC.

    propA类型为Number
    propB默认值为default value
    propC类型为String或者Boolean
    export default {
    props: {
    propA: {
    type: Number
    },
    propB: {
    default: 'default value'
    },
    propC: {
    type: [String, Boolean]
    },
    }
    }
    我们使用vue-property-decorator提供的@Prop可以将上面的代码改造为如下:

    <script lang="ts">
    import {Vue, Component, Prop} from 'vue-property-decorator';

    @Component({})
    export default class "组件名" extends Vue{
        @Prop(Number) propA!: number;
        @Prop({default: 'default value'}) propB!: string;
        @propC([String, Boolean]) propC: string | boolean;
    }
    

    </script>
    这里 !和可选参数?是相反的, !告诉TypeScript我这里一定有值.

    总结: @Prop接受一个参数可以是类型变量或者对象或者数组.@Prop接受的类型比如Number是JavaScript的类型,之后定义的属性类型则是TypeScript的类型.
    Mixins
    在使用Vue进行开发时我们经常要用到混合,结合TypeScript之后我们有两种mixins的方法.

    一种是vue-class-component提供的.

    //定义要混合的类 mixins.ts
    import Vue from 'vue';
    import Component from 'vue-class-component';

    @Component // 一定要用Component修饰
    export default class myMixins extends Vue {
    value: string = "Hello"
    }
    // 引入
    import Component {mixins} from 'vue-class-component';
    import myMixins from 'mixins.ts';

    @Component
    export class myComponent extends mixins(myMixins) {
    // 直接extends myMinxins 也可以正常运行
    created(){
    console.log(this.value) // => Hello
    }
    }
    第二种方式是在@Component中混入.

    我们改造一下mixins.ts,定义vue/type/vue模块,实现Vue接口

    // mixins.ts
    import { Vue, Component } from 'vue-property-decorator';

    declare module 'vue/types/vue' {
    interface Vue {
    value: string;
    }
    }

    @Component
    export default class myMixins extends Vue {
    value: string = 'Hello'
    }
    混入

    import { Vue, Component, Prop } from 'vue-property-decorator';
    import myMixins from '@static/js/mixins';

    @Component({
    mixins: [myMixins]
    })
    export default class myComponent extends Vue{
    created(){
    console.log(this.value) // => Hello
    }
    }
    总结: 两种方式不同的是在定义mixins时如果没有定义vue/type/vue模块, 那么在混入的时候就要继承该mixins; 如果定义vue/type/vue模块,在混入时可以在@Component中mixins直接混入.
    @Model
    Vue组件提供model: {prop?: string, event?: string}让我们可以定制prop和event.
    默认情况下,一个组件上的v-model 会把 value用作 prop且把 input用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop来达到不同的目的。使用model选项可以回避这些情况产生的冲突。

    下面是Vue官网的例子
    Vue.component('my-checkbox', {
    model: {
    prop: 'checked',
    event: 'change'
    },
    props: {
    // this allows using the value prop for a different purpose
    value: String,
    // use checked as the prop which take the place of value
    checked: {
    type: Number,
    default: 0
    }
    },
    // ...
    })
    <my-checkbox v-model="foo" value="some value"></my-checkbox>
    上述代码相当于:

    <my-checkbox
    :checked="foo"
    @change="val => { foo = val }"
    value="some value">
    </my-checkbox>
    即foo双向绑定的是组件的checke, 触发双向绑定数值的事件是change

    使用vue-property-decorator提供的@Model改造上面的例子.

    import { Vue, Component, Model} from 'vue-property-decorator';

    @Component
    export class myCheck extends Vue{
    @Model ('change', {type: Boolean}) checked!: boolean;
    }
    总结, @Model()接收两个参数, 第一个是event值, 第二个是prop的类型说明, 与@Prop类似, 这里的类型要用JS的. 后面在接着是prop和在TS下的类型说明.

    链接:https://www.jianshu.com/p/d8ed3aa76e9b

    相关文章

      网友评论

          本文标题:vue + typeScript

          本文链接:https://www.haomeiwen.com/subject/xrytaltx.html