Vue原理:render函数-子元素处理

210 阅读2分钟

这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

调用函数generator生成render函数时,tag可直接获取,属性可使用genProps函数进行处理并返回期望的属性结构,接下来继续完成前文未完成的工作,对子元素的处理

入口函数genChildren

const genChildren = (el) => {
  // 获取子节点数
  let children = el.children;
	
  if (children && children.length) { // 存在子节点
    // 遍历所有子节点,并将结果以,拼接为字符串
    return `${children.map((c) => gen(c)).join(",")}`;
  } else { // 不存在子节点
    return false;
  }
};

重点就是在gen函数中,如何

const gen = (node) => {
  if (node.type === 1) { // 标签节点
    return generator(node);
  } else if (node.type === 3) { // 注释节点
    return genComment(node)
  } else { // 文本节点
    return genText(node);
  }
};

若是元素节点,递归调用generator即可。若是注释节点,直接调用genComment函数

const genComment = (comment) => {
  return `_e(${JSON.stringify(comment.text)})`;
}

最为复杂的是当其是文本节点时,因为在Vue中文本可以是<p>hello nordon</p>这类纯文案内容填充,也可以是<p>{{ name }}</p>这类纯表达式内容填充,也可以是<p>hello {{ name }}</p>这类两者的混搭使用

首先看一下如何匹配{{}}的正则表达式

const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;

如何利用上面的正则将下面的字符串

const str = 'my is {{ name }}, age is {{ age }}';

转换为

_v("'myis"+_s(name)+",ageis"+_s(age)+"'")

上述这个过程便是genText函数的核心:利用正则的exec进行递归匹配

连续调用两次exec

defaultTagRE.exec(str);

第一次调用控制台输出结果:

const fitstExec = [
  0: "{{ name }}"
  1: " name "
  groups: undefined
  index: 6
  input: "my is {{ name }}, age is {{ age }}"
]

可以得到fitstExec[index]是正则匹配到第一个{的索引位置,其之前的数据便是需要的静态文本内容,fitstExec[1]则是需要使用_s处理的key,此时将fitstExec[0]的长度和fitstExec[index]相加并且将两者和记录

const firstEndIndex = fitstExec[0].length + fitstExec[index];

这里可以得到myis_s(name)

第二次调用控制台输出结果:

const secondExec = [
  0: "{{ age }}"
  1: " age "
  groups: undefined
  index: 25
  input: "my is {{ name }}, age is {{ age }}"
]

可以得到firstEndIndexsecondExec[index]之间的内容为静态文案内容,secondExec[1]则是需要使用_s处理的key

可以得到,ageis_s(age)

将两次得到的结果以+进行拼接得到"'myis"+_s(name)+",ageis"+_s(age)+"'"

上述的过程便是genText函数的核心逻辑,下面通过代码将其实现

const genText = (node) => {
  let text = node.text; // 记录全部的文本信息
  let tokens = []; // 用于存储每一次匹配到的数据
  let match = null; // 增则匹配结果
  let index = null; // 第一个匹配到的 { 位置
  let lastIndex = (defaultTagRE.lastIndex = 0); // 每次匹配结束,下次开始的位置

  // 递归匹配,直到匹配不到 {{
  while ((match = defaultTagRE.exec(text))) {
    index = match.index;

    if (index > lastIndex) {
      // 将两次匹配之间的纯文本信息存储
      tokens.push(JSON.stringify(text.slice(lastIndex, index)));
    }
		
    // 将 {{ }} 之间的内容进行处理再存储
    tokens.push(`_s(${match[1].trim()})`);
    // 将每次匹配结束的位置记录
    lastIndex = index + match[0].length;
  }
	
  // 匹配结束之后,若是最后的位置小于文本的长度,则代表 }} 后面还有纯文本信息,需要继续存储
  if (lastIndex < text.length) {
    tokens.push(JSON.stringify(text.slice(lastIndex)));
  }

  return `_v(${tokens.join("+")})`;
}

自此通过AST生成render函数的简洁版本便完成了,可以返回期望的数据格式