Vue自定義render統一專案組彈框功能
一、本文收穫
pick
二、為什麼要統一封裝彈框;
要封裝成怎樣
通過舉例常規彈框的寫法。我們可以體會到,通常要彈出一個頁面,需要建立一個頁面 normalDialog.vue
包裹 dialogBody.vue
(彈框主體);需要 parent.vue
設定flag控制彈框顯示隱藏, normalDialog.vue
關閉的時候設定 parent.vue
對應 flag
。缺點: 流程繁雜、配置繁瑣、不靈活、樣式不統一和引數傳遞麻煩等 。如果一個專案彈框較多的時候,弊端將會更明顯,大量的 isXxxDialogShow
,大量的 vue
檔案。因此專案組急需一個能簡單配置就能彈出彈框的 API
1. 常規彈框寫法 dialoBody.vue
(彈框主體) ,此處採用 Composition API
的寫法。只做了簡單的頁面,包含校驗,抽取儲存資料的常規邏輯。
<template> <div class="dialog-body"> <div class="item"> <div>名稱</div> <el-input v-model="name"></el-input> </div> <div class="item"> <el-radio-group v-model="attention"> <el-radio label="已關注"></el-radio> <el-radio label="等下關注"></el-radio> </el-radio-group> </div> <div class="item"> <el-radio-group v-model="like"> <el-radio label="已點贊"></el-radio> <el-radio label="等下點贊"></el-radio> </el-radio-group> </div> </div> </template> <script> import { reactive,toRefs } from '@vue/composition-api' import pick from 'lodash/pick' import { Message } from 'element-ui' export default { props: { defaultName: String,},setup(props,ctx) { const ATTENTIONED = '已關注' const LIKED = '已點贊' const state = reactive({ name: props.defaultName,// 名稱 attention: '已關注',// 關注 like: '已點贊',// 點贊 }) /************************************************************* * 頁面繫結的事件 * 建議寫法: * 1. 定義methods常量 * 2. 處理相關業務邏輯的時候,需要繫結事件到頁面的時 * 建議通過methods.onXxx = ()=>{ // 相關邏輯 }的形式定義 * 好處1: onXxx定義的位置和相關業務邏輯程式碼關聯一起 * 好處2: 可以統一通過...methods的形式在setup統一解構 * 好處3: 當頁面邏輯複雜,需要操作的資料關聯性強,不可拆解元件; * 可將相關業務的程式碼在獨立模組定義; * 獨立模組暴露API handleXxx(methods,state),流水線加工methods; * 和Vue2原始碼一樣,流水線加工的思想. */ const methods = {} // 校驗名稱 methods.onNameBlur = () => {} // ************************ 向外暴露的API ************************ const apiMethods = { // 儲存前校驗 isCanSave() { if (state.attention !== ATTENTIONED || state.like !== LIKED) { Message.error('未關注或者點贊,不能關閉,嘻嘻') return false } return true },// 獲取儲存資料 getSaveData() { // ******* lodash pick 從物件中抽取資料 return pick(state,['name','attention','like']) },} return { ...toRefs(state),...methods,apiMethods,} },} </script> <style lang="less"> .dialog-body { width: 100%; height: 100px; } </style>
2.normalDialog.vue
包裹彈框主體 dialoBody.vue
<template> <el-dialog title="帥哥,美女,我是標題" :visible.sync="isShow" width="30%" :before-close="onClose" > <dialog-body default-name="引數傳遞的名稱" ref="inner"></dialog-body> <span slot="footer" class="dialog-footer"> <el-button @click="onClose">取 消</el-button> <el-button type="primary" @click="onOK">確 定</el-button> </span> </el-dialog> </template> <script> import dialogBody from './dialogBody.vue' export default { components: { dialogBody,data() { return { isShow: true,methods: { onClose() { // *********** 修改parent.vue ******** this.$parent.isNormalDialogShow = false },// ******* 控制儲存流程 ******** onOK() { const inner = this.$refs.inner // 校驗是否可以儲存 if (inner.apiMethods.isCanSave()) { // 獲取儲存資料 const postData = inner.apiMethods.getSaveData() console.log('>>>>> postData >>>>>',postData) // 儲存成功後關閉彈框 this.onClose() } },} </script>
parent.vue
// html 部分 <normal-dialog v-if="isNormalDialogShow" /> // Js部分 data(){ isNormalDialogShow:false } methods:{ onDialogShow(){ // ******控制彈框顯示***** this.isNormalDialogShow = true } }
2. 要封裝成怎樣
2.1 API訴求:
isXxxDialogShow el-dialog
2.2 理想API:
import dialogBody from './dialogBody.vue' const dialog = new JSDialog({ comonent: dialogBody,dialogOpts: { // 可擴充套件配置 title: 'JSDialog設定的彈框標題',width: '400px' },props: { defaultName: 'JSDialog傳遞的引數',onOK() { const inner = dialog.getInner() // 能取到dialogBody的引用 // 控制流程 if (inner.apiMethods.isCanSave()) { // 獲取儲存資料 const postData = inner.apiMethods.getSaveData() console.log('>>>>> postData >>>>>',postData) // 關閉彈框 dialog.close() } },onCancel() { dialog.close() // 彈框關閉 },}) dialog.show() // 彈框顯示
三、如何封裝
動態控制顯示內容,腦海浮現的三個方案: 卡槽、動態元件和重寫 render 。下面在動態彈框場景下簡單對比三個方案。
- slot(卡槽) ,和 el-dialog 原理類似,只是再封裝了一層,少定義了 normalDialog.vue 檔案。 缺點:呼叫複雜,不靈活;不容易控制關閉的流程;只能在 template 中定義 。
- component(動態元件) ,建立 commonDialog.vue ,統一掛在 App.vue 下,利用 <component :is="componentId"></component> 動態切換彈框主體, commonDialog.vue 監聽 componentId 變化來切換彈框主體。 缺點:要提前將所有彈框主體元件註冊到commonDialog.vue頁面的components上;依賴於vuex,侵入性較強;純js檔案通過vuex彈出彈框相對複雜,不靈活 。
- 重寫 render , render 是 Vue 對造輪子開發者開放的後門。動態彈框可作為獨立的功能模組,內部通過new Vue ,重寫 render 控制渲染內容。 獨立 Vue 例項,可預先建立,可在任何位置控制彈框,靈活,清晰 。 缺點:暫無
1. 整體程式碼
先整體預覽一下程式碼,下面再細分講解。
import Vue from 'vue' import merge from 'lodash/merge' import orderBy from 'lodash/orderBy' // 按鈕配置項構造器 function btnBuilder(options) { const defaultBtn = { text: '按鈕',// 顯示文字 clickFn: null,// 點選回撥 type: 'default',// 樣式 isHide: false,// 是否隱藏 order: 2 // 順序 } return { ...defaultBtn,...options } } export default class JSDialog { constructor(originOptions) { this.options = {} this.vm = null this._mergeOptions(originOptions) this._initVm() } // 引數合併 _mergeOptions(originOptions) { const defaultOptions = { component: '',// 彈框主體vue頁面 // 可擴充套件el-dialog官方api所有配置項,小駝峰aaaBbbCcc dialogOpts: { width: '40%',title: '預設標題' },// 傳入彈框主體vue元件的引數 props: {},// 點選確定回撥 onOK: () => { console.log('JSDialog default OK'),this.close() },// 點選取消回撥 onCancel: () => { console.log('JSDialog default cancel'),footer: { ok: btnBuilder({ text: '確定',type: 'primary',order: 0 }),cancel: btnBuilder({ text: '取消',order: 1 }) } } // 引數合併到this.options merge(this.options,defaultOptions,originOptions) const footer = this.options.footer Object.entries(footer).forEach(([key,btnOptions]) => { // 確定和取消預設按鈕 if (['ok','cancel'].includes(key)) { const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel // 預設按鈕回撥優先順序: footer配置的clickFn > options配置的onOK和onCancel btnOptions.clickFn = btnOptions.clickFn || clickFn } else { // 新增按鈕 // 完善配置 footer[key] = btnBuilder(btnOptions) } }) } _initVm() { const options = this.options const beforeClose = this.options.footer.cancel.clickFn // 彈框右上角關閉按鈕回撥 this.vm = new Vue({ data() { return { // 需要響應式的資料 footer: options.footer,// 底部按鈕 visible: false // 彈框顯示及關閉 } },methods: { show() { // 彈框顯示 this.visible = true },close() { // 彈框關閉 this.visible = false },clearVm() { // 清除vm例項 this.$destroy() } },mounted() { // 掛載到body上 document.body.appendChild(this.$el) },destroyed() { // 從body上移除 document.body.removeChild(this.$el) },render(createElement) { // 彈框主體 const inner = createElement(options.component,{ props: options.props,// 傳遞引數 ref: 'inner' // 引用 }) // 控制按鈕顯示隱藏 const showBtns = Object.values(this.footer).filter(btn => !btn.isHide) // 控制按鈕順序 const sortBtns = orderBy(showBtns,['order'],['desc']) // 底部按鈕 jsx 寫法 const footer = ( <div slot="footer"> {sortBtns.map(btn => ( <el-button type={btn.type} onClick={btn.clickFn}> {btn.text} </el-button> ))} </div> ) // 彈框主體 const elDialog = createElement( 'el-dialog',{ // el-dialog 配置項 props: { ...options.dialogOpts,visible: this.visible,beforeClose },// **** 看這裡,visible置為false後,el-dialog銷燬後回撥 ***** on: { closed: this.clearVm },ref: 'elDialog' },// 彈框內容:彈框主體和按鈕 [inner,footer] ) return elDialog } }).$mount() } // 封裝API // 關閉彈框 close() { this.vm.close() } // 顯示彈框 show() { this.vm.show() } // 獲取彈框主體例項,可訪問例項上的方法 getInner() { return this.vm.$refs.inner } }
2. 引數合併
要做到 API 訴求中的:呼叫簡單、傳參簡便和可擴充套件控制彈框樣式。引數合併便是 成本最小 的實現方案,配合 TS 效果更佳。定義預設引數,通過 lodash 的 merge ,合併深層屬性。通過引數合併還能做到自定義 footer 按鈕,控制文字,樣式,順序和執行回撥。
// 引數合併 _mergeOptions(originOptions) { const defaultOptions = { component: '',footer: { ok: btnBuilder({ text: '確定',cancel: btnBuilder({ text: '取消','cancel'].includes(key)) { const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel // 預設按鈕回撥優先順序: footer配置的clickFn > options配置的onOK和onCancel btnOptions.clickFn = btnOptions.clickFn || clickFn } else { // 新增按鈕 // 完善配置 footer[key] = btnBuilder(btnOptions) } }) }
3. render函式
摘取一段 渲染函式 & JSX 官方文件關於 render 的描述: Vue 推薦在絕大多數情況下使用模板來建立你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全程式設計的能力。這時你可以用 渲染函式 ,它比模板更接近編譯器。 官方文件對渲染函式的寫法,引數,對應JSX寫法介紹已經很詳細,這裡就不再贅述。下面程式碼是在最新vue-cli建立專案上執行的,嘗試了JS引數建立元素和JSX建立元素兩種寫法。
render(createElement) { // 彈框主體 const inner = createElement(options.component,{ props: options.props,// 傳遞引數 ref: 'inner' // 引用 }) // 控制按鈕顯示隱藏 const showBtns = Object.values(this.footer).filter(btn => !btn.isHide) // 控制按鈕順序 const sortBtns = orderBy(showBtns,['desc']) // 底部按鈕 jsx 寫法 const footer = ( <div slot="footer"> {sortBtns.map(btn => ( <el-button type={btn.type} onClick={btn.clickFn}> {btn.text} </el-button> ))} </div> ) // 彈框主體 const elDialog = createElement( 'el-dialog',{ // el-dialog 配置項 props: { ...options.dialogOpts,visible: this.visible },on: { closed: this.clearVm },ref: 'elDialog' },// 彈框內容:彈框主體和按鈕 [inner,footer] ) return elDialog }
4. 封裝API
暫時只封裝了三個 API ,可根據不同的場景擴充套件 API ,比如彈框不銷燬隱藏,彈框重新整理等。
show() ,彈框顯示
顯示主要是修改 el-dialog 的 visible 為 true ,控制掛載到 body 上的彈框顯示。
show() { this.vm.show() }
close() ,彈框關閉
關閉處理流程:修改 el-dialog 的 visible 為 false ;觸發 el-dialog 的 closed 事件;執行 clearVm ;執行 vm 的 $destroy() ; destroyed() 回撥中將 $el 從 body 中移除。
close() { this.vm.close() }
getInner() ,獲取彈框主體例項,可用於訪問例項上的方法,控制按鈕流程
getInner() { return this.vm.$refs.inner }
四、如何使用
1. 最簡單場景,只配置頁面
按鈕事件回撥採用預設的回撥,確定和取消按鈕都可關閉彈框
import dialogBody from './renderJsx/dialogBody' const dialog = new JSDialog({ component: dialogBody,}) dialog.show() // 彈框顯示
效果如下:
2. 控制彈框樣式及確定流程
可自定義el-dialog支援的配置項,見 Dialog 對話方塊 ;比如:title、 customClass 。通過customClass可統一控制專案內彈框的風格;可控制確定取消按鈕程式碼回撥。
import dialogBody from './renderJsx/dialogBody' const dialog = new JSDialog({ component: dialogBody,dialogOpts: { title: '靚仔,美女歐嗨呦',customClass:'js-dialog' },props: { defaultName: 'JSDialog傳遞的引數' },onOK() { const inner = dialog.getInner() // 能取到dialogBody的引用 // 控制流程 if (inner.apiMethods.isCanSave()) { // 獲取儲存資料 const postData = inner.apiMethods.getSaveData() console.log('>>>>> postData >>>>>',postData) // 關閉彈框 dialog.close() } },onCancel() { dialog.close() // 彈框關閉 } })
效果如下:
3. 自定義footer
自定義按鈕可控制執行回撥,樣式,順序,顯示與隱藏
import dialogBody from './renderJsx/dialogBody' const dialog = new JSDialog({ component: dialogBody,footer: { ok: { // 修改預設按鈕 text: '新增' },cancel: { // 隱藏預設按鈕 isHide: true },add: { // 新增按鈕 text: '另存為',clickFn() { dialog.close() },order: -1 // 控制按鈕順序,order小的顯示在右邊 },add2: { text: '新增按鈕2',order: 3 } } }) dialog.show() // 彈框顯示
效果如下:
總結
到此這篇關於Vue自定義render統一專案組彈框功能的文章就介紹到這了,更多相關Vue自定義render專案組彈框內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!