vue3.0 开发一个“简单“插件
前言
随着 vue3.0beta 的发布,并且其核心插件 vuex,vue-router 也都出了 beta 和 alpha 版进行适配。感觉差不多已经可以用 3.0 撸个项目来提前感受一下 Composition API 的魅力。结果发现,还没有一个 ui 框架出了什么 beta 还是 alpha 来适配 3.0。
于是,那就自己试着撸一个简单 ui 插件,看看和 2.0 有什么不同。于是这个项目就是,重构 element-ui。目标,适配 3.0。
当然本文主要是讲解插件开发的不同点
install 函数
插件的入口文件 index.js
const install = function (app, opts = {}) {// locale.use(opts.locale);
// locale.i18n(opts.i18n);
// register components
components.forEach(component => {
// debugger
app.component(component.name, component)
})
// Vue.use(InfiniteScroll);
// Vue.use(Loading.directive);
/***
* Vue.prototype.$ELEMENT = {
size: opts.size || '',
zIndex: opts.zIndex || 2000
};
*/
app.provide(ELEMENTSymbol, {
size: opts.size || '',
zIndex: opts.zIndex || 2000
})
// Vue.prototype.$loading = Loading.service;
// Vue.prototype.$msgbox = MessageBox;
// Vue.prototype.$alert = MessageBox.alert;
// Vue.prototype.$confirm = MessageBox.confirm;
// Vue.prototype.$prompt = MessageBox.prompt;
// Vue.prototype.$notify = Notification;
// Vue.prototype.$message = Message;
}
这是对 element-ui 源码的 install 函数的改造。其中最大的区别是,install 函数的参数由原本的 Vue 变成 app 了。
不单纯是名称的改变。原来的 Vue 是原型,可以理解是类。而现在的 app 是 Vue 的实例。这样一来,用法就完全不同了。
不能再像以前一样使用原型的 prototype 属性来实现全局变量,函数啥的了。并且新 api 已经不推荐采用 this.$xxx 的方式来访问全局对象。而是采用 provide,inject 函数来封装。
因此以上代码中,所有的 prototype 就必须全都注释掉。也就是因为这个原因,有些开发者在 vue3.0 中直接导入现在 element-ui 版本,运行就直接报错。其实就是卡在了 prototype 属性缺失这里。
button.vue
虽然重构整个 element-ui 是个漫长的踩坑之旅,但柿子先捡软的捏,先弄个比较简单的 button 组件来捏。
export default {
name: 'ElButton',
props: {
type: {
type: String,
default: 'default'
},
size: String,
icon: {
type: String,
default: ''
},
nativeType: {
type: String,
default: 'button'
},
loading: Boolean,
disabled: Boolean,
plain: Boolean,
autofocus: Boolean,
round: Boolean,
circle: Boolean
},
setup(props,ctx) {
// inject
const elForm = inject('elForm', '')
const elFormItem = inject('elFormItem', '')
const ELEMENT = useELEMENT()
// computed
const _elFormItemSize = computed(() => {return (elFormItem || {}).elFormItemSize
})
const buttonSize = computed(() => {return props.size || _elFormItemSize.value || (ELEMENT || {}).size
})
const buttonDisabled = computed(() => {return props.disabled || (elForm || {}).disabled
})
console.log(buttonSize.value)
//methods
const handleClick = (evt) => {ctx.emit('click', evt)
}
return {
buttonSize,
buttonDisabled,
handleClick
}
},
/*inject: {
elForm: {default: ''},
elFormItem: {default: ''}
},*/
/*computed: {_elFormItemSize() {return (this.elFormItem || {}).elFormItemSize;
},
buttonSize() {return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
buttonDisabled() {return this.disabled || (this.elForm || {}).disabled;
}
},*/
/* methods: {handleClick(evt) {this.$emit('click', evt);
}
}*/
};
template 的部分并没有什么改变(可能有些改变我不知道),所以,直接照抄源码。所以不贴出来了。
js 部分,最大的不同就是新 api 推荐的新函数 setup(), 这几乎是一个 all in one 的函数,它可以把以前的 data,computed,methods 等全都写到里面去。写的好,一个组件就 name,props,setup 三个属性就结束了。
setup 中的 props 参数就是用于获取 props 中的属性值用的。ctx 参数是一个封装了 slots,emit 等对象的 proxy 对象,用于替代以前的 this.$slots,this.$emit 等。
button 组件相对简单,三个 computed 的属性用 computed 函数替代,
注意,computed 函数返回的其实是一个 Ref 对象,在 setup 中访问时需要写成 xxx.value 才能获得值。而在 template 中则不需要加 value。会自动解析。
老的 methods 属性中的函数则直接在 setup 函数中定义,通过 return 返回即可。
row.js
这是一个没有 template 的纯 js 组件,挑选这个组件,主要看看他的 render 函数在新 api 中应该怎么写。
import {computed, h, provide, inject} from 'vue'
// 常量
const gutterSymbol = Symbol()
export function useGutter() {return inject(gutterSymbol)
}
export default {
name: 'ElRow',
componentName: 'ElRow',
props: {
tag: {
type: String,
default: 'div'
},
gutter: Number,
type: String,
justify: {
type: String,
default: 'start'
},
align: {
type: String,
default: 'top'
}
},
setup(props, ctx) {const style = () => computed(() => {const ret = {}
if (props.gutter) {ret.marginLeft = `-${props.gutter / 2}px`
ret.marginRight = ret.marginLeft
}
return ret
})
provide(gutterSymbol, props.gutter)
return () => h(props.tag, {
class: [
'el-row',
props.justify !== 'start' ? `is-justify-${props.justify}` : '',
props.align !== 'top' ? `is-align-${props.align}` : '',
{'el-row--flex': props.type === 'flex'}
],
style
}, ctx.slots);
},
/*computed: {style() {const ret = {};
if (this.gutter) {ret.marginLeft = `-${this.gutter / 2}px`;
ret.marginRight = ret.marginLeft;
}
return ret;
}
},*/
/*render() {
return h(this.tag, {
class: [
'el-row',
this.justify !== 'start' ? `is-justify-${this.justify}` : '',
this.align !== 'top' ? `is-align-${this.align}` : '',
{'el-row--flex': this.type === 'flex'}
],
style: this.style
}, this.$slots.default);
}*/
}
在新 api 中,render 函数已经被 setup 的 return 给整合了。当你用 template 时,return 的是 template 中需要使用的绑定数据对象。但如果没有 template,return 的就是渲染函数。同样,这个 return 也支持 jsx 语法。比如
return () => <div>jsx</div>
不过目前官方还没有开发好 vue3.0 的支持 jsx 的 babel 插件。所以,当下仍然只能使用 h 函数,也就是 createElement 函数。
总结一波
首先,插件入口函数 install 变了, 参数由 Vue 原型,变成了 Vue 的根实例。
其次,组件已基本被 setup 函数 all in one 了。一个组件,我们甚至可以写成如下形式:
const vueComponent = {setup(props) {return h('div',props.name)
}
}
是不是很眼熟,是不是很像如下代码:
const reactComponent = (props) => {return <h1>Hello, {props.name}</h1>;
}
没错,很像 react 了。未来 vue3.0 中是铁定支持 jsx 语法的。两者就更像了。vue3.0 的官方征求意见稿中也说了,它们借鉴了 react 的 hook。其实,vue3.0 这次的最大更新就是从对象式编程走向了函数式。react 的 hook 就是为了完善函数式编程而出现的。
终于,两个主流的前端框架,还是走到了一起。不知道隔壁的 angular 啥时候也跟进一波。估计未来某一天就大统一了。
从撸代码的角度,最大的不同就是,以前满屏的 this,看不到了。理论上,新的 api 中,this 是可以完全不需要使用的。一个 setup 函数搞定所有。
目前本项目才开始,但我会持续踩坑下去。
附上项目地址:https://github.com/Hades-li/element-next