Message消息提示组件的原理(Vue和React都有)

3,377 阅读3分钟

Message消息提示是这样的页面提示

Message
如果你在使用Vue那可能会使用element-ui中消息提示,如果你在使用React,那么可能会使用ant-design的消息提示。他们的原理都相似,这篇文章将会带你手写实现消息提示组件。

这两个UI框架在消息提示组件上的用法大致相同,用例如下:

Message.success({message: '恭喜你,这是第一条成功消息'})

消息提示组件并不是直接在模板中使用,而是当做普通的方法进行调用,方法调用的时候会把组件渲染到页面中,那么它渲染的HTML是如何创建的呢?其实渲染的DOM还是一个组件,只不过是通过Message方法进行了一层包装,通过方法让外界调用更简单。

Vue

先准备好消息提示组件,消息提示是一个列表,可以添加多个消息内容,每次消息只有几秒的显示时间将会消失,那么代码如下结构:

<template>
  <div>
    <ul>
      <li v-for="(item, index) in messages" :key="index" class="message">
        {{ item.message }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data () {
    return {
      messages: [],
      id: 0
    }
  },
  methods: {
    add (options) {
      let layer = {
        id: this.id++,
        ...options
      }
      layer.timer = setTimeout(() => {
        this.remove(layer)
      }, 2000);
      this.messages.push(layer)
    },
    remove (layer) {
      this.messages = this.messages.filter(item => item.id !== layer.id)
    }
  }
}
</script>

<style>
.message {
  position: fixed;
  left: 50%;
  top: 30px;
  transform: translate3d(-50%, 0, 0);
  background: #000;
  background: #f0f9eb;
  color: #67c23a;
  padding: 10px 20px;
  border-radius: 4px;
  animation: move 0.3s;
}

@keyframes move {
  0% {
    top: 0;
    opacity: 0;
  }
  100% {
    top: 30px;
    opacity: 1;
  }
}
</style>

通过方法进一步包装组件,可以调用某个方法让消息提示显示出来。关键是组件已经准备好了,要考虑如何挂在到页面中,有几个API大家很少用到的。注意下面代码:

import Vue from 'vue'
import MessageComponent from './MessageComponent.vue'

class Msg {
  constructor () {
    let vm = new Vue({
      render: h => h(MessageComponent)
    }).$mount()
    document.body.appendChild(vm.$el)
    this.component = vm.$children[0]
  }
  success (options) {
    this.component.add(options)
  }
}

Msg.getInstance = (function () {
  let instance;
  return function () {
    if (!instance) {
      instance = new Msg()
    }
    return instance
  }
})()

export const Message = Msg.getInstance()

现在消息提示已经封装完成,可以使用了。

React

其实一开始只准备Vue的消息提示,但是考虑到React也有很多人可能用到,所有添加了这部分内容。

原理也十分类似,先准备好组件DOM结构和基本的交互操作

import React, { Component } from 'react';
import './Message.css'

class MessageComponent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      id: 0,
      messages: [],
      max: 5
    }
  }
  add = (options) => {
    let { id, messages } = this.state
    let layer = {
      id: id++,
      ...options
    }
    layer.timer = setTimeout(() => {
      this.remove(layer)
    }, 2000);
    messages.push(layer)
    this.setState({ id, messages })
  }
  remove = (layer) => {
    clearTimeout(layer.timer)
    let messages = this.state.messages.filter(item => item.id !== layer.id)
    this.setState({ messages })
  }
  render() {
    return (
      <ul>
        {this.state.messages.map(
          (item, index) => <li key={item.id} className="message">{item.message}</li>
        )}
      </ul>
    );
  }
}

export default MessageComponent;

再加上一些样式。

.message {
  position: fixed;
  left: 50%;
  top: 30px;
  transform: translate3d(-50%, 0, 0);
  background: #000;
  background: #f0f9eb;
  color: #67c23a;
  padding: 10px 20px;
  border-radius: 4px;
  animation: move 0.3s;
}

@keyframes move {
  0% {
    top: 0;
    opacity: 0;
  }
  100% {
    top: 30px;
    opacity: 1;
  }
}

关键是DOM挂载的方式不一样

import React from 'react';
import ReactDOM from 'react-dom';
import MessageComponent from './MessageComponent'

class Msg {
  constructor () {
    let myRef = {current: ''}
    const div = document.createElement('div')
    document.body.append(div)
    ReactDOM.render(<MessageComponent ref={myRef} />, div)
    this.refs = myRef
  }
  success (options) {
    this.refs.current.add(options)
  }
}

Msg.getInstance = (function () {
  let instance;
  return function () {
    if (!instance) {
      instance = new Msg()
    }
    return instance
  }
})()

export const Message = Msg.getInstance()

现在你已经学会消息提示组件的原理,可以看出这样的做法是让使用组件的人更加简便,但是多添加了一层封装组件的代码逻辑。