likes
comments
collection
share

【源码&库】Vue3的render函数节点生成源码解读

作者站长头像
站长
· 阅读数 19

继续上一篇的节奏,上一篇我们分析了Vue3render函数的生成过程,在genNode函数中,我们看到了很多节点类型的生成函数;

今天我们就来具体分析一下这些节点类型的生成函数,看看它们是如何生成的;、

genNode

function genNode(node, context) {
    // 如果是 string 类型,则直接作为代码
    if (isString(node)) {
        context.push(node);
        return;
    }
    
    // 如果是 symbol 类型,则使用辅助函数生成代码
    if (isSymbol(node)) {
        context.push(context.helper(node));
        return;
    }
    
    // 根据节点类型,执行不同的生成函数
    switch (node.type) {
        case 1: // 元素节点
        case 9: // if
        case 11: // for
            // 这些节点直接递归生成
            assert(
                node.codegenNode != null,
                `Codegen node is missing for element/if/for node. Apply appropriate transforms first.`
            );
            genNode(node.codegenNode, context);
            break;
        case 2: // 文本节点
            genText(node, context);
            break;
        case 4: // 表达式节点
            genExpression(node, context);
            break;
        case 5: // 插值节点
            genInterpolation(node, context);
            break;
        case 12: // fragment 节点
            genNode(node.codegenNode, context);
            break;
        case 8: // 复合表达式节点
            genCompoundExpression(node, context);
            break;
        case 3: // 注释节点
            genComment(node, context);
            break;
        case 13: // 生成 createVNode 的调用
            genVNodeCall(node, context);
            break;
        case 14: // 生成普通函数调用
            genCallExpression(node, context);
            break;
        case 15: // 生成对象表达式
            genObjectExpression(node, context);
            break;
        case 17: // 生成数组表达式
            genArrayExpression(node, context);
            break;
        case 18: // 生成函数表达式
            genFunctionExpression(node, context);
            break;
        case 19: // 生成条件表达式
            genConditionalExpression(node, context);
            break;
        case 20: // 生成缓存表达式
            genCacheExpression(node, context);
            break;
        case 21: // 生成节点列表
            genNodeList(node.body, context, true, false);
            break;
        case 22:
            break;
        case 23:
            break;
        case 24:
            break;
        case 25:
            break;
        case 26:
            break;
        case 10:
            break;
        default:
        {
            assert(false, `unhandled codegen node type: ${node.type}`);
            const exhaustiveCheck = node;
            return exhaustiveCheck;
        }
    }
}

在这里我们可以看到有很多的节点类型,如下:

  • genText: 文本节点
  • genExpression: 表达式节点
  • genInterpolation: 插值节点
  • genCompoundExpression: 复合表达式节点
  • genComment: 注释节点
  • genVNodeCall: 生成 createVNode 的调用
  • genCallExpression: 生成普通函数调用
  • genObjectExpression: 生成对象表达式
  • genArrayExpression: 生成数组表达式
  • genFunctionExpression: 生成函数表达式
  • genConditionalExpression: 生成条件表达式
  • genCacheExpression: 生成缓存表达式
  • genNodeList: 生成节点列表

我们就来一一分析一下这些节点类型的生成函数,我们这一章还是一样可以通过如下示例代码进行调试和分析:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id='app'></div>

</body>
<script src="./vue.global.js"></script>
<script>
    const {createApp, h} = Vue;

    const app = createApp({
        template: '直接在这里写模板语法'
    });

    debugger;
    app.mount('#app');
</script>
</html>

genText

genText函数用来生成文本节点,具体代码如下:

function genText(node, context) {
    // 如果是纯文本,则直接通过 JSON.stringify 转成字符串
    // 最后的结果是 return "xxx"
    context.push(JSON.stringify(node.content), node);
}

验证代码如下:

const app = createApp({
    template: 'xxx'
});

生成的代码如下:

return function render(_ctx, _cache) {
  with (_ctx) {
    return "xxx"
  }
}

genExpression

