React + Electron — 프론트엔드 프레임워크 통합
"React + Electron은 실무에서 가장 많이 쓰이는 조합" — Vite 기반의 electron-vite를 사용하면 빠르게 프로젝트를 구성할 수 있습니다.
프로젝트 구성 방법
electron-vite 사용 (권장)
# electron-vite 프로젝트 생성
npm create @quick-start/electron my-app -- --template react-ts
cd my-app
npm install
npm run dev
생성되는 프로젝트 구조:
my-app/
├── src/
│ ├── main/ # 메인 프로세스
│ │ └── index.ts
│ ├── preload/ # 프리로드 스크립트
│ │ └── index.ts
│ └── renderer/ # 렌더러 (React 앱)
│ ├── src/
│ │ ├── App.tsx
│ │ └── main.tsx
│ └── index.html
├── electron.vite.config.ts
└── package.json
electron-vite 설정
// electron.vite.config.ts
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()],
},
preload: {
plugins: [externalizeDepsPlugin()],
},
renderer: {
plugins: [react()],
},
});
React 컴포넌트에서 Electron API 사용
Hook 패턴
// hooks/useElectron.ts
import { useCallback } from 'react';
// window.electronAPI 타입 정의
declare global {
interface Window {
electronAPI: {
openFile: () => Promise<string | null>;
saveFile: (content: string) => Promise<boolean>;
getVersion: () => Promise<string>;
onMenuAction: (callback: (action: string) => void) => () => void;
};
}
}
// 파일 열기 훅
export function useFileDialog() {
const openFile = useCallback(async () => {
const filePath = await window.electronAPI.openFile();
return filePath;
}, []);
const saveFile = useCallback(async (content: string) => {
return window.electronAPI.saveFile(content);
}, []);
return { openFile, saveFile };
}
컴포넌트에서 사용
// components/Editor.tsx
import { useState, useEffect } from 'react';
import { useFileDialog } from '../hooks/useElectron';
export function Editor() {
const [content, setContent] = useState('');
const [filePath, setFilePath] = useState<string | null>(null);
const { openFile, saveFile } = useFileDialog();
const handleOpen = async () => {
const path = await openFile();
if (path) {
setFilePath(path);
// 파일 내용 로드 로직
}
};
const handleSave = async () => {
const saved = await saveFile(content);
if (saved) {
console.log('저장 완료');
}
};
// 메뉴 이벤트 수신
useEffect(() => {
const cleanup = window.electronAPI.onMenuAction((action) => {
switch (action) {
case 'save': handleSave(); break;
case 'open': handleOpen(); break;
}
});
return cleanup; // 언마운트 시 리스너 정리
}, [content]);
return (
<div className="editor">
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<div className="toolbar">
<button onClick={handleOpen}>열기</button>
<button onClick={handleSave}>저장</button>
</div>
</div>
);
}
개발 환경 HMR 설정
// src/main/index.ts
import { app, BrowserWindow, shell } from 'electron';
import path from 'path';
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
sandbox: false,
},
});
// 개발 모드: Vite dev server URL 로드
// 프로덕션: 빌드된 파일 로드
if (process.env.ELECTRON_RENDERER_URL) {
win.loadURL(process.env.ELECTRON_RENDERER_URL);
} else {
win.loadFile(path.join(__dirname, '../renderer/index.html'));
}
}
app.whenReady().then(createWindow);
React Router 연동
// renderer/src/App.tsx
import { HashRouter, Routes, Route } from 'react-router-dom';
import { Home } from './pages/Home';
import { Settings } from './pages/Settings';
// Electron에서는 HashRouter를 사용합니다
// BrowserRouter는 file:// 프로토콜에서 동작하지 않습니다
export function App() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</HashRouter>
);
}
면접 포인트 정리
electron-vite가 React + Electron 통합의 현재 표준- 렌더러에서 Electron API는
window.electronAPI를 통해 접근 - 이벤트 리스너는
useEffect의 cleanup 함수로 반드시 정리 - Electron에서는
HashRouter사용 (file:// 프로토콜 호환) - 개발 모드에서는 Vite dev server URL, 프로덕션에서는 빌드 파일 로드
React 통합을 다뤘으면, 다음은 Vue + Electron 통합을 알아봅시다.
댓글 로딩 중...