上一期主题: 手把手教你搭建electron+react+ts环境
本文的起步工程(注意是1.base_env分支): github.com/Licht-club/…
因为是承接上一节,当然此节和上一节也没必然关系
本文涉及文档汇总
ipcMain主进程 ipcRenderer渲染进程 webContents
交互方式总结
主线程
渲染线程
交互模式
ipcMain.handle
ipcRenderer.invoke
渲染进程请求+主进程响应
webContents.send
ipcRenderer.on
主进程推送
ipcMain.on
ipcRenderer.send
渲染进程发起请求
我们的起步工程
我们要完成的功能:模拟实现一个远程控制客户端 1.模拟渲染线程实现登录,获取自己的控制码 2.模拟请求控制一个用户的窗口
渲染线程实现登录
- 修改react页面,添加一个登录按钮
/render-process/main/index.tsx
import React, {useEffect, useState} from 'react'
import ReactDom from 'react-dom'
import { ipcRenderer } from 'electron'
const App = () => {
const [localCode,setLocalCode]=useState('');//本身的控制码
// 模拟登录功能
const login =async () => {
// 获取登陆后的控制码
// 因为登录状态是在主进程维护,通过主进程来处理ipc事件
const code=await ipcRenderer.invoke('login')
// 存储控制码
setLocalCode(code)
}
return <div>
<div>hello react</div>
{localCode? <div>
本身的控制码: {localCode}
</div>: <button onClick={()=>login()}>登录</button>}
</div>
}
ReactDom.render(<App></App>, document.getElementById('root'))
这里使用了
ipcRenderer.invoke, Send a message to the main process via channel and expect a result asynchronously.
- 主线程响应登录请求
main-process/ipc.ts 封装一个函数,处理主进程的事务
import {ipcMain} from 'electron'
export function ipc(){
ipcMain.handle('login',async ()=>{
// mock一个状态码
const code=Math.floor(Math.random()*(999999-100000))+100000;
return code;
})
}
main-process/index.ts 主入口调用函数,在创建窗口之前
import {app, BrowserWindow} from 'electron'
import {create} from './mainWindow'
import {ipc} from "./ipc";
app.on('ready', () => {
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; // 关闭web安全警告
ipc()
create()
})
重启electron,看下效果
效果图
登陆前:
登陆后:
总结
// Renderer process 渲染进程请求
ipcRenderer.invoke('some-name', someArgument).then((result) => {
// ...
})
// Main process 主进程响应
ipcMain.handle('some-name', async (event, someArgument) => {
const result = await doSomeWork(someArgument)
return result
})
请求控制一个用户的窗口
/render-process/main/index.tsx 添加一个input和一个button,省略局部代码
function App(){
const startControl = (remoteCode:string)=>{
// 渲染线程发起一个请求
ipcRenderer.send('control',remoteCode)
}
return <div>
<div>hello react</div>
{localCode? <div>
本身的控制码: {localCode}
</div>: <button onClick={()=>login()}>登录</button>}
<input type="text" value={remoteCode} onChange={e=>setRemoteCode(e.target.value)}/>
<button onClick={()=>startControl(remoteCode)}>请求控制</button>
</div>
}
main-process/ipc.ts 这里ipcMain.on函数的第二个参数是一个回调函数,ts准确识别出来了类型,剩余参数则要自己指定类型
import {ipcMain} from 'electron'
export function ipc(){
ipcMain.handle('login',async ()=>{
// mock一个状态码
const code=Math.floor(Math.random()*(999999-100000))+100000;
return code;
})
ipcMain.on('control',async(e,remoteCode:string)=>{
console.log(remoteCode,'主线程收到的控制码')
})
}
主线程成功收到请求,我们的这个组合也完成了
完成效果图
总结
如果您希望从主流程收到单个响应,例如方法调用的结果,请考虑使用ipcRenderer.invoke。 如果只是单纯的推送和传输推送数据,用ipcRenderer.send
// Renderer process 渲染进程推送
ipcRenderer.send('some-name', ...args)
// Main process 主进程监听
ipcMain.on('some-name', async (...args) => {
})
主线程推送渲染线程
main-process/mainWindow.ts 添加send函数,可以在主线程任意地方使用send函数向渲染线程推送信息
import {BrowserWindow} from 'electron'
import isDev from 'electron-is-dev'
import {resolve} from 'path'
let win: BrowserWindow;
export function create() {
// ...
}
export function send(channel:string,...args:any[]){
win.webContents.send(channel,...args)
}
main-process/ipc.ts 接收到渲染进程的请求后向渲染线程推送一个control-state-change
import {ipcMain} from 'electron'
import {send} from './mainWindow'
export function ipc(){
ipcMain.handle('login',async ()=>{
// mock一个状态码
const code=Math.floor(Math.random()*(999999-100000))+100000;
return code;
})
ipcMain.on('control',async(e,remoteCode:string)=>{
// 这里跟服务端交互,但是mock返回
send('control-state-change',remoteCode,1)
})
}
接下来要在渲染线程监听control-state-change,在useEffect生命周期添加监听,和给dom添加监听基本一样
render-process/main/index.tsx
import React, {useEffect, useState} from 'react'
import ReactDom from 'react-dom'
import {ipcRenderer, IpcRendererEvent} from 'electron'
const App = () => {
const [localCode,setLocalCode]=useState('');//本身的控制码
const [remoteCode,setRemoteCode]=useState('');//其他用户的控制码
const [controlText,setControlText]=useState('');//控制码的文案
const handleControlState = (e:IpcRendererEvent,name:string,type:number)=>{
let text='';
if(type === 1){
//控制别人
text=`正在远程控制${name}`
}
setControlText(text)//当前页面的文本
}
useEffect(()=>{
ipcRenderer.on('control-state-change',handleControlState)//监听ipc事
return ()=>{
//监听函数之后,最好清理掉这个函数(退出时)
ipcRenderer.removeListener('control-state-change',handleControlState)
}
})
// 模拟登录功能
const login =async () => {
// 获取登陆后的控制码
// 因为登录状态是在主进程维护,通过主进程来处理ipc事件
const code=await ipcRenderer.invoke('login')
// 存储控制码
setLocalCode(code)
}
const startControl = (remoteCode:string)=>{
// 渲染线程发起一个请求
ipcRenderer.send('control',remoteCode)
}
return <div>
<div>hello react</div>
{localCode? <div>
本身的控制码: {localCode}
</div>: <button onClick={()=>login()}>登录</button>}
<div>
{controlText}
</div>
<input type="text" value={remoteCode} onChange={e=>setRemoteCode(e.target.value)}/>
<button onClick={()=>startControl(remoteCode)}>请求控制</button>
</div>
}
ReactDom.render(<App></App>, document.getElementById('root'))
这样渲染线程就完成了监听
完成效果图
模拟打开一个被控制的窗口
render\control\index.html 新开一个html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>正在受到控制</div>
</body>
</html>
main-process\controlWindow.ts
import {BrowserWindow} from 'electron'
import {resolve} from 'path'
let win;
export function createControlWindow() {
win=new BrowserWindow({
width:800,
height:800,
webPreferences:{
nodeIntegration:true
}
})
win.loadFile(resolve(__dirname,'../render-process/control/index.html'))
}
main-process\ipc.ts
主线程收到control打开新窗口
import { createControlWindow } from './controlWindow'
export function ipcHandle() {
// ipcMain.handle 主线程响应渲染线程
ipcMain.handle('login',()=>{
let code=Math.floor(Math.random()*(999999-100000))+100000;
console.log(code,'主线程生成的code')
return code
})
ipcMain.on('control',(e,remoteCode)=>{
send('control-state-change',remoteCode,1)
createControlWindow()
})
}
最终效果图
本文代码git地址,注意是2.mainWithRender分支
https://github.com/Licht-club/react-electron/tree/2.mainWithRender