客户非得要求新开一个标签进行通信,那就写!一次性写够八种方案,够不够!

前言

Hello~大家好。我是秋天的一阵风

在我们公司的项目中,存在这样一种场景:在列表页中通常会设置一个 "新增数据" 按钮,以便用户能够方便地录入新数据。一般来说,我们会采用弹窗的形式来完成这一操作。如下图所示:

然而,当录入的字段过多时,弹窗的高度会显得不足,从而导致滚动条的出现,这无疑会对用户体验造成较大影响。因此,在这种情况下,我们通常会选择 跳转到一个独立的创建数据页面

对于前端路由跳转,以 Vue 为例,我们通常会使用以下 API 来实现:

JavaScript 复制代码
this.$router.push('/login'); // 跳转到指定路由
this.$router.replace('/login'); // 替换当前路由
this.$router.go(-1); // 后退一步
this.$router.go(1); // 前进一步

目前,我们使用的三大主流框架------Vue、React 和 Angular------都属于单页面应用框架。在这种框架下,直接进行路由跳转会导致当前列表页面的数据丢失。而我们列表页中往往包含用户已经输入的一系列筛选条件,客户不希望因为新增数据的操作而离开当前页面,从而导致筛选条件丢失。

因此,用户提出了新的要求:必须通过新开一个标签页来打开新增数据页面进行数据创建。并且,在完成数据创建后,原标签页(即列表页面)应能够基于当前的筛选条件进行实时搜索。

一、流程与效果

1. 流程

  1. 在列表页点击'新增数据'按钮,通过window.open打开新标签创建数据页面
  2. 在创建好数据以后,需要通知旧标签也就是列表页面,基于当前的筛选条件重新请求数据

2. 最终效果

二、八种方案

我们可以在创建数据页面成功创建数据后,往cookie里存入一个标识(比如:needRefresh:true),在数据列表页通过定时器轮询的方式获取,得到创建成功的标识后,更新数据。

在文中这个案例,为了方便处理和展示效果,我是直接在cookie里保存数据对象,然后在列表页中直接push进数组中。

javascript 复制代码
npm install js-cookie
javascript 复制代码
// main.js
import Cookies from "js-cookie";

Vue.prototype.$setCookie = (key, value) => {
  Cookies.set(key, value, { expires: 1 }); // 设置 Cookie,有效期为 1 天
};

Vue.prototype.$getCookie = (key) => {
  return Cookies.get(key);
};

Vue.prototype.$removeCookie = (key) => {
  return Cookies.remove(key);
};
javascript 复制代码
// edit.vue 创建数据页

 methods: {
    onSubmit() {
      this.$message({
        message: "创建成功!",
        type: "success",
      });
      this.$setCookie("newData", JSON.stringify(this.formData));
    },
  },
javascript 复制代码
// index.vue 列表页
  data() {
    return {
      filterForm: {
        id: "",
        name: "",
        status: "",
        category: "",
        priority: "",
        assignedTo: "",
        createdBy: "",
      },
      tableData: [],
      filteredData: [],
      pollingTimer: null, // 定时器
    };
  },
 mounted() {
    this.pollCookieData();
  },
  beforeDestroy() {
    // 在组件销毁前清除定时器
    if (this.pollingTimer) {
      clearInterval(this.pollingTimer);
    }
  }, 
 methods: {
    pollCookieData() {
      this.pollingTimer = setInterval(() => {
        const cookieData = this.$getCookie("newData"); 
        if (cookieData) {
          try {
            const parsedData = JSON.parse(cookieData);
            this.tableData.push(parsedData);
            this.applyFilters(); // 基于已有筛选条件过滤数据 
            this.$removeCookie("newData") // 防止重复添加
          } catch (error) {
            console.error("解析 Cookie 数据失败:", error);
          }
        }
      }, 2000); 
    },
 }   

2. LocalStorage 和 Window.Onstorage

javascript 复制代码
//edit.vue 创建数据页
 methods: {
    onSubmit() {
      this.$message({
        message: "创建成功!",
        type: "success",
      });
       localStorage.setItem('newData', JSON.stringify(this.formData));
    },
  },
javascript 复制代码
// index.vue 列表页
 mounted() {
    // 监听 storage 事件
    window.addEventListener("storage", this.handleStorageEvent);
  },
  beforeDestroy() {
    window.removeEventListener("storage", this.handleStorageEvent);
  },
  methods: {
    handleStorageEvent(event) {
       // 监听 storage 事件
       if (event.key === 'newData') {
        if(event.newValue){
          try {
            const parsedData = JSON.parse(event.newValue);
            this.tableData.push(parsedData);
            this.applyFilters();
          } catch (error) {
            console.error("解析 Storage 数据失败:", error);
          }
        }
      }
    },
   } 

