参考electron-trpc的示例代码:(做事件订阅/事件触发)的时候:如何使用(订阅和触发)呢?

您好,在参考electron-trpc的示例代码:(做事件订阅/事件触发)的时候:
https://github.com/jsonnull/electron-trpc/blob/main/examples/basic-react/electron/api.ts

代码如下:

import z from 'zod';
import { initTRPC } from '@trpc/server';
import { observable } from '@trpc/server/observable';
import { EventEmitter } from 'events';

const ee = new EventEmitter();

const t = initTRPC.create({ isServer: true });

export const router = t.router({
  greeting: t.procedure.input(z.object({ name: z.string() })).query((req) => {
    const { input } = req;

    ee.emit('greeting', `Greeted ${input.name}`);
    return {
      text: `Hello ${input.name}` as const,
    };
  }),
  subscription: t.procedure.subscription(() => {
    return observable((emit) => {
      function onGreet(text: string) {
        emit.next({ text });
      }

      ee.on('greeting', onGreet);

      return () => {
        ee.off('greeting', onGreet);
      };
    });
  }),
});

export type AppRouter = typeof router;

请问下,应该如何使用呢?

1、在主进程如何使用(订阅和触发)?
2、在渲染进程如何使用(订阅和触发)?

阅读 1.1k
avatarAI BotBETA

1. 在主进程如何使用(订阅和触发)?

在主进程中,你需要通过 electron-trpc 提供的 API 来订阅事件和触发事件。以下是如何在主进程中实现这些功能的示例:

订阅事件

const { createClient } = require('@trpc/client');
const { app, BrowserWindow } = require('electron');
const path = require('path');

let mainWindow;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      enableRemoteModule: false,
      nodeIntegration: false,
    },
  });

  mainWindow.loadFile('index.html');

  // Create TRPC client
  const trpcClient = createClient({
    url: 'http://localhost:3000/api', // 替换为你的 TRPC 服务器 URL
  });

  // 订阅事件
  const subscription = trpcClient.subscription.subscription({});

  subscription.subscribe({
    next: (data) => {
      console.log('Received greeting:', data.text);
    },
    error: (err) => {
      console.error('Subscription error:', err);
    },
    complete: () => {
      console.log('Subscription completed');
    },
  });
}

app.on('ready', createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

// 触发事件
const ee = require('events').EventEmitter;
const eventEmitter = new ee();

// 在适当的地方(例如响应某个操作)
eventEmitter.emit('greeting', 'John Doe');

注意:这里的 eventEmitter.emit('greeting', 'John Doe'); 是为了模拟触发事件,但在实际场景中,你应该使用你的 TRPC 服务器中的 ee.emit('greeting', ...) 来触发事件。

2. 在渲染进程如何使用(订阅和触发)?

在渲染进程中,由于 Electron 的安全策略,直接访问主进程的 EventEmitter 实例是不可能的。你应该通过 ipcRenderer 与主进程通信,然后在主进程中处理这些请求。

在渲染进程中订阅事件

你可以通过 preload.js 文件来暴露一个接口给渲染进程,以便订阅和触发事件。但是,由于 TRPC 已经处理了 API 的路由,你通常会在渲染进程中直接使用 TRPC 客户端来订阅事件。

// 渲染进程中的 JavaScript (例如:renderer.js)
const { createClient } = require('@trpc/client/react');

const trpcClient = createClient({
  url: 'http://localhost:3000/api', // 替换为你的 TRPC 服务器 URL
});

function useGreetingSubscription() {
  const subscription = trpcClient.subscription.subscription({});

  return React.useMemo(() => {
    const [data, setData] = React.useState(null);
    const [error, setError] = React.useState(null);

    React.useEffect(() => {
      const unsubscribe = subscription.subscribe({
        next: (res) => setData(res),
        error: (err) => setError(err),
      });

      return () => unsubscribe();
    }, [subscription]);

    return { data, error };
  }, [subscription]);
}

function App() {
  const { data, error } = useGreetingSubscription();

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      {data && <div>Received greeting: {data.text}</div>}
    </div>
  );
}

export default App;

在渲染进程中触发事件

