"React + Electron은 실무에서 가장 많이 쓰이는 조합" — Vite 기반의 electron-vite를 사용하면 빠르게 프로젝트를 구성할 수 있습니다.


프로젝트 구성 방법

electron-vite 사용 (권장)

BASH
# electron-vite 프로젝트 생성
npm create @quick-start/electron my-app -- --template react-ts
cd my-app
npm install
npm run dev

생성되는 프로젝트 구조:

PLAINTEXT
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 설정

TYPESCRIPT
// 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 패턴

TSX
// 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 };
}

컴포넌트에서 사용

TSX
// 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 설정

TYPESCRIPT
// 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 연동

TSX
// 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 통합을 알아봅시다.

댓글 로딩 중...