Recommended articles in the past (hope you have a good harvest)
- Lodash’s “redundant” and eye-popping apis
- Don’t JavaScript to use annotations to the fire @ Decorator (comments | Decorator | decoration)
The following code is quite large, so just pay attention to the part marked with *, which will not affect the reading experience
First, this paper harvest
- Why do we want to outlaw the normal bullet frame;
- Deep parameter combination;
pick
Extract and save data;- How to unify Vue project popbox and simplify call
Second, why to unify the package frame; How do you package it
Through the example of conventional bullet frame writing method. Normaldialog. vue wraps dialogBody.vue (the body of the popup box) around the normaldialog. vue (the body of the popup box). Parent. Vue is required to set flag to control the display of the popbox hidden, normaldialog. vue is closed when the parent. Disadvantages: multifarious process, cumbersome configuration, inflexibility, inconsistent style and troublesome parameter transfer. If a project has more pop-ups, drawbacks will be more obvious, a large number of isXxxDialogShow, a large number of Vue files. Therefore, the project team urgently needed an API that could be easily configured to pop up the box.
1. Conventional bullet frame writing method
dialoBody.vue
(Frame body), adopted hereComposition API
In the writing. Just a simple page, with checksums, and the usual logic to extract and store data.
<template> <div class="dialog-body"> <div class="item"> <div> name </div> <el-input V-model ="name"></el-input> </div> <div Class ="item"> <el-radio-group v-model="attention"> <el-radio label=" attention"> </el-radio> <el-radio label=" attention"> </el-radio> </el-radio-group> </div> <div class="item"> <el-radio-group V-model ="like"> <el-radio label=" liked "></el-radio> <el-radio </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 = 'support' const LIKED = 'support' const state = reactive({name: function ()) // props. DefaultName, // props: props 'have thumb up, / / thumb up}) / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * page binding events suggest writing: * 1. Use methods.onxxx = ()=>{// Related logic} to define * Benefits 1: The location defined by onXxx is associated with the relevant business logic code * Benefit 2: Can be unified through... Advantages 3: when the page logic is complex, the data that needs to be operated has a strong correlation, and components cannot be disassembled; * Code of related business can be defined in a separate module; * Independent module exposed API handleXxx(Methods, State), assembly line processing methods; * and Vue2 source, pipeline processing. * / const the methods = {} / name/check the methods onNameBlur = () = > {} / / * * * * * * * * * * * * * * * * * * * * * * * * to expose apis * * * * * * * * * * * * * * * * * * * * * * * * const apiMethods = {/ / save before checking isCanSave () {if (state. Attention! == ATTENTIONED || state.like ! == new) {message. error(' new ', 'new', 'new ') 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>Copy the code
normalDialog.vue
Wrap the body of the framedialoBody.vue
<template> <el-dialog title=" isShow" width="30%" :before-close="onClose" > <dialog-body <span slot="footer" class="dialog-footer"> <el-button @click="onClose"> </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 () {/ / * * * * * * * * * * * modify the parent in vue * * * * * * * * this. $parent. IsNormalDialogShow = false}, / / * * * * * * * control preservation process * * * * * * * * onOK () {const inner = this. $refs. The inner / / check whether can be saved if (inner) apiMethods) isCanSave ()) {/ / For saving data const postData = inner. ApiMethods. GetSaveData () the console. The log (' > > > > > postData > > > > > ', // This. OnClose ()}},},} </script>Copy the code
parent.vue
/ / HTML
<normal-dialog v-if="isNormalDialogShow" />
/ / Js parts
data(){
isNormalDialogShow:false
}
methods:{
onDialogShow(){ // ****** The control dialog box displays *****
this.isNormalDialogShow = true}}Copy the code
2. How should it be packaged
2.1 API Appeal:
- Simple to call;
- Don’t set
isXxxDialogShow
; - Simple transfer of ginseng;
- Easy to control
el-dialog
Style property of; - You can control the closing process of the frame.
2.2 Ideal API:
import dialogBody from './dialogBody.vue'
const dialog = new JSDialog({
comonent: dialogBody,
dialogOpts: { // Scalable configuration
title: 'JSDialog set the title of the popbox '.width: '400px'
},
props: {
defaultName: 'Arguments passed by JSDialog',
},
onOK() {
const inner = dialog.getInner() // Get a dialogBody reference
// Control process
if (inner.apiMethods.isCanSave()) {
// Get the saved data
const postData = inner.apiMethods.getSaveData()
console.log('>>>>> postData >>>>>', postData)
// Close the dialog box
dialog.close()
}
},
onCancel() {
dialog.close() // The popbox closes
},
})
dialog.show() // The box is displayed
Copy the code
Three, how to package
Dynamic control of display content, three scenarios come to mind: card slots, dynamic components, and overriding Render. The following is a simple comparison of the three schemes in the dynamic pop-up scenario.
- Slot (card), and
el-dialog
The principle is similar, but there is another layer of encapsulation, less definitionnormalDialog.vue
File.Disadvantages: Complex call, inflexible; Not easy to control the closure process; Only in thetemplate
Defined in the. - Component (Dynamic)To create
commonDialog.vue
, unified hanging inApp.vue
By using<component :is="componentId"></component>
Dynamically switch the frame body,commonDialog.vue
Listening to thecomponentId
Change to switch the frame body.Cons: Register all popbox body components with commonDialog.vue components in advance; Relying on VUEX, it is highly invasive. Vuex pop-ups from pure JS files are relatively complex and inflexible. - rewrite
render
.render
isVue
A back door for developers who build wheels. Dynamic frame can be used as an independent function module, internal through newVue
, the rewriterender
Control rendering content.independentVue
Instance, can be pre-created, can control the frame in any position, flexible, clear.Disadvantages: None at present
1. Overall code
Here’s a preview of the code as a whole, then break it down.
import Vue from 'vue'
import merge from 'lodash/merge'
import orderBy from 'lodash/orderBy'
// Button configuration item constructor
function btnBuilder(options) {
const defaultBtn = {
text: 'button'.// Display text
clickFn: null.// Click callback
type: 'default'./ / style
isHide: false.// Whether to hide
order: 2 / / order
}
return{... defaultBtn, ... options } }export default class JSDialog {
constructor(originOptions) {
this.options = {}
this.vm = null
this._mergeOptions(originOptions)
this._initVm()
}
// Merge parameters
_mergeOptions(originOptions) {
const defaultOptions = {
component: ' '.// Popbox main vue page
/ / extensible el - all the configuration dialog official API, small hump aaaBbbCcc
dialogOpts: {
width: '40%'.title: 'Default title'
},
// Pass the argument to the popbox body vue component
props: {},
// Click OK to callback
onOK: (a)= > {
console.log('JSDialog default OK'), this.close()
},
// Click to cancel the callback
onCancel: (a)= > {
console.log('JSDialog default cancel'), this.close()
},
footer: {
ok: btnBuilder({
text: 'sure'.type: 'primary'.order: 0
}),
cancel: btnBuilder({
text: 'cancel'.order: 1}}})// The parameters are merged into this.options
merge(this.options, defaultOptions, originOptions)
const footer = this.options.footer
Object.entries(footer).forEach(([key, btnOptions]) = > {
// Determine and cancel the default buttons
if (['ok'.'cancel'].includes(key)) {
const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel
// Default button callback priority: footer configured clickFn > options configured onOK and onCancel
btnOptions.clickFn = btnOptions.clickFn || clickFn
} else {
// Add a button
// Complete the configuration
footer[key] = btnBuilder(btnOptions)
}
})
}
_initVm() {
const options = this.options
const beforeClose = this.options.footer.cancel.clickFn // The close button in the upper right corner of the popbox is called back
this.vm = new Vue({
data() {
return {
// Reactive data is required
footer: options.footer, // Bottom button
visible: false // The popbox is displayed and closed}},methods: {
show() {
// The box is displayed
this.visible = true
},
close() {
// The popbox closes
this.visible = false
},
clearVm() {
// Clear the VM instance
this.$destroy()
}
},
mounted() {
// Mount to body
document.body.appendChild(this.$el)
},
destroyed() {
// Remove from the body
document.body.removeChild(this.$el)
},
render(createElement) {
// Frame body
const inner = createElement(options.component, {
props: options.props, // Pass parameters
ref: 'inner' / / reference
})
// Control button display hidden
const showBtns = Object.values(this.footer).filter(btn= >! btn.isHide)// Control button sequence
const sortBtns = orderBy(showBtns, ['order'], ['desc'])
// Bottom button JSX notation
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 configuration item: {... DialogOpts, visible: this.visible, beforeClose}, // **** { closed: this.clearVm }, ref: 'elDialog'}, // Popbox contents: Frame body and button [inner, Footer]) return elDialog}}).$mount()} close() {this.vm. Close ()} show() {this.vm. {return this.vm.$refs.inner}}Copy the code
2. Merge parameters
To achieve the API appeal: simple call, simple parameter transfer and extensible control frame style. Parameter combination is the least cost implementation scheme, and the effect is better with TS. Define default parameters and merge deep attributes using Lodash’s merge. Parameter merging also allows you to customize the footer button, control text, style, order, and execute callbacks.
// Merge parameters
_mergeOptions(originOptions) {
const defaultOptions = {
component: ' '.// Popbox main vue page
/ / extensible el - all the configuration dialog official API, small hump aaaBbbCcc
dialogOpts: {
width: '40%'.title: 'Default title'
},
// Pass the argument to the popbox body vue component
props: {},
// Click OK to callback
onOK: (a)= > {
console.log('JSDialog default OK'), this.close()
},
// Click to cancel the callback
onCancel: (a)= > {
console.log('JSDialog default cancel'), this.close()
},
footer: {
ok: btnBuilder({
text: 'sure'.type: 'primary'.order: 0
}),
cancel: btnBuilder({
text: 'cancel'.order: 1}}})// The parameters are merged into this.options
merge(this.options, defaultOptions, originOptions)
const footer = this.options.footer
Object.entries(footer).forEach(([key, btnOptions]) = > {
// Determine and cancel the default buttons
if (['ok'.'cancel'].includes(key)) {
const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel
// Default button callback priority: footer configured clickFn > options configured onOK and onCancel
btnOptions.clickFn = btnOptions.clickFn || clickFn
} else { // Add a button
// Complete the configuration
footer[key] = btnBuilder(btnOptions)
}
})
}
Copy the code
3. The render function
Vue recommends using templates to create your HTML in most cases. However, in some scenarios, you really need the full programming power of JavaScript. In this case you can use render functions, which are closer to the compiler than templates. The official documentation of rendering function writing method, parameters, corresponding JSX writing method has been very detailed, here will not repeat. The following code is run on the latest VUE-CLI create project, trying to write both JS parameter create elements and JSX create elements.
render(createElement) {
// Frame body
const inner = createElement(options.component, {
props: options.props, // Pass parameters
ref: 'inner' / / reference
})
// Control button display hidden
const showBtns = Object.values(this.footer).filter(btn= >! btn.isHide)// Control button sequence
const sortBtns = orderBy(showBtns, ['order'], ['desc'])
// Bottom button JSX notation
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 configuration item: {... options.dialogOpts, visible: this.visible }, on: { closed: this.clearVm }, ref: [inner, footer]) return elDialog}Copy the code
4. Packaging API
For the time being, there are only three APIS, which can be extended according to different scenarios, such as undestroyed hidden frame, reload frame, etc.
show()
, the dialog box is displayed
The main display is to change the el-Dialog visible to True to control the display of the popbox mounted to the body.
show() {
this.vm.show()
}
Copy the code
close()
, the dialog box closes
Change el-Dialog visible to false; Trigger the Closed event for the El-Dialog; Perform clearVm; Execute $destroy() for the VM; The destroyed() callback removes $el from the body.
close() {
this.vm.close()
}
Copy the code
getInner()
, get the frame body instance, can be used to access the method on the instance, control button process
getInner() {
return this.vm.$refs.inner
}
Copy the code
Four, how to use
1. In the simplest scenario, configure only the page
The button event callback uses the default callback, and the OK and cancel buttons close the popbox
import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
component: dialogBody,
})
dialog.show() // The box is displayed
Copy the code
The effect is as follows:
2. Control the frame style and determining process
The configuration items supported by el-Dialog can be customized, as shown in the Dialog box. For example, title and customClass. CustomClass allows you to control the style of the bullet frame in the project. Control ok cancel button code callback.
import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
component: dialogBody,
dialogOpts: {
title: 'Pretty boy, pretty girl. Oh, hey.'.customClass:'js-dialog'
},
props: {
defaultName: 'Arguments passed by JSDialog'
},
onOK() {
const inner = dialog.getInner() // Get a dialogBody reference
// Control process
if (inner.apiMethods.isCanSave()) {
// Get the saved data
const postData = inner.apiMethods.getSaveData()
console.log('>>>>> postData >>>>>', postData)
// Close the dialog box
dialog.close()
}
},
onCancel() {
dialog.close() // The popbox closes}})Copy the code
The effect is as follows:
3. Customize footer
Custom buttons control the execution of callbacks, style, order, show and hide
import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
component: dialogBody,
footer: {
ok: { // Change the default button
text: 'new'
},
cancel: { // Hide the default button
isHide: true
},
add: { // Add a button
text: 'Save as',
clickFn() {
dialog.close()
},
order: - 1 // Control the button sequence, order small display on the right
},
add2: {
text: 'Add button 2',
clickFn() {
dialog.close()
},
order: 3
}
}
})
dialog.show() // The box is displayed
Copy the code
The effect is as follows: