vue3-Message组件

在vue3项目中components文件夹创建message文件夹作为存放组件

准备工作

message文件夹中创建四个文件分别是:

  1. AtjMessage.vue - message组件
  2. MessageGroup.vue - 用于创建message组件的父组件,用做message出场和退场动画
  3. type.ts(message.ts) - 是用存放message的props的文件
  4. utils.ts - 用作方法创建message组件的函数

 创建type.ts(message.ts)文件

具体代码如下:

typescript代码:

import { VNode } from 'vue'

export interface MessageProps {
  id?: number
  message?: string | VNode
  showClose?: boolean
  type?: 'success' | 'error' | 'warning' | 'info'
  duration?: number
}

等价于Javascript代码:
/**
 * @typedef {Object} MessageProps
 * @property {number} [id] - 消息ID
 * @property {(string|import('vue').VNode)} [message] - 消息内容
 * @property {boolean} [showClose] - 是否显示关闭按钮
 * @property {('success'|'error'|'warning'|'info')} [type] - 消息类型
 * @property {number} [duration] - 显示时长
 */

// 使用示例
const messageProps = {
  id: 1,
  message: '这是一条消息',
  showClose: true,
  type: 'success',
  duration: 3000
}

创建Message.vue组件

具体代码如下:

<template>
    <div class="atj-message" :class="type" v-if="visible">
      <div class="atj-message-content">
        <div class="atj-message-content-title">
          <div class="atj-message-content-title-text">
            <slot name="title"></slot>
            <!-- 如果内容是VNode,则渲染VNode -->
            <component :is="messageContent" v-if="isVNodes"/>
            <!-- 如果内容是字符串,则渲染字符串 -->
            <template v-else>{{ message }}</template>
            <span
              class="atj-message-content-title-close"
              v-if="showClose"
              @click.stop="handleClose"
            >×</span>
          </div>
        </div>
      </div>
    </div>
</template>

<script lang="ts" setup>
import { defineProps, defineOptions, computed, ref, onMounted, defineEmits } from 'vue'
import type { MessageProps } from './type'
// 定义props
const props = defineProps<MessageProps>()
// 定义emit
const emit = defineEmits(['close'])

defineOptions({
  name: 'AtjMessage'
})

// 默认类型为info
const type = computed(() => props.type ?? 'info')
const visible = ref(true)
// 判断内容是否是VNode
const isVNodes = computed(() => props.message && typeof props.message !== 'string')
// 如果内容是VNode,则渲染VNode
const messageContent = computed(() => isVNodes.value ? props.message : null)

// 关闭消息
// 注意,这里的点击关闭不是设置visible 为 false,
// 因为我们是通过MessageGroup的数组渲染出来的,所以定义emit通知父组件删除数组中对应的元素,
// 这样子我们的退出进场动画都可以由效果,如果直接visible.value为false,退出动画就没有了
function handleClose () {
  emit('close', props.id)
}

// 挂载后设置定时器
onMounted(() => {
  if (props.duration !== 0) {
    setTimeout(() => {
      handleClose()
    }, props.duration || 3000)
  }
})
</script>

<style lang="less">
  .atj-message {
      cursor: pointer;
      border-radius: 6px;
      margin-top: 5px;
      width: fit-content;
      padding: 10px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      .atj-message-content {
          width: 100%;
          height: 100%;
          .atj-message-content-title {
              width: 100%;
              height: 100%;
              display: flex;
              align-items: center;
              justify-content: center;
              .atj-message-content-title-text {
                  font-size: 16px;
                  font-weight: bold;
                  color: #ffffff;
                  text-align: center;
                  .atj-message-content-title-close{
                      width: 10px;
                      height: 10px;
                  }
              }
          }
      }
  }
  .info {
      background-color: #909399;
  }
  .success {
      background-color: #67C23A;
  }
  .error {
      background-color: #F56C6C;
  }
  .warning {
      background-color: #E6A23C;
  }
</style>

创建MessageGroup.vue组件

具体代码如下:

<template>
    <TransitionGroup name="fade" tag="div" class="atj-message-group">
        <AtjMessage
            v-for="item in messages"
            :key="item.id"
            v-bind="item"
            @close="removeMessage"
        />
    </TransitionGroup>
</template>

<script lang="ts" setup>
import { defineOptions, ref, defineExpose } from 'vue'
import AtjMessage from './AtjMessage.vue'
import type { MessageProps } from './type'

defineOptions({
  name: 'MessageGroup'
})
//数组中的每个元素都必须包含 MessageProps 的所有属性
//同时必须有一个 number 类型的 id 属性
//[] 表示这是一个数组
const messages = ref<(MessageProps & { id: number })[]>([])
//存储销毁回调函数的变量
let onDestroy: (() => void) | null = null

