Vue 3 与 Notification API 高级应用

1. 概述

Notification API 允许网页向用户显示系统通知,即使应用在后台运行也能推送消息。Vue 3 与 Notification API 结合,可以构建出具有实时通知功能的应用,如聊天应用、新闻推送、任务提醒等。本集将深入探讨 Notification API 的高级特性,并学习如何在 Vue 3 中优雅地封装和使用这些功能。

1.1 什么是 Notification API?

Notification API 是浏览器提供的 Web API,用于向用户显示系统级别的通知。这些通知会显示在操作系统的通知中心,无论浏览器窗口是否处于活动状态。它支持自定义标题、内容、图标、声音等,并能处理用户交互事件。

1.2 应用场景

  • 聊天应用的新消息通知
  • 新闻和内容推送
  • 任务提醒和日历事件
  • 实时数据更新(如股票、天气)
  • 系统状态和错误提示
  • 电子商务订单状态更新
  • 社交媒体互动通知

1.3 Vue 3 中的优势

  • Composition API 允许将通知逻辑封装为可复用的 composables
  • 响应式系统可以实时更新通知状态
  • 生命周期钩子可以妥善管理通知资源
  • TypeScript 支持提供了更好的类型安全性
  • 与 PWA 结合可以实现离线通知

2. 核心知识

2.1 Notification API 基础

Notification API 主要通过 Notification 构造函数和 Notification 静态属性提供,包含以下核心功能:

// 请求通知权限
const permission = await Notification.requestPermission();

// 检查当前权限状态
const permission = Notification.permission;

// 创建并显示通知
const notification = new Notification(title, options);

// 关闭通知
notification.close();

2.2 通知权限

Notification API 需要用户授权才能显示通知,权限状态包括:

  • default:用户尚未做出选择
  • granted:用户允许显示通知
  • denied:用户拒绝显示通知

2.3 通知选项

创建通知时可以设置多种选项:

interface NotificationOptions {
  body?: string;              // 通知内容
  icon?: string;              // 通知图标 URL
  badge?: string;             // 通知徽章 URL(移动端)
  tag?: string;               // 通知标签,相同标签的通知会替换
  data?: any;                 // 附加数据
  lang?: string;              // 通知语言
  vibrate?: number[];         // 振动模式 [持续时间, 间隔, 持续时间...]
  sound?: string;             // 通知声音 URL
  renotify?: boolean;         // 是否替换现有通知时重新通知
  requireInteraction?: boolean; // 是否需要用户手动关闭
  silent?: boolean;           // 是否静音通知
  timestamp?: number;         // 通知时间戳
  actions?: NotificationAction[]; // 通知操作按钮
}

interface NotificationAction {
  action: string;             // 操作标识符
  title: string;              // 操作按钮文本
  icon?: string;              // 操作按钮图标
}

2.4 创建 Notification Composable

我们可以创建一个 useNotification composable 来封装 Notification API:

// composables/useNotification.ts
import { ref, onMounted, onUnmounted } from 'vue';

export interface NotificationOptions extends globalThis.NotificationOptions {
  /** 是否自动请求权限 */
  autoRequestPermission?: boolean;
  /** 通知点击回调 */
  onClick?: (event: Event, notification: Notification) => void;
  /** 通知关闭回调 */
  onClose?: (event: Event, notification: Notification) => void;
  /** 通知错误回调 */
  onError?: (event: Event, notification: Notification) => void;
  /** 通知显示回调 */
  onShow?: (event: Event, notification: Notification) => void;
}

export interface UseNotificationReturn {
  /** 通知权限状态 */
  permission: ref<NotificationPermission>;
  /** 是否已授权 */
  isGranted: ref<boolean>;
  /** 显示通知 */
  show: (title: string, options?: NotificationOptions) => Notification | null;
  /** 请求权限 */
  requestPermission: () => Promise<NotificationPermission>;
  /** 关闭所有通知 */
  closeAll: () => void;
}

let notificationInstances: Notification[] = [];

