类组件

Vue 类组件

vue-class-component 一个支持使用 Class 方式来开发 Vue 单文件组件的库,其提供了 @Component 装饰器以及多种可扩展的能力。

组件声明

vue-class-component 的基础使用如下:

<template>
  <div>
    <input v-model="msg" />
    <p>prop: {{ propMessage }}</p>
    <p>msg: {{ msg }}</p>
    <p>helloMsg: {{ helloMsg }}</p>
    <p>computed msg: {{ computedMsg }}</p>
    <Hello ref="helloComponent" />
    <World />
    <p>
      <button @click="greet">Greet</button>
    </p>
    <p>
      Clicked: {{ count }} times
      <button @click="increment">+</button>
    </p>
  </div>
</template>

<script lang="ts">
  import Vue from "vue";
  import Component from "../../lib/index";
  import Hello from "./components/Hello.vue";
  import World from "./components/World";
  import { mapState, mapMutations } from "vuex";

  // We declare the props separately
  // to make props types inferable.
  const AppProps = Vue.extend({
    props: {
      propMessage: String,
    },
  });

  @Component({
    components: {
      Hello,
      World,
    },
    // Vuex's component binding helper can use here
    computed: mapState(["count"]),
    methods: mapMutations(["increment"]),
  })
  export default class App extends AppProps {
    // inital data
    msg: number = 123;
    // use prop values for initial data
    helloMsg: string = "Hello, " + this.propMessage;
    // annotate refs type
    $refs!: {
      helloComponent: Hello;
    };
    // additional declaration is needed
    // when you declare some properties in `Component` decorator
    count!: number;
    increment!: () => void;
    // lifecycle hook
    mounted() {
      this.greet();
    }
    // computed
    get computedMsg() {
      return "computed " + this.msg;
    }
    // method
    greet() {
      alert("greeting: " + this.msg);
      this.$refs.helloComponent.sayHello();
    }
    // direct dispatch example
    incrementIfOdd() {
      this.$store.dispatch("incrementIfOdd");
    }
  }
</script>

vue-class-component 还提供了 mixins 的辅助函数,TypeScript 能够自动地推导出 mixin 类型:

import Vue from "vue";
import Component from "vue-class-component";

// You can declare a mixin as the same style as components.
@Component
export default class MyMixin extends Vue {
  mixinValue = "Hello";
}

import Component, { mixins } from "vue-class-component";
import MyMixin from "./mixin.js";

// Use `mixins` helper function instead of `Vue`.
// `mixins` can receive any number of arguments.
@Component
export class MyComp extends mixins(MyMixin) {
  created() {
    console.log(this.mixinValue); // -> Hello
  }
}

自定义扩展

自定义装饰器

vue-class-component 还提供了非常便利的创建自定义装饰器的 createDecorator 函数,该函数允许传入某个回调,其包含了以下几个参数:

  • options: Vue 组件参数对象,改变该属性会影响到关联的组件。
  • key: 该装饰器作用到的属性或者方法名。
  • parameterIndex: 如果该装饰器作用于某个参数,那么返回的就是该参数的次序。
import { createDecorator } from "vue-class-component";

export const NoCache = createDecorator((options, key) => {
  // component options should be passed to the callback
  // and update for the options object affect the component
  options.computed[key].cache = false;
});
import { NoCache } from "./decorators";

@Component
class MyComp extends Vue {
  // the computed property will not be cached
  @NoCache
  get random() {
    return Math.random();
  }
}

自定义 Hooks

在我们使用 Vue Router 这样的 Vue 插件之后,vue-class-component 还提供了 Component.registerHooks 来注册自定义的 Hooks:

// class-component-hooks.js
import Component from "vue-class-component";

// Register the router hooks with their names
Component.registerHooks([
  "beforeRouteEnter",
  "beforeRouteLeave",
  "beforeRouteUpdate", // for vue-router 2.2+
]);

// MyComp.js
import Vue from "vue";
import Component from "vue-class-component";

@Component
class MyComp extends Vue {
  // The class component now treats beforeRouteEnter
  // and beforeRouteLeave as Vue Router hooks
  beforeRouteEnter(to, from, next) {
    console.log("beforeRouteEnter");
    next(); // needs to be called to confirm the navigation
  }

  beforeRouteLeave(to, from, next) {
    console.log("beforeRouteLeave");
    next(); // needs to be called to confirm the navigation
  }
}

值得注意的是,我们需要在组件定义前即完成注册:

// Make sure to register before importing any components
import "./class-component-hooks";

import Vue from "vue";
import MyComp from "./MyComp";

new Vue({
  el: "#app",
  components: {
    MyComp,
  },
});

vue-property-decorator

  • @Component
import { componentA, componentB } from "@/components";

export default {
  components: {
    componentA,
    componentB,
  },
  directives: {
    focus: {
      // 指令的定义
      inserted: function (el) {
        el.focus();
      },
    },
  },
};
import { Component, Vue } from "vue-property-decorator";
import { componentA, componentB } from "@/components";

@Component({
  components: {
    componentA,
    componentB,
  },
  directives: {
    focus: {
      // 指令的定义
      inserted: function (el) {
        el.focus();
      },
    },
  },
})
export default class YourCompoent extends Vue {}
  • @Prop 父子组件之间值的传递
export default {
  props: {
    propA: String, // propA:Number
    propB: [String, Number],
    propC: {
      type: Array,
      default: () => {
        return ["a", "b"];
      },
      required: true,
      validator: (value) => {
        return ["a", "b"].indexOf(value) !== -1;
      },
    },
  },
};
import { Component, Vue, Prop } from vue - property - decorator;

@Component
export default class YourComponent extends Vue {
  @Prop(String)
  propA: string;

  @Prop([String, Number])
  propB: string | number;

  @Prop({
    type: String, // type: [String, Number]
    default: "default value", // 一般为String或Number
    //如果是对象或数组的话。默认值从一个工厂函数中返回
    // defatult: () => {
    //     return ['a','b']
    // }
    required: true,
    validator: (value) => {
      return ["InProcess", "Settled"].indexOf(value) !== -1;
    },
  })
  propC: string;
}
  • @Model

父组件中使用 v-model="checked" 子组件:

<input type="checkbox" :checked="checked" @change="change" />
export default {
  model: {
    prop: "checked",
    event: "change",
  },
  props: {
    checked: {
      type: Boolean,
    },
  },
  methods: {
    change(e) {
      this.$emit("change", e.target.checked);
    },
  },
};
import { Vue, Component, Model, Emit } from "vue-property-decorator";

@Component
export default class YourComponent extends Vue {
  @Model("change", {
    type: Boolean,
  })
  checked!: boolean;

  @Emit("change")
  change(e: MouseEvent) {}
}
  • @Watch
export default {
  watch: {
    person: {
      handler: "onPersonChanged",
      immediate: true,
      deep: true,
    },
  },
  methods: {
    onPersonChanged(val, oldVal) {},
  },
};
import { Vue, Component, Watch } from "vue-property-decorator";

@Component
export default class YourComponent extends Vue {
  @Watch("person", { immediate: true, deep: true })
  onPersonChanged(val: Person, oldVal: Person) {}
}
  • @Emit

@Emit $emit 定义的函数发出它们的返回值,后跟它们的原始参数。如果返回值是 promise,则在发出之前将其解析。如果事件的名称未通过事件参数提供,则使用函数名称。在这种情况下,camelCase 名称将转换为 kebab-case。

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);
    },
    promise() {
      const promise = new Promise((resolve) => {
        setTimeout(() => {
          resolve(20);
        }, 0);
      });

      promise.then((value) => {
        this.$emit("promise", value);
      });
    },
  },
};
import { Vue, Component, Emit } from "vue-property-decorator";

@Component
export default class YourComponent extends Vue {
  count = 0;

  @Emit()
  addToCount(n: number) {
    this.count += n;
  }

  @Emit("reset")
  resetCount() {
    this.count = 0;
  }

  @Emit()
  returnValue() {
    return 10;
  }

  @Emit()
  promise() {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(20);
      }, 0);
    });
  }
}
  • @Provide 提供 / @Inject 注入

父组件不便于向子组件传递数据,就把数据通过 Provide 传递下去,然后子组件通过 Inject 来获取。

const symbol = Symbol("baz");

export const MyComponent = Vue.extend({
  inject: {
    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,
    };
  },
});
import {Vue,Component,Inject,Provide} from 'vue-property-decorator';

const symbol = Symbol('baz')

@Component
export defalut class MyComponent extends Vue{
    @Inject()
    foo!: string;

    @Inject('bar')
    bar!: string;

    @Inject({
        from:'optional',
        default:'default'
    })
    optional!: string;

    @Inject(symbol)
    baz!: string;

    @Provide()
    foo = 'foo'

    @Provide('bar')
    baz = 'bar'
}

实现浅析

Links

上一页