3. window.opener 和 window.postMessage

1. window.opener

定义

window.opener 是一个全局属性,用于引用打开当前窗口的窗口对象。如果当前窗口是通过 window.open() 方法打开的,window.opener 就会指向打开它的父窗口;如果没有父窗口,则为 null

用途
  • 跨窗口操作 :可以通过 window.opener 访问父窗口的 DOM、变量或方法,从而实现父子窗口之间的交互。
  • 关闭父窗口 :可以通过 window.opener.close() 关闭父窗口。
  • 与父窗口通信 :可以通过直接操作 window.opener 来传递数据或调用方法
限制
  • 同源策略 :出于安全考虑,window.opener 只能在同源策略下工作。如果父窗口和子窗口的来源(协议、域名、端口)不同,浏览器会阻止对 window.opener 的访问。
  • 用户体验:直接操作父窗口可能导致用户体验问题(如弹窗或页面篡改),因此需要谨慎使用。

2. window.postMessage

定义

window.postMessage 是一个用于跨窗口(或跨域)通信的方法。它允许在不同来源(origin)的窗口或 iframe 之间安全地传递消息,而不会违反浏览器的同源策略。

用途
  • 跨域通信:可以在不同来源的页面之间传递消息,例如在主页面和嵌入的 iframe 之间。
  • 父子窗口通信 :可以在父窗口和通过 window.open() 打开的子窗口之间通信。
  • 安全交互 :通过指定目标来源(targetOrigin),可以确保消息只发送到预期的窗口,避免安全风险。
语法
JavaScript 复制代码
otherWindow.postMessage(message, targetOrigin);
  • otherWindow :目标窗口的引用,可以是 window.open() 返回的窗口对象,或者 iframe 的 contentWindow
  • message:要发送的消息,可以是字符串、对象或其他可序列化的数据。
  • targetOrigin :目标窗口的来源(协议、域名和端口)。出于安全考虑,必须明确指定目标来源,而不是使用通配符(*)。
javascript 复制代码
//edit.vue 创建数据页
 methods: {
    onSubmit() {
      this.$message({
        message: "创建成功!",
        type: "success",
      });
       if (window.opener) {
        window.opener.postMessage(this.formData, "*"); // 注意:实际使用中应指定具体来源
      }
    },
  },
}
javascript 复制代码
// index.vue 列表页
 mounted() {
    window.addEventListener("message", this.handleMessage);
  },
  beforeDestroy() {
    window.removeEventListener("message", this.handleMessage);
  },
  methods: {
      handleMessage(event) {
      if (event.origin !== window.origin) {
        console.warn("消息来源不安全,已忽略");
        return;
      }
      this.tableData.push(event.data);
      this.applyFilters();
    },
   } 

4. websocket

WebSocket 是一种网络通信协议,用于在客户端(通常是浏览器)和服务器之间建立全双工(full-duplex)的通信通道。与传统的 HTTP 协议不同,WebSocket 允许服务器主动向客户端发送消息,而客户端也可以随时向服务器发送消息,而无需重新建立连接。这种双向通信机制使得 WebSocket 非常适合实时通信场景,如在线聊天、实时数据更新、游戏等。

WebSocket 的特点

  1. 全双工通信

    • 客户端和服务器可以同时发送和接收消息,无需像 HTTP 那样每次都建立新的连接。
    • 一旦连接建立,数据可以在客户端和服务器之间双向流动。
  2. 低延迟

    • WebSocket 的通信基于 TCP,数据传输效率高,延迟低。
    • 适合对实时性要求较高的应用,如在线游戏、股票行情等。
  3. 轻量级

    • WebSocket 的消息头较小,通常只有 2-10 字节,相比 HTTP 的头部(通常几百字节)要轻量得多。
    • 减少了数据传输的开销。
  4. 支持多种数据格式

    • WebSocket 支持二进制数据和文本数据(如 JSON),可以灵活地传输不同类型的数据。
  5. 基于 TCP

    • WebSocket 基于 TCP 协议,确保数据的可靠传输。
    • 可以通过现有的 HTTP 端口(80 和 443)进行通信,方便穿透防火墙。