由于安全原因,渲染进程不应该直接触发主进程中的事件。相反,你应该通过 ipcRenderer 发送一个消息到主进程,然后让主进程处理这个请求并触发相应的事件。但是,在这个场景中,由于你已经在使用 TRPC,你应该通过 TRPC 的 greeting 过程来触发事件,而不是直接操作 EventEmitter

// 渲染进程中的 JavaScript (例如:通过某个按钮触发)
async function triggerGreeting() {
  try {
    const res = await trpcClient.query('greeting', { name: 'John Doe' });
    console.log('Greeting response:', res);
  } catch (err) {
    console.error('Error triggering greeting:', err);
  }
}

// 例如,在某个按钮点击事件中调用 triggerGreeting()

在这个例子中,当 triggerGreeting 函数被调用时,它会通过 TRPC 服务器触发 greeting 事件,然后订阅这个事件的客户端(无论是在主进程还是渲染进程)都会收到这个事件并处理它。

1 个回答

1.设置项目结构

创建一个新的项目文件夹,并在其中创建以下文件和文件夹结构:

my-electron-trpc-app/
├── main.ts
├── preload.ts
├── renderer.ts
├── package.json
├── tsconfig.json
└── webpack.config.js

2.安装依赖

在项目根目录下运行以下命令来安装必要的依赖:

npm init -y
npm install electron @trpc/server @trpc/client electron-trpc zod typescript ts-loader webpack webpack-cli

3. 配置 TypeScript

在项目根目录下创建 tsconfig.json 文件,并添加以下内容:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist"
  },
  "include": ["./**/*.ts"]
}

4. 配置 Webpack

在项目根目录下创建 webpack.config.js 文件,并添加以下内容:

const path = require('path');

module.exports = {
  entry: {
    main: './main.ts',
    preload: './preload.ts',
    renderer: './renderer.ts',
  },
  target: 'electron-main',
  module: {
    rules: [
      {
        test: /\.ts$/,
        include: /src/,
        use: [{ loader: 'ts-loader' }],
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
  },
};

5. 添加 Electron 脚本

package.json

"scripts": {
  "start": "webpack && electron ."
}

6. 编写代码

main.ts(主进程代码)

 import { app, BrowserWindow } from 'electron';
import { createIPCHandler } from 'electron-trpc/main';
import { initTRPC } from '@trpc/server';
import { observable } from '@trpc/server/observable';
import { EventEmitter } from 'events';
import { z } from 'zod';

const ee = new EventEmitter();
const t = initTRPC.create({ isServer: true });

export const router = t.router({
  greeting: t.procedure.input(z.object({ name: z.string() })).query((req) => {
    const { input } = req;
    ee.emit('greeting', `Greeted ${input.name}`);
    return { text: `Hello ${input.name}` };
  }),
  subscription: t.procedure.subscription(() => {
    return observable((emit) => {
      function onGreet(text: string) {
        emit.next({ text });
      }
      ee.on('greeting', onGreet);
      return () => {
        ee.off('greeting', onGreet);
      };
    });
  }),
});

app.on('ready', () => {
  const win = new BrowserWindow({
    webPreferences: {
      preload: 'path/to/preload.js',
    },
  });
  createIPCHandler({ router, windows: [win] });

  // 示例:在主进程中触发事件
  setTimeout(() => {
    ee.emit('greeting', 'Hello from main process');
  }, 5000); // 5秒后触发事件
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

preload.ts (预加载脚本)

import { exposeElectronTRPC } from 'electron-trpc/main';

process.once('loaded', async () => {
  exposeElectronTRPC();
});

renderer.ts (渲染进程代码)

 import { createTRPCProxyClient } from '@trpc/client';
import { ipcLink } from 'electron-trpc/renderer';

export const client = createTRPCProxyClient({
  links: [ipcLink()],
});

// 触发事件
client.greeting({ name: 'YourName' }).then(response => {
  console.log(response.text); // 输出: Hello YourName
});

// 订阅事件
client.subscription('subscription', {
  next(data) {
    console.log(data.text);
  },
  error(err) {
    console.error('Subscription error:', err);
  },
});

7. 运行项目

在项目根目录下运行以下命令来构建并启动 Electron 应用:

npm start
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