export function useNotification(options: NotificationOptions = {}): UseNotificationReturn {
  const permission = ref<NotificationPermission>(Notification.permission);
  const isGranted = ref(permission.value === 'granted');

  // 清理通知实例
  const cleanupNotification = (notification: Notification) => {
    const index = notificationInstances.indexOf(notification);
    if (index > -1) {
      notificationInstances.splice(index, 1);
    }
  };

  // 请求权限
  const requestPermission = async () => {
    if (!('Notification' in window)) {
      console.error('This browser does not support desktop notification');
      return 'denied';
    }

    const result = await Notification.requestPermission();
    permission.value = result;
    isGranted.value = result === 'granted';
    return result;
  };

  // 显示通知
  const show = (title: string, showOptions: NotificationOptions = {}) => {
    if (permission.value !== 'granted') {
      console.error('Notification permission not granted');
      return null;
    }

    const mergedOptions = { ...options, ...showOptions };
    const { onClick, onClose, onError, onShow, ...notificationOptions } = mergedOptions;

    try {
      const notification = new Notification(title, notificationOptions);
      notificationInstances.push(notification);

      // 绑定事件
      if (onClick) {
        notification.addEventListener('click', (event) => onClick(event, notification));
      }
      if (onClose) {
        notification.addEventListener('close', (event) => {
          onClose(event, notification);
          cleanupNotification(notification);
        });
      }
      if (onError) {
        notification.addEventListener('error', (event) => {
          onError(event, notification);
          cleanupNotification(notification);
        });
      }
      if (onShow) {
        notification.addEventListener('show', (event) => onShow(event, notification));
      }

      // 自动关闭(如果设置了 duration)
      if (showOptions.duration) {
        setTimeout(() => {
          notification.close();
        }, showOptions.duration);
      }

      return notification;
    } catch (error) {
      console.error('Error showing notification:', error);
      return null;
    }
  };

  // 关闭所有通知
  const closeAll = () => {
    notificationInstances.forEach(notification => {
      try {
        notification.close();
      } catch (error) {
        console.error('Error closing notification:', error);
      }
    });
    notificationInstances = [];
  };

  onMounted(() => {
    if (options.autoRequestPermission && permission.value === 'default') {
      requestPermission();
    }
  });

  onUnmounted(() => {
    closeAll();
  });

  return {
    permission,
    isGranted,
    show,
    requestPermission,
    closeAll
  };
}

2.5 处理通知事件

通知支持多种事件,我们可以在 composable 中统一处理:

// 在通知创建后绑定事件
notification.addEventListener('click', (event) => {
  // 点击通知时触发
  window.focus();
  notification.close();
});

notification.addEventListener('close', (event) => {
  // 通知关闭时触发
});

notification.addEventListener('error', (event) => {
  // 通知出错时触发
});

notification.addEventListener('show', (event) => {
  // 通知显示时触发
});

3. 最佳实践

3.1 用户体验优化

  • 提供清晰的通知内容和操作选项
  • 避免过度发送通知,设置合理的频率限制
  • 允许用户自定义通知偏好(如声音、振动、频率)
  • 实现通知分组,避免通知泛滥
  • 提供通知历史记录,允许用户查看错过的通知
  • 确保通知内容与应用状态同步

3.2 权限管理

  • 仅在必要时请求通知权限
  • 提供清晰的解释,说明为什么需要通知权限
  • 允许用户随时更改通知设置
  • 处理权限被拒绝的情况,提供优雅降级
  • 不要反复请求权限,这会导致用户体验差

3.3 性能优化

  • 限制同时显示的通知数量
  • 及时清理不再需要的通知实例
  • 优化通知图标和声音资源,减少加载时间
  • 考虑使用 Service Worker 处理后台通知
  • 避免在通知中包含过多复杂内容

3.4 跨浏览器兼容性

  • 检查 Notification 是否存在
  • 考虑浏览器对不同通知选项的支持差异
  • 为不支持的浏览器提供降级方案
  • 测试移动端和桌面端的表现
  • 考虑使用第三方库(如 OneSignal、Firebase Cloud Messaging)处理复杂推送需求

3.5 安全性考虑

  • 确保通知内容来自可信来源
  • 避免在通知中包含敏感信息
  • 验证通知数据的完整性
  • 考虑使用加密保护通知内容
  • 实现通知撤销机制,防止错误信息传播

4. 常见问题与解决方案

4.1 权限被拒绝

问题:用户拒绝了通知权限,导致无法显示通知。

解决方案

  • 提供清晰的解释,说明为什么需要通知权限
  • 允许用户在设置中重新启用通知
  • 实现优雅降级,如在应用内显示通知
