Фреймворк разработки Sugar-Electron Lightweight на основе Electron

Electron

предисловие

Сегодня я представляю вам фреймворк приложений собственной разработки под названием Sugar-Electron, основанный на платформе разработки настольных компьютеров Electron, в надежде улучшить стабильность приложений Electron и помочь командам разработчиков снизить затраты на разработку и обслуживание.

Автор использует Electron как десктопное приложение уже 3 года, и за это время я столкнулся со многими большими и маленькими ямами. Но если подытожить, то самая большая проблема — это стабильность приложения и эффективность разработки. Мы ожидаем, что благодаря этой структуре приложение можно будет оптимизировать в обоих аспектах.

Адрес исходного кода проекта:GitHub.com/сахарные турбины…

Если у вас есть какие-либо вопросы, вы можете отсканировать код, чтобы присоединиться к групповому чату WeChat, чтобы обсудить

О стабильности приложения

Мы знаем, что приложения Electron имеют три основных модуля.

  • основной процесс
  • процесс рендеринга
  • межпроцессного взаимодействия

Так как мы относимся к многооконному (процессу мультирендеринга) приложению, то все общие служебные модули окна будем прописывать в основной модуль процесса, что будет таить в себе скрытую опасность для стабильности всей программы.

В Electron основной процесс контролирует жизненный цикл всей программы, а также отвечает за управление создаваемыми им процессами рендеринга. Как только возникает проблема с кодом основного процесса, возникают следующие условия.

  • Основной процесс аномального коллапса не пойман, прямой результат выхода приложения.
  • Происходит основной процесс, который напрямую приводит ко всем процессам рендеринга, а UI находится в блокирующем неотвечающем состоянии.

Поэтому в Sugar-Electron мы ввели концепцию сервисного процесса, рассчитывая перенести бизнес-код, изначально написанный в основном процессе, в сервисный процесс (по сути, процесс рендеринга), чтобы сбой, вызванный этими кодами, не сделал вся Программа выходит. Диспетчер основного процесса может перезапустить процесс и восстановить состояние до сбоя при сбое Сервиса, тем самым повысив стабильность и доступность всей программы.

Об эффективности разработки

Electron — это платформа для разработки настольных компьютеров, которая предоставляет основу для разработки настольных приложений. Однако в самом фреймворке отсутствуют условности, поэтому при использовании Electron для разработки приложений системные модули будут разделены на различные типы, а код будет написан по-разному, что значительно увеличит стоимость обучения и снизит эффективность разработки. Разработчики. сахар-электрон разработан в соответствии с соглашением, что снижает затраты на совместную работу команды и повышает эффективность.

характеристика

  • Встроенный модуль межпроцессного взаимодействия, поддержка ответа на запрос, методы публикации и подписки
  • Встроенный модуль обмена состоянием между процессами, поддержка изменения синхронизации состояния, мониторинг изменения состояния
  • Встроенный модуль управления процессами, поддержка централизованного управления модулями процессов
  • Встроенный модуль управления конфигурацией, поддержка разработки, тестирования, переключение конфигурации производственной среды
  • Встроенный подключаемый модуль для поддержки расширяемого механизма подключаемых модулей.
  • Фреймворк менее навязчив, а стоимость доступа к проекту и трансформации низкая.
  • постепенное развитие

Принципы дизайна

1. Весь сахарный электрон разработан вокруг процесса рендеринга.Основной процесс действует только как демон для управления процессами (создание, удаление, мониторинг исключений) и планирования (связь процессов, мост функции состояния).

Основной процесс не обрабатывает бизнес-логику, преимущества такой конструкции:

  1. Может предотвратить сбой основного процесса из-за неперехваченного исключения, что приведет к завершению работы приложения.
  2. Избегайте блокировки основного процесса, что приведет к блокировке всех процессов рендеринга, что приведет к блокировке пользовательского интерфейса и зависанию.

2. Все бизнес-модули сахара-электрона являются процессами рендеринга. Мы знаем, что к процессам нельзя получить доступ напрямую.Чтобы сделать вызовы между процессами такими же удобными, как и прямые вызовы между модулями с одним и тем же потоком, Sugar-electron предоставляет следующие три модуля:

  1. Модуль межпроцессного взаимодействия
  2. Модуль обмена состоянием между процессами
  3. Модуль управления процессами

