There are a lot of UI component libraries available, but none of the Dialog components they provide (some libraries are called Model, and all of them are called Dialog in this article) are easy to use in actual business development. What’s going on?

Let’s start with a business scenario design.

First, business scenarios

There is now a popup registration function:

  • 1. Registration information form
  • 2, can control the closure
  • 3, can control the display
class Register {
    isShow = false
    show() {
        this.isShow = true
        this.render()
    }
    close() {
        this.isShow = false
        this.render()
    }
    render() {
        if(! this.isShow)return null
        console.log('display register form')}}Copy the code

Some time ago, the product came to a requirement, to do a pop-up login function:

  • 1. Login information form
  • 2, can control the closure
  • 3, can control the display

What to do now is roll up your sleeves when you need it: copy the Register class and create a Login class, then modify the render() logic. I’m just going to copy and paste whatever I want to add.

Two problems can be identified from the above requirement implementation:

  • 1. On page A, the registration function is A dialog interaction. Suppose there is now A page B where the registration function is always present directly as A page element. Problem: Dialog’s interaction logic is coupled to the registration business logic
  • 2, when you want to add a login function for the same interaction, select the code of the direct Copy registration function to modify. Problem: Code reuse problem

To solve the problem

1. Logic reuse

// The base Dialog class extracts the common logic class Dialog {isShow =false
    show() {
        this.isShow = true
        this.render()
    }
    close() {
        this.isShow = false
        this.render()
    }
    render() {
        throw new Error('Please render'}} // Register function; Class Register extends Dialog {render() {
        if(! this.isShow)return null
        console.log('display register form'}} class Login extends Dialog {render() {
        if(! this.isShow)return null
        console.log('display login form')}}Copy the code

2, the decoupling

// class Register {render() {
        console.log('display register form'}} // Class Login {render() {
        console.log('display login form'} // dialog dialog {isShow =false
    form = null
    show() {
        this.isShow = true
        this.render()
    }
    close() {
        this.isShow = false
        this.render()
    }
    render() {
        throw new Error('Please render'}} class RegisterDialog extends Dialog {constructor() {
        this.handle = new Register()
    }
    render() {
        if(! this.isShow)returnNull this.handle.render()}} class extends Dialog {constructor() {
        this.handle = new Login()
    }
    render() {
        if(! this.isShow)return null
        this.handle.render()
    }
}
Copy the code

When doing object-oriented development, I often hear less inheritance. Why? Poor scalability (the display is now controlled by isShow, and each subclass will have to change if a disabled condition is added later). In the above decoupling process, although dialog is decouple from the general registration function, the RegisterDialog class is strongly associated with Register, and a LoginDialog is re-created when the dialog logic of login is implemented, which leads to the problem of reuse. What can we do?

Inversion of control

// class Register {render() {
        console.log('display register form'}} // Class Login {render() {
        console.log('display login form'Constructor (handle) {this.isshow =false
        this.handle = handle
    }
    show() {
        this.isShow = true
        this.render()
    }
    close() {
        this.isShow = false
        this.render()
    }
    render() {
        if(! this.isShow)returnNull this.handle && this.handle.render()}} // Register dialog Registerdialog.show () const register= new register () register.render() const loginDialog = new Dialog(new Login()) logindialog.show () // Login page const Login = new Login() login.render()Copy the code

The above scenario analysis can summarize the design idea of Dialog, and more importantly, clarify the responsibilities of Dialog. Next comes the topic of this article, why the existing Dialog component design is BB.

Two, have what

The following two screenshots are taken from the official documentation demo of two of the better-known VUE component libraries

Screenshot 1

<dialog>
    <child />
</dialog>
Copy the code

It looks like it fits perfectly with what we’ve been working on

const register = new Register()
const registerDialog = new Dialog(register)
Copy the code

But…

Actual use in business code

Specific Service Scenarios

// contenxt.vue <tempalte> <div> <div>hello world! </div> <Dialog> <! -- Registration Form information --> <Form>... <Input /> ... </Form> </Dialog> </div> </template>Copy the code

