关于element-ui:elementui源码学习之仿写一个elmessage

3次阅读

共计 6978 个字符,预计需要花费 18 分钟才能阅读完成。

问题形容

工作中尽管应用工具库很高兴很高效,但咱们还是要抽空看看工具库的源码,因为源码中会用到一些不常常应用的 api 办法,记住这些 api 办法,可晋升本人的编程能力,有助于当前封装本人的工具库,从而更好的实现一些需要。

需要剖析

组件封装之前,咱们要想一下要封装的这个组件的利用场景和应用需要有哪些,以此为突破口,便于更好的实现代码逻辑

利用场景和需要:音讯提醒

愚认为,message 次要是信息提醒,利用场景在于用户执行了一些操作,是否胜利或失败之类的交互反馈。所以,咱们能够定义这个要封装的组件有以下需要:

  • 须要能够输出信息文字 message 参数
  • 须要 message 信息的类型反馈(胜利反馈、正告反馈、谬误反馈、一般信息反馈)type 参数
  • 须要提醒完当前,能够设定默认隐没工夫 duration 参数
  • 当鼠标悬浮的时候保留这个音讯提醒,不让其隐没 定时器 timer 参数
  • 其余诸如 提醒小图标的类型和文字是否居中 之类的

如果咱们看饿了么 UI 官网的组件,咱们会发现官网思考的还是非常具体的,给到了很多配置项Options 参数,不过在咱们本人理论封装组件中,不须要像官网那样,做很多的配置项,只须要实现罕用的配置项即可,保留最实用的性能即可。

饿了么 UI 官网 el-message 组件:https://element.eleme.cn/#/zh…

效果图

了解形式

对于这个性能成果,集体倡议能够如下了解

  • 先温习一下平时用的不多的 api
  • 再把代码 clone 下来,跑起来(文末附上 github 代码仓库地址)
  • 联合正文,既可疾速了解了

知识点温习之:class 的数组用法和:style 用法

// html
<div
  :class="['messageBox',   /* .messageBox 这个类名确定要加到 div 这个标签上 */ 
    center ? 'horizontal' : '',   /* 是否给 div 标签加上.horizontal 这个类名取决于 center 这个变量的值是否为 true */
    typeArr.includes(type) ? type : '',   /* 是否给 div 这个标签加上 type 变量值的类名,取决于 typeArr 变量数组是否蕴含 type 的值 */
  ]":style="controlTop"/* 等价于:style={top: `12px`} 等价于 style="top: 12px" 即间隔顶部 top 值为 12 像素 */
>
</div>

// js
data() {
    return {
      center: false, // 是否让程度文字居中,默认 false
      type: "info", // 默认 info 类型
      typeArr: ["info", "success", "warning", "error"], // 总共 4 种类型
    };
},
computed: {controlTop() {return { top: `12px`};
    },
  },

为什么要提到:class 的数组用法以及:style 的用法呢?

因为在本例中,给 message 绑定四种类型(胜利、正告、谬误、信息)的款式,就须要应用到;

用户屡次点击触发 message 的呈现,管制下一个 message 的地位在上一个的下方,就须要让一直的更改下一个 message 的 top 值;

知识点温习之 transition 过渡钩子函数

// html
<transition 
    v-on:before-enter="beforeEnter" /* 过渡呈现进入之前 */ 
    v-on:enter="enter" /* 过渡呈现进入 */ 
    v-on:after-enter="afterEnter" /* 过渡呈现进入之后 */ 
    v-on:enter-cancelled="enterCancelled" /* 勾销进入过渡 */
    v-on:before-leave="beforeLeave" /* 过渡隐没来到之前 */
    v-on:leave="leave"  /* 过渡隐没来到 */
    v-on:after-leave="afterLeave" /* 过渡隐没来到之后 */
    v-on:leave-cancelled="leaveCancelled"  /* 勾销过渡隐没来到 */
>
    <!-- ... --> 
</transition>

// js
methods: {
    // ...
    afterLeave(){
        /* 本例中应用了这个钩子,当过渡隐没的时候会触发这个钩子函数,咱们能够在钩子函数中写一些 js 逻辑代码,进行相应操作 */ 
    }
    // ...

}

那么,什么时候,过渡隐没,什么时候过渡呈现?

最显著的就是,v-show 由 true 改为 false、由 false 改为 true 的时候,会主动触发 transition 的过渡钩子函数执行

本例中应用过渡钩子函数次要是因为,当隐没一个 message 的时候,须要缩小一个 message 的计数,所以须要通过这个钩子去进行 js 逻辑代码曹组欧

