electron+vue3全家桶+vite项目搭建【16】electron多窗口,pinia状态无法同步更新问题解决

news2025/4/6 11:45:12

文章目录

    • 引入
    • 实现效果展示
    • 问题展示
    • 解决方案
      • 思路整理
      • 1.主进程添加handle
      • 2.编写pinia插件
      • 3.完善pinia插件
      • 4.最终实现效果

引入

pinia是vue3官方支持的全局状态管理工具,简单易用,但是electron的多窗口虽然加载的页面是单个路由,但其实已经是另一个浏览器,所有状态无法同步更新,接下来我们写一个pinia的插件结合ipc通信实现多窗口同步更新pinia的状态。

demo项目地址

实现效果展示

请添加图片描述

问题展示

我们创建两个窗口,其中一个窗口设置counterStore.counter 增加,另一个窗口并不会跟随变化!!

如下所示:

请添加图片描述

解决方案

思路整理

1.写一个pinia插件,拦截pinia的状态修改,并在状态对象初始化时补充一个版本状态【用来控制当前状态是否需要更新】

2.初始化pinia插件的时候添加ipcRender监听,如果状态改变了通知主进程,【并且修改版本号,版本号在缓存中也存一份】

3.主进程监听状态改变,如果状态改变就通知其他窗口同步更新

4.窗口同步更新时对比版本号,对 大于、小于、等于版本号分别处理

注意:引入版本号的主要原因是渲染进程监听状态改变,进行状态同步的时候,又会触发状态修改的事件,接着通知主进程进行同步,【循环处理,不过实测,当两次更新内容完全一致时,pinia就不会触发状态修改事件,但n个窗口仍会触发 n*(n-1)次状态修改!!】所以我们引入一个版本号在状态修改时判断是否需要通知主进程进行同步

1.主进程添加handle

首先我们在主进程中添加监听,处理pinia的状态变化,可以看到这里获取三个参数

  • storeName:更新的store的名称,对应defineStore()的第一个参数
  • keys:更新内容的对象key集合 【json序列化后】
  • values:更新内容的对象值集合 【json序列化后】

electron\main\index.ts

/**pinia多窗口共享 */
ipcMain.handle(
  "pinia-store-change",
  (event, storeName: string, keys, values) => {
    // 遍历window执行
    for (const currentWin of BrowserWindow.getAllWindows()) {
      const webContentsId = currentWin.webContents.id;
        // 只更新其他窗口 【这里排除通知主进程的窗口】
      if (webContentsId !== event.sender.id) {
        currentWin.webContents.send("pinia-store-set", storeName, keys, values);
      }
    }
  }
);

2.编写pinia插件

赶时间的话可直接用后面完善的pinia插件,这里主要是演示问题,解释为什么要用版本进行控制

pinia官网

可以看到pinia插件的说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3Pj0Y7w-1683017496931)(D:\我的坚果云\blog\imgs\image-20230428094713910.png)]

1.我们在src/store目录下新建plugins目录,然后创建一个shareStorePlugin.ts文件:

  • 我们在状态修改时通知主进程状态修改了,并且监听状态修改了,就以 keys,values进行传输修改的内容

  • 我们在store更新的时候输出主动更新

  • 如果更新发生在ipc的监听中时,我们输出被动更新

src\store\plugins\shareStorePlugin.ts

import { ipcRenderer } from "electron";
import { PiniaPluginContext } from "pinia";

declare module "pinia" {
  export interface PiniaCustomProperties {
    storeUpdateVersion: number; // 标记store变更的版本
  }
}

// 处理electron多窗口,pinia共享问题
export function shareStorePlugin({ store }: PiniaPluginContext) {
  // 初始化本地缓存版本
  const storeName: string = store.$id;
  // 监听数据变化
  store.$subscribe(() => {
    console.log(`主动更新 ${storeName} 的状态`);
    // 通知主线程更新
    ipcRenderer.invoke(
      "pinia-store-change",
      storeName,
      Object.keys(store.$state),
      Object.values(store.$state)
    );
  });

  // 监听数据同步修改
  ipcRenderer.on(
    "pinia-store-set",
    (event, targetStoreName: string, keys: string[], values: any[]) => {
      // 监听到状态改变后,同步更新状态
      if (storeName === targetStoreName) {
        console.log("被动更新状态:" + storeName);

        /// 更新各个key对应的值的状态
        for (let i = 0; i < keys.length; i++) {
          store.$state[keys[i]] = values[i];
        }
      }
    }
  );
}

