ข้ามไปยังเนื้อหาหลัก

การใช้ร่วมกับ Electron

แอปพลิเคชัน Electron มีสถาปัตยกรรมพิเศษที่ประกอบด้วยหลาย Processes ซึ่งมีความรับผิดชอบต่างกัน การใช้ FSD ในบริบทนี้ต้องมีการปรับโครงสร้างให้เข้ากับธรรมชาติของ Electron

└── src
├── app # Common app layer
│ ├── main # Main process
│ │ └── index.ts # Main process entry point
│ ├── preload # Preload script และ Context Bridge
│ │ └── index.ts # Preload entry point
│ └── renderer # Renderer process
│ └── index.html # Renderer process entry point
├── main
│ ├── features
│ │ └── user
│ │ └── ipc
│ │ ├── get-user.ts
│ │ └── send-user.ts
│ ├── entities
│ └── shared
├── renderer
│ ├── pages
│ │ ├── settings
│ │ │ ├── ipc
│ │ │ │ ├── get-user.ts
│ │ │ │ └── save-user.ts
│ │ │ ├── ui
│ │ │ │ └── user.tsx
│ │ │ └── index.ts
│ │ └── home
│ │ ├── ui
│ │ │ └── home.tsx
│ │ └── index.ts
│ ├── widgets
│ ├── features
│ ├── entities
│ └── shared
└── shared # Common code ระหว่าง Main และ Renderer
└── ipc # IPC description (event names, contracts)

กฎ Public API

แต่ละ Process ต้องมี Public API ของตัวเอง ตัวอย่างเช่น คุณไม่สามารถ Import modules จาก main ไป renderer ได้ มีแค่โฟลเดอร์ src/shared เท่านั้นที่เป็น Public สำหรับทั้งสอง Processes และยังจำเป็นสำหรับการอธิบาย Contracts สำหรับการปฏิสัมพันธ์ระหว่าง Process

การเปลี่ยนแปลงเพิ่มเติมจากโครงสร้างมาตรฐาน

แนะนำให้ใช้ Segment ใหม่ ipc ซึ่งเป็นที่ที่การปฏิสัมพันธ์ระหว่าง Processes เกิดขึ้น Layers pages และ widgets ตามชื่อแล้ว ไม่ควรอยู่ใน src/main คุณสามารถใช้ features, entities และ shared ได้ Layer app ใน src ประกอบด้วย Entry points สำหรับ main และ renderer รวมถึง IPC ด้วย ไม่ควรให้ Segments ใน app layer มีจุดตัดกัน (Intersection points)

ตัวอย่างการปฏิสัมพันธ์ (Interaction example)

src/shared/ipc/channels.ts
export const CHANNELS = {
GET_USER_DATA: 'GET_USER_DATA',
SAVE_USER: 'SAVE_USER',
} as const;

export type TChannelKeys = keyof typeof CHANNELS;
src/shared/ipc/events.ts
import { CHANNELS } from './channels';

export interface IEvents {
[CHANNELS.GET_USER_DATA]: {
args: void,
response?: { name: string; email: string; };
};
[CHANNELS.SAVE_USER]: {
args: { name: string; };
response: void;
};
}
src/shared/ipc/preload.ts
import { CHANNELS } from './channels';
import type { IEvents } from './events';

type TOptionalArgs<T> = T extends void ? [] : [args: T];

export type TElectronAPI = {
[K in keyof typeof CHANNELS]: (...args: TOptionalArgs<IEvents[typeof CHANNELS[K]]['args']>) => IEvents[typeof CHANNELS[K]]['response'];
};
src/app/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';
import { CHANNELS, type TElectronAPI } from 'shared/ipc';

const API: TElectronAPI = {
[CHANNELS.GET_USER_DATA]: () => ipcRenderer.sendSync(CHANNELS.GET_USER_DATA),
[CHANNELS.SAVE_USER]: args => ipcRenderer.invoke(CHANNELS.SAVE_USER, args),
} as const;

contextBridge.exposeInMainWorld('electron', API);
src/main/features/user/ipc/send-user.ts
import { ipcMain } from 'electron';
import { CHANNELS } from 'shared/ipc';

export const sendUser = () => {
ipcMain.on(CHANNELS.GET_USER_DATA, ev => {
ev.returnValue = {
name: 'John Doe',
email: 'john.doe@example.com',
};
});
};
src/renderer/pages/user-settings/ipc/get-user.ts
import { CHANNELS } from 'shared/ipc';

export const getUser = () => {
const user = window.electron[CHANNELS.GET_USER_DATA]();

return user ?? { name: 'John Donte', email: 'john.donte@example.com' };
};

ดูเพิ่มเติม