javascript 复制代码
// main.js
const ws = new WebSocket('ws://yourserver.com/path');
window.ws = ws; // 挂载到全局对象

ws.onopen = () => {
  console.log('WebSocket连接成功');
};

ws.onmessage = (event) => {
  console.log('收到服务器消息:', event.data);
};

ws.onerror = (error) => {
  console.error('WebSocket连接发生错误:', error);
};

ws.onclose = () => {
  console.log('WebSocket连接关闭');
};
javascript 复制代码
// A.vue
export default {
  methods: {
    sendMessage() {
      const message = 'Hello from Page A';
      window.ws.send(message); // 通过全局 WebSocket 发送消息
    }
  }
};
javascript 复制代码
// B.vue
export default {
  mounted() {
    window.ws.onmessage = (event) => {
      const message = event.data;
      console.log('收到消息:', message);
      this.receivedMessage = message; // 可以将消息存储到组件的 data 中
    };
  },
  data() {
    return {
      receivedMessage: null
    };
  }
};

5. IndexedDB + 定时器轮询

IndexedDB 介绍

IndexedDB 是一种浏览器内置的 NoSQL 数据库,用于在客户端存储大量结构化数据。它支持复杂的数据结构,包括对象和二进制数据,并且可以通过索引快速检索。

主要特性
  1. 存储量大 :IndexedDB 的存储空间通常比 localStorage 更大,一般不少于 250MB,甚至没有上限。
  2. 异步操作:所有操作都是异步的,不会阻塞主线程。
  3. 支持事务:通过事务确保数据操作的原子性和一致性。
  4. 键值对存储:数据以键值对的形式存储,每个对象存储(object store)类似于数据库中的表。
  5. 支持索引:可以通过索引加速数据检索。
  6. 同源限制:IndexedDB 遵循同源策略,只能访问当前域名下的数据库。
基本概念
  • 数据库(Database) :存储数据的容器,每个域名可以创建多个数据库。
  • 对象存储(Object Store) :类似于数据库中的表,用于存储数据。
  • 事务(Transaction) :用于执行数据库操作,确保数据一致性。
  • 索引(Index) :用于加速数据检索。
  • 游标(Cursor) :用于遍历对象存储中的记录。
bash 复制代码
// 简化indexedDB操作
npm install idb
javascript 复制代码
// edit.vue 创建数据页
import { openDB } from "idb";
export default {
 methods: {
    onSubmit() {
      this.$message({
        message: "创建成功!",
        type: "success",
      });
       const dbPromise = openDB("sharedDB", 2, {
        upgrade(db) {
          if (!db.objectStoreNames.contains("messages")) {
            db.createObjectStore("messages", {
              keyPath: "id",
              autoIncrement: true,
            });
          }
        },
      });

      sendMessage();
      const _that = this;
      async function sendMessage() {
        const db = await dbPromise;
        const tx = db.transaction("messages", "readwrite");
        const store = tx.objectStore("messages");
        const message = { content: JSON.stringify(_that.formData) };
        await store.add(message);
      }
    },
  },
 },
}
javascript 复制代码
// index.vue 列表页
 mounted() {
    this.handleIndexedDB();
  },
  beforeDestroy() {
     if (this.indexedDBTimer) {
      clearInterval(this.indexedDBTimer);
    }
  },
  methods: {
    handleIndexedDB() {
      const dbPromise = openDB("sharedDB", 2, {
        upgrade(db) {
          if (!db.objectStoreNames.contains("messages")) {
            db.createObjectStore("messages", {
              keyPath: "id",
              autoIncrement: true,
            });
          }
        },
      });
      const _that = this;
      async function pollMessages() {
        const db = await dbPromise;
        const tx = db.transaction("messages", "readonly");
        const store = tx.objectStore("messages");
        const messages = await store.getAll();
        if (messages.length) {
          if (
            !_that.tableData.some((item) => item.id === messages[0].id) &&
            !messages[0].id
          ) {
            _that.tableData.push(JSON.parse(messages[0].content));
            _that.applyFilters();
          }
        }
      }

      // 每隔一段时间轮询一次
      this.indexedDBTimer = setInterval(pollMessages, 1000);
    },
   } 

6. shared worker + 定时器轮询

Shared Worker 是一种特殊的 Web Worker,允许多个浏览器上下文(如多个标签页、窗口或 iframe)共享同一个后台线程。与普通的 Dedicated Worker 不同,Shared Worker 的生命周期独立于页面,即使所有连接的页面都关闭,Shared Worker 仍可继续运行。