2.在pinia中注册插件,我们调整src/store/index.ts:

import { createPinia } from "pinia";
import { shareStorePlugin } from "./plugins/shareStorePlugin";

const pinia = createPinia();

// 添加状态共享插件
pinia.use(shareStorePlugin);

export default pinia;

3.我们看一下测试效果

我们打开三个窗口,分别点击三个窗口中的counter自增,可以看到值已经能够正常同步,但有一个问题,当我们点击一个窗口的自增时,内容是这样的:

  • 被点击窗口 , 主动更新一次,被动更新两次
  • 其他窗口,被动更新一次,主动更新一次,被动更新一次

由此,我们可知,我们窗口被主进程通知更新状态的监听中,修改store的值,又会触发store值改变的事件,然后又会通知主进程告知其他窗口同步更新,但是当我们两次修改值完全一致时,则不会触发store值改变的事件!!

**注意:**也就是说,当前写法,如果我们有n个窗口,我们值修改一个值,在这个窗口中,这个值就会被动更新 n-1次,而主进程会被通知n次
请添加图片描述

3.完善pinia插件

虽然上面我们已经能够实现状态管理同步,但如果窗口很多的化,循环调用多次是很可怕的,所以我们使用一个版本号来控制状态更新。

  • 用版本号,而不是用 true/false变量来管控,主要考虑极端情况,窗口被动更新还没同步,又触发了主动更新时,方便根据版本号进行 状态同步的微调

  • 这里通过版本号来判断状态更新时,如果是被动更新导致的,则不需要通知主进程

  • 我们在ipcRender.invoke执行前输出 主动更新

  • 在ipcRender.on中输出 被动更新

import { ipcRenderer } from "electron";
import cacheUtils from "@/utils/cacheUtils";
import { PiniaPluginContext } from "pinia";

// 预设本地store版本缓存时间为50s  实际开发中可以设置很大,缓存时间的限制,目的是为了让版本归零,避免自增超过上限
const STORE_CACHE_TIME = 50;
// 设置本地store缓存的key
const STORE_CACHE_KEY_PREFIX = "storeUpdateVersion_";

declare module "pinia" {
  export interface PiniaCustomProperties {
    storeUpdateVersion: number; // 标记store变更的版本
  }
}

/**获取本地缓存的store的修改版本 */
function getLocalStoreUpdateVersion(storeCacheKey: string) {
  let currentStoreUpdateVersion: number = cacheUtils.get(storeCacheKey);
  // 如果本地没有,就初始化一个
  if (
    currentStoreUpdateVersion === null ||
    currentStoreUpdateVersion === undefined
  ) {
    currentStoreUpdateVersion = 0;
    cacheUtils.set(storeCacheKey, currentStoreUpdateVersion, STORE_CACHE_TIME);
  }
  return currentStoreUpdateVersion;
}

// 处理electron多窗口,pinia共享问题
export function shareStorePlugin({ store }: PiniaPluginContext) {
  // 初始化本地缓存版本
  const storeName: string = store.$id;
  /// 缓存key
  const storeCacheKey = STORE_CACHE_KEY_PREFIX + storeName;
  let currentStoreUpdateVersion: number =
    getLocalStoreUpdateVersion(storeCacheKey);
  // 初始化同步store版本
  store.storeUpdateVersion = currentStoreUpdateVersion;

  // 监听数据变化
  store.$subscribe(() => {
    // 获取本地存储的最新状态
    currentStoreUpdateVersion = cacheUtils.get(storeCacheKey);
    /// 如果本地缓存过期,则重置一个缓存,并且通知主进程让其他窗口更新状态
    if (
      currentStoreUpdateVersion === null ||
      currentStoreUpdateVersion === undefined
    ) {
      currentStoreUpdateVersion = 0;
      cacheUtils.set(
        storeCacheKey,
        currentStoreUpdateVersion,
        STORE_CACHE_TIME
      );
      store.storeUpdateVersion = currentStoreUpdateVersion;
      console.log(`主动更新 ${storeName} 的状态`);
      // 通知主线程更新
      ipcRenderer.invoke(
        "pinia-store-change",
        storeName,
        Object.keys(store.$state),
        Object.values(store.$state)
      );
    } else {
      // 如果版本一致,则增加版本号,且更新本地存储版本 ,并且通知主线程告知其他窗口同步更新store状态
      if (store.storeUpdateVersion === currentStoreUpdateVersion) {
        store.storeUpdateVersion++;
        cacheUtils.set(
          storeCacheKey,
          store.storeUpdateVersion,
          STORE_CACHE_TIME
        );
        console.log(`主动更新 ${storeName} 的状态`);

        // 通知主线程更新
        ipcRenderer.invoke(
          "pinia-store-change",
          store.$id,
          Object.keys(store.$state),
          Object.values(store.$state)
        );
      } else {
        // 如果当前store的版本大于本地存储的版本,说明本地版本重置了【过期重新创建】,此时重置store的版本
        // 如果当前store的版本小于本地存储的版本,说明是被动更新引起的state变动回调,此时仅更新版本即可
        store.storeUpdateVersion = currentStoreUpdateVersion;
      }
    }
  });

  // 监听数据同步修改
  ipcRenderer.on(
    "pinia-store-set",
    (event, targetStoreName: string, keys: string[], values: any[]) => {
      // 监听到状态改变后,同步更新状态
      if (storeName === targetStoreName) {
        console.log("被动更新状态:" + storeName);

        /// 更新各个key对应的值的状态
        for (let i = 0; i < keys.length; i++) {
          store.$state[keys[i]] = values[i];
        }
      }
    }
  );
}

