命令式组件Message、Dialog的主流写法分析

栏目: IT技术 · 发布时间: 4年前

内容简介:这里还是以element-ui为例,那我们就看看里面的Message。它的dom结构什么的就写在node-modules/element-ui/packages/notification/src/main.vue里面可以看出,这里东西不多,只有一些对应的class、关闭按钮外,就剩下title和message了,这里看到用了slot,所以我们通常也只能将自己的自定义内容放入此插槽中。

这里还是以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="[ typeClass, iconClass ]"
        v-if="type || iconClass">
      </i>
      <div :class="{ 'is-with-icon': typeClass || iconClass }">
        <h2 v-text="title"></h2>
        <div v-show="message">
          <slot>
            <p v-if="!dangerouslyUseHTMLString">{{ message }}</p>
            <p v-else v-html="message"></p>
          </slot>
        </div>
        <div
         
          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等。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

BLOG启示录

BLOG启示录

胡嘉玺 / 清华大学出版社 / 2010-7 / 48.00元

《BLOG启示录:WordPress博客建设与经营》分三大部分,共17章。第一部分(1~5章)主要是介绍Web 2.0、博客,以及LAMP的安装和架设,更有完整的主机、域名、DNS、WordPress安装、申请、交费等步骤,读者即使对计算机、主机、网络、域名等知识完全不懂,也可以依照《BLOG启示录:WordPress博客建设与经营》的步骤来落实整个WordPress基础建设的架构及安装。 ......一起来看看 《BLOG启示录》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具