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

2019-10-22  本文已影响0人  hd_superman

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

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

规则千万条,简洁第一条
本文链接: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的特性实现,更好的保障用户的数据安全,实乃团队协作之利器。欢迎大家使用~

上一篇下一篇

猜你喜欢

热点阅读