zoukankan      html  css  js  c++  java
  • 框架-Vue Class Component 官方支持(vue 2.*、Vue Class Component、vue-property-decorator 9.0.2、vuex-class 0.3.2)

    疑问

    • 注释:装饰器传入的组件选项不具有类型定义无法在 class 中通过 this. 获得完成提醒 ?
    • 因为 Vue 的声明文件天生就具有循环性,TypeScript 可能在推断某个方法的类型的时候存在困难。因此,你可能需要在 render 或 computed 里的方法上标注返回值。
      疑问:Class Components 中并不是真正的继承于 Vue,而是通过装饰器调用了 vue.extends 来生成一个构造函数。在这个过程中定义在类上的方法实际为处理前的实例的方法,所以 methods 使用箭头函数的话 this 会指向处理前的实例。

    总结

    vue 及相关库提供的类型和对象

    // 以下为构造函数和相关类型
    import Vue, { VNodeData, VNode } from 'vue'
    import VueRouter, { RouteConfig,Route, RawLocation } from "vue-router";
    import { mapGetters, mapActions } from 'vuex'
    
    // vuex 对应装饰器
    import { State, Getter, Action, Mutation, namespace } from 'vuex-class'
    
    // Component 组件装饰器
    // createDecorator 用于创建装饰器
    // PropSync 定义使用 async 实现双向绑定的 prop 
    // Model 改变 v-model 绑定值的方法
    // Provide 普通 inject/provide 的组件写法
    // ProvideReactive 可响应 inject/provide 的组件写法
    import Component, { createDecorator, mixins } from 'vue-class-component'
    import { Component, Vue, Prop, PropSync, Model, Watch, Emit, Ref, Provide,ProvideReactive } from "vue-property-decorator";
    

    @Component([options])

    • {Object} [options]
    • 使用 @Component 装饰器为类添加注释,从而以直观和标准的类语法定义组件数据和方法。
    • options 可以传递任何 Vue 组件选项
    • 当调用原始构造函数以收集初始组件数据时,建议不要 constructor 自己声明。由于Vue类组件的工作方式,constructor 会被调用两次
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class Counter extends Vue {
      // data 类属性等价于组件 data
      count = 0
      // 如果初始值为undefined,则 class 属性代表的 data 将不是响应的,这意味着将不会检测到对属性的更改
      // 为了避免这种情况,您可以使用 null 或使用 data hook 来代替
      data() {
        return {
          // `hello` will be reactive as it is declared via `data` hook.
          hello: undefined
        }
      }
    
      // 类方法等价于组件 methods
      increment() {
        this.count++
      }
    
      // 可以将计算属性声明为类属性 getter / setter
      get name() {
        return this.firstName + ' ' + this.lastName
      }
      set name(value) {
        const splitted = value.split(' ')
        this.firstName = splitted[0]
        this.lastName = splitted[1] || ''
      }
    
      // data,render 所有 Vue生命周期可以直接声明为类方法,但是您不能通过实例调用它们。
      // 注释:.tsx 文件需要 import 'vue-class-component/hooks' 这个空文件来引入对应 ts 声明才能够实现 vue 选项的输入提醒
      mounted() {
        console.log('mounted')
      }
      render() {
        return <div>Hello World!</div>
      }
    }
    
    • 除了上面的选项,对于所有其他选项,请将它们传递给装饰器函数
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component({
      template: '<button @click="onClick">Click!</button>',
    })
    export default class MyComponent extends Vue {}
    

    @Prop(options: (PropOptions | Constructor[] | Constructor) = {}) decorator

    import { Vue, Component, Prop } from 'vue-property-decorator'
    
    @Component
    export default class YourComponent extends Vue {
      @Prop(Number) readonly propA: number | undefined
      @Prop({ default: 'default value' }) readonly propB!: string
      @Prop([String, Boolean]) readonly propC: string | boolean | undefined
      // 引入 reflect-metadata 库,把 ts 类自动设为 prop 的 type
      @Prop() age!: number
    }
    

    @PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {}) decorator

    • 可以视为在传入 prop 时使用了 .sync
    import { Vue, Component, PropSync } from 'vue-property-decorator'
    @Component
    export default class YourComponent extends Vue {
      @PropSync('name', { type: String }) syncedName!: string
    }
    // 等价于
    export default {
      props: {
        name: {
          type: String
        }
      },
      computed: {
        syncedName: {
          get() {
            return this.name
          },
          set(value) {
            this.$emit('update:name', value)
          }
        }
      }
    }
    

    @Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {}) decorator

    • 注释:参数1可为 undefined 但是不会在父组件上绑定任何事件,要恢复默认值依然需要输入 'input'
    • 注释:参数2为可选,类型说明没有表现出来
    • @Model 定义的 prop 也能够通过 reflect-metadata 自动生成 type,即上面的例子可以不写 { type: Boolean }
    import { Vue, Component, Model } from 'vue-property-decorator'
    @Component
    export default class YourComponent extends Vue {
      @Model('change', { type: Boolean }) readonly checked!: boolean
    }
    // 等价于
    export default {
      model: {
        prop: 'checked', // 默认为 value
        event: 'change' // 默认为 input
      },
      props: {
        checked: {
          type: Boolean
        }
      }
    }
    

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

    • 注释:把一个函数装饰为对当前实例下某个属性的 watch
    import { Vue, Component, Watch } from 'vue-property-decorator'
    @Component
    export default class YourComponent extends Vue {
      @Watch('child')
      onChildChanged(val: string, oldVal: string) {}
    
      @Watch('person', { immediate: true, deep: true })
      onPersonChanged1(val: Person, oldVal: Person) {}
    
      @Watch('person')
      onPersonChanged2(val: Person, oldVal: Person) {}
    }
    // 等价于
    export default {
      watch: {
        child: [
          {
            handler: 'onChildChanged',
            immediate: false,
            deep: false
          }
        ],
        person: [
          {
            handler: 'onPersonChanged1',
            immediate: true,
            deep: true
          },
          {
            handler: 'onPersonChanged2',
            immediate: false,
            deep: false
          }
        ]
      },
      methods: {
        onChildChanged(val, oldVal) {},
        onPersonChanged1(val, oldVal) {},
        onPersonChanged2(val, oldVal) {}
      }
    }
    

    @Emit(event?: string) decorator

    • 注释:用来装饰一个函数,在函数的最后 this.$emit 抛出返回值
    import { Vue, Component, Emit } from 'vue-property-decorator'
    
    @Component
    export default class YourComponent extends Vue {
      count = 0
      // 使用函数名为事件名称,没有返回值,默认 emit 函数参数
      @Emit()
      addToCount(n: number) {
        this.count += n
      }
    
      // 自定义事件名称
      @Emit('reset')
      resetCount() {
        this.count = 0
      }
    
      // 指定 emit 返回值
      @Emit()
      returnValue() {
        return 10
      }
    
      // 函数接收原生事件对象时会作为 emit 的第 3 个参数,见下面的对比例子
      @Emit()
      onInputChange(e) {
        return e.target.value
      }
    
      // 返回是一个 promise 时
      @Emit()
      promise() {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve(20)
          }, 0)
        })
      }
    }
    // 等价于
    export default {
      data() {
        return {
          count: 0
        }
      },
      methods: {
        addToCount(n) {
          this.count += n
          this.$emit('add-to-count', n)
        },
        resetCount() {
          this.count = 0
          this.$emit('reset')
        },
        returnValue() {
          this.$emit('return-value', 10)
        },
        onInputChange(e) {
          this.$emit('on-input-change', e.target.value, e)
        },
        promise() {
          const promise = new Promise(resolve => {
            setTimeout(() => {
              resolve(20)
            }, 0)
          })
    
          promise.then(value => {
            this.$emit('promise', value)
          })
        }
      }
    }
    

    @Ref(refKey?: string) decorator

    • 注释:把当前组件中 ref 指向的实例映射到 computed 中,并设置该 computed 不缓存结果(ref 不是响应的)
    import { Vue, Component, Ref } from 'vue-property-decorator'
    
    import AnotherComponent from '@/path/to/another-component.vue'
    
    @Component
    export default class YourComponent extends Vue {
      @Ref() readonly anotherComponent!: AnotherComponent
      @Ref('aButton') readonly button!: HTMLButtonElement
    }
    // 等价于
    export default {
      computed() {
        anotherComponent: {
          cache: false,
          get() {
            return this.$refs.anotherComponent as AnotherComponent
          }
        },
        button: {
          cache: false,
          get() {
            return this.$refs.aButton as HTMLButtonElement
          }
        }
      }
    }
    

    @Provide(key?: string | symbol) / @Inject(options?: { from?: InjectKey, default?: any } | InjectKey) decorator

    import { Component, Inject, Provide, Vue } from 'vue-property-decorator'
    const symbol = Symbol('baz')
    @Component
    export class MyComponent extends Vue {
      @Inject() readonly foo!: string
      @Inject('bar') readonly bar!: string
      @Inject({ from: 'optional', default: 'default' }) readonly optional!: string
      @Inject(symbol) readonly baz!: string
    
      @Provide() foo = 'foo'
      @Provide('bar') baz = 'bar'
    }
    // 等价于
    const symbol = Symbol('baz')
    export const MyComponent = Vue.extend({
      inject: { // 接收父、祖组件的 provide
        foo: 'foo',
        bar: 'bar',
        optional: { from: 'optional', default: 'default' },
        [symbol]: symbol
      },
      data() {
        return {
          foo: 'foo',
          baz: 'bar'
        }
      },
      provide() { // 当前组件传入子孙组件的值
        return {
          foo: this.foo,
          bar: this.baz
        }
      }
    })
    

    @ProvideReactive(key?: string | symbol) / @InjectReactive(options?: { from?: InjectKey, default?: any } | InjectKey) decorator

    • 装饰 Provide 和 Inject,并使它们具有响应
    const key = Symbol()
    @Component
    class ParentComponent extends Vue {
      @ProvideReactive() one = 'value'
      @ProvideReactive(key) two = 'value'
    }
    
    @Component
    class ChildComponent extends Vue {
      @InjectReactive() one!: string
      @InjectReactive(key) two!: string
    }
    

    mixins

    • 可以接受任意数量的参数
    import Vue from 'vue'
    import Component, { mixins } from 'vue-class-component'
    @Component
    class Hello extends Vue {
      hello = 'Hello'
    }
    @Component
    class World extends Vue {
      world = 'World'
    }
    
    @Component
    export class HelloWorld extends mixins(Hello, World) {
      created () {
        console.log(this.hello + ' ' + this.world + '!') // -> Hello World!
      }
    }
    

    vuex

    import Vue from 'vue'
    import Component from 'vue-class-component'
    import {
      State,
      Getter,
      Action,
      Mutation,
      namespace
    } from 'vuex-class'
    
    const someModule = namespace('path/to/module')
    
    @Component
    export class MyComp extends Vue {
      @State('foo') stateFoo
      @State(state => state.bar) stateBar
      @Getter('foo') getterFoo
      @Action('foo') actionFoo
      @Mutation('foo') mutationFoo
      @someModule.Getter('foo') moduleGetterFoo
    
      // If the argument is omitted, use the property name 如果省略了参数,则使用属性名
      // for each state/getter/action/mutation type
      @State foo
      @Getter bar
      @Action baz
      @Mutation qux
    
      created () {
        this.stateFoo // -> store.state.foo
        this.stateBar // -> store.state.bar
        this.getterFoo // -> store.getters.foo
        this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
        this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
        this.moduleGetterFoo // -> store.getters['path/to/module/foo']
      }
    }
    

    补充现有类型

    // 确保在声明补充的类型之前导入 'vue'
    import Vue from 'vue'
    import { Route, RawLocation } from 'vue-router'
    
    declare module 'vue/types/vue' {
      // 给 vue 实例补充属性声明,例如:vue-router 的 $router、$route 等属性
      // 注释:在 class 中添加该属性的类型提醒
      // 注释:可以配合 Component.registerHooks 添加路由守卫的挂钩和类型声明
      interface Vue {
        $myProperty: string
        beforeRouteEnter?(
          to: Route,
          from: Route,
          next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
        ): void
      }
      // 给 vue 构造函数添加属性
      interface VueConstructor {
        $myGlobal: string
      }
    }
    
    // 额外的组件选项
    // 注释:这个组件选项是指装饰器中组件选项,并不会出现在 class 上的提醒,而且只对 .tsx 文件有效,对 .vue 文件无效
    declare module 'vue/types/options' {
      interface ComponentOptions<V extends Vue> {
        myOption?: string
      }
    }
    var vm = new Vue({
      myOption: 'Hello'
    })
    

    Component.registerHooks(hooks)

    • {Array} hooks
    • 注册方法名称,类组件将这些名称的方法作为挂钩处理。
    • 建议将此注册代码写在单独的文件中,因为您必须在任何组件定义之前注册它们。
    • 注释:webpack 打包时会依序加载并运行所有模块,然后才运行当前模块下的代码
    import Component from 'vue-class-component'
    
    Component.registerHooks([
      'beforeRouteEnter',
      'beforeRouteLeave',
      'beforeRouteUpdate'
    ])
    
    @Component
    export default class HelloWorld extends Vue {
      beforeRouteEnter(to, from, next) {...}
    }
    
    • 以下是内置的钩子名称,类组件将它们视为特殊方法,不会被注册为组件的 methods
      • data
      • beforeCreate
      • created
      • beforeMount
      • mounted
      • beforeDestroy
      • destroyed
      • beforeUpdate
      • updated
      • activated
      • deactivated
      • render
      • errorCaptured
      • serverPrefetch
    • Only available in TypeScript. It enables built-in hooks methods auto-complete once your import it 一个空的引入文件,为了实现内置钩子的 TS 提醒
    • 注释:脚手架构建的项目如果安装 vue-tsx-support 库的话,会改变 vue-class-component 库的位置,可以引用 vue-property-decorator 下的库引入
    import 'vue-class-component/hooks'
    
    import "vue-property-decorator/node_modules/vue-class-component/hooks";
    

    createDecorator(callback)

    • {Function} callback
    • 返回 {Function}
    • 创建一个装饰器。
    • createDecorator 期望将回调函数作为第一个参数,并且该回调函数将接收以下参数:
      • options:Vue 组件选项对象。对此对象所做的更改将影响所提供的组件。
      • key:这个装饰器需要处理的类上的某个属性的名称,这个装饰器用在那个属性上就传入哪个属性的名称
      • parameterIndex: The index of a decorated argument if the custom decorator is used for an argument. ?
    • 注释:装饰器内的 this 指向组件实例
    import Vue from 'vue'
    import Component,{ createDecorator } from 'vue-class-component'
    
    const Log = createDecorator((options, key) => {
      const originalMethod = options.methods[key]
      options.methods[key] = function wrapperMethod(...args) {
        console.log(`Invoked: ${key}(`, ...args, ')')
        originalMethod.apply(this, args)
      }
    })
    
    @Component
    class MyComp extends Vue {
      @Log
      hello(value) {
        // ...
      }
    }
    

    以下为原文——————————————————————————————————————————————————————————————————————————————

    https://cn.vuejs.org/v2/guide/typescript.html

    发布为 NPM 包的官方声明文件

    推荐配置

    • 疑问:TS 的相关配置,还看不懂
    • 使用 --noImplicitAny 选项将会帮助你找到这些未标注的方法。
    // tsconfig.json
    {
      "compilerOptions": {
        // 与 Vue 的浏览器支持保持一致
        "target": "es5",
        // 这可以对 `this` 上的数据属性进行更严格的推断
        "strict": true,
        // 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:
        "module": "es2015",
        "moduleResolution": "node"
      }
    }
    
    • 注意你需要引入 strict: true (或者至少 noImplicitThis: true,这是 strict 模式的一部分) 以利用组件方法中 this 的类型检查,否则它会始终被看作 any 类型。

    开发工具链

    工程创建

    编辑器支持

    基本用法

    • 要让 TypeScript 正确推断 Vue 组件选项中的类型,您需要使用 Vue.component 或 Vue.extend 定义组件:
    • 注释:vue 对 TS 的支持并不完美,默认只支持在组件内进行推断,并且无法识别 this.$refs. 的正确类型
    import Vue from 'vue'
    const Component = Vue.extend({
      // 类型推断已启用
    })
    
    const Component = {
      // 这里不会有类型推断,
      // 因为 TypeScript 不能确认这是 Vue 组件的选项
    }
    

    基于类的 Vue 组件

    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    // @Component 修饰符注明了此类为一个 Vue 组件
    @Component({
      // 所有的组件选项都可以放在这里
      template: '<button @click="onClick">Click!</button>'
    })
    export default class MyComponent extends Vue {
      // 初始数据可以直接声明为实例的属性
      message: string = 'Hello!'
    
      // 组件方法也可以直接声明为实例的方法
      onClick (): void {
        window.alert(this.message)
      }
    }
    

    增强类型以配合插件使用

    • 插件可以增加 Vue 的全局/实例属性和组件选项。在这些情况下,在 TypeScript 中制作插件需要类型声明。庆幸的是,TypeScript 有一个特性来补充现有的类型,叫做模块补充 (module augmentation)。
    • 注释:当给 vue 写插件或着引入插件时,需要在补充 vue 的类型声明时,可以使用 TS 的模块补充特性
    • 注释:给 vue 实例的原型上添加属性
    // 1. 确保在声明补充的类型之前导入 'vue'
    import Vue from 'vue'
    
    // 2. 定制一个文件,设置你想要补充的类型
    //    在 types/vue.d.ts 里 Vue 有构造函数类型
    declare module 'vue/types/vue' {
    // 3. 声明为 Vue 补充的东西
      interface Vue {
        $myProperty: string
      }
    }
    
    var vm = new Vue()
    console.log(vm.$myProperty) // 将会顺利编译通过
    
    • 在你的项目中包含了上述作为声明文件的代码之后 (像 my-property.d.ts) 也可以声明额外的属性和组件选项
    • 疑问: .d.ts 的运行方式?
    • 注释:给 vue 构造函数添加属性
    import Vue from 'vue'
    
    declare module 'vue/types/vue' {
      // 可以使用 `VueConstructor` 接口
      // 来声明全局属性,和上面
      interface VueConstructor {
        $myGlobal: string
      }
    }
    // 全局属性
    console.log(Vue.$myGlobal)
    
    // ComponentOptions 声明于 types/options.d.ts 之中
    declare module 'vue/types/options' {
      interface ComponentOptions<V extends Vue> {
        myOption?: string
      }
    }
    // 额外的组件选项
    var vm = new Vue({
      myOption: 'Hello'
    })
    

    标注返回值

    • 因为 Vue 的声明文件天生就具有循环性,TypeScript 可能在推断某个方法的类型的时候存在困难。因此,你可能需要在 render 或 computed 里的方法上标注返回值。
    • 疑问:因为 vue 嵌套的缘故吗?
    import Vue, { VNode } from 'vue'
    
    const Component = Vue.extend({
      data () {
        return {
          msg: 'Hello'
        }
      },
      methods: {
        // 需要标注有 `this` 参与运算的返回值类型
        greet (): string {
          return this.msg + ' world'
        }
      },
      computed: {
        // 需要标注
        greeting(): string {
          return this.greet() + '!'
        }
      },
      // `createElement` 是可推导的,但是 `render` 需要返回值类型
      render (createElement): VNode {
        return createElement('div', this.greeting)
      }
    })
    
    • 如果你发现类型推导或成员补齐不工作了,标注某个方法也许可以帮助你解决这个问题。使用 --noImplicitAny 选项将会帮助你找到这些未标注的方法。

    ——————————————————————————————————————————————————————————————————————————————————

    vue-class-component

    总览

    • 使用@Component装饰器为类添加注释,从而以直观和标准的类语法定义组件数据和方法。
    <template>
      <div>
        <button v-on:click="decrement">-</button>
        {{ count }}
        <button v-on:click="increment">+</button>
      </div>
    </template>
    
    <script>
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    // Define the component in class-style
    @Component
    export default class Counter extends Vue {
      // Class properties will be component data 类属性等价于组件 data
      count = 0
    
      // Methods will be component methods 类方法等价于组件 methods
      increment() {
        this.count++
      }
    
      decrement() {
        this.count--
      }
    }
    </script>
    
    • 通过以类样式定义组件,不仅可以更改语法,还可以利用某些ECMAScript语言功能,例如类继承和装饰器。
    • 疑问:编译之后这个模块输出的是一个构造函数吗?
    • Vue类组件还提供了一个用于mixin继承的mixins助手,以及一个轻松创建自己的装饰器的createDecorator函数。
    • 疑问:mixin 和继承有什么运用上的区别,它们分别在什么时候被使用更合适
    • 疑问:Vue Property Decorator提供的@Prop和@Watch装饰器。 https://github.com/kaorun343/vue-property-decorator

    安装

    Vue的CLI设置

    手动设置

    NPM

    • 注释:这个库提供了一个装饰器
    npm install --save vue vue-class-component
    

    构建设置

    • 要使用Vue类组件,您需要在项目中配置TypeScript或Babel,因为它依赖于ECMAScript阶段1装饰器
    • 它不支持阶段2装饰器,因为TypeScript Transpiler仍然仅支持旧的装饰器规范。
    • 注释:@vue/cli 创建的项目是通过 TS 转义装饰器的

    TypeScript

    • tsconfig.json在您的项目根目录上创建并指定experimentalDecorators选项,以便其转译装饰器语法:
    {
      "compilerOptions": {
        "target": "es5",
        "module": "es2015",
        "moduleResolution": "node",
        "strict": true,
        "experimentalDecorators": true
      }
    }
    

    Babel

    • 安装 @babel/plugin-proposal-decorators 和 @babel/plugin-proposal-class-properties
    npm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
    
    • 然后在项目根目录 .babelrc 上配置
    • 由于Vue类组件仅支持阶段1(旧版)装饰器规范,因此需要legacy和loose选项。
    {
      "plugins": [
        ["@babel/proposal-decorators", { "legacy": true }],
        ["@babel/proposal-class-properties", { "loose": true }]
      ]
    }
    

    CDN

    不同的版本

    • Vue类组件针对不同环境和用途提供不同构建。它可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行。它没有自己专有的规范,是集结了 CommonJs、CMD、AMD 的规范于一身
    • 注释:UMD 通用模块定义规范(Universal Module Definition)
    • 开发
      • vue-class-component.js (UMD)
      • vue-class-component.common.js (CommonJS)
      • vue-class-component.esm.js (用于捆绑器的ES模块)
      • vue-class-component.esm.browser.js (用于浏览器的ES模块)
    • 生产
      • vue-class-component.min.js (UMD)
      • vue-class-component.esm.browser.min.js (用于浏览器的ES模块)

    类组件

    data

    • 如果初始值为undefined,则 class 属性代表的 data 将不是响应的,这意味着将不会检测到对属性的更改:
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class HelloWorld extends Vue {
      // `message` will not be reactive value
      message = undefined
    }
    
    • 为了避免这种情况,您可以使用 null 或使用 data hook 来代替
    • 疑问:同时存在类属性和 data hook 是否合法有效?
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class HelloWorld extends Vue {
      // `message` will be reactive with `null` value
      message = null
    
      // See Hooks section for details about `data` hook inside class.
      data() {
        return {
          // `hello` will be reactive as it is declared via `data` hook.
          hello: undefined
        }
      }
    }
    

    methods

    computed

    • 可以将计算属性声明为类属性 getter / setter:
    <template>
      <input v-model="name">
    </template>
    
    <script>
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class HelloWorld extends Vue {
      firstName = 'John'
      lastName = 'Doe'
    
      // Declared as computed property getter
      get name() {
        return this.firstName + ' ' + this.lastName
      }
    
      // Declared as computed property setter
      set name(value) {
        const splitted = value.split(' ')
        this.firstName = splitted[0]
        this.lastName = splitted[1] || ''
      }
    }
    </script>
    

    挂钩

    • data,render 所有 Vue生命周期可以直接声明为类方法,但是您不能通过实例调用它们。
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class HelloWorld extends Vue {
      // Declare mounted lifecycle hook
      mounted() {
        console.log('mounted')
      }
    
      // Declare render function
      render() {
        return <div>Hello World!</div>
      }
    }
    

    其他选项

    • 对于所有其他选项,请将它们传递给装饰器函数:
    <template>
      <OtherComponent />
    </template>
    
    <script>
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import OtherComponent from './OtherComponent.vue'
    
    @Component({
      // Specify `components` option.
      // See Vue.js docs for all available options:
      // https://vuejs.org/v2/api/#Options-Data
      components: {
        OtherComponent
      }
    })
    export default class HelloWorld extends Vue {}
    </script>
    

    额外的挂钩

    • 如果您使用Vue Router等Vue插件,则可能希望类组件解析它们提供的钩子。
    • 注释:beforeRouteEnter 等路由守卫是组件级守卫,需要先在装饰器中注册它们,要求装饰器进行相应的处理,处理方式应该跟data、render、生命周期一致
    // class-component-hooks.js
    import Component from 'vue-class-component'
    
    // Register the router hooks with their names
    Component.registerHooks([
      'beforeRouteEnter',
      'beforeRouteLeave',
      'beforeRouteUpdate'
    ])
    
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class HelloWorld extends Vue {
      // The class component now treats beforeRouteEnter,
      // beforeRouteUpdate and beforeRouteLeave as Vue Router hooks
      beforeRouteEnter(to, from, next) {
        console.log('beforeRouteEnter')
        next()
      }
    
      beforeRouteUpdate(to, from, next) {
        console.log('beforeRouteUpdate')
        next()
      }
    
      beforeRouteLeave(to, from, next) {
        console.log('beforeRouteLeave')
        next()
      }
    }
    
    • 建议将此注册代码写在单独的文件中,因为您必须在任何组件定义之前注册它们。
    • 注释:这个注册只是对装饰器的修改,并不需要依赖 vue 是否 use 了 vue-router
    // main.js
    
    // Make sure to register before importing any components
    import './class-component-hooks'
    
    import Vue from 'vue'
    import App from './App'
    
    new Vue({
      el: '#app',
      render: h => h(App)
    })
    

    自定义装饰器

    • createDecorator 期望将回调函数作为第一个参数,并且该回调函数将接收以下参数:
      • options:Vue 组件选项对象。对此对象所做的更改将影响所提供的组件。
      • key:这个装饰器需要处理的类上的某个属性的名称
      • parameterIndex: The index of a decorated argument if the custom decorator is used for an argument. ?
    • 疑问:一个有参数的装饰器是怎么自定义的?
    • 疑问:parameterIndex 是如果传递到装饰器中的?
    // decorators.js
    import { createDecorator } from 'vue-class-component'
    
    // Declare Log decorator.
    export const Log = createDecorator((options, key) => {
      // Keep the original method for later.
      const originalMethod = options.methods[key]
    
      // Wrap the method with the logging logic.
      options.methods[key] = function wrapperMethod(...args) {
        // Print a log.
        console.log(`Invoked: ${key}(`, ...args, ')')
    
        // Invoke the original method.
        originalMethod.apply(this, args)
      }
    })
    
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import { Log } from './decorators'
    @Component
    class MyComp extends Vue {
      // It prints a log when `hello` method is invoked. 这个装饰器装饰了 hello 这个方法,使它在被调用前先进行日志打印
      @Log
      hello(value) {
        // ...
      }
    }
    

    扩展和混合

    Extend

    • 每个被继承的类都必须是一个类组件。换句话说,它需要继承Vue构造函数作为祖先并由 @Component 装饰器进行装饰。
    // super.js
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    // Define a super class component
    @Component
    export default class Super extends Vue {
      superValue = 'Hello'
    }
    
    import Super from './super'
    import Component from 'vue-class-component'
    
    // Extending the Super class component
    @Component
    export default class HelloWorld extends Super {
      created() {
        console.log(this.superValue) // -> Hello
      }
    }
    

    mixins

    • 注释:JS 的原型链是单链,混入能够实现多个组件配置混入到一个组件内
    • Vue 类组件提供了 mixins 辅助功能,通过使用 mixins 帮助程序,TypeScript 可以推断混合类型并在组件类型上继承它们。
    • 注释:mixins 应该是把两个构造函数合并成了一个
    // mixins.js
    import Vue from 'vue'
    import Component from 'vue-class-component'
    // You can declare mixins as the same style as components.
    @Component
    export class Hello extends Vue {
      hello = 'Hello'
    }
    @Component
    export class World extends Vue {
      world = 'World'
    }
    
    import Component, { mixins } from 'vue-class-component'
    import { Hello, World } from './mixins'
    // Use `mixins` helper function instead of `Vue`.
    // `mixins` can receive any number of arguments.
    @Component
    export class HelloWorld extends mixins(Hello, World) {
      created () {
        console.log(this.hello + ' ' + this.world + '!') // -> Hello World!
      }
    }
    

    类组件的警告

    • Vue类组件通过实例化底层的原始构造函数,将类属性收集为 Vue 实例数据。尽管我们可以像本地类方式那样定义实例数据,但有时我们需要知道其工作方式。 ?
    • 疑问:通过 vue 创建一个实例,然后修改这个实例的原型指向继承的类的实例上?
    • 疑问:extends 的本质是 创建一个 vue 实例,作为 MyComp 的原型?
    • 疑问:以下问题可能都是由于 @Component 内部的逻辑造成的。

    this属性初始化器中的值

    • 如果将类属性定义为箭头函数并在其中访问 this,它将无法正常工作。这是因为在初始化类属性时,this 只是Vue实例的代理对象
    • 注释:在非 class 组件中,箭头函数 this 指向 undefined
    • 注释:vue 应该是获取组件选项对象,然后用该对象生成一个组件构造函数,然后再通过该构造函数创建对应的 vue 实例。
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class MyComp extends Vue {
      foo = 123
    
      // DO NOT do this
      bar = () => {
        // Does not update the expected property.
        // `this` value is not a Vue instance in fact.
        this.foo = 456
      }
    }
    
    • 注释:过去的理解有误,实例能够从原型上继承属性,但是不代表构造函数中定义的属性在原型上存在绑定。
    class New {
      new = "new";
      showThis = () => {
        return this;
      };
    }
    // 等价于
    function(){
      this.new = 'new';
      this.showThis = ()=>{
        return this
      }
    }
    

    始终使用生命周期挂钩代替constructor

    • 当调用原始构造函数以收集初始组件数据时,建议不要constructor自己声明:
    • 由于Vue类组件的工作方式,fetch 将被意外调用两次。
    • 建议写生命周期挂钩,例如created,而不是constructor
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class Posts extends Vue {
      posts = []
    
      // DO NOT do this
      constructor() {
        fetch('/posts.json')
          .then(res => res.json())
          .then(posts => {
            this.posts = posts
          })
      }
    }
    

    props 定义

    • Vue类组件没有提供用于 props 定义的专用 API。但是,您可以通过使用规范 Vue.extend 来做到这一点:
    • 注释:使用 class extend class 的方式,继承来的 props 不能被当前 this 正确识别
    <template>
      <div>{{ message }}</div>
    </template>
    
    <script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    // Define the props by using Vue's canonical way.
    const GreetingProps = Vue.extend({
      props: {
        name: String
      }
    })
    
    // Use defined props by extending GreetingProps.
    @Component
    export default class Greeting extends GreetingProps {
      get message(): string {
        // this.name will be typed
        return 'Hello, ' + this.name
      }
    }
    </script>
    
    • 注释:可能这个教程比较旧了,实际还可以通过 @Prop 这个装饰器来完成
    import { Component, Prop, Vue } from "vue-property-decorator";
    
    @Component
    export default class HelloWorld extends Vue {
      @Prop() private msg!: string;
      doSome = () => {
        console.log(this.msg);
      };
      showThis() {
        console.log(this.msg);
      }
    }
    

    属性类型声明

    • 有时,您必须在类组件之外定义组件属性和方法。
    • 例如,Vue的官方状态管理库 Vuex 提供 mapGetters 和 mapActions 帮助程序将商店映射到组件属性和方法。这些帮助程序需要在组件选项对象中使用。
    • 可以将组件选项传递给 @Component 装饰器的参数。但是,当它们在运行时运行时,不会自动在类型级别上声明属性和方法。您需要在类组件中手动声明其类型:
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import { mapGetters, mapActions } from 'vuex'
    
    // Interface of post
    import { Post } from './post'
    
    @Component({
      computed: mapGetters([
        'posts'
      ]),
    
      methods: mapActions([
        'fetchPosts'
      ])
    })
    export default class Posts extends Vue {
      // Declare mapped getters and actions on type level.
      // You may need to add `!` after the property name
      // to avoid compilation error (definite assignment assertion).
    
      // Type the mapped posts getter.
      posts!: Post[]
    
      // Type the mapped fetchPosts action.
      fetchPosts!: () => Promise<void>
    
      mounted() {
        // Use the mapped getter and action.
        this.fetchPosts().then(() => {
          console.log(this.posts)
        })
      }
    }
    

    $refs类型扩展

    • $refs 组件的类型声明为处理所有可能的ref类型的最广泛的类型。
    • 注释:$refs. 没有任何输入提示
    • 可以通过覆盖$refs类组件中的类型来指定特定的引用类型
    <template>
      <input ref="input">
    </template>
    
    <script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component'
    
    @Component
    export default class InputFocus extends Vue {
      // annotate refs type.
      // The symbol `!` (definite assignment assertion)
      // is needed to get rid of compilation error.
      $refs!: {
        input: HTMLInputElement
      }
    
      mounted() {
        // Use `input` ref without type cast.
        this.$refs.input.focus()
      }
    }
    </script>
    

    挂钩自动完成

    • Vue 的类组件提供了内置的钩子类型,这使得能够自动完成对 data,render 和其他生命周期的钩子类组件声明
    • 要启用它,您需要导入 vue-class-component/hooks
    • 注释:这个文件其实是一个空文件,用来引入对于这些钩子的ts声明。即引用这个库后,在类组件中写 render 等方法时将出现对应提示。并且只在 .tsx 文件中有效,在 .vue 文件中无效。
    • 注释:.vue 文件需要写在装饰器参数中才有相应的提醒
    // main.ts
    import 'vue-class-component/hooks' // import hooks type to enable auto-complete
    import Vue from 'vue'
    import App from './App.vue'
    
    new Vue({
      render: h => h(App)
    }).$mount('#app')
    
    • 可以自己手动添加自定义钩子
    import Vue from 'vue'
    import { Route, RawLocation } from 'vue-router'
    
    declare module 'vue/types/vue' {
      // Augment component instance type
      interface Vue {
        beforeRouteEnter?(
          to: Route,
          from: Route,
          next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
        ): void
    
        beforeRouteLeave?(
          to: Route,
          from: Route,
          next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
        ): void
    
        beforeRouteUpdate?(
          to: Route,
          from: Route,
          next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
        ): void
      }
    }
    

    ——————————————————————————————————————————————————————————————————————————————————

    vue-property-decorator

    安装

    • 注释:vue-property-decorator 包含了 vue-class-component
    npm i -S vue-property-decorator
    

    @Prop(options: (PropOptions | Constructor[] | Constructor) = {}) decorator

    import { Vue, Component, Prop } from 'vue-property-decorator'
    
    @Component
    export default class YourComponent extends Vue {
      @Prop(Number) readonly propA: number | undefined
      @Prop({ default: 'default value' }) readonly propB!: string
      @Prop([String, Boolean]) readonly propC: string | boolean | undefined
    }
    
    • If you'd like to set type property of each prop value from its type definition, you can use reflect-metadata. 疑问:如果喜欢在 ts 中设置 prop 的类型,可以使用 https://github.com/rbuckton/reflect-metadata
    • 疑问:让 TS 的类型自动写为 prop 的 type,便于在改 ts 编译后这个组件的参数依然能够被正确限制
    • Set emitDecoratorMetadata to true 设置 reflect-metadata 的参数 emitDecoratorMetadata 为 true
    • Import reflect-metadata before importing vue-property-decorator (importing reflect-metadata is needed just once.) 在 vue-property-decorator 前引入 reflect-metadata,全局只需要引入一次
    • Each prop's default value need to be defined as same as the example code shown in above. 如果需要编译后有默认值需要在 @Porps 的参数中定义 default。
    • It's not supported to define each default property like @Prop() prop = 'default value'. 这种写法并不被允许,它被视为在组件内改变了 props
    import 'reflect-metadata'
    import { Vue, Component, Prop } from 'vue-property-decorator'
    
    @Component
    export default class MyComponent extends Vue {
      @Prop() age!: number
    }
    

    @PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {}) decorator

    • This way you can interface with the property as it was a regular data property whilst making it as easy as appending the .sync modifier in the parent component. 可以视为在传入 prop 时使用了 .sync
    • 注释:这里的 computed 方法,也是开发双向绑定组件的优秀写法
    import { Vue, Component, PropSync } from 'vue-property-decorator'
    @Component
    export default class YourComponent extends Vue {
      @PropSync('name', { type: String }) syncedName!: string
    }
    // 等价于
    export default {
      props: {
        name: {
          type: String
        }
      },
      computed: {
        syncedName: {
          get() {
            return this.name
          },
          set(value) {
            this.$emit('update:name', value)
          }
        }
      }
    }
    

    @Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {}) decorator

    • 注释:参数1可为 undefined 但是不会在父组件上绑定任何事件,要恢复默认值依然需要输入 'input'
    • 注释:参数2为可选,类型说明没有表现出来
    import { Vue, Component, Model } from 'vue-property-decorator'
    @Component
    export default class YourComponent extends Vue {
      @Model('change', { type: Boolean }) readonly checked!: boolean
    }
    // 等价于
    export default {
      model: {
        prop: 'checked', // 默认为 value
        event: 'change' // 默认为 input
      },
      props: {
        checked: {
          type: Boolean
        }
      }
    }
    
    • @Model property can also set type property from its type definition via reflect-metadata . @Model 定义的 prop 也能够通过 reflect-metadata 自动生成 type,即上面的例子可以不写 { type: Boolean }

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

    • 注释:把一个函数装饰为对当前实例下某个属性的 watch
    import { Vue, Component, Watch } from 'vue-property-decorator'
    @Component
    export default class YourComponent extends Vue {
      @Watch('child')
      onChildChanged(val: string, oldVal: string) {}
    
      @Watch('person', { immediate: true, deep: true })
      onPersonChanged1(val: Person, oldVal: Person) {}
    
      @Watch('person')
      onPersonChanged2(val: Person, oldVal: Person) {}
    }
    // 等价于
    export default {
      watch: {
        child: [
          {
            handler: 'onChildChanged',
            immediate: false,
            deep: false
          }
        ],
        person: [
          {
            handler: 'onPersonChanged1',
            immediate: true,
            deep: true
          },
          {
            handler: 'onPersonChanged2',
            immediate: false,
            deep: false
          }
        ]
      },
      methods: {
        onChildChanged(val, oldVal) {},
        onPersonChanged1(val, oldVal) {},
        onPersonChanged2(val, oldVal) {}
      }
    }
    

    @Provide(key?: string | symbol) / @Inject(options?: { from?: InjectKey, default?: any } | InjectKey) decorator

    import { Component, Inject, Provide, Vue } from 'vue-property-decorator'
    const symbol = Symbol('baz')
    @Component
    export class MyComponent extends Vue {
      @Inject() readonly foo!: string
      @Inject('bar') readonly bar!: string
      @Inject({ from: 'optional', default: 'default' }) readonly optional!: string
      @Inject(symbol) readonly baz!: string
    
      @Provide() foo = 'foo'
      @Provide('bar') baz = 'bar'
    }
    // 等价于
    const symbol = Symbol('baz')
    export const MyComponent = Vue.extend({
      inject: { // 接收父、祖组件的 provide
        foo: 'foo',
        bar: 'bar',
        optional: { from: 'optional', default: 'default' },
        [symbol]: symbol
      },
      data() {
        return {
          foo: 'foo',
          baz: 'bar'
        }
      },
      provide() { // 当前组件传入子孙组件的值
        return {
          foo: this.foo,
          bar: this.baz
        }
      }
    })
    

    @ProvideReactive(key?: string | symbol) / @InjectReactive(options?: { from?: InjectKey, default?: any } | InjectKey) decorator

    • These decorators are reactive version of @Provide and @Inject. 装饰 Provide 和 Inject,并使它们具有响应
    const key = Symbol()
    @Component
    class ParentComponent extends Vue {
      @ProvideReactive() one = 'value'
      @ProvideReactive(key) two = 'value'
    }
    
    @Component
    class ChildComponent extends Vue {
      @InjectReactive() one!: string
      @InjectReactive(key) two!: string
    }
    

    @Emit(event?: string) decorator

    • 注释:用来装饰一个函数,在函数的最后 this.$emit 抛出返回值
    • If the return value is a promise, it is resolved before being emitted. 如果返回值是一个 promise 对象,则在 promise then 时 emit then 的结果回值
    • If the name of the event is not supplied via the event argument, the function name is used instead. In that case, the camelCase name will be converted to kebab-case. 如果事件名称没有通过参数名称,那么会使用函数名称转换为 - 链接后的名称
    import { Vue, Component, Emit } from 'vue-property-decorator'
    
    @Component
    export default class YourComponent extends Vue {
      count = 0
      // 使用函数名为事件名称,没有返回值,默认 emit 函数参数
      @Emit()
      addToCount(n: number) {
        this.count += n
      }
      // 自定义事件名称
      @Emit('reset')
      resetCount() {
        this.count = 0
      }
      // 指定 emit 返回值
      @Emit()
      returnValue() {
        return 10
      }
      // 函数接收原生事件对象时会作为 emit 的第 3 个参数
      @Emit()
      onInputChange(e) {
        return e.target.value
      }
      // 返回是一个 promise 时
      @Emit()
      promise() {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve(20)
          }, 0)
        })
      }
    }
    // 等价于
    export default {
      data() {
        return {
          count: 0
        }
      },
      methods: {
        addToCount(n) {
          this.count += n
          this.$emit('add-to-count', n)
        },
        resetCount() {
          this.count = 0
          this.$emit('reset')
        },
        returnValue() {
          this.$emit('return-value', 10)
        },
        onInputChange(e) {
          this.$emit('on-input-change', e.target.value, e)
        },
        promise() {
          const promise = new Promise(resolve => {
            setTimeout(() => {
              resolve(20)
            }, 0)
          })
    
          promise.then(value => {
            this.$emit('promise', value)
          })
        }
      }
    }
    

    @Ref(refKey?: string) decorator

    • 注释:把当前组件中 ref 指向的实例映射到 computed 中,并设置该 computed 不缓存结果(ref 不是响应的)
    import { Vue, Component, Ref } from 'vue-property-decorator'
    
    import AnotherComponent from '@/path/to/another-component.vue'
    
    @Component
    export default class YourComponent extends Vue {
      @Ref() readonly anotherComponent!: AnotherComponent
      @Ref('aButton') readonly button!: HTMLButtonElement
    }
    // 等价于
    export default {
      computed() {
        anotherComponent: {
          cache: false,
          get() {
            return this.$refs.anotherComponent as AnotherComponent
          }
        },
        button: {
          cache: false,
          get() {
            return this.$refs.aButton as HTMLButtonElement
          }
        }
      }
    }
    

    ——————————————————————————————————————————————————————————————————————————————————

    https://github.com/ktsn/vuex-class/

    • vuex 的使用说明

    Installation

    npm install --save vuex-class
    

    Example

    import Vue from 'vue'
    import Component from 'vue-class-component'
    import {
      State,
      Getter,
      Action,
      Mutation,
      namespace
    } from 'vuex-class'
    
    const someModule = namespace('path/to/module')
    
    @Component
    export class MyComp extends Vue {
      @State('foo') stateFoo
      @State(state => state.bar) stateBar
      @Getter('foo') getterFoo
      @Action('foo') actionFoo
      @Mutation('foo') mutationFoo
      @someModule.Getter('foo') moduleGetterFoo
    
      // If the argument is omitted, use the property name
      // for each state/getter/action/mutation type
      @State foo
      @Getter bar
      @Action baz
      @Mutation qux
    
      created () {
        this.stateFoo // -> store.state.foo
        this.stateBar // -> store.state.bar
        this.getterFoo // -> store.getters.foo
        this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
        this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
        this.moduleGetterFoo // -> store.getters['path/to/module/foo']
      }
    }
    

    ——————————————————————————————————————————————————————————————————————————————————

    https://github.com/rbuckton/reflect-metadata

    • 让 TS 的类型自动写为 prop 的 type,便于在改 ts 编译后这个组件的参数依然能够被正确限制
    • 和开发不太相关,暂时忽略
  • 相关阅读:
    svn git 共存
    如何写软件设计文档
    spring boot requestbody string to date
    asp.net core 1.1 publish to a linux
    asp.net core 1.1 entityframework mysql
    [FPGA]記錄一些不錯的網站推薦給大家參考。
    [FPGA][DE0] Qsys 加入 FLASH 記憶體 方法及步驟
    [FPGA][Nios][DP83848] 網路開發筆記-軟體篇(1)
    [Nios][UART] 使用UART 的一些問題?
    [Nios][Eclipse] find_fast_cwd: WARNING: Couldn't compute FAST_CWD pointer
  • 原文地址:https://www.cnblogs.com/qq3279338858/p/12631728.html
Copyright © 2011-2022 走看看