Since the registration logic is independent and the above writing makes context.vue logic heavy, most students would actually use it as follows (separate registerDialog into a separate component)

// registerDialog.vue
<template>
    <Dialog>
        <Form>
            ....
            <Input />
            ...
        </Form>
    </Dialgo>
</template>

// context.vue
<template>
    <div>
        <div>hello world</div>
        <RegisterDialog />
    </div>
</template>
Copy the code

So the problem is that registerDialog makes the logic of the dialog and the logic of the registration very strongly coupled, when we want to implement the logic of login

// loginDialog.vue
<template>
    <Dialog>
        <Form>
            ....
            <Input />
            ...
        </Form>
    </Dialgo>
</template>

// context.vue
<template>
    <div>
        <div>hello world</div>
        <RegisterDialog />
        <LoginDialog />
    </div>
</template>
Copy the code

Each subsequent request must repeat the registerDialog, loginDialog logic, while handling the passing of vUE component properties and events. This widely used registerDialog approach is a step backwards in design. Why is that?

Third, the realization of the ideal

<template> <div> <div>hello world</div> <! Register --> <Dialog> <Register /> </Dialog> <! Login -- - > < Dialgo > < Login / > < / Dialog > < / div > < / template >Copy the code

In my opinion, this is the ideal implementation, why is it not in our code? Reason: None of the Dialog components currently support this approach

Register has operations of submitting form and resetting form, and the Dialog component has buttons of confirm and cancel. In actual business scenarios, the confirm operation of dialog should be associated with the submission of form, and the cancel operation of Dialog should be associated with the reset of form. However, the existing Dialog component does not implement this association

// Existing scheme class Register {submit() {
        console.log('Submit form')}reset() {
        console.log('Reset form')}render() {
        console.log('Render form')
    }
}
class Dialog {
    constructor(handle) {
        this.isShow = false
        this.handle = handle
    }
    show() {
        this.isShow = true
        this.render()
    }
    cancle() {
        this.isShow = false
        this.render()
    }
    ok() {
        this.isShow = false
        this.render()
    }
    render() {
        if(! this.isShow)returnnull this.handle && this.handle.render() } } const registerDialog = new Dialog(new Register()) // dialog Ok () // I want to submit the form and close the dialog, but I can'tCopy the code

This is one of the most vexing problems I have with the Dialog component at this stage. Is it impossible to solve this problem?

transformDialog

class Dialog {
    constructor(handle) {
        this.isShow = false
        this.handle = handle
    }
    show() {
        this.isShow = true
        this.render()
    }
    cancle() {
        this.isShow = falseReset this.handle && this.handle.reset() this.render()}ok() {
        this.isShow = falseSubmit this.handle && this.handle.submit() this.render()}render() {
        if(! this.isShow)returnnull this.handle && this.handle.render() } } const registerDialog = new Dialog(new Register() registerDialog.show() Registerdialog.ok () // Register's submit method is executedCopy the code

Vue implementation

Pseudo code, the actual design should be considered to accommodate a variety of scenarios

// Dialog.vue
<template>
    <div>
        ...
        <div class="body">
            <slot />
        </div>
        <div class="footer">
            <Button @click="handleCancle"</Button> < button@click ="handleOk"</Button> </div> </div> </template> <script>export default {
        ...
        mounted() {
            this.handle = this.$children.length > 0 ? this.$children[0] : null
        },
        methods: {
            handleCancle() {
                this.visible = false
                this.handle && this.handle.$emit('onReset')},handleOk() {
                this.visible = false
                this.handle && this.handle.$emit('onSubmit')
            }
        }
    }
</script>
Copy the code

Four, the last

At present, common UI frameworks, such as Element-UI of VUE system, iView of React system and Ant-design of React system, focus only on dialog interaction. From the perspective of business, I think they are defective in design. Of course, you can modify an existing Dialog component in your own project by selecting higher-order components.