<template>
  <div class="notification-settings">
    <h3>通知设置</h3>
    <div v-if="!isGranted">
      <p>启用通知以接收实时更新</p>
      <button @click="requestPermission">允许通知</button>
    </div>
    <div v-else>
      <p>通知已启用</p>
      <button @click="openBrowserSettings">更改设置</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { useNotification } from './composables/useNotification';

const { isGranted, requestPermission } = useNotification();

const openBrowserSettings = () => {
  // 提示用户在浏览器设置中更改通知权限
  alert('请在浏览器设置中管理通知权限');
};
</script>

4.2 通知不显示

问题:权限已授予,但通知仍然不显示。

解决方案

  • 检查浏览器是否支持 Notification API
  • 确保通知选项设置正确
  • 检查浏览器和操作系统的通知设置
  • 考虑浏览器的 "请勿打扰" 模式
  • 实现调试机制,记录通知相关事件

4.3 点击通知无反应

问题:点击通知后没有预期的行为。

解决方案

  • 确保正确绑定了 click 事件监听器
  • 在点击事件中调用 window.focus() 激活应用
  • 处理通知的 data 属性,根据数据执行相应操作
  • 测试不同浏览器的点击行为差异

4.4 移动端兼容性问题

问题:通知在桌面端正常,但在移动端不显示或表现异常。

解决方案

  • 检查移动端浏览器对 Notification API 的支持
  • 优化通知图标和徽章,适配移动端显示
  • 考虑使用 PWA 和 Service Worker 实现可靠的移动端通知
  • 测试不同移动设备和操作系统的表现

5. 高级学习资源

5.1 官方文档

5.2 第三方库

5.3 相关标准

6. 实践练习

6.1 练习 1:创建通知组件

目标:创建一个可复用的 Vue 3 通知组件。

要求

  1. 使用 useNotification composable 实现通知功能
  2. 支持自定义标题、内容、图标和操作按钮
  3. 实现通知权限管理
  4. 支持通知队列和自动关闭
  5. 实现通知历史记录

代码框架

<template>
  <div class="notification-component">
    <button @click="showNotification" :disabled="!isGranted">
      发送通知
    </button>
    <div v-if="!isGranted">
      <button @click="requestPermission">允许通知</button>
    </div>
    
    <!-- 通知历史记录 -->
    <div class="notification-history">
      <h3>通知历史</h3>
      <div v-for="(notification, index) in notificationHistory" :key="index">
        <div class="history-item">
          <h4>{{ notification.title }}</h4>
          <p>{{ notification.body }}</p>
          <span>{{ new Date(notification.timestamp).toLocaleString() }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useNotification } from './composables/useNotification';

// 实现通知组件
</script>

6.2 练习 2:实现推送通知

目标:结合 Push API 和 Service Worker 实现服务器推送通知。

要求

  1. 注册 Service Worker 处理推送事件
  2. 实现推送订阅和取消订阅功能
  3. 处理服务器推送的通知
  4. 支持自定义推送数据和操作

提示

  • 需要一个后端服务来发送推送消息
  • 使用 VAPID 密钥进行推送认证
  • 在 Service Worker 中处理 push 事件

6.3 练习 3:创建通知中心

目标:创建一个完整的通知中心应用。

要求

  1. 支持多种类型的通知(消息、提醒、系统通知)
  2. 实现通知分类和筛选
  3. 支持通知标记为已读/未读
  4. 支持通知删除和清空
  5. 实现通知设置自定义
  6. 支持通知音效和振动设置

提示

  • 使用 localStorage 或 IndexedDB 存储通知历史
  • 实现响应式设计,适配不同设备
  • 考虑性能优化,尤其是在通知数量较多时

7. 总结

本集深入探讨了 Vue 3 与 Notification API 的高级应用,包括:

  • Notification API 的核心概念和使用方法
  • 创建可复用的 useNotification composable
  • 最佳实践,如用户体验优化、权限管理和性能优化
  • 常见问题的解决方案
  • 高级学习资源和实践练习

通过本集的学习,您应该能够熟练地在 Vue 3 应用中集成 Notification API,构建出功能丰富的实时通知系统。在实际开发中,还需要考虑浏览器兼容性、移动端表现和与后端服务的集成等因素,以确保通知功能的可靠性和良好的用户体验。

« 上一篇 Vue 3 与 Clipboard API 下一篇 » Vue 3 与 AI 集成