v-for

288 阅读3分钟

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

  • 需要注意的是,对于位置参数而言,他们的值得位置固定,如果你只需要 valueindex。你也一定要指定 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'
}

其实通过解析出的属性值可以看出,这里需要将 itemsitemitem/indexvalue/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) 解析为 itemindex 两个变量即可。

接下来 match[1] 去掉小括号即 alias,留下 item, index,就可以解析出最后的两个变量 itemindex

可以注意到,使用 const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/iteratorMatch 获得是 index 这个变量,item 还未获取。此时只需要将 , index 部分替换即可。

此时使用 alias.replace(forIteratorRE) 即可获得 item,此时把获得的变量拼接成一个对象即可。