阅读 676

Element组件源码研究-Message组件

本文的研究思路是通过阅读Element源码,然后自动动手一步一步编写组件,完善其对应功能。

准备工作

准备工作搞起来,先写一个测试页MessageShownPage

<template>
    <div>
        内容
        <el-message />
    </div>
</template>

<script>
import ElMessage from '../../components/Message/message.vue'

export default {
    name: 'MessageShownPage',
    components: {
        ElMessage
    }
}
</script>

<style>
</style>
复制代码

基本实现

接下来组件进行message最简单的实现:

<template>
     <transition name="el-message-fade">
         <div
            :class="[
                'el-message',
                type ? `el-message--${ type }` : '',
            ]"
            :style="positionStyle"
            v-show="visible"
        >
            <i :class="typeClass"></i>
            <slot>
                <p class="el-message__content">{{ message }}</p>
            </slot>
        </div>
     </transition>
</template>

<script>
const typeMap = {
    success: 'success',
    info: 'info',
    warning: 'warning',
    error: 'error'
};

export default {
    data() {
      return {
          visible: true,
          message: '这是一条消息提示',
          type: 'info',
          verticalOffset: 20,
      };
    },
    computed: {
        typeClass() {
            return this.type
            ? `el-message__icon el-icon-${ typeMap[this.type] }`
            : '';
        },
        positionStyle() {
            return {
                'top': `${ this.verticalOffset }px`
            };
        }
    }
}
</script>
复制代码

效果如下:

一个静态的,在页面上的message消息。

看一下它的样式:

.el-message {
    min-width: 380px;
    box-sizing: border-box;
    border-radius: 4px;
    border: 1px solid #ebeef5;
    position: fixed;
    left: 50%;
    top: 20px;
    transform: translateX(-50%);
    background-color: #edf2fc;
    transition: opacity .3s,transform .4s,top .4s;
    overflow: hidden;
    padding: 15px 15px 15px 20px;
    display: flex;
    align-items: center;
}
复制代码

可以看到message是通过 position: fixed;定位到页面上。

实现3秒后自动消失效果

我们的组件是包裹在transition name="el-message-fade"标签下。下面的div 的 v-show="visible"控制组件显示隐藏的时候,会触发transition的动画。所以我们定义个定时器,3秒后控制visible = false即可。

methods: {
    close() {
        this.closed = true;
        this.visible = false
    },
    startTimer() {
        // duration是3000
        if (this.duration > 0) {
            this.timer = setTimeout(() => {
                if (!this.closed) {
                    this.close();
                }
            }, this.duration);
        }
    },
},
mounted() {
  this.startTimer();
},
复制代码

这样,我们的组件就会3秒后自动消失了。

通过指令方式调用message

现在Vue上挂载message对象:

import messageService from './components/Message/service.js'
Vue.prototype.$message = messageService;
复制代码

这样我们就可以在组件中使用this.$message()了。接下来实现service.js

import Vue from 'vue';
import Main from './message.vue';
let MessageConstructor = Vue.extend(Main);

let instance;

const Message = function(options) {
    // 调用this.$message时传递进来的参数
    options = options || {};
    // 如果参数是个string,直接赋值给message
    if (typeof options === 'string') {
        options = {
          message: options
        };
    }
    // 构造message实例
    instance = new MessageConstructor({
        data: options
    });
    instance.$mount();
    // 将dom添加到网页上
    document.body.appendChild(instance.$el);

    instance.visible = true;

    return instance;
}

export default Message;
复制代码

我们在测试页面里写:

mounted() {
    setTimeout(() => {
        this.$message('就这?就这?')
    }, 1000);
},
复制代码

1秒后,message就出现了,3秒后,message消失。达到了通过指令方式调用message。

message不同状态

message有'success', 'warning', 'info', 'error'四种状态。

this.$message({
    message: '恭喜你,这是一条成功消息',
    type: 'success'
})
复制代码

通过上面的代码,我们就可以调出一个成功提示。还有下面一种调用方式。

this.$message.error('错了哦,这是一条错误消息');
复制代码

直接点出error方法,就可以调用一个错误提示。我们来支持一下。在service.js中添加代码:

const status = ['success', 'warning', 'info', 'error']
status.forEach(type => {
    Message[type] = options => {
        if (typeof options === 'string') {
            options = {
                message: options
            };
        }
        options.type = type;
        return Message(options);
    };
});
复制代码

这样我们的$message就多了四个方法。显示的效果与直接传递options对象效果一致。

效果如下:

可关闭

调用方式:

this.$message({
    showClose: true,
    message: '这是一条消息提示'
});
复制代码

我们在组件中加入代码:

 <i v-if="showClose" class="el-message__closeBtn el-icon-close" @click="close"></i>
复制代码

效果如下:

close方法我们先前已经实现。

文字居中

调用方式:

this.$message({
    message: '居中的文字',
    center: true
});
复制代码

通过center控制样式即可实现。在组件div的样式中添加center ? 'is-center' : ''。

效果如下:

使用 HTML 片段

调用方式:

this.$message({
  dangerouslyUseHTMLString: true,
  message: '<strong>这是 <i>HTML</i> 片段</strong>'
});
复制代码

组件内添加代码:

<slot>
    <p v-if="!dangerouslyUseHTMLString" class="el-message__content">{{ message }}</p>
    <p v-else v-html="message" class="el-message__content"></p>
</slot>
复制代码

v-html即可支持html片段显示。

效果如下:

VNode支持

调用方式:

const h = this.$createElement;
this.$message({
  message: h('p', null, [
    h('span', null, '内容可以是 '),
    h('i', { style: 'color: teal' }, 'VNode')
  ])
});
复制代码

在service.js中的Message函数中添加

// 如果是vnode节点,直接将其赋值到instance.$slots.default上即可。
if (isVNode(instance.message)) {
    instance.$slots.default = [instance.message];
    instance.message = null;
}
复制代码

再看isVNode方法:

// node是对象,并且有componentOptions属性,我们就认为它是个vnode节点。
function isVNode(node) {
  return node !== null && typeof node === 'object' && hasOwn(node, 'componentOptions');
}

复制代码

我想通过分析vue源码的话,应该可以看到vnode对象拥有componentOptions属性,这里不在细究。

效果如下:

支持多条message显示

下面我们来支持连续出现message的情况。在service.js中:

let instances = [];
const Message = function(options) {
    ...
    // 添加mesagge到dom后
    // 当有新message时,它的verticalOffset是前面所有message的高加16
    let verticalOffset = options.offset || 20;
    instances.forEach(item => {
        verticalOffset += item.$el.offsetHeight + 16;
    });
    instance.verticalOffset = verticalOffset;
    instance.visible = true;
    instances.push(instance);
    
    return instance;
}
复制代码

效果如下:

总结

这篇我们研究了Message这个比较有趣的组件。

代码在码云:https://gitee.com/DaBuChen/my-element-ui/tree/message

其他组件源码研究:

Element组件源码研究-Button

Element组件源码研究-Input输入框

Element组件源码研究-Layout,Link,Radio

Element组件源码研究-Checkbox多选框

Element组件源码研究-InputNumber 计数器

Element组件源码研究-Loading组件

Element组件源码研究-Message组件