Search K
Appearance
做前端开发的同学都知道,在 Vue 里,所有东西都是组件,可以说组件是在平时开发工作中,与我们打交道最多的东西。
而组件设计,是前端开发中一个非常重要的概念,也是前端开发中一个非常重要的技能。
但是,你真的对组件很熟悉了吗?你设计的组件真的好用吗?
我们下面以一个全局弹窗组件作为例子,去聊聊命令式组件设计与开发中的技巧。
在传统的 Vue 组件开发中,对于一个全局弹窗组件,我们通常会使用单文件组件:
<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>
这无可厚非,我相信这是大部分前端开发者的选择,而且多年的开发经验告诉我们,确实这种组件开发方式是最常用的。
但是,让我们来回忆一下,浏览器自带的弹窗是怎样调用的?
// 就一句代码
alert('这是一条弹窗消息!')
// 就一句代码
alert('这是一条弹窗消息!')
而我们自己开发的弹窗组件是怎样调用的?
// 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>
这对于组件的使用者而言,使用成本无疑是非常高的,但我只是想弹窗提示一下信息而已,我并不需要知道弹窗组件内部是如何实现的,我希望只调用一个方法,然后弹窗就出现了,这对于我来说,能大大降低我使用的心智负担。
没错,这就是所谓的命令式组件
,也就是通过一句代码命令,我就能使用这个组件。
我们站在使用者的角度思考一下,怎样使用弹窗组件是最方便的?
// 假设myAlert是自定义的弹窗函数
myAlert('这是一条弹窗消息!')
// 假设myAlert是自定义的弹窗函数
myAlert('这是一条弹窗消息!')
这样就是最简便的使用方式了,跟浏览器自带的alert
一样。当然,如果你还想在点击关闭按钮的时候,做一些额外的事情,那么可能需要一个回调函数作为参数传给myAlert
,像这样:
myAlert('这是一条弹窗消息!', () => {
console.log('做些额外的操作')
})
myAlert('这是一条弹窗消息!', () => {
console.log('做些额外的操作')
})
所以不难想象,我需要的无非是封装一个myAlert
函数,该函数接收两个参数,第一个参数是弹窗的消息内容,第二个参数是点击关闭按钮后的回调函数。思路清晰,开干!
// 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 这个文件,看看它是在干嘛?
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
写成这样:
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函数,这样代码看起来就比较直观更容易维护了。