Skip to content

Latest commit

 

History

History
188 lines (158 loc) · 5.8 KB

v-for.md

File metadata and controls

188 lines (158 loc) · 5.8 KB

本篇文章,我们主要讲解v-for指令的处理流程。v-for是我们最常用的指令之一,我们从一个例子入手,详细的看一下Vue中对它的处理流程。

<div id="app">
  <p v-for="(value, key, index) in object">{{ index }}. {{ key }} : {{ value }}</p>
</div>
<script type="text/javascript">
  var vm = new Vue({
    el: '#app',
    data: {
      object: {
        height: '178cm',
        weight: '80kg',
        gender: 'male',
        address: 'BeiJing'
      }
    }
  })
</script>

还是从src/compiler/parse/index.js文件入手,在start函数中,对于v-for指令,我们通过processFor方法来进行解析:

function processFor (el) {
  let exp
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    const inMatch = exp.match(forAliasRE)
    if (!inMatch) {
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid v-for expression: ${exp}`
      )
      return
    }
    el.for = inMatch[2].trim()
    const alias = inMatch[1].trim()
    const iteratorMatch = alias.match(forIteratorRE)
    if (iteratorMatch) {
      el.alias = iteratorMatch[1].trim()
      el.iterator1 = iteratorMatch[2].trim()
      if (iteratorMatch[3]) {
        el.iterator2 = iteratorMatch[3].trim()
      }
    } else {
      el.alias = alias
    }
  }
}

getAndRemoveAttr从字面上我们就猜得到,它的功能是删除v-for属性,并返回该属性对应的值。这里exp的值为(value, key, index) in object。之前我们提到过forAliasRE

export const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/

从正则我们知道v-for中,使用in或者of是完全一样的。匹配之后,inMatch的值为["(value, key, index) in object", "(value, key, index)", "object", index: 0, input: "(value, key, index) in object"]

所以el.for中保存的就是我们要遍历的对象或数组或数字或字符串。

再来看forIteratorRE

export const forIteratorRE = /\((\{[^}]*\}|[^,]*),([^,]*)(?:,([^,]*))?\)/

我们v-for可以有如下形式:

v-for="item in items"
v-for="(item, index) in items"
v-for="(value, key, index) in object"

我们的例子中,是最全的一种,其中value是属性值、key是属性名、index是索引值。

所以,最终处理完ast中添加了如下属性:

el.alias = value
el.iterator1 = key
el.iterator2 = index

最终经过静态内容处理之后的p标签对应ast结构为:

{
  alias: "value",
  attrsList: [],
  attrsMap: {v-for: "(value, key, index) in object"},
  children: [{
    expression: "_s(index)+". "+_s(key)+" : "+_s(value)",
    text: "{{ index }}. {{ key }} : {{ value }}",
    type: 2,
    static: false
  }],
  for: "object",
  iterator1: "key",
  iterator2: "index",
  plain: true,
  tag: "p",
  type: 1,
  static: false,
  staticRoot: false
}

接着,就是根据ast结果,生成对应的render字符串。

打开src/compiler/codegen/index.js文件,这回我们会走到genFor函数中,

function genFor (el: any): string {
  const exp = el.for
  const alias = el.alias
  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''

  if (
    process.env.NODE_ENV !== 'production' &&
    maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key
  ) {
    warn(
      `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
      `v-for should have explicit keys. ` +
      `See https://vuejs.org/guide/list.html#key for more info.`,
      true /* tip */
    )
  }

  el.forProcessed = true // avoid recursion
  return `_l((${exp}),` +
    `function(${alias}${iterator1}${iterator2}){` +
      `return ${genElement(el)}` +
    '})'
}

这里if块是开发环境做一些校验。如果是自定义元素且不是slottemplate,则必须有el.key

最终返回的拼接后的字符串是一个_l函数,其中第一个参数是el.forobject,第二个参数是一个函数,函数的参数是我们的三个变量valuekeyindex。该函数返回值中再次调用genElement生成p元素的render字符串。

最终生成的render函数字符串为:

"_c('div',{attrs:{"id":"app"}},_l((object),function(value,key,index){return _c('p',[_v(_s(index)+". "+_s(key)+" : "+_s(value))])}))"

前面提到过,_c是创建一个vnode对象、_v是创建一个vnode文本结点,这些我们在vnode中详细讲解,这里我们重点说一些_l。从render.js中,我们知道它对应的函数就是src/core/instance/render-helpers/render-list.js中的renderList方法。

export function renderList (
  val: any,
  render: () => VNode
): ?Array<VNode> {
  let ret, i, l, keys, key
  // 数组或字符串
  if (Array.isArray(val) || typeof val === 'string') {
    ret = new Array(val.length)
    for (i = 0, l = val.length; i < l; i++) {
      ret[i] = render(val[i], i)
    }
  // 数字
  } else if (typeof val === 'number') {
    ret = new Array(val)
    for (i = 0; i < val; i++) {
      ret[i] = render(i + 1, i)
    }
  // 对象
  } else if (isObject(val)) {
    keys = Object.keys(val)
    ret = new Array(keys.length)
    for (i = 0, l = keys.length; i < l; i++) {
      key = keys[i]
      ret[i] = render(val[key], key, i)
    }
  }
  return ret
}

我们这里传入的val就是objectrender就是生成p段落的render函数。

代码中的三段if判断,是因为我们v-for可以遍历的不止数组和对象,还有数字和字符串。

最终返回的ret是一个VNode数组,每一个元素都是一个p标签对应的VNode

从上面的分析中,我们也可以看出v-for影响的范围只是在生成VNode对象生成的个数,而对VNode内部没有影响。