๐ Electron + React ํ๋ก์ธ์ค ๊ฐ IPC ํต์ ๊ณผ preload ์ฌ์ฉ ๋ฐฉ๋ฒ
- Electron ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฃผ๋ก ๋ฉ์ธ ํ๋ก์ธ์ค์ ๋ ๋๋ฌ ํ๋ก์ธ์ค๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
- Main Process๋ ๋จ ํ๋๋ง ์คํ๋๋ฉฐ ์ฑ์ ๋ณธ์ฒด์ด๊ณ ,
Renderer Process๋ ํ๋ฉด์ ๊ทธ๋ ค์ง๋ n๊ฐ์ ์น๋ทฐ ํ๋ก์ธ์ค์ ๋๋ค. - ์ด ๋ ํ๋ก์ธ์ค ๊ฐ์๋ Electron IPC (Inter-Process Communication)๋ฅผ ์ฌ์ฉํ์ฌ ํต์ ํฉ๋๋ค.
๊ทธ๋ผ IPC๋ฅผ ์ฌ์ฉํ์ฌ ๋ ํ๋ก์ธ์ค ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๊ณ ๋ช ๋ น์ ๋ณด๋ด๋ ๋ฐฉ๋ฒ์ ์๊ฐํฉ๋๋ค.
๐ IPC๋ก Main - Renderer ํ๋ก์ธ์ค ๊ฐ ๋ฐ์ดํฐ ์ฃผ๊ณ ๋ฐ๊ธฐ
์ค์ ๋ก ๋ ํ๋ก์ธ์ค ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํด์๋ ์ ๋ง ๊ฐ๋จํ ์ ์ฐจ๋ง ๊ฑฐ์น๋ฉด ๋ฉ๋๋ค.
- ํน์ ์ฑ๋๋ช
์ ์ ํ ๋ค
ipcMain
oripcRenderer
์on
ํจ์๋ก listen ipcMain
oripcRenderer
์send
ํจ์๋ก ์ฑ๋๋ช ๊ณผ ๋ฐ์ดํฐ๋ฅผ ๋ ๋ฆฌ๊ธฐ
๊ฐ๋จํ ์ฝ๋ ํ์ธ ์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ํด main process์ BrowserWindow์ ์ต์ ์ ๋ฃ์ด์ฃผ๊ฒ ์ต๋๋ค.
๐ nodeIntegration, contextIsolation
ipc ๋ชจ๋๋ก ๋ฉ์ธ - ๋ ๋๋ฌ ํ๋ก์ธ์ค ๊ฐ ํต์ ์ ์ํด ๋ค์๊ณผ ๊ฐ์ด webPreferences
์ ์ต์
์ ์ถ๊ฐํด ์ค๋๋ค.
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
nodeIntegration: true
- ์ด ์ต์ ์ ๋ ๋๋ฌ ํ๋ก์ธ์ค์์ Node.js ๋ชจ๋์ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค.
- ์ด๋ฅผ ํตํด Node.js์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ด์ ํ์ผ ์์คํ ์ก์ธ์ค๋ ๋คํธ์ํฌ ์์ฒญ ๋ฑ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
contextIsolation: false
- ์ด ์ต์ ์ ๋ ๋๋ฌ ํ๋ก์ธ์ค์ JavaScript ์ปจํ ์คํธ๋ฅผ ๊ฒฉ๋ฆฌ์ํค๋ ๊ฒ์ ํด์ ํฉ๋๋ค.
- ๊ธฐ๋ณธ์ ์ผ๋ก Electron์ ๋ ๋๋ฌ ํ๋ก์ธ์ค์ JavaScript ์ฝ๋๋ฅผ ๊ฒฉ๋ฆฌํ์ฌ ๋ณด์์ ๊ฐํํฉ๋๋ค.
- ๊ทธ๋ฌ๋ ์ด ์ต์
์ ๋นํ์ฑํํ๋ฉด ๋ ๋๋ฌ ํ๋ก์ธ์ค์ธ ๋ฆฌ์กํธ์์๋ ๋ฉ์ธ ํ๋ก์ธ์ค์
require()
ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค. - ์ด ์ต์
์ ํ์ฑํํ๊ณ ๋ ๋๋ฌ ํ๋ก์ธ์ค์์ window.require๋ฅผ ์ฌ์ฉํ๋ฉด
TypeError: window.require is not a function
์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๐ Main Process, ipcMain
// electron.js or main.js
const { ipcMain } = await import('electron');
ipcMain.on("channel", (event, data) => {
console.log(":: From Renderer Process ::", data);
event.sender.send("channel", "From Main Process");
});
๋ฉ์ธ ํ๋ก์ธ์ค์์ ipcMain
๋ชจ๋์ ์ด์ฉํด “channel”์ด๋ ์ฑ๋๋ช
์ผ๋ก ํต์ ํ๋ ์ฝ๋์
๋๋ค.
๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ก๊ทธ๋ก ์ฐ์ด์ค ๋ค event ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์กํฉ๋๋ค.
๐ Renderer Process, ipcRenderer
import { useState, useEffect } from "react";
export const Ipc = () => {
const [message, setMessage] = useState("");
const { ipcRenderer } = window.require("electron");
useEffect(() => {
// IPC ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
ipcRenderer.on("channel", (event: any, data: string) => {
setMessage(data); // ๋ฐ์ ๋ฉ์์ง๋ฅผ ์ํ๋ก ์ค์
});
}, [ipcRenderer]);
const handleClick = () => {
ipcRenderer.send("channel", "HIHIHIHI");
};
return (
<div className="App">
<h1>Recive: {message}</h1>
<button onClick={handleClick}>Click me</button>
</div>
);
};
๋ฒํผ์ ํด๋ฆญํ๋ฉด ๋ฉ์ธ์ง๋ฅผ ์ ์กํ๊ณ , ๋ฐ์ ๋ฐ์ดํฐ๋ useEffect๋ฅผ ํตํด ์ํ์ ์ ์ฅํด ์ค๋๋ค.
์ ์ํด์ ๋ด์ผ ํ ์ ์ ipcRenderer ๋ชจ๋์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ๋๋ค.
const { ipcRenderer } = window.require("electron");
์ด๋ ์ผ๋ ํธ๋ก ์์ ์ฃผ๋ก ์ฌ์ฉ๋๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ ๋๋ฌ ํ๋ก์ธ์ค์์ Node.js ๋ชจ๋ ์์คํ ์ ์ฌ์ฉํ ์ ์๊ฒ ํด์ฃผ๋ ๋ฉ์ปค๋์ฆ์ ๋๋ค.
์ผ๋ฐ์ ์ธ ๋ธ๋ผ์ฐ์ ์์๋ require()
ํจ์๊ฐ ์ ์๋์ด ์์ง ์๊ธฐ ๋๋ฌธ์,
Electron์์๋ ์ด๋ฅผ window
๊ฐ์ฒด์ ์์ฑ์ผ๋ก ์ ๊ณตํ์ฌ ๋ ๋๋ฌ ํ๋ก์ธ์ค์์๋
Node.js ์คํ์ผ์ ๋ชจ๋ ์์คํ
์ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค.
์ด๋ฅผ ์ํด์ ์์์ ๋จผ์ ์ดํด๋ดค๋ nodeIntegration
, contextIsolation
์ ๋ํ ์ธํ
์ด ์ ํ์ ์ผ๋ก ์ด๋ค์ค์ผ ํฉ๋๋ค.
๐ ๊ฒฐ๊ณผ
Click me ๋ฒํผ์ ํด๋ฆญํ๊ฒ ๋๋ฉด
์ ์์ ์ผ๋ก ํ๋ก์ธ์ค ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ชจ์ต์ ํ์ธํ ์ ์์ต๋๋ค.
๐ preload๋ก ์์ ํ๊ฒ ํต์ ํ๊ธฐ
์์์ ์๊ฐ๋๋ฆฐ ๋ฐฉ๋ฒ์ contextIsolation: false
๋ฅผ ํตํด
์น์์ ๋ฉ์ธ ํ๋ก์ธ์ค์ ์ง์ ์ ๊ทผํ ์ ์๋ ๊ฐ๋ฅ์ฑ์ ์ด์ด์ฃผ๊ธฐ ๋๋ฌธ์ ๊ถ์ฅํ์ง ์์ต๋๋ค.
๊ทธ๋ ๊ธฐ์ preload ํ์ผ๋ก ํ๋ก์ธ์ค ๊ฐ ์์ ํ๊ฒ ํต์ ํ ์ ์๋ ์ค๊ฐ ๋งค๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
๐ preload.js
import { contextBridge, ipcRenderer } from "electron";
const preloadInterface = "myPreload";
contextBridge.exposeInMainWorld(preloadInterface, {
listenChannelMessage: (callback) =>
ipcRenderer.on("channel", (_, data) => callback(data)),
sendMessage: (data) => ipcRenderer.send("channel", data),
});
- contextBridge.exposeInMainWorld ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๋ ๋๋ฌ ํ๋ก์ธ์ค์ ์ ์ญ ์ปจํ ์คํธ์์๋ง ์ ๊ทผ ๊ฐ๋ฅํ API๋ฅผ ๋ ธ์ถํฉ๋๋ค.
- preloadInterface ๋ณ์์ ๋ด๊ธด ์ด๋ฆ์ ์น๋จ์์ window.myPreload์ ๊ฐ์ด ์ ์ญ์ผ๋ก ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ๊ณต์๋ฌธ์์๋ ์ ํ์๋ฏ ๊ฐ๋ ฅํ API(ipcRenderer)๋ฅผ ์ง์ ๋ ธ์ถํ์ง ๋ง๊ณ ํ๋์ ๊ธฐ๋ฅ์ ๋ํด wrapping ํด์ ์ฌ์ฉํ๋ผ๊ณ ํฉ๋๋ค.
๐ preload ํ์ ์ ์ธ
declare global {
interface Window {
myPreload: {
listenChannelMessage: (callback: (...arg: any[]) => void) => void;
sendMessage: (message: string) => void;
};
}
}
preload.d.ts
ํ์ผ์ ์์ฑํ ํจ์๋ค์ global window property ํ์
์ ์ถ๊ฐํด ์ค๋๋ค.
๊ทธ๋ฐ๋ฐ CRA๋ก ์์ฑํ react ํ๋ก์ ํธ์ ๊ฒฝ์ฐ ์ด์ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ๊ฒฝ์ฐ
ํ์ ์คํฌ๋ฆฝํธ๊ฐ ์ ์ ์ ๋ชป ์ฐจ๋ ค์ ํ์ ์ธ์์ด ์ค๋ฝ๊ฐ๋ฝํฉ๋๋ค.
๋ง์ฝ ์ธ์์ด ์ ๋ ๊ฒฝ์ฐ, ๊ธฐ๋ณธ์ ์ผ๋ก ์์ฑ๋ react-app-env.d.ts
์ ๋ค์๊ณผ ๊ฐ์ด ํ์
์ ์ถ๊ฐํด ์ค๋๋ค.
/// <reference types="react-scripts" />
interface Window {
myPreload: {
listenChannelMessage: (callback: (...arg: any[]) => void) => void;
sendMessage: (message: string) => void;
};
}
๐ ๋ฉ์ธ ํ๋ก์ธ์ค ํ์ผ์ preload ์ถ๊ฐ
// ES Module์ผ ๊ฒฝ์ฐ์๋ง ์ ์ธ
const __dirname = path.resolve();
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, "public/preload.mjs"),
},
});
์์ฑํด ๋ preload ํ์ผ์ webPreferences์ ์ถ๊ฐํด ์ค๋๋ค.
ํ๋ก์ ํธ๋ฅผ ES Module ๋ฐฉ์์ผ๋ก ์ค์ ํด ๋์ด์ __dirname ์ด์ ํด๊ฒฐ๊ณผ preload.js -> preload.mjs ๋ฐฉ์์ ์ฐจ์ด๊ฐ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐ๋ฐํ๊ฒฝ์๋ง ๋ง์ถฐ์ path๋ฅผ ์ค์ ํด ๋์๋๋ฐ ์ค์ ํจํค์ง๊น์ง ๊ณ ๋ คํ๋ฉด ๊ฒฝ๋ก ๋ถ๊ธฐ์ฒ๋ฆฌ๊ฐ ํ์ํ ์ ์์ต๋๋ค.
๐ React์์ preload ์ฌ์ฉ
import { useEffect, useState } from "react";
export const Preload = () => {
const [message, setMessage] = useState("");
useEffect(() => {
window.myPreload.listenChannelMessage(setMessage);
}, []);
const handleClick = () => {
window.myPreload.sendMessage("send message by preload");
};
return (
<div className="App">
<h1>Recive: {message}</h1>
<button onClick={handleClick}>Click me</button>
</div>
);
};
๋ฏธ๋ฆฌ ๋ฑ๋กํ preload๋ window ์ ์ญ ๊ฐ์ฒด๋ฅผ ํตํด ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
๋ฑ๋กํ ํ์ ์ ๋ง์ถฐ ์๋์์ฑ๋ ๊น๋ํ๊ฒ ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๐ ๊ฒฐ๊ณผ
์ ์๋ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
IPC ํต์ ์ ํตํด ์ฑ๊ณผ ์น์ ๊ธฐ๋ฅ์ ์ ๋ฒ๋ฌด๋ ค์ ์ฌ์ฉํ๋ฉด ์ข๊ฒ ์ต๋๋ค.
์ ์ฒด ์ฝ๋๋ ์์ ๋ ์ ๊นํ๋ธ์ ์ฌ๋ ค๋์์ผ๋ ์ฐธ๊ณ ํด ์ฃผ์ธ์!
https://github.com/LasBe-code/electron-react-ex
'React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
React ์นด๋ ์ธํฐ๋์ ์ ๋๋ฉ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (0) | 2024.09.21 |
---|---|
[React] ErrorBoundary & Suspense, ๊ฑฐ์ ์๋ฒฝํ ์ฌ์ฉ๋ฐฉ๋ฒ ๊ฐ์ด๋ (6) | 2024.06.04 |
[React] ์ธ์ ์ฌ์ด ๋ฆฌ์กํธ ๋ชจ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (0) | 2024.05.07 |
[Electron, React, TS] ๋ฐ์คํฌํฑ ์ฑ ํ๋ก์ ํธ ์์ฑํ๊ธฐ (4) | 2024.04.01 |
[React] ๋ฆฌ์กํธ ์คํฌ๋กค ์ ๋๋ฉ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ๋ฐ (0) | 2024.03.13 |
๋๊ธ