3. Для обеспечения того, чтобы ядро ​​​​фреймворка было достаточно оптимизированным, стабильным и эффективным, очень важна возможность расширения каркаса.По этой причине, сахар-электрон предоставляет настраиваемый механизм подключаемых модулей для расширения возможностей каркаса, что также может способствовать повторному использованию бизнес-логики и даже расширению экосистемы.

Ниже приводится логическое представление фреймворка: image

сахара-электрон разработан на основе микроядерной архитектуры, как показано на следующем рисунке:

image

Ядро его структуры состоит из семи модулей:

  • Базовый класс процесса BaseWindow
  • Класс сервисного процесса Сервис
  • Управление процессами WindowCenter
  • Межпроцессное взаимодействие ipc
  • Хранилище обмена состоянием между процессами
  • конфигурация центра конфигурации
  • Плагины управления плагинами

Установить

npm i sugar-electron

строительные леса

npm i sugar-electron-cli -g

sugar-electron-cli init

Основные функции

Базовый класс процесса — BaseWindow

Базовый класс процесса BaseWindow основан на вторичной инкапсуляции BrowserWindow, и сахар-электрон использует BaseWindow в качестве носителя для агрегирования всех основных модулей фреймворка.

Например

Создайте процесс рендеринга с помощью BrowserWindow

// 在主进程中.
const { BrowserWindow } = require('electron')
let win = new BrowserWindow({ width: 800, height: 600, show: false });
win.on('ready-to-show', () => {})
win.loadURL('https://github.com');

Создайте процесс рендеринга с помощью BaseWindow

// 在主进程中.
const { BaseWindow } = require('sugar-electron');
let win = new BaseWindow('winA', {
  url: 'https://github.com' // BaseWindow 特有属性,默认打开的页面
  width: 800, ght: 600, show: false
});
win.on('ready-to-show', () => {})
const browserWindowInstance = winA.open();

Класс сервисного процесса - Сервис

В реальном развитии бизнеса нам нужен процесс, несущий функции общего модуля бизнес-процесса, для этого и рождается Сервис. Экземпляр процесса службы на самом деле является процессом рендеринга, но разработчику нужно только передать js-файл начальной записи, чтобы создать процесс рендеринга, а BaseWindow — это то же самое, что и совокупность всех основных модулей фреймворка.

Например

// -----------------------主进程-----------------------
const service = new Service('service', path.join(__dirname, 'app.js'), true);
service.on('success', function () {
    console.log('service进程启动成功');
});
service.on('fail', function () {
    console.log('service进程启动异常');
});
service.on('crashed', function () {
    console.log('service进程崩溃'); // 对应webContents.on('crashed')
});
service.on('closed', function () {
    console.log('service进程关闭'); // 对应browserWindow.on('closed')
});

Технологическая связь — IPC

Являясь основным модулем межпроцессного взаимодействия, ipc поддерживает три метода взаимодействия:

  1. запрос-ответ (между рендерером)
  2. Публикация-подписка (интеррендерер)
  3. Основной процесс взаимодействует с процессом рендеринга

ответ на запрос

Логический вид:

image

Например

// 服务进程service
const { ipc } = require('sugar-electron');  
// 注册响应服务A1
ipc.response('service-1', (json, cb) => {
    console.log(json); // { name: 'winA' }
    cb('service-1响应');
});

// 渲染进程winA
const { ipc, windowCenter } = require('sugar-electron');  

const r1 = await windowCenter.service.request('service-1', { name: 'winA' });
console.log(r1); // service-1响应
// 等同
const r2 = await ipc.request('service', 'service-1', { name: 'winA' });
console.log(r2); // service-1响应

аномальный

Код состояния 1 Инструкция 2
1 процесс не найден
2 Служба регистрации процессов не найдена
3 тайм-аут

опубликовать подписаться

Логический вид:

image

Например

// 服务进程service
const { ipc } = require('sugar-electron');
setInterval(() => {
    ipc.publisher('service-publisher', { name: '发布消息' });
}, 1000);

// winA
const { ipc, windowCenter } = require('sugar-electron');  

// 订阅
const unsubscribe = windowCenter.service.subscribe('service-publisher', (json) => {
    console.log(json); // { name: '发布消息' }
});
// 等同
const unsubscribe = ipc.subscribe('service', service-publisher', (json) => {
    console.log(json); // { name: '发布消息' }
});


// 取消订阅 
unsubscribe();
// 等同
windowCenter.service.unsubscribe('service-publisher', cb);

