一起浅做一下全局提示组件

全局提示组件在前端中算是比较重要的一个组件,他能极大提高用户交互体验。当然,在社区比较有人气的 UI 组件库也必有他身影。

只不过组件库叫法可能不太相同,有叫 messagetoastalert 等,但就是这么一玩意。

image.png

ant-design-vue 组件库为例

image.png

其核心代码

message.info('This is a normal message')
复制代码

他是API的方式进行调用组件,以平常使用components注册组件,之后在template中使用的方式不太相同

想要实现这个,其实并不困难

但在之前我想声明一下:ant-design-vue 的 message 做个type分类,有info、success、error等。但为了读者方便理解,我们统一就用message来进行调用,读者可以根据本文提供的demo源码自行进行调整(贴一下 ant-design-vue 源码)

image.png

首先,我们在 components 目录下创建 message 目录,同时创建 Message.vue 和 index.js 文件 image.png

然后在 Message.vue 中写

<!-- Message.vue -->
<template>
  <div>{{ content }}</div>
</template>

<script>
export default {
  name: 'Message',
  props: {
    content: {
      type: String,
      default: ''
    }
  }
}
</script>
复制代码

再者在 index.js 中写

// index.js
import { render, createVNode } from 'vue'
import Message from './Message.vue'

export default function message (content) {
  const div = document.createElement('div')
  const vm = createVNode(Message, {
    content
  })
  render(vm, div)
  document.body.appendChild(div)
}
复制代码

最后在 app.vue 引入使用

<!-- app.vue -->
<template>
</template>
<script>
import message from './components/message'
export default{
  setup() {
    message('消息组件1')
    message('消息组件2')
  }
}
</script>
复制代码

效果:

image.png

ok,这样就实现了以API的方式进行调用组件

其核心代码其实只有以下

const div = document.createElement('div')
const vm = createVNode(Message, {
  content
})
render(vm, div)
document.body.appendChild(div)
复制代码

createVNode 可以认为就是h函数,支持直接转成虚拟dom对象

image.png

再去调用 render,渲染至div下,再将div插入body

由于我们做的是全局组件,应该不被任何因素干扰,比如 vue-router

但是上面例子我们会发现,我们只要调用一个 message 方法,就要创建一个div插入至body,这显然不是符合vue数据驱动视图的理念

image.png

那,接下里进行改造

先从 Message.vue 文件开始

我们先定义一个消息列表messages,之后提供添加 add 和删除delete消息列两方法,再暴露add方法出去

当然,删除消息是需要根据id进行删除,可不能瞎删

<!-- Message.vue -->
<script>
import { ref, unref } from 'vue'
export default {
  name: 'Message',
  setup (props, { expose }) {
    // 消息列表
    const messages = ref([])
    
    let id = 0
    // 生成id
    const uuid = () => `message_${id++}`
    
    // 添加消息对象
    const add = (message) => {
      const id = uuid()
      const _message = {
        ...message,
        id
      }
      unref(messages).push(_message)
      const { duration = 3 } =  message
      // 设置定时器
      const timer = setTimeout(() => {
        clearTimeout(timer)
        remove(id)
      }, duration * 1000)
    }
    
    // 根据 id 删除消息对象
    const remove = (id) => {
      messages.value = unref(messages).filter(message => message.id !== id)
    }
    
    // 暴露出add 和 remove
    expose({
      add
    })
    
    return {
      messages
    }
  }
}
</script>
复制代码

v-for把消息列表渲染出来,同时使用transition-group做一些列表过渡动画

<!-- Message.vue -->
<template>
  <transition-group
    class="message"
    tag="div"
  >
    <div
      class="message-content"
      v-for="message in messages"
      :key="message.id"
    >
      {{ message.content }}
    </div>
  </transition-group>
</template>
复制代码

再编写一点消息列表样式和其弹出动画样式

<!-- Message.vue -->
<style scoped>
.message {
  position: fixed;
  z-index: 999;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
}

.message-content {
  padding: 8px 16px;
  border-radius: 3px;
  box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
  background: #fff;
  margin-bottom: 20px;
}

.v-enter-active,
.-leave-active {
  transition: all 200ms ease-in;
}
.v-enter-from,
.v-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>
复制代码

再改造一下入口文件

之前我们发现在 app.vue 中调用一次 message方法,就会一次 dom 操作,那我们使用闭包写一个单例模式进行改造

再者我们在 Message.vue 暴露出add的方法可以直接进行操作消息列表

// index.js
import { render, createVNode } from 'vue'
import Message from './Message.vue'

let vm
// 使用单例模式,不再重新插入body
function getMessageInstance () {
  if (vm) return
  const div = document.createElement('div')
  vm = createVNode(Message)
  render(vm, div)
  document.body.appendChild(div)
}

export default function message (content = '', duration) {
  getMessageInstance()
  vm.component.exposed.add({
    content,
    duration
  })
}
复制代码

最后在 app.vue 文件中随便编写一些测试代码

<!-- app.vue -->
<template>
  <button @click="onClick">测试</button>
</template>
<script>
import message from './components/message'
export default{
  setup() {
    message('消息组件1', 4)
    message('消息组件2', 2)
    let index = 3
    const onClick = () => {
      message(`消息组件${index++}`)
    }
    return {
      onClick
    }
  }
}
</script>
复制代码

看看最终效果

屏幕录制2022-06-06-下午2.22.gif

好,这就是全局提示组件设计大致思路。

回顾一下会发现,其实Vue的组件开发依旧绕不开 JavaScript,可见 JavaScript 在前端的份量。

感谢阅读

猜你喜欢

转载自juejin.im/post/7106024921683197960