前端UI组件【免费下载链接】formily Cross Device High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3项目地址https://gitcode.com/gh_mirrors/fo/formily点击查看免费下载FormPath 是 Formily 表单内核formily/core中最核心的路径引擎它同时承担路径匹配与数据操作两大职责一方面以*(aa,bb,cc)这类匹配语法定位表单字段另一方面以a.b.c这类数据路径读写表单值。本文将以 FormPath.md 为骨架结合formily/path包的源码实现index.ts、parser.ts、matcher.ts与核心测试用例match.spec.ts完整讲解 FormPath 的语法体系、实例方法、静态方法以及它在 Formily 内核中的真实应用场景。读完本文你将能够熟练编写字段匹配规则如*(!aa,bb)、aa.*[1:2].bb、用相对路径操作数组相邻元素并理解form.query()、字段联动等高级 API 背后的路径原理。FormPath 要解决的两类问题FormPath 的核心目标是解决两类问题路径匹配问题Path matching要求给定的路径必须是合法的匹配语法例如*(aa,bb,cc)。这类语法用于判断某个字段地址是否命中一组规则是 Formily 字段联动、响应式订阅、校验/重置范围的核心基础。数据操作问题Data manipulation要求给定的路径必须是合法的数据操作路径即形如a.b.c且不能携带*等通配符。这类路径用于对表单values、initialValues等数据源做读写。在formily/path的实现中这两类问题被统一收敛到Path类index.ts并通过isMatchPattern、segments、tree等属性区分两种模式。formily/core则通过 shared/externals.ts 将Path以FormPath之名重新导出因此在业务代码中你看到的是import { FormPath } from formily/core。构造函数与属性构造函数class FormPath { constructor(pattern: FormPathPattern, base?: FormPathPattern) }pattern路径模式可以是字符串、数字、字符串/数字数组或正则表达式见下文FormPathPattern。base可选基准路径配合相对路径语法使用用于计算相对基准的绝对路径。从源码看构造逻辑实际由模块级parse函数完成index.ts简单路径不含*、~、[]、,、:、空格且不以.开头会走快速拆分为segments的捷径复杂路径则交给Parser生成 AST 树。此外Path.parse内部维护了一个pathCacheindex.ts相同模式与基准会命中缓存避免重复解析。实例属性属性说明类型默认值length非匹配路径的段数可直接读取路径长度Number0entire路径完整字符串与输入数据一致Stringsegments非匹配路径的完整分段结果ArrayString \| Number[]isMatchPattern是否为匹配路径BooleanisWildMatchPattern是否为全通配路径如a.b.*BooleanhaveExcludePattern是否包含反向匹配如*(!a.b.c)Booleantree解析得到的 AST 树Node从 Path 类定义 可以看到这些属性在构造函数中按parse结果逐一赋值tree是Parser产生的 AST节点类型在 types.ts 中定义包括Identifier、WildcardOperator、GroupExpression、RangeExpression、DestructorExpression、DotOperator等它是后续Matcher执行匹配的直接输入。FormPathPattern 类型签名type FormPathPattern string | number | Arraystring | number | RegExp在formily/core的类型定义types.ts中FormPathPattern还额外支持FormPath实例与匹配器函数formily/path侧的Pattern类型types.ts同样纳入了Path与MatcherFunction。也就是说任何接受路径的地方都可以传入已解析好的FormPath对象从而复用缓存。数据路径语法Data path syntax数据路径用于读写数据特征是不包含匹配通配符由以下四种子语法组成。点路径Point path最常用的a.b.c格式用点号分隔各个路径节点主要用于读写数据import { FormPath } from formily/core const target {} FormPath.setIn(target, a.b.c, value) console.log(FormPath.getIn(target, a.b.c)) //value console.log(target) //{a:{b:{c:value}}}底层实现对应 index.ts 中的getIn/setInsetIn会沿着分段逐个创建中间对象当下一段是数字时创建数组否则创建对象并在末段写入值。下标路径Subscript path数组路径支持下标下标既可用点语法也可用方括号import { FormPath } from formily/core const target { array: [], } FormPath.setIn(target, array.0.aa, 000) console.log(FormPath.getIn(target, array.0.aa)) //000 console.log(target) //{array:[{aa:000}]} FormPath.setIn(target, array[1].aa, 111) console.log(FormPath.getIn(target, array.1.aa)) //111 console.log(target) //{array:[{aa:000},{aa:111}]}注意setIn的一个实现细节index.ts当目标是数组而当前分段不是数字下标时setIn会直接返回避免把非数字键写入数组。解构表达式Deconstruction expression解构表达式类似 ES6 解构语法但不支持...展开非常适合处理前后端数据结构不一致的场景。它的特性解构表达式会被视为点路径的一个普通节点因此匹配语法中只需把它当作普通节点匹配即可在setIn中使用解构路径数据会被解构写入在getIn中使用解构路径数据会被重组读出。import { FormPath } from formily/core const target {} FormPath.setIn(target, parent.[aa,bb], [11, 22]) console.log(target) //{parent:{aa:11,bb:22}} console.log(FormPath.getIn(target, parent.[aa,bb])) //[11,22] console.log(FormPath.parse(parent.[aa,bb]).toString()) //parent.[aa,bb]解构表达式的解析与写入由 destructor.ts 的getDestructor、setInByDestructor、getInByDestructor等函数实现它们被 index.ts 的getIn/setIn在遇到解构节点时调用。此外解构表达式还支持对象模式如parent.{aa,bb}以及嵌套解构。相对路径Relative path相对路径语法主要体现在数据型路径开头的点语法对计算数组相邻元素非常有用。特性如下一个点代表当前路径n 个点代表向前走 n-1 步方括号内可以使用下标计算表达式[]表示当前下标 1[-]表示当前下标 -1[n]表示当前下标 n[-n]表示当前下标 -n路径匹配时不能混用分组匹配与范围匹配例如*(..[1].aa,..[2].bb)是非法用法。import { FormPath } from formily/core console.log(FormPath.parse(.dd, aa.bb.cc).toString()) //aa.bb.dd console.log(FormPath.parse(..[].dd, aa.1.cc).toString()) //aa.1.dd console.log(FormPath.parse(..[].dd, aa.1.cc).toString()) //aa.2.dd console.log(FormPath.parse(..[10].dd, aa.1.cc).toString()) //aa.11.dd实现层面相对路径的处理位于 parser.ts 的parseDotOperator当路径以点开头且提供了base时解析器会以 base 的 segments 为初始值逐点弹出末尾节点并记录relative下标最终合成绝对路径calculate函数parser.ts则负责[n]这类下标表达式的数值运算。匹配路径语法Match path syntax匹配路径用于判断字段地址是否命中规则以下八种语法构成了 Formily 字段查询、联动、校验范围控制的基础能力。全匹配Full match匹配所有路径只需一个*import { FormPath } from formily/core console.log(FormPath.parse(*).match(aa)) //true console.log(FormPath.parse(*).match(aa.bb)) //true console.log(FormPath.parse(*).match(cc)) //true局部匹配Partial match匹配某个节点位置上的所有路径同样使用*import { FormPath } from formily/core console.log(FormPath.parse(aa.*.cc).match(aa.bb.cc)) //true console.log(FormPath.parse(aa.*.cc).match(aa.kk.cc)) //true console.log(FormPath.parse(aa.*.cc).match(aa.dd.cc)) //true分组匹配Group Match同时匹配多条路径且支持嵌套语法为*(pattern1,pattern2,pattern3...)import { FormPath } from formily/core console.log( FormPath.parse(aa.*(bb,kk,dd,ee.*(oo,gg).gg).cc).match(aa.bb.cc) ) //true console.log( FormPath.parse(aa.*(bb,kk,dd,ee.*(oo,gg).gg).cc).match(aa.kk.cc) ) //true console.log( FormPath.parse(aa.*(bb,kk,dd,ee.*(oo,gg).gg).cc).match(aa.dd.cc) ) //true console.log( FormPath.parse(aa.*(bb,kk,dd,ee.*(oo,gg).gg).cc).match(aa.ee.oo.gg.cc) ) //true console.log( FormPath.parse(aa.*(bb,kk,dd,ee.*(oo,gg).gg).cc).match(aa.ee.gg.gg.cc) ) //true分组表达式的解析位于 parser.tsGroupExpression节点会递归包含子表达式这正是嵌套分组的来源。反向匹配Reverse match反向匹配主要用于排除指定路径语法为*(!pattern1,pattern2,pattern3)import { FormPath } from formily/core console.log(FormPath.parse(*(!aa,bb,cc)).match(aa)) //false console.log(FormPath.parse(*(!aa,bb,cc)).match(kk)) //true反向标记!会在解析时置haveExcludePattern trueparser.ts并影响matchAliasGroup的计分逻辑见下文。扩展匹配Extended matching用于匹配路径的起始子串语法为pattern~import { FormPath } from formily/core console.log(FormPath.parse(test~).match(test_111)) //true console.log(FormPath.parse(test~).match(test_222)) //true范围匹配Range match用于匹配数组下标区间语法为*[x:y]x 与 y 可以为空表示开区间import { FormPath } from formily/core console.log(FormPath.parse(aa.*[1:2].bb).match(aa.1.bb)) //true console.log(FormPath.parse(aa.*[1:2].bb).match(aa.2.bb)) //true console.log(FormPath.parse(aa.*[1:2].bb).match(aa.3.bb)) //false console.log(FormPath.parse(aa.*[1:].bb).match(aa.3.bb)) //true console.log(FormPath.parse(aa.*[:100].bb).match(aa.3.bb)) //true console.log(FormPath.parse(aa.*[:100].bb).match(aa.1000.bb)) //falseRangeExpression节点的解析在 parser.ts遇到冒号即进入结束段解析且当只有起始值无冒号时end会默认等于start精确匹配单一下标。转义匹配Escape match路径节点若包含关键字可以用\或[[]]语法转义import { FormPath } from formily/core console.log( FormPath.parse(aa.\\,\\*\\{\\}\\.\\(\\).bb).match( aa.\\,\\*\\{\\}\\.\\(\\).bb ) ) //true console.log(FormPath.parse(aa.[[,*{}.()]].bb).match(aa.[[,*{}.()]].bb)) // true[[]]转义对应解析器中的IgnoreExpression节点parser.ts方括号包裹的内容会被当作字面量节点整体入栈从而绕开关键字解释。解构匹配Destructuring matching带解构表达式的路径在匹配时可以直接匹配无需转义import { FormPath } from formily/core console.log(FormPath.parse(target.[aa,bb]).match(target.[aa,bb])) //true实例方法MethodtoString输出路径的完整字符串同时支持匹配路径与数据操作路径interface toString { (): string }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).toString()) //aa.bb.cc console.log(FormPath.parse(aa.bb.*).toString()) //aa.bb.* console.log(FormPath.parse(*(aa,bb,cc)).toString()) //*(aa,bb,cc)toArray输出路径的数组分段仅支持数据操作路径匹配路径返回[]interface toArray { (): Arraystring | number }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).toArray().join(--)) //aa--bb--cc console.log(FormPath.parse(aa.bb.*).toArray()) //[] console.log(FormPath.parse(*(aa,bb,cc)).toArray()) //[]concat连接数据操作路径interface concat { (...args: FormPathPattern[]): FormPath }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).concat(dd.ee.mm).toString()) //aa.bb.cc.dd.ee.mm console.log( FormPath.parse(aa.bb.cc).concat([dd, ee, mm], kk.oo).toString() ) //aa.bb.cc.dd.ee.mm.kk.oo从实现看index.tsconcat会先把参数统一转成片段再拼接到当前segments之后并重新生成entire若当前路径是匹配路径则直接抛错。slice截取数据操作路径的一段interface slice { (start?: number, end?: number): FormPath }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).slice(1).toString()) //bb.ccpush向数据操作路径尾部追加路径片段interface push { (...args: FormPathPattern[]): FormPath }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).push(dd.kk).toString()) //aa.bb.cc.dd.kk实现上push直接委托给concatindex.ts。pop弹出数据操作路径的最后一个键interface pop { (): FormPath }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).pop().toString()) //aa.bbsplice拼接数据操作路径对应 Array 的 splice 语义interface splice { ( startIndex: number, deleteCount?: number, ...inertItems: Arraystring | number ): FormPath }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).splice(2, 1).toString()) //aa.bb console.log(FormPath.parse(aa.bb.cc).splice(2, 0, ee.gg).toString()) //aa.bb.ee.gg.cc console.log(FormPath.parse(aa.bb.cc).splice(2, 0, [kk, mm]).toString()) //aa.bb.kk.mm.ccforEach遍历数据操作路径interface forEach { (eacher: (key: string | number, index: number) void): void }import { FormPath } from formily/core const keys [] FormPath.parse(aa.bb.cc).forEach((key) { keys.push(key) }) console.log(keys) //[aa,bb,cc]map对数据操作路径做循环映射interface map { (mapper: (key: string | number, index: number) void): void }import { FormPath } from formily/core console.log( FormPath.parse(aa.bb.cc).map((key) { return key ~ }) //[aa~,bb~,cc~] )reduce对路径中的每个元素依次执行 reducer 函数升序并把结果聚合成单一返回值interface reduceT { (reducer: (value: T, key: string | number, index: number) void): void accumulator: T }import { FormPath } from formily/core console.log( FormPath.parse(aa.bb.cc).reduce((count) { return count 1 }, 0) ) //3parent获取当前数据操作路径的父路径interface parent { (): FormPath }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).parent().toString()) //aa.bb实现即slice(0, length - 1)index.ts。includes判断给定数据操作路径是否为当前路径的子路径interface includes { (pattern: FormPathPattern): boolean }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).includes(aa.bb)) //true console.log(FormPath.parse(aa.bb.cc).includes(cc.bb)) //false需要注意实现中index.tsincludes对匹配路径有限制——当前路径是匹配路径时子路径必须是普通路径反之亦然否则会抛错。transform基于正则提取数据并做路径组装interface transformT { (regExp: RegExp, callback: (...matches: string[]) T): T }import { FormPath } from formily/core console.log( FormPath.parse(aa.1.cc).transform( /\d/, (index) aa.${parseInt(index) 1}.cc ) ) //aa.2.ccmatch用匹配路径语法匹配当前路径interface match { (pattern: FormPathPattern): boolean }import { FormPath } from formily/core console.log(FormPath.parse(aa.1.cc).match(aa.*.cc)) //truematch是 FormPath 最核心的方法其实现index.ts有三个值得注意的点双向语义既可以用匹配路径去匹配普通路径调用方是普通路径、参数是匹配模式时也可以让普通路径去匹配普通路径此时走Matcher.matchSegments的等值比较缓存每次匹配结果按模式字符串写入matchCache重复匹配同一模式直接命中缓存并顺带恢复上次的matchScore匹配评分Matcher在匹配过程中会累计score分数越高代表匹配越精确这个分数是matchAliasGroup处理反向匹配时的依据。matchAliasGroup别名组匹配主要用来同时匹配 formily 中的地址address与路径pathinterface matchAliasGroup { (pattern: FormPathPattern, alias: FormPathPattern): boolean }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).matchAliasGroup(aa.bb.cc, aa.cc)) //true实现逻辑index.ts分别对 name 与 alias 执行match并记录各自的分数若当前模式含反向排除haveExcludePattern则取分数更高者的匹配结果防止反向规则误伤别名字段否则返回两者的逻辑或。测试用例 match.spec.ts 覆盖了多种组合场景。existIn根据当前路径判断指定数据是否存在interface existIn { (pattern: FormPathPattern): boolean }import { FormPath } from formily/core console.log(FormPath.parse(aa.bb.cc).existIn({})) //false注意这里使用的是hasOwnProperty判定index.ts因此值为undefined但键存在仍返回true。getIn / setIn / deleteIn / ensureIn基于当前路径读写、删除、兜底创建数据interface getIn { (pattern: FormPathPattern): any } interface setIn { (pattern: FormPathPattern, value: any): void } interface deleteIn { (pattern: FormPathPattern): boolean } interface ensureIn { (pattern: FormPathPattern, value: any): any }import { FormPath } from formily/core const target {} FormPath.parse(aa.bb.cc).setIn(target, value) console.log(FormPath.parse(aa.bb.cc).getIn(target)) //value const target2 { aa: { bb: { cc: value, }, }, } FormPath.parse(aa.bb.cc).deleteIn(target2) console.log(FormPath.parse(aa.bb.cc).getIn(target2)) //undefined const target3 {} FormPath.parse(aa.bb.cc).ensureIn(target3, value) console.log(FormPath.parse(aa.bb.cc).getIn(target3)) //valueensureIn的实现index.ts是先读不存在则写默认值再读这在 Formily 内部被广泛用于按路径惰性初始化数据结构。静态方法Static method静态方法提供与实例方法对等的功能但签名上把目标数据/路径作为前置参数使用更直接。match根据匹配路径生成一个路径匹配函数interface match { (pattern: FormPathPattern): (pattern: FormPathPattern) boolean }import { FormPath } from formily/core console.log(FormPath.match(aa.*.cc)(aa.bb.cc)) // true实现index.ts返回一个带isMatcher标记与path引用的函数该函数可继续参与FormPathPattern的解析parse遇到匹配器函数时会展开为原始路径。transform正则转换路径interface transformT { ( pattern: FormPathPattern, regexp: RegExp, callback: (...matches: string[]) T ): T }import { FormPath } from formily/core console.log( FormPath.transform( aa.0.bb, /\d/, (index) aa.${parseInt(index) 1}.bb ) ) // aa.1.bbparse解析路径为FormPath实例interface parse { (pattern: FormPathPattern): FormPath }import { FormPath } from formily/core console.log(FormPath.parse(aa.0.bb))parse是几乎所有 FormPath 操作的入口。从实现index.ts看它带有两级缓存传入的pattern是已解析的Path时按entire缓存否则按${pattern}:${base}组合缓存。getIn / setIn / deleteIn / existIn / ensureIn基于路径对目标数据执行读写、删除、存在性判断与兜底创建interface getIn { (target: any, pattern: FormPathPattern): any } interface setIn { (target: any, pattern: FormPathPattern, value: any): void } interface deleteIn { (target: any, pattern: FormPathPattern): void } interface existIn { (target: any, pattern: FormPathPattern): void } interface ensureIn { (target: any, pattern: FormPathPattern, defaultValue: any): any }import { FormPath } from formily/core console.log(FormPath.getIn({ aa: [{ bb: value }] }, aa.0.bb)) //value const target {} FormPath.setIn(target, aa.bb.cc, value) console.log(target) //{aa:{bb:{cc:value}}} const target2 { aa: { bb: { cc: value, }, }, } FormPath.deleteIn(target2, aa.bb.cc) console.log(target2) //{aa:{bb:{}}} const target3 { aa: { bb: { cc: value, }, }, } console.log(FormPath.existIn(target3, aa.bb.cc)) //true console.log(FormPath.existIn(target3, aa.bb.kk)) //false const target4 {} FormPath.ensureIn(target4, aa.bb.cc, value) console.log(FormPath.getIn(target4, aa.bb.cc)) //value所有静态方法都只是先parse再调用实例方法的语法糖index.ts。FormPath 在 Formily 内核中的实际应用理解语法与 API 之后再看 FormPath 在formily/core中的真实角色能帮助你建立路径即一切的整体认知。字段匹配与联动BaseField.match字段模型BaseField提供了match方法BaseField.ts它直接使用matchAliasGroup同时匹配字段的address绝对地址与path相对路径match (pattern: FormPathPattern) { return FormPath.parse(pattern).matchAliasGroup(this.address, this.path) }这是 Formily 字段联动如linkages、x-reactions的依赖匹配的底层判定依据——为什么一个模式既能命中完整地址aa.bb又能命中相对路径bb原因就在matchAliasGroup的双路匹配。表单值操作Form 模型的 setValuesIn 系列Form模型将 FormPath 直接用于操作表单值Form.tssetValuesIn (pattern: FormPathPattern, value: any) { FormPath.setIn(this.values, pattern, value) } deleteValuesIn (pattern: FormPathPattern) { FormPath.deleteIn(this.values, pattern) } existValuesIn (pattern: FormPathPattern) { return FormPath.existIn(this.values, pattern) } getValuesIn (pattern: FormPathPattern) { return FormPath.getIn(this.values, pattern) }initialValues也有对应的setInitialValuesIn、deleteInitialValuesIn、existInitialValuesIn、getInitialValuesIn系列Form.ts。此外form.setValues、form.reset、form.validate、form.clearErrors等方法的pattern参数默认值均为*Form.ts意味着默认作用于全表单字段——这正是全匹配语法在内核中的直接体现。字段地址生成createField 中的 basePath name创建字段时Form.createField会以basePath为基准拼接字段名生成绝对地址Form.tsconst address FormPath.parse(props.basePath).concat(props.name)concat在这里承担了父子地址拼接的职责字段地址本身就是一个FormPath实例。校验反馈过滤onFieldEffects在 onFieldEffects.ts 中matchAliasGroup被用于过滤校验反馈的命中范围确保反馈只作用于地址或路径命中的字段。与之配套internals.ts 中的过滤逻辑也用FormPath.parse(...).match(...)对path与address分别做匹配。数据兜底创建FormPath.ensureIninternals.ts 使用FormPath.ensureIn(form, requests.updates, [])按路径惰性创建表单内部的请求队列结构避免在字段更新时重复初始化。源码脉络与测试验证如果你想深入 FormPath 的实现细节建议按以下顺序阅读index.tsPath类的完整实现包括parse分支、实例方法、静态方法与缓存机制tokenizer.ts 与 parser.ts词法分析Token 切分与语法分析AST 构建Parser直接继承Tokenizermatcher.tsAST 树的匹配执行器负责计算scoredestructor.ts解构表达式的规则解析与读写实现testsbasic.spec.ts、match.spec.ts、parser.spec.ts、accessor.spec.ts、share.spec.ts五个测试文件覆盖了匹配、排除、别名组、范围、相对路径与数据读写等全部行为。例如 match.spec.ts 验证了基本匹配语义expect(Path.parse(xxx.eee~).match(xxx.eee)).toBeTruthy() expect(Path.parse(*(!xxx.eee,yyy)).match(xxx.ooo.ppp)).toBeTruthy() expect(Path.parse(~.aa).match(xxx.aa)).toBeTruthy()其中~.aa的用法表明扩展匹配甚至可以出现在路径中间位置是文档之外的一个有用补充。小结FormPath 是 Formily 表单体系的地基匹配语法负责定位字段数据路径负责读写数据两者统一在同一个Path解析器与匹配器之上。掌握FormPathPattern的十种语法点路径、下标、解构、相对、全匹配、局部匹配、分组、反向、扩展、范围、转义理解match/matchAliasGroup的评分机制你就能顺畅阅读 Formily 的联动、校验、重置等高级特性源码也能在自己的业务中写出精确、可维护的字段路径规则。赞分享前端UI组件【免费下载链接】formily Cross Device High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3项目地址https://gitcode.com/gh_mirrors/fo/formily点击查看免费下载相关推荐Formily FormPath 完全指南路径匹配与数据操作的统一引擎Formily FormPath 完全指南路径匹配与数据操作的统一引擎 导读 FormPath 是 Formily 表单方案 packages/core前端UI组件Express 路由完全指南从路由方法、路径匹配到模块化 Router 的实战解析Express 路由完全指南从路由方法、路径匹配到模块化 Router 的实战解析 路由Routing是 Express 应用处理 HTTP 请求的核心机文档教程教育Gulp 5 Glob 匹配完全指南从 *、** 到 ! 的路径模式语法与工程实践Gulp 5 Glob 匹配完全指南从 、 到 ! 的路径模式语法与工程实践 本文是 gulp 官方 Getting Started 系列中 Explaini构建工具CLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
