隨着vue3.0beta的發佈,而且其核心插件vuex,vue-router也都出了beta和alpha版進行適配。感受差很少已經能夠用3.0擼個項目來提早感覺一下Composition API的魅力。結果發現,尚未一個ui框架出了什麼beta仍是alpha來適配3.0。
因而,那就本身試着擼一個簡單ui插件,看看和2.0有什麼不一樣。因而這個項目就是,重構element-ui。目標,適配3.0。
固然本文主要是講解插件開發的不一樣點前端
插件的入口文件index.jsvue
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屬性缺失這裏。react
雖然重構整個element-ui是個漫長的踩坑之旅,但柿子先撿軟的捏,先弄個比較簡單的button組件來捏。git
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返回便可。github
這是一個沒有template的純js組件,挑選這個組件,主要看看他的render函數在新api中應該怎麼寫。vue-router
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語法。好比vuex
return () => <div>jsx</div>
不過目前官方尚未開發好vue3.0的支持jsx的babel插件。因此,當下仍然只能使用h函數,也就是createElement函數。編程
首先,插件入口函數install變了,參數由Vue原型,變成了Vue的根實例。
其次,組件已基本被setup函數all in one了。一個組件,咱們甚至能夠寫成以下形式:element-ui
const vueComponent = { setup(props) { return h('div',props.name) } }
是否是很眼熟,是否是很像以下代碼:api
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函數搞定全部。
目前本項目纔開始,但我會持續踩坑下去。