孔乙己显出极高兴的样子,将两个指头的长指甲敲着柜台,点头说,“对呀对呀!……回字有四样写法,你知道么?”我愈不耐烦了,努着嘴走远。孔乙己刚用指甲蘸了酒,想在柜上写字,见
我毫不热心,便又叹一口气,显出极惋惜的样子。
《“茴”字写法》系列文章主要总结常见的代码操作,给出多种实现方式,就像茴香豆的“茴”字有多种写法一样。
规则千万条,简洁第一条
本文链接: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 写法常见问题
- 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+ ])
- 与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; }
- 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%的代码
最后打个广告~
TaskHub 是我们团队开发的一个 Markdown 加密网盘,支持常见的任务管理功能还有Markdown 文件编辑,所有功能与平台解耦,只使用Markdown的特性实现,更好的保障用户的数据安全,实乃团队协作之利器。欢迎大家使用~