过渡钩子详见官网文档:https://cn.vuejs.org/v2/guide…

知识点温习之 vue 销毁组件的形式

  1. 应用 v-if 官网举荐最好的形式
  2. 应用 key 个别用的不是特地多
  3. this.$destroy(true) vue 的 1.x 版本经常应用,从 2.x 版本就不反对了,相当于兼容写法
  4. 本人手动移除 this.$el.parentNode.removeChild(this.$el); 本例应用之
    对于第 3 点和第 4 点,尤大佬还亲自答复了对于这个问题的 issues。简略截图如下:

issues 地址如下:https://github.com/vuejs/vue/…

为什么提到这个销毁 dom 形式呢?

因为咱们应用 v -show 加上去 transition 管制 message 的暗藏和隐没的,这个成果丝滑一些,没有应用 v -if 间接干掉 dom。所以须要手动写代码,在过渡隐没当前,当咱们看不到 message 的时候,再偷偷的给 message 移除掉即可

残缺代码

整体代码思路

  1. 搞一个 message 组件用于继承
  2. 应用 Vue.extend 继承这个组件造成一个结构器
  3. 定义一个函数,函数一执行,就应用结构器创立一个 message 显示,默认 3 秒主动隐没
  4. 把这个函数挂载在原型上,并裸露进来,不便拜访应用

对于 Vue.extend 继承不太熟悉的,能够先看看笔者的另外两篇文章哦

继承 … https://segmentfault.com/a/11…

继承 … https://segmentfault.com/a/11…

应用的.vue 文件代码

// html
<button @click="showMessage1"> 信息弹出 </button>
<button @click="showMessage2"> 胜利弹出 </button>
<button @click="showMessage3"> 正告弹出 </button>
<button @click="showMessage4"> 谬误弹出 </button>
<button @click="showMessage5"> 弹出 5 秒敞开 </button>
<button @click="showMessage6"> 文字居中哦 </button>
<button @click="showMessage7"> 引入应用 </button>

// 一种是原型链应用形式,另一种是引入应用形式
import MyMessage from "@/components/index.js";
methods: {showMessage1() {
  this.$myMessage({
    message: "信息弹出",
    type: "info",
  });
},
showMessage2() {
  this.$myMessage({
    message: "胜利弹出",
    type: "success",
  });
},
showMessage3() {
  this.$myMessage({
    message: "正告弹出",
    type: "warning",
  });
},
showMessage4() {
  this.$myMessage({
    message: "谬误弹出",
    type: "error",
  });
},
showMessage5() {
  this.$myMessage({message: "弹出 5 秒敞开",});
},
showMessage6() {
  this.$myMessage({
    message: "文字居中哦",
    center: true,
  });
},
showMessage7() {
  MyMessage({
    message: "引入应用",
    type: "success",
  });
},
},

通过继承挂载原型上便于动态创建文件代码

import Vue from 'vue';
import messageComponent from './src/index.vue' // 引入组件,不便继承
let MessageConstructor = Vue.extend(messageComponent); // 引入一个 message 结构器,不便 new 之

let instance = null // 定义组件实例
let count = 0 // 定义统计次数,便于晓得创立多少个实例

const MyMessage = function (options) {if (options.duration & typeof options.duration !== 'number') { // 对于 duration 数字类型的校验
        console.error('Error! duration Must be a numeric type') // 用户乱传递非数字类型参数,就抛错不执行后续代码
        return
    }
    count = count + 1 // MyMessage 函数调用一次,统计次数加一个
    instance = new MessageConstructor({ // 实例化一个组件实例
        data: options, // data 传参数,组件的 data 接管(即传递配置项)propsData: { // propsData 传参,count: count, // 将统计的次数传递给子组件
            cutCount: cutCount // 传递一个函数,当 MyMessage 隐没的时候,告诉外界
        },
    });
    instance.$mount(); // 实例组件挂载
    document.body.appendChild(instance.$el); // 把这个组件实例的 dom 元素,追加到 document 文档中
    instance.isShowMyMessage = true; // 将组件的 isShowMyMessage 属性值置为 true,即让实例呈现,即音讯呈现
    return instance; // MyMessage 函数执行一次,就会返回一个加工好的实例对象
}

function cutCount() { // 当 message 隐没一个
    count = count - 1 // 就把外界统计的数量缩小一个
    let messageBoxDomList = document.querySelectorAll('.messageBox') // 而后选中所有的 messageDOM 元素
    for (let i = 0; i < messageBoxDomList.length; i++) { // 遍历一下这个 DOM 伪数组
        let dom = messageBoxDomList[i] // 所有的都往上挪动 60 像素
        dom.style['top'] = parseInt(dom.style['top']) - 60 + 'px'
    }
}

