模式语法
@silver-formily/path 的强项不是普通点路径,而是它把“路径模式”也统一放进了同一套系统里。
交互示例
这个 playground 会把当前 pattern 的解析结果、flags 和匹配结果直接展示出来,适合试验 *、**、~、group 和相对路径:
<script setup lang="ts">
import { Path } from '@silver-formily/path'
import { computed, ref } from 'vue'
const pattern = ref('*(phases.*.type,phases.*.steps.*.type)')
const base = ref('')
const target = ref('phases.0.steps.1.type')
const parsed = computed(() => {
try {
const value = Path.parse(pattern.value, base.value || undefined)
return { value, error: '' }
}
catch (error) {
return { value: null, error: error instanceof Error ? error.message : String(error) }
}
})
const matchResult = computed(() => {
if (parsed.value.error || !parsed.value.value)
return { matched: false, error: parsed.value.error }
try {
return {
matched: parsed.value.value.match(target.value),
error: '',
}
}
catch (error) {
return {
matched: false,
error: error instanceof Error ? error.message : String(error),
}
}
})
const presets = [
{ text: 'single wildcard', value: 'user.*', target: 'user.name' },
{ text: 'optional deep wildcard', value: 'aa.**', target: 'aa.bb.cc' },
{ text: 'expand', value: 't.0.value~', target: 't.0.value_list' },
{ text: 'exclude group', value: '*(!basic.name,versionTag)', target: 'basic.id' },
{ text: 'relative path', value: '.value', target: 'users.0.value', base: 'users.0' },
]
function usePreset(item: { text: string, value: string, target: string, base?: string }) {
pattern.value = item.value
target.value = item.target
base.value = item.base ?? ''
}
</script>
<template>
<div class="playground">
<div class="tagRow">
<button v-for="item in presets" :key="item.text" class="btn secondary" @click="usePreset(item)">
{{ item.text }}
</button>
</div>
<div class="grid">
<label class="field">
<span class="fieldLabel">Pattern</span>
<input v-model="pattern" class="input">
</label>
<label class="field">
<span class="fieldLabel">Base</span>
<input v-model="base" class="input" placeholder="optional">
</label>
<label class="field">
<span class="fieldLabel">Target</span>
<input v-model="target" class="input">
</label>
</div>
<div v-if="parsed.error || matchResult.error" class="errorBox">
{{ parsed.error || matchResult.error }}
</div>
<div v-else-if="parsed.value" class="panelGrid">
<div class="panel">
<div class="panelTitle">
Parsed Path
</div>
<div class="panelBody metricValue">
{{ parsed.value.toString() }}
</div>
</div>
<div class="panel">
<div class="panelTitle">
Segments
</div>
<div class="panelBody metricValue">
{{ JSON.stringify(parsed.value.toArr(), null, 2) }}
</div>
</div>
<div class="panel">
<div class="panelTitle">
Match Target
</div>
<div class="panelBody">
<span :class="matchResult.matched ? 'statusTrue' : 'statusFalse'">
{{ matchResult.matched }}
</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
Flags
</div>
<div class="panelBody tagRow">
<span class="tag">isMatchPattern: {{ parsed.value.isMatchPattern }}</span>
<span class="tag">isWildMatchPattern: {{ parsed.value.isWildMatchPattern }}</span>
<span class="tag">haveRelativePattern: {{ parsed.value.haveRelativePattern }}</span>
<span class="tag">haveExcludePattern: {{ parsed.value.haveExcludePattern }}</span>
<span class="tag">isRegExp: {{ parsed.value.isRegExp }}</span>
</div>
</div>
</div>
</div>
</template>
<style scoped src="../../shared/path-demo.css"></style> Parsed Path
*(phases.*.type,phases.*.steps.*.type)
Segments
[]
Match Target
true
Flags
isMatchPattern: trueisWildMatchPattern: truehaveRelativePattern: falsehaveExcludePattern: falseisRegExp: false
查看源码
普通路径
ts
Path.parse('user.profile.nickname')
Path.parse(['user', 'profile', 'nickname'])这是最常见的数据操作路径,主要用于 getIn / setIn / deleteIn。
下标路径
数组下标既可以写成点路径,也可以写成中括号:
ts
const target = { array: [] }
Path.setIn(target, 'array.0.aa', '000')
Path.setIn(target, 'array[1].aa', '111')
Path.getIn(target, 'array.0.aa')
// '000'
Path.getIn(target, 'array.1.aa')
// '111'解构表达式
解构表达式会作为路径中的一个普通节点存在,但在数据读写时会触发“拆开 / 重组”的行为:
ts
const target = {}
Path.setIn(target, 'parent.[aa,bb]', [11, 22])
// { parent: { aa: 11, bb: 22 } }
Path.getIn(target, 'parent.[aa,bb]')
// [11, 22]
Path.parse('parent.[aa,bb]').toString()
// 'parent.[aa,bb]'这类语法很适合前后端结构不一致时的投影映射。
单层通配 *
* 表示匹配一层:
ts
Path.parse('user.*').match('user.name')
// true
Path.parse('user.*').match('user.profile.name')
// false可选深层通配 **
** 表示“从这里开始,后面可以有零层到多层”:
ts
Path.parse('aa.**').match(['aa'])
// true
Path.parse('aa.**').match(['aa', 'bb', 'cc'])
// trueexpand ~
~ 用来表达前缀扩展匹配:
ts
Path.parse('xxx.eee~').match('xxx.eee')
// true
Path.parse('t.0.value~').match(['t', 0, 'value_list'])
// true这在 Formily 的字段别名、数组子字段扩展里很常见。
相对表达式
当你传了 base path 时,pattern 可以写成相对形式:
ts
Path.parse('.value', 'users.0').toString()
// 'users.0.value'更复杂的相对路径在数组邻居计算里尤其常见:
ts
Path.parse('.dd', 'aa.bb.cc').toString()
// 'aa.bb.dd'
Path.parse('..[].dd', 'aa.1.cc').toString()
// 'aa.1.dd'
Path.parse('..[+].dd', 'aa.1.cc').toString()
// 'aa.2.dd'
Path.parse('..[+10].dd', 'aa.1.cc').toString()
// 'aa.11.dd'这里可以记住三条规则:
- 一个点表示当前路径。
n个点表示向前回退n - 1步。- 中括号中的
+/-表达式可以基于当前数组下标做偏移计算。
全匹配
全匹配就是单独一个 *,表示匹配所有路径:
ts
Path.parse('*').match('aa')
// true
Path.parse('*').match('aa.bb')
// true局部匹配
把 * 放到某个具体节点上,就是“只匹配当前位置的任意一层”:
ts
Path.parse('aa.*.cc').match('aa.bb.cc')
// true
Path.parse('aa.*.cc').match('aa.kk.cc')
// truegroup
group 语法允许你把多个候选路径合并到一个 pattern 中:
ts
Path.parse('*(aa,bb,cc)').match('bb')
// true
Path.parse('*(phases.*.type,phases.*.steps.*.type)').match('phases.0.steps.1.type')
// true
Path.parse('aa.*(bb,kk,dd,ee.*(oo,gg).gg).cc').match('aa.ee.oo.gg.cc')
// true排除模式
在 group 里加 ! 可以表达排除:
ts
Path.parse('*(!aaa)').match('ggg')
// true
Path.parse('*(!aaa)').match('aaa')
// false
Path.parse('*(!basic.name,versionTag)').match('basic.id')
// true排除模式常见于“通配大部分字段,但排除少数敏感路径”的场景。
正则路径
正则也属于合法 pattern:
ts
Path.parse(/^\d+$/).match('212')
// true
Path.parse(/^\d+$/).match('212dd')
// false范围匹配
范围匹配主要服务于数组索引:
ts
Path.parse('aa.*[1:2].bb').match('aa.1.bb')
// true
Path.parse('aa.*[1:2].bb').match('aa.3.bb')
// false
Path.parse('aa.*[1:].bb').match('aa.3.bb')
// true
Path.parse('aa.*[:100].bb').match('aa.1000.bb')
// falsex 或 y 留空时表示开区间。
转义匹配
如果路径节点本身包含语法关键字,可以用反斜杠或 [[...]] 转义:
ts
Path.parse('aa.\\,\\*\\{\\}\\.\\(\\).bb').match('aa.\\,\\*\\{\\}\\.\\(\\).bb')
// true
Path.parse('aa.[[,*{}.()]].bb').match('aa.[[,*{}.()]].bb')
// true解构匹配
带解构表达式的路径在匹配时可以直接按原样匹配,无需额外转义:
ts
Path.parse('target.[aa,bb]').match('target.[aa,bb]')
// true