回顾下我们日常工作中常用的vue的东西
- data() 中定义数据
- 子组件通过props接受父组件传来的值
- 子组件通过$emit给父组件传递信息
- 通过watch来进行观察数据
- 通过computed计算属性
- vue组件的生命周期方法
- 路由以及vuex
这一篇章将根据vue-class-component以及vue-property-decorator来说明除了路由以及vuex的使用方法,通过通过vue写ts有两种方法,一种是和原有写法类似的还有一种是基于类的写法。以下所有的写法都是基于类的写法
vue-class-component解析
vue-class-component核心的代码为component.js
export function componentFactory(Component, options = {}) {
options.name = options.name || Component._componentTag || Component.name;
// 获取组件的原型
const proto = Component.prototype;
// 遍历原型
Object.getOwnPropertyNames(proto).forEach(function (key) {
if (key === 'constructor') {
return;
}
// 如果原型key是$internalHooks的话就将数据直接设置为options中,此时由于继承vue的component,上面的属性都转成了方法,可以直接使用
if ($internalHooks.indexOf(key) > -1) {
options[key] = proto[key];
return;
}
// 获取组件自身属性
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
// 如果存在value 属性则代码为非计算属性
if (descriptor.value !== void 0) {
// 如果类型为function则代表为方法,这里要注意一下watch以及emit的都是放着方法里面
if (typeof descriptor.value === 'function') {
(options.methods || (options.methods = {}))[key] = descriptor.value;
}
else {
// 如果声明是数据,判断传递的参数中是否包含mixins如果包含将数据放入到data中
(options.mixins || (options.mixins = [])).push({
data() {
return { [key]: descriptor.value };
}
});
}
}
else if (descriptor.get || descriptor.set) {
// 如果存在get和set,则为计算属性
(options.computed || (options.computed = {}))[key] = {
get: descriptor.get,
set: descriptor.set
};
}
});
// 如果options中包含混入,则调用data中的collectDataFromConstructor方法,将数据通过defineProperty来变成可观察的
(options.mixins || (options.mixins = [])).push({
data() {
return collectDataFromConstructor(this, Component);
}
});
// 获取方法继承的装饰器,用来处理上面改变的options
const decorators = Component.__decorators__;
if (decorators) {
decorators.forEach(fn => fn(options));
delete Component.__decorators__;
}
// 获取父级的原型
const superProto = Object.getPrototypeOf(Component.prototype);
// 判断原型是什么,如果是vue则直接取父级的构造函数,否则直接取vue
const Super = superProto instanceof Vue
? superProto.constructor
: Vue;
// 子类继承
const Extended = Super.extend(options);
forwardStaticMembers(Extended, Component, Super);
if (reflectionIsSupported()) {
copyReflectionMetadata(Extended, Component);
}
return Extended;
}
大家都知道在js的原型中,我们定义在类中的方法或者值都将是类原型链上的一员,所以基于此才有了上面的代码。我们其实可以想想现在官方出的两个装饰器,其实都是通过@xxx(装饰器)来表明当前定义的为什么数据,类似ng中的注入,在解析的时候通过装饰器将类的原型链上的数据类比到以往的vue的相对应的方法中即可,所以我们可以这样理解装饰器的作用将数据类比成原有vue数据,只是写法上变化了,穿了衣服的猫依旧是猫。例如
@Prop() propA:string
//会最终转换成
props:{
propA:''
}
@Watch('propA',{
deep:true
})
test(newValue:string,oldValue:string){
console.log('propA值改变了' + newValue);
}
//会被转化成
watch:{
propA:handler(newValue,oldValue){
this.test(newValue,oldValue);
},
deep:true
}
test方法会被转化到methods中
vue-property-decorator 核心原理已在上面说明了。现在就看这里面有哪些装饰器,查看vue-property-decorator.d.ts里面我们能够看到所具有的装饰器有
/**
* decorator of an inject
* @param from key
* @return PropertyDecorator
*/
export declare function Inject(options?: InjectOptions | InjectKey): import("vue-class-component").VueDecorator;
/**
* decorator of a reactive inject
* @param from key
* @return PropertyDecorator
*/
export declare function InjectReactive(options?: InjectOptions | InjectKey): import("vue-class-component").VueDecorator;
/**
* decorator of a provide
* @param key key
* @return PropertyDecorator | void
*/
export declare function Provide(key?: string | symbol): import("vue-class-component").VueDecorator;
/**
* decorator of a reactive provide
* @param key key
* @return PropertyDecorator | void
*/
export declare function ProvideReactive(key?: string | symbol): import("vue-class-component").VueDecorator;
/**
* decorator of model
* @param event event name
* @param options options
* @return PropertyDecorator
*/
export declare function Model(event?: string, options?: PropOptions | Constructor[] | Constructor): (target: Vue, key: string) => void;
/**
* decorator of a prop
* @param options the options for the prop
* @return PropertyDecorator | void
*/
export declare function Prop(options?: PropOptions | Constructor[] | Constructor): (target: Vue, key: string) => void;
/**
* decorator of a synced prop
* @param propName the name to interface with from outside, must be different from decorated property
* @param options the options for the synced prop
* @return PropertyDecorator | void
*/
export declare function PropSync(propName: string, options?: PropOptions | Constructor[] | Constructor): PropertyDecorator;
/**
* decorator of a watch function
* @param path the path or the expression to observe
* @param WatchOption
* @return MethodDecorator
*/
export declare function Watch(path: string, options?: WatchOptions): import("vue-class-component").VueDecorator;
/**
* decorator of an event-emitter function
* @param event The name of the event
* @return MethodDecorator
*/
export declare function Emit(event?: string): (_target: Vue, propertyKey: string, descriptor: any) => void;
/**
* decorator of a ref prop
* @param refKey the ref key defined in template
*/
export declare function Ref(refKey?: string): import("vue-class-component").VueDecorator;
至于我上面说的理论对或者不对我们可以看看vue装饰器的实现
export function Watch(path, options) {
if (options === void 0) { options = {}; }
var _a = options.deep, deep = _a === void 0 ? false : _a, _b = options.immediate, immediate = _b === void 0 ? false : _b;
return createDecorator(function (componentOptions, handler) {
if (typeof componentOptions.watch !== 'object') {
componentOptions.watch = Object.create(null);
}
var watch = componentOptions.watch;
if (typeof watch[path] === 'object' && !Array.isArray(watch[path])) {
watch[path] = [watch[path]];
}
else if (typeof watch[path] === 'undefined') {
watch[path] = [];
}
watch[path].push({ handler: handler, deep: deep, immediate: immediate });
});
}
下面来看具体在基于类组件中如何定义吧
data()中定义数据
前面说过了,装饰器class将类的原型中的数据直接挂在相对应的原有vue方法中,但是data除外,data只直接将非装饰器以及非方法的直接放在data()中 ,所以我们定义的时候只需要这样做即可
export default class Test extends Vue {
public message1: string = "asd"
public message2: string = "asd1";
public message3: string = "asd2";
props传值
props的话就没有data那么舒服了,因为他需要使用装饰器了,写法如下
@Prop({
type: Number,
default: 1,
required: false
})
propA?: number
@Prop()
propB:string
如果没有默认值的建议使用可选属性?来定义,这样避免报错。
$emit传值
$emit传值涉及到两部分,一个是父组件定义的方法,一个是子组件触发。当前emit有三种方法
首先子组件的写法
<template>
<div class="test-container">
{{message}}
<input type="button" value="点击触发父级方法" @click="bindSend"/>
<input type="button" value="点击触发父级方法" @click="handleSend"/>
<input type="button" value="点击触发父级方法" @click="bindSend2"/>
<!-- <Hello></Hello> -->
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch, Emit } from "vue-property-decorator";
// 注明此类为一个vue组件
@Component({})
export default class Test extends Vue {
// 第一种
@Emit()
private bindSend():string{
return this.message
}
// 第二种
@Emit()
private bindSend1(msg:string){
// 如果不处理可以不写下面的,会自动将参数回传
msg += 'love';
return msg;
}
//原有放在methods中的方法平铺出来
public handleSend():void {
this.bindSend1(this.message);
}
// 第三种
// 这里的emit中的参数是表明父级通过什么接受,类似以前的$emit('父级定义的方法')
@Emit('test')
private bindSend2(){
return '这个可以用test接受';
}
}
</script>
父组件的接收代码,这里父组件懒得换成ts写法了,意思到了就行了(哈哈)
<template>
<div id="app">
<Test :propA="propA" @bind-send = "bindSend" @bind-send1 = 'handleSend1' @test = 'handleSend2'></Test>
</div>
</template>
<script>
export default {
name: 'App',
components: {
Test:()=>import('./components/test.vue')
},
data(){
return {}
},
methods: {
bindSend(val) {
console.log('子集触发了emit方法' +val);
},
handleSend1(val) {
console.log('子集触发了emit方法' +val);
},
handleSend2(val) {
console.log('子集触发了emit方法' +val);
}
},
}
</script>
如果我们需要传多个值的话,我们怎么弄呢?来一起看看源码的实现你就知道了
export function Emit(event) {
return function (_target, propertyKey, descriptor) {
var key = hyphenate(propertyKey);
var original = descriptor.value;
descriptor.value = function emitter() {
var _this = this;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var emit = function (returnValue) {
var emitName = event || key;
if (returnValue === undefined) {
if (args.length === 0) {
_this.$emit(emitName);
}
else if (args.length === 1) {
_this.$emit(emitName, args[0]);
}
else {
_this.$emit.apply(_this, [emitName].concat(args));
}
}
else {
if (args.length === 0) {
_this.$emit(emitName, returnValue);
}
else if (args.length === 1) {
_this.$emit(emitName, returnValue, args[0]);
}
else {
_this.$emit.apply(_this, [emitName, returnValue].concat(args));
}
}
};
var returnValue = original.apply(this, args);
if (isPromise(returnValue)) {
returnValue.then(emit);
}
else {
emit(returnValue);
}
return returnValue;
};
};
}
通过源码我们可以发现有两种方法,如果在方法中也就是
@Emit()
private bindSend1(msg:string){
// 如果不处理可以不写下面的,会自动将参数回传
msg += 'love';
return msg;
}
通过return返回的方式话的话只会取args[0],所以这种情况建议直接以一个对象或者数组方式传递
上面说到了给父组件传值的时候可以只通过参数,而不需要return,那么源码中可以看到var returnValue = original.apply(this, args);所以我们也可以这样来传
@Emit()
private bindSend1(msg:string,love:string){}
//原有放在methods中的方法平铺出来
public handleSend():void {
this.bindSend1(this.message,'love');
}
父组件中接受数据
handleSend1(val,val1) {
console.log('子集触发了emit方法' +val + val1);
},
watch观察数据
//原有的watch属性
@Watch('propA',{
deep:true
})
test(newValue:string,oldValue:string){
console.log('propA值改变了' + newValue);
}
computed计算属性
在前面vue-class-component介绍里面我们提到过Component怎么处理数据的
else if (descriptor.get || descriptor.set) {
// 如果存在get和set,则为计算属性
(options.computed || (options.computed = {}))[key] = {
get: descriptor.get,
set: descriptor.set
};
}
所以我们在写计算属性的时候只需要按照以下方法写即可
public get computedMsg(){
return '这里是计算属性' + this.message;
}
public set computedMsg(message:string){
}
vue组件的生命周期方法
和以前一样使用即可
到此基础使用已经结束,下篇说明路由以及vuex使用方法
最后附上完整代码
<template>
<div class="test-container">
{{message}}
<input type="button" value="点击触发父级方法" @click="bindSend"/>
<input type="button" value="点击触发父级方法" @click="handleSend"/>
<input type="button" value="点击触发父级方法" @click="bindSend2"/>
<!-- <Hello></Hello> -->
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch, Emit } from "vue-property-decorator";
import Hello from "./HelloWorld.vue";
// 注明此类为一个vue组件
@Component({
components: {
Hello
}
})
export default class Test extends Vue {
// 原有data中的数据在这里展开编写
public message: string = "asd";
//原有props中的数据展开编写
@Prop({
type: Number,
default: 1,
required: false
})
propA?: number
@Prop()
propB:string
//原有computed
public get computedMsg(){
return '这里是计算属性' + this.message;
}
public set computedMsg(message:string){
}
//原有的watch属性
@Watch('propA',{
deep:true
})
public test(newValue:string,oldValue:string){
console.log('propA值改变了' + newValue);
}
// 以前需要给父级传值的时候直接方法中使用emit就行了,当前需要通过emit来处理
@Emit()
private bindSend():string{
return this.message
}
@Emit()
private bindSend1(msg:string,love:string){
// 如果不处理可以不写下面的,会自动将参数回传
// msg += 'love';
// return msg;
}
//原有放在methods中的方法平铺出来
public handleSend():void {
this.bindSend1(this.message,'love');
}
// 这里的emit中的参数是表明父级通过什么接受,类似以前的$emit('父级定义的方法')
@Emit('test')
private bindSend2(){
return '这个可以用test接受';
}
}
</script>
网友评论