<div style="font-size:16px;">
<div class="pgc-img">
<img alt="0ec7693ad636b68f6a6edf7824a0b1ec.png" src="https://beijingoptbbs.oss-cn-beijing.aliyuncs.com/cs/5606289-3ff75f7d7d20df5d54a03a9a3078675c.png">
</div>
<p class="pgc-end-literature">作者:HcySunYang</p>
<p class="pgc-end-literature">转发链接:https://mp.weixin.qq.com/s/cbLm56UcoV6DQI2jBMM8YQ</p>
<h1 class="pgc-h-arrow-right">目录</h1>
<p>教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(上)</p>
<p>教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(下)本篇</p>
<h1 class="pgc-h-arrow-right">预字符串化</h1>
<p>静态提升的 VNode 节点或节点树本身是静态的,那么能否将其预先字符串化呢?如下模板所示:</p>
<pre class="blockcode"><code></code></pre>
<div>
<code> </code>
</div>
<code></code>
<p>假设如上模板中有大量连续的静态的 p 标签,当采用了 hoist 优化时,结果如下:</p>
<pre class="blockcode"><code>cosnt hoist1 = createVNode('p', null, null, PatchFlags.HOISTED)cosnt hoist2 = createVNode('p', null, null, PatchFlags.HOISTED)... 20 个 hoistx 变量cosnt hoist20 = createVNode('p', null, null, PatchFlags.HOISTED)render() { return (openBlock(), createBlock('div', null, [ hoist1, hoist2, ...20 个变量, hoist20 ]))}</code></pre>
<p>预字符串化会将这些静态节点序列化为字符串并生成一个 Static 类型的 VNode:</p>
<pre class="blockcode"><code>const hoistStatic = createStaticVNode('</code></pre>
<p>这有几个明显的优势:</p>
<ul><li>生成代码的体积减少</li><li>减少创建 VNode 的开销</li><li>减少内存占用</li></ul>
<p>静态节点在运行时会通过 innerHTML 来创建真实节点,因此并非所有静态节点都是可以预字符串化的,可以预字符串化的静态节点需要满足以下条件:</p>
<ul><li>非表格类标签:caption 、thead、tr、th、tbody、td、tfoot、colgroup、col</li><li>标签的属性必须是:</li><li>标准 HTML attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes</li><li>或 data-/aria- 类属性</li></ul>
<p>当一个节点满足这些条件时代表这个节点是可以预字符串化的,但是如果只有一个节点,那么并不会将其字符串化,可字符串化的节点必须连续且达到一定数量才行:</p>
<ul><li>如果节点没有属性,那么必须有连续 <strong>20</strong> 个及以上的静态节点存在才行,例如:
<div></div></li></ul>
<p>或者在这些连续的节点中有 <strong>5</strong> 个及以上的节点是有属性绑定的节点:</p>
<pre class="blockcode"><code></code></pre>
<p>这段节点的数量虽然没有达到 20 个,但是满足 5 个节点有属性绑定。</p>
<p>这些节点不一定是兄弟关系,父子关系也是可以的,只要阈值满足条件即可,例如:</p>
<pre class="blockcode"><code></code></pre>
<p><strong>预字符串化会在编译时计算属性的值</strong>,例如:</p>
<pre class="blockcode"><code></code></pre>
<p>在与字符串化之后:</p>
<pre class="blockcode"><code>const hoistStatic = createStaticVNode('</code></pre>
<p>可见 id 属性值是计算后的。</p>
<h1 class="pgc-h-arrow-right">Cache Event handler</h1>
<p>如下组件的模板所示:</p>
<pre class="blockcode"><code></code></pre>
<p>这段模板如果手写渲染函数的话相当于:</p>
<pre class="blockcode"><code>render(ctx) { return h(Comp, { onChange: () => (ctx.a + ctx.b) })}</code></pre>
<p>很显然,每次 render 函数执行的时候,Comp 组件的 props 都是新的对象,onChange 也会是全新的函数。这会导致触发 Comp 组件的更新。</p>
<p>当 Vue3 Compiler 开启 prefixIdentifiers 以及 cacheHandlers 时,这段模板会被编译为:</p>
<pre class="blockcode"><code>render(ctx, cache) { return h(Comp, { onChange: cache[0] || (cache[0] = ($event) => (ctx.a + ctx.b)) })}</code></pre>
<p>这样即使多次调用渲染函数也不会触发 Comp 组件的更新,因为 Vue 在 patch 阶段比对 props 时就会发现 onChange 的引用没变。</p>
<p>如上代码中 render 函数的 cache 对象是 Vue 内部在调用渲染函数时注入的一个数组,像下面这种:</p>
<pre class="blockcode"><code>render.call(ctx, ctx, [])</code></pre>
<p>实际上,我们即使不依赖编译也能手写出具备 cache 能力的代码:</p>
<pre class="blockcode"><code>const Comp = { setup() { // 在 setup 中定义 handler const handleChange = () => {/* ... */} return () => { return h(AnthorComp, { onChange: handleChange // 引用不变 }) } }}</code></pre>
<p>因此我们最好不要写出如下这样的代码:</p>
<pre class="blockcode"><code>const Comp = { setup() { return () => { r |
|