Связь между основным процессом и процессом рендеринга (имя процесса «main», зарезервированное для основного процесса)

Концепция дизайна структуры Sugar-electron Все бизнес-модули завершаются каждым процессом рендеринга, поэтому в основном нет функции связи с основным процессом, но это не исключает сцены, где основной процесс взаимодействует с процессом рендеринга. Таким образом, модуль связи процесса сахара-электрона поддерживает интерфейс связи с основным процессом.

Например

// 主进程
const { ipc } = require('sugar-electron');
ipc.response('test', (data, cb) => {
    console.log(data); // 我是渲染进程
    cb('我是主进程')
});
 
// winA
const { windowCenter } = require('sugar-electron');
const res = windowCenter.main.request('test', '我是渲染进程');
console.log(res); // 我是主进程

// 或者
const res = ipc.request('main', 'test', '我是渲染进程');
console.log(res); // 我是主进程

Управление процессами — WindowCenter

Сахар-электрон Все бизнес-модули представляют собой процесс рендеринга. Мы знаем, что между процессами не доступна напрямую, все с модулем управления процессом.

Все процессы рендеринга могут найти соответствующий процесс рендеринга в windowCenter по уникальному ключу, соответствующему имени процесса, так что вызовы между процессами так же удобны, как и прямые вызовы между модулями одного и того же потока.

Например

Требование: открыть winB в winA и установить окно B setSize(400, 400) после завершения инициализации webContents winB

// 主进程
const { BaseWindow, Service, windowCenter } = require('sugar-electron');
// 设置窗口默认设置,详情请参考Electron BrowserWindow文档
BaseWindow.setDefaultOption({
  show: false
});
 
// winA
const winA = new BaseWindow('winA', {
   url: `file://${__dirname}/indexA.html`
});
 
// winB
const winB = new BaseWindow('winB', {
   url: `file://${__dirname}/indexB.html`
});
 
// 创建winA窗口实例
windowCenter.winA.open(); // 等同于winA.open();
// winA
const { windowCenter } = require('sugar-electron');
const winB = windowCenter.winB;
// 创建winB窗口实例
await winB.open();
// 订阅窗口创建完成“ready-to-show”
const unsubscribe = winB.subscribe('ready-to-show', () => {
   // 解绑订阅
   unsubscribe();
  // 设置winB size[400, 400]
   const r1 = await winB.setSize(400, 400);
   // 获取winB size[400, 400]
   const r2 = await winB.getSize();
   console.log(r1, r2);
});

==Примечание: дескриптор сервисного процесса также можно получить через windowCenter==

Разделение состояний между процессами — хранилище

Сахар-электрон — это архитектура с несколькими процессами.В бизнес-системе неизбежно совместное состояние нескольких бизнес-процессов. Поскольку память между процессами независима друг от друга и не взаимодействует друг с другом, структура сахара-электрона интегрирует модуль обмена состоянием процесса.

Модуль обмена состоянием процесса разделен на две части:

  • Основной процесс объявляет общие данные состояния
  • Рендеринг настроек процесса, получение общих данных о состоянии и подписка на изменения состояния

Например

// 主进程——初始化申明state
const { store } = require('sugar-electron');
store.createStore({
    state: {
        name: '我是store'
    },
    modules: {
        moduleA: {
            state: {
                name: '我是moduleA'
            }
        },
        moduleB: {
            state: {
                name: '我是moduleB'
            },
            modules: {
                moduleC: {
                    state: {
                        name: '我是moduleC'
                    }
                }
            }
        }
    }
});
// 渲染进程A,订阅state变化
const { store } = require('sugar-electron');
console.log(store.state.name); // 我是store
// 订阅更新消息
const unsubscribe = store.subscribe((data) => {
    console.log(store.state.name); // 改变state
    unsubscribe(); // 取消订阅
});
 
// moduleA
const moduleA = store.getModule('moduleA');
console.log(moduleA.state.name); // 我是moduleA
const unsubscribeA = moduleA.subscribe((data) => {
    console.log(moduleA.state.name); // 改变moduleA
    unsubscribeA(); // 取消订阅
});

// 渲染进程B,设置state
const { store } = require('sugar-electron');
await store.setState({
    'name': '改变state'
});
 
// moduleA
const moduleA = store.getModule('moduleA');
await moduleA.setState({
    'name': '改变moduleA'
});

конфигурация - конфигурация

