feat(ReduxService): Implement comprehensive Redux state management service for main process
This commit is contained in:
parent
9b79051ea5
commit
3a6d49d3fc
219
src/main/services/ReduxService.ts
Normal file
219
src/main/services/ReduxService.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import { ipcMain } from 'electron'
|
||||||
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
import { windowService } from './WindowService'
|
||||||
|
|
||||||
|
type StoreValue = any
|
||||||
|
type Unsubscribe = () => void
|
||||||
|
|
||||||
|
export class ReduxService extends EventEmitter {
|
||||||
|
private stateCache: any = {}
|
||||||
|
private isReady = false
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.setupIpcHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupIpcHandlers() {
|
||||||
|
// 监听 store 就绪事件
|
||||||
|
ipcMain.handle('redux-store-ready', () => {
|
||||||
|
this.isReady = true
|
||||||
|
this.emit('ready')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听 store 状态变化
|
||||||
|
ipcMain.on('redux-state-change', (_, newState) => {
|
||||||
|
this.stateCache = newState
|
||||||
|
this.emit('stateChange', newState)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForStoreReady(webContents: Electron.WebContents, timeout = 10000): Promise<void> {
|
||||||
|
if (this.isReady) return
|
||||||
|
|
||||||
|
const startTime = Date.now()
|
||||||
|
while (Date.now() - startTime < timeout) {
|
||||||
|
try {
|
||||||
|
const isReady = await webContents.executeJavaScript(`
|
||||||
|
!!window.store && typeof window.store.getState === 'function'
|
||||||
|
`)
|
||||||
|
if (isReady) {
|
||||||
|
this.isReady = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略错误,继续等待
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
|
}
|
||||||
|
throw new Error('Timeout waiting for Redux store to be ready')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加同步获取状态的方法
|
||||||
|
getStateSync() {
|
||||||
|
return this.stateCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加同步选择器方法
|
||||||
|
selectSync<T = StoreValue>(selector: string): T | undefined {
|
||||||
|
try {
|
||||||
|
// 使用 Function 构造器来安全地执行选择器
|
||||||
|
const selectorFn = new Function('state', `return ${selector}`)
|
||||||
|
return selectorFn(this.stateCache)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to select from cache:', error)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改 select 方法,优先使用缓存
|
||||||
|
async select<T = StoreValue>(selector: string): Promise<T> {
|
||||||
|
try {
|
||||||
|
// 如果已经准备就绪,先尝试从缓存中获取
|
||||||
|
if (this.isReady) {
|
||||||
|
const cachedValue = this.selectSync<T>(selector)
|
||||||
|
if (cachedValue !== undefined) {
|
||||||
|
return cachedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果缓存中没有,再从渲染进程获取
|
||||||
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
if (!mainWindow) {
|
||||||
|
throw new Error('Main window is not available')
|
||||||
|
}
|
||||||
|
await this.waitForStoreReady(mainWindow.webContents)
|
||||||
|
return await mainWindow.webContents.executeJavaScript(`
|
||||||
|
(() => {
|
||||||
|
const state = window.store.getState();
|
||||||
|
return ${selector};
|
||||||
|
})()
|
||||||
|
`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to select store value:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 派发 action
|
||||||
|
async dispatch(action: any): Promise<void> {
|
||||||
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
if (!mainWindow) {
|
||||||
|
throw new Error('Main window is not available')
|
||||||
|
}
|
||||||
|
await this.waitForStoreReady(mainWindow.webContents)
|
||||||
|
try {
|
||||||
|
await mainWindow.webContents.executeJavaScript(`
|
||||||
|
window.store.dispatch(${JSON.stringify(action)})
|
||||||
|
`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to dispatch action:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅状态变化
|
||||||
|
async subscribe(selector: string, callback: (newValue: any) => void): Promise<Unsubscribe> {
|
||||||
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
if (!mainWindow) {
|
||||||
|
throw new Error('Main window is not available')
|
||||||
|
}
|
||||||
|
await this.waitForStoreReady(mainWindow.webContents)
|
||||||
|
|
||||||
|
// 在渲染进程中设置监听
|
||||||
|
await mainWindow.webContents.executeJavaScript(`
|
||||||
|
if (!window._storeSubscriptions) {
|
||||||
|
window._storeSubscriptions = new Set();
|
||||||
|
|
||||||
|
// 设置全局状态变化监听
|
||||||
|
const unsubscribe = window.store.subscribe(() => {
|
||||||
|
const state = window.store.getState();
|
||||||
|
window.electron.ipcRenderer.send('redux-state-change', state);
|
||||||
|
});
|
||||||
|
|
||||||
|
window._storeSubscriptions.add(unsubscribe);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
// 在主进程中处理回调
|
||||||
|
const handler = async () => {
|
||||||
|
try {
|
||||||
|
const newValue = await this.select(selector)
|
||||||
|
callback(newValue)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in subscription handler:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('stateChange', handler)
|
||||||
|
return () => {
|
||||||
|
this.off('stateChange', handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取整个状态树
|
||||||
|
async getState(): Promise<any> {
|
||||||
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
if (!mainWindow) {
|
||||||
|
throw new Error('Main window is not available')
|
||||||
|
}
|
||||||
|
await this.waitForStoreReady(mainWindow.webContents)
|
||||||
|
try {
|
||||||
|
return await mainWindow.webContents.executeJavaScript(`
|
||||||
|
window.store.getState()
|
||||||
|
`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get state:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量执行 actions
|
||||||
|
async batch(actions: any[]): Promise<void> {
|
||||||
|
for (const action of actions) {
|
||||||
|
await this.dispatch(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reduxService = new ReduxService()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
async function example() {
|
||||||
|
try {
|
||||||
|
// 读取状态
|
||||||
|
const settings = await reduxService.select('state.settings')
|
||||||
|
console.log('settings', settings)
|
||||||
|
|
||||||
|
// 派发 action
|
||||||
|
await reduxService.dispatch({
|
||||||
|
type: 'settings/updateApiKey',
|
||||||
|
payload: 'new-api-key'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 订阅状态变化
|
||||||
|
const unsubscribe = await reduxService.subscribe('state.settings.apiKey', (newValue) => {
|
||||||
|
console.log('API key changed:', newValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 批量执行 actions
|
||||||
|
await reduxService.batch([
|
||||||
|
{ type: 'action1', payload: 'data1' },
|
||||||
|
{ type: 'action2', payload: 'data2' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 同步方法虽然可能不是最新的数据,但响应更快
|
||||||
|
const apiKey = reduxService.selectSync('state.settings.apiKey')
|
||||||
|
console.log('apiKey', apiKey)
|
||||||
|
|
||||||
|
// 处理保证是最新的数据
|
||||||
|
const apiKey1 = await reduxService.select('state.settings.apiKey')
|
||||||
|
console.log('apiKey1', apiKey1)
|
||||||
|
|
||||||
|
// 取消订阅
|
||||||
|
unsubscribe()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user