blog icon indicating copy to clipboard operation
blog copied to clipboard

Message消息提示实现原理

Open wuxianqiang opened this issue 5 years ago • 0 comments

使用Vue的小伙伴可能使用过element-ui中的Message 消息提示 使用React的小伙伴可能使用过ant-designMessage全局提示

Message.success({message: '消息提示'})

他们的原理都相似,这篇文章将会带你手写实现消息提示组件。

Vue

对于这样的消息组件,是用函数执行的进行调用,并不是在模板中直接使用组件的形式,不是组件那他们的DOM是如何创建到页面中显示出来的呢?其实还是利用了组件的虚拟DOM渲染的。

先准备好消息提示组件

<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;
  }
}

ul,li {
  list-style: none;
}
</style>

然后调用方法再渲染DOM到页面

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

先实现这个组件的DOM结构和基本的交换效果。MessageComponent.js文件

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.css文件

.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;
  }
}

ul,li {
  list-style: none;
}

现在组件和样式都准备好了,我们需要考虑组件如何渲染到页面中。通过用法我们知道Message是当做函数来直接使用的。说明函数执行的过程中会把消息组件渲染到页面中。

Message.success({ message: Math.random() }) }

原理

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()

wuxianqiang avatar Dec 30 '19 10:12 wuxianqiang