remote
模块为渲染进程(web页面)和主进程通信(IPC)提供了一种简单方法。
通俗讲就是 remote 模块让仅在主进程中可用的一些模块,比如app
, 通知
,dialog
,渲染进程也可以调用。举个例子:
remote.dialog.showMessageBox({
type: 'info',
message: '提交成功!'
});
这么看 remote 模块可以说是非常好用啦,渲染进程就不用显式通过 ipcRenderer
/ ipcMain
与主进程通信。除此之外,可以通过 remote.getGlobal 获取主进程中 的全局变量, 通过 remote.process 拿到主进程的 process 对象信息
remote 模块返回的每个对象 (包括函数) 表示主进程中的一个对象 (我们称它为远程对象或远程函数)。 当调用远程对象的方法时, 调用远程函数, 或者使用远程构造函数 (函数) 创建新对象时, 实际上是在发送同步进程消息。 Electron 确保只要渲染进程中的远程对象一直存在(换句话说,没有被回收),主进程中的相应对象就不会被释放。 当远程对象被垃圾回收后,主进程中的相应对象将被解除引用。
发送同步进程消息,会阻塞页面渲染?当主进程中的相应对象发生改变,渲染进程中的远程对象是否会对应改变?
从现象看:
1、通过计算器从拉取数据开始处于卡顿状态,直到遍历完返回数据继续计数
2、主进程的变量tips/tipObj改变,渲染进程中的变量tips不变,tipObj会对应改变
具体代码逻辑:
渲染进程(页面)
import React, { useEffect, useState, Fragment } from 'react';
import { Button } from 'antd';
import { useIpcRenderer } from '../../hooks/useIpcRenderer';
const { remote } = window.require('electron');
let tips: string;
let tipObj = {};
const Demo: React.FC = () => {
const [num, setNum] = useState(0);
const loadData = () => {
tips= remote.getGlobal('tips');
tipObj = remote.getGlobal('tipObj');
console.log("Before change:", tips, JSON.stringify(tipObj));
console.log("load data start");
console.time("1");
const loadData = remote.getGlobal('loadData');
console.log("load data end");
console.timeEnd("1");
console.log("literate data start");
console.time("2");
const list = loadData();
list.forEach((item:Object) => {
console.log(JSON.stringify(item));
});
console.log("literate data end");
console.timeEnd("2");
}
//计数器
useEffect(() => {
let _val:number = 0;
setInterval(() => {
setNum(++_val);
}, 500);
}, [])
useIpcRenderer({
changeTips: (evt: Event, val: any) => {
console.log("After changed:", tips, JSON.stringify(tipObj));
}
})
return (
主进程
...
global.tips = 'It\'s 2019';
global.tipObj = {
content: 'It\'s 2019'
};
global.loadData = () => {
let ret = [];
for (let i = 0; i < 1000; i ++) {
let obj = {}
for (let j = 0; j < 15; j ++) {
obj[j] = j;
}
ret.push(obj)
}
setTimeout(() => {
tips = '2020 is coming!';
tipObj.content = '2020 is coming!';
this.win.webContents.send('changeTips');
}, 8000);
return ret;
}
...
我们深入 remote 源码找找上边现象的原因:
v7.1.2 electron/lib/renderer/api/remote.js:
...
// Alias to remote.require('electron').xxx.
//发送一个同步信息到主进程,获取模块对象,对返回数据的具体处理见metaToValue函数
exports.getBuiltin = (module) => {
const command = 'ELECTRON_BROWSER_GET_BUILTIN'
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack())
return metaToValue(meta)
}
...
// 将仅主进程可用模块browserModules添加给remote模块
const addBuiltinProperty = (name) => {
Object.defineProperty(exports, name, {
get: () => exports.getBuiltin(name)
})
}
const { commonModuleList } = require('@electron/internal/common/api/module-list') //渲染进程和主进程都可用模块
const browserModules = commonModuleList.concat(require('@electron/internal/browser/api/module-keys')) //仅主进程可用模块
// And add a helper receiver for each one.
browserModules
.filter((m) => !m.private)
.map((m) => m.name)
.forEach(addBuiltinProperty)
metaToValue函数:
// Convert meta data from browser into real value.
function metaToValue (meta) {
const types = {
value: () => meta.value,
array: () => meta.members.map((member) => metaToValue(member)),
buffer: () => Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength),
promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
error: () => metaToError(meta),
exception: () => { throw metaToError(meta.value) }
}
//对基本的对象包括数据只是做值的拷贝操作
if (meta.type in types) {
return types[meta.type]()
} else {
//对返回对象进行缓存,避免重复获取
let ret
if (remoteObjectCache.has(meta.id)) {
v8Util.addRemoteObjectRef(contextId, meta.id)
return remoteObjectCache.get(meta.id)
}
/**对函数类型包了一层函数remoteFunction用于发送同步的进程间信息,并将remoteFunction返回给渲染进程
**/
// A shadow class to represent the remote function object.
if (meta.type === 'function') {
const remoteFunction = function (...args) {
let command
if (this && this.constructor === remoteFunction) {
command = 'ELECTRON_BROWSER_CONSTRUCTOR'
} else {
command = 'ELECTRON_BROWSER_FUNCTION_CALL'
}
const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args))
return metaToValue(obj)
}
ret = remoteFunction
} else {
ret = {}
}
/**
对对象类型也做了类似函数的操作,并且对返回对象属性重写 get、set 方法。当调用远程对象上的属性或者给远程对象的属性赋值,
都会发送同步的进程间消息,所以主进程修改对象的值,渲染进程可以同步到值的改变,详见setObjectMembers函数
*/
setObjectMembers(ret, ret, meta.id, meta.members)
...
remoteObjectCache.set(meta.id, ret)
return ret
}
}
而remote.getGlobal实际也是进行一个同步请求通信操作
// Get a global object in browser.
exports.getGlobal = (name) => {
const command = 'ELECTRON_BROWSER_GLOBAL'
const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack())
return metaToValue(meta)
}
小结
remote 模块实际作用是帮我们做了IPC操作,通过发送同步请求实现主线程/渲染进程的数据传递