tldraw 数据模型基石:@tldraw/tlschema 完整 API 解析与自定义 Shape 实战
2026/9/11 8:31:22 网站建设 项目流程

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 类默认记录:TLAssetTLBindingTLCameraTLDocumentTLInstanceTLInstancePageStateTLInstancePresenceTLPageTLPointerTLShapeTLUser。每类记录都由一个RecordType工厂导出,并带一个类型守卫:

记录类型RecordType 导出类型守卫关键字段
文档DocumentRecordTypeisDocumentnamegridSizemeta
页面PageRecordTypeisPagenameindexmeta
相机CameraRecordTypexyz
指针PointerRecordTypexylastActivityTimestamp
实例createInstanceRecordType(内部)30+ 个 UI 状态字段(见下)
页面状态InstancePageStateRecordTypepageIdselectedShapeIdseditingShapeId
在线状态InstancePresenceRecordType协作光标、选区、相机等
用户UserRecordTypeisUserIdnamecolorimageUrl

三类单例记录有固定的 ID 常量:TLDOCUMENT_IDTLINSTANCE_IDTLPOINTER_IDTLInstanceId/TLPointerId/TLPageId等均为RecordId<...>字符串模板类型)。ID 由createShapeIdcreateBindingIdcreateCommentIdcreateCustomRecordId等工厂生成,并配套idValidator(prefix)shapeIdValidatorassetIdValidatorbindingIdValidatorpageIdValidatorparentIdValidatoruserIdValidator等校验器。

TLInstance:编辑器 UI 状态的"快照容器"

TLInstance(见 packages/tlschema/src/records/TLInstance.ts)是理解 tldraw 状态设计的关键:它把所有非持久化、与视图相关的 UI 状态收敛进一条记录,字段包括brushcameraStatechatMessagecurrentPageIdcursordevicePixelRatioduplicatePropsexportBackgroundfollowingUserIdhighlightedUserIdsinsetsisChangingStyleisChattingisCoarsePointerisDebugModeisFocusedisFocusModeisGridModeisHoveringCanvasisPenModeisReadonlyisToolLockedopacityForNextShapeopenMenusscreenBoundsscribblesstylesForNextShapezoomBrush等。与其配套的TLInstancePageState则按页保存croppingShapeIdeditingShapeIderasingShapeIdsfocusedGroupIdhintingShapeIdshoveredShapeIdselectedShapeIds。从源码结构可以推断:读写这类高频状态通过Store的信号机制(@tldraw/stateSignal)驱动 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)

  • arrowstart/end(VecModel)、bendkindarc | elbow)、arrowheadStart/arrowheadEndcolor/dash/fill/font/size/labelColor/scalelabelPositionelbowMidPointrichText
  • bookmarkurlassetId: null | TLAssetIdwh
  • drawsegments: TLDrawShapeSegment[]isClosedisCompleteisPenscale/scaleX/scaleYcolor/dash/fill/size
  • embedurlwh
  • framenamewhcolor
  • geogeo(20 种几何形状,见GeoShapeGeoStyle)、w/h/growYalign/verticalAlignfontflipX/flipYurlrichText及通用样式;
  • group:无自定义 props(空对象);
  • highlight:与 draw 类似(segmentscolorsizescale等);
  • imageassetIdurlw/hcrop: TLShapeCrop | nullflipX/flipYplayingaltText
  • linepoints: Record<string, TLLineShapePoint>splinecubic | line)、color/dash/size/scale
  • noterichTextfontSizeAdjustmentgrowYtextLastEditedByurl与通用样式;
  • textrichTextautoSizetextAlignwfont/color/size/scale
  • videoassetIdurlw/htimeplayingautoplayaltText

此外ShapeWithCropTLAssetShapeExtractShapeByProps等工具类型可按 props 条件提取图形子集,TLIndexedShapes则把默认图形与用户自定义图形统一索引。

资源 Schema:defaultAssetSchemas 与 TLAssetStore

defaultAssetSchemas注册三种内置资源(createTLSchema.ts):

  • bookmarktitledescriptionimagefaviconsrc: null | string
  • imagesrcwhnamemimeTypefileSize?isAnimatedpixelRatio?
  • videosrcwhnamemimeTypefileSize?isAnimated

资产记录本身通过createAssetRecordType生成,props 由各资产 schema 的 props 合并而来。API 报告还给出了配套的存储接口TLAssetStoreupload(asset, file, abortSignal?)必须实现,resolve(asset, ctx)(根据TLAssetContextdprscreenScalesteppedScreenScalenetworkEffectiveTypeshouldResolveToOriginal决定返回原图还是缩略图)与remove(assetIds)可选。这与TLStoreProps.assetsRequired<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')、isExactisPrecisenormalizedAnchor: VecModelsnapElbowArrowSnap'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,其语义是:

  1. 同一个值可以同时批量设置到多个图形上(如全选改颜色);
  2. 最近一次使用的值会被自动记住,并应用到之后新建的图形上(如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(从主题色注册而来)、DefaultDashStylesolid | dashed | dotted | draw | none)、DefaultFillStylenone | semi | solid | pattern | lined-fill | fill)、DefaultFontStyledraw | sans | serif | mono)、DefaultSizeStyles | m | l | xl)、DefaultHorizontalAlignStyle/DefaultVerticalAlignStyle/DefaultTextAlignStyle
  • 类型特有样式:GeoShapeGeoStyle(20 种几何)、LineShapeSplineStylecubic | line)、ArrowShapeKindStyleArrowShapeArrowheadStartStyle/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_TYPESTL_HANDLE_TYPESvertex | virtual | clone | create)、TL_SCRIBBLE_STATESstarting | 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 有严格命名约定(由createShapePropsMigrationIdscreateAssetPropsMigrationIdscreateBindingPropsMigrationIdscreateCustomRecordMigrationIds生成):