4.最终实现效果

  • 如果本地没有缓存,第一次调用可能会出现一次循环调用
  • 正常情况下,一次窗口的状态改变,只会触发一次主动更新,其他窗口只会触发一次被动更新
    请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/483186.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制

&#x1f389;欢迎来到Labview专栏~四足爬行机器人仿真与控制 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;Labview-3D虚拟平台 文章作者技术和水平有限&#xff0c;如果文中出现错误&#…

【python】keras包:深度学习

Part 0. 环境配置 与 学习数据下载 keras包 与 tensorflow包 WinR &#xff0c;输入指令&#xff1a; pip install tensorflow pip install keras 推荐镜像&#xff1a;-i https://pypi.tuna.tsinghua.edu.cn/simple/ 关于包 keras包相当于是 tensflow 包的前端 tensflow包…

15 | Qt的自定义信号

1 前提 Qt 5.14.2 2 具体操作 2.1 自定义信号 2.1.1 UI界面设置 2.1.1.1 widget.ui 2.1.1.2 setdialog.ui 2.1.2 headers 2.1.2.1 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui {class Widget; } QT_END_NAMESP…

105-Linux_Libevent库的安装与使用

文章目录 一.Libevent 概述1.Libevent的特点2.Libevent使用模型3.Libevent 支持的事件类型 二.Libevent的安装三.Libevent简单使用实例 一.Libevent 概述 Libevent 是开源社区的一款高性能的 I/O 框架库&#xff0c;使用 Libevent 的著名案例有&#xff1a;高性能的分布式内存…

操作系统之死锁处理策略

概念 一、什么是死锁 哲学家进程问题中&#xff0c;都在等待另外的哲学家放弃另一只筷子&#xff0c;造成了都不能用餐的现象&#xff0c;互相等待对方的资源 二、死锁、饥饿、死循环区别 三、死锁产生的条件 1、互斥条件 只有对互斥使用的资源的争抢才能导致死锁&#xff0…

shell编程、makefile学习笔记

windows :\r\n linux:\n 1.shell介绍 1.1、shell是操作系统的终端命令行 (1)shell可以理解为软件系统提供给用户操作的命令行界面&#xff0c;可以说它是人机交互的一种方式。 (2)我们可以使用shell和操作系统、uboot等软件系统进行交互。具体来说就是我们通过shell给软件…

【SpringBoot 应用打包与部署】

SpringBoot 应用打包与部署 笔记记录 1. Jar包方式打包与部署1.1 添加maven打包插件1.2 双击package打包成功1.3 IDEA中运行jar文件 2. War包方式打包与部署2.1 声明打包方式为War包2.2 双击package2.3 将打包好的war包放在Tomcat的webapps目录下 1. Jar包方式打包与部署 1.1 …

嵌入式开发--无刷电机学习4--SVPWM

