匹配能力
匹配能力是 Formily 在字段联动、effects、query 中大量依赖的部分。理解这一节之后,再回去看 core 里的 matchAliasGroup 会更顺。
交互示例
下面这个 playground 适合直接比较 match、Path.match、matchAliasGroup 和 includes 的差异:
<script setup lang="ts">
import { Path } from '@silver-formily/path'
import { computed, ref } from 'vue'
const pattern = ref('aa.*(!bb)')
const name = ref('kk.mm.aa.cc')
const alias = ref('aa.cc')
const includeSource = ref('a.b')
const includeTarget = ref('a.b')
const result = computed(() => {
try {
const parsed = Path.parse(pattern.value)
return {
directName: parsed.match(name.value),
directAlias: parsed.match(alias.value),
aliasGroup: parsed.matchAliasGroup(name.value, alias.value),
matcher: Path.match(pattern.value)(name.value),
includes: Path.parse(includeSource.value).includes(includeTarget.value),
score: parsed.matchScore,
error: '',
}
}
catch (error) {
return {
directName: false,
directAlias: false,
aliasGroup: false,
matcher: false,
includes: false,
score: 0,
error: error instanceof Error ? error.message : String(error),
}
}
})
const presets = [
{
text: 'alias success',
pattern: 'aa.*(!bb)',
name: 'kk.mm.aa.cc',
alias: 'aa.cc',
},
{
text: 'alias reject',
pattern: 'aa.*(!bb)',
name: 'kk.mm.aa.bb',
alias: 'aa.bb',
},
{
text: 'exclude score',
pattern: 'aa.bb.*(11,22,33).*(!aa,bb,cc)',
name: 'aa.bb.11.mm',
alias: 'aa.cc.dd.bb.11.mm',
},
]
function usePreset(item: { text: string, pattern: string, name: string, alias: string }) {
pattern.value = item.pattern
name.value = item.name
alias.value = item.alias
}
</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">Name</span>
<input v-model="name" class="input">
</label>
<label class="field">
<span class="fieldLabel">Alias</span>
<input v-model="alias" class="input">
</label>
</div>
<div class="grid">
<label class="field">
<span class="fieldLabel">includes source</span>
<input v-model="includeSource" class="input">
</label>
<label class="field">
<span class="fieldLabel">includes target</span>
<input v-model="includeTarget" class="input">
</label>
</div>
<div v-if="result.error" class="errorBox">
{{ result.error }}
</div>
<div class="panelGrid">
<div class="panel">
<div class="panelTitle">
match(name)
</div>
<div class="panelBody">
<span :class="result.directName ? 'statusTrue' : 'statusFalse'">{{ result.directName }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
match(alias)
</div>
<div class="panelBody">
<span :class="result.directAlias ? 'statusTrue' : 'statusFalse'">{{ result.directAlias }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
matchAliasGroup
</div>
<div class="panelBody">
<span :class="result.aliasGroup ? 'statusTrue' : 'statusFalse'">{{ result.aliasGroup }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
Path.match(pattern)(name)
</div>
<div class="panelBody">
<span :class="result.matcher ? 'statusTrue' : 'statusFalse'">{{ result.matcher }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
includes(source, target)
</div>
<div class="panelBody">
<span :class="result.includes ? 'statusTrue' : 'statusFalse'">{{ result.includes }}</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
last matchScore
</div>
<div class="panelBody metricValue">
{{ result.score }}
</div>
</div>
</div>
</div>
</template>
<style scoped src="../../shared/path-demo.css"></style> match(name)
false
match(alias)
true
matchAliasGroup
true
Path.match(pattern)(name)
false
includes(source, target)
true
last matchScore
0
查看源码
match
Path#match 的两边并不要求都是普通路径。通常有两种用法:
ts
Path.parse('a.b.c').match('*')
// true
Path.parse('*').match('a.b.c')
// true
Path.parse('aa.1.cc').match('aa.*.cc')
// true当双方都是 match pattern 时会直接报错,因为这不是一个明确的匹配方向。
ts
Path.parse('*').match('aa.*.cc')
// throw ErrorPath.match
如果你要复用 matcher,静态方法会更自然:
ts
const isTarget = Path.match('aa.*(!bb,cc)')
isTarget('aa.dd')
// true
isTarget('aa.bb')
// false如果你只需要编译一次 matcher 再多次复用,这种写法会比反复 parse(...).match(...) 更自然。
transform
transform 常用来从路径里提取索引或标识,再拼出新的路径:
ts
Path.parse('aa.1.cc').transform(/\d+/, index => `aa.${Number(index) + 1}.cc`)
// 'aa.2.cc'静态方法也支持相同的能力:
ts
Path.transform('aa.0.bb', /\d+/, index => `aa.${Number(index) + 1}.bb`)
// 'aa.1.bb'matchScore
每次匹配后,Path 实例会更新 matchScore。在多数场景下你不需要直接操作它,但 matchAliasGroup 会用它来比较包含排除模式时的匹配优先级。
matchAliasGroup
这是 core 中非常关键的一个方法。它用同一 pattern 同时匹配 name 和 alias,并根据是否带排除模式决定最终结果。
ts
const pattern = Path.parse('aa.*(!bb)')
pattern.matchAliasGroup('kk.mm.aa.cc', 'aa.cc')
// true
pattern.matchAliasGroup('kk.mm.aa.bb', 'aa.bb')
// false基础场景下,它也可以理解成“同一个 pattern 同时比对真实 name 和 alias”:
ts
Path.parse('aa.bb.cc').matchAliasGroup('aa.bb.cc', 'aa.cc')
// true带排除模式时,返回值会更依赖 matchScore:
ts
const excludePattern = Path.parse('aa.bb.*(11,22,33).*(!aa,bb,cc)')
excludePattern.matchAliasGroup('aa.bb.11.mm', 'aa.cc.dd.bb.11.mm')
// trueincludes
includes 适合判断一个普通路径是否以前缀形式包含另一个普通路径:
ts
Path.parse('a.b').includes('a.b')
// true
Path.parse('a.b').includes('a.c')
// false
Path.parse('a.b').includes('a.b.c')
// false如果把 match pattern 传给普通路径,或者反过来,都可能抛错。它不是 match 的替代品,而是一个更严格的前缀判断。