Skip to content

命令式组件设计

组件的重要性

做前端开发的同学都知道,在 Vue 里,所有东西都是组件,可以说组件是在平时开发工作中,与我们打交道最多的东西。

而组件设计,是前端开发中一个非常重要的概念,也是前端开发中一个非常重要的技能。

但是,你真的对组件很熟悉了吗?你设计的组件真的好用吗?

我们下面以一个全局弹窗组件作为例子,去聊聊命令式组件设计与开发中的技巧。

单文件组件

在传统的 Vue 组件开发中,对于一个全局弹窗组件,我们通常会使用单文件组件:

html
<template>
    <div v-if="show" class="alert">
        <div class="alert-wrapper">
            <div class="alert-header">
                <h4>{{ title }}</h4>
            </div>
            <div class="alert-body">
                <span>{{ msg }}</span>
            </div>
            <div class="alert-footer">
                <button @click="handleClose">关闭</button>
            </div>
        </div>
    </div>
</template>
<template>
    <div v-if="show" class="alert">
        <div class="alert-wrapper">
            <div class="alert-header">
                <h4>{{ title }}</h4>
            </div>
            <div class="alert-body">
                <span>{{ msg }}</span>
            </div>
            <div class="alert-footer">
                <button @click="handleClose">关闭</button>
            </div>
        </div>
    </div>
</template>

这无可厚非,我相信这是大部分前端开发者的选择,而且多年的开发经验告诉我们,确实这种组件开发方式是最常用的。

但是,让我们来回忆一下,浏览器自带的弹窗是怎样调用的?

javascript
// 就一句代码
alert('这是一条弹窗消息!')
// 就一句代码
alert('这是一条弹窗消息!')

而我们自己开发的弹窗组件是怎样调用的?

javascript
// 1. 引入组件
import Alert from '../../components/alert.vue'
// 2. 定义弹窗显示与否的变量
const show = ref(false)
// 3. template使用组件
<Alert v-model="show" msg="这是一条弹窗消息!" />
// 4. 在需要弹窗显示的时候设置show为true
<button class="alert-btn" @click="show = true">弹窗</button>
// 1. 引入组件
import Alert from '../../components/alert.vue'
// 2. 定义弹窗显示与否的变量
const show = ref(false)
// 3. template使用组件
<Alert v-model="show" msg="这是一条弹窗消息!" />
// 4. 在需要弹窗显示的时候设置show为true
<button class="alert-btn" @click="show = true">弹窗</button>

这对于组件的使用者而言,使用成本无疑是非常高的,但我只是想弹窗提示一下信息而已,我并不需要知道弹窗组件内部是如何实现的,我希望只调用一个方法,然后弹窗就出现了,这对于我来说,能大大降低我使用的心智负担。

没错,这就是所谓的命令式组件,也就是通过一句代码命令,我就能使用这个组件。

命令式组件

我们站在使用者的角度思考一下,怎样使用弹窗组件是最方便的?

javascript
// 假设myAlert是自定义的弹窗函数
myAlert('这是一条弹窗消息!')
// 假设myAlert是自定义的弹窗函数
myAlert('这是一条弹窗消息!')

这样就是最简便的使用方式了,跟浏览器自带的alert一样。当然,如果你还想在点击关闭按钮的时候,做一些额外的事情,那么可能需要一个回调函数作为参数传给myAlert,像这样:

javascript
myAlert('这是一条弹窗消息!', () => {
    console.log('做些额外的操作')
})
myAlert('这是一条弹窗消息!', () => {
    console.log('做些额外的操作')
})

所以不难想象,我需要的无非是封装一个myAlert函数,该函数接收两个参数,第一个参数是弹窗的消息内容,第二个参数是点击关闭按钮后的回调函数。思路清晰,开干!

javascript
// my-alert.js
export const myAlert = (msg, closeCallback) => {
    
}
// my-alert.js
export const myAlert = (msg, closeCallback) => {
    
}

这样就定义好了myAlert函数了,接下来,我们思考一下,如果把我们前面定义好的alert.vue组件引入到my-alert.js里,然后通过函数调用把它显示出来是不是就可以了?

可能你会说,怎么可能,这是个js文件,怎么能在js文件里引入单文件组件呢?

问这句话说明你没有理解 vue 是如何渲染的,其实这事你做过,只是你没留意而已。不信你看看你的 vue 工程,打开 main.js 这个文件,看看它是在干嘛?

javascript
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

这里main.js不就是js文件吗?App.vue不就是单文件组件吗?不正是在js文件里面引入单文件组件吗?

所以,我们完全可以把my-alert.js写成这样:

javascript
import { createApp } from 'vue'
import Alert from './alert.vue'

export const myAlert = (msg, closeCallback) => {
    const div = document.createElement('div')
    document.body.appendChild(div)
    const app = createApp(Alert, {
        msg,
        modelValue: true,
        onClose() {
            closeCallback && closeCallback()
            app.unmount()
            div.remove()
        }
    })
    app.mount(div)
}
import { createApp } from 'vue'
import Alert from './alert.vue'

export const myAlert = (msg, closeCallback) => {
    const div = document.createElement('div')
    document.body.appendChild(div)
    const app = createApp(Alert, {
        msg,
        modelValue: true,
        onClose() {
            closeCallback && closeCallback()
            app.unmount()
            div.remove()
        }
    })
    app.mount(div)
}

以上代码,我们创建了一个div,挂在body后面,然后通过createApp创建了一个vue实例,把Alert组件挂载到div上。

注意,这里我们通过把modelValue属性设置为true,这样就相当于把Alert组件显示出来了。因为Alert组件点击关闭按钮会触发一个close事件,所以我们把onClose事件也传过去,当点击关闭按钮的时候,执行closeCallback回调函数,然后卸载组件。

一切都很巧妙,通过这样,我们就可以直接引入my-alert.js然后直接调用函数来显示弹窗消息了。但是因为从来没有人注意过vue应用是怎么挂在到dom上的,没人注意过main.js文件是在做什么,所以想不到可以这样实现。

提升

但是这样满足不了软件设计的高内聚思想,我们还可以把alert.vue文件写进my-alert.js文件里。

怎么做?你应该知道,vue单文件组件的template其实是render函数的语法糖,既然是语法糖,自然可以换成render函数在js文件里实现,不需要依赖单文件组件。至于css的话,也可以通过css-in-js技术,把css写进js文件里。

当然了,render函数后期不好维护,毕竟它阅读起来没有那么直观,所以我们还可以利用jsx来替代render函数,这样代码看起来就比较直观更容易维护了。

上次更新于: