基於Vue構造器建立Form元件的通用解決方案
在前端平常的業務中,無論是官網、展示頁還是後臺運營系統都離不開表單,它承載了大部分的資料採集工作。所以如何更好地實現它,是平常工作中的一個重要問題。
在應用Vue框架去開發業務時,會將頁面上每個獨立的可視/可互動區域拆分為一個元件,再通過多個元件的自由組合來組成新的頁面。例如
<template>
<header></header>
...
<content></content>
...
<footer></footer>
</template>
複製程式碼
當用戶的某個行為頁面觸發表單時(例如註冊、建立內容等),我們會在頁面中彈出一個From元件。通常的做法是在template
<register-form>
元件用於開發登錄檔單,並通過控制data
中的UI.isOpen
來對其display
進行控制。
<template>
<header></header>
...
<content></content>
...
<footer></footer>
...
<register-form v-if="UI.isOpen">
<form-item></form-item>
...
<submit-button></submit-button>
</register-form>
</template>
複製程式碼
這樣開發有一點優勢,Form元件與原元件可以通過prop
以及event
方便通訊以及資料傳遞。但是也會有以下幾個缺陷:
- 當前元件的
data
必須要有UI.isOpen
來控制表單,如果存在多個表單時,就會有大段的程式碼來維護表單的狀態; - 如果多個
button
觸發同一個表單時,可能需要對錶單的data
進行重置; - 與元件化思想相違背,表單不屬於當前頁面,它只是由於使用者行為觸發的結果。
為了解決以上缺陷,並且還能具備方便通訊的優勢,本文選擇用Vue.extend
將原有<register-form></register-form>
元件轉化為函式,並維護在當前元件的method
例項:
程式碼地址:[[github.com/FatGe/examp…]]
- APP元件
<template>
<div id="app">
<el-button
type="primary" icon="el-icon-edit-outline"
@click="handleClick"
>註冊</el-button>
</div>
</template>
<script>
import register from './components/register'
import { transform } from './transform'
export default {
name: 'App',
methods: {
register: transform(register),
handleClick () {
this.register({
propsData: {
name: '皮鞋'
},
done: name => alert(`${name}牛B`)
})
}
}
}
</script>
複製程式碼
當APP元件中<el-button>
的點選事件觸發時,呼叫register
方法,將表單元件掛載在頁面中。
- Form元件
<template>
<div class="mock" v-if="isVisible">
<div class="form-wrapper">
<i class="el-icon-close close-btn" @click.stop="close"></i>
<div class="header">
...
</div>
<div class="content">
...
</div>
<div class="footer">
<el-button
type="primary"
@click="handleClick"
>確定</el-button>
<el-button
type="primary"
@click="handleClick"
>取消</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
name: { type: String, default: '' }
},
data () {
return {
isVisible: true
}
},
watch: {
isVisible (newValue) {
if (!newValue) {
this.destroyElement()
}
}
},
methods: {
handleClick ({ type }) {
const handler = {
close: () => this.close()
}
},
destroyElement () {
this.$destroy()
},
close () {
this.isVisible = false
}
},
created () {
// init data
}
mounted () {
document.body.appendChild(this.$el)
},
destroyed () {
this.$el.parentNode.removeChild(this.$el)
}
}
</script>
複製程式碼
兩者的通訊通過register
的options
還完成,並且Form元件中維護了自身的isVisible
,當用戶點選close
或者取消時,表單會自動關閉並從頁面中移除。
原理:
上述程式碼中,最為關鍵的一步就是transform
函式,它將原有的Form元件從原文的single-file components轉化為了method,其原理如下
const transform = (component) => {
const _constructor = Vue.extend(component)
return function (options = {}) {
const {
propsData
} = options
let instance = new _constructor({
propsData
}).$mount(document.createElement('div'))
return instance
}
}
複製程式碼
首先利用Vue的API-Vue.extend(options)
[cn.vuejs.org/v2/api/#Vue…]建立一個Form元件的子類
const _constructor = Vue.extend(component)
複製程式碼
然後return
一個function
,它的功能是,在method
呼叫時,將元件例項化
const {
propsData
} = options
let instance = new _constructor({
propsData
}).$mount(document.createElement('div'))
複製程式碼
為了能夠控制例項化後的元件,選擇instance
返回。
當元件例項化時,它只是掛載到document.createElement('div')
上,但是並沒有掛載到頁面上,所以需要將其appendChild
到頁面中。為了更好的語義化,選擇在元件的生命週期中完成它在頁面中的掛載。例項化時,會觸發元件mounted
生命週期,所以當其觸發時可以掛載在document.body
中,具體如下
mounted () {
document.body.appendChild(this.$el)
}
複製程式碼
有了掛載,就必須要有登出。對應的生命週期應該是destroyed
,所以
method: {
destroyElement () {
this.$destroy()
}
},
destroyed () {
this.$el.parentNode.removeChild(this.$el)
}
複製程式碼
元件登出的時間與它在頁面中顯示息息相關,所以
watch: {
isVisible (newValue) {
if (!newValue) {
this.destroyElement()
// 如果需要新增過渡動畫
// this.$el.addEventListener('transitionend', this.destroyElement)
}
}
}
複製程式碼
一般Form元件有兩個功能:
- done:代表使用者確認;
- cancel:代表使用者取消;
當done或cancel觸發時,APP元件內可能會有相應的變化,所以在元件例項化之後,利用$on
去監聽對應的done
事件以及cancel
事件。
done && inlineListen({
method: 'done',
options,
instance
})
cancel && inlineListen({
method: 'cancel',
options,
instance
})
複製程式碼
其中inlineListen
函式可以方便後續新增其他的event
,其程式碼為
const inlineListen = ({
method,
options,
instance
}) => {
let listener = `on${method}`
instance[listener] = options[method]
instance.$on(method, function (data) {
this[listener](data)
})
}
複製程式碼
以上是解決Form元件的原理以及方案。
使用:
可以將上述屬於Form元件公有的data
以及method
獨立出來,再通過mixins
引入到每個表單內,例如
export default {
data() {
return {
visible: true
}
},
watch: {
visible(newValue) {
if (!newValue) {
this.destroyElement()
}
}
},
mounted() {
document.body.appendChild(this.$el)
},
destroyed() {
this.$el.parentNode.removeChild(this.$el)
},
methods: {
destroyElement() {
this.$destroy()
},
close() {
this.visible = false
}
}
}
複製程式碼
再通過mixins
混入。
<script>
import popupWin from '../mixins/popup-win'
export default {
mixins: [popupWin],
data () {
return {
input: '',
gender: 1
}
},
methods: {
handleClick ({ type }) {
const handler = {
close: () => this.close()
}
}
}
}
</script>
複製程式碼
總結:
通過上述的transform
函式,將原有的注入式元件轉化為了命令式,簡化了頁面狀態的維護,在通過mixins
混入公有data
以及method
,簡化了表單元件開發。上述方法也可用於開發toast、alert、confirm等元件,只需要將Vue.prototype = transform(Toast-Component)
。
原創宣告: 該文章為原創文章,轉載請註明出處。