Formily Core Query 对象完全指南:字段查询 API 的方法签名、底层实现与实战用法
Formily Core Query 对象完全指南字段查询 API 的方法签名、底层实现与实战用法【免费下载链接】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本文围绕 Query.md 文档展开系统讲解 Formily 核心包formily/core中 Query 查询对象的全部 APItake、map、forEach、reduce、get、getIn、value、initialValue的签名与语义并结合 Query.ts 源码剖析其匹配原理、相对路径解析逻辑以及 Form/Field 中query方法的真实调用场景。一、导读Query 是什么在 Formily 中Query是由Form实例或Field实例上的query(pattern)方法返回的一个查询结果对象。它封装了一次按路径模式检索字段节点的结果集并提供一组与数组操作同构的方法take/map/forEach/reduce以及便捷的状态读取方法get/getIn/value/initialValue让开发者可以用声明式的路径模式FormPathPattern批量定位字段并读取其状态。理解 Query 是掌握 Formily 核心包字段操作的基础Form 内部的clearErrors、clearWarnings、queryFeedbacks、clearFormGraph等内置能力全部建立在 Query 之上。读完本文你将掌握 Query 对象每个方法的调用签名、返回值语义、与FormPathPattern的配合方式以及它在 Formily 源码中的实际工作链路。二、Query 的诞生从query()方法说起Query 对象本身并不需要手动new而是通过两个入口获取Form实例上的query(pattern: FormPathPattern): Query详见 Form.mdField实例含Field、ArrayField、ObjectField、VoidField即GeneralField上的query(pattern: FormPathPattern | RegExp): Query详见 Field.md。从源码看两个入口最终都构造了同一个Query类Query.ts// Form 侧Form.ts#L513-L519 query (pattern: FormPathPattern): Query { return new Query({ pattern, base: , form: this, }) } // Field 侧BaseField.ts#L320-L326 query (pattern: FormPathPattern | RegExp) { return new Query({ pattern, base: this.address, form: this.form, }) }两者唯一的差异在于构造参数baseForm.query传入base: 查询基准是表单根路径Field.query传入base: this.address即以当前字段地址为基准进行相对路径查询因此可以在一个字段内部查询它的相邻字段例如查询同级的另一个字段。IQueryProps在 types.ts 中定义为{ pattern: FormPathPattern; base: FormPathPattern; form: Form }。构造时的匹配逻辑Query构造函数会先把pattern与base合并解析为绝对路径this.pattern FormPath.parse(props.pattern, props.base)随后分两条路径收集匹配地址addresses: string[]精确模式非匹配模式如果pattern不是模糊匹配模式!this.pattern.isMatchPattern则直接通过takeMatchPattern在form.fields与form.indexes中查找标识符对应的绝对地址命中则addresses [matched]。这里优先查找绝对字段其次查找索引字段。匹配模式通配/正则遍历form.fields对每个字段调用field.match(this.pattern)判断是否命中命中则把地址压入addresses。因此 Query 的结果集在构造时就已经确定后续所有方法都围绕这份addresses列表工作。一个通用的内部辅助函数Query.ts顶部的output函数决定了结果的形式const output (field, taker) { if (!field) return if (isFn(taker)) { return taker(field, field.address) } return field }不传回调时返回字段节点本身传回调时以(field, address)为参数调用回调并返回其结果字段不存在已被销毁时返回undefined。这正是take/map/forEach/reduce支持可选映射器的实现基础。三、遍历类方法take / map / forEach / reduce文档明确指出使用这四类方法时结果集中必须存在对应节点否则相关字段会得到undefined遍历将无内容可处理。3.1 take —— 提取结果集第一个节点描述从查询结果集中提取第一条结果可选传入getter对取到的字段做映射。文档强调读取前必须确认存在对应节点。签名Query.mdinterface take { (): GeneralField Result(getter: (field: GeneralField, address: FormPath) Result): Result }实现Query.tstake(taker?: any): any { return output(this.form.fields[this.addresses[0]], taker) }即取addresses[0]对应的字段再交给output处理。典型用法const field form.query(username).take() // 直接拿字段 const title form.query(username).take(f f.title) // 拿映射结果3.2 map —— 遍历并映射整个结果集描述对查询结果集逐一遍历并映射返回与结果集等长的新数组。签名interface map { (): GeneralField[] Result(mapper?: (field: GeneralField, address: FormPath) Result): Result[] }实现map(iterator?: any): any { return this.addresses.map((address) output(this.form.fields[address], iterator) ) }不传mapper时返回字段节点数组GeneralField[]传入mapper时返回映射后的Result[]。mapper接收两个参数字段节点field与路径对象addressFormPath实例。// 收集所有以 user 开头的字段的标题 const titles form.query(user.*).map((f) f.title) // 直接获取所有匹配字段节点 const fields form.query(*).map()3.3 forEach —— 仅遍历不返回描述遍历查询结果集与map的区别是不产生返回值。签名interface forEach { Result(eacher: (field: GeneralField, address: FormPath) Result): void }实现forEachResult(iterator) { return this.addresses.forEach((address) output(this.form.fields[address], iterator) ) }Form 内部大量使用forEach批量操作字段例如clearErrorsForm.tsclearErrors (pattern: FormPathPattern *) { this.query(pattern).forEach((field) { if (!isVoidField(field)) { field.setFeedback({ type: error, messages: [] }) } }) }类似地clearWarnings、clearSuccesses、onUnmount中的字段销毁this.query(*).forEach((field) field.destroy(false))、clearFormGraph等都依赖forEach完成批量遍历。3.4 reduce —— 结果集归约描述对查询结果集执行归约reduce操作可传入初始值。签名interface reduce { Result( reducer: (value: Result, field: GeneralField, address: FormPath) Result, initial?: Result ): Result }实现Query.tsreduceResult(reducer, initial?: Result): Result { return this.addresses.reduce( (value, address) output(this.form.fields[address], (field, address) reducer(value, field, address) ), initial ) }reducer每次回调收到三个参数累计值value、当前字段field、当前路径address。Form 的queryFeedbacksForm.ts就是 reduce 的经典用例——把多个字段的反馈消息汇聚成一条数组queryFeedbacks (search: ISearchFeedback): IFormFeedback[] { return this.query(search.address || search.path || *).reduce( (messages, field) { if (isVoidField(field)) return messages return messages.concat( field .queryFeedbacks(search) .map((feedback) ({ ...feedback, address: field.address.toString(), path: field.path.toString(), })) .filter((feedback) feedback.messages.length 0) ) }, [] ) }四、读取类方法get / getIn / value / initialValue这组方法用于取出第一个匹配节点的某个状态属性无需自己调用take再取属性。4.1 get —— 读取第一个节点的指定属性描述从查询结果集中找到第一个结果并读取它的指定属性。K被约束为IGeneralFieldState的键因此具有类型安全。文档同样强调读取前必须确认节点存在。签名interface get { K extends keyof IGeneralFieldState(key: K): IGeneralFieldState[K] }IGeneralFieldState IFieldState IVoidFieldState见 types.ts涵盖value、initialValue、display、pattern、title、description、required、disabled、visible、editable等通用字段状态。实现Query.tsgetK extends keyof IGeneralFieldState(key: K): IGeneralFieldState[K] { const results: any this.take() if (results) { return results[key] } }内部直接复用take()取第一个节点再读取键值。例如const required form.query(username).get(required) const visible form.query(password).get(visible)4.2 getIn —— 支持 FormPathPattern 的深度读取描述找到第一个结果后使用FormPathPattern路径语法在其状态内继续深度读取属性能力比get更强。签名interface getIn { (pattern?: FormPathPattern): any }实现getIn(pattern?: FormPathPattern) { return FormPath.getIn(this.take(), pattern) }它委托给FormPath.getIn因此可以穿透多层结构读取例如读取字段component的props中的某个深层选项const placeholder form.query(username).getIn(component.props.placeholder) const componentType form.query(username).getIn(component.type)FormPathPattern的完整语法参见 FormPath 文档文档中标注为/api/entry/form-path#formpathpattern对应页面。4.3 value —— 查询指定路径的值描述查询指定路径的当前值不限于 Field 节点——即对于任意路径即使没有对应字段节点也能读取表单数据中该路径的值。签名interface value { (): any }实现Query.tsvalue() { return this.get(value) }const v form.query(user.name).value()4.4 initialValue —— 查询指定路径的初始值描述查询指定路径的初始值同样不限于 Field 节点。签名interface initialValue { (): any }实现initialValue() { return this.get(initialValue) }const iv form.query(user.name).initialValue()五、匹配模式与相对路径Query 的进阶用法5.1 通配与模糊匹配当pattern是匹配模式isMatchPattern为真时Query 会对全量字段做field.match(this.pattern)过滤因此支持 Formily 路径语法中的通配符能力例如form.query(*.name) // 所有层级下的 name 字段 form.query(array.*.value) // 数组内每个元素的 value 字段 form.query(*) // 全部字段Form 中默认参数pattern: *的方法clearErrors、clearWarnings、clearSuccesses、clearFormGraph等都利用了这一特性实现对全表单字段的批量操作。5.2 相对路径与 buildDataPath从Field.query发起查询时base为当前字段地址此时this.pattern.haveRelativePattern为真构造函数会调用buildDataPath(form.fields, pattern)internals.ts把相对路径换算成基于数据的绝对路径再交给takeMatchPattern精确解析。这意味着你可以在一个字段的reactions或副作用逻辑中基于自身位置去找邻居field.query(.confirmPassword) // 以当前字段为基准的同级字段 field.query(..*) // 向上一级再匹配所有字段5.3 与 setFieldState / getFieldState 的分工Form 上还有setFieldState/getFieldStateForm.ts它们本质是按模式批量读写字段状态的更高层封装而 Query 是这一能力的基础设施。当需要更细粒度的遍历map/reduce/forEach、或需要拿到字段节点本身做进一步方法调用时直接使用form.query(pattern)更合适。六、源码结构总览Query 相关文件索引文件说明Query.mdQuery 对象 API 官方文档本文主体Query.tsQuery 类完整实现构造匹配、八个方法Form.tsForm.query入口及内部消费clearErrors 等BaseField.tsField.query入口相对路径基准types.tsIGeneralFieldState、GeneralField、IQueryProps类型定义internals.tsbuildDataPath相对路径换算实现models/index.tsQuery 等模型的统一导出入口七、小结何时用哪个方法只需第一个匹配字段或其属性 →take()/take(getter)需要批量收集匹配字段或派生数据 →map()/map(mapper)需要批量副作用清空错误、销毁字段、设置状态→forEach()需要汇总聚合多个字段的数据如汇总反馈消息→reduce(reducer, initial?)读取第一个字段的指定状态键→get(key)类型安全读取第一个字段的深层嵌套属性→getIn(pattern?)支持 FormPathPattern读取任意路径的当前值 / 初始值不要求存在字段节点→value()/initialValue()。掌握 Query 的八种方法及其底层匹配机制你就能像 Formily 内部实现一样用一行form.query(pattern)精确、批量地操控任意表单字段状态写出更简洁且可维护的表单逻辑。【免费下载链接】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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考