сахарный электрон предоставляет конфигурацию с несколькими средами, которую можно переключать в соответствии с переменными среды, а конфигурация среды генерации загружается по умолчанию.

config
|- config.base.js     // 基础配置
|- config.js          // 生产配置
|- config.test.js     // 测试配置——环境变量env=test
|- config.dev.js      // 开发配置——环境变量env=dev

блок-схема:

image

Например

// 主进程
const { config } = require('sugar-electron');
global.config = config.setOption({ appName: 'sugar-electron', configPath: path.join(__dirname, 'config') });

// 渲染进程
const { config } = require('sugar-electron');
console.log(config);

==Примечания:==

  • Файл конфигурации AppData/appName config.json { "env": "переменные среды", "config": "конфигурация" }
  • сахар-электрон автоматически инициализируется по умолчанию в соответствии с конфигурацией корневого каталога

Плагины - плагины

Хороший фреймворк неотделим от масштабируемости и повторного использования фреймворка в бизнесе. Разработчики настраивают и настраивают плагины через модуль плагинов для установки плагинов.

==Использование плагина требует трех шагов:==

  1. индивидуальный пакет
  2. config проблема с конфигурацией каталога plugins.js установка плагина конфигурации
  3. Используйте плагины

Пакет плагинов

// 1、自定义封装ajax插件adpter
const axios = require('axios');
const apis = {
    FETCH_DATA_1: {
        url: '/XXXXXXX1',
        method: 'POST'
    }
}
 
module.exports = {
    /**
     * 安装插件,自定义插件必备
     * @ctx [object] 框架上下文对象{ config, ipc, store, windowCenter, plugins }
     * @params [object] 配置参数
    */
    install(ctx, params = {}) {
        // 通过配置文件读取基础服务配置
        const baseServer = ctx.config.baseServer;
        return {
            async callAPI(action, option) {
                const { method, url } = apis[action];
                try {
                    // 通过进程状态共享SDK获取用户ID
                    const token = ctx.store.state.token;
                    const res = await axios({
                        method,
                        url: `${baseServer}${url}`,
                        data: option,
                        timeout: params.timeout // 通过插件配置超时时间
                    });
                    if (action === 'LOGOUT') {
                        // 通过进程间通信模块,告知主进程退出登录
                        ctx.ipc.sendToMain('LOGOUT');
                    }
                    return res;
                } catch (error) {
                    throw error;
                }
            }
        }
    }
}

Установка плагина

Настройте установку плагина в каталоге центра конфигурации plugins.js

config
|- config.base.js     // 基础配置
|- config.js          // 生产配置
|- config.test.js     // 测试配置——环境变量env=test
|- config.dev.js      // 开发配置——环境变量env=dev
|- plugins.js         // 插件配置文件
// 2、配置插件安装
const path = require('path');
exports.adpter = {
    // 如果根路径plugins目录有对应的插件名,则不需要配置path或package
    path: path.join(__dirname, '../plugins/adpter'),  // 插件绝对路径
    package: 'adpter',  // 插件包名,如果package与path同时存在,则package优先级更高
    enable: true, // 是否启动插件
    include: ['winA'], // 插件使用范围,如果为空,则所有渲染进程安装
    params: { timeout: 20000 } // 传入插件参数
};

использование плагина

// 3、使用插件——winA
const { plugins } = require('sugar-electron');
const res = await plugins.adpter.callAPI('FETCH_DATA_1', {});

Автоматически инициализировать основные модули

Разработчики, которые использовали яйцо, должны знать, что базовый функциональный модуль яйца будет автоматически инициализирован в соответствии с соответствующим каталогом. сахар-электрон также предоставляет возможность автоматической инициализации на основе каталога. Нужно только использовать интерфейс запуска фреймворка, чтобы начать передачу параметров конфигурации, чтобы завершить автоматическую инициализацию основного модуля.

Например

const { start } = require('sugar-electron');
start({
    appName: '应用名',
    basePath: '启动目录',
    configPath: '配置中心目录', // 可选,默认basePath + './config'
    storePath: '进程状态共享目录', // 可选,默认basePath + './store'
    windowCenterPath: '配置中心目录', // 可选,默认basePath + './windowCenter'
    pluginsPath: '插件目录', // 可选,默认basePath + './plugins'
})

Меры предосторожности

1. Поскольку модуль ядра сахара-электрона автоматически определяет среду основного процесса или процесса рендеринга и автоматически выбирает модули для загрузки в разных средах, если вы используете упаковку веб-пакета, код обеих сред будет упакован, и могут возникнуть исключения.

