创建项目
提供 npm create 的 electron 项目有:
1
|
pnpm create @quick-start/electron
|
选择 react 或 react-ts 框架即可。
项目结构
1
2
3
4
5
6
7
8
9
|
src/
├── main/
├── index.ts
├── preload/
├── index.ts
├── index.d.ts
├── renderer/
│ ├── src/
│ └── App.tsx
|
简单总结一下 electron 的结构:
- main 即主程序(浏览器),可以进行本地文件的访问。
- preload 是每个(浏览器)页面载入前执行的指令。通常会通过 ipc 暴露 main 中的 api,使得页面能够访问主程序中的 api。api 通常会挂载到
window 对象上。
- renderer 是渲染线程,可通俗理解为页面和 UI。
配置
我通常会在 src 下加入 shared 文件夹,这样可以方便 electron 各部分查询类型。其下有两个文件: types.ts 是程序用到的类型,schema.ts 是数据库的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// src/shared/types.ts
export interface posts {
id: string
title: string
author: string
content: string
}
// src/shared/schema.ts
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core'
export const books = sqliteTable('posts', {
id: text('id').primaryKey(),
title: text('title').notNull(),
author: text('author'),
content: text('content').notNull(),
})
|
在 src/main/db.ts 中,定义数据库操作相关的 api:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// src/main/db.ts
export class StorageService {
private static db
// drizzle createClient 和 migrate 之类的就在这里进行
private static async initDb() {
if (this.db) return this.db
const db = createClient({ url: ... })
this.db = drizzle(db)
await migrate(this.db, { migrationsFolder: './drizzle' })
return this.db
}
// 具体的数据库操作方法
private static async getPosts() {
const db = await this.initDb()
const result = await db.select().from(books)
return result
}
...
}
|
定义完成后,就可以将数据库 api 映射到 IPC 中():
1
2
3
4
5
6
7
8
9
10
11
|
// src/main/index.ts
app.whenReady().then(() => {
...
ipcMain.handle('storage:getPosts', async () => {
return await StorageService.getPosts()
})
... // 可以定义更多
})
|
ipcMain 定义完了,另一端的 ipcRenderer 在 preload 中定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// src/preload/index.ts
const api= {
storage: {
getPosts: () => ipcRenderer.invoke('storage:getPosts'),
...
}
}
// 将 api 放到 renderer 的 window 中
contextBridge.exposeInMainWorld('api', api)
// src/preload/index.d.ts
import { ElectronAPI } from '@electron-toolkit/preload'
import { api } from './index'
declare global {
interface Window {
electron: ElectronAPI
api: typeof api // 直接使用 typeof api,懒得再手写了
}
}
|
最后,在 renderer 中就可以调用 window.api.getPosts 与数据库交互了。
如何快捷访问 shared
如果不用绝对路径访问 shared 文件夹,则会导致很多跨越多个部分的相对路径导入。为了实现绝对路径引入,需要修改 tsconfig.json, tsconfig.node.json, tsconfig.web.json 文件(后两个即为主进程和渲染进程的 tsconfig)。
首先需要给 tsconfig.node.json, tsconfig.web.json 文件中的 "include" 属性都加上 "src/shared/*.ts"。其次需要给它们两个添加 compilerOptions:
1
2
3
4
5
|
"compilerOptions": {
"paths": {
"@shared/*": ["src/shared/*"]
}
}
|
这样就可以通过引用 @shared/types 来直接引用 shared 文件夹中的属性了。实际上许多高级项目中使用的 @/xxx 也是这么实现的。例如我想在 renderer 中也实现类似的效果:
1
2
3
4
5
6
|
"compilerOptions": {
"paths": {
"@/*": ["src/renderer/src/*"],
"@shared/*": ["src/shared/*"]
}
}
|
那么也可以直接通过 @/component/xxx 之类的绝对路径访问相关的组件了。