戰(zhàn):用 Hono 后端與 React 前端構(gòu)建類(lèi)型安全的實(shí)時(shí)應(yīng)用)
Rivet Actors 全棧實(shí)戰(zhàn)用 Hono 后端與 React 前端構(gòu)建類(lèi)型安全的實(shí)時(shí)應(yīng)用【免費(fèi)下載鏈接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/riv/actors本文以倉(cāng)庫(kù)examples/hono-react示例為主體講解如何用 RivetKit 的actor()/setup()定義有狀態(tài) Actor用 Hono 承載 HTTP 路由與 React 靜態(tài)資源再通過(guò)rivetkit/react的useActor/useEvent在瀏覽器端實(shí)時(shí)訂閱 Actor 事件。讀完本文你將掌握單一代碼庫(kù) 共享類(lèi)型 雙向?qū)崟r(shí)通信的 Rivet Actors 全棧開(kāi)發(fā)模式并能直接復(fù)刻出可運(yùn)行的計(jì)數(shù)服務(wù)。示例概覽一個(gè)文件看懂全棧結(jié)構(gòu)examples/hono-react是倉(cāng)庫(kù)中展示全棧full-stack集成的最小可運(yùn)行示例后端是 Hono Web 框架前端是 ReactVite 構(gòu)建業(yè)務(wù)邏輯與狀態(tài)管理完全由 Rivet Actor 承擔(dān)前后端共享同一份 TypeScript 類(lèi)型定義。示例的目錄結(jié)構(gòu)如下相對(duì)倉(cāng)庫(kù)根目錄examples/hono-react/ ├── frontend/ # React 前端App.tsx、main.tsx ├── src/ │ ├── index.ts # Actor 定義 registry 啟動(dòng)入口 │ └── server.ts # Hono HTTP 服務(wù)器 ├── index.html # Vite 入口 HTML ├── package.json # 依賴(lài)與 npm scripts ├── tsconfig.json # 前后端統(tǒng)一 TS 配置 ├── turbo.json # Turborepo 任務(wù)依賴(lài)聲明 └── vite.config.ts # Vite 構(gòu)建與開(kāi)發(fā)代理配置從依賴(lài)清單package.json可以看出它的技術(shù)選型運(yùn)行時(shí)rivetkit^2.3.17提供actor、event、setup等 Actor 原語(yǔ)hono^4.7.0提供 HTTP 路由前端react/react-dom^18.2.0與rivetkit/react^2.3.17提供createRivetKit、useActor等 React hooks開(kāi)發(fā)工具tsx負(fù)責(zé)以 watch 模式運(yùn)行 TS 服務(wù)端vite負(fù)責(zé)前端開(kāi)發(fā)服務(wù)器concurrently將兩者并行拉起vitest用于測(cè)試。值得注意README 中提到的 Actor 定義位于src/backend/registry.ts而在當(dāng)前倉(cāng)庫(kù)中實(shí)際路徑為 src/index.ts未拆分 backend 子目錄。本文以倉(cāng)庫(kù)實(shí)際源碼為準(zhǔn)??焖匍_(kāi)始三步跑起全棧示例README 給出了標(biāo)準(zhǔn)啟動(dòng)流程結(jié)合本倉(cāng)庫(kù)實(shí)際環(huán)境整理如下# 克隆倉(cāng)庫(kù)若尚未克隆 git clone https://gitcode.com/GitHub_Trending/riv/actors.git cd riv/actors/examples/hono-react # 安裝依賴(lài)倉(cāng)庫(kù)使用 pnpm workspace也可用 npm install npm install # 并行啟動(dòng) Hono 服務(wù)端與 Vite 前端 npm run devdev腳本定義在 package.json 中dev: concurrently -n server,vite \tsx --watch src/index.ts\ \vite\它同時(shí)做兩件事tsx --watch src/index.ts以 watch 模式運(yùn)行 Actor 入口文件變更后自動(dòng)重啟vite啟動(dòng) Vite 開(kāi)發(fā)服務(wù)器提供 React 前端熱更新。此外還有dev:server僅啟動(dòng)服務(wù)端、buildVite 構(gòu)建、start以tsx直接運(yùn)行用于生產(chǎn)、check-typestsc --noEmit類(lèi)型檢查與testvitest 運(yùn)行測(cè)試等腳本可在開(kāi)發(fā)與部署階段按需選用。啟動(dòng)后Rivet 運(yùn)行時(shí)默認(rèn)監(jiān)聽(tīng)http://localhost:6420見(jiàn)下文 Vite 代理配置瀏覽器打開(kāi) Vite 提供的地址即可看到計(jì)數(shù)器界面。Actor 定義actoreventsetup三件套示例的核心業(yè)務(wù)邏輯全部濃縮在 src/index.ts 中import { actor, event, setup } from rivetkit; export const counter actor({ state: { count: 0 }, events: { newCount: eventnumber(), }, actions: { increment: (c, x: number) { c.state.count x; c.broadcast(newCount, c.state.count); return c.state.count; }, }, }); export const registry setup({ use: { counter }, }); registry.start();這段代碼完整覆蓋了 Rivet Actor 的三個(gè)核心概念1. 有狀態(tài) Actorstatestate: { count: 0 }聲明了 Actor 的持久狀態(tài)。狀態(tài)由 Rivet 運(yùn)行時(shí)管理跨請(qǐng)求保留這也是有狀態(tài)工作負(fù)載stateful workloads的基礎(chǔ)。在 actor() 定義中可以看到actor()接收一個(gè)經(jīng)過(guò)ActorConfigSchema校驗(yàn)的配置對(duì)象泛型參數(shù)涵蓋TState、TConnParams、TEvents、TActions等從而在編譯期約束狀態(tài)、事件與動(dòng)作的類(lèi)型。2. 類(lèi)型化事件eventT()newCount: eventnumber()聲明了一個(gè)攜帶number負(fù)載的實(shí)時(shí)事件。動(dòng)作內(nèi)部通過(guò)c.broadcast(newCount, c.state.count)向所有已連接客戶端廣播新計(jì)數(shù)官方文檔中說(shuō)明broadcast面向使用.connect()建立的連接事件通道獨(dú)立于低層 WebSocket。eventT在 schema.ts 中定義配合 TypeScript 推斷事件名與負(fù)載類(lèi)型天然聯(lián)動(dòng)。3. 動(dòng)作actionsincrement接收上下文c與參數(shù)x修改狀態(tài)、廣播事件并返回新值。動(dòng)作默認(rèn)并行執(zhí)行且極輕量官方文檔 actions.mdx 說(shuō)明其可通過(guò) HTTP 或 WebSocket 調(diào)用需要并發(fā)控制時(shí)可升級(jí)為隊(duì)列queues。注意客戶端調(diào)用動(dòng)作是異步的即使動(dòng)作本身是同步函數(shù)也需要await。4. Registry 與啟動(dòng)setup({ use: { counter } })將 Actor 注冊(cè)進(jìn) registryregistry.start()啟動(dòng)運(yùn)行時(shí)。registry 的類(lèi)型typeof registry會(huì)被服務(wù)端客戶端和 React hooks 復(fù)用是全棧類(lèi)型安全的樞紐。Hono 后端HTTP 端點(diǎn) Rivet 請(qǐng)求轉(zhuǎn)發(fā)服務(wù)端邏輯在 src/server.tsimport { Hono } from hono; import { createClient } from rivetkit/client; import { registry } from ./index.ts; const client createClienttypeof registry(); const app new Hono(); // Example HTTP endpoint app.post(/increment/:name, async (c) { const name c.req.param(name); const counter client.counter.getOrCreate(name); const newCount await counter.increment(1); return c.text(String(newCount)); }); app.all(/api/rivet/*, (c) registry.handler(c.req.raw)); export default app;這里有三個(gè)值得深入的點(diǎn)1. 類(lèi)型安全的客戶端createClienttypeof registry()客戶端以 registry 類(lèi)型為泛型參數(shù)于是client.counter自動(dòng)獲得counterActor 的類(lèi)型信息——getOrCreate、increment的簽名和返回值全部可被編譯器校驗(yàn)不存在手寫(xiě)接口對(duì)齊的負(fù)擔(dān)。這正是示例強(qiáng)調(diào)的前后端共享類(lèi)型落地點(diǎn)createClient與ActorDefinition的類(lèi)型傳播鏈路在 client 模塊 中定義。2. 按 key 取用 ActorgetOrCreate(name)Rivet Actor 以 key 區(qū)分實(shí)例。getOrCreate語(yǔ)義是存在則取、不存在則創(chuàng)建因此每個(gè)不同的name都會(huì)獲得獨(dú)立的counter實(shí)例與獨(dú)立的狀態(tài)。前端輸入框切換名稱(chēng)實(shí)際上就是在不同 Actor 實(shí)例之間切換。3. 一鍵暴露全部 Actor APIregistry.handler(c.req.raw)這一行把 Hono 收到的請(qǐng)求原始 Request 交給 Rivet registry 處理相當(dāng)于把 Actor 的 actions 全部通過(guò)/api/rivet/*路徑以 HTTP 形式暴露同時(shí) WebSocket 握手也由此接管。這樣就不需要為每個(gè) action 手寫(xiě)路由業(yè)務(wù)邏輯保持在 Actor 內(nèi)部HTTP 層只是薄薄的傳輸殼。React 前端createRivetKituseActoruseEvent前端在 frontend/App.tsx 中消費(fèi) Actorimport { createRivetKit } from rivetkit/react; import { useState } from react; import type { registry } from ../src/index.ts; const { useActor } createRivetKittypeof registry(http://localhost:6420); function App() { const [count, setCount] useState(0); const [counterName, setCounterName] useState(test-counter); const counter useActor({ name: counter, key: [counterName], }); counter.useEvent(newCount, (x: number) setCount(x)); const increment async () { await counter.connection?.increment(1); }; return ( div h1Counter: {count}/h1 input typetext value{counterName} onChange{(e) setCounterName(e.target.value)} placeholderCounter name / button onClick{increment}Increment/button /div ); } export default App;1.createRivetKittypeof registry(url)由rivetkit/react包提供的工廠函數(shù)內(nèi)部創(chuàng)建 Rivet 客戶端并返回一組 React hooks實(shí)現(xiàn)見(jiàn) react/src/mod.ts。它同樣以typeof registry為泛型因此 hooks 天然感知所有 Actor 的名稱(chēng)、key 結(jié)構(gòu)、actions 與 events 簽名。2.useActor({ name, key })連接指定 Actor 實(shí)例并訂閱其狀態(tài)。其實(shí)現(xiàn)基于getOrCreateActor與useStorereact/src/mod.ts相同 opts 會(huì)復(fù)用同一 Actor 實(shí)例組件掛載時(shí)自動(dòng)mount()建立連接卸載時(shí)自動(dòng)清理。返回對(duì)象包含handle、connection、connStatus、error等字段UseActorState 類(lèi)型。3.counter.useEvent(newCount, cb)以 hooks 方式訂閱 Actor 廣播的實(shí)時(shí)事件事件回調(diào)通過(guò)useRef保持最新、由useEffect掛載與清理react/src/mod.ts。當(dāng)后端increment里執(zhí)行c.broadcast(newCount, ...)時(shí)前端回調(diào)被觸發(fā)setCount更新 UI——這就是不輪詢(xún)、無(wú)外部 pub-sub的實(shí)時(shí)通道。4. 調(diào)用動(dòng)作counter.connection?.increment(1)通過(guò)已建立的連接直接調(diào)用incrementaction編譯期即可校驗(yàn)參數(shù)與返回類(lèi)型。由于 action 調(diào)用是異步的這里使用await。前端入口 frontend/main.tsx 與 index.html 是標(biāo)準(zhǔn)的 React 掛載流程無(wú)特殊邏輯。開(kāi)發(fā)期聯(lián)通Vite 代理與端口約定前后端在開(kāi)發(fā)期通過(guò) vite.config.ts 中的代理聯(lián)通server: { clearScreen: false, proxy: { /actors: { target: http://localhost:6420, ws: true }, /metadata: { target: http://localhost:6420 }, /health: { target: http://localhost:6420 }, }, },三個(gè)路徑前綴/actors、/metadata、/health被代理到localhost:6420——即 Rivet 運(yùn)行時(shí)默認(rèn)監(jiān)聽(tīng)的端口/actors開(kāi)啟了ws: true允許 WebSocket 升級(jí)這是useActor建立實(shí)時(shí)連接、接收newCount事件的關(guān)鍵瀏覽器端代碼以http://localhost:6420直連見(jiàn) App.tsx 的createRivetKit參數(shù)而靜態(tài)資源由 Vite 提供兩者在開(kāi)發(fā)期并存互不干擾。build腳本將產(chǎn)物輸出到public目錄build.outDir: public生產(chǎn)部署時(shí)由 Hono 或靜態(tài)服務(wù)器統(tǒng)一托管。端到端數(shù)據(jù)流回顧把上述模塊串起來(lái)一次點(diǎn)擊按鈕的完整鏈路是React 按鈕觸發(fā)counter.connection?.increment(1)通過(guò) WebSocket經(jīng) Vite 代理轉(zhuǎn)發(fā)到localhost:6420調(diào)用counterActor 的incrementactionRivet 運(yùn)行時(shí)執(zhí)行incrementc.state.count x更新持久狀態(tài)隨后c.broadcast(newCount, count)向所有連接廣播新值前端counter.useEvent(newCount, ...)收到事件setCount觸發(fā) React 重渲染同時(shí)任意 HTTP 客戶端如curl -X POST /increment/foo經(jīng)由 Hono 路由app.post(/increment/:name)走client.counter.getOrCreate(name).increment(1)完成同一操作——兩種調(diào)用方式共享同一份狀態(tài)。這一模式正是 Rivet Actors 面向 AI Agent、協(xié)作應(yīng)用與持久化執(zhí)行場(chǎng)景的典型形態(tài)業(yè)務(wù)狀態(tài)收斂在 Actor 內(nèi)HTTP 與 WebSocket 都只是訪問(wèn)通道前端通過(guò)類(lèi)型安全的 hooks 獲得實(shí)時(shí)同步。相關(guān)文檔與延伸閱讀本示例涉及的核心能力在倉(cāng)庫(kù)官方文檔中均有系統(tǒng)講解可繼續(xù)深入Actions動(dòng)作動(dòng)作的定義、嵌套動(dòng)作、三種調(diào)用方式前端createClient/ 后端registry.handler/ Actor 間c.client()與錯(cuò)誤處理State狀態(tài)Actor 狀態(tài)的生命周期與更新語(yǔ)義Events實(shí)時(shí)事件c.broadcast全量廣播、conn.send定向發(fā)送與客戶端訂閱方式Connections連接.connect()持久連接的建立與維護(hù)快速上手從零開(kāi)始編寫(xiě)第一個(gè) Actor 的完整流程。若要在自己的項(xiàng)目中使用可參照 turbo.json 聲明build對(duì)rivetkit/react#build與rivetkit#build的依賴(lài)確保本地開(kāi)發(fā)時(shí) SDK 包先行構(gòu)建。本示例以 MIT 協(xié)議開(kāi)源可直接作為腳手架改造使用。【免費(fèi)下載鏈接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/riv/actors創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考