Поэтому, если вы используете webpack для упаковки, внесите сахар-электрон следующим образом:

// 主进程
const { ipc, store, ... } = require('sugar-electron/main')


// 渲染进程
const { ipc, store, ... } = require('sugar-electron/render')

API

start

Интерфейс запуска фреймворка, автоматическое монтирование config, store, windowCenter, модулей плагинов

API основного процесса

/**
 * 启动sugar
 * @param {object} options 启动参数
 * @param {string} options.appName 应用名
 * @param {string} options.basePath 启动目录
 * @param {string} options.configPath 配置目录,默认basePath + './config'
 * @param {string} options.storePath 进程状态共享目录,默认basePath + './store'
 * @param {string} options.windowCenterPath 窗口中心目录,默认basePath + './windowCenter'
 * @param {string} options.pluginsPath 插件目录,默认basePath + './plugins'
*/
start(opions)

Пример использования

// -----------------------主进程-----------------------
start({
    appName: string, 应用名,%appData%目录
    basePath: string, 启动目录
    configPath: string, 配置目录
    storePath: string, 进程状态共享目录
    windowCenterPath: string, 窗口中心目录
    pluginsPath: string 插件目录
});

BaseWindow

/**
 * 主进程调用
 * @param {string} name
 * @param {object} option
 */
new BaseWindow(name, option);

API основного процесса

setDefaultOptions [метод класса] Устанавливает конфигурацию окна по умолчанию

/**
 * @param {object} option 参考electron BrowserWindow
 */
setDefaultOptions(option)

open [метод экземпляра] Создает экземпляр BrowserWindow

/**
 * @param {object} option 参考electron BrowserWindow
 * @return {browserWindow}
 */
open(option)

getInstance [метод экземпляра]

/**
 * @return {browserWindow}
 */
getInstance(option)

издатель [метод экземпляра] публикует уведомление в текущем окне, обратитесь к модулю ipc

/**
 * @param {string} eventName 通知事件名
 * @param {object} param 参数
 * @return {browserWindow}
 */
publisher(eventName, param)

Пример использования

// -----------------------主进程-----------------------
const { BaseWindow } = require('sugar-electron');
BaseWindow.setDefaultOptions({
    width: 600,
    height: 800,
    show: false,
    ...
});
const winA = new BaseWindow('winA', { url: 'https://github.com' });
const instance = winA.open({...}); // 创建窗口
instance === winA.getInstance(); // true

Service

API основного процесса

/*
 * 创建服务进程
 * @param {string} name 服务进程名
 * @param {string} path 启动入口文件路径(绝对路径)
 * @param {boolean} devTool  是否打开调试工具,默认false
 */
new Service(name = '', path = '', openDevTool = false);

Пример использования

// -----------------------主进程-----------------------
const service = new Service('service', path.join(__dirname, 'app.js'), true);
service.on('success', function () {
    console.log('service进程启动成功');
});
service.on('fail', function () {
    console.log('service进程启动异常');
});
service.on('crashed', function () {
    console.log('service进程崩溃'); // 对应webContents.on('crashed')
});
service.on('closed', function () {
    console.log('service进程关闭'); // 对应browserWindow.on('closed')
});

windowCenter

Основной процесс, API процесса рендеринга

windowCenter: object 进程集合key=进程名 value=进程实例,默认{}

Пример использования

// -----------------------主进程-----------------------
const service = new Service('service', path.join(__dirname, 'app.js'), true);
const winA = new BaseWindow('winA', {});
const winB = new BaseWindow('winB', {});
windowCenter['service'] === service; // true
windowCenter['winA'] === winA; // true
windowCenter['winB'] === winB; // true
windowCenter['winA'].open(); // 创建winA窗口实例,同步调用
windowCenter['winA'].on('ready-to-show', () => {
    windowCenter['winA'].setFullscreen(true);
});
// -----------------------渲染进程-----------------------
// 渲染进程接口调用实际上是通过ipc通道通知主进程进程接口调用,所以接口异步而非同步
(async () => {
   await windowCenter['winA'].open(); // 创建winA窗口实例,异步Promise调用
   windowCenter['winA'].subscribe('ready-to-show', async () => {
       await windowCenter['winA'].setFullscreen(true);
   });
})()

ipc

API основного процесса ответ ответ

