美文网首页
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