@junsma/react
v0.1.0
Published
React bindings for @junsma/core: an external store consumed through useSyncExternalStore, with fine-grained subscriptions for streaming chat UIs.
Maintainers
Readme
@junsma/react
@junsma/core的 React 绑定:external store +useSyncExternalStore,支持细粒度订阅。
npm i @junsma/react @junsma/core react完整文档见仓库根目录 README。
快速开始
import { createAIStream } from '@junsma/core';
import { AIStreamProvider, useAIStream, useAIStreamStatus } from '@junsma/react';
const client = createAIStream({ endpoint: '/api/chat', protocol: 'ui-message' });
export function App() {
return (
<AIStreamProvider client={client}>
<Chat sessionId="chat-1" />
</AIStreamProvider>
);
}
function Chat({ sessionId }: { sessionId: string }) {
const { messages, sendMessage, stop, isStreaming, metrics } = useAIStream({ sessionId });
return (
<>
{messages.map((message) => (
<pre key={message.id}>{message.parts.map((part) => (part.type === 'text' ? part.text : '')).join('')}</pre>
))}
{isStreaming && <button onClick={() => stop()}>停止</button>}
<span>{metrics.ttftMs}ms</span>
<button onClick={() => void sendMessage('你好')}>发送</button>
</>
);
}细粒度订阅
token 洪流不应该拖着整页重渲染。只关心状态、指标或单条消息的组件请用切片 Hook:
const status = useAIStreamStatus(client, sessionId); // 只在状态跃迁时重渲染
const metrics = useAIStreamMetrics(client, sessionId); // 只订阅指标
const messages = useAIStreamMessages(client, sessionId); // 只订阅消息列表
// 或自定义切片 + 自定义比较器
const lastText = useSessionSelector(client, sessionId, (s) => s.messages.at(-1), Object.is);导出总览
| 导出 | 说明 |
| --- | --- |
| AIStreamProvider | 注入 client;session 生命周期独立于组件,路由切换不丢流 |
| useAIStreamClient / useAIStreamContext | 读取 client(后者不抛错,返回 null) |
| useAIStream(options) | 完整绑定:messages / status / isStreaming / error / metrics / finishReason / sendMessage / stop / regenerate / setMessages |
| useSessionSelector | 细粒度订阅原语 |
| useAIStreamStatus / useAIStreamMessages / useAIStreamMetrics | 常用切片 |
| useStoreSelector | 通用「external store + selector」Hook |
两个经典坑(已处理)
getSnapshot每次返回新对象会让 React 无限重渲染 —— store 缓存快照引用,selector 缓存 selection。- selector 返回新对象(
s => ({ ...s }))会导致每次都是新引用 —— 传isEqual比较器,相等时复用旧引用让 React bail out。
MIT
