tldraw 数据模型基石:@tldraw/tlschema 完整 API 解析与自定义 Shape 实战
【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. World's best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw
@tldraw/tlschema是 tldraw SDK 中负责"持久化数据模型"定义的核心包:它用一套类型系统 + 校验器 + 迁移序列,描述了编辑器存储(Store)中全部记录(Record)、图形(Shape)、资源(Asset)、绑定(Binding)的数据结构。阅读本篇,你将掌握该包的完整公开 API(记录类型、shape/asset/binding schema、StyleProp 样式体系、迁移机制、校验器与协作相关类型),并能基于createTLSchema在真实项目中自定义 shape、资产与记录类型。
包定位:tldraw 的"模式"层
在 tldraw 的架构中,@tldraw/tlschema(版本 5.4.0)的职责非常明确:"Type definitions, schema migrations, and other type metadata for the tldraw editor's default persisted data"(见 packages/tlschema/README.md)。它本身不渲染任何 UI,而是回答三个问题:
- 一份 tldraw 文档里可以存在哪些类型的记录(record)?
- 每种记录的属性(props)如何校验、默认值是什么?
- 当数据结构随版本演进时,如何把旧数据无损迁移到新结构?
该包只依赖@tldraw/store、@tldraw/state、@tldraw/utils、@tldraw/validate四个兄弟包(见 packages/tlschema/package.json),并将 React 作为 peer dependency(^18.2.0 || ^19.2.1),要求 Node>=22.12.0。README 将其内部类型归纳为三类:
- Record types(记录类型):直接挂载到
Store上的根记录,定义在src/records目录,如页面、相机、实例状态; - Shape types(图形类型):根记录
TLShape的子类型,通过自定义 props 区分不同图形(矩形、箭头、文本……); - Asset types(资源类型):根记录
TLAsset的子类型,用于承载图片、视频、书签等外部资源元数据。
记录体系:TLRecord 与内置 RecordType
TLRecord是所有持久化记录的联合类型,由TLCustomRecord | TLDefaultRecord组成。其中TLDefaultRecord(api-report 中TLDefaultRecord条目)涵盖 11 类默认记录:TLAsset、TLBinding、TLCamera、TLDocument、TLInstance、TLInstancePageState、TLInstancePresence、TLPage、TLPointer、TLShape、TLUser。每类记录都由一个RecordType工厂导出,并带一个类型守卫:
| 记录类型 | RecordType 导出 | 类型守卫 | 关键字段 |
|---|---|---|---|
| 文档 | DocumentRecordType | isDocument | name、gridSize、meta |
| 页面 | PageRecordType | isPage | name、index、meta |
| 相机 | CameraRecordType | — | x、y、z |
| 指针 | PointerRecordType | — | x、y、lastActivityTimestamp |
| 实例 | createInstanceRecordType(内部) | — | 30+ 个 UI 状态字段(见下) |
| 页面状态 | InstancePageStateRecordType | — | pageId、selectedShapeIds、editingShapeId等 |
| 在线状态 | InstancePresenceRecordType | — | 协作光标、选区、相机等 |
| 用户 | UserRecordType | isUserId | name、color、imageUrl |
三类单例记录有固定的 ID 常量:TLDOCUMENT_ID、TLINSTANCE_ID、TLPOINTER_ID(TLInstanceId/TLPointerId/TLPageId等均为RecordId<...>字符串模板类型)。ID 由createShapeId、createBindingId、createCommentId、createCustomRecordId等工厂生成,并配套idValidator(prefix)与shapeIdValidator、assetIdValidator、bindingIdValidator、pageIdValidator、parentIdValidator、userIdValidator等校验器。
TLInstance:编辑器 UI 状态的"快照容器"
TLInstance(见 packages/tlschema/src/records/TLInstance.ts)是理解 tldraw 状态设计的关键:它把所有非持久化、与视图相关的 UI 状态收敛进一条记录,字段包括brush、cameraState、chatMessage、currentPageId、cursor、devicePixelRatio、duplicateProps、exportBackground、followingUserId、highlightedUserIds、insets、isChangingStyle、isChatting、isCoarsePointer、isDebugMode、isFocused、isFocusMode、isGridMode、isHoveringCanvas、isPenMode、isReadonly、isToolLocked、opacityForNextShape、openMenus、screenBounds、scribbles、stylesForNextShape、zoomBrush等。与其配套的TLInstancePageState则按页保存croppingShapeId、editingShapeId、erasingShapeIds、focusedGroupId、hintingShapeIds、hoveredShapeId、selectedShapeIds。从源码结构可以推断:读写这类高频状态通过Store的信号机制(@tldraw/state的Signal)驱动 React 渲染,这也是package.json中依赖@tldraw/state的原因。
内置图形 Schema:defaultShapeSchemas
defaultShapeSchemas(packages/tlschema/src/createTLSchema.ts)一次性注册了 tldraw 的全部 13 种内置图形,每种都包含props校验器与migrations迁移序列:
export const defaultShapeSchemas = { arrow: { migrations: arrowShapeMigrations, props: arrowShapeProps }, bookmark: { migrations: bookmarkShapeMigrations, props: bookmarkShapeProps }, draw: { migrations: drawShapeMigrations, props: drawShapeProps }, embed: { migrations: embedShapeMigrations, props: embedShapeProps }, frame: { migrations: frameShapeMigrations, props: frameShapeProps }, geo: { migrations: geoShapeMigrations, props: geoShapeProps }, group: { migrations: groupShapeMigrations, props: groupShapeProps }, highlight:{ migrations: highlightShapeMigrations, props: highlightShapeProps }, image: { migrations: imageShapeMigrations, props: imageShapeProps }, line: { migrations: lineShapeMigrations, props: lineShapeProps }, note: { migrations: noteShapeMigrations, props: noteShapeProps }, text: { migrations: textShapeMigrations, props: textShapeProps }, video: { migrations: videoShapeMigrations, props: videoShapeProps }, }所有 shape 都实现自基础接口TLBaseShape<Type, Props>(packages/tlschema/src/shapes/TLBaseShape.ts):
interface TLBaseShape<Type extends string, Props extends object> { readonly id: TLShapeId readonly typeName: 'shape' type: Type x: number y: number rotation: number index: IndexKey parentId: TLParentId // TLPageId | TLShapeId isLocked: boolean opacity: TLOpacityType meta: JsonObject props: Props }各图形的 props 一览(来自 api-report)
- arrow:
start/end(VecModel)、bend、kind(arc | elbow)、arrowheadStart/arrowheadEnd、color/dash/fill/font/size/labelColor/scale、labelPosition、elbowMidPoint、richText; - bookmark:
url、assetId: null | TLAssetId、w、h; - draw:
segments: TLDrawShapeSegment[]、isClosed、isComplete、isPen、scale/scaleX/scaleY、color/dash/fill/size; - embed:
url、w、h; - frame:
name、w、h、color; - geo:
geo(20 种几何形状,见GeoShapeGeoStyle)、w/h/growY、align/verticalAlign、font、flipX/flipY、url、richText及通用样式; - group:无自定义 props(空对象);
- highlight:与 draw 类似(
segments、color、size、scale等); - image:
assetId、url、w/h、crop: TLShapeCrop | null、flipX/flipY、playing、altText; - line:
points: Record<string, TLLineShapePoint>、spline(cubic | line)、color/dash/size/scale; - note:
richText、fontSizeAdjustment、growY、textLastEditedBy、url与通用样式; - text:
richText、autoSize、textAlign、w、font/color/size/scale; - video:
assetId、url、w/h、time、playing、autoplay、altText。
此外ShapeWithCrop、TLAssetShape、ExtractShapeByProps等工具类型可按 props 条件提取图形子集,TLIndexedShapes则把默认图形与用户自定义图形统一索引。
资源 Schema:defaultAssetSchemas 与 TLAssetStore
defaultAssetSchemas注册三种内置资源(createTLSchema.ts):
- bookmark:
title、description、image、favicon、src: null | string; - image:
src、w、h、name、mimeType、fileSize?、isAnimated、pixelRatio?; - video:
src、w、h、name、mimeType、fileSize?、isAnimated。
资产记录本身通过createAssetRecordType生成,props 由各资产 schema 的 props 合并而来。API 报告还给出了配套的存储接口TLAssetStore:upload(asset, file, abortSignal?)必须实现,resolve(asset, ctx)(根据TLAssetContext的dpr、screenScale、steppedScreenScale、networkEffectiveType、shouldResolveToOriginal决定返回原图还是缩略图)与remove(assetIds)可选。这与TLStoreProps.assets(Required<TLAssetStore>)直接对应——即每个 Store 都必须提供一个能上传、能解析 URL 的资源实现。
绑定(Binding):箭头与图形的关系
defaultBindingSchemas目前只有一种绑定arrow(createTLSchema.ts)。TLBaseBinding<Type, Props>的基础结构为:
interface TLBaseBinding<Type extends string, Props extends object> { readonly id: TLBindingId readonly typeName: 'binding' type: Type fromId: TLShapeId toId: TLShapeId props: Props meta: JsonObject }箭头绑定TLArrowBinding的 props 为terminal('start' | 'end')、isExact、isPrecise、normalizedAnchor: VecModel、snap(ElbowArrowSnap:'center' | 'edge' | 'edge-point' | 'none')。arrowBindingVersions目前只有AddSnap一个版本(com.tldraw.binding.arrow/1),而arrowBindingMigrations负责把旧版箭头绑定数据迁移到新结构——这正体现了 tldraw 从"绑定内嵌在 arrow 图形 props 里"(旧版本AddSnap之前的做法)演进到"独立 binding 记录"的迁移路径。
StyleProp:可复用的样式属性体系
StyleProp(packages/tlschema/src/styles/StyleProp.ts)是 tldraw 中一类特殊的 props,其语义是:
- 同一个值可以同时批量设置到多个图形上(如全选改颜色);
- 最近一次使用的值会被自动记住,并应用到之后新建的图形上(如
stylesForNextShape)。
两种定义方式(源码中的官方示例):
// 任意类型样式 const MyLineWidthProp = StyleProp.define('myApp:lineWidth', { defaultValue: 1, type: T.number, }) // 枚举样式 const MySizeProp = StyleProp.defineEnum('myApp:size', { defaultValue: 'medium', values: ['small', 'medium', 'large'], })StyleProp.defineEnum返回EnumStyleProp子类,额外暴露只读的values数组,并支持addValues/removeValues在运行时增减合法取值。内置样式全部是枚举样式:
DefaultColorStyle(从主题色注册而来)、DefaultDashStyle(solid | dashed | dotted | draw | none)、DefaultFillStyle(none | semi | solid | pattern | lined-fill | fill)、DefaultFontStyle(draw | sans | serif | mono)、DefaultSizeStyle(s | m | l | xl)、DefaultHorizontalAlignStyle/DefaultVerticalAlignStyle/DefaultTextAlignStyle;- 类型特有样式:
GeoShapeGeoStyle(20 种几何)、LineShapeSplineStyle(cubic | line)、ArrowShapeKindStyle、ArrowShapeArrowheadStartStyle/ArrowShapeArrowheadEndStyle(9 种箭头)。
createTLSchema内部会用getShapePropKeysByStyle(props)扫描所有 shape 的 props,按 StyleProp 的 id 去重收集进stylesById;若发现两个不同实例用了相同 id,会直接抛出Multiple StyleProp instances with the same id错误(createTLSchema.ts)——这解释了为何官方建议自定义 StyleProp 时用appName:propName前缀保证唯一。另有一组常量集合TL_CURSOR_TYPES、TL_HANDLE_TYPES(vertex | virtual | clone | create)、TL_SCRIBBLE_STATES(starting | stopping | active | paused | complete)、TL_CANVAS_UI_COLOR_TYPES及配套的SetValue<T>工具类型。
迁移机制:数据结构演进的保险丝
凡是改动持久化结构,就必须提供"旧→新"与"新→旧"的迁移函数。API 报告中的核心类型:
interface TLPropsMigration { readonly id: MigrationId readonly dependsOn?: MigrationId[] readonly up: (props: any) => any readonly down?: 'none' | 'retired' | ((props: any) => any) } interface TLPropsMigrations { readonly sequence: Array<StandaloneDependsOn | TLPropsMigration> }迁移 ID 有严格命名约定(由createShapePropsMigrationIds、createAssetPropsMigrationIds、createBindingPropsMigrationIds、createCustomRecordMigrationIds生成):
com.tldraw.shape.${shapeType}/${version} com.tldraw.asset.${assetType}/${version} com.tldraw.binding.${bindingType}/${version} com.tldraw.${recordType}/${version}以arrowShapeVersions为例,它记录了 8 次演进:AddLabelColor/1→AddIsPrecise/2→AddLabelPosition/3→ExtractBindings/4→AddScale/5→AddElbow/6→AddRichText/7→AddRichTextAttrs/8。从中可以读出 tldraw 数据模型的真实演进史:先是给箭头加标签颜色、精确吸附,随后把绑定关系从 shape props 中剥离为独立 binding 记录(ExtractBindings),再引入缩放、折线、富文本。README 给出了编写迁移的完整范式(packages/tlschema/README.md):
const Versions = { RemoveSomeProp: 1, AddOwnerId: 2, // 新增版本号 } as const export const shapeTypeMigrations = defineMigrations({ currentVersion: Versions.Initial, firstVersion: Versions.Initial, migrators: { [Versions.AddOwnerId]: { // 旧 → 新:补默认值 up: (shape) => ({ ...shape, ownerId: null }), // 新 → 旧:删除字段 down: ({ ownerId, ...shape }) => shape, }, }, })关于down迁移,源码注释给出了一条实用建议:已经发布数月以上的down迁移可以标记为'retired'(或'none'),因为长开不刷新的浏览器标签页终究会过期,没必要永久维护降级路径(packages/tlschema/src/recordsWithProps.ts)。Store 层面还有storeMigrations、rootShapeMigrations、rootBindingMigrations负责整个 store / shape 根 / binding 根结构的变化;createTLSchema在组装时会依次合并storeMigrations、各记录迁移、props 迁移与用户自定义迁移(createTLSchema.ts)。相应的强制测试位于 packages/tlschema/src/migrations.test.ts 与 packages/tlschema/src/store-migrations.test.ts,README 明确要求"新增迁移必须补测试,否则测试会报错"。
校验器:运行时数据防线
所有 props 都用@tldraw/validate的T.Validator描述。RecordProps<R>工具类型把记录 props 映射为"每个字段一个校验器";RecordPropsType<Config>反向从校验器配置推导出 TypeScript 类型,并通过MakeUndefinedOptional让可选字段体现为?:(recordsWithProps.ts)。批量创建校验器的工厂包括createShapeValidator、createAssetValidator、createBindingValidator——例如createShapeValidator会展开出id/index/isLocked/meta/opacity/parentId/rotation/typeName/x/y/type/props的完整对象校验。常用基础校验器还有vecModelValidator、boxModelValidator(x/y/w/h)、scribbleValidator、opacityValidator、canvasUiColorTypeValidator、ElbowArrowSnap、ImageShapeCrop、richTextValidator。ID 校验采用idValidator(prefix)工厂,保证形如shape:xxx、page:xxx的格式约束。
Store 相关类型:TLStore 与 TLStoreProps
API 报告定义了 Store 层的一整套类型别名:
type TLStore = Store<TLRecord, TLStoreProps> type TLStoreSchema = StoreSchema<TLRecord, TLStoreProps> type TLSerializedStore = SerializedStore<TLRecord> type TLStoreSnapshot = StoreSnapshot<TLRecord>TLStoreProps是 Store 的依赖注入契约:
interface TLStoreProps { assets: Required<TLAssetStore> users: Required<TLUserStore> defaultName: string onMount(editor: unknown): (() => void) | void collaboration?: { status: null | Signal<'offline' | 'online'> mode?: null | Signal<'readonly' | 'readwrite'> } }其中TLUserStore提供currentUser: Signal<null | TLUser>与可选的resolve(userId)派生信号;TLUser记录含name、color、imageUrl、meta。createCachedUserResolve(resolveFn)可为resolve加缓存,把同步查询包装成响应式Signal。Store 创建时还会接入onValidationFailure与createIntegrityChecker(来自 packages/tlschema/src/TLStore.ts),在 packages/tlschema/src/TLStore.test.ts 与 packages/tlschema/src/recordsWithProps.test.ts 中都有对应测试覆盖。
协作在线状态与评论
Presence:InstancePresenceRecordType生成TLInstancePresence记录,承载cursor、brush、camera、scribbles、selectedShapeIds、followingUserId、chatMessage、screenBounds、color、userName、userId、lastActivityTimestamp等协作字段。getDefaultUserPresence(store, user)给出默认的在线状态快照;createPresenceStateDerivation($user, opts?)则把用户信号转成"给定 store 即返回在线状态信号"的派生函数(opts.getUserPresence允许定制状态构造,测试见 packages/tlschema/src/createPresenceStateDerivation.test.ts)。
评论:commentSchemaRecords注册了comment、comment-thread、comment-reaction三类自定义记录(CustomRecordInfo)。TLComment含threadId、pageId、authorId、body: TLRichText、createdAt、editedAt、isDeleted;TLCommentThread的anchor支持四种定位方式(region/shape/page/point,其中region带pinX/pinY,shape带isPrecise),并有resolved解决状态;TLCommentReaction记录表情回应。配套工厂createComment、createCommentThread、createCommentReaction与 ID 工厂createCommentId等(测试见 packages/tlschema/src/records/TLComment.test.ts)。评论功能由独立的@tldraw/commenting包消费,但数据模型定义在此。
主题、字体与多语言
主题:TLThemes目前只含default: TLTheme,由colors(dark/light两套TLThemeColors)、fonts: TLThemeFonts(draw/sans/serif/mono四套TLThemeFont,含fontFamily与faces?: TLFontFace[])、fontSize、lineHeight、strokeWidth、id组成。TLDefaultColor定义了每种颜色的 14 个语义槽(fill、solid、semi、pattern、noteFill、frameFill、highlightSrgb/highlightP3等),TLThemeDefaultColors列出red/green/blue/yellow/orange/violet/light-red.../black/white/grey等命名色以及selectionFill、selectionStroke、snap、laser、cursor、brushFill等 UI 色,TLThemeUiColorKeys汇总 UI 色键名。registerColorsFromThemes/registerFontsFromThemes可在运行时向DefaultColorStyle注册新主题颜色与字体,DefaultFontFamilies提供四套默认字体族字符串。
语言:LANGUAGES常量数组列出了 40 余种语言(含zh-cn、zh-tw、ja、ko-kr、ar、fa、he、ur等,详见 packages/tlschema/src/translations/translations.ts),getDefaultTranslationLocale()依据运行时环境返回默认 locale,TLLanguage即其元素类型;翻译文件本体位于仓库根目录 assets/translations。
b64Vecs:Base64 向量编解码
b64Vecs是把手写轨迹(VecModel[])编码为紧凑 Base64 字符串的工具类,用于高效存储画笔点集:encodePoints(points, dim?)/decodePoints(base64, dim?)支持 2D/3D(DIM_2D = 2、DIM_3D = 3),并提供了decodeFirstPoint、decodeLastPoint、isSinglePoint等便捷方法与_legacyDecodePoints/_legacyEncodePoints旧版兼容实现(源码与测试见 packages/tlschema/src/misc/b64Vecs.ts、packages/tlschema/src/misc/b64Vecs.test.ts)。compressLegacySegments负责把旧格式的自由/直线段压缩成TLDrawShapeSegment。
实战:用 createTLSchema 构建自定义模型
createTLSchema(options)是组装 schema 的唯一入口,其完整签名(createTLSchema.ts):
createTLSchema({ shapes?: Record<string, SchemaPropsInfo> // 默认 defaultShapeSchemas bindings?: Record<string, SchemaPropsInfo> // 默认 defaultBindingSchemas assets?: Record<string, SchemaPropsInfo> // 默认 defaultAssetSchemas user?: UserSchemaInfo // 自定义用户 meta 与迁移 records?: Record<string, CustomRecordInfo> // 自定义记录类型 migrations?: readonly MigrationSequence[] // 额外迁移序列 }): TLSchemaSchemaPropsInfo的三个字段props/meta/migrations分别对应属性校验、元数据校验与迁移。以下是源码文档中给出的三个经典用法:
// 1) 只保留部分内置图形,构建最小 schema const minimalSchema = createTLSchema({ shapes: { geo: defaultShapeSchemas.geo, text: defaultShapeSchemas.text }, }) // 2) 在默认图形之外追加自定义图形 const customSchema = createTLSchema({ shapes: { ...defaultShapeSchemas, myCustomShape: { props: myCustomShapeProps, migrations: myCustomShapeMigrations }, }, }) // 3) 为 user 记录扩展自定义 meta(字段视为可选,出现即校验) const schemaWithCustomUser = createTLSchema({ user: { meta: { isAdmin: T.boolean, department: T.string } }, }) // 4) 注册全新自定义记录类型(scope 有 'document' | 'session' 等取值) const schemaWithCustomRecords = createTLSchema({ records: { comment: { scope: 'document', validator: T.object({ id: T.string, typeName: T.literal('comment'), text: T.string, shapeId: T.string, }), }, }, })注意:自定义记录名不能与内置类型名冲突(asset/binding/camera/document/instance/instance_page_state/page/instance_presence/pointer/shape/store/user,见 createTLSchema.ts),否则会抛出异常。CustomRecordInfo还支持createDefaultProperties提供默认字段。自定义 record 的迁移 ID 用createCustomRecordMigrationIds/createCustomRecordMigrationSequence生成,实现见 packages/tlschema/src/records/TLCustomRecord.ts。
组合好的 schema 直接交给@tldraw/store使用:
const schema = createTLSchema({ shapes: defaultShapeSchemas }) const store = new Store({ schema })整体流程测试可参考 packages/tlschema/src/createTLSchema.test.ts 与 packages/tlschema/src/tests/migrationTestUtils.ts。
结语
@tldraw/tlschema用"记录 + 校验器 + 迁移"三件套,为整个 tldraw 编辑器提供了严谨的持久化数据契约:createTLSchema统一组装 13 种默认图形、3 种资源、箭头绑定、11 类根记录与协作/评论模型,StyleProp支撑批量样式编辑,版本化迁移保证数据在多次结构演进后依然无损。无论你是要自定义 shape 接入自己的领域模型,还是要深度集成协作与持久化,这份 API 清单都是你在 packages/tlschema 中按图索骥的最佳起点。
【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. World's best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考