Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从零写一个 Vue(七)v-if 和 v-for #12

Open
buppt opened this issue Jun 25, 2020 · 0 comments
Open

从零写一个 Vue(七)v-if 和 v-for #12

buppt opened this issue Jun 25, 2020 · 0 comments

Comments

@buppt
Copy link
Owner

buppt commented Jun 25, 2020

写在前面

本篇是从零实现 vue2 系列第七篇,在 YourVue 中实现 v-if 和 v-for。

文章会最先更新在公众号:BUPPT。代码仓库:https://github.com/buppt/YourVue

正文

main.js 的模版中加入这两个指令

<div v-if="count===1">count === 1</div>
<div v-else-if="count===2">count === 2</div>
<div v-else>count != 1 && count != 2</div>
<div v-for="(item, key, index) in items">
    <p>{{item}}</p>
</div>

v-if

vue 解析模版时,遇到 v-if 会执行下面的函数,

function processIf (el) {
  var exp = getAndRemoveAttr(el, 'v-if'); //获取 if 表达式
  if (exp) {
    el.if = exp; //增加 if 属性
    addIfCondition(el, {  // 增加 ifConditions 属性,属性值是一个对象
      exp: exp, // exp 表示当前 v-if 的值
      block: el // block 是当前 ast 对象的引用
    });
  } else {
    if (getAndRemoveAttr(el, 'v-else') != null) { //增加 el.else 属性
      el.else = true;
    }
    var elseif = getAndRemoveAttr(el, 'v-else-if'); //添加 elseif 属性
    if (elseif) {
      el.elseif = elseif;
    }
  }
}
function addIfCondition (el, condition) { //增加 ifConditions 属性
  if (!el.ifConditions) {
    el.ifConditions = [];
  }
  el.ifConditions.push(condition);
}

添加 ifConditions 参数,其中是 exp 是判断条件,block 就是当前 ast 的引用。

{
    type: 1,
    if: "count===1",
    ifConditions: [
      {exp: "count===1", block: {}}
      {exp: "count===2", block: {}}
      {exp: undefined, block: {}}
    ]
}

对于 v-else 和 v-else-if,并没有加到自己的 ast 树中,而是把自己对应的 ast 对象添加到最近的 v-if 的 ifConditions 里,代码如下:

if (element.elseif || element.else) {
    var prev = findPrevElement(parent.children);
    if (prev && prev.if) { 
      addIfCondition(prev, {
        exp: el.elseif,
        block: el
      });
    }
}
function findPrevElement (children) {
  var i = children.length;
  while (i--) {
    if (children[i].type === 1) {
      return children[i]
    } else {
      children.pop();
    }
  }
}

在 gencode 时添加判断,如果存在 if 判断,执行 genIf()。

function genElement(el){
  if (el.for && !el.forProcessed) {
    return genFor(el)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el)
  } else {
    ...
  }
}

export function genIf (el){
  el.ifProcessed = true // avoid recursion
  return genIfConditions(el.ifConditions.slice())
}
function genIfConditions (conditions) {
  if (!conditions.length) {
    return '_e()'
  }
  const condition = conditions.shift()
  if (condition.exp) {
    return `(${condition.exp})?${
      genElement(condition.block)
    }:${
      genIfConditions(conditions)
    }`
  } else {
    return `${genElement(condition.block)}`
  }
}

其实 genIf 没有使用新的 render 函数,只是在 render 函数中添加了判断

(count===1)?
  _c('div',{},[_v("count === 1")]):
  (count===2)?
    _c('div',{},[_v("count === 2")]):
    _c('div',{},[_v("count != 1 && count != 2")]
  )

v-for

同样 v-for 在 ast 中添加了 for、alias、iterator1 和 iterator2 四个参数。

function parseFor (exp) {
  var inMatch = exp.match(forAliasRE);
  if (!inMatch) { return }
  var res = {};
  res.for = inMatch[2].trim();
  var alias = inMatch[1].trim().replace(stripParensRE, ''); 
  var iteratorMatch = alias.match(forIteratorRE);
  if (iteratorMatch) { // v-for="(item, key, index) in items"
    res.alias = alias.replace(forIteratorRE, '');   //获取别名 (item)
    res.iterator1 = iteratorMatch[1].trim();     //获取索引1 (key)
    if (iteratorMatch[2]) {
      res.iterator2 = iteratorMatch[2].trim();  //获取索引2 (index)
    }
  } else {
    res.alias = alias;
  }
  return res;
}

ast 如下

{
    tag: "div"
    type: 1
    for: "items"
    alias: "item"
    iterator1: "key"
    iterator2: "index"
}

在 gencode 阶段,执行 genFor 时引入了新的 render 函数 _l()

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

生成的 code 函数如下

_l( (items),
    function(item,key,index){
      return _c('div',{},[_c('p',{},[_v(_s(item))])])
    }
  )

render 阶段执行 renderList 函数

export function initRender(vm){
    vm._l = renderList
}
function renderList (val, render){
  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 {
      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)
      }
  }
  if (!ret) {
    ret = []
  }
  ret._isVList = true
  return ret
}

从 renderList 函数的实现可以看出来,v-for 可以遍历数组,还可以遍历字符串、number、object,最后会返回一个 VNode 数组。

这时候在父元素 createElement 的时候,就会通过第四篇文章中说过的 simpleNormalizeChildren 将 VNode 数组展开,然后 v-for 就和其他同层元素相同,一起创建和更新。

本篇代码:https://github.com/buppt/YourVue/tree/master/oldSrc/6.if%26for

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant