tldraw SDK 的 `@tldraw/sync` 公共 API 全解析:`useSync`、`useSyncDemo` 与多人协同集成实战 📅 发布时间:2026/9/11 16:43:59 👁 浏览次数: tldraw SDK 的tldraw/sync公共 API 全解析useSync、useSyncDemo与多人协同集成实战【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldrawtldraw/sync是 tldraw SDK 中为 React 应用提供实时多人协同能力的官方绑定层本指南以该包的公共 API 报告packages/sync/api-report.api.md为骨架结合 packages/sync/src/useSync.ts、packages/sync/src/useSyncDemo.ts 等源码与测试系统讲解useSync/useSyncDemo两个 React Hook 的全部公开类型、配置项、底层连接机制与实战用法。读完本文你将能在一小时内为自己的 React 应用接入 tldraw 多人白板从连接 WebSocket、上传图片资源到展示在线光标、处理断线重连与鉴权失败。tldraw/sync是什么tldraw/sync是 tldraw SDK 的多人同步 React 绑定包packages/sync/package.json 中描述为 tldraw infinite canvas SDK (multiplayer sync react bindings)它把底层基于 WebSocket 的同步引擎tldraw/sync-core包装成两个声明式的 React HookuseSync连接任意自建服务器uri或connect二选一useSyncDemo直接连接 tldraw 官方演示服务器一行代码即可完成原型。从源码看包的入口 packages/sync/src/index.ts 做了两件事一是export * from tldraw/sync-core把底层同步协议全部透传出去例如TLSyncClient、TLPersistentClientSocket、ClientWebSocketAdapter等详见 packages/sync-core/src/index.ts二是显式导出本包新增的useSync、useSyncDemo及其配套类型并在模块加载时注册库版本信息。也就是说tldraw/sync本质上是一个薄封装 完整透传的包公共 API 报告中的每一项导出都在 React 与同步引擎之间扮演明确角色。RemoteTLStoreWithStatus远程同步 store 的状态机类型定义export type RemoteTLStoreWithStatus | Exclude TLStoreWithStatus, { status: synced-local } | { status: not-synced } | { status: synced-remote } | (ExtractTLStoreWithStatus, { status: synced-remote } { readonly objectAccess: TLObjectStoreAccess })RemoteTLStoreWithStatus是useSync与useSyncDemo的返回值类型。与基础类型TLStoreWithStatus不同它显式排除了not-synced本地未同步与synced-local仅本地两种状态——因为远程协同 store 的生命周期只有三种可能正在连接、已同步到服务器、出错。源码 packages/sync/src/useSync.ts 中的注释明确写道远程 store 始终处于 loading、connected 或 error 之一。三种状态与 objectAccess在synced-remote分支上类型额外挂载了一个readonly objectAccess: TLObjectStoreAccess字段。它表示服务器为本次会话授予的对象存储通道object-store lane例如评论记录的写权限与画布只读状态相互独立——也就是说一个会话可以被允许评论但不被允许编辑画布。该字段默认值为write见 packages/sync/src/useSync.ts。从 packages/sync/src/useSync.ts 的返回值推导逻辑可以看出完整状态机内部状态返回状态说明尚未初始化 / 无 readyClient{ status: loading }正在建立连接并同步初始状态出现 error{ status: error, error }连接失败或同步错误TLRemoteSyncError连接成功{ status: synced-remote, connectionStatus, store, objectAccess }store可直接传给Tldraw /其中connectionStatus取值来自TLPersistentClientSocketStatus为online | offline | error见 packages/sync-core/src/lib/TLSyncClient.ts且当 socket 处于 error 时会归一化为offline。典型消费模式function MyCollaborativeApp() { const store: RemoteTLStoreWithStatus useSync({ uri: wss://myserver.com/sync/room-123, assets: myAssetStore }) if (store.status loading) { return divConnecting to multiplayer session.../div } if (store.status error) { return divConnection failed: {store.error.message}/div } // store.status synced-remote return Tldraw store{store.store} / }Tldraw store{store} /会自行处理三种状态loading/error/synced-remote并渲染对应的界面而显式分支写法则适合做自定义加载页或错误提示。值得注意的是 packages/sync/src/useSync.test.tsx 中专门有一条测试验证当房间从旧 URI例如NOT_FOUND切换到新房间时旧客户端的 error 会被清除新房间正常进入synced-remote——这是onLoad回调中刻意保留objectAccess、丢弃旧 error 的防护逻辑packages/sync/src/useSync.ts。useSync连接自建服务器的核心 Hook签名与选项类型export function useSync(opts: UseSyncOptions TLStoreSchemaOptions): RemoteTLStoreWithStatusUseSyncOptions是联合类型由两个互斥分支构成export type UseSyncOptions UseSyncOptionsWithUri | UseSyncOptionsWithConnectFn export interface UseSyncOptionsWithUri extends UseSyncOptionsBase { uri: string | (() string | Promisestring) connect?: never } export interface UseSyncOptionsWithConnectFn extends UseSyncOptionsBase { connect: UseSyncConnectFn uri?: never }uri模式WebSocket 地址可静态字符串也可异步函数每次连接尝试都会重新调用便于携带会过期的鉴权 token 或做动态房间路由。HTTP/HTTPS 地址会被自动升级为 WebSocket 连接。connect模式自定义传输层工厂函数返回TLPersistentClientSocket适合不想用默认 WebSocket 传输的场景如自定义长连接协议。源码 packages/sync/src/useSync.ts 对两种模式做了强制校验同时传uri与connect会抛出uri and connect cannot be used together两者都不传则抛出uri or connect must be provided。uri模式的保留查询参数在uri模式下useSync内部通过ClientWebSocketAdapter建立连接packages/sync-core/src/lib/ClientWebSocketAdapter.ts并自动在 URI 上追加两个查询参数sessionId当前浏览器标签页的会话标识TAB_ID同一用户的多标签页会被识别为不同会话storeId本次 hook 实例生成的唯一 store 标识uniqueId()。sessionId与storeId是保留参数名——如果用户自己的 URI 中已经包含它们会抛出明确错误packages/sync/src/useSync.tsif (withParams.searchParams.has(sessionId)) { throw new Error(useSync. sessionId is a reserved query param name. Please use a different name) }服务端正是依据这两个参数区分会话、识别同一房间内的多个连接。你自己的鉴权参数如?token...需使用其他名字。选项总览UseSyncOptionsBase选项类型必填说明assetsTLAssetStore是图片/视频等二进制资源的上传与解析实现。生产环境必须提供否则大文件会以 base64 内联存储导致序列化性能问题uristring \| (() string \| Promisestring)二选一WebSocket 服务器地址可动态返回如携带刷新后的 tokenconnectUseSyncConnectFn二选一自定义传输层工厂返回TLPersistentClientSocketusersTLUserStore否用户身份、在线状态与操作归属attribution数据源getUserPresence(store, user) TLPresenceStateInfo \| null否自定义广播给其他客户端的 presence 数据光标、选区等onCustomMessageReceived(data: any) void否接收其他客户端经TLSyncClient.sendMessage发送的自定义消息themesPartialTLThemes否自定义主题色名会在 store 构建前自动注册保证持久化数据通过校验onMount(editor: Editor) void否internal编辑器挂载回调roomIdstring否internal房间标识仅用于埋点trackAnalyticsEvent(name, data) void否internal分析事件上报选项详解assets: TLAssetStore必填——资源存储抽象必须实现upload上传新文件并返回 URL与resolve展示时按上下文解析 URL。原型阶段可用inlineBase64AssetStore但生产环境必须替换为外部对象存储。源码注释特别强调不提供 assets 时大图片与视频会被内联为 base64序列化性能显著下降packages/sync/src/useSync.ts。const myAssetStore: TLAssetStore { upload: async (asset, file) { const url await uploadToCloudStorage(file) return { src: url } }, resolve: (asset, context) { return getOptimizedUrl(asset.src, context) } }users?: TLUserStore——用户 storecurrentUser提供当前用户身份用于 presence 广播与形状归属可选resolve(userId)按 ID 解析其他用户。两者都返回响应式Signal。若不提供useSync会回退到基于 localStorage 默认偏好实现的defaultUserStore并用createCachedUserResolve包装先尝试从 store 中的instance_presence记录反查用户信息packages/sync/src/useSync.ts。匿名会话时currentUser会退化为基于getUserPreferences()创建的匿名用户记录但归属attribution仍正确返回 null见 packages/sync/src/useSync.ts 的注释说明。getUserPresence?——响应式函数store 状态变化时被调用决定广播给其他客户端的 presence 对象。默认实现getDefaultUserPresence包含标准光标位置与选区状态自定义实现可附加当前工具、视图状态等数据返回null则隐藏 presencepackages/sync/src/useSync.tsgetUserPresence: (store, user) ({ userId: user.id, userName: user.name, cursor: { x: 100, y: 200 }, currentTool: select, isActive: true })onCustomMessageReceived?——自定义消息通道。配合TLSyncClient.sendMessage使用可在形状与 presence 同步之外实现任意客户端间通信例如聊天消息onCustomMessageReceived: (data) { if (data.type chat) { displayChatMessage(data.message, data.userId) } }themes?: PartialTLThemes——自定义主题。传入后useSync会在构建 store 前调用resolveThemes、registerColorsFromThemes、registerFontsFromThemes完成注册使带自定义颜色的持久化数据在加载时能通过校验packages/sync/src/useSync.ts。动态鉴权示例function AuthenticatedApp() { const store useSync({ // 每次连接尝试都会重新执行token 过期后可以刷新 uri: async () { const token await getAuthToken() return wss://myserver.com/sync/room-123?token${token} }, assets: authenticatedAssetStore, users: myUserStore, getUserPresence: (store, user) ({ userId: user.id, userName: user.name, cursor: getCurrentCursor(store) }) }) return Tldraw store{store} / }底层生命周期TLSyncClient与连接状态useSync内部把配置编译为一个TLSyncClientTLRecord, TLStorepackages/sync/src/useSync.ts并围绕它做了四件关键工作连接状态信号化把 socket 的connectionStatus包装成atom(collaboration status, ...)onStatusChange时更新并以collaboration.status注入 storepackages/sync/src/useSync.ts。只读模式切换onAfterConnect时根据服务端下发的isReadonly在事务内设置syncMode为readonly或readwrite同时调用store.ensureStoreIsUsable()做崩溃恢复兜底packages/sync/src/useSync.ts。presence 派生通过createPresenceStateDerivation(currentUser, { getUserPresence })(store)构建响应式 presence还用一个computed根据instance_presence记录数量判断当前房间是否只有自己一个会话otherSessions.size 0决定 presence 同步频率走solo还是full模式packages/sync/src/useSync.ts。错误分类上报onSyncError根据TLSyncErrorCloseEventReason区分NOT_FOUND房间不存在、FORBIDDEN无权限、NOT_AUTHENTICATED未认证、RATE_LIMITED限流等场景记录埋点后以TLRemoteSyncError设置错误状态并关闭 socketpackages/sync/src/useSync.ts。组件卸载时effect cleanup会依次执行client.close()与socket.close()并通过didCancel标记防止异步回调在卸载后继续写入状态packages/sync/src/useSync.ts。useSyncDemo一行代码接入官方演示服务器签名与选项export function useSyncDemo(options: UseSyncDemoOptions TLStoreSchemaOptions): RemoteTLStoreWithStatus export interface UseSyncDemoOptions { roomId: string // 房间 ID命名空间与所有使用 demo 服务器的人共享 users?: TLUserStore // 可选默认基于 localStorage host?: string // internal演示服务器地址 getUserPresence?(store: TLStore, user: TLUser): TLPresenceStateInfo | null }使用示例function MyApp() { const store useSyncDemo({ roomId: my-app-test-room }) return Tldraw store{store} / }这是接入多人白板的最小可用代码useSyncDemo内部自动完成三件事packages/sync/src/useSyncDemo.ts构造连接地址${host}/connect/${encodeURIComponent(roomId)}默认 host 为https://demo.tldraw.xyz可通过TLDRAW_BEMO_URL环境变量覆盖用createDemoAssetStore(host)生成一套开箱即用的资源存取实现上传到 demo 服务器、经TLDRAW_IMAGE_URL指向的图片优化服务做按需缩放在onMount中注册 URL 外部资源处理器通过{host}/bookmarks/unfurl服务抓取链接元数据生成 bookmark 卡片。此外它还会把自定义的shapeUtils/bindingUtils与默认实现合并packages/sync/src/useSyncDemo.ts测试 packages/sync/src/useSyncDemo.test.ts 对这一行为有明确断言。官方演示服务器的限制请务必注意 demo 服务器的数据语义源码 JSDoc 明确声明见 packages/sync/src/useSyncDemo.ts数据大约一天后删除只适合原型与演示数据公开任何知道 roomId 的人都能访问。建议用公司/项目名做前缀避免碰撞追求隐私请直接生成 UUID 作为房间名图片上传被禁用当 host 属于tldraw.com/tldraw.xyz域含子域时upload会弹出提示并抛错防止滥用 demo 基础设施packages/sync/src/useSyncDemo.ts测试见 packages/sync/src/useSyncDemo.test.ts。Demo 资源存储的智能图片优化createDemoAssetStore的resolve实现展示了 tldraw 的资源优化思路packages/sync/src/useSyncDemo.ts视频、data: URL、动图isAnimatedImageType、矢量图isVectorImageType不做变换直接返回原 URL只有托管在 tldraw 自有域名上的图片才走优化服务外部域名原样返回对 ≥ 1.5 MB 的图片按steppedScreenScale、dpr与网络类型非 4g 网络补偿系数 0.5计算目标宽度追加w参数交给图片 worker 缩放。这些逻辑与上传文件名校验非字母数字字符替换为-一起由 packages/sync/src/useSyncDemo.test.ts 中的多组用例覆盖验证。从 API 到实战仓库中的完整接入范例仓库的 templates/sync-cloudflare 模板展示了useSync在真实前后端架构中的完整用法。其客户端房间页 templates/sync-cloudflare/client/pages/Room.tsx 直接从 URL 参数取房间号并建立连接export function Room() { const { roomId } useParams{ roomId: string }() // Create a store connected to multiplayer. const store useSync({ // We need to know the websockets URI... uri: ${window.location.origin}/api/connect/${roomId}, // ...and how to handle static assets like images videos assets: multiplayerAssetStore, }) return ( RoomWrapper roomId{roomId} Tldraw store{store} options{{ deepLinks: true }} onMount{(editor) { editor.registerExternalAssetHandler(url, getBookmarkPreview) }} / /RoomWrapper ) }这个例子浓缩了接入useSync的全部要素URI 拼接/api/connect/${roomId}由同源的 Cloudflare Worker 代理 WebSocket、资源存储multiplayerAssetStore处理图片/视频、store 透传Tldraw store{store} /自动处理 loading 与光标/在线用户等多人 UX、书签解包registerExternalAssetHandler(url, ...)。类似的用法还出现在 templates/simple-server-example/src/client/App.tsx 与 templates/socketio-server-example/src/client/App.tsx 中。环境要求与安装安装yarn add tldraw/sync或npm install tldraw/sync。React 版本peer 依赖要求react/react-dom为^18.2.0 || ^19.2.1见 packages/sync/package.json。Node 版本 22.12.0。许可tldraw/sync是 tldraw SDK 的一部分遵循 packages/sync/LICENSE.md 中的 SDK 许可协议免费使用需保留画布上的 Made with tldraw 水印移除水印需购买商业许可详见 packages/sync/README.md。版本注册包入口会调用registerTldrawLibraryVersion注册库名、版本与模块信息packages/sync/src/index.ts便于 SDK 内部识别与调试。常见问题速查Q1useSync报错 uri or connect must be provided两者必须二选一提供同时提供则报 uri and connect cannot be used togetherpackages/sync/src/useSync.ts。Q2URI 里带了sessionId/storeId参数这两个是保留参数名会被 hook 自动注入用户自定义同名参数会触发显式报错。请改用其他名字携带鉴权信息。Q3为什么连上了却看不到其他用户的光标检查users与getUserPresencepresence 数据由getUserPresence响应式生成并广播默认实现依赖instance_presence记录多标签页/多设备属于不同会话需要各自连接同一房间才会互相可见。Q4生产环境能否用useSyncDemo不能。demo 数据约一天后删除、完全公开且 tldraw 官方域名禁止图片上传。生产场景应自建服务器并使用useSync 自定义TLAssetStore。Q5服务端返回不同错误时如何区分处理store.status error时store.error为TLRemoteSyncError其 reason 可对应NOT_FOUND、FORBIDDEN、NOT_AUTHENTICATED、RATE_LIMITED等场景见 packages/sync-core/src/lib/TLSyncClient.ts 中的TLSyncErrorCloseEventReason据此可定制房间不存在、未登录、被限流等不同 UI。【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考