美文网首页
vue + typescript(二)基础使用篇

vue + typescript(二)基础使用篇

作者: 来瓶二锅头00 | 来源:发表于2020-07-03 12:20 被阅读0次

回顾下我们日常工作中常用的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>

相关文章

网友评论

      本文标题:vue + typescript(二)基础使用篇

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