genExpression函数用来生成表达式节点,具体代码如下:

function genExpression(node, context) {
    // 获取表达式 和 是否静态标识
    const { content, isStatic } = node;
    // 如果是静态的,直接将内容作为字符串进行处理,否则就直接将表达式进行返回
    context.push(isStatic ? JSON.stringify(content) : content, node);
}

验证代码如下:

const app = createApp({
    template: '{{ 1 + 1 }}'
});

生成的代码如下:

const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString } = _Vue

    return _toDisplayString(1 + 1)
  }
}

genInterpolation

可以看到上面的最终生成的结果会有一个toDisplayString的函数包装,这个函数是由genInterpolation函数生成的;

而这个函数就是由genInterpolation函数生成的,这个函数就是用来处理插值表达式的,具体代码如下:

function genInterpolation(node, context) {
    // 从上下文中获取 push、helper 和 pure
    const { push, helper, pure } = context;
    
    // pure 用来标记是否是纯的,如果是纯的,则会添加 PURE_ANNOTATION
    // 也就是 /*#__PURE__*/ 标记
    // 有了这个标记,在代码优化阶段,就会告诉编译器,可以安全地删除或替换它的调用,而不影响程序的行为
    if (pure)
      push(PURE_ANNOTATION);
    
    // TO_DISPLAY_STRING 就是生成 toDisplayString 函数的辅助函数
    // 通过调用 helper(TO_DISPLAY_STRING) 就会生成:_toDisplayString
    push(`${helper(TO_DISPLAY_STRING)}(`);
    
    // 递归生成插值表达式的内容,按照我们的示例就会进入上面的 genExpression 函数
    genNode(node.content, context);
    
    // 最后将 ) 添加到代码中
    push(`)`);
}

验证代码和结果同上;

genCompoundExpression

复合表达式节点指的是一个节点中包含多个表达式,例如:{{ 1 + 1 }} {{ 2 + 2 }},这里就是一个复合表达式节点,这个节点中包含了两个表达式,分别是 1 + 12 + 2; 只要是一个节点中包含多个表达式,那么这个节点就是一个复合表达式节点;

genCompoundExpression函数用来生成复合表达式节点,具体代码如下:

function genCompoundExpression(node, context) {
    // 直接遍历子节点
    for (let i = 0; i < node.children.length; i++) {
        const child = node.children[i];
        // 如果是 string 类型,则直接推入到上下文中
        if (isString(child)) {
            context.push(child);
        }
        
        // 否则就递归调用 genNode 函数
        else {
            genNode(child, context);
        }
    }
}

验证代码如下:

const app = createApp({
    template: '{{ 1 + 1 }} {{ 2 + 2 }}'
});

生成的代码如下:

const _Vue = Vue

return function render(_ctx, _cache) {
    with (_ctx) {
        const { toDisplayString: _toDisplayString } = _Vue

        return _toDisplayString(1 + 1) + " " + _toDisplayString(2 + 2)
    }
}

genComment

genComment函数用来生成注释节点,具体代码如下:

function genComment(node, context) {
    // 同上
    const { push, helper, pure } = context;
    if (pure) {
        push(PURE_ANNOTATION);
    }
    
    // helper(CREATE_COMMENT) 用来生成 _createCommentVNode 函数
    // 将注释内容转换为 JSON 格式的字符串作为内容
    push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);
}

验证代码如下:

const app = createApp({
    template: '<!-- 这是注释 -->'
});

生成的代码如下:

const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { createCommentVNode: _createCommentVNode } = _Vue

    return _createCommentVNode(" 这是注释 ")
  }
}

genVNodeCall

genVNodeCall函数用来生成复合表达式节点,具体代码如下:

function genVNodeCall(node, context) {
    // 获取 push、helper 和 pure
    const { push, helper, pure } = context;
    
    // 获取节点的类型和 props
    const {
        tag,
        props,
        children,
        patchFlag,
        dynamicProps,
        directives,
        isBlock,
        disableTracking,
        isComponent
    } = node;
    
    // 是否存在指令,如果存在,则需要添加 WITH_DIRECTIVES 标记
    // 最后会生成:_withDirectives
    if (directives) {
        push(helper(WITH_DIRECTIVES) + `(`);
    }
    
    // 是否是一个块,块指的是有能成对出现的节点,像 html 标签都是
    // 也会有自闭合标签,像 img、input 等,但是他们也都是成块的
    // 所以我理解的块指的是不可拆分的一个整体就表示为一个块
    // 这里最后会生成:_openBlock
    if (isBlock) {
        push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `);
    }
    
    // 是否纯函数
    if (pure) {
        push(PURE_ANNOTATION);
    }
    
    // 根据是否是块节点和是否是组件选择适当的 createVNode 辅助函数
    // 这里最后就就是会生成不同的函数处理函数,可以看下面贴出来的 getVNodeBlockHelper 和 getVNodeHelper 函数
    const callHelper = isBlock ? getVNodeBlockHelper(context.inSSR, isComponent) : getVNodeHelper(context.inSSR, isComponent);
    push(helper(callHelper) + `(`, node);
    
    // 生成包含 createVNode 调用的参数列表
    // genNullableArgs 用于生成可空的参数列表,这个函数就是会移除尾部的控制,然后将剩余的参数列表返回
    // 例如 ['div', null, [], null, null] 会生成 ['div', null, []]
    // 然后 ['div', null, []] 这个就是一个函数的参数列表,例如 fn('div', null, []) 这样的调用
    // 后面会有 genNodeList 函数的讲解
    genNodeList(
        genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
        context
    );
    
    // 推入函数的结束括号
    push(`)`);
    
    // 如果是块节点,则需要推入块节点的结束括号
    if (isBlock) {
        push(`)`);
    }
    
    // 如果存在指令,则推入逗号和生成的指令函数,本质也是通过递归调用 genNode 函数
    if (directives) {
        push(`, `);
        genNode(directives, context);
        push(`)`);
    }
}

function getVNodeHelper(ssr, isComponent) {
    return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE;
}
function getVNodeBlockHelper(ssr, isComponent) {
    return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK;
}

验证代码如下:

const app = createApp({
    template: '<div>xxx</div>'
});

生成的代码如下:

const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

    return (_openBlock(), _createElementBlock("div", null, "xxx"))
  }
}

genCallExpression

普通函数调用本身不是由开发者直接书写的,而是内部处理的,例如使用 v-if 会生成一个注释节点来进行标记(如果节点隐藏,没有替代的节点),这个时候就会生成 _createCommentVNode 函数的调用;

genCallExpression函数用来生成普通函数调用,具体代码如下:

function genCallExpression(node, context) {
    // 获取 push、helper 和 pure
    const { push, helper, pure } = context;
    
    // 这一步是生成调用函数名
    const callee = isString(node.callee) ? node.callee : helper(node.callee);
    
    // 纯
    if (pure) {
        push(PURE_ANNOTATION);
    }
    
    // 推入函数名和左括号
    push(callee + `(`, node);
    
    // 这一步可以理解为生成调用函数的参数列表
    genNodeList(node.arguments, context);
    
    // 推入右括号
    push(`)`);
}

验证代码如下:

const app = createApp({
    template: '<div  v-if="true">xxx</div>',
});

生成的代码如下:

const _Vue = Vue
const { createCommentVNode: _createCommentVNode } = _Vue

const _hoisted_1 = { key: 0 }

return function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue

    return true
      ? (_openBlock(), _createElementBlock("div", _hoisted_1, "xxx"))
      : _createCommentVNode("v-if", true)
  }
}

genObjectExpression

对象表达式指的是一个对象的键值对,例如我们使用的 @click="xxx" 最后的解释会变成 onCLick: xxx,这里的 onClick 就是一个对象表达式; 还有其他的情况,例如 v-bind 也会生成一个对象表达式;

genObjectExpression函数用来生成对象表达式,具体代码如下:

function genObjectExpression(node, context) {
    // 取出一些辅助函数
    const { push, indent, deindent, newline } = context;
    
    // 取出 properties 属性列表
    const { properties } = node;
    
    // 如果没有属性,则直接推入 {},表示空对象
    if (!properties.length) {
        push(`{}`, node);
        return;
    }
    
    // 判断是否多行,如果是多行,则需要添加缩进
    const multilines = properties.length > 1 || properties.some((p) => p.value.type !== 4);
    push(multilines ? `{` : `{ `);
    multilines && indent();
    
    // 遍历属性列表
    for (let i = 0; i < properties.length; i++) {
        const { key, value } = properties[i];
        // 生成 key
        genExpressionAsPropertyKey(key, context);
        push(`: `);
        
        // 生成 value
        genNode(value, context);
        
        // 如果不是最后一个,则推入逗号和换行
        if (i < properties.length - 1) {
            push(`,`);
            newline();
        }
    }
    
    // 如果是多行,则需要减少缩进
    multilines && deindent();
    
    // 推入右括号
    push(multilines ? `}` : ` }`);
}

验证代码如下:

const app = createApp({
    template: '<div @click="() => {}">xxx</div>'
});

生成的代码如下:

const _Vue = Vue
const {  } = _Vue

const _hoisted_1 = ["onClick"]

return function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

    return (_openBlock(), _createElementBlock("div", { onClick: () => {} }, "xxx", 8 /* PROPS */, _hoisted_1))
  }
}

genArrayExpression

数组表达式和对象表达式类似,只不过是一个数组的形式,可用于自定义指令的参数列表;

genArrayExpression函数用来生成数组表达式,具体代码如下:

function genArrayExpression(node, context) {
    // 内部是调用 genNodeListAsArray 函数
    genNodeListAsArray(node.elements, context);
}

function genNodeListAsArray(nodes, context) {
    // 判断是否多行,多行就增加缩进
    const multilines = nodes.length > 3 || nodes.some((n) => isArray(n) || !isText(n));
    context.push(`[`);
    multilines && context.indent();
    
    // 还是使用 genNodeList 来生成数据
    genNodeList(nodes, context, multilines);
    
    multilines && context.deindent();
    context.push(`]`);
}

验证代码如下:

const app = createApp({
    template: '<div v-xxx="[xxx, yyy]">{{i}}</div>',
});

生成的代码如下:

const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, openBlock: _openBlock, createElementBlock: _createElementBlock, withDirectives: _withDirectives } = _Vue

    const _directive_xxx = _resolveDirective("xxx")

    return _withDirectives((_openBlock(), _createElementBlock("div", null, [
      _createTextVNode(_toDisplayString(i), 1 /* TEXT */)
    ])), [
      [_directive_xxx, [xxx, yyy]]
    ])
  }
}

genFunctionExpression

函数表达式指的是需要用函数来处理的情况,表示内容已经是不可控了,例如 v-for、插槽等不可控的内容; v-if 并不会生成函数表达式,因为 v-if 只是一个条件,最后会生成一个三元表达式;

genFunctionExpression函数用来生成函数表达式,具体代码如下:

function genFunctionExpression(node, context) {
    // 辅助函数
    const {push, indent, deindent} = context;
    
    // 节点的一些属性
    const {params, returns, body, newline, isSlot} = node;
    
    // 如果是插槽函数,添加 _withCtx 辅助函数
    if (isSlot) {
        push(`_${helperNameMap[WITH_CTX]}(`);
    }
    
    // 推入函数的左括号,箭头函数的开头
    push(`(`, node);
    
    // 如果参数是一个数组,则递归生成参数列表
    if (isArray(params)) {
        genNodeList(params, context);
    }
    
    // 否则就使用 genNode 生成参数
    else if (params) {
        genNode(params, context);
    }
    
    // 推入函数的右括号,箭头函数的结尾
    push(`) => `);
    if (newline || body) {
        push(`{`);
        indent();
    }
    
    // 如果有返回值,则生成返回值
    if (returns) {
        // 如果需要换行,输出 return
        // 箭头函数如果没有花括号,可以直接返回,所以这里需要添加 return
        if (newline) {
            push(`return `);
        }
        
        // 如果返回值是数组,调用 genNodeListAsArray 生成返回值列表的代码
        if (isArray(returns)) {
            genNodeListAsArray(returns, context);
        }
        
        // 否则就调用 genNode 生成返回值
        else {
            genNode(returns, context);
        }
    } 
    
    // 没有返回值,但存在函数体
    else if (body) {
        genNode(body, context);
    }
    
    // 如果需要换行,添加换行和缩进
    if (newline || body) {
        deindent();
        push(`}`);
    }
    
    // 插槽函数,输出插槽函数的结束符号
    if (isSlot) {
        push(`)`);
    }
}

验证代码如下:

const app = createApp({
    template: '<div v-for="i in 10">{{i}}</div>',
});

生成的代码如下:

const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString } = _Vue

    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(10, (i) => {
      return (_openBlock(), _createElementBlock("div", null, _toDisplayString(i), 1 /* TEXT */))
    }), 256 /* UNKEYED_FRAGMENT */))
  }
}

genConditionalExpression

条件表达式指的是 v-if、v-else-if、v-else 等条件语句;

genConditionalExpression函数用来生成条件表达式,具体代码如下:

function genConditionalExpression(node, context) {
    // 各种情况的属性
    const { test, consequent, alternate, newline: needNewline } = node;
    
    // 辅助函数
    const { push, indent, deindent, newline } = context;
    
    // 如果测试条件是一个字符串文本
    if (test.type === 4) {
        // 如果字符串内容不是一个简单的标识符,就需要加括号
        const needsParens = !isSimpleIdentifier(test.content);
        needsParens && push(`(`);
        
        // 上面的 genExpression 函数就是用来生成表达式的
        genExpression(test, context);
        needsParens && push(`)`);
    } 
    
    // 如果测试条件是一个复杂的表达式
    else {
      push(`(`);
      // 递归调用 genNode 函数
      genNode(test, context);
      push(`)`);
    }
    
    // 如果需要换行,添加缩进
    needNewline && indent();
    
    // 缩进级别增加
    context.indentLevel++;
    needNewline || push(` `);
    
    // 三元表达式的问号
    push(`? `);
    
    // 递归调用 genNode 函数,这是条件为真的情况
    genNode(consequent, context);
    
    // 缩进级别减少
    context.indentLevel--;
    needNewline && newline();
    needNewline || push(` `);
    
    // 三元表达式的冒号
    push(`: `);
    
    // 如果是嵌套的条件表达式,需要添加缩进级别
    const isNested = alternate.type === 19;
    if (!isNested) {
      context.indentLevel++;
    }
    
    // 这里是条件为假的情况
    genNode(alternate, context);
    
    // 如果是嵌套的条件表达式,需要减少缩进级别
    if (!isNested) {
      context.indentLevel--;
    }
    
    // 如果需要换行,执行减少缩进的操作(不带换行)
    needNewline && deindent(
      true
      /* without newline */
    );
}

验证代码如下:

const app = createApp({
    template: '<div v-if="true">xxx</div>',
});

生成的代码如下:

const _Vue = Vue
const { createCommentVNode: _createCommentVNode } = _Vue

const _hoisted_1 = { key: 0 }

return function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue

    return true
      ? (_openBlock(), _createElementBlock("div", _hoisted_1, "123"))
      : _createCommentVNode("v-if", true)
  }
}

genCacheExpression

缓存表达式主要使用于v-once指令,这个指令会将节点缓存起来,不会再进行更新;

genCacheExpression函数用来生成缓存表达式,具体代码如下:

function genCacheExpression(node, context) {
    // 辅助函数
    const {push, helper, indent, deindent, newline} = context;
    
    // 这里是生成缓存的 key
    push(`_cache[${node.index}] || (`);
    
    // 如果是 vnode 节点,则需要添加缩进和设置追踪标记
    // 这里设置为 -1,表示不需要追踪
    if (node.isVNode) {
        indent();
        push(`${helper(SET_BLOCK_TRACKING)}(-1),`);
        newline();
    }
    
    // 设置缓存的值
    push(`_cache[${node.index}] = `);
    
    // 还是递归调用 genNode 函数来生成缓存的值
    genNode(node.value, context);
    
    // 启用缓存的追踪标记,并返回缓存的值
    if (node.isVNode) {
        push(`,`);
        newline();
        push(`${helper(SET_BLOCK_TRACKING)}(1),`);
        newline();
        push(`_cache[${node.index}]`);
        deindent();
    }
    
    // 最后推入右括号
    push(`)`);
}

验证代码如下:

const app = createApp({
    template: '<div v-once>xxx</div>',
});

生成的代码如下:

const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { setBlockTracking: _setBlockTracking, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, createElementVNode: _createElementVNode } = _Vue

    return _cache[0] || (
      _setBlockTracking(-1),
      _cache[0] = _createElementVNode("div", null, [
        _createTextVNode(_toDisplayString("xxx"), 1 /* TEXT */)
      ]),
      _setBlockTracking(1),
      _cache[0]
    )
  }
}

genNodeList

本质上genNodeList函数是处理所有 list 类型的数据,在上述的很多函数中都会使用到; 本案例为了能更清晰的看到它的作用,能在genNode中看到它的调用,会使用 v-for + v-memo 来进行验证; v-memo 指令的作用不在本文章中进行讲解,需要的可以自行查阅资料;

genNodeList函数用来生成节点列表,具体代码如下:

function genNodeList(nodes, context, multilines = false, comma = true) {
    // 辅助函数
    const {push, newline} = context;
    
    // 直接遍历节点
    for (let i = 0; i < nodes.length; i++) {
        
        // 获取当前节点
        const node = nodes[i];
        
        // 如果是字符串,则直接推入
        if (isString(node)) {
            push(node);
        } 
        
        // 如果是数组,则递归调用 genNodeListAsArray 函数
        // genNodeListAsArray 函数在上面已经出现过了,这里就不再赘述了
        else if (isArray(node)) {
            genNodeListAsArray(node, context);
        } 
        
        // 否则就递归调用 genNode 函数
        else {
            genNode(node, context);
        }
        
        // 如果不是最后一个
        if (i < nodes.length - 1) {
            
            // 如果是多行,则推入逗号和换行
            if (multilines) {
                comma && push(",");
                newline();
            } 
            
            // 否则就推入逗号和空格
            else {
                comma && push(", ");
            }
        }
    }
}

验证代码如下:

const app = createApp({
    template: '<div v-for="i in 10" v-memo="[i]">i</div>',
});

生成的代码如下:

const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createTextVNode: _createTextVNode, isMemoSame: _isMemoSame, withMemo: _withMemo } = _Vue

    return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(10, (i, __, ___, _cached) => {
      const _memo = ([i])
      if (_cached && _isMemoSame(_cached, _memo)) return _cached
      const _item = (_openBlock(), _createElementBlock("div", null, [
        _createTextVNode("i")
      ]))
      _item.memo = _memo
      return _item
    }, _cache, 0), 256 /* UNKEYED_FRAGMENT */))
  }
}

总结

本章节主要讲解了genNode函数,这个函数是用来生成节点的,这个函数会根据节点的类型来调用不同的函数来生成节点,例如文本节点、表达式节点、插值节点等等;

而通过这一章也了解到了巨量的Vue的模版语法可以书写的方式,其中的很多细节也只有翻源码才知道原来还可以这样玩;

到这里我们的模板到AST的转换,在从AST的转换到render函数的代码生成,已经全部完成了;

这一块足足花了五章的内容来进行分析,信息量是巨大的,同时也算是完成了一个里程碑,收获满满,成就感也满满;

历史章节