核心特点

  1. 共享性:多个页面可以共享同一个 Shared Worker,实现数据共享和通信。
  2. 持久性 :即使所有连接的页面关闭,Shared Worker 也不会立即终止,除非显式调用 close()
  3. 单线程:Shared Worker 内部仍然是单线程执行,通过事件循环处理多个连接的请求。
  4. 同源限制:Shared Worker 的脚本文件必须遵守同源策略。
javascript 复制代码
// shared-worker.js
const connections = [];

onconnect = (event) => {
  const port = event.ports[0];
  connections.push(port);

  port.onmessage = (event) => {
    const message = event.data;
    console.log(`Shared Worker received message: ${message}`);

    // 转发消息到所有连接的端口(除了发送消息的端口)
    connections.forEach((conn) => {
      if (conn !== port) {
        conn.postMessage(message);
      }
    });
  };

  port.onclose = () => {
    console.log('Connection closed');
    connections.splice(connections.indexOf(port), 1);
  };
};
javascript 复制代码
//edit.vue 创建数据页
mounted:{
  this.worker = new SharedWorker('./shared-worker.js');
  this.worker.port.start();
},
 methods: {
    onSubmit() {
      this.$message({
        message: "创建成功!",
        type: "success",
      });
     this.worker.port.postMessage(this.formData);
    },
  },
}
javascript 复制代码
// index.vue 列表页
 mounted() {
   this.worker = new SharedWorker('./shared-worker.js');
    this.worker.port.start();
    this.worker.port.onmessage = (event) => {
      this.receivedMessage = event.data;
      this.$message.info(`收到新消息: ${this.receivedMessage}`);
    };
  },

7. service Worker

Service Worker 是一种运行在浏览器背景中的脚本,用于实现诸如离线支持、消息推送、后台同步等功能。它为 Web 应用提供了更强大的功能和更好的用户体验,尤其是在网络不稳定或离线状态下。

1. 主要功能

  1. 离线支持:通过缓存资源,Service Worker 可以在离线状态下为用户提供页面内容。
  2. 消息推送:即使页面未打开,Service Worker 也可以接收和处理推送消息。
  3. 后台同步:允许在后台同步数据,例如自动上传用户数据。
  4. 自定义网络请求:拦截和修改网络请求,实现缓存优先、网络优先等策略。
  5. 性能优化:通过缓存静态资源,减少网络请求,提升页面加载速度。

2. 工作原理

Service Worker 是一个独立于主线程运行的脚本,它通过事件驱动的方式工作,主要监听以下事件:

  • install:Service Worker 被安装时触发,通常用于缓存静态资源。
  • activate:Service Worker 被激活时触发,用于清理旧的缓存。
  • fetch:拦截网络请求,允许自定义响应。
  • push:接收推送消息。
  • sync:处理后台同步任务。

3. 生命周期

  1. 注册:通过 JavaScript 在页面中注册 Service Worker。
  2. 安装(Install) :Service Worker 被安装到浏览器中。
  3. 激活(Activate) :Service Worker 被激活并接管页面。
  4. 运行(Running) :Service Worker 开始处理事件。
  5. 更新:当 Service Worker 脚本更新时,会重新安装并激活。
javascript 复制代码
// service-worker.js
self.addEventListener('message', (event) => {
  const message = event.data;
  console.log('Service Worker received message:', message);

  // 将消息转发到所有客户端(标签页)
  self.clients.matchAll().then((clients) => {
    clients.forEach((client) => {
      if (client.id !== event.source.id) {
        client.postMessage(message);
      }
    });
  });
});
javascript 复制代码
//edit.vue 创建数据页
mounted:{
this.registerServiceWorker();
},
 methods: {
    async registerServiceWorker() {
      if ('serviceWorker' in navigator) {
        await navigator.serviceWorker.register('/service-worker.js');
        console.log('Service Worker 注册成功');
      } else {
        console.warn('Service Worker 不支持');
      }
    },
    onSubmit() {
      this.$message({
        message: "创建成功!",
        type: "success",
      });
      if (navigator.serviceWorker.controller) {
        navigator.serviceWorker.controller.postMessage(this.formData);
      }
    },
  },
}
javascript 复制代码
// index.vue 列表页
 mounted() {
    this.registerServiceWorker();
    this.setupMessageListener();
  },
  methods: {
    async registerServiceWorker() {
      if ('serviceWorker' in navigator) {
        await navigator.serviceWorker.register('/service-worker.js');
        console.log('Service Worker 注册成功');
      } else {
        console.warn('Service Worker 不支持');
      }
    },
    setupMessageListener() {
      navigator.serviceWorker.addEventListener('message', (event) => {
        console.log(event)
      });
    }
  }

