Vue原理:render函数-属性处理

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

渲染流程中核心部分之一是render函数的生成,整个过程的核心部分分为两块:

  1. 通过parseHTML函数将HTML编译成AST(PS:前文已完成)
  2. 通过AST生成render函数(PS:接下来的重点)

目标

首先明确当我们有下面这段HTML

<div id="app"><p>hi {{msg}}</p> hello</div>
复制代码

通过前文的parseHTML将其编译为AST

image-20211114202444856.png

其对应生成的render函数

function render() {
  with(this) {
    return _c('div', {
      attrs: {
        "id": "app"
      }
    }, [_c('p', [_v("hi " + _s(msg))]), _v(" hello")])
  }
}
复制代码

明确了HTMLASTrender函数三者的关联,其三者的整个流程为

function compileToFunction(template) {
  // 1. 解析HTML 生成AST
  const ast = parseHTML(template);
  // optimize(ast, options)

  // 2. 根据 AST 生成 render函数
  const { render } = generator(ast);

  // render 返回的是虚拟DOM
  return {
    ast,
    render,
  };
}
复制代码

期望中的generator函数应该为

function generator(ast) {
  // code 待生成
  return {
    render: new Function(`with(this) { 
      return ${code} 
    }`),
  };
}
复制代码

其中的code就是上述的

_c('div', {
  attrs: {
    "id": "app"
  }
}, [_c('p', [_v("hi " + _s(msg))]), _v(" hello")])
复制代码

最终返回的函数使用了with,其中code是一段字符串(如上所示),可以得知AST生成render函数这个过程的核心:通过递归遍历所有的AST节点,通过判断AST节点类型进行对应的字符串拼接

因为Vue的组件都只能存在一个跟节点,可以直接得到code的跟节点的tag

const code = `_c(
    "${el.tag}"
)`;
复制代码

若是当前的元素节点存在属性时,如下面这段HTML

<div id="app" class="content"></div>
复制代码

将其转换为AST时其属性的表现形式为

{
  attrs: [{
    name: 'id',
    value: 'app'
  }, {
    name: 'class',
    value: 'content'
  }]
}
复制代码

render函数中其为

{
  attrs: {
    'id': 'app',
    'class': 'content'
  }
}
复制代码

此时需要一个函数,用于处理元素标签上的属性,将其格式转换为render函数期望的格式

const genProps = (attrs) => {
  let str = "";
	
  // 遍历AST上当前元素节点所有的属性
  for (let i = 0; i < attrs.length; i++) {
    let attr = attrs[i];
		
    // 对于样式 需要特殊处理 -- style="color: aqua;"  ==> {style: {color: aqua}}
    if (attr.name === "style") {
      let obj = {};
			
      // 将样式中的每个属性和属性的值进行切割
      // "color: aqua; height: 200px;" ===> ['color: aqua', ' height: 200px']
      attr.value.split(";").forEach((item) => {
        // 获取属性和属性的值
        let [key, value] = item.split(":");
        if (key) {
          obj[key] = value.trim();
        }
      });

      attr.value = obj;
    }
		
    // 将数据进行字符串拼接
    str += `${attr.name}: ${JSON.stringify(attr.value)},`;
  }
	
  // 返回有拼接完成的字符串
  return `{${str.slice(0, -1)}}`;
};
复制代码

此时code可以进一步完善

const code = `_c(
    "${ast.tag}", 
    ${ast.attrs.length ? genProps(ast.attrs) : "undefined"}
)`;
复制代码

至此已经完成了render函数生成工作的初始化和属性的处理

Guess you like

Origin juejin.im/post/7032858722435072007