com.tldraw.shape.${shapeType}/${version} com.tldraw.asset.${assetType}/${version} com.tldraw.binding.${bindingType}/${version} com.tldraw.${recordType}/${version}

arrowShapeVersions为例,它记录了 8 次演进:AddLabelColor/1AddIsPrecise/2AddLabelPosition/3ExtractBindings/4AddScale/5AddElbow/6AddRichText/7AddRichTextAttrs/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 层面还有storeMigrationsrootShapeMigrationsrootBindingMigrations负责整个 store / shape 根 / binding 根结构的变化;createTLSchema在组装时会依次合并storeMigrations、各记录迁移、props 迁移与用户自定义迁移(createTLSchema.ts)。相应的强制测试位于 packages/tlschema/src/migrations.test.ts 与 packages/tlschema/src/store-migrations.test.ts,README 明确要求"新增迁移必须补测试,否则测试会报错"。

校验器:运行时数据防线

所有 props 都用@tldraw/validateT.Validator描述。RecordProps<R>工具类型把记录 props 映射为"每个字段一个校验器";RecordPropsType<Config>反向从校验器配置推导出 TypeScript 类型,并通过MakeUndefinedOptional让可选字段体现为?:(recordsWithProps.ts)。批量创建校验器的工厂包括createShapeValidatorcreateAssetValidatorcreateBindingValidator——例如createShapeValidator会展开出id/index/isLocked/meta/opacity/parentId/rotation/typeName/x/y/type/props的完整对象校验。常用基础校验器还有vecModelValidatorboxModelValidatorx/y/w/h)、scribbleValidatoropacityValidatorcanvasUiColorTypeValidatorElbowArrowSnapImageShapeCroprichTextValidator。ID 校验采用idValidator(prefix)工厂,保证形如shape:xxxpage: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记录含namecolorimageUrlmetacreateCachedUserResolve(resolveFn)可为resolve加缓存,把同步查询包装成响应式Signal。Store 创建时还会接入onValidationFailurecreateIntegrityChecker(来自 packages/tlschema/src/TLStore.ts),在 packages/tlschema/src/TLStore.test.ts 与 packages/tlschema/src/recordsWithProps.test.ts 中都有对应测试覆盖。

协作在线状态与评论

PresenceInstancePresenceRecordType生成TLInstancePresence记录,承载cursorbrushcamerascribblesselectedShapeIdsfollowingUserIdchatMessagescreenBoundscoloruserNameuserIdlastActivityTimestamp等协作字段。getDefaultUserPresence(store, user)给出默认的在线状态快照;createPresenceStateDerivation($user, opts?)则把用户信号转成"给定 store 即返回在线状态信号"的派生函数(opts.getUserPresence允许定制状态构造,测试见 packages/tlschema/src/createPresenceStateDerivation.test.ts)。

评论commentSchemaRecords注册了commentcomment-threadcomment-reaction三类自定义记录(CustomRecordInfo)。TLCommentthreadIdpageIdauthorIdbody: TLRichTextcreatedAteditedAtisDeletedTLCommentThreadanchor支持四种定位方式(region/shape/page/point,其中regionpinX/pinYshapeisPrecise),并有resolved解决状态;TLCommentReaction记录表情回应。配套工厂createCommentcreateCommentThreadcreateCommentReaction与 ID 工厂createCommentId等(测试见 packages/tlschema/src/records/TLComment.test.ts)。评论功能由独立的@tldraw/commenting包消费,但数据模型定义在此。

主题、字体与多语言

主题TLThemes目前只含default: TLTheme,由colorsdark/light两套TLThemeColors)、fonts: TLThemeFontsdraw/sans/serif/mono四套TLThemeFont,含fontFamilyfaces?: TLFontFace[])、fontSizelineHeightstrokeWidthid组成。TLDefaultColor定义了每种颜色的 14 个语义槽(fillsolidsemipatternnoteFillframeFillhighlightSrgb/highlightP3等),TLThemeDefaultColors列出red/green/blue/yellow/orange/violet/light-red.../black/white/grey等命名色以及selectionFillselectionStrokesnaplasercursorbrushFill等 UI 色,TLThemeUiColorKeys汇总 UI 色键名。registerColorsFromThemes/registerFontsFromThemes可在运行时向DefaultColorStyle注册新主题颜色与字体,DefaultFontFamilies提供四套默认字体族字符串。

语言LANGUAGES常量数组列出了 40 余种语言(含zh-cnzh-twjako-krarfaheur等,详见 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 = 2DIM_3D = 3),并提供了decodeFirstPointdecodeLastPointisSinglePoint等便捷方法与_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[] // 额外迁移序列 }): TLSchema

SchemaPropsInfo的三个字段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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询