类组件
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'
}