/**
 * 注册响应服务
 * @param {string} eventName 事件名  
 * @param {function} callback 回调
 * 使用方式,渲染进程ipc.request('main', eventName, param)
 */
response(eventName, callback)

API рендерера

setDefaultRequestTimeout устанавливает время ожидания ответа

setDefaultRequestTimeout(timeout = 0);

запрос запрос

/**
 * @param {string} toId 进程ID(注册通信进程模块名) 
 * @param {string} eventName 事件名 
 * @param {any} data 请求参数 
 * @param {number} timeout 超时时间,默认20s * 
 * @return 返回Promise对象
 */
request(toId, eventName, data, timeout)

ответ ответ

/**
 * 注册响应服务
 * @param {string} eventName 事件名  
 * @param {function} callback 回调
 */
response(eventName, callback)

служба ответа на отказ от регистрации

/**
 * @param {string} eventName 事件名  
 * @param {function} callback 回调
 */
unresponse(eventName, callback)

издатель публикует

/**
 * @param {string} eventName 事件名  
 * @param {any} param 参数
 */
publisher(eventName, param)

подписаться подписаться

/**
 * @param {string} toId 进程ID(注册通信进程模块名) 
 * @param {string} eventName 事件名 
 * @param {function} callback 回调
 */
subscribe(toId, eventName, callback)

отписаться отписаться

/**
 * @param {string} toId 进程ID(注册通信进程模块名) 
 * @param {string} eventName 事件名 
 * @param {function} callback 回调
 */
unsubscribe(toId, eventName, callback)

Пример использования

// ---------------------winA---------------------
const { ipc } = require('sugar-electron');  
// 注册响应服务A1
ipc.response('get-data', (json, cb) => {
    console.log(json); // { name: 'winB' }
    cb('winA响应');
});


// ---------------------winB---------------------
const { ipc, windowCenter } = require('sugar-electron');  
const btn1 = document.querySelector('#btn1');
const { winA } = windowCenter;
btn1.onclick = () => {
    const r1 = await winA.request('get-data', { name: 'winB' });
    console.log(r1); // winA响应
    // 等同
    const r2 = await ipc.request('get-data', 'get-data', { name: 'winB' });
    console.log(r2); // winA响应
}

store

API основного процесса

Состояние инициализации createStore

/**
 * @param {object} store
 */
createStore(store)

API рендерера

setState установить состояние

/**
 * 单个值设置
 * @param {string} key
 * @param {any} value
 * @return 返回Promise对象
 */
 setState(key, value)
 
 /**
 * 批量设置
 * @param {object} state
 * @return 返回Promise对象
 */
 setState(state)

подписаться подписаться на уведомление об изменении значения текущего модуля

/**
 * @param {function} cb 订阅回调
 * @return {function} 返回注销订阅function
 */
subscribe(cb)

отписаться отписаться

/**
 * @param {funtion} cb 订阅回调
 */
unsubscribe(cb)

getModule Получить модуль

/**
 * 获取module
 * @param {string} moduleName 模块名
 * @return {object} module
 * 返回:setState: 设置当前模块state;subscribe: 订阅;unsubscribe: 注销订阅;getModule: 获取当前模块的子模块;getModules
 */
getModule(moduleName)

getModules получить все модули

/**
 * @return {array} [module, module, module]
 */
getModules()

Пример использования

// 主进程——初始化申明state
const { store } = require('sugar-electron');
store.createStore({
    state: {
        name: '我是store'
    },
    modules: {
        moduleA: {
            state: {
                name: '我是moduleA'
            }
        },
        moduleB: {
            state: {
                name: '我是moduleB'
            },
            modules: {
                moduleC: {
                    state: {
                        name: '我是moduleC'
                    }
                }
            }
        }
    }
});
 
// 渲染进程
const { store } = require('sugar-electron');
store.state.name; // 我是store

// 订阅更新消息
const unsubscribe = store.subscribe((data) => {
    console.log('更新:', data); // 更新:{ name: '我是store1' }
});
await store.setState({
    'name': '我是store1'
});
unsubscribe(); // 取消订阅
 
// moduleA
const moduleA = store.getModule('moduleA');
moduleA.state.name; // 我是moduleA
const unsubscribeA = moduleA.subscribe((data) => {
    console.log('更新:', data); // 更新:{ name: '我是moduleA1' }
});
await moduleA.setState({
    'name': '我是moduleA1'
});
moduleA.unsubscribe(cb); // 取消订阅