美文网首页
“茴”字写法 —— Vue typescript 组件

“茴”字写法 —— Vue typescript 组件

作者: hd_superman | 来源:发表于2019-10-22 14:44 被阅读0次

    孔乙己显出极高兴的样子,将两个指头的长指甲敲着柜台,点头说,“对呀对呀!……回字有四样写法,你知道么?”我愈不耐烦了,努着嘴走远。孔乙己刚用指甲蘸了酒,想在柜上写字,见
    我毫不热心,便又叹一口气,显出极惋惜的样子。

    《“茴”字写法》系列文章主要总结常见的代码操作,给出多种实现方式,就像茴香豆的“茴”字有多种写法一样。

    规则千万条,简洁第一条
    本文链接:https://taskhub.work/article/75576861798703104


    正文开始

    Vue 组件有很多中写法,在3.0之后会更好的支持typescript,ts用过都知道,真香,无论是代码提示还是代码重构都非常方便,本人之前写过一个UI库大概4~5万行规模,全用ts,期间发现很多不合理的地方,对UI库进行重构,只花了2天时间。下面通过对比不同写法。

    各个Vue 组件UI库实现方式:

    UI 库 实现方式
    muse-ui 完全不写 <template> 只使用 render 函数
    iview 使用 .vue 文件,样式单独写
    element 使用 .vue 文件,样式单独写
    vant 使用 .vue 文件,样式单独写
    ant-design-vue 使用 .jsx 文件,样式单独写
    vux 使用带 <style> 的 .vue 文件,但在使用时必须用 vux-loader
    cube-ui 使用带 <style> 的 .vue 文件,但有一些配置

    在实际开发中用不用 *.vue 这样的单文件组件来开发呢?
    网上有很多网友吐槽Vue的单文件组件模式写法,认为Vue的模板语法很鸡肋,各种不方便,不如全部jsx。决定这个问题的关键是解耦,包括功能解耦、模块解耦、甚至框架解耦。*.vue文件组织方式虽然多少和Vue相关,但是实际操作时发现要解耦重构也不是很大的问题,所以还OK。

    三种组件写法对比

    Object API 29 lines

    import Vue, { PropOptions } from 'vue'
    
    interface User {
      firstName: string
      lastName: number
    }
    
    export default Vue.extend({
      name: 'YourComponent',
    
      props: {
        user: {
          type: Object,
          required: true
        } as PropOptions<User>
      },
    
      data () {
        return {
          message: 'This is a message'
        }
      },
    
      computed: {
        fullName (): string {
          return `${this.user.firstName} ${this.user.lastName}`
        }
      }
    })
    

    Class API 17 lines

    import { Vue, Component, Prop } from 'vue-property-decorator'
    
    interface User {
      firstName: string
      lastName: number
    }
    
    @Component
    export default class YourComponent extends Vue {
      @Prop({ type: Object, required: true }) readonly user!: User
    
      message: string = 'This is a message'
    
      get fullName (): string {
        return `${this.user.firstName} ${this.user.lastName}`
      }
    }
    

    Function API 25 lines

    import Vue from 'vue'
    import { computed, value } from 'vue-function-api'
    
    interface User {
      firstName: string
      lastName: number
    }
    
    interface YourProps {
      user?: User
    }
    
    export default Vue.extend({
      name: 'YourComponent',
    
      setup ({ user }: YourProps) {
        const fullName = computed(() => `${user.firstName} ${user.lastName}`)
        const message = value('This is a message')
    
        return {
          fullName,
          message
        }
      }
    })
    
    写法 优点 缺点
    Object API Vue 官方写法,方便Vue直接处理组件 1. 代码长、缩进多,组件复杂时难以理清逻辑,不好进行分割
    2. 混入较多Vue的概念,新手学习成本高
    Class API 相关概念可以用class的思路理解,可以更好地描述Vue的混入、data、computed,生命周期钩子等概念。Vue 3.0 将原生支持class写法 用到了修饰器语法特性,目前还在实验阶段(typescript可以使用helper函数解决兼容问题,问题不大)
    Function API 无状态,更好的单元测试、并行化 函数式写法很容易写出回调地狱,导致代码可读性、可维护性差,目前纯粹function api 写法较少见

    完成同样一件事,ts的class写法简洁得多,在工程较大时减少1/3左右的代码,可维护性大大提高。

    typescript class 写法常见问题

    1. route 钩子无效问题
      使用class写法会发现部分Vue的钩子函数无法使用问题,可以通过注册钩子函数解决,如下:
      import Component from 'vue-class-component'
      
      // Register the router hooks with their names
      Component.registerHooks([
        'beforeRouteEnter',
        'beforeRouteLeave',
        'beforeRouteUpdate' // for vue-router 2.2+
      ])
      
    2. 与Vuex配合使用问题
      使用vuex-class 解决,如state映射
      import { Vue, Component } from 'vue-property-decorator';
      import { User } from '@/api/account';
      import { State } from 'vuex-class';
      
      
      @Component
      export default class TestPage extends Vue {
      
        @State(state => state.user, { namespace: 'account' })
        user!: User;
      
      }
      
    3. Vue 混入功能
      代码经常需要各种错误,包括用户输入错误、安全检测、后台错误、网络故障等。如果全部错误处理代码放进组件中,代码臃肿,阅读性差,可以将常见的错误处理逻辑提取出来,通过混入的方式插入组件中。class写法推荐使用vue-property-decorator 的 Mixins,参考附录

    附录

    附上模板代码,解决大多数Vue typescript 组件问题
    Vue class 组件

    import { Vue, Component, Prop, Watch, Model, Mixins } from 'vue-property-decorator';
    import { State, Getter, Action, Mutation } from 'vuex-class';
    import axios, { AxiosError } from 'axios';
    
    interface Person {
      userId: string;
      nickname: string;
    }
    
    @Component
    class CommonHandler extends Vue {
      onNetworkError(e: AxiosError) {
        console.log('on error');
      }
    }
    
    @Component
    export default class Test extends Mixins(CommonHandler) {
      @Prop(Number) readonly propA: number | undefined
    
      @Prop({ default: 'default value' }) readonly propB!: string
    
      @Prop([String, Boolean]) readonly propC: string | boolean | undefined
    
      @Model('change', { type: Boolean }) readonly checked!: boolean
      
      message: string = 'hello world';
    
      get propBLen(): number {
        return this.propB.length;
      }
    
      @Watch('child')
      onChildChanged(val: string, oldVal: string) {}
    
      @Watch('person', { immediate: true, deep: true })
      onPersonChanged1(val: Person, oldVal: Person) {}
    
      @Watch('person')
      onPersonChanged2(val: Person, oldVal: Person) {}
     
      change() {
        console.log('on change');
      }
    
      created() {
        // 调用混入中的错误处理函数,简化代码
        axios.get('hello').catch(this.onNetworkError);
      }
    
      mounted() { console.log('mounted'); }
    }
    

    Vue 官方写法

    import axios from 'axios';
    
    const CommonHandler = {
      methods: {
        onNetworkError(e) {
          console.log('on error');
        }
      },
    }
    
    export default {
      mixins: [CommonHandler],
      
      props: {
        propA: {
          type: Number
        },
        propB: {
          default: 'default value'
        },
        propC: {
          type: [String, Boolean]
        }
      },
    
      model: {
        prop: 'checked',
        event: 'change'
      },
    
      data() {
        return {
          message: 'hello world',
        }
      },
    
      computed: {
        propBLen() {
          return this.propB.length;
        },
      },
    
      watch: {
        child: [
          {
            handler: 'onChildChanged',
            immediate: false,
            deep: false
          }
        ],
        person: [
          {
            handler: 'onPersonChanged1',
            immediate: true,
            deep: true
          },
          {
            handler: 'onPersonChanged2',
            immediate: false,
            deep: false
          }
        ]
      },
    
      methods: {
        change() {
          console.log('on change');
        },
    
        onChildChanged(val, oldVal) {},
    
        onPersonChanged1(val, oldVal) {},
    
        onPersonChanged2(val, oldVal) {}
      },
    
      // vue lifecycle hooks  
      created() {
        // 调用混入中的错误处理函数,简化代码
        axios.get('hello').catch(this.onNetworkError);
      },
    
      mounted() { console.log('mounted'); }
    }
    

    52行对比84行,同样功能减少38%的代码

    最后打个广告~

    image

    TaskHub 是我们团队开发的一个 Markdown 加密网盘,支持常见的任务管理功能还有Markdown 文件编辑,所有功能与平台解耦,只使用Markdown的特性实现,更好的保障用户的数据安全,实乃团队协作之利器。欢迎大家使用~

    相关文章

      网友评论

          本文标题:“茴”字写法 —— Vue typescript 组件

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