1

Problem Description

Although it is very happy and efficient to use the tool library at work, we still have to take time to look at the source code of the tool library, because some api methods that are not often used will be used in the source code. Remembering these api methods can improve your programming ability. It is helpful to encapsulate your own tool library in the future, so as to better realize some requirements.

demand analysis

Before component encapsulation, we need to think about the application scenarios and usage requirements of the component to be encapsulated, and use this as a breakthrough to better implement code logic.

Application scenarios and requirements: message prompts

I think that the message is mainly an information prompt, and the application scenario is the interactive feedback such as whether the user has performed some operations, whether it succeeded or failed. So, we can define that the component to be encapsulated has the following requirements:

  • Need to be able to enter the information text message parameter
  • The type of feedback that requires message information (successful feedback, warning feedback, error feedback, normal information feedback) type parameter
  • After you need to prompt, you can set the default disappearance time duration parameter
  • When the mouse is hovering, keep this message prompt, do not let it disappear timer timer parameter
  • Others such as prompting the type of small icon and whether the text is centered

If we look at the official components of the Ele.me UI, we will find that the official consideration is still very detailed, and a lot of configuration items Options parameters are given, but in our own actual package components, we do not need to do a lot of configuration items like the official ones. , you only need to implement the commonly used configuration items, and keep the most practical functions.

Eleme UI official el-message component: https://element.eleme.cn/#/zh-CN/component/message

renderings

way of understanding

Regarding the effect of this function, personal suggestions can be understood as follows

  • First review the APIs that are not often used
  • Then clone the code and run it (the github code repository address is attached at the end of the article)
  • Combined with comments, you can quickly understand

Knowledge point review: class array usage and :style usage

 // 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` };
    },
  },

Why mention the array usage of :class and the usage of :style?

Because in this example, to bind four types of styles (success, warning, error, information) to message, you need to use;

When the user clicks multiple times to trigger the appearance of the message, and controls the position of the next message to be below the previous one, it is necessary to continuously change the top value of the next message;

The transition transition hook function for knowledge point review

 // 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逻辑代码,进行相应操作 */ 
    }
    // ...

}

So, when does the transition disappear and when does the transition appear?

Most obviously, v-show由true改为false、由false改为true的时候,会自动触发transition的过渡钩子函数执行

In this example, the transition hook function is used mainly because when a message disappears, the count of a message needs to be reduced, so it is necessary to use this hook to carry out the js logic code.

See the official documentation for transition hooks: https://cn.vuejs.org/v2/guide/transitions.html

The way of vue destroying components of knowledge point review

  1. Use v-if the best way officially recommended
  2. Using key is generally not used much
  3. this.$destroy(true) The 1.x version of vue is often used, but it is not supported since the 2.x version, which is equivalent to compatible writing
  4. Manually remove it yourself this.$el.parentNode.removeChild(this.$el); This example uses points 3 and 4. You also personally answered the issues about this issue. A simple screenshot is as follows:

The issues address is as follows: https://github.com/vuejs/vue/issues/3534

Why do you mention this way of destroying the DOM?

Because we use v-show and go to transition to control the hiding and disappearing of the message, the effect is smoother, and the dom is directly killed without v-if. So we need to write the code manually. After the transition disappears, when we can't see the message, we can secretly remove the message.

full code

Overall code idea

  1. Make a message component for inheritance
  2. Use Vue.extend to extend this component to form a constructor
  3. Define a function. Once the function is executed, use the constructor to create a message display, which will automatically disappear in 3 seconds by default.
  4. Mount this function on the prototype and expose it for easy access and use

If you are not familiar with Vue.extend inheritance, you can read my other two articles first.

Inherited... https://segmentfault.com/a/1190000040848258

Inherited... https://segmentfault.com/a/1190000041974425

.vue file code used

 // 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",
  });
},
},

Easy to dynamically create file code via inheritance mounted on prototype

 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 component code for inheritance

 <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 repository code address

The elementui source code learns to imitate the components. When the preparations are not busy, write a series, and I will write as many comments as possible. Make progress and grow together with everyone ^_^

github address: https://github.com/shuirongshuifu/elementSrcCodeStudy

水冗水孚
1.1k 声望584 粉丝

每一个不曾起舞的日子,都是对生命的辜负