SVPWM空间矢量脉宽调节 这张图是基于α和β坐标系&#xff0c;也就是定子磁场坐标系&#xff0c;图中的Uout就是定子磁场的空间矢量&#xff0c;它的角度表示定子线圈产生磁场的方向&#xff0c;长度表示磁场的强度&#xff0c;以电机匀速旋转为例&#xff0c;FOC控制的目标就是…

【Java】抽象类接口Object类

目录 1.抽象类 2.接口 2.1实现多个接口 2.2接口之间的关系 2.3接口使用实例 2.3.1Comparable接口 2.3.2Comparator接口 2.3.2Clone接口 2.4抽象类与接口的区别 3.Object类 3.1getClass方法 3.2equals方法 3.3hashcode方法 1.抽象类 定义&#xff1a;抽象方法&…

[Cursor Tool] 面向编程的ChatGPT工具的入门使用指南

文章目录 0. 面向编程的ChatGPT工具的入门使用指南1. Cursor的下载和安装2. Cursor的基本功能的使用2.1 关于Cursor的Chat模式2.2 关于Cursor的Edit模式 3 关于Cursor的项目级应用4 使用Cursor帮助我们从项目的设计出发来为我们提供建议 0. 面向编程的ChatGPT工具的入门使用指南…

车载软件架构——闲聊几句AUTOSAR BSW(三)

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 我特别喜欢一个老话,来都来了。我觉得这就是松弛感,既然来了,就开心起来吧!松弛感来自于专注,焦虑不是靠克服的,是靠忘记的,当你很专注做一件事的时候…

剪枝与重参:课程总结

目录 课程总结前言1. 基础快速入门2. 基于VGG的模型剪枝3. 英伟达2-4剪枝方案4. YOLOv8剪枝5. ACNet、DBB、RepVGG重参个人总结 课程总结 前言 手写AI推出的全新模型剪枝与重参课程。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课程主要是课程总结&#xff0c;对之前学…

设计模式之代理模式(静态代理动态代理)

目录 1、什么是代理模式 2、代理模式的结构 3、代理模式的实现 3.1 静态代理和动态代理概念 3.2 静态代理 3.3 动态搭理 3.3.1 代码实现 3.3.2 Proxy类讲解 4、动态代理VS静态代理 5、代理模式优缺点 1、什么是代理模式 由于某些原因需要给某对象提供一个代理以控制对…

【开发工具】 Adobe 2022 最详细的安装方法 就是这么简单 绿色 安全方便

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&#x1…

vulnhub靶场之Black-Widow-final

1.信息收集 探测存活主机&#xff0c;发现192.168.239.177存活 对目标主机192.168.239.177进行端口扫描&#xff0c;发现存活22、80、111、2049、3128等端口 在浏览器中访问http://192.168.239.177&#xff0c;并查看源码&#xff0c;未发现有用的信息 对http://192.168.23…

【openGauss实战11】性能报告WDR深度解读

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

c++题库练习

19. 以下叙述中正确的是&#xff08;&#xff09; A 使用typedef说明新类型名时&#xff0c;其格式是&#xff1a; typedef 新类型名 原类型名; B 在程序中&#xff0c;允许用typedef来说明一种新的类型名 C 使用typedef 说明新类型名时&#xff0c;后面不能加分号 D 在使用typ…

three.js学习 06 - 结合GSAP(补间动画)设置各种动画效果(运动效果与双击暂停动画等效果)

1. GSAP简介 GSAP&#x1f44d;&#x1f3fc;是前端业内非常有名的一个动效库&#xff0c;有大量的优秀的网站都在使用它。它不仅能在原生JS的环境下使用&#xff0c;也能配合各种当前流行的框架进行使用。 通过使用它&#xff0c;非常多原本实现起来很有难度的交互动画效果&a…

计算机必读基础书籍

计算机必读数据 一&#xff1a;故事背景1.1 前言1.2 提示 二&#xff1a;计算机组成2.1 是什么2.2 有什么2.2.1 计算机系统概述2.2.2 数据信息的表示2.2.3 运算方法与运算器2.2.4 存储系统2.2.5 指令系统2.2.6 中央处理器2.2.7 指令流水线2.2.8 总线系统2.2.9 输入输出 2.3 思维…

[MAY DAY]五一综合训练 之——最值问题

文章目录 > **## * 要赋值 &#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; *** %#&#xffe5;#%#*&&#xff01;&#xff01;&#xff01;&#xff01;要赋值一、双指针求最大连续和双指针算法分析&#xff1a; 注意&#xff…