前言
随着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 () => jsx
不过目前官方还没有开发好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 Hello, {props.name}
;
}
没错,很像react了。未来vue3.0中是铁定支持jsx语法的。两者就更像了。vue3.0的官方征求意见稿中也说了,它们借鉴了react的hook。其实,vue3.0这次的最大更新就是从对象式编程走向了函数式。react的hook就是为了完善函数式编程而出现的。
终于,两个主流的前端框架,还是走到了一起。不知道隔壁的angular啥时候也跟进一波。估计未来某一天就大统一了。
从撸代码的角度,最大的不同就是,以前满屏的this,看不到了。理论上,新的api中,this是可以完全不需要使用的。一个setup函数搞定所有。
目前本项目才开始,但我会持续踩坑下去。