{"componentChunkName":"component---src-templates-blog-post-js","path":"/react-lazy/","result":{"data":{"site":{"siteMetadata":{"title":"😏","author":"thoamsy"}},"markdownRemark":{"id":"6db3d9de-fb68-524f-b66a-62f1a582b447","html":"<p><strong>NOTE: 8 月 15 号重写了内容，之前的描述有点太迷，完全没有逻辑性</strong></p>\n<p>以下分析基于 16.8</p>\n<p><code class=\"language-text\">React Lazy</code> 的实现其实是内部维护是一个新的对象，拥有四种状态，分别对应 Promise 的。它们是一个 enum，从 0-2 分别对应：\n<code class=\"language-text\">0: Pending</code>\n<code class=\"language-text\">1: Resolved</code>\n<code class=\"language-text\">2: Rejected</code></p>\n<p>我们可以看一下 React 针对普通 Component 和 Lazy Component 上实现的区别：就是如何 resolve Component。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">case</span> ClassComponent<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> Component <span class=\"token operator\">=</span> failedUnitOfWork<span class=\"token punctuation\">.</span>type<span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">isLegacyContextProvider</span><span class=\"token punctuation\">(</span>Component<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">popLegacyContext</span><span class=\"token punctuation\">(</span>failedUnitOfWork<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">case</span> ClassComponentLazy<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> Component <span class=\"token operator\">=</span> <span class=\"token function\">getResultFromResolvedThenable</span><span class=\"token punctuation\">(</span>failedUnitOfWork<span class=\"token punctuation\">.</span>type<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">isLegacyContextProvider</span><span class=\"token punctuation\">(</span>Component<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">popLegacyContext</span><span class=\"token punctuation\">(</span>failedUnitOfWork<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n   <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>ClassComponent 可以直接通过 type 这个字段拿到，这个字段其实就是 React Element 被创建时，<code class=\"language-text\">&lt;A /&gt;</code> 里的这个 A。而 lazy 中的 <code class=\"language-text\">getResultFromResolvedThenable</code> 就是读取这个参数的 <code class=\"language-text\">_reactResult</code> 对象。</p>\n<p>而 <code class=\"language-text\">getResultFromResolvedThenable</code> 其实实现十分简单。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">getResultFromResolvedThenable</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">thenable</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> thenable<span class=\"token punctuation\">.</span>_reactResult<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>因为可以确认这里的 thenable 就是 lazy 的返回值，所以 <code class=\"language-text\">_reactResult</code> 应该是在实现的时候，被插进的一个变量。这里包含了 Component 函数。</p>\n<p>接下来我们搜索 <code class=\"language-text\">_reactResult</code> 出现在哪个地方，找了下面代码。它实现了如何将 lazy 组件 resolve 成最终 Component。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> readLazyComponentType<span class=\"token operator\">&lt;</span><span class=\"token constant\">T</span><span class=\"token operator\">></span><span class=\"token punctuation\">(</span>thenable<span class=\"token operator\">:</span> Thenable<span class=\"token operator\">&lt;</span><span class=\"token constant\">T</span><span class=\"token operator\">></span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token constant\">T</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> status <span class=\"token operator\">=</span> thenable<span class=\"token punctuation\">.</span>_reactStatus<span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">switch</span> <span class=\"token punctuation\">(</span>status<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">case</span> Resolved<span class=\"token operator\">:</span>\n      <span class=\"token keyword\">const</span> Component<span class=\"token operator\">:</span> <span class=\"token constant\">T</span> <span class=\"token operator\">=</span> thenable<span class=\"token punctuation\">.</span>_reactResult<span class=\"token punctuation\">;</span>\n      <span class=\"token keyword\">return</span> Component<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">case</span> Rejected<span class=\"token operator\">:</span>\n      <span class=\"token keyword\">throw</span> thenable<span class=\"token punctuation\">.</span>_reactResult<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">case</span> Pending<span class=\"token operator\">:</span>\n      <span class=\"token keyword\">throw</span> thenable<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">default</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      thenable<span class=\"token punctuation\">.</span>_reactStatus <span class=\"token operator\">=</span> Pending<span class=\"token punctuation\">;</span>\n      thenable<span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span>\n        <span class=\"token parameter\">resolvedValue</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>thenable<span class=\"token punctuation\">.</span>_reactStatus <span class=\"token operator\">===</span> Pending<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            thenable<span class=\"token punctuation\">.</span>_reactStatus <span class=\"token operator\">=</span> Resolved<span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span> resolvedValue <span class=\"token operator\">===</span> <span class=\"token string\">'object'</span> <span class=\"token operator\">&amp;&amp;</span> resolvedValue <span class=\"token operator\">!==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n              <span class=\"token comment\">// If the `default` property is not empty, assume it's the result</span>\n              <span class=\"token comment\">// of an async import() and use that. Otherwise, use the</span>\n              <span class=\"token comment\">// resolved value itself.</span>\n              <span class=\"token keyword\">const</span> defaultExport <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>resolvedValue<span class=\"token operator\">:</span> any<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>default<span class=\"token punctuation\">;</span>\n              resolvedValue <span class=\"token operator\">=</span> defaultExport <span class=\"token operator\">??</span> resolvedValue<span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n              resolvedValue <span class=\"token operator\">=</span> resolvedValue<span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n            thenable<span class=\"token punctuation\">.</span>_reactResult <span class=\"token operator\">=</span> resolvedValue<span class=\"token punctuation\">;</span>\n          <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        <span class=\"token parameter\">error</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>thenable<span class=\"token punctuation\">.</span>_reactStatus <span class=\"token operator\">===</span> Pending<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            thenable<span class=\"token punctuation\">.</span>_reactStatus <span class=\"token operator\">=</span> Rejected<span class=\"token punctuation\">;</span>\n            thenable<span class=\"token punctuation\">.</span>_reactResult <span class=\"token operator\">=</span> error<span class=\"token punctuation\">;</span>\n          <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token keyword\">throw</span> thenable<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>可以看到上面的代码中出现了 3 次 throw。分别出现在了 <code class=\"language-text\">Rejected, Pending, Default</code> 三个 case 里。我们先关注 default 的逻辑，里面会猜测 resolve 的值是否含有 default，如果是的话，说明可能是 async import 的返回值，那么就直接将 default 给 resolve 出来，否则返回自身。接着就会把它写入到 <code class=\"language-text\">_reactResult</code> 中。</p>\n<p>那么为什么这些地方都要 throw 它呢？这里就是 Suspense 的逻辑了，Suspense 会 catch 组被 throw 出来的 error，如果它是一个 Promise 的话，就会将 children 都渲染成 fallback 的值，一旦 Promise 被 resolve 则会继续渲染一次，并得到最终的结果。\n所以这里 default 和 Pending 都是针对 Suspense 的场景。而 Rejected 就没啥好说了，出了异常当然要抛出去。</p>\n<p>最后看一下 React.lazy 的实现。它放回的就是一个 thenable 对象，字段和上面的处理逻辑是一致的。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> lazy<span class=\"token operator\">&lt;</span><span class=\"token constant\">T</span><span class=\"token punctuation\">,</span> <span class=\"token constant\">R</span><span class=\"token operator\">></span><span class=\"token punctuation\">(</span><span class=\"token function-variable function\">ctor</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> Thenable<span class=\"token operator\">&lt;</span><span class=\"token constant\">T</span><span class=\"token punctuation\">,</span> <span class=\"token constant\">R</span><span class=\"token operator\">></span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">let</span> thenable <span class=\"token operator\">=</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">resolve<span class=\"token punctuation\">,</span> reject</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>thenable <span class=\"token operator\">===</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token comment\">// Lazily create thenable by wrapping in an extra thenable.</span>\n        thenable <span class=\"token operator\">=</span> <span class=\"token function\">ctor</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        ctor <span class=\"token operator\">=</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n      <span class=\"token keyword\">return</span> thenable<span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span>resolve<span class=\"token punctuation\">,</span> reject<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token comment\">// React uses these fields to store the result.</span>\n    _reactStatus<span class=\"token operator\">:</span> <span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span>\n    _reactResult<span class=\"token operator\">:</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>因为 status 默认值是 -1 也就会进入 👆 的 default 逻辑，运行 ctor，开始尝试解析 lazy 中的组件。这也就是 lazy 的实现原理。</p>","frontmatter":{"title":"React Lazy 的实现原理","spoiler":"简单讨论一下 React.lazy 内部实现","image":null,"date":"2018/12/23"}}},"pageContext":{"slug":"/react-lazy/","previous":{"fields":{"slug":"/browser/"},"frontmatter":{"title":"Inside look at modern web browser（翻译）","image":null,"spoiler":"谷歌搬砖"}},"next":{"fields":{"slug":"/react-trap/"},"frontmatter":{"title":"一些 React 中的刁钻问题","image":null,"spoiler":"收集一些不为人所知的，React 的细节问题。帮助自己理顺 React 内部深层的逻辑"}}}}}