v-for parse
在了解 v-for
指令解析之前,需要先明确这个指令的使用场景。
使用场景举例
第一种: <div v-for="item in items"></div>
第二种:<div v-for="(item, index) in items"></div>
第三种:<div v-for="(value, key, index) in object"></div>
分析
-
当遍历对象
items
是一个数组时,item
是必须的,代表数组的每一项的数据选项;index
是可选的。需要注意的是,变量名不是固定的,他们是位置参数,不论起什么样的名称和其真实值没有任何关系。 -
当遍历对象是
object
(非数组时),行为和Object.entries
比较像,这时可以指定位置参数value
,key
,index
。 -
需要注意的是,对于位置参数而言,他们的值得位置固定,如果你只需要
value
和index
。你也一定要指定key
,不然index
保存的就是key
的值。
明确了使用场景,结合这里解析函数,可以得到 v-for
指令的初步解析结果为(跟上面的三种情况一一对应):
第一种:
vFor = {
value: 'item in items',
name: 'v-for'
}
第二种
vFor = {
value: '(item, index) in items',
name: 'v-for'
}
第三种
vFor = {
value: '(value, key, index) in items',
name: 'v-for'
}
其实通过解析出的属性值可以看出,这里需要将 items
、item
、item/index
、value/key/index
这几个变量解析出来,作为迭代对象(items
)和迭代变量保存,方便下一步的代码生成。
设定目标
根据上面的分析结合 Vue
源码,上面的三种情况应该解析成下面这三种格式的对象
// 第一种
vFor = {
for: 'items',
alias: 'item'
}
// 第二种
vFor = {
for: 'items',
alias: 'item',
iterator1: 'index'
}
// 第三种
vFor = {
for: 'object',
alias: 'value',
iterator1: 'key',
iterator2: 'index'
}
明确了解析目标,下一步就可以开始写解析的代码。
coding
和解析 html
字符串一样,需要借助正则的帮助来完成整个匹配过程。
const stripParensRE = /^\(|\)$/g
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
function parseVFor(input) {
const match = input.match(forAliasRE)
if(match){
let res = {}
res.for = match[2]
// 去掉小括号
const alias = match[1].replace(stripParensRE, '').trim()
// 匹配出 item, index 的后半部分 , index
const iteratorMatch = alias.match(forIteratorRE)
if(iteratorMatch){
// item
res.alias = alias.replace(forIteratorRE, '')
// index/key
res.iterator1 = iteratorMatch[1].trim()
if(iteratorMatch[2]){
// index
res.iterator2 = iteratorMatch[2].trim()
}
} else {
res.alias = alias.trim()
}
return res
}
}
console.log('dsdaddad', parse("(item, key, index) in items"));
上述代码虽然短,但是充斥着正则的匹配和字符串替换,下面将会展开来进行解析
coding parse
正则
三段正则在这里起到了至关重要的作用,这里通过以些例子来了解一下正则的具体作用
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
这个正则主要是匹配出 in/of
前后的字符串。看下面的例子:
const stripParensRE = /^\(|\)$/g
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const str = "(item, index) in items"
const match = str.match(forAliasRE)
// 此时的 match 为
/***
* match = [
* "(item, index) in items"
"(item, index)"
"items"
groups: undefined
index: 0
input: "(item, index) in items"
* ]
*
*/
const alias = match[1].replace(stripParensRE, '')
// alise === item, index
// 匹配出 index
const iteratorMatch = alias.match(forIteratorRE)
// 此时的 iteratorMatch 为
/***
* iteratorMatch = [
* ", index"
" index"
undefined
groups: undefined
index: 4
input: "item, index"
* ]
*
*/
// 替换掉 , index 获取item
const item = alias.replace(forIteratorRE, '');
// 最终结果
const vFor = {
for: match[2], // items
alias: item, // item
iterator1: iteratorMatch[1], // 这里如果是遍历对象时 key,数组是 index,如果没指定,这里是 undefined
iterator2: iteratorMatch[2], // 只有遍历的是非数组对象时,这里是 index,否则为 undefined
}
可以看到 match[1]
是 in/of
之前的字符串,而 match[2]
就是 in/of
之后的字符串。这样子就把迭代变量 items
和 每次迭代的参数 (item, index)
解析了出来。那么下面的就是将 (item, index)
解析为 item
和 index
两个变量即可。
接下来 match[1]
去掉小括号即 alias
,留下 item, index
,就可以解析出最后的两个变量 item
和 index
。
可以注意到,使用 const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
即 iteratorMatch
获得是 index
这个变量,item
还未获取。此时只需要将 , index
部分替换即可。
此时使用 alias.replace(forIteratorRE)
即可获得 item
,此时把获得的变量拼接成一个对象即可。