命令式组件Message、Dialog的主流写法分析
命令式组件Message、Dialog的主流写法分析
这里还是以element-ui为例,那我们就看看里面的Message。 它的dom结构什么的就写在node-modules/element-ui/packages/notification/src/main.vue里面
<template>
<transition name="el-notification-fade">
<div
:class="['el-notification', customClass, horizontalClass]"
v-show="visible"
:style="positionStyle"
@mouseenter="clearTimer()"
@mouseleave="startTimer()"
@click="click"
role="alert"
>
<i
class="el-notification__icon"
:class="[ typeClass, iconClass ]"
v-if="type || iconClass">
</i>
<div class="el-notification__group" :class="{ 'is-with-icon': typeClass || iconClass }">
<h2 class="el-notification__title" v-text="title"></h2>
<div class="el-notification__content" v-show="message">
<slot>
<p v-if="!dangerouslyUseHTMLString">{{ message }}</p>
<p v-else v-html="message"></p>
</slot>
</div>
<div
class="el-notification__closeBtn el-icon-close"
v-if="showClose"
@click.stop="close"></div>
</div>
</div>
</transition>
</template>
可以看出,这里东西不多,只有一些对应的class、关闭按钮外,就剩下title和message了,这里看到用了slot,所以我们通常也只能将自己的自定义内容放入此插槽中。 js方便就是支持大家常用的点击、关闭回调,里面维护了一个计时器来控制关闭。
我们能将它作为命令式只用的主要实现逻辑是在node-modules/element-ui/packages/notification/src/main.js里面。
这里的核心是Notification方法,这个包导出的也是它。
我们先看大家常用的this.$message.success
方法是什么
['success', 'warning', 'info', 'error'].forEach(type => {
Notification[type] = options => {
if (typeof options === 'string' || isVNode(options)) {
options = {
message: options
};
}
options.type = type;
return Notification(options);
};
});
通过这里我们可以得知,原来平时使用的this.$message.success
其实就是Notification.success
,本质就是一个预先设定了type
后再调用了Notification
方法。
let instance;
let instances = [];
let seed = 1;
const Notification = function(options) {
if (Vue.prototype.$isServer) return;
options = merge({}, options);
const userOnClose = options.onClose;
const id = 'notification_' + seed++;
const position = options.position || 'top-right';
options.onClose = function() {
Notification.close(id, userOnClose);
};
instance = new NotificationConstructor({
data: options
});
if (isVNode(options.message)) {
instance.$slots.default = [options.message];
options.message = 'REPLACED_BY_VNODE';
}
instance.id = id;
instance.$mount();
document.body.appendChild(instance.$el);
instance.visible = true;
instance.dom = instance.$el;
instance.dom.style.zIndex = PopupManager.nextZIndex();
let verticalOffset = options.offset || 0;
instances.filter(item => item.position === position).forEach(item => {
verticalOffset += item.$el.offsetHeight + 16;
});
verticalOffset += 16;
instance.verticalOffset = verticalOffset;
instances.push(instance);
return instance;
};
上面的NotificationConstructor
就是
import Main from './main.vue';
const NotificationConstructor = Vue.extend(Main);
使用Vue构造函数生成组件实例instance
,这和我们默认用vue-cli生成vue项目的main.js是不是很像。
import App from './app.vue';
new Vue({
render: h => h(App),
router,
store: new Vuex.Store(store),
}).$mount("#app");
唯一的区别是我们在mounted
的时候没有传参数,这意味着我们需要自己将生成的dom插入目标位置,这也更符合我们的预期。
instances
数组是用来存储多个组件实例的,在垂直方向如果有多个组件实例的话,会给它们直接加入16px的偏移量避免它们重叠在一起。
这里还给每个实例更新了verticalOffset
属性的值来记录垂直方向的偏移量,方便positionStyle
变更,更新组件的位置。
我们同样可以看到,在移除组件的时候,需要同步变更instances
数组里面该instance
后面的实例对应的style样式。
Notification.close = function(id, userOnClose) {
let index = -1;
const len = instances.length;
const instance = instances.filter((instance, i) => {
if (instance.id === id) {
index = i;
return true;
}
return false;
})[0];
if (!instance) return;
if (typeof userOnClose === 'function') {
userOnClose(instance);
}
instances.splice(index, 1);
if (len <= 1) return;
const position = instance.position;
const removedHeight = instance.dom.offsetHeight;
for (let i = index; i < len - 1 ; i++) {
if (instances[i].position === position) {
instances[i].dom.style[instance.verticalProperty] =
parseInt(instances[i].dom.style[instance.verticalProperty], 10) - removedHeight - 16 + 'px';
}
}
};
顺便提一下,PopupManager
是element-ui里面公共的通过z-index来确保最新创建的dom永远保持在最上方不被旧的元素遮挡的机制,实现原理也很简单。
鉴于message传参的options.message支持传入vNode,所以会有针对vNode相关的判断和处理逻辑。
简单总结一下,使用Vue构造函数这种方法是常用的命令式组件的套路,类似的还有Dialog(有的组件库叫Modal)、Notice等。
- 分类:
- Web前端
相关文章
用个数组来理解vue的diff算法(一)
原文地址: 道招网 的 用个数组来理解vue的diff算法(一) Vue使用的diff算法,我相信用vue的估计都听过,并且看到源码的也不在少数。 先对下面的代码做下说明: 由于这里用 阅读更多…
vue实现自定义组件的v-model双向数据绑定
一般来说我们用v-model是在input中 常见用法如下 <input type="text" v-model="username"> 用户名:{{username}} 其 阅读更多…
React router用hooks读取routeName、根据routeName跳转
在迁移Vue至React的过程中遇到了一些路由相关的问题,在Vue项目中经常会使用routeName,毕竟使用path太长了,也记不住,我自己看了看React router也没有发现routeNam 阅读更多…
element-ui表单源码解析之el-input
关于表单校验el-input做的主要工作就是跟el-form-item交互,把input的相关事件发送给el-form-item,上一篇已经讲到在el-form-item的mounted的生命周期里 阅读更多…
邮箱收件人组件(vue版)成长历程(一)
前期项目中需要优化原始的收件人、抄送、密送部分,换成更加现代化的样式和用户,当时将这部分抽象成一个组件了,最近的需求是发件人也要使用该组件,鉴于发件人比收件人等需要校验的地方和交互习惯变动点较多,进 阅读更多…
Vue同一路由跳转页面不刷新解决方案及注意事项之二
之前写过一个 《Vue同一路由跳转页面不刷新解决方案及注意事项》 ,在这篇文章里面鞋到了怎么解决这个问题,具体内容可以点击查看,这里简单说一下,就是利用将时间戳传给路由的query,也就导致 $ro 阅读更多…