export default MyMessage // 裸露进来
Vue.prototype.$myMessage = MyMessage; // 挂载在 vue 原型上,不便 this.$myMessage 调用

用于继承的 message 组件代码

<template>
  <transition name="message-fade" @after-leave="handleAfterLeave">
    <div
      :class="['messageBox',
        center ? 'horizontal' : '',
        typeArr.includes(type) ? type : '',
      ]":style="controlTop"v-show="isShowMyMessage"@mouseenter="clearTimerFn"@mouseleave="startTimerFn"
    >
      <span> {{iconObj[type] }} {{message}}</span>
    </div>
  </transition>
</template>

<script>
export default {
  name: "myMessage",
  props: {
    count: {
      // 统计次数
      type: Number,
      default: 1,
    },
    cutCount: {
      // dom 隐没告诉外界函数
      type: Function,
    },
  },
  data() {
    return {
      isShowMyMessage: false, // v-show 的标识布尔值
      message: "", // 提醒的音讯文字
      timer: null, // 用来革除的定时器
      duration: 3000, // 默认 3 秒隐没
      center: false, // 是否让程度文字居中,默认 false
      type: "info", // 默认 info 类型
      typeArr: ["info", "success", "warning", "error"], // 总共 4 种类型
      iconObj: {
        // 这里的对应图标,就以 红桃、黑桃、方块、梅花 为例吧
        info: "♥",
        success: "♠",
        warning: "♦",
        error: "♣",
      },
    };
  },
  computed: {controlTop() {
      return {
        // 间隔顶部的地位,取决于创立了几个 message
        top: `${12 + (this.count - 1) * 60}px`,
      };
    },
  },
  mounted() {this.startTimerFn(); // 开启定时器,默认 3 秒后销毁组件
  },
  methods: {
    // 开始定时器计时,要销毁 dom 元素
    startTimerFn() {
      // 工夫大于 0,才做计时隐没暗藏
      if (this.duration > 0) {this.timer = setTimeout(() => {this.close(); // 达到计时工夫,就暗藏这个 notice
        }, this.duration);
      }
    },
    // 鼠标移入,革除定时器,使 dom 永远存在;鼠标移出,再从新计时筹备移除 dom
    clearTimerFn() {clearTimeout(this.timer);
    },
    // 过渡动画隐没时,会执行此钩子函数,销毁组件,同时移除 dom
    handleAfterLeave() {
      // 在移除一个 dom 之前,要先告诉外界的计数 count 减去一个,并让余下的所有 dom 都往上挪动,即更改地位
      this.cutCount();
      // 而后移除 dom
      this.$destroy(true);
      this.$el.parentNode.removeChild(this.$el);
    },
    // 敞开暗藏 dom
    close() {
      this.isShowMyMessage = false;
      /**
       * 留神当 v -show 为 false 的时候,会触发过渡动画隐没钩子 handleAfterLeave 函数执行
       * 相当于在 close 函数中,执行了 this.handleAfterLeave()
       * */
    },
  },
};
</script>

<style lang="less" scoped>
// 默认款式
.messageBox {
  min-width: 320px;
  height: auto; // 高度由内容撑开
  padding: 16px; // 加上内边距
  border: 1px solid #e9e9e9;
  position: fixed; // 应用固定定位,使地位凑近顶部并居中
  top: 20px;
  left: 50%;
  transform: translateX(-50%); // 管制居中
  box-sizing: border-box;
  border-radius: 4px; // 加圆角难看一些
  background-color: #edf2fc;
  // 过渡成果
  transition: opacity 0.3s, transform 0.4s, top 0.4s;
  display: flex; // 开启弹性盒垂直居中
  align-items: center;
}
// 文字居中款式
.horizontal {justify-content: center;}
// 胜利提醒款式
.success {
  color: #67c23a;
  background-color: #f0f9eb;
}
// 正告提醒款式
.warning {
  color: #e6a23c;
  background-color: #fdf6ec;
}
// 谬误提醒款式
.error {
  color: #f56c6c;
  background-color: #fef0f0;
}
// 过渡成果款式
.message-fade-enter,
.message-fade-leave-active {
  opacity: 0;
  -webkit-transform: translate(-50%, -100%);
  transform: translate(-50%, -100%);
}
</style>

github 仓库代码地址

elementui 源码学习仿写组件,筹备工作不忙的时候,写一个系列,我会尽可能多写点正文哦。与大家共同进步成长 ^_^

github 地址:https://github.com/shuirongsh…

正文完
 0