8. BroadCast Channel

Broadcast Channel 是一种用于在同一来源(协议、域名、端口)下不同浏览器上下文(如标签页、iframe、Worker、Service Worker)之间进行消息广播的 Web API。它提供了一种简单、高效且实时的通信机制,适用于跨窗口或标签页的实时数据同步。

工作原理

  1. 创建频道 :通过 new BroadcastChannel(name) 创建一个广播频道实例,name 是一个字符串,用于标识频道。
  2. 发送消息 :调用 channel.postMessage(message) 方法向频道发送消息,消息可以是任意类型的数据。
  3. 接收消息 :在其他上下文中创建相同名称的频道实例,并通过 channel.onmessageaddEventListener('message', callback) 监听消息。
  4. 关闭频道 :不再需要时,调用 channel.close() 关闭频道以释放资源。

核心特性

  • 同源策略:仅支持同源上下文之间的通信,确保安全性。
  • 实时通信:消息传递几乎是即时的,适合需要即时同步的场景。
  • 简单易用:API 简洁明了,易于实现和维护。

使用场景

  1. 用户状态同步:当用户在一个标签页中登录或注销时,其他标签页可以实时同步状态。
  2. 实时数据更新:在多个标签页中实时显示更新的数据,例如股票行情或聊天消息。
  3. 多标签页协同工作:用户在多个标签页中操作同一个应用时,各标签页可以协同工作。

注意事项

  1. 不支持跨域BroadcastChannel 仅适用于同源上下文,无法用于跨域通信。
  2. 不持久化消息:页面刷新或关闭后,之前的消息无法再获取。
  3. 浏览器兼容性 :现代浏览器普遍支持 BroadcastChannel,但在使用时需检查兼容性。
javascript 复制代码
// broadcastChannel.js
const broadcastChannel = {
  channel: null,
  createChannel(channelName) {
    this.channel = new BroadcastChannel(channelName);
  },
  sendMessage(message) {
    if (this.channel) {
      this.channel.postMessage(message);
    }
  },
  receiveMessage(callback) {
    if (this.channel) {
      this.channel.onmessage = (event) => {
        callback(event.data);
      };
    }
  },
  closeChannel() {
    if (this.channel) {
      this.channel.close();
      this.channel = null;
    }
  }
};

export default broadcastChannel;
javascript 复制代码
//edit.vue 创建数据页
import broadcastChannel from '@/broadcastChannel.js';

mounted:{
    broadcastChannel.createChannel('myChannel');
}, 
beforeDestroy() {
    broadcastChannel.closeChannel();
},
 methods: {
    onSubmit() {
      this.$message({
        message: "创建成功!",
        type: "success",
      });
      broadcastChannel.sendMessage(this.formData);
    },
  },
}
javascript 复制代码
// index.vue 列表页
import broadcastChannel from '@/broadcastChannel.js';
 mounted() {
   broadcastChannel.createChannel('myChannel');
    broadcastChannel.receiveMessage((data) => {
      console.log(data)
    });
  },
  beforeDestroy() {
    broadcastChannel.closeChannel();
  }
相关推荐
deming_su30 分钟前
第六课:数据库集成:MongoDB与Mongoose技术应用
javascript·数据库·mongodb·node.js·html
几何心凉1 小时前
如何解决Vue组件间传递数据的问题?
前端·javascript·vue.js
七灵微1 小时前
【前端】BOM & DOM
前端·javascript·servlet
yqcoder1 小时前
ES6 解构详解
前端·javascript·es6
WolvenSec1 小时前
Web基础:HTML快速入门
前端·html
青阳流月1 小时前
js读书笔记(补充知识)
前端·javascript
木心操作1 小时前
css动画实现铃铛效果
前端·css·css3
亦良Cool1 小时前
将Exce中工作簿的多个工作表拆分为单独的Excel文件
前端·html·excel
Moment3 小时前
从方案到原理,带你从零到一实现一个 前端白屏 检测的 SDK ☺️☺️☺️
前端·javascript·面试
鱼樱前端3 小时前
Vue3 + TypeScript 整合 MeScroll.js 组件
前端·vue.js