//添加函数
//在我们进行添加的时候用户是不需要输入id的,所以我们要自己创建id
//...options,这一段就是说:展开旧的数据把id分别加入对应的数据当中,这样有利于我们进行删除操作
const addMessage = (options: MessageProps) => {
  const id = Date.now()
  messages.value.push({
    ...options,
    id
  })
}
//进行删除操作
//在上述代码中我们是这样些的
/*<AtjMessage
    v-for="item in messages"
    :key="item.id"
    v-bind="item"
    @close="removeMessage"
/>*/
//我们在message定义了emit,我们在MessageGroup.vue使用以下函数进行监听
const removeMessage = (id: number) => {
  const index = messages.value.findIndex(item => item.id === id)
  if (index > -1) {
//删除对应的函数
    messages.value.splice(index, 1)
//如果messages数组长度为0就通知uilt.ts文件将创建的div和MessageGroup.vue组件删除
    if (messages.value.length === 0 && onDestroy) {
      setTimeout(() => {
        onDestroy?.()
      }, 500) // 等待动画结束后再销毁
    }
  }
}
//将MessageGroup.vue组件的addMessage方法和onDestroy销毁方法暴露出去让uilt.ts文件可访问
defineExpose({
  addMessage,
  onDestroy: (callback: () => void) => {
    onDestroy = callback
  }
})
</script>

<style lang="less">
.atj-message-group {
    position: relative;
    min-width: 300px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

/* 1. 声明过渡效果 */
.fade-move,
.fade-enter-active,
.fade-leave-active {
  transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

/* 2. 声明进入和离开的状态 */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}

/* 3. 确保离开的项目被移除出了布局流
      以便正确地计算移动时的动画效果。 */
.fade-leave-active {
  position: absolute;
}
</style>

创建utils.ts文件

具体代码如下:

import { createVNode, render } from 'vue'
import MessageGroup from './MessageGroup.vue'
import type { MessageProps } from './type'

let messageInstance: any = null
let container: HTMLElement | null = null

export function createMessage () {
  if (!messageInstance) {
    // 创建容器
    container = document.createElement('div')
    container.style.position = 'fixed'
    container.style.top = '20px'
    container.style.left = '50%'
    container.style.transform = 'translateX(-50%)'
    container.style.zIndex = '9999'
    document.body.appendChild(container)

    // 创建 vnode
    const vnode = createVNode(MessageGroup)
    render(vnode, container)
    messageInstance = vnode.component

    // 设置销毁回调
    messageInstance.exposed?.onDestroy(() => {
      if (container && document.body.contains(container)) {
        render(null, container)
        document.body.removeChild(container)
        messageInstance = null
        container = null
      }
    })
  }

  return messageInstance
}
//在其他文件使用uilt.ts文件可以使用这个方法创建message
export function message (options: MessageProps) {
  const instance = createMessage()
  instance?.exposed?.addMessage(options)
}

使用

具体代码如下:

在homeview页面中:

<template>
  <div class="home">
    <button @click="messageclick">点击</button>
  </div>
</template>

<script lang="ts" setup>
import { message } from '@/components/message/utils'
import { defineComponent, h } from 'vue'
defineComponent({
  name: 'HomeView'
})

function messageclick () {
  // 调用 message 组件的函数
  // message({
  //   message: '你点击了我',
  //   type: 'success',
  //   showClose: true
  // })
  message({
    message: h('p', null, [
      h('span', null, '内容可以是 '),
      h('i', { style: 'color: teal' }, 'VNode')
    ]),
    type: 'success',
    duration: 30000
  })
}
</script>

### Vue3 组件间通信方法与最佳实践 #### 使用 `props` 和 `$emit` 在父组件向子组件传递数据时,可以利用 `props` 属性。相反地,在子组件需要通知父组件某些事件发生时,则可以通过调用 `$emit()` 来触发自定义事件[^1]。 ```html <!-- ParentComponent.vue --> <template> <ChildComponent :message="parentMessage" @childEvent="handleChildEvent"/> </template> <script setup> import { ref } from &#39;vue&#39;; const parentMessage = "Hello Child"; function handleChildEvent(data){ console.log(`Received data from child component: ${data}`); } </script> ``` ```html <!-- ChildComponent.vue --> <template> <div>{{ message }}</div> <button @click="$emit(&#39;childEvent&#39;, &#39;Data to send&#39;)">Emit Event</button> </template> <script setup> defineProps({ message: String, }); </script> ``` #### 利用 Composition API 提供依赖注入 (`provide/inject`) 对于跨多层嵌套的祖孙关系之间共享状态的情况,Composition API 下的新版 `provide`/`inject` 可以更方便地管理这种深层次的数据流[^2]。 ```javascript // App.vue (GrandParent Component) export default { setup() { const sharedState = reactive({ count: 0 }); provide(&#39;sharedState&#39;, readonly(sharedState)); return {}; } }; ``` ```javascript // SomeDeeplyNestedChild.vue setup(){ const sharedState = inject(&#39;sharedState&#39;); watch(() => sharedState.count, () => { console.log("Shared state changed:", sharedState); }); return { sharedState }; } ``` #### 应用全局事件总线或第三方库如 `mitt` 当面临非父子级联结构下的兄弟节点或其他任意位置组件间的通讯需求时,可考虑引入轻量级的消息订阅发布机制——比如官方推荐之外的选择 `mitt` 或者创建简单的全局事件中心对象。 ```bash npm install mitt ``` ```typescript // eventBus.ts import mitt from &#39;mitt&#39;; const emitter = mitt(); export default emitter; ``` ```javascript // SenderComponent.vue emitter.emit(&#39;custom-event-name&#39;, payload); ``` ```javascript // ReceiverComponent.vue onMounted(() => { emitter.on(&#39;custom-event-name&#39;, handlerFunction); }); onUnmounted(() => { emitter.off(&#39;custom-event-name&#39;, handlerFunction); }); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值