ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Wasp 框架 WebSocket 全栈实时通信指南:从 Wasp 配置到 React Hooks 与类型安全

Wasp 框架 WebSocket 全栈实时通信指南:从 Wasp 配置到 React Hooks 与类型安全 Wasp 框架 WebSocket 全栈实时通信指南从 Wasp 配置到 React Hooks 与类型安全【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/waspWasp 通过内置的 Socket.IO 集成为 Web 应用提供了开箱即用的实时通信能力你只需在app声明中开启webSocket字段并分别编写服务端事件处理与客户端 React Hook 调用即可获得自动配置的 URL、CORS 处理以及全栈类型安全。读完本文你将掌握在 Wasp 项目中启用 WebSocket、定义事件处理器、在 React 中使用useSocket/useSocketListener完成实时聊天、实时投票等场景的完整实战方案。本文以 version-0.11.8 版 WebSocket 文档 为核心骨架并结合仓库内的 实时投票示例应用 与生成器模板源码从配置、编码到底层初始化原理逐层展开。一、Wasp 中的 WebSocket 集成概览Wasp 提供了完全集成的 WebSocket 体验其底层在客户端与服务端均基于 Socket.IO。框架为你处理了三件脏活累活URL 自动配置客户端 Socket 自动连接到由 Wasp 生成的服务端 API 地址对应config.apiUrl无需手写连接地址CORS 自动开启服务端 Socket.IO 实例创建时自动以config.frontendUrl作为 CORS 允许来源React 抽象提供useSocket与useSocketListener两个 Hook供 React 组件直接使用。要从零开始接入 WebSocket按官方文档给出的四步走即可在服务端定义 WebSocket 逻辑事件与处理器在 Wasp 文件中开启 WebSockets并与服务端逻辑关联在客户端 React 中通过useSocket和useSocketListener使用 WebSocket可选为事件与负载定义类型获得全栈类型安全。二、在 Wasp 文件中开启 WebSocket在app声明中添加webSocket字段并传入必需的fn指向服务端事件处理函数即可开启 WebSocket。autoConnect为可选项控制客户端是否自动建立连接默认值为true。version-0.11.8 时代的.wasp文件写法如下app todoApp { // ... webSocket: { fn: import { webSocketFn } from server/webSocket.js, autoConnect: true, // optional, default: true }, }其中server/webSocket.js指向src/server/webSocket.js。如果使用 JavaScript 与 TypeScript此处写法一致仅导入路径对应的文件扩展名不同。作为对照仓库当前版本的示例应用 examples/websockets-realtime-voting/main.wasp.ts 采用的是新版 TS-specmain.wasp.ts写法但字段语义完全相同import { app } from wasp.sh/spec import { votingWebSocket } from ./src/ws-server with { type: ref } export default app({ name: whereDoWeEat, // ... auth、client 等其它配置 webSocket: { fn: votingWebSocket, }, // ... })三、定义服务端事件处理函数webSocketFn3.1 函数签名io与context在服务端你的 WebSocket 函数会收到两个参数io: ServerSocket.IO 服务端实例可用来注册所有常规的 Socket.IO 事件connection、disconnect、自定义事件等context包含 Wasp 应用的全部实体Entity例如context.entities.SomeEntity让你在事件回调中直接操作数据库。另外如果用户已登录服务端每个socket上会挂载socket.data.user可用于识别当前连接的用户身份。3.2 一个完整的聊天事件处理器以下是官方文档提供的webSocketFn示例以 TypeScript 版本展示JavaScript 版本除类型标注外逻辑一致import type { WebSocketDefinition, WaspSocketData } from wasp/webSocket import { v4 as uuidv4 } from uuid export const webSocketFn: WebSocketFn (io, context) { io.on(connection, (socket) { const username socket.data.user?.email || socket.data.user?.username || unknown console.log(a user connected: , username) socket.on(chatMessage, async (msg) { console.log(message: , msg) io.emit(chatMessage, { id: uuidv4(), username, text: msg }) // You can also use your entities here: // await context.entities.SomeEntity.create({ someField: msg }) }) }) } // Typing our WebSocket function with the events and payloads // allows us to get type safety on the client as well type WebSocketFn WebSocketDefinition ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData interface ServerToClientEvents { chatMessage: (msg: { id: string, username: string, text: string }) void; } interface ClientToServerEvents { chatMessage: (msg: string) void; } interface InterServerEvents {} // Data that is attached to the socket. // NOTE: Wasp automatically injects the JWT into the connection, // and if present/valid, the server adds a user to the socket. interface SocketData extends WaspSocketData {}要点解读io.emit(chatMessage, ...)向所有已连接客户端广播消息socket.on(...)则监听某个具体连接发来的事件socket.data.user由 Wasp 自动注入客户端建立连接时会携带会话凭证服务端在握手阶段校验通过后把用户对象挂到socket.data上详见下文底层实现一节context.entities让事件处理器可以读写数据库例如在聊天场景中将消息落库。3.3 全栈类型安全的来源WebSocketDefinition将webSocketFn标注为WebSocketDefinitionClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData后服务端定义的事件与负载类型会被自动暴露到客户端。从生成器模板 waspc/data/Generator/templates/sdk/wasp/server/webSocket/index.ts 可以看到它的定义export type WebSocketDefinition ClientToServerEvents extends EventsMap DefaultEventsMap, ServerToClientEvents extends EventsMap DefaultEventsMap, InterServerEvents extends EventsMap DefaultEventsMap, SocketData extends WaspSocketData WaspSocketData ( io: ServerClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData, context: { entities: { // 这里由 Wasp 生成所有实体 - prisma 客户端 } } ) Promisevoid | void export interface WaspSocketData { user?: AuthUser // 仅当开启 auth 时生成 }也就是说你在服务端写下的四个泛型参数会成为整个类型链的唯一事实来源single source of truth客户端 Hook 的类型都由它推导而来。四、在客户端使用 WebSocket4.1useSocketHookuseSocket提供客户端对 WebSocket 的访问返回socket: Socket用于发送socket.emit与接收事件的 Socket 实例isConnected: booleanSocket.IO 连接状态可用来渲染连接状态指示例如绿点/红点。两个注意点Wasp 默认会自动建立客户端到服务端的 WebSocket 连接因此无需手动调用socket.connect()或socket.disconnect()若在 Wasp 文件中设置了autoConnect: false则需要按需自行调用这两个方法。此外所有使用useSocket的组件共享同一个底层 socket 实例它是一个单例由WebSocketProvider在应用根部注入因此跨组件通信无需额外维护连接。4.2useSocketListenerHookuseSocketListener: (event, callback) void用于注册事件处理器并且会在组件卸载unmount时自动注销处理器避免内存泄漏与重复监听。4.3 完整客户端示例实时聊天页面import React, { useState } from react import { useSocket, useSocketListener, ServerToClientPayload, } from wasp/webSocket export const ChatPage () { const [messageText, setMessageText] useState // 使用辅助类型获取 chatMessage 事件的负载类型 ClientToServerPayloadchatMessage () const [messages, setMessages] useState ServerToClientPayloadchatMessage[] ([]) // socket 实例的类型由服务端定义推导而来 const { socket, isConnected } useSocket() // 类型安全的事件处理器chatMessage 事件与其负载类型在服务端定义 useSocketListener(chatMessage, logMessage) function logMessage(msg: ServerToClientPayloadchatMessage) { setMessages((priorMessages) [msg, ...priorMessages]) } function handleSubmit(e: React.FormEventHTMLFormElement) { e.preventDefault() // 类型安全的事件发送器 socket.emit(chatMessage, messageText) setMessageText() } const messageList messages.map((msg) ( li key{msg.id} em{msg.username}/em: {msg.text} /li )) const connectionIcon isConnected ? : return ( h2Chat {connectionIcon}/h2 div form onSubmit{handleSubmit} input typetext value{messageText} onChange{(e) setMessageText(e.target.value)} / button typesubmitSubmit/button /form ul{messageList}/ul /div / ) }TypeScript 模式下全栈类型安全在此生效VS Code 会对事件名与负载提供自动补全写错事件名或负载结构会直接报类型错误。另外可借助两个辅助类型精确提取某个事件的负载类型ClientToServerPayloadeventName客户端发送给服务端的事件负载类型ServerToClientPayloadeventName服务端推送给客户端的事件负载类型。这两个辅助类型的实现可在 waspc/data/Generator/templates/sdk/wasp/client/webSocket/index.ts 中看到其本质是Parameters...[0]的类型提取export type ServerToClientPayloadEvent extends keyof ServerToClientEvents ParametersServerToClientEvents[Event][0] export type ClientToServerPayloadEvent extends keyof ClientToServerEvents ParametersClientToServerEvents[Event][0]4.4 Hook 的底层实现从模板源码可以确认useSocket/useSocketListener的实现方式WebSocketProvider.tsx 创建了一个模块级单例socket io(config.apiUrl, { transports: [websocket], autoConnect: ... })并在 React Context 中暴露{ socket, isConnected }它还监听connect/disconnect事件来同步isConnected状态index.ts 中useSocketListener通过useEffect调用socket.on(event, handler)并在清理函数中socket.off(event, handler)这正是它卸载时自动注销的来源同一模板中refreshAuthToken()会在会话建立/清除时把sessionId写入socket.auth从而实现登录后自动携带身份建立连接。五、实战案例实时投票应用仓库中的 examples/websockets-realtime-voting 是一个完整的、可运行的 TypeScript 实时投票示例同时使用了 Wasp 内置认证与 WebSocket非常适合对照本文学习。5.1 服务端维护投票状态并广播在 src/ws-server.ts 中服务端定义了两个方向的事件并实现了完整的投票逻辑import { type WebSocketDefinition } from wasp/server/webSocket; interface ServerToClientEvents { updateState: (state: PollState) void; } interface ClientToServerEvents { vote: (optionId: number) void; askForStateUpdate: () void; } interface InterServerEvents {} export const votingWebSocket: WebSocketDefinition ClientToServerEvents, ServerToClientEvents, InterServerEvents (io, context) { const poll: PollState { /* 问题与三个选项每个选项含 votes: string[] */ }; io.on(connection, (socket) { if (!socket.data.user) { console.log(Socket connected without user); return; } const connectionUsername socket.data.user.getFirstProviderUserId(); socket.on(askForStateUpdate, () { socket.emit(updateState, poll); }); socket.on(vote, (optionId) { // 先移除该用户之前的投票再投给新选项 poll.options.forEach((option) { option.votes option.votes.filter((username) username ! connectionUsername); }); const option poll.options.find((o) o.id optionId); if (!option) return; option.votes.push(connectionUsername); io.emit(updateState, poll); // 广播最新状态给所有在线用户 }); socket.on(disconnect, () { /* ... */ }); }); };值得注意的细节未登录的 socket 直接返回不进入业务逻辑每个连接的投票记录用socket.data.user.getFirstProviderUserId()标识用户每次投票后用io.emit(updateState, poll)把整份状态广播给所有客户端——这正是多人在线看到投票结果实时刷新的原因。5.2 客户端订阅状态与发送投票在 src/pages/MainPage.tsx 中页面挂载时请求一次状态快照并订阅服务端广播import { type ServerToClientPayload, useSocket, useSocketListener } from wasp/client/webSocket; const MainPage () { const [poll, setPoll] useStateServerToClientPayloadupdateState | null(null); const { socket } useSocket(); useSocketListener(updateState, (newState) setPoll(newState)); useEffect(() { socket.emit(askForStateUpdate); // 挂载后拉取当前状态 }, []); function handleVote(optionId: number) { socket.emit(vote, optionId); } // ... 渲染选项卡片、票数进度条、已投票用户头像等 };这里的ServerToClientPayloadupdateState直接复用了服务端PollState的结构两端类型严格一致。5.3 端到端验证该示例附带的 Playwright 测试 e2e-tests/tests/simple.spec.ts 验证了完整的实时投票链路注册账号 → 登录 → 点击投票 → 断言用户名出现在卡片上、按钮变为 Voted 且被禁用、票数从 0 / 0 变为非零。这说明通过 WebSocket 投出的票不仅更新了本地状态还经由服务端广播回当前客户端形成了闭环验证。本地运行该示例的步骤详见其 READMEcd examples/websockets-realtime-voting wasp install wasp db migrate-dev wasp start六、API ReferencewebSocket字典字段如下app todoApp { // ... webSocket: { fn: import { webSocketFn } from server/webSocket.js, autoConnect: true, // optional, default: true }, }字段类型是否必需说明fnWebSocketFn必需定义 WebSocket 事件与处理器的服务端函数即上文webSocketFnautoConnectbool可选是否自动连接 WebSocket 服务器默认true七、底层实现原理生成器视角Wasp 的 WebSocket 支持并非黑盒其生成器模板完整揭示了服务端初始化与鉴权流程。7.1 服务端初始化与 CORS模板 waspc/data/Generator/templates/server/src/webSocket/initialization.ts 展示了服务端启动时发生的事情const io new Server(server, { cors: { origin: config.frontendUrl, } }) io.use(addUserToSocketDataIfAuthenticated) // 仅当开启 auth 时生成 const context { entities: { // 所有实体映射到 prisma 客户端注入给用户的 webSocketFn } } await (userWebSocketFn)(io, context)Socket.IO 实例挂载在既有 HTTP Server 上CORS 的origin自动取自config.frontendUrl这就是文档所说我们处理 CORS的实现context.entities由框架依据 Prisma schema 中的全部实体自动组装注入给用户函数若开启认证会注册一个握手中间件addUserToSocketDataIfAuthenticated从socket.handshake.auth.sessionId读取会话校验通过后把用户挂到socket.data.user异常时静默放行保持匿名连接。7.2 客户端连接与会话注入客户端模板 WebSocketProvider.tsx 中socket 单例创建于模块加载时io(config.apiUrl, { transports: [websocket], autoConnect })并实现refreshAuthToken()在会话设置/清除时同步socket.auth.sessionId必要时重连。这套机制保证了自动连接和登录后带身份通信两个体验默认成立。八、小结在 Wasp 中接入 WebSocket 的完整链路是app.webSocket配置开启 → 服务端webSocketFn用iocontext定义事件与数据库访问 → 客户端用useSocket拿连接、用useSocketListener订阅事件 → 可选地通过WebSocketDefinition获得贯穿前后端的类型安全。框架负责 URL、CORS、自动连接与会话注入这些样板逻辑而你只需专注业务事件本身。若想进一步深挖可直接阅读 version-0.11.8 的原始文档或对照新版语法文档 web/docs/advanced/web-sockets.md 以及完整的 实时投票示例 动手实践。【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表