<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>前端這件小事 Archives - 成長駭客交流第一站 - HyperGrowths™</title>
	<atom:link href="https://hypergrowths.com/tag/%E5%89%8D%E7%AB%AF%E9%80%99%E4%BB%B6%E5%B0%8F%E4%BA%8B/feed/" rel="self" type="application/rss+xml" />
	<link>https://hypergrowths.com/tag/前端這件小事/</link>
	<description>用SEO內容行銷加速增長? 企業發展遇到增長瓶頸？加入 HyperGrowths，學習突破性增長策略，優化行銷方案，助力企業飛躍式發展</description>
	<lastBuildDate>Sat, 30 Jan 2021 18:30:21 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.3.5</generator>

<image>
	<url>https://hypergrowths.com/wp-content/uploads/2020/11/cropped-?.png</url>
	<title>前端這件小事 Archives - 成長駭客交流第一站 - HyperGrowths™</title>
	<link>https://hypergrowths.com/tag/前端這件小事/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>小程序長列表渲染優化另一種解決方案</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/15080/topic-146791824/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 18:30:21 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/15080/topic-146791824/</guid>

					<description><![CDATA[<p>前言有些需求需要展示長列表，無限下拉都會一直顯示出更多的數據。但是當一個頁面展示的DOM節點過多的時候，會造成小程序頁面的卡頓嚴重的會直接白屏。原因有以下幾點： 列表數據很大，不斷獲取下一屏的數據，set…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/15080/topic-146791824/" data-wpel-link="internal">小程序長列表渲染優化另一種解決方案</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">小程序長列表渲染優化另一種解決方案</h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic1.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2><b>前言</b></h2>
<p>有些需求需要展示長列表，無限下拉都會一直顯示出更多的數據。但是當一個頁面展示的DOM節點過多的時候，會造成小程序頁面的<b>卡頓嚴重的會直接白屏</b>。</p>
<p>原因有以下幾點：</p>
<ul>
<li>列表數據很大，不斷獲取下一屏的數據，setData的數據越來越多的時候耗時高</li>
<li>渲染DOM 結構多，每次setData 都需要創建新的虛擬- 樹、和舊樹diff 操作耗時都比較高</li>
<li>DOM 結構多，佔用的內存高，造成頁面被系統回收的概率變大，會白屏</li>
</ul>
<p>針對這個場景，小程序官方已經有一個<a href="https://link.zhihu.com/?target=https%3A//github.com/wechat-miniprogram/recycle-view" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">解決方案</a>recycle-view：但是使用之後，我發現了很多問題，比如<b>下一頁的頁面渲染不完整，或者拉取下一頁的數據會閃屏。</b></p>
<figure data-size="small"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-afb67e3830a4f1a9060a2d967150613d_r.jpg" data-caption="" data-size="small" data-rawwidth="862" data-rawheight="1802" class="origin_image zh-lightbox-thumb" width="862" data-original="https://pic2.zhimg.com/v2-afb67e3830a4f1a9060a2d967150613d_b.jpg" title="v2-afb67e3830a4f1a9060a2d967150613d_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-afb67e3830a4f1a9060a2d967150613d_r.jpg" data-caption="" data-size="small" data-rawwidth="862" data-rawheight="1802" class="origin_image zh-lightbox-thumb lazy" width="862" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='862'%20height='1802'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-afb67e3830a4f1a9060a2d967150613d_b.jpg" title="v2-afb67e3830a4f1a9060a2d967150613d_r"></figure>
<figure data-size="small"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-448d0c7be7e0ca473c6d2314c6f2e7ca_r.jpg" data-caption="" data-size="small" data-rawwidth="760" data-rawheight="1564" class="origin_image zh-lightbox-thumb" width="760" data-original="https://pic3.zhimg.com/v2-448d0c7be7e0ca473c6d2314c6f2e7ca_b.jpg" title="v2-448d0c7be7e0ca473c6d2314c6f2e7ca_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-448d0c7be7e0ca473c6d2314c6f2e7ca_r.jpg" data-caption="" data-size="small" data-rawwidth="760" data-rawheight="1564" class="origin_image zh-lightbox-thumb lazy" width="760" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='760'%20height='1564'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-448d0c7be7e0ca473c6d2314c6f2e7ca_b.jpg" title="v2-448d0c7be7e0ca473c6d2314c6f2e7ca_r"></figure>
<p>這些問題都已經反饋給相關開發，但是還沒有得到回复，所以我也不確定是不是我沒有用對，萬一等了半個月最後得到的結論是，官方組件不能滿足我們的場景，那就GG了。所以我只能暫時先追求另外一種解決方案了。</p>
<p>通過查看<a href="https://link.zhihu.com/?target=https%3A//github.com/wechat-miniprogram/recycle-view" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">官方文檔</a>跟組件代碼，可以看到他們的實現思路是這樣的：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-3226c0a9b7158d45248c91ca97c3ca6b_r.jpg" data-caption="" data-size="normal" data-rawwidth="1864" data-rawheight="708" class="origin_image zh-lightbox-thumb" width="1864" data-original="https://pic4.zhimg.com/v2-3226c0a9b7158d45248c91ca97c3ca6b_b.jpg" title="v2-3226c0a9b7158d45248c91ca97c3ca6b_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-3226c0a9b7158d45248c91ca97c3ca6b_r.jpg" data-caption="" data-size="normal" data-rawwidth="1864" data-rawheight="708" class="origin_image zh-lightbox-thumb lazy" width="1864" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1864'%20height='708'&gt;&lt;/svg&gt;" data-actualsrc="https://pic4.zhimg.com/v2-3226c0a9b7158d45248c91ca97c3ca6b_b.jpg" title="v2-3226c0a9b7158d45248c91ca97c3ca6b_r"></figure>
<p>由此我猜想，為什麼會出現渲染不完的情況，應該是由於它需要靠著用戶提供的item的高度來算哪些item需要渲染，然後來計算應該渲染出來的屏幕高度。那如果這個計算渲染屏幕高度偏少，就會有渲染不完的情況，計算的高度偏多，又會有渲染出空白的情況。當然我沒有去調試它的代碼，畢竟看別人的代碼是痛苦的，只是瞎猜一通而已。</p>
<p>既然我猜他是因為高度的問題，才出現那麼多問題，同時依賴用戶提供高度我覺得總是不靠譜，萬一他給錯了，就會使得渲染有問題。可不可以不要知道item的高度也可以知道哪些元素被渲染出來呢？</p>
<p>答案是可以的。</p>
<p>我們可以以一屏為一個單位，而不是以一個item為一個單位，這樣我無需開發者給我提供他的高度。我自己去記錄每一屏的高度，然後onscroll的時候，根據scollTop來計算當前應該渲染哪一屏，我把它首尾兩屏的元素也一起加起來算是我總的需要渲染元素。</p>
<p>這一套方案我已經用在了自己的項目：公眾號視頻頻道上。有興趣的小伙伴可以體驗一下，入口是：微信-&gt;訂閱號助手-&gt;常讀的訂閱號第一個入口點入即可。 （不過該功能還在灰度中，灰度到的朋友歡迎反饋～沒有被灰度到的朋友再等等）</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-d6c4f856b6f9305bc3d9d69cf6a4593c_r.jpg" data-caption="" data-size="normal" data-rawwidth="2010" data-rawheight="1742" class="origin_image zh-lightbox-thumb" width="2010" data-original="https://pic1.zhimg.com/v2-d6c4f856b6f9305bc3d9d69cf6a4593c_b.jpg" title="v2-d6c4f856b6f9305bc3d9d69cf6a4593c_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-d6c4f856b6f9305bc3d9d69cf6a4593c_r.jpg" data-caption="" data-size="normal" data-rawwidth="2010" data-rawheight="1742" class="origin_image zh-lightbox-thumb lazy" width="2010" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2010'%20height='1742'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-d6c4f856b6f9305bc3d9d69cf6a4593c_b.jpg" title="v2-d6c4f856b6f9305bc3d9d69cf6a4593c_r"></figure>
<p>有人可能會說：如果我是一屏拉取所有的數據而不是分屏，那你這個方法就不可行了。確實是這樣的，但是我覺得應該沒有什麼場景需要一次性拉取所有的數據。原因如下：<br /> 1、一屏你拉取所有節點，用戶根本看不完，所以意義不大<br />2、數據太大，造成網絡傳輸慢<br />3、setState也慢造成首屏慢我感覺沒啥好處，壞處倒是挺多的。<b>所以不太建議一屏直接返回所有數據。</b></p>
<h2>實現思路：</h2>
<p>這裡我們通過改造一個通過分屏無限下拉長列表來說一下整個的實現思路。 <a href="https://link.zhihu.com/?target=https%3A//developers.weixin.qq.com/s/I9KsIKmo7yhz" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">developers.weixin.qq.com</span> <span class="invisible">/s/I9KsIKmo7yhz</span></a></p>
<figure data-size="small"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-c5163708ab58272ad964ae6f5dfe8abf_r.jpg" data-caption="" data-size="small" data-rawwidth="754" data-rawheight="1338" class="origin_image zh-lightbox-thumb" width="754" data-original="https://pic4.zhimg.com/v2-c5163708ab58272ad964ae6f5dfe8abf_b.jpg" title="v2-c5163708ab58272ad964ae6f5dfe8abf_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-c5163708ab58272ad964ae6f5dfe8abf_r.jpg" data-caption="" data-size="small" data-rawwidth="754" data-rawheight="1338" class="origin_image zh-lightbox-thumb lazy" width="754" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='754'%20height='1338'&gt;&lt;/svg&gt;" data-actualsrc="https://pic4.zhimg.com/v2-c5163708ab58272ad964ae6f5dfe8abf_b.jpg" title="v2-c5163708ab58272ad964ae6f5dfe8abf_r"></figure>
<p>這是一個長列表，它一屏獲取20個數據，由於元素都長的一樣，所以我用文案標明當前渲染的第幾個元素。<b>特別說明一下：由於我每一個item只渲染一個元素，並且渲染的數據也很簡單，所以不會有我說的那些卡頓，白屏的問題。但是後面的實踐可以用戶複雜的項目中</b>。</p>
<p>我的實現方案分為下面兩步：<br /> 1、將渲染列表的數組list改成二維數組。<br /> 2、只渲染當然可視區域的那一屏以及它前後一屏的元素。其他用空白div佔位要做到第二步，我們還需要分成三小步</p>
<ul>
<li>需要知道每一屏的高度，這樣我們才能給這個佔位的空白div元素設置高度。</li>
<li>渲染下一屏last的數據，除了保留last，以及last-1那一屏的渲染，其他節點應該為空。</li>
<li>如果是獲取非最後一屏幕的，通過監聽onscrollTop 獲取到scrollTop的值，算取當前應該渲染哪一屏，在重新組裝數據setState</li>
</ul>
<h3>第一步</h3>
<p><b>1、將渲染列表的數組list改成二維數組</b></p>
<p>為什麼要改成二維數組：</p>
<ul>
<li>因為我們是以一屏的數據為單位的，所以用二維數組來裝每一屏的數據，方便以屏為單位渲染元素。</li>
<li>性能優化</li>
</ul>
<p>原來我們是不停的獲取到新一屏的數據就不斷的concat進來，再去setState。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-beb0b3e9184f84594b92d31540c80561_r.jpg" data-caption="" data-size="normal" data-rawwidth="1256" data-rawheight="284" class="origin_image zh-lightbox-thumb" width="1256" data-original="https://pic2.zhimg.com/v2-beb0b3e9184f84594b92d31540c80561_b.jpg" title="v2-beb0b3e9184f84594b92d31540c80561_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-beb0b3e9184f84594b92d31540c80561_r.jpg" data-caption="" data-size="normal" data-rawwidth="1256" data-rawheight="284" class="origin_image zh-lightbox-thumb lazy" width="1256" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1256'%20height='284'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-beb0b3e9184f84594b92d31540c80561_b.jpg" title="v2-beb0b3e9184f84594b92d31540c80561_r"></figure>
<p>這樣的話，到後期這個finalArr會越來越大，由於setData的調用會把數據從邏輯層傳到渲染層，數據太大會增加通信時間。在性能不好的機型上，setState就會佔用很長的時間，從而造成頁面卡頓。</p>
<p>而我們通過改成二維數組，二維數組裡的每一個子數組都用來裝一屏的數據，然後每次只setState當前的下一屏幕的數據，就可以減少這個通信時間。</p>
<p>說了那麼多，來看看具體如何操作。</p>
<p>首先需要給每一屏一個索引值index來來表示當前是第幾屏，這裡我們用wholePageIndex來表示。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">onLoad</span><span class="o">:</span><span class="kd">function</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="kr">const</span><span class="nx">arr</span><span class="o">=</span><span class="p">[</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">}</span><span class="p">]</span><span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span><span class="p">[</span><span class="s1">'list['</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="s1">']'</span><span class="p">]</span><span class="o">:</span><span class="nx">arr</span><span class="p">})</span><span class="p">},</span></code></pre>
</div>
<p>然後setData的時候，只是將當前這一屏幕的數據賦值。同理，下拉刷新之後獲取渲染下一屏的數據也一樣，只是需要給wholePageIndex加1表示是下一屏，在賦值即可。</p>
<div class="highlight">
<pre><code class="language-js"><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span><span class="p">[</span><span class="s1">'list['</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="s1">']'</span><span class="p">]</span><span class="o">:</span><span class="nx">arr</span><span class="p">})</span></code></pre>
</div>
<p>完成後的代碼如下： <a href="https://link.zhihu.com/?target=https%3A//developers.weixin.qq.com/s/LSQsSKmj7bhb" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">developers.weixin.qq.com</span> <span class="invisible">/s/LSQsSKmj7bhb</span></a><br />這樣我們就可以看到，整個渲染是以屏為單位來渲染，比如當前渲染了三屏，那麼dom的結構如下：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-2bb0c3f4e243e841bace9f855cd498fe_r.jpg" data-caption="" data-size="normal" data-rawwidth="2678" data-rawheight="980" class="origin_image zh-lightbox-thumb" width="2678" data-original="https://pic3.zhimg.com/v2-2bb0c3f4e243e841bace9f855cd498fe_b.jpg" title="v2-2bb0c3f4e243e841bace9f855cd498fe_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-2bb0c3f4e243e841bace9f855cd498fe_r.jpg" data-caption="" data-size="normal" data-rawwidth="2678" data-rawheight="980" class="origin_image zh-lightbox-thumb lazy" width="2678" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2678'%20height='980'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-2bb0c3f4e243e841bace9f855cd498fe_b.jpg" title="v2-2bb0c3f4e243e841bace9f855cd498fe_r"></figure>
<p class="ztext-empty-paragraph"></p>
<h3>第二步</h3>
<p><b>2、只渲染當然可視區域的那一屏以及它前後一屏的元素。其他用空白div佔位來省去渲染過多DOM節點造成的白屏問題</b></p>
<p>這裡我做了第一屏，或者最後一屏，渲染兩屏的數據即可。因為首屏沒有上面一屏，最後一屏沒有下一屏。</p>
<p>前面說了要做到這一步，我們還得拆分成好幾步：</p>
<ul>
<li>需要知道每一屏的高度，這樣我們才能給這個佔位的空白div元素設置高度。</li>
<li>渲染下一屏last的數據，除了保留last，以及last-1那一屏的渲染，其他節點應該為空。</li>
<li>如果是獲取非最後一屏幕的，通過監聽onscrollTop 獲取到scrollTop的值，算取當前應該渲染哪一屏，在重新組裝數據setState。</li>
</ul>
<p>在開始工作之前，先定義幾個變量一會使用。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">wholeVideoList</span><span class="o">:</span><span class="nx">用来装所有屏的数据</span><span class="nx">currentRenderIndex</span><span class="o">:</span><span class="nx">当前正在渲染哪一屏</span><span class="nx">pageHeightArr</span><span class="o">:</span><span class="nx">用来装每一屏的高度</span><span class="nx">windowHeight</span><span class="o">:</span><span class="nx">当前屏幕的高度</span></code></pre>
</div>
<p>在onLoad裡定義：</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">onLoad</span><span class="o">:</span><span class="kd">function</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">wholeVideoList</span><span class="o">=</span><span class="p">[];</span><span class="c1">//用來裝所有屏的數據</span><span class="k">this</span><span class="p">.</span><span class="nx">currentRenderIndex</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="c1">//當前正在渲染哪一屏</span><span class="k">this</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="o">=</span><span class="p">[];</span><span class="c1">//用來裝每一屏的高度</span><span class="k">this</span><span class="p">.</span><span class="nx">windowHeight</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="c1">//當前屏幕的高度</span><span class="kr">const</span><span class="nx">arr</span><span class="o">=</span><span class="p">[</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">}</span><span class="p">]</span><span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span><span class="p">[</span><span class="s1">'list['</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="s1">']'</span><span class="p">]</span><span class="o">:</span><span class="nx">arr</span><span class="p">})</span><span class="p">}</span></code></pre>
</div>
<p>同時為了區分好每一屏，我們給每一屏的最外層的div都加一個id: <code>wrp_{{pageIndex}}</code> 。</p>
<div class="highlight">
<pre><code class="language-text">&lt;view class="page"&gt; &lt;view wx:for="{{ list }}" id="wrp_{{pageIndex}}" wx:for-index="pageIndex" wx:for-item="listSingleItem" wx:key="index"&gt; &lt;view wx:if="{{ listSingleItem.length &gt; 0 }}"&gt; &lt;view class="wrp" wx:for="{{ listSingleItem }}" wx:for-index="index" wx:for-item="listItem" wx:key="index"&gt;当前是第{{ listItem.idx }}个元素，为第{{ pageIndex }} 屏数据&lt;/view&gt; &lt;/view&gt; &lt;view wx:else style="height: {{ listSingleItem.height}}px"&gt; &lt;/view&gt; &lt;/view&gt; &lt;/view&gt;</code></pre>
</div>
<p>完成之後，效果如下：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-00c7005d7755f65dde69854f23d61f8a_r.jpg" data-caption="" data-size="normal" data-rawwidth="2350" data-rawheight="1500" class="origin_image zh-lightbox-thumb" width="2350" data-original="https://pic3.zhimg.com/v2-00c7005d7755f65dde69854f23d61f8a_b.jpg" title="v2-00c7005d7755f65dde69854f23d61f8a_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-00c7005d7755f65dde69854f23d61f8a_r.jpg" data-caption="" data-size="normal" data-rawwidth="2350" data-rawheight="1500" class="origin_image zh-lightbox-thumb lazy" width="2350" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2350'%20height='1500'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-00c7005d7755f65dde69854f23d61f8a_b.jpg" title="v2-00c7005d7755f65dde69854f23d61f8a_r"></figure>
<p>這樣，每一屏的最外層都有一個帶id的元素包裹，我們將要利用它幫助我們去獲取這一屏的高度。</p>
<p>做完預備工作之後，接下來就可以完成我們的第一步驟：獲取每一屏幕的高度，在這裡我把獲取高度放進一個setHeight的函數來完成。</p>
<p><b>注意：這一步一定要在setState之後，也就是頁面渲染完成之後完成，這樣才能獲取元素的高度。</b></p>
<p>在setHeight裡，我們通過wx.createSelectorQuery去獲取當前最新渲染這一屏的高度，將其賦值給用於記錄每一屏高度的數組：pageHeightArr。</p>
<p>獲取首屏的高度，需要在onload裡做，而獲取非首屏的高度需要在獲取下一屏數據的函數getVideoInfoData做。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">onLoad</span><span class="o">:</span><span class="kd">function</span><span class="p">()</span><span class="p">{</span><span class="c1">// ...</span><span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span><span class="p">[</span><span class="s1">'list['</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="s1">']'</span><span class="p">]</span><span class="o">:</span><span class="nx">arr</span><span class="p">},</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setHeight</span><span class="p">();</span><span class="c1">//獲取首屏高度</span><span class="p">})</span><span class="p">},</span><span class="nx">setHeight</span><span class="o">:</span><span class="kd">function</span><span class="p">()</span><span class="p">{</span><span class="kr">const</span><span class="nx">that</span><span class="o">=</span><span class="k">this</span><span class="p">;</span><span class="kr">const</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">query</span><span class="o">=</span><span class="nx">wx</span><span class="p">.</span><span class="nx">createSelectorQuery</span><span class="p">();</span><span class="k">this</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="sb">`#wrp_</span><span class="si">${</span><span class="nx">wholePageIndex</span><span class="si">}</span><span class="sb">`</span><span class="p">).</span><span class="nx">boundingClientRect</span><span class="p">()</span><span class="k">this</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">exec</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">res</span><span class="p">){</span><span class="nx">that</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="p">[</span><span class="nx">wholePageIndex</span><span class="p">]</span><span class="o">=</span><span class="nx">res</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">&amp;&amp;</span><span class="nx">res</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">height</span><span class="p">;</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'that.pageHeightArr'</span><span class="o">+</span><span class="nx">that</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="p">)</span><span class="p">})</span><span class="p">},</span><span class="nx">onReachBottom</span><span class="o">:</span><span class="kd">function</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">getVideoInfoData</span><span class="p">();</span><span class="p">},</span><span class="nx">getVideoInfoData</span><span class="o">:</span><span class="kd">function</span><span class="p">()</span><span class="p">{</span><span class="kr">const</span><span class="nx">arr</span><span class="o">=</span><span class="p">[</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">}]</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span><span class="p">[</span><span class="s1">'list['</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="s1">']'</span><span class="p">]</span><span class="o">:</span><span class="nx">arr</span><span class="p">},</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setHeight</span><span class="p">();</span><span class="c1">//獲取第二屏的高度</span><span class="p">})</span><span class="p">},</span></code></pre>
</div>
<p>經過這樣操作之後，我們就知道當前每一屏的高度為多少了，這裡我把這個pageHeightArr打印出來：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-5924079de2a5328a07e2222e8cd44871_r.jpg" data-caption="" data-size="normal" data-rawwidth="546" data-rawheight="52" class="origin_image zh-lightbox-thumb" width="546" data-original="https://pic2.zhimg.com/v2-5924079de2a5328a07e2222e8cd44871_b.png" title="v2-5924079de2a5328a07e2222e8cd44871_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-5924079de2a5328a07e2222e8cd44871_r.jpg" data-caption="" data-size="normal" data-rawwidth="546" data-rawheight="52" class="origin_image zh-lightbox-thumb lazy" width="546" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='546'%20height='52'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-5924079de2a5328a07e2222e8cd44871_b.png" title="v2-5924079de2a5328a07e2222e8cd44871_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>完成第一小步之後，接著來看第二小步：渲染下一屏last的數據，除了保留last，以及last-1那一屏的渲染，其他節點用一個空白div佔位。</p>
<p>這裡有個難點，<b>如何做到只渲染後兩屏的數據，其他用空白div佔位又不會造成閃屏呢？</b>其實也很簡單，我們前面已經記錄了每一屏的高度，所以設置空白div的高度為這個高度佔位即可。二維數組裡的子元素我們要渲染的屏幕還是用數組來表示，不渲染的屏幕我們直接用一個對象{height: xXX}來表示。這樣在wxml裡就可以根據元素是否有length屬性來判斷是要渲染真的節點還是空節點。</p>
<p>下面來實現一下:</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">getVideoInfoData</span><span class="o">:</span><span class="kd">function</span><span class="p">()</span><span class="p">{</span><span class="kr">const</span><span class="nx">arr</span><span class="o">=</span><span class="p">[</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">},</span><span class="p">{</span><span class="nx">idx</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">index</span><span class="o">++</span><span class="p">}</span><span class="p">]</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span><span class="kr">const</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="p">;</span><span class="c1">//新增代碼</span><span class="k">this</span><span class="p">.</span><span class="nx">currentRenderIndex</span><span class="o">=</span><span class="nx">wholePageIndex</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">wholeVideoList</span><span class="p">[</span><span class="nx">wholePageIndex</span><span class="p">]</span><span class="o">=</span><span class="nx">arr</span><span class="p">;</span><span class="kd">let</span><span class="nx">datas</span><span class="o">=</span><span class="p">{};</span><span class="kd">let</span><span class="nx">tempList</span><span class="o">=</span><span class="k">new</span><span class="nb">Array</span><span class="p">(</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="mi">1</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="k">if</span><span class="p">(</span><span class="nx">wholePageIndex</span><span class="o">&gt;</span><span class="mi">2</span><span class="p">)</span><span class="p">{</span><span class="nx">tempList</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">item</span><span class="p">,</span><span class="nx">index</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="nx">index</span><span class="o">&lt;</span><span class="nx">tempList</span><span class="p">.</span><span class="nx">length</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span><span class="p">{</span><span class="nx">tempList</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="nx">height</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="p">[</span><span class="nx">index</span><span class="p">]};</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="nx">tempList</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">wholeVideoList</span><span class="p">[</span><span class="nx">index</span><span class="p">];</span><span class="p">}</span><span class="p">})</span><span class="nx">datas</span><span class="p">.</span><span class="nx">list</span><span class="o">=</span><span class="nx">tempList</span><span class="p">;</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="nx">datas</span><span class="p">[</span><span class="s1">'list['</span><span class="o">+</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="s1">']'</span><span class="p">]</span><span class="o">=</span><span class="nx">arr</span><span class="p">;</span><span class="p">}</span><span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">(</span><span class="nx">datas</span><span class="p">,</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setHeight</span><span class="p">();</span><span class="p">})</span><span class="p">},</span></code></pre>
</div>
<p>首先我先把最新渲染的index賦值給表示當前正在渲染的屏幕的currentRenderIndex，同時將最新的數據賦值給裝有所有屏幕數據的wholeVideoList。 currentRenderIndex在第三步要使用，而wholeVideoList一會就會用到。</p>
<p>然後，我先同樣定義了一個數組tempList，當wholePageIndex &gt; 2時，也就是渲染到第四屏的時候就有選擇的渲染，這個不是固定的，你可以根據你的需求改。接著我們用一個循環，判斷當前是否是後兩屏，如果是，才是有數據的數組，這裡的數據就從我們剛剛賦值的wholeVideoList獲取，否則子元素直接為一個對象{ height: this.pageHeightArr[ index]}。</p>
<p>給tempList賦值完之後，setState即可，</p>
<p>完成到這裡還不夠，還需要對wxml改造一下：</p>
<div class="highlight">
<pre><code class="language-html"><span class="p">&lt;</span><span class="nt">view</span><span class="na">class</span><span class="o">=</span><span class="s">"page"</span><span class="p">&gt;</span><span class="p">&lt;</span><span class="nt">view</span><span class="na">wx:for</span><span class="o">=</span><span class="s">"{{ list }}"</span><span class="na">id</span><span class="o">=</span><span class="s">"wrp_{{pageIndex}}"</span><span class="na">wx:for-index</span><span class="o">=</span><span class="s">"pageIndex"</span><span class="na">wx:for-item</span><span class="o">=</span><span class="s">"listSingleItem"</span><span class="na">wx:key</span><span class="o">=</span><span class="s">"index"</span><span class="p">&gt;</span><span class="p">&lt;</span><span class="nt">view</span><span class="na">wx:if</span><span class="o">=</span><span class="s">"{{ listSingleItem.length &gt; 0 }}"</span><span class="p">&gt;</span><span class="p">&lt;</span><span class="nt">view</span><span class="na">class</span><span class="o">=</span><span class="s">"wrp"</span><span class="na">wx:for</span><span class="o">=</span><span class="s">"{{ listSingleItem }}"</span><span class="na">wx:for-index</span><span class="o">=</span><span class="s">"index"</span><span class="na">wx:for-item</span><span class="o">=</span><span class="s">"listItem"</span><span class="na">wx:key</span><span class="o">=</span><span class="s">"index"</span><span class="p">&gt;</span>當前是第{{ listItem.idx }}個元素，為第{{ pageIndex }} 屏數據<span class="p">&lt;/</span><span class="nt">view</span><span class="p">&gt;</span><span class="p">&lt;/</span><span class="nt">view</span><span class="p">&gt;</span><span class="p">&lt;</span><span class="nt">view</span><span class="na">wx:else</span><span class="na">style</span><span class="o">=</span><span class="s">"height: {{ listSingleItem.height}}px"</span><span class="p">&gt;</span><span class="p">&lt;/</span><span class="nt">view</span><span class="p">&gt;</span><span class="p">&lt;/</span><span class="nt">view</span><span class="p">&gt;</span><span class="p">&lt;/</span><span class="nt">view</span><span class="p">&gt;</span></code></pre>
</div>
<p>這裡可以看到<code>wx:if="{{ listSingleItem.length &gt; 0 }}"</code>才去渲染數據，否則只是渲染一個空節點</p>
<div class="highlight">
<pre><code class="language-html"><span class="p">&lt;</span><span class="nt">view</span><span class="na">wx:else</span><span class="na">style</span><span class="o">=</span><span class="s">"height: {{ listSingleItem.height}}px"</span><span class="p">&gt;</span><span class="p">&lt;/</span><span class="nt">view</span><span class="p">&gt;</span></code></pre>
</div>
<p>到這里當我們滾動到第四屏的時候，就可以看到效果了，除了後面兩屏真的渲染了數據，前面的都為一個div佔位，高度為我們之前記錄的高度。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-a2bc88e88f2425dabfed9677b93a1305_r.jpg" data-caption="" data-size="normal" data-rawwidth="2884" data-rawheight="1956" class="origin_image zh-lightbox-thumb" width="2884" data-original="https://pic2.zhimg.com/v2-a2bc88e88f2425dabfed9677b93a1305_b.jpg" title="v2-a2bc88e88f2425dabfed9677b93a1305_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-a2bc88e88f2425dabfed9677b93a1305_r.jpg" data-caption="" data-size="normal" data-rawwidth="2884" data-rawheight="1956" class="origin_image zh-lightbox-thumb lazy" width="2884" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2884'%20height='1956'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-a2bc88e88f2425dabfed9677b93a1305_b.jpg" title="v2-a2bc88e88f2425dabfed9677b93a1305_r"></figure>
<p>到這裡第二小步完成。</p>
<p>接著第三小步：如果是獲取非最後一屏幕的，通過監聽onscrollTop 獲取到scrollTop的值，算取當前應該渲染哪一屏，在重新組裝數據setState。</p>
<p>這一塊的思路是這樣的，在onPageScroll的回調函數里，通過e.scrollTop獲取到當前的滾動距離。<br /><b>注意：這裡考慮到性能問題，對onPageScroll做一個節流。</b></p>
<p>根據這個滾動距離以及每一屏的高度，來計算當前應該渲染哪一屏的數據。這個需要怎麼去算？其實很簡單，循環去遍歷pageHeightArr，用第一屏的高度tempScrollTop與滾動高度realScrollTop +當前屏幕的高度this.windowHeight做比較，如果tempScrollTop比較小，則用tempScrollTop累加第二屏的高度，一直到<code>tempScrollTop &gt; realScrollTop + this.windowHeight</code>就是當前應該渲染的屏幕。</p>
<p>上述文字翻譯成代碼如下:</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">onPageScroll</span><span class="o">:</span><span class="nx">throttle</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span><span class="p">{</span><span class="kr">const</span><span class="nx">realScrollTop</span><span class="o">=</span><span class="nx">e</span><span class="p">.</span><span class="nx">scrollTop</span><span class="p">;</span><span class="kr">const</span><span class="nx">that</span><span class="o">=</span><span class="k">this</span><span class="p">;</span><span class="c1">//滾動的時候需要實時去計算當然應該在哪一屏幕</span><span class="kd">let</span><span class="nx">tempScrollTop</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="kr">const</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="p">;</span><span class="k">for</span><span class="p">(</span><span class="kd">var</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;</span><span class="k">this</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="p">{</span><span class="nx">tempScrollTop</span><span class="o">=</span><span class="nx">tempScrollTop</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span><span class="k">if</span><span class="p">(</span><span class="nx">tempScrollTop</span><span class="o">&gt;</span><span class="nx">realScrollTop</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">windowHeight</span><span class="p">)</span><span class="p">{</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'set this.computedCurrentIndex'</span><span class="o">+</span><span class="nx">i</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">computedCurrentIndex</span><span class="o">=</span><span class="nx">i</span><span class="p">;</span><span class="k">break</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="p">},</span><span class="mi">500</span><span class="p">),</span></code></pre>
</div>
<p>計算出當前應該渲染的屏幕之後，接著我們就可以需要去對比，當前正在渲染的屏幕index: currentRenderIndex 是不是跟我們計算出來的computedCurrentIndex一樣，如果不同，就說明我們需要調整渲染的屏幕數據，調整的方式跟第二步很像，唯一的區別就是，在這裡我們渲染的是computedCurrentIndex，以及computedCurrentIndex +-1(即前後兩屏)的數據而不是後兩屏的數據。最後將computedCurrentIndex 賦值給currentRenderIndex即可。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">onPageScroll</span><span class="o">:</span><span class="nx">throttle</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span><span class="p">{</span><span class="kr">const</span><span class="nx">realScrollTop</span><span class="o">=</span><span class="nx">e</span><span class="p">.</span><span class="nx">scrollTop</span><span class="p">;</span><span class="kr">const</span><span class="nx">that</span><span class="o">=</span><span class="k">this</span><span class="p">;</span><span class="c1">//滾動的時候需要實時去計算當然應該在哪一屏幕</span><span class="kd">let</span><span class="nx">tempScrollTop</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="kr">const</span><span class="nx">wholePageIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">wholePageIndex</span><span class="p">;</span><span class="k">for</span><span class="p">(</span><span class="kd">var</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;</span><span class="k">this</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="p">{</span><span class="nx">tempScrollTop</span><span class="o">=</span><span class="nx">tempScrollTop</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span><span class="k">if</span><span class="p">(</span><span class="nx">tempScrollTop</span><span class="o">&gt;</span><span class="nx">realScrollTop</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">windowHeight</span><span class="o">-</span><span class="mi">30</span><span class="p">)</span><span class="p">{</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'set this.computedCurrentIndex'</span><span class="o">+</span><span class="nx">i</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">computedCurrentIndex</span><span class="o">=</span><span class="nx">i</span><span class="p">;</span><span class="k">break</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="kr">const</span><span class="nx">currentRenderIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">currentRenderIndex</span><span class="p">;</span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">computedCurrentIndex</span><span class="o">!==</span><span class="nx">currentRenderIndex</span><span class="p">)</span><span class="p">{</span><span class="c1">//這裡給不渲染的元素佔位</span><span class="kd">let</span><span class="nx">tempList</span><span class="o">=</span><span class="k">new</span><span class="nb">Array</span><span class="p">(</span><span class="nx">wholePageIndex</span><span class="o">+</span><span class="mi">1</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="nx">tempList</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">item</span><span class="p">,</span><span class="nx">index</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">computedCurrentIndex</span><span class="o">-</span><span class="mi">1</span><span class="o">&lt;=</span><span class="nx">index</span><span class="o">&amp;&amp;</span><span class="nx">index</span><span class="o">&lt;=</span><span class="k">this</span><span class="p">.</span><span class="nx">computedCurrentIndex</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="p">{</span><span class="nx">tempList</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span><span class="o">=</span><span class="nx">that</span><span class="p">.</span><span class="nx">wholeVideoList</span><span class="p">[</span><span class="nx">index</span><span class="p">];</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="nx">tempList</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="nx">height</span><span class="o">:</span><span class="nx">that</span><span class="p">.</span><span class="nx">pageHeightArr</span><span class="p">[</span><span class="nx">index</span><span class="p">]};</span><span class="p">}</span><span class="p">})</span><span class="k">this</span><span class="p">.</span><span class="nx">currentRenderIndex</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">computedCurrentIndex</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">setData</span><span class="p">({</span><span class="nx">list</span><span class="o">:</span><span class="nx">tempList</span><span class="p">},</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setHeight</span><span class="p">();</span><span class="p">})</span><span class="p">}</span><span class="p">},</span><span class="mi">500</span><span class="p">),</span></code></pre>
</div>
<p>完整代碼如下:<br /><a href="https://link.zhihu.com/?target=https%3A//developers.weixin.qq.com/s/u4VWjKm67mhz" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">developers.weixin.qq.com</span> <span class="invisible">/s/u4VWjKm67mhz</span></a></p>
<p>到這，我們就完成了，一起來看看效果。</p>
<p><a class="video-box" href="https://link.zhihu.com/?target=https%3A//www.zhihu.com/video/1253480128120266752" target="_blank" data-video-id="" data-video-playable="true" data-name="" data-poster="https://pic4.zhimg.com/v2-cc59742f1d265037e3b51fb8fc46dc53.jpg?source=382ee89a" data-lens-id="1253480128120266752" rel="noopener nofollow external noreferrer" data-wpel-link="external"><img decoding="async" class="thumbnail" src="" data-original="https://pic4.zhimg.com/v2-cc59742f1d265037e3b51fb8fc46dc53.jpg?source=382ee89a"><span class="content"><span class="url">https://www.zhihu.com/video/1253480128120266752</span></span></a></p>
<p>可以看到，除了收尾只渲染兩屏，中間都是渲染三屏，其他用的是空白div佔位。</p>
<h2>總結</h2>
<p>可以看到整個過程的思路也是比較簡單，就是以屏為單位，只渲染屏幕前後兩屏的數據，不渲染的屏數用空白div佔位即可。</p>
<p>但是！當我寫完這篇文章，我想起來一個問題，為啥要通過監聽scroll 的判斷哪一屏來渲染， 直接用observeAPI 就好了啊，把這些scroll裡亂七八糟的計算去掉。這里大家可以自己試試，答案在這一篇</p>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/15080/topic-146791824/" data-wpel-link="internal">小程序長列表渲染優化另一種解決方案</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>從敲下一行JS代碼到這行代碼被執行，中間發生了什麼？</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/15000/topic-101137995/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 18:26:13 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/15000/topic-101137995/</guid>

					<description><![CDATA[<p>前言我們每天都在寫JS，你是否想過，計算機是怎麼識別你的這一行代碼，並且執行相應指令？本篇文章為你講述從敲下一行JS代碼到這行代碼可以被執行算出正確的結果，都經歷了什麼。編譯學過計算器基礎的，即使學的…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/15000/topic-101137995/" data-wpel-link="internal">從敲下一行JS代碼到這行代碼被執行，中間發生了什麼？</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">從敲下一行JS代碼到這行代碼被執行，中間發生了什麼？ </h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic1.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2>前言</h2>
<p>我們每天都在寫JS，你是否想過，計算機是怎麼識別你的這一行代碼，並且執行相應指令？本篇文章為你講述從敲下一行JS代碼到這行代碼可以被執行算出正確的結果，都經歷了什麼。</p>
<h2>編譯</h2>
<p>學過計算器基礎的，即使學的不好，大概都知道計算機跟人能讀懂的語言是不一樣的，它只認識0101的二進制數。也就是<b>機器指令碼（machine instruction code ）</b> 。一開始，人們都是用它來寫程序，可以想到最早的程序員有多痛苦。這種二進制碼不易被人類理解和記憶， 估計出錯太多，最後終於聰明的人類終於發明了適合自己學習記憶各種高級計算機語言，也包括JS。</p>
<p>但是機器並不能直接理解JS語言，所以這裡就需要一個中介幫忙程序解釋並且將其編譯成機器指令碼給計算機執行。這個過程就叫<b>編譯</b>。</p>
<p>而我們chrome瀏覽器裡的V8引擎就是幫我們做這個事情的中介。但是並不是只有google一家在做瀏覽器啊，所以市面上還有很多JS引擎。下面是從網上趴的圖：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-62b6740e53c283f2054e2f4d9a4a2e00_r.jpg" data-caption="" data-size="normal" data-rawwidth="1296" data-rawheight="642" class="origin_image zh-lightbox-thumb" width="1296" data-original="https://pic1.zhimg.com/v2-62b6740e53c283f2054e2f4d9a4a2e00_b.jpg" title="v2-62b6740e53c283f2054e2f4d9a4a2e00_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-62b6740e53c283f2054e2f4d9a4a2e00_r.jpg" data-caption="" data-size="normal" data-rawwidth="1296" data-rawheight="642" class="origin_image zh-lightbox-thumb lazy" width="1296" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1296'%20height='642'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-62b6740e53c283f2054e2f4d9a4a2e00_b.jpg" title="v2-62b6740e53c283f2054e2f4d9a4a2e00_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>而另前端痛苦不堪的瀏覽器兼容問題，就是因為使用的JS引擎不同，所以能夠理解的JS語法不同，我們就需要寫好幾種兼容語法。</p>
<p>所以終極解決兼容問題的方法就是：全部瀏覽器都用一種JS引擎，目前v8大有一統天下的趨勢，不過這個東西最終能不能實現今天就不討論了。</p>
<h2>編譯原理</h2>
<p>無論是哪種編譯器，原理都差不多。所以我們直接來看看編譯原理，就知道V8大概是如何工作的了。</p>
<p>編譯一般分為三個步驟：</p>
<ul>
<li><b>詞法分析(laxical Analysis)</b>詞法分析的意思就是，將代碼塊切分成最小的單位。這些最小單位稱為token。比如var a = 2;可以切分成var,a,=,2。</li>
<li><b>語法分析(Syntactic Analysis)</b>將詞法單元轉換成一個有層級，代表程序語法結構的樹，這就是我們經常說的AST，抽象語法樹。</li>
</ul>
<p>注意：詞法分析跟語法分析不是完全獨立的，而是交錯運行的。也就是說，並不是等所有的token都生成之後，才用語法分析器來處理。一般都是每取得一個token，就開始用語法分析器來處理了。</p>
<p>下面我們來看看一個add函數會生成怎樣的語法樹：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">add</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="nx">b</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="nx">a</span><span class="o">+</span><span class="nx">b</span><span class="p">}</span></code></pre>
</div>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-23335dfead88a75d0869616398ecee62_r.jpg" data-caption="" data-size="normal" data-rawwidth="1290" data-rawheight="1332" class="origin_image zh-lightbox-thumb" width="1290" data-original="https://pic3.zhimg.com/v2-23335dfead88a75d0869616398ecee62_b.jpg" title="v2-23335dfead88a75d0869616398ecee62_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-23335dfead88a75d0869616398ecee62_r.jpg" data-caption="" data-size="normal" data-rawwidth="1290" data-rawheight="1332" class="origin_image zh-lightbox-thumb lazy" width="1290" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1290'%20height='1332'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-23335dfead88a75d0869616398ecee62_b.jpg" title="v2-23335dfead88a75d0869616398ecee62_r"></figure>
<p>生成的樹太長了，截圖不完整，可以在<a href="https://link.zhihu.com/?target=https%3A//astexplorer.net/" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">AST Exploer</a>看到最終的AST。</p>
<p>可以看到這就是這段函數的樹形展示，如果你沒看懂，可以看這篇<a href="https://link.zhihu.com/?target=https%3A//segmentfault.com/a/1190000015653342" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文章</a>。這裡就不具體解釋每個FunctionDeclaration Identifier BlockStatement的意思了。</p>
<p>AST可是所有編譯器以及轉換器的基礎核心，我們常用的babel轉碼過程就是先將ES6的代碼編成AST，然後轉換成ES5的AST，最後由這個AST還原出ES5代碼。有興趣的可以看這篇<a href="https://link.zhihu.com/?target=https%3A//the-super-tiny-compiler.glitch.me/intro" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文章</a>，這篇文章是將LISP-style代碼的轉成C-style代碼，不過原理都一樣。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-5ff2f444306d80d87014a4407215cf5e_r.jpg" data-caption="" data-size="normal" data-rawwidth="1850" data-rawheight="386" class="origin_image zh-lightbox-thumb" width="1850" data-original="https://pic3.zhimg.com/v2-5ff2f444306d80d87014a4407215cf5e_b.jpg" title="v2-5ff2f444306d80d87014a4407215cf5e_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-5ff2f444306d80d87014a4407215cf5e_r.jpg" data-caption="" data-size="normal" data-rawwidth="1850" data-rawheight="386" class="origin_image zh-lightbox-thumb lazy" width="1850" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1850'%20height='386'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-5ff2f444306d80d87014a4407215cf5e_b.jpg" title="v2-5ff2f444306d80d87014a4407215cf5e_r"></figure>
<p><b>可以說基於AST，你可以隨意玩轉各種編程語言的相互轉換。</b></p>
<p>構建語法樹，還有一層作用，就是<b>發現語法錯誤</b>。當JS解析器發現無法構造這個抽象語法樹的時候，就會報語法錯誤，並結束整個代碼塊的解析。而對於一些強類型語言（也就是一開始就要定義這個變量是什麼類型，後面都不能改變），在構建出語法樹之後，還會有類型檢查。但是對於JS這種弱類型語言，就沒有這一步。當然TypeScipt為我們提供了類型檢查，並且可以將我們的typeScript代碼編譯成JS。</p>
<ul>
<li><b>代碼生成(Code Genaration)</b></li>
</ul>
<p>最後一步就是將AST轉成計算機可以識別的機器指令碼。</p>
<p>V8引擎的編譯過程基本就是上面這個過程，但是它多了一步生成字節碼的過程。首先用解析器生成AST，然後用解釋器Ignition根據語法樹生成字節碼，最後再用TurboFan將字節碼生成機器指令碼。</p>
<p>為什麼要先轉成字節碼？是因為直接生成機器指令碼太佔內存了。</p>
<p>整個過程就是這麼簡單了。</p>
<h2>V8 為什麼那麼快</h2>
<p>因為JS是解釋型語言，支持動態類型，弱類型，那就沒辦法先編譯找到變量的地址跟類型，所以JS的編譯過程發生在執行前的那段時間，對JS引擎的性能要求特別高。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-2a9d49399e860554623c23c526ce5d41_r.jpg" data-caption="" data-size="normal" data-rawwidth="1658" data-rawheight="550" class="origin_image zh-lightbox-thumb" width="1658" data-original="https://pic2.zhimg.com/v2-2a9d49399e860554623c23c526ce5d41_b.jpg" title="v2-2a9d49399e860554623c23c526ce5d41_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-2a9d49399e860554623c23c526ce5d41_r.jpg" data-caption="" data-size="normal" data-rawwidth="1658" data-rawheight="550" class="origin_image zh-lightbox-thumb lazy" width="1658" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1658'%20height='550'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-2a9d49399e860554623c23c526ce5d41_b.jpg" title="v2-2a9d49399e860554623c23c526ce5d41_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>那麼V8是如何做到的呢？</p>
<p>1、<b>腳本流(script streaming)</b></p>
<p>以前的chrome裡，網絡拿到數據之後，必須經過chrome主線程轉發到流解析器。但是，當網絡數據到達之後，主線程有可能被其他事情佔住，比如HTML解析，佈局，其他JS執行。這樣這些數據就沒辦法被即使解析。</p>
<p>從Chrome 75開始，V8可以將腳本直接從網絡流傳輸到流解析器中，而無需等待chrome主線程。</p>
<p>這意味著腳本一旦開始加載，V8就會在單獨的線程上解析。這樣下載腳本完成後幾乎立即完成解析，從而縮短頁面加載時間。</p>
<p>2、<b>字節碼緩存</b></p>
<p>首次訪問頁面的時候，JS代碼會被編譯成字節碼。當再次訪問同一個頁面的時候，會直接復用首次解析出來的字節碼。這樣就省去了下載，解析，編譯的步驟，可以使chrome節省大約40%的時間。</p>
<p>3、<b>內聯</b></p>
<p>如果一個函數內部調用其他函數，那麼編譯器會直接函數中將要執行的內容放到主函數里。</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">add</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="nx">b</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="nx">a</span><span class="o">+</span><span class="nx">b</span><span class="p">;</span><span class="p">}</span><span class="kd">function</span><span class="nx">calculateTwoPlusFive</span><span class="p">()</span><span class="p">{</span><span class="kd">var</span><span class="nx">sum</span><span class="p">;</span><span class="k">for</span><span class="p">(</span><span class="kd">var</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;=</span><span class="mi">1000000000</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="p">{</span><span class="nx">sum</span><span class="o">=</span><span class="nx">add</span><span class="p">(</span><span class="mi">2</span><span class="o">+</span><span class="mi">5</span><span class="p">);</span><span class="p">}</span><span class="p">}</span><span class="kd">var</span><span class="nx">start</span><span class="o">=</span><span class="k">new</span><span class="nb">Date</span><span class="p">();</span><span class="nx">calculateTwoPlusFive</span><span class="p">();</span><span class="kd">var</span><span class="nx">end</span><span class="o">=</span><span class="k">new</span><span class="nb">Date</span><span class="p">();</span><span class="kd">var</span><span class="nx">timeTaken</span><span class="o">=</span><span class="nx">end</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">()</span><span class="o">-</span><span class="nx">start</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">();</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Took "</span><span class="o">+</span><span class="nx">timeTaken</span><span class="o">+</span><span class="s2">"ms"</span><span class="p">);</span></code></pre>
</div>
<p>內聯屬性會將這個代碼編譯成</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">add</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="nx">b</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="nx">a</span><span class="o">+</span><span class="nx">b</span><span class="p">;</span><span class="p">}</span><span class="kd">function</span><span class="nx">calculateTwoPlusFive</span><span class="p">()</span><span class="p">{</span><span class="kd">var</span><span class="nx">sum</span><span class="p">;</span><span class="k">for</span><span class="p">(</span><span class="kd">var</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;=</span><span class="mi">1000000000</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">){</span><span class="nx">sum</span><span class="o">=</span><span class="mi">2</span><span class="o">+</span><span class="mi">5</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="kd">var</span><span class="nx">start</span><span class="o">=</span><span class="k">new</span><span class="nb">Date</span><span class="p">();</span><span class="nx">calculateTwoPlusFive</span><span class="p">();</span><span class="kd">var</span><span class="nx">end</span><span class="o">=</span><span class="k">new</span><span class="nb">Date</span><span class="p">();</span><span class="kd">var</span><span class="nx">timeTaken</span><span class="o">=</span><span class="nx">end</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">()</span><span class="o">-</span><span class="nx">start</span><span class="p">.</span><span class="nx">valueOf</span><span class="p">();</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Took "</span><span class="o">+</span><span class="nx">timeTaken</span><span class="o">+</span><span class="s2">"ms"</span><span class="p">);</span><span class="nx">複製代碼</span></code></pre>
</div>
<p>我把這段代碼放在safari上跑需要1454ms，而chrome只需要453ms，基本只有三分之一。</p>
<p>4、<b>隱藏類</b></p>
<p>對於C++/Java，訪問指令可以在編譯階段生成。</p>
<p>因為它們的每一個變量都有指定的類型。所以一個對象包含什麼成員，這些成員是什麼類型，在對像中的偏移量都可以在編譯階段就確定了。那麼在CPU執行的時候就輕鬆了，要訪問這個對像中的某個變量的時候，直接用對象的首地址加偏移量就可以訪問到。</p>
<p>但是JS是動態語言，運行的時候不僅可以隨意換類型，還可以動態添加刪除屬性。所以訪問對象屬性完全得運行的時候才能決定。</p>
<p>如果JS引擎每次都需要進行動態查詢，會造成大量的性能損耗。所以V8引入了隱藏類機制。在初始化對象時候，會給他創建一個隱藏類，而後增刪屬性都會在創建一個隱藏類或者查找之前已經創建好的類。</p>
<p>那麼這些隱藏類裡的成員對於這個類來說就是固定的。所以他們的偏移量對於這個類來說也是固定的，那麼在後續再次調用的時候就能很快的定位到他的位置。下面來看個例子：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">Person</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span><span class="nx">age</span><span class="p">)</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="o">=</span><span class="nx">name</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">age</span><span class="o">=</span><span class="nx">age</span><span class="p">;</span><span class="p">}</span><span class="kd">var</span><span class="nx">daisy</span><span class="o">=</span><span class="k">new</span><span class="nx">Person</span><span class="p">(</span><span class="s2">"daisy"</span><span class="p">,</span><span class="mi">32</span><span class="p">);</span><span class="kd">var</span><span class="nx">alice</span><span class="o">=</span><span class="k">new</span><span class="nx">Person</span><span class="p">(</span><span class="s2">"alice"</span><span class="p">,</span><span class="mi">20</span><span class="p">);</span><span class="nx">daisy</span><span class="p">.</span><span class="nx">email</span><span class="o">=</span><span class="s2">"daisy@qq.com"</span><span class="p">;</span><span class="nx">daisy</span><span class="p">.</span><span class="nx">job</span><span class="o">=</span><span class="s2">"engineer"</span><span class="p">;</span><span class="nx">alice</span><span class="p">.</span><span class="nx">job</span><span class="o">=</span><span class="s2">"engineer"</span><span class="p">;</span><span class="nx">alice</span><span class="p">.</span><span class="nx">email</span><span class="o">=</span><span class="s2">"alice@qq.com"</span><span class="p">;</span></code></pre>
</div>
<p>對於這段代碼，它的隱藏類的生成過程如下：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-03e177a9a0c2687c4845259c09638abd_r.jpg" data-caption="" data-size="normal" data-rawwidth="1668" data-rawheight="466" class="origin_image zh-lightbox-thumb" width="1668" data-original="https://pic2.zhimg.com/v2-03e177a9a0c2687c4845259c09638abd_b.jpg" title="v2-03e177a9a0c2687c4845259c09638abd_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-03e177a9a0c2687c4845259c09638abd_r.jpg" data-caption="" data-size="normal" data-rawwidth="1668" data-rawheight="466" class="origin_image zh-lightbox-thumb lazy" width="1668" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1668'%20height='466'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-03e177a9a0c2687c4845259c09638abd_b.jpg" title="v2-03e177a9a0c2687c4845259c09638abd_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>首先兩個new Person（）的時候，生成的隱藏類為C0，因為此時沒有任何屬性。當執行this.name = name;的時候多了一個屬性，於是又生成了C1。後面同理，到C2生成的時候，daisy跟alice的隱藏類都是一樣的，就是C2，此時有兩個屬性。</p>
<p>但是後面由於動態添加屬性的順序不同，就造成了屬性在類中的偏移量不同，也會生成不同的隱藏類。這樣就沒辦法共享隱藏類，導致浪費資源生成新的隱藏類。</p>
<p><b>所以我們動態賦值的時候，盡量保證順序也是一致的。</b></p>
<p>5、<b>熱點函數會被直接編譯成機器碼</b></p>
<p>v8在運行的時候，會採集JS代碼運行數據。當發現某個函數被頻繁調用，那麼就會將它標記成熱點函數，並且認為他是一個類型穩定的函數。這時候會將它生成更為高效的機器碼。</p>
<p>但是在後面的運行中，萬一類型發生變化，V8又要回退到字節碼。</p>
<p>比如：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">add</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="nx">b</span><span class="p">){</span><span class="k">return</span><span class="nx">a</span><span class="o">+</span><span class="nx">b</span><span class="p">}</span><span class="c1">// 这里使用add的时候一直传入number类型
</span><span class="k">for</span><span class="p">(</span><span class="kd">var</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;</span><span class="mi">10000</span><span class="p">;</span><span class="o">++</span><span class="nx">i</span><span class="p">){</span><span class="nx">add</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span><span class="nx">i</span><span class="p">);</span><span class="p">}</span><span class="c1">// 最后却传了string，会退回到字节码，会使得性能受损
</span><span class="nx">add</span><span class="p">(</span><span class="s1">'a'</span><span class="p">,</span><span class="s1">'b'</span><span class="p">);</span></code></pre>
</div>
<p>同理，下面兩段代碼可以猜猜誰的執行效率高？</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">// 片段1</span> <span class="kd">var</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">add</span> <span class="o">:</span> <span class="kd">function</span> <span class="p">(</span> <span class="nx">a</span> <span class="p">,</span> <span class="nx">b</span> <span class="p">){</span> <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span> <span class="p">;</span> <span class="p">}</span> <span class="p">};</span> <span class="nx">obj</span> <span class="p">.</span> <span class="nx">name</span> <span class="o">=</span> <span class="s1">'li'</span> <span class="p">;</span> <span class="c1">// 片段2</span> <span class="kd">var</span> <span class="nx">person</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">add</span> <span class="o">:</span> <span class="kd">function</span> <span class="p">(</span> <span class="nx">a</span> <span class="p">,</span> <span class="nx">b</span> <span class="p">){</span> <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span> <span class="p">;}</span> <span class="nx">name</span> <span class="o">:</span> <span class="s1">'li'</span> <span class="p">};</span></code></pre>
</div>
<p>答案是2。結合前面知識，我們可以知道，方法一中動態添加屬性會生成一個新的隱藏類。如果add函數此時已經被轉成機器碼，那麼對於方法一來說，就沒辦法復用了。因為類都是新的了。</p>
<p><b>所以函數參數類型越穩定，對象內部屬性越穩定，V8的效率越高。</b></p>
<h2>總結</h2>
<p>從敲下一段JS代碼到它最終被計算機理解並執行，中間經歷了<b>詞法分析，語法分析，生成機器碼，執行機器碼</b>的過程<b>。</b></p>
<p>當然這個編譯的過程是很複雜的，尤其js還是動態語言，對於js引擎的性能要求就很高了。 V8做了很多事情來提升瀏覽器的性能，其中包括但不限於：</p>
<ol>
<li>腳本流</li>
</ol>
<p>下載的同時就已經在解析，節省時間</p>
<p>2.字節碼緩存</p>
<p>訪問同一個頁面的時候直接復用之前的字節碼，不在重新編譯生成</p>
<p>3.內聯</p>
<p>將主函數中調用的函數，直接換成將要執行的語句</p>
<p>4.隱藏類</p>
<p>通過隱藏類快速定位到動態加入的屬性<b>注意：動態加入的屬性順序不一樣，會造成生成不同的隱藏類，我們動態賦值同一個構造函數對象的時候，盡量保證順序也是一致的。</b></p>
<p>5.熱點函數編譯成機器碼</p>
<p>將常用的函數直接一步到位編成機器碼。<b>注意：常用的函數傳入的類型保持固定。並且對象的屬性越穩定，越有利於性能。</b></p>
<p class="ztext-empty-paragraph"></p>
<p>參考文檔<br />1、 <a href="https://link.zhihu.com/?target=https%3A//juejin.im/post/5ada727c518825670b33a584" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">juejin.im/post/5ada727c</span> <span class="invisible">518825670b33a584</span></a></p>
<p>2、 <a href="https://link.zhihu.com/?target=https%3A//the-super-tiny-compiler.glitch.me/intro" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">the-super-tiny-compiler.glitch.me</span> <span class="invisible">/intro</span></a></p>
<p>3、 <a href="https://link.zhihu.com/?target=https%3A//blog.csdn.net/weixin_34184561/article/details/87999100" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">blog.csdn.net/weixin_34</span> <span class="invisible">184561/article/details/87999100</span></a></p>
<p>4、 <a href="https://link.zhihu.com/?target=https%3A//segmentfault.com/a/1190000016231512%23comment-area" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">segmentfault.com/a/1190</span> <span class="invisible">000016231512#comment-area</span></a></p>
<p>5、 <a href="https://link.zhihu.com/?target=https%3A//blog.csdn.net/weixin_34184561/article/details/87999100" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">blog.csdn.net/weixin_34</span> <span class="invisible">184561/article/details/87999100</span></a></p>
<p>6、 <a href="https://link.zhihu.com/?target=https%3A//s0v80dev.icopy.site/blog/v8-release-78" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">s0v80dev.icopy.site/blo</span> <span class="invisible">g/v8-release-78</span></a></p>
<p>7、 <a href="https://link.zhihu.com/?target=https%3A//blog.csdn.net/weixin_34184561/article/details/87999100" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">blog.csdn.net/weixin_34</span> <span class="invisible">184561/article/details/87999100</span></a></p>
<p>8、 <a href="https://link.zhihu.com/?target=https%3A//juejin.im/post/5c36fc33518825253b5e94e3" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">juejin.im/post/5c36fc33</span> <span class="invisible">518825253b5e94e3</span></a></p>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/15000/topic-101137995/" data-wpel-link="internal">從敲下一行JS代碼到這行代碼被執行，中間發生了什麼？</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>最簡化VUE的響應式原理</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/14739/topic-88648401/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 18:17:00 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/14739/topic-88648401/</guid>

					<description><![CDATA[<p>前言前端目前兩個當家花旦框架VUE React，它們能夠流行開來，響應式原理做出了巨大貢獻。畢竟，它通過數據的變更就能夠更新相應的視圖，極大的將我們從繁瑣的DOM操作中解放出來。所以掌握它們的響應式原理，對掌…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14739/topic-88648401/" data-wpel-link="internal">最簡化VUE的響應式原理</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">最簡化VUE的響應式原理</h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic4.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h3>前言</h3>
<p>前端目前兩個當家花旦框架VUE React，它們能夠流行開來，響應式原理做出了巨大貢獻。畢竟，它通過數據的變更就能夠更新相應的視圖，極大的將我們從繁瑣的DOM操作中解放出來。</p>
<p>所以掌握它們的響應式原理，對掌握前端框架的精髓就很重要了。</p>
<p>本文用最簡單的方式來解釋VUE2 最重點的響應式原理，看不懂算我輸！</p>
<h3>一、 響應式原理</h3>
<p>什麼是響應式原理？</p>
<p><b>意思就是在改變數據的時候，視圖會跟著更新</b>。這意味著你只需要進行數據的管理，給我們搬磚提供了很大的便利。 React也有這種特性，但是React的響應式方式跟VUE完全不同。</p>
<p>React是通過this.setState去改變數據，然後根據新的數據重新渲染出虛擬DOM，最後通過對比虛擬DOM找到需要更新的節點進行更新。</p>
<p>也就是說React是依靠著虛擬DOM以及DOM的diff算法做到這一點的。而關於React這方面的文章，我已經寫了很多了，還不了解的同學可以自行<a href="https://zhuanlan.zhihu.com/p/43145754" class="internal" data-wpel-link="external" rel="nofollow external noopener noreferrer">複習一下</a>。</p>
<p>而VUE則是利用了<b>Object.defineProperty</b>的方法裡面的setter與getter方法的<b>觀察者模式</b>來實現。</p>
<p>所以在學習VUE的響應式原理之前，先學習兩個預備知識：<br /> Object.defineProperty 與觀察者模式。</p>
<p>如果你已經掌握了，可以直接跳到第三part。</p>
<h3>二、預備知識</h3>
<h3>2.1 <u><a href="https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">Object.defineProperty</a></u></h3>
<p>這個方法就是在一個對像上定義一個新的屬性，或者改變一個對象現有的屬性，並且返回這個對象。裡面有兩個字段set,get。顧名思義，set都是取設置屬性的值，而get就是獲取屬性的值。</p>
<p>舉個栗子：</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">//在對像中添加一個屬性與存取描述符的範例</span><span class="kd">var</span><span class="nx">bValue</span><span class="p">;</span><span class="kd">var</span><span class="nx">o</span><span class="o">=</span><span class="p">{};</span><span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">o</span><span class="p">,</span><span class="s2">"b"</span><span class="p">,</span><span class="p">{</span><span class="nx">get</span><span class="o">:</span><span class="kd">function</span><span class="p">(){</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'監聽正在獲取b'</span><span class="p">)</span><span class="k">return</span><span class="nx">bValue</span><span class="p">;</span><span class="p">},</span><span class="nx">set</span><span class="o">:</span><span class="kd">function</span><span class="p">(</span><span class="nx">newValue</span><span class="p">){</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'監聽正在設置b'</span><span class="p">)</span><span class="nx">bValue</span><span class="o">=</span><span class="nx">newValue</span><span class="p">;</span><span class="p">},</span><span class="nx">enumerable</span><span class="o">:</span><span class="kc">true</span><span class="p">,</span><span class="nx">configurable</span><span class="o">:</span><span class="kc">true</span><span class="p">});</span><span class="nx">o</span><span class="p">.</span><span class="nx">b</span><span class="o">=</span><span class="mi">38</span><span class="p">;</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">o</span><span class="p">.</span><span class="nx">b</span><span class="p">)</span></code></pre>
</div>
<p>最終打印</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">监听正在设置b</span><span class="nx">监听正在获取b</span><span class="mi">38</span></code></pre>
</div>
<p>從在上述栗子中，可以看到當我們對ob 賦值38的時候，就會調用set函數，這時候給bValue賦值，之後我們就可以通過ob來獲取這個值，這時候，get函數被調用。</p>
<p>掌握到這一步，我們已經可以實現一個極簡的VUE雙向綁定了。</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">input</span><span class="nx">type</span><span class="o">=</span><span class="s2">"text"</span><span class="nx">id</span><span class="o">=</span><span class="s2">"txt"</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">span</span><span class="nx">id</span><span class="o">=</span><span class="s2">"sp"</span><span class="o">&gt;&lt;</span><span class="err">/span&gt;</span><span class="o">&lt;</span><span class="nx">script</span><span class="o">&gt;</span><span class="kd">var</span><span class="nx">txt</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'txt'</span><span class="p">),</span><span class="nx">sp</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'sp'</span><span class="p">),</span><span class="nx">obj</span><span class="o">=</span><span class="p">{}</span><span class="c1">//給對象obj添加msg屬性，並設置setter訪問器</span><span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span><span class="s1">'msg'</span><span class="p">,</span><span class="p">{</span><span class="c1">//設置obj.msg當obj.msg反生改變時set方法將會被調用</span><span class="nx">set</span><span class="o">:</span><span class="kd">function</span><span class="p">(</span><span class="nx">newVal</span><span class="p">)</span><span class="p">{</span><span class="c1">//當obj.msg被賦值時同時設置給input/span</span><span class="nx">txt</span><span class="p">.</span><span class="nx">value</span><span class="o">=</span><span class="nx">newVal</span><span class="nx">sp</span><span class="p">.</span><span class="nx">innerText</span><span class="o">=</span><span class="nx">newVal</span><span class="p">}</span><span class="p">})</span><span class="c1">//監聽文本框的改變當文本框輸入內容時改變obj.msg</span><span class="nx">txt</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'keyup'</span><span class="p">,</span><span class="kd">function</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span><span class="p">{</span><span class="nx">obj</span><span class="p">.</span><span class="nx">msg</span><span class="o">=</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">})</span><span class="o">&lt;</span><span class="err">/script&gt;</span></code></pre>
</div>
<p>VUE給data裡所有的屬性加上set,get這個過程就叫做<b>Reactive化</b>。</p>
<h3>2.2 觀察者模式</h3>
<p>什麼是觀察者模式？它分為<b>註冊環節跟發布環節</b>。</p>
<p>比如我去買芝士蛋糕，但是店家還沒有做出來。這時候我又不想在店外面傻傻等，我就需要隔一段時間來回來問問蛋糕做好沒，對於我來說是很麻煩的事情，說不定我就懶得買了。</p>
<p>店家肯定想要做生意，不想流失我這個吃貨客戶。於是，在蛋糕沒有做好的這段時間，有客戶來，他們就讓客戶把自己的電話留下，這就是觀察者模式中的<b>註冊環節</b>。然後蛋糕做好之後，一次性通知所有記錄了的客戶，這就是觀察者的<b>發布環節</b>。</p>
<p>這裡來簡單實現一個觀察者模式的類</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">Observer</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">dep</span><span class="o">=</span><span class="p">[];</span><span class="nx">register</span><span class="p">(</span><span class="nx">fn</span><span class="p">)</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">dep</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">fn</span><span class="p">)</span><span class="p">}</span><span class="nx">notify</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">dep</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">item</span><span class="p">=&gt;</span><span class="nx">item</span><span class="p">())</span><span class="p">}</span><span class="p">}</span><span class="kr">const</span><span class="nx">wantCake</span><span class="o">=</span><span class="k">new</span><span class="nx">Oberver</span><span class="p">();</span><span class="c1">//每來一個顧客就註冊一個想執行的函數</span><span class="nx">wantCake</span><span class="p">.</span><span class="nx">register</span><span class="p">(()</span><span class="p">=&gt;</span><span class="p">{</span><span class="s1">'console.log("call daisy")'</span><span class="p">})</span><span class="nx">wantCake</span><span class="p">.</span><span class="nx">register</span><span class="p">(()</span><span class="p">=&gt;</span><span class="p">{</span><span class="s1">'console.log("call anny")'</span><span class="p">})</span><span class="nx">wantCake</span><span class="p">.</span><span class="nx">register</span><span class="p">(()</span><span class="p">=&gt;</span><span class="p">{</span><span class="s1">'console.log("call sunny")'</span><span class="p">})</span><span class="c1">//最後蛋糕做好之後，通知所有的客戶</span><span class="nx">wantCake</span><span class="p">.</span><span class="nx">notify</span><span class="p">()</span></code></pre>
</div>
<h3>三、原理解析</h3>
<p>在學完了前面的鋪墊之後，我們終於可以開始講解VUE的響應式原理了。</p>
<p>官網用了一張圖來表示這個過程，但是剛開始看可能看不懂，等到文章的最後，我們再來看，應該就能看懂了。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-6abd75e4c9f312059482eeb7917ff84c_r.jpg" data-caption="" data-size="normal" data-rawwidth="1384" data-rawheight="842" class="origin_image zh-lightbox-thumb" width="1384" data-original="https://pic1.zhimg.com/v2-6abd75e4c9f312059482eeb7917ff84c_b.jpg" title="v2-6abd75e4c9f312059482eeb7917ff84c_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-6abd75e4c9f312059482eeb7917ff84c_r.jpg" data-caption="" data-size="normal" data-rawwidth="1384" data-rawheight="842" class="origin_image zh-lightbox-thumb lazy" width="1384" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1384'%20height='842'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-6abd75e4c9f312059482eeb7917ff84c_b.jpg" title="v2-6abd75e4c9f312059482eeb7917ff84c_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>總共分為三步驟：<br /><b>1、init階段：</b> VUE的data的屬性都會被reactive化，也就是加上setter/getter函數。</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">defineReactive</span><span class="p">(</span><span class="nx">obj</span><span class="o">:</span><span class="nb">Object</span><span class="p">,</span><span class="nx">key</span><span class="o">:</span><span class="nx">string</span><span class="p">,</span><span class="p">...)</span><span class="p">{</span><span class="kr">const</span><span class="nx">dep</span><span class="o">=</span><span class="k">new</span><span class="nx">Dep</span><span class="p">()</span><span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span><span class="nx">key</span><span class="p">,</span><span class="p">{</span><span class="nx">enumerable</span><span class="o">:</span><span class="kc">true</span><span class="p">,</span><span class="nx">configurable</span><span class="o">:</span><span class="kc">true</span><span class="p">,</span><span class="nx">get</span><span class="o">:</span><span class="kd">function</span><span class="nx">reactiveGetter</span><span class="p">()</span><span class="p">{</span><span class="p">....</span><span class="nx">dep</span><span class="p">.</span><span class="nx">depend</span><span class="p">()</span><span class="k">return</span><span class="nx">value</span><span class="p">....</span><span class="p">},</span><span class="nx">set</span><span class="o">:</span><span class="kd">function</span><span class="nx">reactiveSetter</span><span class="p">(</span><span class="nx">newVal</span><span class="p">)</span><span class="p">{</span><span class="p">...</span><span class="nx">val</span><span class="o">=</span><span class="nx">newVal</span><span class="nx">dep</span><span class="p">.</span><span class="nx">notify</span><span class="p">()</span><span class="p">...</span><span class="p">}</span><span class="p">})</span><span class="p">}</span><span class="kr">class</span><span class="nx">Dep</span><span class="p">{</span><span class="kr">static</span><span class="nx">target</span><span class="o">:</span><span class="o">?</span><span class="nx">Watcher</span><span class="p">;</span><span class="nx">subs</span><span class="o">:</span><span class="nb">Array</span><span class="o">&lt;</span><span class="nx">Watcher</span><span class="o">&gt;</span><span class="p">;</span><span class="nx">depend</span><span class="p">()</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="p">)</span><span class="p">{</span><span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">addDep</span><span class="p">(</span><span class="k">this</span><span class="p">)</span><span class="p">}</span><span class="p">}</span><span class="nx">notify</span><span class="p">()</span><span class="p">{</span><span class="kr">const</span><span class="nx">subs</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">subs</span><span class="p">.</span><span class="nx">slice</span><span class="p">()</span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="nx">l</span><span class="o">=</span><span class="nx">subs</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;</span><span class="nx">l</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="p">{</span><span class="nx">subs</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">update</span><span class="p">()</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>其中這裡的Dep就是一個觀察者類，每一個data的屬性都會有一個dep對象。當getter調用的時候，去dep裡註冊函數，<br />至於註冊了什麼函數，我們等會再說。</p>
<p>setter的時候，就是去通知執行剛剛註冊的函數。</p>
<p><b>2、mount階段：</b></p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">mountComponent</span><span class="p">(</span><span class="nx">vm</span><span class="o">:</span><span class="nx">Component</span><span class="p">,</span><span class="nx">el</span><span class="o">:</span><span class="o">?</span><span class="nx">Element</span><span class="p">,</span><span class="p">...)</span><span class="p">{</span><span class="nx">vm</span><span class="p">.</span><span class="nx">$el</span><span class="o">=</span><span class="nx">el</span><span class="p">...</span><span class="nx">updateComponent</span><span class="o">=</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">vm</span><span class="p">.</span><span class="nx">_update</span><span class="p">(</span><span class="nx">vm</span><span class="p">.</span><span class="nx">_render</span><span class="p">(),</span><span class="p">...)</span><span class="p">}</span><span class="k">new</span><span class="nx">Watcher</span><span class="p">(</span><span class="nx">vm</span><span class="p">,</span><span class="nx">updateComponent</span><span class="p">,</span><span class="p">...)</span><span class="p">...</span><span class="p">}</span><span class="kr">class</span><span class="nx">Watcher</span><span class="p">{</span><span class="nx">getter</span><span class="o">:</span><span class="nb">Function</span><span class="p">;</span><span class="c1">//代碼經過簡化</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">vm</span><span class="o">:</span><span class="nx">Component</span><span class="p">,</span><span class="nx">expOrFn</span><span class="o">:</span><span class="nx">string</span><span class="o">|</span><span class="nb">Function</span><span class="p">,</span><span class="p">...)</span><span class="p">{</span><span class="p">...</span><span class="k">this</span><span class="p">.</span><span class="nx">getter</span><span class="o">=</span><span class="nx">expOrFn</span><span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="o">=</span><span class="k">this</span><span class="c1">//注意這裡將當前的Watcher賦值給了Dep.target</span><span class="k">this</span><span class="p">.</span><span class="nx">value</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">getter</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nx">vm</span><span class="p">,</span><span class="nx">vm</span><span class="p">)</span><span class="c1">//調用組件的更新函數</span><span class="p">...</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>mount 階段的時候，會創建一個Watcher類的對象。這個Watcher實際上是連接Vue組件與Dep的橋樑。<br />每一個Watcher對應一個vue component。</p>
<p>這裡可以看出new Watcher的時候，constructor 裡的this.getter.call(vm, vm)函數會被執行。 getter就是updateComponent。這個函數會調用組件的render函數來更新重新渲染。</p>
<p>而render函數里，會訪問data的屬性，比如</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">render</span><span class="o">:</span><span class="kd">function</span><span class="p">(</span><span class="nx">createElement</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">'h1'</span><span class="p">,</span><span class="k">this</span><span class="p">.</span><span class="nx">blogTitle</span><span class="p">)</span><span class="p">}</span></code></pre>
</div>
<p>此時會去調用這個屬性blogTitle的getter函數，即：</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">// getter函数
</span><span class="nx">get</span><span class="o">:</span><span class="kd">function</span><span class="nx">reactiveGetter</span><span class="p">()</span><span class="p">{</span><span class="p">....</span><span class="nx">dep</span><span class="p">.</span><span class="nx">depend</span><span class="p">()</span><span class="k">return</span><span class="nx">value</span><span class="p">....</span><span class="p">},</span><span class="c1">// dep的depend函数
</span><span class="nx">depend</span><span class="p">()</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="p">)</span><span class="p">{</span><span class="nx">Dep</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">addDep</span><span class="p">(</span><span class="k">this</span><span class="p">)</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>在depend的函數里，Dep.target就是watcher本身（我們在class Watch裡講過，不記得可以往上第三段代碼），這裡做的事情就是給blogTitle註冊了Watcher這個對象。這樣每次render一個vue 組件的時候，如果這個組件用到了blogTitle，那麼這個組件相對應的Watcher對像都會被註冊到blogTitle的Dep中。</p>
<p>這個過程就叫做<b>依賴收集</b>。</p>
<p>收集完所有依賴blogTitle屬性的組件所對應的Watcher之後，當它發生改變的時候，就會去通知Watcher更新關聯的組件。</p>
<p><b>3、更新階段：</b></p>
<p>當blogTitle 發生改變的時候，就去調用Dep的notify函數,然後通知所有的Watcher調用update函數更新。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">notify</span><span class="p">()</span><span class="p">{</span><span class="kr">const</span><span class="nx">subs</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">subs</span><span class="p">.</span><span class="nx">slice</span><span class="p">()</span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="nx">l</span><span class="o">=</span><span class="nx">subs</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;</span><span class="nx">l</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="p">{</span><span class="nx">subs</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">update</span><span class="p">()</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>可以用一張圖來表示：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-cbc890983833db0a0b35841c05f4d3d1_r.jpg" data-caption="" data-size="normal" data-rawwidth="1618" data-rawheight="778" class="origin_image zh-lightbox-thumb" width="1618" data-original="https://pic2.zhimg.com/v2-cbc890983833db0a0b35841c05f4d3d1_b.jpg" title="v2-cbc890983833db0a0b35841c05f4d3d1_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-cbc890983833db0a0b35841c05f4d3d1_r.jpg" data-caption="" data-size="normal" data-rawwidth="1618" data-rawheight="778" class="origin_image zh-lightbox-thumb lazy" width="1618" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1618'%20height='778'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-cbc890983833db0a0b35841c05f4d3d1_b.jpg" title="v2-cbc890983833db0a0b35841c05f4d3d1_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>由此圖我們可以看出Watcher是連接VUE component 跟data屬性的橋樑。</p>
<h3>總結</h3>
<p>最後，我們通過解釋官方的圖來做個總結。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-abc8633ff694fe9aef01c0673dcac976_r.jpg" data-caption="" data-size="normal" data-rawwidth="2822" data-rawheight="1680" class="origin_image zh-lightbox-thumb" width="2822" data-original="https://pic3.zhimg.com/v2-abc8633ff694fe9aef01c0673dcac976_b.jpg" title="v2-abc8633ff694fe9aef01c0673dcac976_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-abc8633ff694fe9aef01c0673dcac976_r.jpg" data-caption="" data-size="normal" data-rawwidth="2822" data-rawheight="1680" class="origin_image zh-lightbox-thumb lazy" width="2822" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2822'%20height='1680'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-abc8633ff694fe9aef01c0673dcac976_b.jpg" title="v2-abc8633ff694fe9aef01c0673dcac976_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p><b>1、第一步：</b>組件初始化的時候，先給每一個Data屬性都註冊getter，setter，也就是reactive化。然後再new 一個自己的Watcher對象，此時watcher會立即調用組件的render函數去生成虛擬DOM。在調用render的時候，就會需要用到data的屬性值，此時會觸發getter函數，將當前的Watcher函數註冊進sub裡。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-f750a195dba1d0b5ab73b50edc3d8ffa_r.jpg" data-caption="" data-size="normal" data-rawwidth="2882" data-rawheight="1694" class="origin_image zh-lightbox-thumb" width="2882" data-original="https://pic3.zhimg.com/v2-f750a195dba1d0b5ab73b50edc3d8ffa_b.jpg" title="v2-f750a195dba1d0b5ab73b50edc3d8ffa_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-f750a195dba1d0b5ab73b50edc3d8ffa_r.jpg" data-caption="" data-size="normal" data-rawwidth="2882" data-rawheight="1694" class="origin_image zh-lightbox-thumb lazy" width="2882" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2882'%20height='1694'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-f750a195dba1d0b5ab73b50edc3d8ffa_b.jpg" title="v2-f750a195dba1d0b5ab73b50edc3d8ffa_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p><b>2、第二步：</b>當data屬性發生改變之後，就會遍歷sub裡所有的watcher對象，通知它們去重新渲染組件。</p>
<p>整個過程就是那麼簡單啦~</p>
<h3>彩蛋</h3>
<p>本來這篇文章應該已經結束了，但是我們既然已經學會了響應式原理，那當然要對目前vue的一些規則做點解釋啦。算是贈送的彩蛋~。</p>
<ul>
<li>如果你想要屬性是響應式的，就一定要寫在data對象裡。因為VUE只對data裡的屬性做reactive化處理。</li>
</ul>
<div class="highlight">
<pre><code class="language-text">var vm = new Vue({ data:{ a:1 } }) // `vm.a` 是响应的vm.b = 2 // `vm.b` 是非响应的</code></pre>
</div>
<p>或者使用Vue.set(vm.someObject, 'b', 2)動態添加。</p>
<h3>參考文檔：</h3>
<p>1、 <a href="https://zhuanlan.zhihu.com/p/67893936" class="internal" data-wpel-link="external" rel="nofollow external noopener noreferrer"><span class="invisible">https://</span> <span class="visible">zhuanlan.zhihu.com/p/67</span> <span class="invisible">893936</span></a><br /> 2、 <a href="https://link.zhihu.com/?target=https%3A//www.njleonzhang.com/2018/09/26/vue-reactive.html" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://www.</span> <span class="visible">njleonzhang.com/2018/09</span> <span class="invisible">/26/vue-reactive.html</span></a><br /> 3、 <a href="https://link.zhihu.com/?target=https%3A//vuejs.bootcss.com/v2/guide/reactivity.html" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">https://</span> <span class="visible">vuejs.bootcss.com/v2/gu</span> <span class="invisible">ide/reactivity.html</span></a></p>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14739/topic-88648401/" data-wpel-link="internal">最簡化VUE的響應式原理</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>React新特性全解（下）&#8211; 一文讀懂Hooks</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/14390/topic-59579340/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 18:02:40 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/14390/topic-59579340/</guid>

					<description><![CDATA[<p>前言這篇文章主要講Hooks，如果你想了解React 16的其他新特性，請移步React 16新特性全解（上）， React 16新特性全解（中）v16.8 Hooks Hooks是什麼？我們知道，functional component在使用的時候有一些限制，…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14390/topic-59579340/" data-wpel-link="internal">React新特性全解（下）&#8211; 一文讀懂Hooks</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">React新特性全解（下）-- 一文讀懂Hooks </h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic4.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2><b>前言</b></h2>
<blockquote><p>這篇文章主要講Hooks，如果你想了解React 16的其他新特性，請移步<a href="https://zhuanlan.zhihu.com/p/57544233" class="internal" data-wpel-link="external" rel="nofollow external noopener noreferrer">React 16新特性全解（上）</a> ， <a href="https://zhuanlan.zhihu.com/p/59518164" class="internal" data-wpel-link="external" rel="nofollow external noopener noreferrer">React 16新特性全解（中）</a></p></blockquote>
<h2><b>v16.8 Hooks</b></h2>
<p><b>Hooks是什麼？</b></p>
<p>我們知道，functional component在使用的時候有一些限制，比如需要生命週期、state的時候就不能用functional component。而有了Hooks，你就可以在funtional component裡，使用class component的功能:props，state，context，refs，和生命週期函數等等。</p>
<p><b>雖然Hooks已經有要取代正宮class的趨勢了，但是react目前沒有計劃拋棄class，所以不要慌，你還是可以跟往常一樣使用class。</b></p>
<p>在真正介紹Hook之前，還是一樣先來了解為什麼要引入Hooks？其實不單單是為了給functional component賦於class component的功能。</p>
<p>還有下面的問題：</p>
<p><b>1.組件之間很難復用邏輯</b></p>
<p>之前如果我們需要復用邏輯，常用的兩種方式是<a href="https://link.zhihu.com/?target=https%3A//reactjs.org/docs/render-props.html" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">render props</a>跟<a href="https://link.zhihu.com/?target=https%3A//reactjs.org/docs/higher-order-components.html" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">higher-order components</a> 。但是這兩種方式都需要你重構代碼，所以比較麻煩。</p>
<p>最重要的是，用這兩種方式的話，在React Devtools裡，會看到很多的嵌套組件。</p>
<p class="ztext-empty-paragraph"></p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-a0c394e39afa62eb30edb39878805408_r.jpg" data-caption="" data-size="normal" data-rawwidth="2262" data-rawheight="920" class="origin_image zh-lightbox-thumb" width="2262" data-original="https://pic1.zhimg.com/v2-a0c394e39afa62eb30edb39878805408_b.jpg" title="v2-a0c394e39afa62eb30edb39878805408_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-a0c394e39afa62eb30edb39878805408_r.jpg" data-caption="" data-size="normal" data-rawwidth="2262" data-rawheight="920" class="origin_image zh-lightbox-thumb lazy" width="2262" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2262'%20height='920'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-a0c394e39afa62eb30edb39878805408_b.jpg" title="v2-a0c394e39afa62eb30edb39878805408_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>在這個圖可以看到Header外層套著很多嵌套組件。</p>
<p><b>2.複雜組件很難理解</b></p>
<p>在之前的class component裡，我們的生命週期函數里通常放著不相關的代碼，而相關的代碼確要放在不同的生命週期函數里。這樣說可能有點繞，我們來看一個具體的例子。</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">App</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">component</span><span class="p">{</span><span class="nx">componentDidMount</span><span class="p">()</span><span class="p">{</span><span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'scroll'</span><span class="p">,</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'a'</span><span class="p">)})</span><span class="k">this</span><span class="p">.</span><span class="nx">fetchData</span><span class="p">();</span><span class="p">}</span><span class="nx">componentDidUpdate</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">fetchData</span><span class="p">();</span><span class="p">}</span><span class="nx">componentWillUnmount</span><span class="p">()</span><span class="p">{</span><span class="nb">window</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'scroll'</span><span class="p">,</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'a'</span><span class="p">)})</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">ddd</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>這應該是我們平時會經常寫的代碼。在componentDidMount裡做事件綁定，獲取數據。在componentWillUnMount裡解綁事件。</p>
<p>這樣做的問題是：componentDidMount裝著的代碼都是不相關的，而相關聯的事件綁定以及事件解綁，分散在componentDidMount 跟componentWillUnMount裡。<b>這樣如果組件的邏輯越寫越複雜之後，就會變得很難維護易出bug。</b></p>
<p>後面講Effect Hook的時候，我會介紹這個問題用Hooks怎麼解決。</p>
<p><b>3.class比較難學</b></p>
<p>React團隊發現class是初學者學習React的大障礙。要學習class component,你必須要知道幾點：</p>
<ol>
<li>this在JS是如何工作的（光是這個就夠繞的）</li>
<li>記得綁定事件</li>
<li>了解state，props，state以及從上而下的數據流</li>
<li>functional component跟class component的區別，如何使用它們</li>
</ol>
<h2><b>如何使用</b></h2>
<p>理解了Hooks誕生的原因，接著來看看要如何使用。</p>
<p>假設我需要實現一個功能，點擊app時候，count數目加一。用class的形式實現就是這樣的：</p>
<p><b>Class:</b></p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">Example</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">count</span><span class="o">:</span><span class="mi">0</span><span class="p">};</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">You</span><span class="nx">clicked</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="p">}</span><span class="nx">times</span><span class="o">&lt;</span><span class="err">/p&gt;</span><span class="o">&lt;</span><span class="nx">button</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span><span class="p">=&gt;</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">count</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="o">+</span><span class="mi">1</span><span class="p">})}</span><span class="o">&gt;</span><span class="nx">Click</span><span class="nx">me</span><span class="o">&lt;</span><span class="err">/button&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p><b>Hooks:</b></p>
<p>如果需要用Hooks實現，變成</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">import</span> <span class="nx">React</span> <span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'react'</span> <span class="p">;</span> <span class="kd">function</span> <span class="nx">Example</span> <span class="p">()</span> <span class="p">{</span> <span class="c1">// Declare a new state variable, which we'll call "count"</span> <span class="kr">const</span> <span class="p">[</span> <span class="nx">count</span> <span class="p">,</span> <span class="nx">setCount</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span> <span class="p">(</span> <span class="mi">0</span> <span class="p">);</span> <span class="k">return</span> <span class="p">(</span> <span class="o">&lt;</span> <span class="nx">div</span> <span class="o">&gt;</span> <span class="o">&lt;</span> <span class="nx">p</span> <span class="o">&gt;</span> <span class="nx">You</span> <span class="nx">clicked</span> <span class="p">{</span> <span class="nx">count</span> <span class="p">}</span> <span class="nx">times</span> <span class="o">&lt;</span> <span class="err">/p&gt;</span> <span class="o">&lt;</span> <span class="nx">button</span> <span class="nx">onClick</span> <span class="o">=</span> <span class="p">{()</span> <span class="p">=&gt;</span> <span class="nx">setCount</span> <span class="p">(</span> <span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">)}</span> <span class="o">&gt;</span> <span class="nx">Click</span> <span class="nx">me</span> <span class="o">&lt;</span> <span class="err">/button&gt;</span> <span class="o">&lt;</span> <span class="err">/div&gt;</span> <span class="p">);</span> <span class="p">}</span></code></pre>
</div>
<p><a href="https://link.zhihu.com/?target=https%3A//codesandbox.io/s/v3j5xx6903" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">演示地址</a></p>
<p>在這裡demo中。 useState就是Hook。我們通過它來在function component裡加入state數據。</p>
<p>兩者對比如下：</p>
<p><b>1.定義state變量</b></p>
<p>Class:</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">Example</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">count</span><span class="o">:</span><span class="mi">0</span><span class="p">};</span><span class="p">}</span></code></pre>
</div>
<p>Hooks:</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">import</span><span class="nx">React</span><span class="p">,</span><span class="p">{</span><span class="nx">useState</span><span class="p">}</span><span class="nx">from</span><span class="s1">'react'</span><span class="p">;</span><span class="kd">function</span><span class="nx">Example</span><span class="p">()</span><span class="p">{</span><span class="c1">// 通过useState这个Hooks定义state变量:count。并且通过useState给count赋初始值0，只在初始化时候使用一次
</span><span class="kr">const</span><span class="p">[</span><span class="nx">count</span><span class="p">,</span><span class="nx">setCount</span><span class="p">]</span><span class="o">=</span><span class="nx">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span></code></pre>
</div>
<p>在function component裡，我們是沒有this的。所以沒辦法向Class那樣用this來創建state，這時候Hooks閃亮登場。</p>
<p>通過useState這個hooks我們可以定義count這個state變量。由Hooks定義的state變量不一定要是object，可以是string、number。傳入的內容相當於給變量賦初始值。</p>
<div class="highlight">
<pre><code class="language-text">function ExampleWithManyStates() { // Declare multiple state variables! const [age, setAge] = useState(42); const [fruit, setFruit] = useState('banana'); const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]); // ... }</code></pre>
</div>
<p><b>2.渲染state</b></p>
<p>Class:</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">You</span><span class="nx">clicked</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="p">}</span><span class="nx">times</span><span class="o">&lt;</span><span class="err">/p&gt;</span></code></pre>
</div>
<p>Hooks:</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">You</span><span class="nx">clicked</span><span class="p">{</span><span class="nx">count</span><span class="p">}</span><span class="nx">times</span><span class="o">&lt;</span><span class="err">/p&gt;</span></code></pre>
</div>
<p>可以不需要用this，直接使用count</p>
<p><b>3.更新state</b></p>
<p>Class:</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">button</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span><span class="p">=&gt;</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">count</span><span class="o">:</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="o">+</span><span class="mi">1</span><span class="p">})}</span><span class="o">&gt;</span><span class="nx">Click</span><span class="nx">me</span><span class="o">&lt;</span><span class="err">/button&gt;</span></code></pre>
</div>
<p>Hooks:</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">button</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span><span class="p">=&gt;</span><span class="nx">setCount</span><span class="p">(</span><span class="nx">count</span><span class="o">+</span><span class="mi">1</span><span class="p">)}</span><span class="o">&gt;</span><span class="nx">Click</span><span class="nx">me</span><span class="o">&lt;</span><span class="err">/button&gt;</span></code></pre>
</div>
<p>我們可以看到<code>const [count, setCount] = useState(0);</code> useState返回兩個參數，一個是當前state的值，還有一個其實是一個函數，用來改變state的值，就是setCount。</p>
<p>類似setState，但是不同的是，<b>它不會將舊的state跟新的state合併在一起，而是覆蓋式的重寫state的值。</b></p>
<p>說完了functional component裡面如何使用state之後，我們再來看如何用Effect Hook來取代生命週期。</p>
<p>一般我們都會在生命週期componentDidMount, componentDidUpdate與componentWillUnmount中做一些副作用的操作，例如：獲取數據，訂閱，手動改變DOM。而在hooks裡，這些生命週期函數都被統一成一個方法useEffect。</p>
<p>下面我們來舉個例子：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">import</span><span class="nx">React</span><span class="p">,</span><span class="p">{</span><span class="nx">useState</span><span class="p">,</span><span class="nx">useEffect</span><span class="p">}</span><span class="nx">from</span><span class="s1">'react'</span><span class="p">;</span><span class="kd">function</span><span class="nx">Example</span><span class="p">()</span><span class="p">{</span><span class="kr">const</span><span class="p">[</span><span class="nx">count</span><span class="p">,</span><span class="nx">setCount</span><span class="p">]</span><span class="o">=</span><span class="nx">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="c1">// Similar to componentDidMount and componentDidUpdate:</span><span class="nx">useEffect</span><span class="p">(()</span><span class="p">=&gt;</span><span class="p">{</span><span class="c1">// Update the document title using the browser API</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="o">=</span><span class="sb">`You clicked</span><span class="si">${</span><span class="nx">count</span><span class="si">}</span><span class="sb">times`</span><span class="p">;</span><span class="p">});</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">You</span><span class="nx">clicked</span><span class="p">{</span><span class="nx">count</span><span class="p">}</span><span class="nx">times</span><span class="o">&lt;</span><span class="err">/p&gt;</span><span class="o">&lt;</span><span class="nx">button</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span><span class="p">=&gt;</span><span class="nx">setCount</span><span class="p">(</span><span class="nx">count</span><span class="o">+</span><span class="mi">1</span><span class="p">)}</span><span class="o">&gt;</span><span class="nx">Click</span><span class="nx">me</span><span class="o">&lt;</span><span class="err">/button&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span></code></pre>
</div>
<p>在這個例子中，我們在useEffect 裡完成了副作用的操作。 <a href="https://link.zhihu.com/?target=https%3A//codesandbox.io/s/v3j5xx6903" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">demo演示</a></p>
<p>Effects Hooks 就在functional component裡， 所以它可以直接訪問props跟state。 <b>React在每次render之後都會調用effect，包括第一次render。</b></p>
<p>但是這裡還遺留兩個問題</p>
<p>1、我們在開篇說到，class component有個問題就是生命週期函數里的代碼都是不相關的，而相關的代碼確要被打散在不同的生命週期函數里。這個問題用Hooks的話就可以解決。</p>
<p>比如綁定、解綁事件，在使用class的時候，在componentDidMount裡監聽了一個事件，之後需要在componentWillMount裡給它解綁。</p>
<p>用Hook只需要在useEffect一個函數就可以做到。它可以通過返回一個函數來專門做清除的工作，代碼如下：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">import</span><span class="nx">React</span><span class="p">,</span><span class="p">{</span><span class="nx">useState</span><span class="p">,</span><span class="nx">useEffect</span><span class="p">}</span><span class="nx">from</span><span class="s1">'react'</span><span class="p">;</span><span class="kd">function</span><span class="nx">FriendStatus</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">const</span><span class="p">[</span><span class="nx">isOnline</span><span class="p">,</span><span class="nx">setIsOnline</span><span class="p">]</span><span class="o">=</span><span class="nx">useState</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="kd">function</span><span class="nx">handleStatusChange</span><span class="p">(</span><span class="nx">status</span><span class="p">)</span><span class="p">{</span><span class="nx">setIsOnline</span><span class="p">(</span><span class="nx">status</span><span class="p">.</span><span class="nx">isOnline</span><span class="p">);</span><span class="p">}</span><span class="nx">useEffect</span><span class="p">(()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">ChatAPI</span><span class="p">.</span><span class="nx">subscribeToFriendStatus</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">friend</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="nx">handleStatusChange</span><span class="p">);</span><span class="c1">//在這裡返回一個函數來做這件事</span><span class="k">return</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">ChatAPI</span><span class="p">.</span><span class="nx">unsubscribeFromFriendStatus</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">friend</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="nx">handleStatusChange</span><span class="p">);</span><span class="p">};</span><span class="p">});</span><span class="k">if</span><span class="p">(</span><span class="nx">isOnline</span><span class="o">===</span><span class="kc">null</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="s1">'Loading...'</span><span class="p">;</span><span class="p">}</span><span class="k">return</span><span class="nx">isOnline</span><span class="o">?</span><span class="s1">'Online'</span><span class="o">:</span><span class="s1">'Offline'</span><span class="p">;</span><span class="p">}</span></code></pre>
</div>
<p>在這個case中，unsubscribeFromFriendStatus不僅僅會在組件unmount的時候調用，同時在重新渲染的時候也會調用。</p>
<p>如果你只想useEffect只在mount與unmount時候調用，需要這樣傳一個[] 作為第二個參數。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">useEffect</span><span class="p">(()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">ChatAPI</span><span class="p">.</span><span class="nx">subscribeToFriendStatus</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">friend</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="nx">handleStatusChange</span><span class="p">);</span><span class="k">return</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">ChatAPI</span><span class="p">.</span><span class="nx">unsubscribeFromFriendStatus</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">friend</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="nx">handleStatusChange</span><span class="p">);</span><span class="p">};</span><span class="p">},</span><span class="p">[]);</span></code></pre>
</div>
<p>2、有時候我們並不想每次state的改變，都去調用useEffect。在class裡，會這樣做</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">componentDidUpdate</span><span class="p">(</span><span class="nx">prevProps</span><span class="p">,</span><span class="nx">prevState</span><span class="p">)</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="nx">prevState</span><span class="p">.</span><span class="nx">count</span><span class="o">!==</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="p">)</span><span class="p">{</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="o">=</span><span class="sb">`You clicked </span><span class="si">${</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="si">}</span><span class="sb"> times`</span><span class="p">;</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>在Hooks，可以通過給useEffect傳入第二個參數，即它關注的state變量，來做到這件事。</p>
<p>例如：當count改變時候，才去調用useEffect。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">useEffect</span><span class="p">(()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="o">=</span><span class="sb">`You clicked </span><span class="si">${</span><span class="nx">count</span><span class="si">}</span><span class="sb"> times`</span><span class="p">;</span><span class="p">},</span><span class="p">[</span><span class="nx">count</span><span class="p">]);</span><span class="c1">// Only re-run the effect if count changes
</span></code></pre>
</div>
<p><b>注意事項：</b></p>
<ol>
<li>Hooks只可以在頂層使用。也就是說它不能寫在循環體，條件渲染，或者嵌套function裡</li>
<li>只可以在React的function組件裡使用Hooks。</li>
</ol>
<p><b>說了那麼多，總結以下，其實Hooks就是幫助我們在function component裡直接使用原來class component才有的特性。</b></p>
<p>以上，我們只接觸到了兩種hooks，還有更多比如<a href="https://link.zhihu.com/?target=https%3A//reactjs.org/docs/hooks-reference.html" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">useContext, useReducer, useCallback</a> ,感興趣的同學可以自己看下~</p>
<p>最後是使用Hooks的一些建議：</p>
<p><b>一些建議</b></p>
<ol>
<li>是否需要用hoooks重構以前的代碼？</li>
</ol>
<p>No，react團隊不推薦用hooks重新寫一遍。推薦做法是新的組件可以直接使用，然後需要改老組件代碼的時候在順便改就行了。</p>
<p>2.支持Hooks的工具</p>
<p>React DevTools對hooks已經支持。同時強烈推薦安裝hooks的eslint校驗庫<a href="https://link.zhihu.com/?target=https%3A//www.npmjs.com/package/eslint-plugin-react-hooks" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">eslint-plugin-react-hooks</a> 。</p>
<p>Reac團隊下一步計劃</p>
<p>因為現在React Hooks還不能完全cover所有class的功能，雖然他們已經很相近了。目前為止Hooks不支持兩個不常使用的API getSnapshotBeforeUpdate 跟componentDidCatch，但是React團隊正在努力，未來會支持的。</p>
<p class="ztext-empty-paragraph"></p>
<p>參考文檔：</p>
<p>1.</p>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14390/topic-59579340/" data-wpel-link="internal">React新特性全解（下）&#8211; 一文讀懂Hooks</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>React 16 新特性全解（中）</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/14377/topic-59518164/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 18:02:06 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/14377/topic-59518164/</guid>

					<description><![CDATA[<p>前言這篇文章主要介紹v16.4~ v16.6的特性，如果你關注Hooks，可以直接看React 16 新特性全解（下）目錄v16.4新增指針事件fix生命週期函數v16.5提供新的調試工具v16.6memolazysuspense簡化contextType增加getDeri…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14377/topic-59518164/" data-wpel-link="internal">React 16 新特性全解（中）</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">React 16 新特性全解（中） </h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic1.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2><b>前言</b></h2>
<blockquote><p><b>這篇文章主要介紹v16.4~ v16.6的特性，如果你關注Hooks，可以直接看</b><a href="https://zhuanlan.zhihu.com/p/59579340" class="internal" data-wpel-link="external" rel="nofollow external noopener noreferrer">React 16新特性全解（下）</a></p></blockquote>
<h2><b>目錄</b></h2>
<ul>
<li>v16.4</li>
</ul>
<ol>
<li>新增指針事件</li>
<li>fix生命週期函數</li>
</ol>
<ul>
<li>v16.5</li>
</ul>
<ol>
<li>提供新的調試工具</li>
</ol>
<ul>
<li>v16.6</li>
</ul>
<ol>
<li>memo</li>
<li> lazy</li>
<li> suspense</li>
<li>簡化contextType</li>
<li>增加getDerivedStateFromError</li>
</ol>
<h2><b>16.4</b></h2>
<p><b>新增指針事件</b></p>
<p>新增了對對指針設備（例如鼠標，觸控筆，或者手指觸摸）觸發的dom事件：</p>
<ul>
<li>onPointerDown</li>
<li> onPointerMove</li>
<li> onPointerEnter</li>
<li> onPointerLeave</li>
<li> onPointerOver</li>
<li> onPointerOut</li>
</ul>
<p><a href="https://link.zhihu.com/?target=https%3A//reactjs.org/blog/2018/05/23/react-v-16-4.html" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">等等</a>。</p>
<p>但是這個事件僅支持那些支持指針事件的瀏覽器，比如目前最新版本的Chrome，Firefox，Edge IE瀏覽器）。但是如果你的應用程序真的依賴這些事件，可以使用第三方的polyfill。因為React團隊對了避免增大react的bundle size，所以沒有放進去。</p>
<p><a href="https://link.zhihu.com/?target=https%3A//codesandbox.io/s/ox5lx949oq" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">demo時刻</a></p>
<p>這裡展示的是一個鼠標的拖拽功能，可以自己try try。</p>
<p><b>fix生命週期函數- getDerivedStateFromProps</b></p>
<p><b>React 16.0 ~ React 16.3 :</b></p>
<p>生命週期的圖如下：</p>
<p class="ztext-empty-paragraph"></p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-fec8d723f9401d73686ab02d6bf9c019_r.jpg" data-caption="" data-size="normal" data-rawwidth="1766" data-rawheight="968" class="origin_image zh-lightbox-thumb" width="1766" data-original="https://pic2.zhimg.com/v2-fec8d723f9401d73686ab02d6bf9c019_b.jpg" title="v2-fec8d723f9401d73686ab02d6bf9c019_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-fec8d723f9401d73686ab02d6bf9c019_r.jpg" data-caption="" data-size="normal" data-rawwidth="1766" data-rawheight="968" class="origin_image zh-lightbox-thumb lazy" width="1766" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1766'%20height='968'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-fec8d723f9401d73686ab02d6bf9c019_b.jpg" title="v2-fec8d723f9401d73686ab02d6bf9c019_r"></figure>
<p>這裡我們可以看到getDerivedStateFromProps這個生命週期函數有個問題就是在Updating階段的時候，無論使用setState還是forceUpdate,都不調用getDerivedStateFromProps。</p>
<p>餵，這明顯有問題啊？ ！那我在updating階段都沒辦法監聽到props的改變來搞事情了。</p>
<p>React團隊還是很快意識到了這個問題的。所以在這個版本，他們fix了這個問題，新的圖長這樣：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-bb0140855eacdd7d3610ed006a7e426f_r.jpg" data-caption="" data-size="normal" data-rawwidth="1272" data-rawheight="704" class="origin_image zh-lightbox-thumb" width="1272" data-original="https://pic4.zhimg.com/v2-bb0140855eacdd7d3610ed006a7e426f_b.jpg" title="v2-bb0140855eacdd7d3610ed006a7e426f_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-bb0140855eacdd7d3610ed006a7e426f_r.jpg" data-caption="" data-size="normal" data-rawwidth="1272" data-rawheight="704" class="origin_image zh-lightbox-thumb lazy" width="1272" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1272'%20height='704'&gt;&lt;/svg&gt;" data-actualsrc="https://pic4.zhimg.com/v2-bb0140855eacdd7d3610ed006a7e426f_b.jpg" title="v2-bb0140855eacdd7d3610ed006a7e426f_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>很簡單，就是fix了之前Updating階段用setState，forceUpdate調用不到getDerivedStateFromProps這個問題。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-3ebb56d61824228682c6e07818bfe3b4_r.jpg" data-caption="" data-size="normal" data-rawwidth="428" data-rawheight="422" class="origin_image zh-lightbox-thumb" width="428" data-original="https://pic1.zhimg.com/v2-3ebb56d61824228682c6e07818bfe3b4_b.jpg" title="v2-3ebb56d61824228682c6e07818bfe3b4_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-3ebb56d61824228682c6e07818bfe3b4_r.jpg" data-caption="" data-size="normal" data-rawwidth="428" data-rawheight="422" class="origin_image zh-lightbox-thumb lazy" width="428" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='428'%20height='422'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-3ebb56d61824228682c6e07818bfe3b4_b.jpg" title="v2-3ebb56d61824228682c6e07818bfe3b4_r"></figure>
<h2><b>v16.4.2</b></h2>
<p>這個小版本的發布主要是為了fix一個服務端的XSS漏洞，我本來不想講，但是看大家那麼好學，還是提一下把。</p>
<p>呃，別告訴我你還不知道什麼是XSS漏洞，拜託趕緊現在立刻馬上去學一下，這個必會。一個簡單的例子就是用戶輸入了你不想讓他輸入的內容，尤其帶有JS html標籤的，給你的網站帶來了麻煩。</p>
<p>這個XSS具體場景是這樣的：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">let</span><span class="nx">props</span><span class="o">=</span><span class="p">{};</span><span class="c1">// 注意：这里props的属性依靠用户输入
</span><span class="nx">props</span><span class="p">[</span><span class="nx">userProvidedData</span><span class="p">]</span><span class="o">=</span><span class="s2">"hello"</span><span class="p">;</span><span class="kd">let</span><span class="nx">element</span><span class="o">=</span><span class="o">&lt;</span><span class="nx">div</span><span class="p">{...</span><span class="nx">props</span><span class="p">}</span><span class="o">/&gt;</span><span class="p">;</span><span class="kd">let</span><span class="nx">html</span><span class="o">=</span><span class="nx">ReactDOMServer</span><span class="p">.</span><span class="nx">renderToString</span><span class="p">(</span><span class="nx">element</span><span class="p">);</span></code></pre>
</div>
<p>此時，用戶輸入：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">let</span><span class="nx">userProvidedData</span><span class="o">=</span><span class="s1">'&gt;&lt;/div&gt;&lt;script&gt;alert("hi")&lt;/script&gt;'</span><span class="p">;</span></code></pre>
</div>
<p>那麼最終生成的html就會變成：</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;&lt;</span><span class="err">/div&gt;&lt;script&gt;alert("hi")&lt;/script&gt;</span></code></pre>
</div>
<p>這就是我們常見的XSS攻擊。</p>
<p>但是現實中我們dom元素屬性需要依賴用戶輸入的場景非常的少，所以對於大部分應用來說沒有影響，<b>最重要的是意味著對大部分開發者都沒有影響，這樣我們就不用擔心要半夜起來改代碼</b>，還是可以的。</p>
<p class="ztext-empty-paragraph"></p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-3be204b2e2c9a22b05bc1a19498776a3_r.jpg" data-caption="" data-size="normal" data-rawwidth="1090" data-rawheight="1058" class="origin_image zh-lightbox-thumb" width="1090" data-original="https://pic4.zhimg.com/v2-3be204b2e2c9a22b05bc1a19498776a3_b.jpg" title="v2-3be204b2e2c9a22b05bc1a19498776a3_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-3be204b2e2c9a22b05bc1a19498776a3_r.jpg" data-caption="" data-size="normal" data-rawwidth="1090" data-rawheight="1058" class="origin_image zh-lightbox-thumb lazy" width="1090" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1090'%20height='1058'&gt;&lt;/svg&gt;" data-actualsrc="https://pic4.zhimg.com/v2-3be204b2e2c9a22b05bc1a19498776a3_b.jpg" title="v2-3be204b2e2c9a22b05bc1a19498776a3_r"></figure>
<p class="ztext-empty-paragraph"></p>
<h2><b>v16.5 React Profiler</b></h2>
<p>這個版本提供了對新的Profiler DevTools插件的支持。這個插件就厲害了，可以通過收集每個組件的渲染耗時，來幫助我們找到ReactApp的渲染瓶頸，並且整個界面更加清爽清晰，簡直是開發者的福利。</p>
<p>話不多說，下面來講解下如何使用：</p>
<ol>
<li>首先安裝<a href="https://link.zhihu.com/?target=https%3A//chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">React DevTools插件</a></li>
</ol>
<p>如果你的React版本已經升到16.5以上，那麼你的DevTools的界面會變成這樣： 打開第二個tab。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-a44d170f5d5c5b5e6ce671f7f5a0b84d_r.jpg" data-caption="" data-size="normal" data-rawwidth="1398" data-rawheight="588" class="origin_image zh-lightbox-thumb" width="1398" data-original="https://pic2.zhimg.com/v2-a44d170f5d5c5b5e6ce671f7f5a0b84d_b.jpg" title="v2-a44d170f5d5c5b5e6ce671f7f5a0b84d_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-a44d170f5d5c5b5e6ce671f7f5a0b84d_r.jpg" data-caption="" data-size="normal" data-rawwidth="1398" data-rawheight="588" class="origin_image zh-lightbox-thumb lazy" width="1398" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1398'%20height='588'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-a44d170f5d5c5b5e6ce671f7f5a0b84d_b.jpg" title="v2-a44d170f5d5c5b5e6ce671f7f5a0b84d_r"></figure>
<p>2.點擊中間這個button開始記錄</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-583dadb46457fc361f0a7136a49ee2c9_r.jpg" data-caption="" data-size="normal" data-rawwidth="1392" data-rawheight="576" class="origin_image zh-lightbox-thumb" width="1392" data-original="https://pic2.zhimg.com/v2-583dadb46457fc361f0a7136a49ee2c9_b.jpg" title="v2-583dadb46457fc361f0a7136a49ee2c9_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-583dadb46457fc361f0a7136a49ee2c9_r.jpg" data-caption="" data-size="normal" data-rawwidth="1392" data-rawheight="576" class="origin_image zh-lightbox-thumb lazy" width="1392" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1392'%20height='576'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-583dadb46457fc361f0a7136a49ee2c9_b.jpg" title="v2-583dadb46457fc361f0a7136a49ee2c9_r"></figure>
<p>3.接著操作你想要記錄的操作，完事之後點stop</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-041e6c0b15edbabdd6a2159b45566ffc_r.jpg" data-caption="" data-size="normal" data-rawwidth="1458" data-rawheight="606" class="origin_image zh-lightbox-thumb" width="1458" data-original="https://pic1.zhimg.com/v2-041e6c0b15edbabdd6a2159b45566ffc_b.jpg" title="v2-041e6c0b15edbabdd6a2159b45566ffc_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-041e6c0b15edbabdd6a2159b45566ffc_r.jpg" data-caption="" data-size="normal" data-rawwidth="1458" data-rawheight="606" class="origin_image zh-lightbox-thumb lazy" width="1458" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1458'%20height='606'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-041e6c0b15edbabdd6a2159b45566ffc_b.jpg" title="v2-041e6c0b15edbabdd6a2159b45566ffc_r"></figure>
<p>4.看結果圖</p>
<p>在講解之前，先普及一些知識。知道Fiber的同學應該都了解，現在React渲染過程分為兩個階段：</p>
<p><b>一、render階段</b></p>
<p>這個階段主要是對比，有那些DOM節點需要更新。</p>
<p><b>二、commit階段</b></p>
<p>將第一階段收集的訊息更新到真實節點，完成之後會調用componentDidMount跟componentDidUpdate。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-af0079fa1a8f60384c8fa2ba87fb1886_r.jpg" data-caption="" data-size="normal" data-rawwidth="1812" data-rawheight="772" class="origin_image zh-lightbox-thumb" width="1812" data-original="https://pic3.zhimg.com/v2-af0079fa1a8f60384c8fa2ba87fb1886_b.jpg" title="v2-af0079fa1a8f60384c8fa2ba87fb1886_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-af0079fa1a8f60384c8fa2ba87fb1886_r.jpg" data-caption="" data-size="normal" data-rawwidth="1812" data-rawheight="772" class="origin_image zh-lightbox-thumb lazy" width="1812" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1812'%20height='772'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-af0079fa1a8f60384c8fa2ba87fb1886_b.jpg" title="v2-af0079fa1a8f60384c8fa2ba87fb1886_r"></figure>
<p>接著看結果圖。</p>
<p>圖示一的地方顯示的是每一次commit的耗時，其中黑色表示當前選中的commit，可以左右移動來選擇，其中柱子越高說明這次的commit耗時約多。</p>
<p>圖示二表示的是這次commit發生在第幾S，它的render階段耗時多少。</p>
<p>圖示三表示的是這次commit裡每一個組件的耗時。由圖中我們可以看到Router耗時最多，達到18.4ms。而耗時主要來源於Nav 跟Route組件。如果你去點擊每個組件，還可以在右邊看到這個組件此時的state跟props，那就可以很清晰的知道這個組件此刻在渲染什麼。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-4e869717466fd0a63680b5b2d1f28471_r.jpg" data-caption="" data-size="normal" data-rawwidth="2034" data-rawheight="792" class="origin_image zh-lightbox-thumb" width="2034" data-original="https://pic2.zhimg.com/v2-4e869717466fd0a63680b5b2d1f28471_b.jpg" title="v2-4e869717466fd0a63680b5b2d1f28471_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-4e869717466fd0a63680b5b2d1f28471_r.jpg" data-caption="" data-size="normal" data-rawwidth="2034" data-rawheight="792" class="origin_image zh-lightbox-thumb lazy" width="2034" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2034'%20height='792'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-4e869717466fd0a63680b5b2d1f28471_b.jpg" title="v2-4e869717466fd0a63680b5b2d1f28471_r"></figure>
<p><b>小技巧</b></p>
<p>再說最後一個小技巧，你可以選中某個組件，然後點擊右上角兩次相鄰的commit，這樣你就知道是哪個state的改變引發了這次的re-render。比如下面的例子，就是scrollOffset的變化導致整個App的變化。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-d29bdd18d8efa8bed39beb673fd8aed8_r.jpg" data-caption="" data-size="normal" data-rawwidth="1972" data-rawheight="796" class="origin_image zh-lightbox-thumb" width="1972" data-original="https://pic1.zhimg.com/v2-d29bdd18d8efa8bed39beb673fd8aed8_b.jpg" title="v2-d29bdd18d8efa8bed39beb673fd8aed8_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-d29bdd18d8efa8bed39beb673fd8aed8_r.jpg" data-caption="" data-size="normal" data-rawwidth="1972" data-rawheight="796" class="origin_image zh-lightbox-thumb lazy" width="1972" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1972'%20height='796'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-d29bdd18d8efa8bed39beb673fd8aed8_b.jpg" title="v2-d29bdd18d8efa8bed39beb673fd8aed8_r"></figure>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-90d0bc073d7f17ef18a4326d9ce0d389_r.jpg" data-caption="" data-size="normal" data-rawwidth="2126" data-rawheight="750" class="origin_image zh-lightbox-thumb" width="2126" data-original="https://pic2.zhimg.com/v2-90d0bc073d7f17ef18a4326d9ce0d389_b.jpg" title="v2-90d0bc073d7f17ef18a4326d9ce0d389_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-90d0bc073d7f17ef18a4326d9ce0d389_r.jpg" data-caption="" data-size="normal" data-rawwidth="2126" data-rawheight="750" class="origin_image zh-lightbox-thumb lazy" width="2126" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2126'%20height='750'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-90d0bc073d7f17ef18a4326d9ce0d389_b.jpg" title="v2-90d0bc073d7f17ef18a4326d9ce0d389_r"></figure>
<p>以上就是一些基本使用，關於實際例子的演示，還挺多的，可以單獨寫一篇文章了。有時間的話在給大家介紹。</p>
<h2><b>v16.6</b></h2>
<h2><b>memo</b></h2>
<p><b>React 15</b> ：如果你想阻止組件的重複渲染，在class component裡可以使用PureComponent, shouldComponentUpdate來幫助你。但是如果你是function component，對不起，沒有這個功能， 只能每次都重新渲染。</p>
<p><b>React 16</b> ：為了全面擁抱function component,React團隊寫了memo來幫助function component實現這個阻止重複渲染的功能。</p>
<p><a href="https://link.zhihu.com/?target=https%3A//codesandbox.io/s/k97pn44k4o" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">demo</a></p>
<p>在這個demo可以看到，如果沒有memo，每次點擊button控制台都會打印render表示重新渲染。但是加了memo之後，就不會了。</p>
<h2><b>lazy、suspense</b></h2>
<p>lazy需要跟Suspence配合使用，所以這裡放在一起介紹。</p>
<p>lazy實際上是幫助我們實現代碼分割的功能，使用過webpack的同學都知道，webpack也有這個功能。那為什麼他們都要做這個功能呢？</p>
<p>其實是這樣的：由於有些內容，我們並不一定要在首屏展示，所以這些資源我們沒有必要一開始就要去獲取，那麼這些資源就可以動態獲取。這樣的話，相當於把不需要首屏展示的代碼分割出來，減少首屏代碼的體積，提升性能。</p>
<p><a href="https://link.zhihu.com/?target=https%3A//codesandbox.io/s/jz98v20r7y" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">demo</a></p>
<p>在這個demo中，有3個tab。但是我們的首屏只需要先展示一個，所以其他的可以動態引入。這里以動態引入B為例：</p>
<div class="highlight">
<pre><code class="language-js"><span class="c">&lt;!--</span><span class="kr">import</span><span class="nx">B</span><span class="nx">from</span><span class="s2">"./B"</span><span class="p">;</span><span class="o">--&gt;</span><span class="c1">// 需要用到的时候才加载进来，当然还有预加载更好
</span><span class="kr">const</span><span class="nx">B</span><span class="o">=</span><span class="nx">lazy</span><span class="p">(()</span><span class="p">=&gt;</span><span class="kr">import</span><span class="p">(</span><span class="s2">"./B"</span><span class="p">));</span></code></pre>
</div>
<p>但是這樣的話，一開始就可以點擊B，會報錯。因為需要當組件還在加載渲染的時候，需要一個place holder防止組件還沒加載完畢的時候可以有東西顯示給用戶。</p>
<p>這時候Suspence得作用就出來了。 Suspence 很像Error Boundary，不同的是Error Boundary是用來捕獲錯誤，顯示相應的callback組件。而Suspence是用來捕獲還沒有加載好的組件，並暫停渲染，顯示相應的callback。</p>
<p>我們給B加上Suspense 就搞定了。</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">Suspense</span><span class="nx">fallback</span><span class="o">=</span><span class="p">{</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">Loading</span><span class="p">...</span><span class="o">&lt;</span><span class="err">/div&gt;}&gt;</span><span class="o">&lt;</span><span class="nx">TabPanel</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">B</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/TabPanel&gt;</span><span class="o">&lt;</span><span class="err">/Suspense&gt;</span></code></pre>
</div>
<p>跟Error Boundary一樣，Suspence也有一個放置位置的問題，是整個包裹在App外，還是只是給單獨的組件包裹？</p>
<p>我的選擇與Error Boundary依舊一致，應該將其包裹在子組件外面，因為這樣當某個組件沒有加載好的時候，不會影響到整個App都顯示一個loading的標識。</p>
<p><b>注意：</b></p>
<ol>
<li>SSR不支持lazy這個特性。</li>
<li> Lazy 必須搭配Suspence使用，否則會報錯</li>
</ol>
<p><b>進一步優化：</b></p>
<p>這裡我們在進一步思考一個點，目前我們的B組件是需要用到的時候才加載。萬一這個組件需要獲取數據，使得他顯示比較慢，就會顯示loading，導致我們用戶體驗比較差呢。所以我們可否在瀏覽器閒著的時候預加載這些即將要用到資源？</p>
<p>答案是可以的，React團隊也在做這件事情。但是這個API目前為止還沒有Ready，大家先知道這個事情好了~</p>
<h2><b>簡化static contextType</b></h2>
<p>簡化獲取context的方式，之前需要用一個在外層包裹一個<code>&lt;Consumer&gt;</code> ,如下：</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">// Theme context, default to light theme</span><span class="kr">const</span><span class="nx">ThemeContext</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">createContext</span><span class="p">(</span><span class="s1">'light'</span><span class="p">);</span><span class="c1">// Signed-in user context</span><span class="kr">const</span><span class="nx">UserContext</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">createContext</span><span class="p">({</span><span class="nx">name</span><span class="o">:</span><span class="s1">'Guest'</span><span class="p">,</span><span class="p">});</span><span class="kr">class</span><span class="nx">App</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="kr">const</span><span class="p">{</span><span class="nx">signedInUser</span><span class="p">,</span><span class="nx">theme</span><span class="p">}</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">;</span><span class="c1">// App component that provides initial context values</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">ThemeContext</span><span class="p">.</span><span class="nx">Provider</span><span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">theme</span><span class="p">}</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">UserContext</span><span class="p">.</span><span class="nx">Provider</span><span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">signedInUser</span><span class="p">}</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">Layout</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/UserContext.Provider&gt;</span><span class="o">&lt;</span><span class="err">/ThemeContext.Provider&gt;</span><span class="p">);</span><span class="p">}</span><span class="p">}</span><span class="kd">function</span><span class="nx">Layout</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">Sidebar</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">Content</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span><span class="c1">// A component may consume multiple contexts</span><span class="c1">//同時如果是function component用Consumer</span><span class="kd">function</span><span class="nx">Content</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">ThemeContext</span><span class="p">.</span><span class="nx">Consumer</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">theme</span><span class="p">=&gt;</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">UserContext</span><span class="p">.</span><span class="nx">Consumer</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">user</span><span class="p">=&gt;</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">ProfilePage</span><span class="nx">user</span><span class="o">=</span><span class="p">{</span><span class="nx">user</span><span class="p">}</span><span class="nx">theme</span><span class="o">=</span><span class="p">{</span><span class="nx">theme</span><span class="p">}</span><span class="o">/&gt;</span><span class="p">)}</span><span class="o">&lt;</span><span class="err">/UserContext.Consumer&gt;</span><span class="p">)}</span><span class="o">&lt;</span><span class="err">/ThemeContext.Consumer&gt;</span><span class="p">);</span><span class="p">}</span></code></pre>
</div>
<p>現在可以直接通過this.context獲取。</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">MyClass</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="kr">static</span><span class="nx">contextType</span><span class="o">=</span><span class="nx">MyContext</span><span class="p">;</span><span class="nx">componentDidMount</span><span class="p">()</span><span class="p">{</span><span class="kd">let</span><span class="nx">value</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">;</span><span class="cm">/* perform a side-effect at mount using the value of MyContext */</span><span class="p">}</span><span class="nx">componentDidUpdate</span><span class="p">()</span><span class="p">{</span><span class="kd">let</span><span class="nx">value</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">;</span><span class="cm">/* ... */</span><span class="p">}</span><span class="nx">componentWillUnmount</span><span class="p">()</span><span class="p">{</span><span class="kd">let</span><span class="nx">value</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">;</span><span class="cm">/* ... */</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="kd">let</span><span class="nx">value</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">;</span><span class="cm">/* render something based on the value of MyContext */</span><span class="p">}</span><span class="p">}</span><span class="nx">MyClass</span><span class="p">.</span><span class="nx">contextType</span><span class="o">=</span><span class="nx">MyContext</span><span class="p">;</span></code></pre>
</div>
<h2><b>新增static getDerivedStateFromError</b></h2>
<p>v16.3這個版本里，React 除了Error Boundaries來捕獲錯誤，裡面主要是使用了componentDidCatch來捕獲錯誤。<b>但是它是在錯誤已經發生之後並且render函數被調用之後，才會被調用。</b>也就是說如果一個組件出現的錯誤，在調用componentDidCatch之前只能返回null給用戶。<br />而getDerivedStateFromError 可以在render函數之嵌捕獲到錯誤，所以它更適合寫用來顯示fallback UI的邏輯。</p>
<p>注意事項： componentDidCatch,getDerivedStateFromError都無法捕獲服務端的錯誤，但是React團隊正在努力支持SSR。</p>
<p>改進前的ErrorBoundary:</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">ErrorBoundary</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">hasError</span><span class="o">:</span><span class="kc">false</span><span class="p">};</span><span class="nx">componentDidCatch</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span><span class="nx">info</span><span class="p">)</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">hasError</span><span class="o">:</span><span class="kc">false</span><span class="p">})</span><span class="nx">logErrorToMyService</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span><span class="nx">info</span><span class="p">);</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">hasError</span><span class="p">)</span><span class="p">{</span><span class="c1">// You can render any custom fallback UI</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="nx">Something</span><span class="nx">went</span><span class="nx">wrong</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/h1&gt;;</span><span class="p">}</span><span class="k">return</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">;</span><span class="p">}</span></code></pre>
</div>
<p>改進後的ErrorBoundary（推薦寫法）：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">ErrorBoundary</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">hasError</span><span class="o">:</span><span class="kc">false</span><span class="p">};</span><span class="kr">static</span><span class="nx">getDerivedStateFromError</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="p">{</span><span class="c1">// Update state so the next render will show the fallback UI.</span><span class="c1">//更新state所以下次render可以立刻顯示fallback UI</span><span class="k">return</span><span class="p">{</span><span class="nx">hasError</span><span class="o">:</span><span class="kc">true</span><span class="p">};</span><span class="p">}</span><span class="nx">componentDidCatch</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span><span class="nx">info</span><span class="p">)</span><span class="p">{</span><span class="c1">// You can also log the error to an error reporting service</span><span class="nx">logErrorToMyService</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span><span class="nx">info</span><span class="p">);</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">hasError</span><span class="p">)</span><span class="p">{</span><span class="c1">// You can render any custom fallback UI</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="nx">Something</span><span class="nx">went</span><span class="nx">wrong</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/h1&gt;;</span><span class="p">}</span><span class="k">return</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">;</span><span class="p">}</span></code></pre>
</div>
<p>以上就是v16.4 ~ v16.6的全部內容，考慮到大家應該比較累了，Hooks又是比較大的特性，所以還是分到下一篇吧~</p>
<p>本文對你有幫助的話，點個贊吧~</p>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14377/topic-59518164/" data-wpel-link="internal">React 16 新特性全解（中）</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>React 16 新特性全解（上）</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/14348/topic-57544233/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 17:59:52 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/14348/topic-57544233/</guid>

					<description><![CDATA[<p>前言本次系列分上下兩篇文章，上主要介紹從v16.0~ 16.4的新特性，下主要介紹16.5~16.8。下面就開始吧~ 本篇文章較長預計需要15min（當然主要是因為demo太多），大家可以搞點瓜子邊啃邊看。最好能留出一隻手自己在c…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14348/topic-57544233/" data-wpel-link="internal">React 16 新特性全解（上）</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">React 16 新特性全解（上） </h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic4.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2><b>前言</b></h2>
<p>本次系列分上下兩篇文章，上主要介紹從v16.0~ 16.4的新特性，下主要介紹16.5~16.8。下面就開始吧~</p>
<p>本篇文章較長預計需要15min（當然主要是因為demo太多），大家可以搞點瓜子邊啃邊看。最好能留出一隻手自己在codePen上自己調試一下。</p>
<h2><b>目錄</b></h2>
<p><b>v16.0</b></p>
<ol>
<li>render支持返回數組和字符串<a href="https://link.zhihu.com/?target=https%3A//codepen.io/anon/pen/yZderP%3Feditors%3D1000" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">演示</a></li>
<li>Error Boundary</li>
<li> createPortal</li>
<li>支持自定義DOM 屬性</li>
<li>Fiber</li>
<li>提升SSR渲染速度</li>
<li>減小文件體積</li>
</ol>
<p><b>v16.1</b></p>
<p>react-call-return</p>
<p><b>v16.2</b></p>
<p>Fragment</p>
<p><b>v16.3</b></p>
<ol>
<li>生命週期函數的更新</li>
<li>createContext</li>
<li> createRef</li>
<li> forwardRef</li>
<li> strict Mode</li>
</ol>
<p>下面就開始吧~</p>
<h2><b>v16.0</b></h2>
<p><b>主要特性：</b></p>
<p><b>一、render可以返回字符串，數組，數字</b></p>
<p><b>React 15</b> :只可以返回單一組件，也就是說即使你返回的是一個string，也需要用div包住。</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">MyComponent</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">hello</span><span class="nx">world</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="p">);</span><span class="p">}</span></code></pre>
</div>
<p><b>React 16:</b>支持返回這五類：React elements,數組和Fragments，Portal，String/numbers，boolean/null。</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">Example</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">[</span><span class="o">&lt;</span><span class="nx">div</span><span class="nx">key</span><span class="o">=</span><span class="s2">"1"</span><span class="o">&gt;</span><span class="nx">first</span><span class="nx">element</span><span class="o">&lt;</span><span class="err">/div&gt;,</span><span class="o">&lt;</span><span class="nx">div</span><span class="nx">key</span><span class="o">=</span><span class="s2">"2"</span><span class="o">&gt;</span><span class="nx">second</span><span class="nx">element</span><span class="o">&lt;</span><span class="err">/div&gt;,</span><span class="p">];</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>注意：無論返回的形式是怎麼樣的，都要保持render是一個純函數。所以要求我們不要改state的狀態，同時不要直接跟瀏覽器直接交互，讓它每次調用生成的結果都是一致的。</p>
<p><b>二、Error boundary（錯誤邊界）</b></p>
<p><b>React 15</b> ：渲染過程中有出錯，直接crash整個頁面，並且錯誤訊息不明確，可讀性差</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">BuggyCounter</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">counter</span><span class="o">:</span><span class="mi">0</span><span class="p">};</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="p">}</span><span class="nx">componentWillMount</span><span class="p">()</span><span class="p">{</span><span class="k">throw</span><span class="k">new</span><span class="nb">Error</span><span class="p">(</span><span class="s1">'I am crash'</span><span class="p">);</span><span class="p">}</span><span class="nx">handleClick</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">(({</span><span class="nx">counter</span><span class="p">})</span><span class="p">=&gt;</span><span class="p">({</span><span class="nx">counter</span><span class="o">:</span><span class="nx">counter</span><span class="o">+</span><span class="mi">1</span><span class="p">}));</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">counter</span><span class="o">===</span><span class="mi">5</span><span class="p">)</span><span class="p">{</span><span class="c1">// Simulate a JS error</span><span class="k">throw</span><span class="k">new</span><span class="nb">Error</span><span class="p">(</span><span class="s1">'I crashed!'</span><span class="p">);</span><span class="p">}</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">h1</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="p">}</span><span class="o">&gt;</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">counter</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/h1&gt;;</span><span class="p">}</span><span class="p">}</span><span class="kd">function</span><span class="nx">App</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">b</span><span class="o">&gt;</span><span class="nx">This</span><span class="nx">is</span><span class="nx">an</span><span class="nx">example</span><span class="k">of</span><span class="nx">error</span><span class="nx">boundaries</span><span class="k">in</span><span class="nx">React</span><span class="mf">16.</span><span class="o">&lt;</span><span class="nx">br</span><span class="o">/&gt;&lt;</span><span class="nx">br</span><span class="o">/&gt;</span><span class="nx">Click</span><span class="nx">on</span><span class="nx">the</span><span class="nx">numbers</span><span class="nx">to</span><span class="nx">increase</span><span class="nx">the</span><span class="nx">counters</span><span class="p">.</span><span class="o">&lt;</span><span class="nx">br</span><span class="o">/&gt;</span><span class="nx">The</span><span class="nx">counter</span><span class="nx">is</span><span class="nx">programmed</span><span class="nx">to</span><span class="k">throw</span><span class="nx">when</span><span class="nx">it</span><span class="nx">reaches</span><span class="mf">5.</span><span class="nx">This</span><span class="nx">simulates</span><span class="nx">a</span><span class="nx">JavaScript</span><span class="nx">error</span><span class="k">in</span><span class="nx">a</span><span class="nx">component</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/b&gt;</span><span class="o">&lt;</span><span class="err">/p&gt;</span><span class="o">&lt;</span><span class="nx">hr</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">These</span><span class="nx">two</span><span class="nx">counters</span><span class="nx">are</span><span class="nx">inside</span><span class="nx">the</span><span class="nx">same</span><span class="nx">error</span><span class="nx">boundary</span><span class="p">.</span><span class="nx">If</span><span class="nx">one</span><span class="nx">crashes</span><span class="p">,</span><span class="nx">the</span><span class="nx">error</span><span class="nx">boundary</span><span class="nx">will</span><span class="nx">replace</span><span class="nx">both</span><span class="k">of</span><span class="nx">them</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/p&gt;</span><span class="o">&lt;</span><span class="nx">BuggyCounter</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">hr</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span><span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">App</span><span class="o">/&gt;</span><span class="p">,</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'root'</span><span class="p">)</span><span class="p">);</span></code></pre>
</div>
<p><a href="https://link.zhihu.com/?target=https%3A//codepen.io/anon/pen/ErBjZM%3Feditors%3D0010" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">demo地址</a></p>
<p>比如上面這個App，可以看到子組件BuggyCounter出了點問題，在沒有Error Boundary的時候，整個App都會crash掉，所以顯示白屏。</p>
<p><b>React 16</b> ：用於捕獲<b>子組件樹的</b>JS異常（即錯誤邊界只可以捕獲組件在樹中比他低的組件錯誤。），記錄錯誤並展示一個回退的UI。</p>
<p>捕獲範圍：</p>
<ol>
<li>渲染期間</li>
<li>生命週期內</li>
<li>整個組件樹構造函數內</li>
</ol>
<p>如何使用：</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">//先定一個組件ErrorBoundary</span><span class="kr">class</span><span class="nx">ErrorBoundary</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">error</span><span class="o">:</span><span class="kc">null</span><span class="p">,</span><span class="nx">errorInfo</span><span class="o">:</span><span class="kc">null</span><span class="p">};</span><span class="p">}</span><span class="nx">componentDidCatch</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span><span class="nx">errorInfo</span><span class="p">)</span><span class="p">{</span><span class="c1">// Catch errors in any components below and re-render with error message</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">error</span><span class="o">:</span><span class="nx">error</span><span class="p">,</span><span class="nx">errorInfo</span><span class="o">:</span><span class="nx">errorInfo</span><span class="p">})</span><span class="c1">// You can also log error messages to an error reporting service here</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="c1">//有錯誤的時候展示回退</span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">errorInfo</span><span class="p">)</span><span class="p">{</span><span class="c1">// Error path</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">h2</span><span class="o">&gt;</span><span class="nx">Something</span><span class="nx">went</span><span class="nx">wrong</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/h2&gt;</span><span class="o">&lt;</span><span class="nx">details</span><span class="nx">style</span><span class="o">=</span><span class="p">{{</span><span class="nx">whiteSpace</span><span class="o">:</span><span class="s1">'pre-wrap'</span><span class="p">}}</span><span class="o">&gt;</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">error</span><span class="o">&amp;&amp;</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">toString</span><span class="p">()}</span><span class="o">&lt;</span><span class="nx">br</span><span class="o">/&gt;</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">errorInfo</span><span class="p">.</span><span class="nx">componentStack</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/details&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span><span class="c1">//正常的話，直接展示組件</span><span class="k">return</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="kr">class</span><span class="nx">BuggyCounter</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">counter</span><span class="o">:</span><span class="mi">0</span><span class="p">};</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="p">}</span><span class="nx">componentWillMount</span><span class="p">()</span><span class="p">{</span><span class="k">throw</span><span class="k">new</span><span class="nb">Error</span><span class="p">(</span><span class="s1">'I am crash'</span><span class="p">);</span><span class="p">}</span><span class="nx">handleClick</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">(({</span><span class="nx">counter</span><span class="p">})</span><span class="p">=&gt;</span><span class="p">({</span><span class="nx">counter</span><span class="o">:</span><span class="nx">counter</span><span class="o">+</span><span class="mi">1</span><span class="p">}));</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">counter</span><span class="o">===</span><span class="mi">5</span><span class="p">)</span><span class="p">{</span><span class="c1">// Simulate a JS error</span><span class="k">throw</span><span class="k">new</span><span class="nb">Error</span><span class="p">(</span><span class="s1">'I crashed!'</span><span class="p">);</span><span class="p">}</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">h1</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="p">}</span><span class="o">&gt;</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">counter</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/h1&gt;;</span><span class="p">}</span><span class="p">}</span><span class="kd">function</span><span class="nx">App</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">b</span><span class="o">&gt;</span><span class="nx">This</span><span class="nx">is</span><span class="nx">an</span><span class="nx">example</span><span class="k">of</span><span class="nx">error</span><span class="nx">boundaries</span><span class="k">in</span><span class="nx">React</span><span class="mf">16.</span><span class="o">&lt;</span><span class="nx">br</span><span class="o">/&gt;&lt;</span><span class="nx">br</span><span class="o">/&gt;</span><span class="nx">Click</span><span class="nx">on</span><span class="nx">the</span><span class="nx">numbers</span><span class="nx">to</span><span class="nx">increase</span><span class="nx">the</span><span class="nx">counters</span><span class="p">.</span><span class="o">&lt;</span><span class="nx">br</span><span class="o">/&gt;</span><span class="nx">The</span><span class="nx">counter</span><span class="nx">is</span><span class="nx">programmed</span><span class="nx">to</span><span class="k">throw</span><span class="nx">when</span><span class="nx">it</span><span class="nx">reaches</span><span class="mf">5.</span><span class="nx">This</span><span class="nx">simulates</span><span class="nx">a</span><span class="nx">JavaScript</span><span class="nx">error</span><span class="k">in</span><span class="nx">a</span><span class="nx">component</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/b&gt;</span><span class="o">&lt;</span><span class="err">/p&gt;</span><span class="o">&lt;</span><span class="nx">hr</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">ErrorBoundary</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">These</span><span class="nx">two</span><span class="nx">counters</span><span class="nx">are</span><span class="nx">inside</span><span class="nx">the</span><span class="nx">same</span><span class="nx">error</span><span class="nx">boundary</span><span class="p">.</span><span class="nx">If</span><span class="nx">one</span><span class="nx">crashes</span><span class="p">,</span><span class="nx">the</span><span class="nx">error</span><span class="nx">boundary</span><span class="nx">will</span><span class="nx">replace</span><span class="nx">both</span><span class="k">of</span><span class="nx">them</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/p&gt;</span><span class="o">&lt;</span><span class="nx">BuggyCounter</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/ErrorBoundary&gt;</span><span class="o">&lt;</span><span class="nx">hr</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span><span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">App</span><span class="o">/&gt;</span><span class="p">,</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'root'</span><span class="p">)</span><span class="p">);</span></code></pre>
</div>
<p><a href="https://link.zhihu.com/?target=https%3A//codepen.io/anon/pen/ErBjZM%3Feditors%3D0010" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">demo演示：</a></p>
<p>可以看到加上Error Boundary之後，除了出錯的組件，其他的地方都不受影響。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-60b7261bdb0e8e902e357d03cd5a7119_r.jpg" data-rawwidth="2166" data-rawheight="714" data-size="normal" data-caption="" class="origin_image zh-lightbox-thumb" width="2166" data-original="https://pic2.zhimg.com/v2-60b7261bdb0e8e902e357d03cd5a7119_b.jpg" title="v2-60b7261bdb0e8e902e357d03cd5a7119_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-60b7261bdb0e8e902e357d03cd5a7119_r.jpg" data-rawwidth="2166" data-rawheight="714" data-size="normal" data-caption="" class="origin_image zh-lightbox-thumb lazy" width="2166" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2166'%20height='714'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-60b7261bdb0e8e902e357d03cd5a7119_b.jpg" title="v2-60b7261bdb0e8e902e357d03cd5a7119_r"></figure>
<p>而且它很清晰的告訴我們是哪個組件發生了錯誤。</p>
<p><b>注意事項：</b></p>
<p>Error Boundary無法捕獲下面的錯誤：</p>
<p>1、事件函數里的錯誤</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">MyComponent</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">error</span><span class="o">:</span><span class="kc">null</span><span class="p">};</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="p">}</span><span class="nx">handleClick</span><span class="p">()</span><span class="p">{</span><span class="k">try</span><span class="p">{</span><span class="c1">// Do something that could throw</span><span class="p">}</span><span class="k">catch</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">error</span><span class="p">});</span><span class="p">}</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">error</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">h1</span><span class="o">&gt;</span><span class="nx">Caught</span><span class="nx">an</span><span class="nx">error</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/h1&gt;</span><span class="p">}</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">div</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Click</span><span class="nx">Me</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>上面的例子中，handleClick方法裡面發生的錯誤，Error Boundary是捕獲不到的。因為它不發生在渲染階段，所以採用try/catch來捕獲。</p>
<p>2、異步代碼（例如setTimeout 或requestAnimationFrame 回調函數）</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span> <span class="nx">A</span> <span class="kr">extends</span> <span class="nx">React</span> <span class="p">.</span> <span class="nx">Component</span> <span class="p">{</span> <span class="nx">render</span> <span class="p">()</span> <span class="p">{</span> <span class="c1">// 此错误无法被捕获，渲染时组件正常返回`&lt;div&gt;&lt;/div&gt;`</span> <span class="nx">setTimeout</span> <span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span> <span class="p">(</span> <span class="s1">'error'</span> <span class="p">)</span> <span class="p">},</span> <span class="mi">1000</span> <span class="p">)</span> <span class="k">return</span> <span class="p">(</span> <span class="o">&lt;</span> <span class="nx">div</span> <span class="o">&gt;&lt;</span> <span class="err">/div&gt;</span> <span class="p">)</span> <span class="p">}</span> <span class="p">}</span></code></pre>
</div>
<p>3、服務端渲染</p>
<p>因為服務器渲染不支持Error Boundary</p>
<p>4、Error Boundary自身拋出來的錯誤（而不是其子組件）</p>
<p>那這裡還遺留一個問題？錯誤邊界放在哪裡。一般來說，有兩個地方：</p>
<p>1、可以放在頂層，告訴用戶有東西出錯。但是我個人不建議這樣，這感覺失去了錯誤邊界的意義。因為有一個組件出錯了，其他正常的也沒辦法正常顯示了</p>
<p>2、包在子組件外面，保護其他應用不崩潰。</p>
<h2><b>三、react portal</b></h2>
<p>在介紹這個新特性之前，我們先來看看為什麼需要portal。在沒有portal之前，如果我們需要寫一個Dialog組件，我們會這樣寫。</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">div</span><span class="kr">class</span><span class="o">=</span><span class="s2">"app"</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="p">...</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">{</span><span class="nx">needDialog</span><span class="o">?</span><span class="o">&lt;</span><span class="nx">Dialog</span><span class="o">/&gt;</span><span class="o">:</span><span class="kc">null</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/div&gt;</span></code></pre>
</div>
<p>問題：</p>
<p>1、最終渲染產生的html存在於JSX產生的HTML在一起，這時候dialog 如果需要position：absolute 控制位置的話，需要保證dialog 往上沒有position：relative 的干擾。</p>
<p>2、層級關係不清晰，dialog實際是獨立在app之外的。</p>
<p>所以這時候Portal降臨。</p>
<p>Portal可以幫助我們在JSX中跟普通組件一樣直接使用dialog, 但是又可以讓dialog內容層級不在父組件內，而是顯示在獨立於原來app在外的同層級組件。</p>
<p><b>如何使用：</b></p>
<p>HTML:</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">div</span><span class="nx">id</span><span class="o">=</span><span class="s2">"app-root"</span><span class="o">&gt;&lt;</span><span class="err">/div&gt;</span><span class="c1">// 这里为我们定义Dialog想要放入的位置
</span><span class="o">&lt;</span><span class="nx">div</span><span class="nx">id</span><span class="o">=</span><span class="s2">"modal-root"</span><span class="o">&gt;&lt;</span><span class="err">/div&gt;</span></code></pre>
</div>
<p>JS:</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">// These two containers are siblings in the DOM</span><span class="kr">const</span><span class="nx">appRoot</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'app-root'</span><span class="p">);</span><span class="kr">const</span><span class="nx">modalRoot</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'modal-root'</span><span class="p">);</span><span class="c1">// Let's create a Modal component that is an abstraction around</span><span class="c1">// the portal API.</span><span class="kr">class</span><span class="nx">Modal</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="c1">// Create a div that we'll render the modal into. Because each</span><span class="c1">// Modal component has its own element, we can render multiple</span><span class="c1">// modal components into the modal container.</span><span class="k">this</span><span class="p">.</span><span class="nx">el</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">'div'</span><span class="p">);</span><span class="p">}</span><span class="nx">componentDidMount</span><span class="p">()</span><span class="p">{</span><span class="c1">// Append the element into the DOM on mount. We'll render</span><span class="c1">// into the modal container element (see the HTML tab).</span><span class="c1">//這邊會將我們生成的portal element插入到modal-root裡。</span><span class="nx">modalRoot</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">el</span><span class="p">);</span><span class="p">}</span><span class="nx">componentWillUnmount</span><span class="p">()</span><span class="p">{</span><span class="c1">// Remove the element from the DOM when we unmount</span><span class="nx">modalRoot</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">el</span><span class="p">);</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="c1">// Use a portal to render the children into the element</span><span class="k">return</span><span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">createPortal</span><span class="p">(</span><span class="c1">// Any valid React child: JSX, strings, arrays, etc.</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">,</span><span class="c1">// A DOM element</span><span class="k">this</span><span class="p">.</span><span class="nx">el</span><span class="p">,</span><span class="p">);</span><span class="p">}</span><span class="p">}</span><span class="c1">// The Modal component is a normal React component, so we can</span><span class="c1">// render it wherever we like without needing to know that it's</span><span class="c1">// implemented with portals.</span><span class="kr">class</span><span class="nx">App</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="o">=</span><span class="p">{</span><span class="nx">showModal</span><span class="o">:</span><span class="kc">false</span><span class="p">};</span><span class="k">this</span><span class="p">.</span><span class="nx">handleShow</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">handleShow</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">handleHide</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">handleHide</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="p">}</span><span class="nx">handleShow</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">showModal</span><span class="o">:</span><span class="kc">true</span><span class="p">});</span><span class="p">}</span><span class="nx">handleHide</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">showModal</span><span class="o">:</span><span class="kc">false</span><span class="p">});</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="c1">// Show a Modal on click.</span><span class="c1">// (In a real app, don't forget to use ARIA attributes</span><span class="c1">// for accessibility!)</span><span class="kr">const</span><span class="nx">modal</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">showModal</span><span class="o">?</span><span class="p">(</span><span class="c1">//注意~~~~~~~~~~~~~這裡可以自行加上這個調試</span><span class="c1">// &lt;Modal&gt;</span><span class="o">&lt;</span><span class="nx">div</span><span class="nx">className</span><span class="o">=</span><span class="s2">"modal"</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">With</span><span class="nx">a</span><span class="nx">portal</span><span class="p">,</span><span class="nx">we</span><span class="nx">can</span><span class="nx">render</span><span class="nx">content</span><span class="nx">into</span><span class="nx">a</span><span class="nx">different</span><span class="nx">part</span><span class="k">of</span><span class="nx">the</span><span class="nx">DOM</span><span class="p">,</span><span class="nx">as</span><span class="k">if</span><span class="nx">it</span><span class="nx">were</span><span class="nx">any</span><span class="nx">other</span><span class="nx">React</span><span class="nx">child</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="nx">This</span><span class="nx">is</span><span class="nx">being</span><span class="nx">rendered</span><span class="nx">inside</span><span class="nx">the</span><span class="err">#</span><span class="nx">modal</span><span class="o">-</span><span class="nx">container</span><span class="nx">div</span><span class="p">.</span><span class="o">&lt;</span><span class="nx">button</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleHide</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Hide</span><span class="nx">modal</span><span class="o">&lt;</span><span class="err">/button&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="c1">//&lt;/Modal&gt;</span><span class="p">)</span><span class="o">:</span><span class="kc">null</span><span class="p">;</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="nx">className</span><span class="o">=</span><span class="s2">"app"</span><span class="o">&gt;</span><span class="nx">This</span><span class="nx">div</span><span class="nx">has</span><span class="nx">overflow</span><span class="o">:</span><span class="nx">hidden</span><span class="p">.</span><span class="o">&lt;</span><span class="nx">button</span><span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleShow</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Show</span><span class="nx">modal</span><span class="o">&lt;</span><span class="err">/button&gt;</span><span class="p">{</span><span class="nx">modal</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span><span class="p">}</span><span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">App</span><span class="o">/&gt;</span><span class="p">,</span><span class="nx">appRoot</span><span class="p">);</span></code></pre>
</div>
<p>沒有portal生成與有portal的時候生成的層級關係如下：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-3b535514e9929d6a4ca252b9294c40b0_r.jpg" data-rawwidth="1870" data-rawheight="362" data-size="normal" data-caption="" class="origin_image zh-lightbox-thumb" width="1870" data-original="https://pic1.zhimg.com/v2-3b535514e9929d6a4ca252b9294c40b0_b.jpg" title="v2-3b535514e9929d6a4ca252b9294c40b0_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-3b535514e9929d6a4ca252b9294c40b0_r.jpg" data-rawwidth="1870" data-rawheight="362" data-size="normal" data-caption="" class="origin_image zh-lightbox-thumb lazy" width="1870" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1870'%20height='362'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-3b535514e9929d6a4ca252b9294c40b0_b.jpg" title="v2-3b535514e9929d6a4ca252b9294c40b0_r"></figure>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-040ef08c5a2589637ab5b57a290543ab_r.jpg" data-rawwidth="2088" data-rawheight="380" data-size="normal" data-caption="" class="origin_image zh-lightbox-thumb" width="2088" data-original="https://pic4.zhimg.com/v2-040ef08c5a2589637ab5b57a290543ab_b.jpg" title="v2-040ef08c5a2589637ab5b57a290543ab_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-040ef08c5a2589637ab5b57a290543ab_r.jpg" data-rawwidth="2088" data-rawheight="380" data-size="normal" data-caption="" class="origin_image zh-lightbox-thumb lazy" width="2088" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2088'%20height='380'&gt;&lt;/svg&gt;" data-actualsrc="https://pic4.zhimg.com/v2-040ef08c5a2589637ab5b57a290543ab_b.jpg" title="v2-040ef08c5a2589637ab5b57a290543ab_r"></figure>
<p>可以很清楚的看到，使用portal之後，modal不在嵌在app-root裡。</p>
<p><b>四、自定義DOM屬性</b></p>
<p>React 15：忽略未標準化的html 和svg屬性</p>
<p>React 16：去掉了這個限制</p>
<p>為什麼要做這個改動呢？兩個原因：</p>
<ol>
<li>不能用自定義屬性，對於非標準（proposal階段）新屬性還有其他框架（Angular）很不友好</li>
<li>React 15之所以可以過濾掉非標準的屬性，是因為他們維護了一個白名單的文件（放在bundle size 裡）。而隨著時間的增加，標準化的屬性越來越多，意味著要一直維護這個文件，同時這個文件也會越來越大，增加bundle的體積。</li>
</ol>
<p>所以還不如去掉這個限制。</p>
<p><a href="https://link.zhihu.com/?target=https%3A//codepen.io/gaearon/pen/gxNVdP%3Feditors%3D0010" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">演示</a></p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-bbff34ad6e0bfd15ce80c0ff6d8f1594_r.jpg" data-rawwidth="2014" data-rawheight="834" data-size="normal" data-caption="" class="origin_image zh-lightbox-thumb" width="2014" data-original="https://pic1.zhimg.com/v2-bbff34ad6e0bfd15ce80c0ff6d8f1594_b.jpg" title="v2-bbff34ad6e0bfd15ce80c0ff6d8f1594_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-bbff34ad6e0bfd15ce80c0ff6d8f1594_r.jpg" data-rawwidth="2014" data-rawheight="834" data-size="normal" data-caption="" class="origin_image zh-lightbox-thumb lazy" width="2014" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='2014'%20height='834'&gt;&lt;/svg&gt;" data-actualsrc="https://pic1.zhimg.com/v2-bbff34ad6e0bfd15ce80c0ff6d8f1594_b.jpg" title="v2-bbff34ad6e0bfd15ce80c0ff6d8f1594_r"></figure>
<p>可以看到自定義屬性已經生效了。</p>
<p><b>五、優化SSR</b></p>
<p>具體優化了下面五個方面：</p>
<ol>
<li>生成更簡潔的HTML</li>
<li>寬鬆的客戶端一致性校驗</li>
<li>無需提前編譯</li>
<li>react 16服務端渲染速度更快</li>
<li>支持流式渲染</li>
</ol>
<p><b>1、生成更簡潔的HTML</b></p>
<p>先看下面的HTML，react 15與react 16的服務端分別會生成什麼。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">renderToString</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">This</span><span class="nx">is</span><span class="nx">some</span><span class="o">&lt;</span><span class="nx">span</span><span class="o">&gt;</span><span class="nx">server</span><span class="o">-</span><span class="nx">generated</span><span class="o">&lt;</span><span class="err">/span&gt; &lt;span&gt;HTML.&lt;/span&gt; </span><span class="o">&lt;</span><span class="err">/div&gt; );</span></code></pre>
</div>
<p><b>react15:</b></p>
<p>有data-reactid, text noded ，react-text各種屬性。</p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">div</span><span class="nx">data</span><span class="o">-</span><span class="nx">reactroot</span><span class="o">=</span><span class="s2">""</span><span class="nx">data</span><span class="o">-</span><span class="nx">reactid</span><span class="o">=</span><span class="s2">"1"</span><span class="nx">data</span><span class="o">-</span><span class="nx">react</span><span class="o">-</span><span class="nx">checksum</span><span class="o">=</span><span class="s2">"122239856"</span><span class="o">&gt;</span><span class="c">&lt;!--</span><span class="nx">react</span><span class="o">-</span><span class="nx">text</span><span class="o">:</span><span class="mi">2</span><span class="o">--&gt;</span><span class="nx">This</span><span class="nx">is</span><span class="nx">some</span><span class="c">&lt;!--</span><span class="o">/</span><span class="nx">react</span><span class="o">-</span><span class="nx">text</span><span class="o">--&gt;</span><span class="o">&lt;</span><span class="nx">span</span><span class="nx">data</span><span class="o">-</span><span class="nx">reactid</span><span class="o">=</span><span class="s2">"3"</span><span class="o">&gt;</span><span class="nx">server</span><span class="o">-</span><span class="nx">generated</span><span class="o">&lt;</span><span class="err">/span&gt;</span><span class="c">&lt;!--</span><span class="nx">react</span><span class="o">-</span><span class="nx">text</span><span class="o">:</span><span class="mi">4</span><span class="o">--&gt;</span><span class="c">&lt;!--</span><span class="err">/react-text --&gt;</span><span class="o">&lt;</span><span class="nx">span</span><span class="nx">data</span><span class="o">-</span><span class="nx">reactid</span><span class="o">=</span><span class="s2">"5"</span><span class="o">&gt;</span><span class="nx">HTML</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/span&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span></code></pre>
</div>
<p><b>react 16:</b></p>
<div class="highlight">
<pre><code class="language-js"><span class="o">&lt;</span><span class="nx">div</span><span class="nx">data</span><span class="o">-</span><span class="nx">reactroot</span><span class="o">=</span><span class="s2">""</span><span class="o">&gt;</span><span class="nx">This</span><span class="nx">is</span><span class="nx">some</span><span class="o">&lt;</span><span class="nx">span</span><span class="o">&gt;</span><span class="nx">server</span><span class="o">-</span><span class="nx">generated</span><span class="o">&lt;</span><span class="err">/span&gt; </span><span class="o">&lt;</span><span class="nx">span</span><span class="o">&gt;</span><span class="nx">HTML</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/span&gt; </span><span class="o">&lt;</span><span class="err">/div&gt;</span></code></pre>
</div>
<p>可以看到，react 16去掉了很多屬性，它的好處很明顯：增加易讀性，同時很大程度上減少html的文件大小。</p>
<p><b>2、寬鬆的客戶端一致性校驗</b></p>
<p><b>react 15</b> ：會將SSR的結果與客戶端生成的做一個個字節的對比校驗，一點不匹配發出waring同時就替換整個SSR生成的樹。</p>
<p><b>react 16</b> ：對比校驗會更寬鬆一些，比如，react 16允許屬性順序不一致，而且遇到不匹配的標籤，還會做子樹的修改，不是整個替換。</p>
<p><b>注意點：</b> react16不會自動fix SSR屬性跟client html屬性的不同，但是仍然會報waring，所以我們需要自己手動去修改。</p>
<p><b>3、無需提前編譯</b></p>
<p>react 15：如果你直接使用SSR，會有很多需要檢查procee.env的地方，但是讀取在node中讀取process.env是很消耗時間的。所以在react 15的時候，需要提前編譯，這樣就可以移除process.env的引用。</p>
<p>react 16：只有一次檢查process.env的地方，所以就不需要提前編譯了，可以開箱即用。</p>
<p><b>4、react 16服務端渲染速度更快</b></p>
<p>為什麼呢？因為react 15下，server client都需要生成vDOM，但是其實在服務端， 當我們使用renderToString的時候，生成的vDom就會被立即拋棄掉， 所以在server端生成vDom是沒有意義的。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-90cc9be797932a120fb06b6de45c8986_r.jpg" data-rawwidth="1782" data-rawheight="972" data-size="normal" class="origin_image zh-lightbox-thumb" width="1782" data-original="https://pic3.zhimg.com/v2-90cc9be797932a120fb06b6de45c8986_b.jpg" title="v2-90cc9be797932a120fb06b6de45c8986_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-90cc9be797932a120fb06b6de45c8986_r.jpg" data-rawwidth="1782" data-rawheight="972" data-size="normal" class="origin_image zh-lightbox-thumb lazy" width="1782" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1782'%20height='972'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-90cc9be797932a120fb06b6de45c8986_b.jpg" title="v2-90cc9be797932a120fb06b6de45c8986_r"><figcaption>圖片來源（https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67）</figcaption></figure>
<p><b>5、支持流式渲染(</b> renderyToNodeStream <b>)</b></p>
<p>使用流式渲染會提升首個字節到（TTFB）的速度。但是什麼是流式渲染呢？</p>
<p>可以理解為內容以一種流的形式傳給前端。所以在下一部分的內容被生成之前，開頭的內容就已經被發到瀏覽器端了。這樣瀏覽器就可以更早的編譯渲染文件內容。</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">// using Express</span><span class="kr">import</span><span class="p">{</span><span class="nx">renderToNodeStream</span><span class="p">}</span><span class="nx">from</span><span class="s2">"react-dom/server"</span><span class="kr">import</span><span class="nx">MyPage</span><span class="nx">from</span><span class="s2">"./MyPage"</span><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">"/"</span><span class="p">,</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="nx">res</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">res</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">"&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;title&gt;My Page&lt;/title&gt;&lt;/head&gt;&lt;body&gt;"</span><span class="p">);</span><span class="nx">res</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">"&lt;div id='content'&gt;"</span><span class="p">);</span><span class="kr">const</span><span class="nx">stream</span><span class="o">=</span><span class="nx">renderToNodeStream</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">MyPage</span><span class="o">/&gt;</span><span class="p">);</span><span class="nx">stream</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">res</span><span class="p">,</span><span class="p">{</span><span class="nx">end</span><span class="o">:</span><span class="kc">false</span><span class="p">});</span><span class="nx">stream</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'end'</span><span class="p">,</span><span class="p">()</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">res</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">"&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;"</span><span class="p">);</span><span class="nx">res</span><span class="p">.</span><span class="nx">end</span><span class="p">();</span><span class="p">});</span><span class="p">});</span></code></pre>
</div>
<p>新的API server: renderyToNodeStream, renderToStaticNodeStream (renderToString, renderToStaticMarkup) client: hydyate</p>
<p><b>如何使用：</b></p>
<p><b>React 15：</b></p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">// server:</span><span class="c1">// using Express client</span><span class="kr">import</span><span class="p">{</span><span class="nx">renderToString</span><span class="p">}</span><span class="nx">from</span><span class="s2">"react-dom/server"</span><span class="kr">import</span><span class="nx">MyPage</span><span class="nx">from</span><span class="s2">"./MyPage"</span><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">"/"</span><span class="p">,</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="nx">res</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">res</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">"&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;title&gt;My Page&lt;/title&gt;&lt;/head&gt;&lt;body&gt;"</span><span class="p">);</span><span class="nx">res</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">"&lt;div id='content'&gt;"</span><span class="p">);</span><span class="nx">res</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">renderToString</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">MyPage</span><span class="o">/&gt;</span><span class="p">));</span><span class="nx">res</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">"&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;"</span><span class="p">);</span><span class="nx">res</span><span class="p">.</span><span class="nx">end</span><span class="p">();</span><span class="p">});</span><span class="c1">// client</span><span class="kr">import</span><span class="p">{</span><span class="nx">render</span><span class="p">}</span><span class="nx">from</span><span class="s2">"react-dom"</span><span class="kr">import</span><span class="nx">MyPage</span><span class="nx">from</span><span class="s2">"./MyPage"</span><span class="nx">render</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">MyPage</span><span class="o">/&gt;</span><span class="p">,</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">"content"</span><span class="p">));</span></code></pre>
</div>
<p><b>React 16：</b></p>
<p>其實就是把client端的render改成hydrate。</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">// client</span> <span class="kr">import</span> <span class="p">{</span> <span class="nx">hydrate</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">"react-dom"</span> <span class="kr">import</span> <span class="nx">MyPage</span> <span class="nx">from</span> <span class="s2">"./MyPage"</span> <span class="nx">hydrate</span> <span class="p">(</span> <span class="o">&lt;</span> <span class="nx">MyPage</span> <span class="o">/&gt;</span> <span class="p">,</span> <span class="nb">document</span> <span class="p">.</span> <span class="nx">getElementById</span> <span class="p">(</span> <span class="s2">"content"</span> <span class="p">));</span></code></pre>
</div>
<p>當然，現在依然兼容render，但是17之後不再兼容，所以還是直接用hydrate好一點。</p>
<p><b>注意事項：不支持ErrorBoundary跟Portal，所以需要直出的頁面就不能用了。</b></p>
<p><b>五、減小了32%bundle的體積</b></p>
<p>React 庫大小從20.7kb（壓縮後6.9kb）降低到5.3kb（壓縮後2.2kb）</p>
<p>ReactDOM 庫大小從141kb（壓縮後42.9kb）降低到103.7kb（壓縮後32.6kb）</p>
<p>React + ReactDOM 庫大小從161.7kb（壓縮後49.8kb）降低到109kb（壓縮後43.8kb）</p>
<p><b>六、Fiber</b></p>
<p>由於Fiber不是新的API，是react對於對比更新的一種新算法，它影響著生命週期函數的變化跟異步渲染。需要詳細了解的同學可以戳下面的鏈接，這應該是我看過最易懂得解釋Fiber得視頻。</p>
<h2><b>v16.1</b></h2>
<p>react-call-return</p>
<p>這就是一個庫，平時用的比較少，所以暫時不講。</p>
<h2><b>v16.2</b></h2>
<p>主要特性：Fragement</p>
<p>React 15：render函數只能接受一個組件，所以一定要外層包一層&lt;div&gt;。</p>
<p>React16：可以通過Fragement直接返回多個組件。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;&gt;</span><span class="o">&lt;</span><span class="nx">ChildA</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">ChildB</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">ChildC</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/&gt;</span><span class="p">);</span><span class="p">}</span></code></pre>
</div>
<p>但是這樣看起來，似乎可以用v16.0 return一個數組搞定。</p>
<p>但是返回數組是有缺點的，比如：這段html</p>
<div class="highlight">
<pre><code class="language-html">Some text. <span class="p">&lt;</span> <span class="nt">h2</span> <span class="p">&gt;</span> A heading <span class="p">&lt;/</span> <span class="nt">h2</span> <span class="p">&gt;</span> More text. <span class="p">&lt;</span> <span class="nt">h2</span> <span class="p">&gt;</span> Another heading <span class="p">&lt;/</span> <span class="nt">h2</span> <span class="p">&gt;</span> Even more text.</code></pre>
</div>
<p>用Fragment寫，方便又快捷：</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">render</span> <span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="c1">// Extraneous div element :(</span> <span class="o">&lt;</span> <span class="nx">Fragement</span> <span class="o">&gt;</span> <span class="nx">Some</span> <span class="nx">text</span> <span class="p">.</span> <span class="o">&lt;</span> <span class="nx">h2</span> <span class="o">&gt;</span> <span class="nx">A</span> <span class="nx">heading</span> <span class="o">&lt;</span> <span class="err">/h2&gt;</span> <span class="nx">More</span> <span class="nx">text</span> <span class="p">.</span> <span class="o">&lt;</span> <span class="nx">h2</span> <span class="o">&gt;</span> <span class="nx">Another</span> <span class="nx">heading</span> <span class="o">&lt;</span> <span class="err">/h2&gt;</span> <span class="nx">Even</span> <span class="nx">more</span> <span class="nx">text</span> <span class="p">.</span> <span class="o">&lt;</span> <span class="err">/Fragement&gt;</span> <span class="p">);</span> <span class="p">}</span></code></pre>
</div>
<p>用數組寫.... 一言難盡（什麼，你還沒看出來有什麼區別！下面我來帶你）</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">[</span><span class="s2">"Some text."</span><span class="p">,</span><span class="o">&lt;</span><span class="nx">h2</span><span class="nx">key</span><span class="o">=</span><span class="s2">"heading-1"</span><span class="o">&gt;</span><span class="nx">A</span><span class="nx">heading</span><span class="o">&lt;</span><span class="err">/h2&gt;,</span><span class="s2">"More text."</span><span class="p">,</span><span class="o">&lt;</span><span class="nx">h2</span><span class="nx">key</span><span class="o">=</span><span class="s2">"heading-2"</span><span class="o">&gt;</span><span class="nx">Another</span><span class="nx">heading</span><span class="o">&lt;</span><span class="err">/h2&gt;,</span><span class="s2">"Even more text."</span><span class="p">];</span><span class="p">}</span></code></pre>
</div>
<p>缺點：</p>
<ul>
<li>數組裡的子節點必須要用逗號分離</li>
<li>數組裡的子節點必須要帶key防止waring</li>
<li> string類型要用雙引號括住</li>
</ul>
<p>所以，Fragement還是很大程度上給我們提供了便利。</p>
<p>注意點：</p>
<p>&lt;&gt; &lt;/&gt; 不支持寫入屬性，包括keys。如果你需要keys，你可以直接使用&lt;Fragment&gt; （但是Fragment也只可以接受keys這一個屬性，將來會支持更多）</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span> <span class="nx">Glossary</span> <span class="p">(</span> <span class="nx">props</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="o">&lt;</span> <span class="nx">dl</span> <span class="o">&gt;</span> <span class="p">{</span> <span class="nx">props</span> <span class="p">.</span> <span class="nx">items</span> <span class="p">.</span> <span class="nx">map</span> <span class="p">(</span> <span class="nx">item</span> <span class="p">=&gt;</span> <span class="p">(</span> <span class="c1">// Without the `key`, React will fire a key warning</span> <span class="o">&lt;</span> <span class="nx">Fragment</span> <span class="nx">key</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">item</span> <span class="p">.</span> <span class="nx">id</span> <span class="p">}</span> <span class="o">&gt;</span> <span class="o">&lt;</span> <span class="nx">dt</span> <span class="o">&gt;</span> <span class="p">{</span> <span class="nx">item</span> <span class="p">.</span> <span class="nx">term</span> <span class="p">}</span> <span class="o">&lt;</span> <span class="err">/dt&gt;</span> <span class="o">&lt;</span> <span class="nx">dd</span> <span class="o">&gt;</span> <span class="p">{</span> <span class="nx">item</span> <span class="p">.</span> <span class="nx">description</span> <span class="p">}</span> <span class="o">&lt;</span> <span class="err">/dd&gt;</span> <span class="o">&lt;</span> <span class="err">/Fragment&gt;</span> <span class="p">))}</span> <span class="o">&lt;</span> <span class="err">/dl&gt;</span> <span class="p">);</span> <span class="p">}</span></code></pre>
</div>
<p><a href="https://link.zhihu.com/?target=https%3A//codepen.io/reactjs/pen/VrEbjE%3Feditors%3D1000" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">官方演示</a></p>
<p>好了，相信看到這裡，大家都很想睡覺了。堅持一下，還有五個點就講完了~。</p>
<figure data-size="normal"><noscript><img decoding="async" src="" data-rawwidth="240" data-rawheight="240" data-size="normal" data-caption="" data-thumbnail="https://pic4.zhimg.com/v2-697ee7af2083956eea554adbe7f9225b_b.jpg" class="content_image" width="240" data-original="https://pic4.zhimg.com/v2-697ee7af2083956eea554adbe7f9225b_b.gif"></noscript><img decoding="async" src="" data-rawwidth="240" data-rawheight="240" data-size="normal" data-caption="" data-thumbnail="https://pic4.zhimg.com/v2-697ee7af2083956eea554adbe7f9225b_b.jpg" class="content_image lazy" width="240" data-actualsrc="https://pic4.zhimg.com/v2-697ee7af2083956eea554adbe7f9225b_b.gif" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='240'%20height='240'&gt;&lt;/svg&gt;"></figure>
<h2><b>16.3</b></h2>
<p><b>一、新的生命週期函數</b></p>
<p>由於異步渲染的改動，有可能會導致componentWillMount, componentWillReceiveProps,componentWillUpdate ，所以需要拋棄三個函數。</p>
<p>由於這是一個很大的改變會影響很多現有的組件，所以需要慢慢的去改。目前react 16 只是會報waring，在react 17你就只能在前面加"UNSAFE_" 的前綴來使用。不能不說react團隊真是太貼心了，他們還寫了一個<a href="https://link.zhihu.com/?target=https%3A//github.com/reactjs/react-codemod%23rename-unsafe-lifecycles" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">腳本</a>自動幫你加上這些前綴。瘋狂打call~</p>
<p>同時新加了兩個生命週期函數來替代他們，分別是：</p>
<p><b>getDerivedStateFromProps</b> :這個方法用於替代componentWillReceiveProps，相關內容可以看<a href="https://link.zhihu.com/?target=https%3A//reactjs.org/blog/2018/03/27/update-on-async-rendering.html" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">這篇文章</a>,但是大多數情況下，都不需要用到這兩種方法。因為你都可以用其他辦法來替代。</p>
<p>而getSnapshotBeforeUpate使用的場景很少，這裡就不介紹了。</p>
<p><b>二、新的context API</b></p>
<p>1、context 就是可以使用全局的變量，不需要一層層pass props下去，比如主題顏色</p>
<div class="highlight">
<pre><code class="language-js"><span class="c1">// Context lets us pass a value deep into the component tree</span><span class="c1">// without explicitly threading it through every component.</span><span class="c1">// Create a context for the current theme (with "light" as the default).</span><span class="kr">const</span><span class="nx">ThemeContext</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">createContext</span><span class="p">(</span><span class="s1">'light'</span><span class="p">);</span><span class="kr">class</span><span class="nx">App</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="c1">// Use a Provider to pass the current theme to the tree below.</span><span class="c1">// Any component can read it, no matter how deep it is.</span><span class="c1">// In this example, we're passing "dark" as the current value.</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">ThemeContext</span><span class="p">.</span><span class="nx">Provider</span><span class="nx">value</span><span class="o">=</span><span class="s2">"dark"</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">Toolbar</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/ThemeContext.Provider&gt;</span><span class="p">);</span><span class="p">}</span><span class="p">}</span><span class="c1">// A component in the middle doesn't have to</span><span class="c1">// pass the theme down explicitly anymore.</span><span class="kd">function</span><span class="nx">Toolbar</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">ThemedButton</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span><span class="kr">class</span><span class="nx">ThemedButton</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="c1">// Assign a contextType to read the current theme context.</span><span class="c1">// React will find the closest theme Provider above and use its value.</span><span class="c1">// In this example, the current theme is "dark".</span><span class="kr">static</span><span class="nx">contextType</span><span class="o">=</span><span class="nx">ThemeContext</span><span class="p">;</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">Button</span><span class="nx">theme</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">}</span><span class="o">/&gt;</span><span class="p">;</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>但是需要謹慎使用，因為這會讓你的組件復用性變差。一般來說，如果你只是想避免需要傳很多次props的話，可以直接使用component composition（就是通過props自己傳給指定的）會更好。例如：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">Page</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">const</span><span class="nx">user</span><span class="o">=</span><span class="nx">props</span><span class="p">.</span><span class="nx">user</span><span class="p">;</span><span class="c1">//簡單來說就是直接父組件將props傳下去</span><span class="kr">const</span><span class="nx">userLink</span><span class="o">=</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">Link</span><span class="nx">href</span><span class="o">=</span><span class="p">{</span><span class="nx">user</span><span class="p">.</span><span class="nx">permalink</span><span class="p">}</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">Avatar</span><span class="nx">user</span><span class="o">=</span><span class="p">{</span><span class="nx">user</span><span class="p">}</span><span class="nx">size</span><span class="o">=</span><span class="p">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">avatarSize</span><span class="p">}</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/Link&gt;</span><span class="p">);</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">PageLayout</span><span class="nx">userLink</span><span class="o">=</span><span class="p">{</span><span class="nx">userLink</span><span class="p">}</span><span class="o">/&gt;</span><span class="p">;</span><span class="p">}</span><span class="c1">// Now, we have:</span><span class="o">&lt;</span><span class="nx">Page</span><span class="nx">user</span><span class="o">=</span><span class="p">{</span><span class="nx">user</span><span class="p">}</span><span class="o">/&gt;</span><span class="c1">// ... which renders ...</span><span class="o">&lt;</span><span class="nx">PageLayout</span><span class="nx">userLink</span><span class="o">=</span><span class="p">{...}</span><span class="o">/&gt;</span><span class="c1">// ... which renders ...</span><span class="o">&lt;</span><span class="nx">NavigationBar</span><span class="nx">userLink</span><span class="o">=</span><span class="p">{...}</span><span class="o">/&gt;</span><span class="c1">// ... which renders ...</span><span class="p">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">userLink</span><span class="p">}</span></code></pre>
</div>
<p>什麼場景下需要用context?<b>一些相同的data需要被大多的component用到，並且還是在不同的層級裡。</b>一般用於主題，存儲數據等。</p>
<p><b>三、createRef API</b></p>
<p>react15 的時候提供了兩種refs的方法： string 跟callback string:</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">MyComponent</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="p">}</span><span class="c1">//通過this.refs.textInput來獲取</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">input</span><span class="nx">type</span><span class="o">=</span><span class="s2">"text"</span><span class="nx">ref</span><span class="o">=</span><span class="s1">'textInput'</span><span class="o">/&gt;</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="nx">callback</span><span class="o">:</span><span class="kr">class</span><span class="nx">MyComponent</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="p">}</span><span class="c1">//通過this.textInput來獲取</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">input</span><span class="nx">type</span><span class="o">=</span><span class="s2">"text"</span><span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">element</span><span class="p">=&gt;</span><span class="k">this</span><span class="p">.</span><span class="nx">textInput</span><span class="o">=</span><span class="nx">element</span><span class="p">}</span><span class="o">/&gt;</span><span class="p">;</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>由於用string的方式會導致一些潛在的<a href="https://link.zhihu.com/?target=https%3A//github.com/facebook/react/issues/1373" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">問題</a>，所以之前推薦使用callback。但是用string的方法明顯方便一點啊餵~</p>
<p>所以react 團隊get到了大家的需求，又出了一個新的api 可以用string的方式而且還沒有缺點， 真是可喜可賀，可口可樂。</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">class</span><span class="nx">MyComponent</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">inputRef</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">();</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">input</span><span class="nx">type</span><span class="o">=</span><span class="s2">"text"</span><span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">inputRef</span><span class="p">}</span><span class="o">/&gt;</span><span class="p">;</span><span class="p">}</span><span class="nx">componentDidMount</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">inputRef</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>使用場景：</p>
<ol>
<li>用於操作focus, text 選擇，media playback</li>
<li>觸發即時動畫</li>
<li>與第三方組件結合</li>
</ol>
<p>注意事項：</p>
<p>1、functional component 是不能傳ref屬性的，因為他們沒有instance</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">MyFunctionComponent</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="o">&lt;</span><span class="nx">input</span><span class="o">/&gt;</span><span class="p">;</span><span class="p">}</span><span class="kr">class</span><span class="nx">Parent</span><span class="kr">extends</span><span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span><span class="k">this</span><span class="p">.</span><span class="nx">textInput</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">();</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="c1">// 这个不能工作
</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">MyFunctionComponent</span><span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">textInput</span><span class="p">}</span><span class="o">/&gt;</span><span class="p">);</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>但是！只要你要引用的對像是DOM元素或者是class component, 那你可以在functional component裡可以使用ref屬性</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span> <span class="nx">CustomTextInput</span> <span class="p">(</span> <span class="nx">props</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// textInput must be declared here so the ref can refer to it</span> <span class="kd">let</span> <span class="nx">textInput</span> <span class="o">=</span> <span class="nx">React</span> <span class="p">.</span> <span class="nx">createRef</span> <span class="p">();</span> <span class="kd">function</span> <span class="nx">handleClick</span> <span class="p">()</span> <span class="p">{</span> <span class="nx">textInput</span> <span class="p">.</span> <span class="nx">current</span> <span class="p">.</span> <span class="nx">focus</span> <span class="p">();</span> <span class="p">}</span> <span class="k">return</span> <span class="p">(</span> <span class="o">&lt;</span> <span class="nx">div</span> <span class="o">&gt;</span> <span class="o">&lt;</span> <span class="nx">input</span> <span class="nx">type</span> <span class="o">=</span> <span class="s2">"text"</span> <span class="nx">ref</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">textInput</span> <span class="p">}</span> <span class="o">/&gt;</span> <span class="o">&lt;</span> <span class="nx">input</span> <span class="nx">type</span> <span class="o">=</span> <span class="s2">"button"</span> <span class="nx">value</span> <span class="o">=</span> <span class="s2">"Focus the text input"</span> <span class="nx">onClick</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">handleClick</span> <span class="p">}</span> <span class="o">/&gt;</span> <span class="o">&lt;</span> <span class="err">/div&gt;</span> <span class="p">);</span> <span class="p">}</span></code></pre>
</div>
<p><b>簡而言之：functional component裡可以使用refs但是不能把ref屬性給它本身。</b></p>
<p><b>四、forwardRef API</b></p>
<p>使用場景： 父組件需要將自己的引用傳給子組件</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">const</span><span class="nx">TextInput</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">forwardRef</span><span class="p">((</span><span class="nx">props</span><span class="p">,</span><span class="nx">ref</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">input</span><span class="nx">type</span><span class="o">=</span><span class="s2">"text"</span><span class="nx">placeholder</span><span class="o">=</span><span class="s2">"Hello forwardRef"</span><span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">ref</span><span class="p">}</span><span class="o">/&gt;</span><span class="p">))</span><span class="kr">const</span><span class="nx">inputRef</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">()</span><span class="kr">class</span><span class="nx">App</span><span class="kr">extends</span><span class="nx">Component</span><span class="p">{</span><span class="nx">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="p">{</span><span class="kr">super</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span><span class="k">this</span><span class="p">.</span><span class="nx">myRef</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">()</span><span class="p">}</span><span class="nx">handleSubmit</span><span class="o">=</span><span class="nx">event</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">()</span><span class="nx">alert</span><span class="p">(</span><span class="s1">'input value is:'</span><span class="o">+</span><span class="nx">inputRef</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="p">}</span><span class="nx">render</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">form</span><span class="nx">onSubmit</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleSubmit</span><span class="p">}</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">TextInput</span><span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">inputRef</span><span class="p">}</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">button</span><span class="nx">type</span><span class="o">=</span><span class="s2">"submit"</span><span class="o">&gt;</span><span class="nx">Submit</span><span class="o">&lt;</span><span class="err">/button&gt;</span><span class="o">&lt;</span><span class="err">/form&gt;</span><span class="p">)</span><span class="p">}</span><span class="p">}</span><span class="kr">const</span><span class="nx">FancyButton</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">forwardRef</span><span class="p">((</span><span class="nx">props</span><span class="p">,</span><span class="nx">ref</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">button</span><span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">ref</span><span class="p">}</span><span class="nx">className</span><span class="o">=</span><span class="s2">"FancyButton"</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/button&gt;</span><span class="p">));</span><span class="c1">// You can now get a ref directly to the DOM button:</span><span class="kr">const</span><span class="nx">ref</span><span class="o">=</span><span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">();</span><span class="o">&lt;</span><span class="nx">FancyButton</span><span class="nx">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">ref</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Click</span><span class="nx">me</span><span class="o">!&lt;</span><span class="err">/FancyButton&gt;;</span></code></pre>
</div>
<p>這樣我們就可以直接用this.ref 拿到對button的引用。如果你寫的是一個高階組件，那麼推薦使用forwardAPI 將ref傳給下面的component。</p>
<p><b>五、strictMode component</b></p>
<p>嚴格模式用來幫助開發者發現潛在問題的工具。就像Fragment 一樣，它不會render任何的DOM 元素。注意：只有在development模式下才能用。</p>
<p>它可以幫助我們：</p>
<ol>
<li>識別出使用不安全生命週期的組件</li>
<li>對使用string ref進行告警</li>
<li>對使用findDOMNode進行告警</li>
<li>探測某些產生副作用的方法</li>
<li>對使用棄用context API進行警告</li>
</ol>
<p>還會有更多的功能在後續版本加進來。</p>
<p>使用：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">ExampleApplication</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">Header</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">React</span><span class="p">.</span><span class="nx">StrictMode</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">ComponentOne</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="nx">ComponentTwo</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="o">&lt;</span><span class="err">/React.StrictMode&gt;</span><span class="o">&lt;</span><span class="nx">Footer</span><span class="o">/&gt;</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">);</span><span class="p">}</span></code></pre>
</div>
<p>如果文章對你有用，順手點個贊唄</p>
<p>參考文檔：</p>
<p>1、 <a href="https://zhuanlan.zhihu.com/p/29880992" class="internal" data-wpel-link="external" rel="nofollow external noopener noreferrer">React Portal</a></p>
<p>2、 <a href="https://link.zhihu.com/?target=https%3A//reactjs.org/docs/portals.html" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">官方文檔</a></p>
<p>3、 <a href="https://link.zhihu.com/?target=http%3A//www.ayqy.net/blog/react-16/" class=" external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external"><span class="invisible">http://www.</span> <span class="visible">ayqy.net/blog/react-16/</span></a></p>
<p>4、 <a href="https://link.zhihu.com/?target=https%3A//hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">React 16 SSR新特性</a></p>
<p>5、 <a href="https://link.zhihu.com/?target=https%3A//reactjs.org/docs/refs-and-the-dom.html" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">Refs and the Dom</a></p>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14348/topic-57544233/" data-wpel-link="internal">React 16 新特性全解（上）</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>RIS，創建React 應用的新選擇</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/14344/topic-57281453/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 17:59:36 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/14344/topic-57281453/</guid>

					<description><![CDATA[<p>前言RIS, React Integrated Solution. 它的目標是提供一套基本的構建配置方案，而且配置是能高度擴展的，希望通過它能對外輸出React 的一些最佳實踐。 RIS 是我在去年11月開始開源出去的，這工具之前在內部團隊…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14344/topic-57281453/" data-wpel-link="internal">RIS，創建React 應用的新選擇</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">RIS，創建React 應用的新選擇</h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="beyondxgb"><meta itemprop="image" content="https://pic2.zhimg.com/5fbf7b41353017b0edaffbd9b6702222_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/xin-lun-53"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2>前言</h2>
<p><b>RIS, React Integrated Solution.</b>它的目標是提供一套基本的構建配置方案，而且配置是能高度擴展的，希望通過它能對外輸出React的一些最佳實踐。</p>
<p><a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">RIS</a>是我在去年11月開始開源出去的，這工具之前在內部團隊使用過，反饋還不錯，斷斷續續維護和寫文檔，現在介紹給大家，希望能給大家提供一些幫助和啟發。</p>
<h2>背景</h2>
<p>目前社區已經有很多優秀的工具或框架做前端構建的事情，而且集成了相關的最佳實踐，比如<a href="https://link.zhihu.com/?target=https%3A//github.com/facebook/create-react-app" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">create-react-app</a> ， <a href="https://link.zhihu.com/?target=https%3A//github.com/react-boilerplate/react-boilerplate" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">react-boilerplate</a> ， <a href="https://link.zhihu.com/?target=https%3A//github.com/umijs/umi" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">umi</a>等等，這三個我都覺得做得很不錯，我做<b>RIS</b>的時候很多想法都來源於它們。那為啥我還要重複造輪子？輪子不怕造，最重要是能說服自己，我覺得自己可以做出跟別人不一樣甚至有創新的東西出來，其次，在造的過程中，也是自己對於前端構建一些思考的沉澱。</p>
<p>在這說下自己的一些想法：</p>
<ul>
<li><b>create-react-app</b> :它的構建和開發體驗我覺得是做得最好的，也得到了社區的認可，但我覺得唯一缺乏的是擴展比較麻煩，受限於里面內置的構建，當然有人說可以使用<code>enject</code>的方式，但這樣又失去了工具的意義了。</li>
<li> <b>react-boilerplate</b> ：它的亮點主要是集成了一些最佳實踐，基本包含了項目開發中所需的技術，但只是一個工程，沒有工具化。</li>
<li> <b>umi</b> ：它是支付寶做的企業級應用框架，可以通過插件集成相關的功能，約束比較強，自主權很小。</li>
</ul>
<p><b>RIS</b>要做到的是更像是<b>create-react-app</b>的方式，提供最底層的構建，同時提供通用的一些開發方案，再給予開發者足夠的自由定制自己想要的，希望通過這個工具輸出React開發的最佳實踐。</p>
<h2>快速體驗</h2>
<div class="highlight">
<pre><code class="language-bash">npx create-ris ris-app <span class="nb">cd</span> ris-app npm start</code></pre>
</div>
<blockquote><p><b>npm</b>的版本需要是<b>5.2</b>及以上</p></blockquote>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-e021dbb4590bef516a283d22fe098cc3_r.jpg" data-caption="" data-size="normal" data-rawwidth="743" data-rawheight="425" data-thumbnail="https://pic4.zhimg.com/v2-e021dbb4590bef516a283d22fe098cc3_b.jpg" class="origin_image zh-lightbox-thumb" width="743" data-original="https://pic4.zhimg.com/v2-e021dbb4590bef516a283d22fe098cc3_b.gif" title="v2-e021dbb4590bef516a283d22fe098cc3_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-e021dbb4590bef516a283d22fe098cc3_r.jpg" data-caption="" data-size="normal" data-rawwidth="743" data-rawheight="425" data-thumbnail="https://pic4.zhimg.com/v2-e021dbb4590bef516a283d22fe098cc3_b.jpg" class="origin_image zh-lightbox-thumb lazy" width="743" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='743'%20height='425'&gt;&lt;/svg&gt;" data-actualsrc="https://pic4.zhimg.com/v2-e021dbb4590bef516a283d22fe098cc3_b.gif" title="v2-e021dbb4590bef516a283d22fe098cc3_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>詳細可參考<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris/blob/master/docs/started/quick-start.md" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文檔</a>。</p>
<h2>特性</h2>
<p>主要的特性：</p>
<ul>
<li><i><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /></i><b>開箱即用</b>，內置了空白模板和標準模板，集成<code>react</code> , <code>react-outer</code>等。</li>
<li> <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" />︎<b>高擴展性</b>，可以高度定制項目內容和構建配置。</li>
<li> <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" />︎<b>極致的開發體驗</b>，使用了<code>DLL</code>加快構建速度，可以使用命令快速新建組件和頁面。</li>
<li> <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" />︎<b>高性能</b>，使用了<code>react-loadable</code>實現代碼的按需加載。</li>
<li> <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" />︎<b>強大的數據模擬功能</b>,很方便地在開發環境模擬數據。</li>
<li> <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" />︎<b>高效的數據流處理</b>，集成了<a href="https://link.zhihu.com/?target=https%3A//github.com/beyondxgb/xredux" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">xredux</a> ，很好地處理數據流問題。</li>
</ul>
<h2>開箱即用</h2>
<p>使用<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/create-ris" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">create-ris</a>可快速創建腳手架，目前集成了<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/create-ris/tree/master/template/simple" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">空白模板</a>和<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/create-ris/tree/master/template/standard" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">標準模板</a>。</p>
<div class="highlight">
<pre><code class="language-bash">npx create-ris &lt;appName&gt;</code></pre>
</div>
<h2>空白模板（simple)</h2>
<p>創建出來的項目和<b>create-react-app</b>創建的差不多，只是一個空白項目。</p>
<div class="highlight">
<pre><code class="language-text">├── README.md ├── package.json ├── src │ ├── App.js │ ├── App.scss │ ├── index.html │ └── index.js └── tools ├── generators │ ├── component │ │ ├── class.js.hbs │ │ ├── index.js │ │ └── stateless.js.hbs │ ├── index.js │ └── utils │ └── componentExists.js ├── risrc.js ├── server │ └── index.js └── webpack ├── base.js ├── dev.js └── prod.js</code></pre>
</div>
<h2>標準模板（standard）</h2>
<p>主要集成了<code>react-router</code> , <code>react-redux</code> , <code>xredux</code> ， <code>react-loadable</code>等庫，主要提供單頁應用的標準解決方案。</p>
<div class="highlight">
<pre><code class="language-text">├── README.md ├── mock │ └── rules.js ├── package.json ├── src │ ├── assets │ ├── components │ ├── core │ │ └── request │ ├── index.html │ ├── index.js │ ├── pages │ │ ├── Demo │ │ │ ├── Loadable.js │ │ │ ├── index.js │ │ │ ├── index.scss │ │ │ └── model.js │ ├── routes.js │ ├── services │ ├── store │ └── utils └── tools</code></pre>
</div>
<p>詳細目錄可以創建項目來體驗一下。</p>
<h2>高擴展性</h2>
<p>高擴展性體現在兩方面，一方面是項目的高擴展性和構建的高擴展性。</p>
<p>第一，從創建出來的項目可以看出來，沒有對任何使用的技術進行封裝，使用的都是業界比較認可的技術，版本可以自主控制，開發者可以自由選擇項目的技術棧。</p>
<p>第二，構建上是可以高度定制的，目前是通過靈活合併項目中的配置的方式進行定制，下面主要介紹下如何定制構建配置。</p>
<p>在項目根目錄下有個<code>tools</code>文件夾，裡面有<code>webpack</code> , <code>server</code>和<code>generators</code>文件夾。下面主要介紹<code>webpack</code>和<code>server</code>如何配置。</p>
<h2>webpack</h2>
<p><code>webpack</code>文件夾有三個文件<code>base.js</code> ， <code>dev.js</code>和<code>prod.js</code> ，分別對應基礎配置，開發環境和生產環境的配置。</p>
<p><code>base.js</code></p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">const</span><span class="nx">path</span><span class="o">=</span><span class="nx">require</span><span class="p">(</span><span class="s1">'path'</span><span class="p">);</span><span class="kr">const</span><span class="nx">resolveApp</span><span class="o">=</span><span class="nx">relativePath</span><span class="p">=&gt;</span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">cwd</span><span class="p">(),</span><span class="nx">relativePath</span><span class="p">);</span><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="o">=</span><span class="p">{</span><span class="nx">entry</span><span class="o">:</span><span class="nx">resolveApp</span><span class="p">(</span><span class="s1">'src/index.js'</span><span class="p">),</span><span class="nx">module</span><span class="o">:</span><span class="p">{</span><span class="nx">rules</span><span class="o">:</span><span class="p">[],</span><span class="p">},</span><span class="nx">resolve</span><span class="o">:</span><span class="p">{</span><span class="p">},</span><span class="nx">plugins</span><span class="o">:</span><span class="p">[],</span><span class="p">};</span></code></pre>
</div>
<p>格式是和<a href="https://link.zhihu.com/?target=https%3A//webpack.js.org/configuration/" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">官方配置</a>格式是一致的，構建時會對這些配置進行合併。類似這樣：</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">merge</span><span class="p">([</span><span class="nx">built</span><span class="o">-</span><span class="k">in</span><span class="nx">config</span><span class="p">],</span><span class="nx">merge</span><span class="p">(</span><span class="nx">base</span><span class="p">.</span><span class="nx">js</span><span class="p">,</span><span class="nx">dev</span><span class="p">.</span><span class="nx">js</span><span class="p">));</span></code></pre>
</div>
<h2>server</h2>
<p><code>server</code>的配置也是一樣的，提供<a href="https://link.zhihu.com/?target=https%3A//webpack.js.org/configuration/dev-server/%23devserver" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">官方配置</a>格式，然後進行合併。</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="o">=</span><span class="p">{</span><span class="nx">port</span><span class="o">:</span><span class="mi">3000</span><span class="p">,</span><span class="nx">compress</span><span class="o">:</span><span class="kc">true</span><span class="p">,</span><span class="nx">quiet</span><span class="o">:</span><span class="kc">false</span><span class="p">,</span><span class="nx">clientLogLevel</span><span class="o">:</span><span class="s1">'none'</span><span class="p">,</span><span class="nx">disableHostCheck</span><span class="o">:</span><span class="kc">true</span><span class="p">,</span><span class="nx">historyApiFallback</span><span class="o">:</span><span class="p">{</span><span class="nx">disableDotRule</span><span class="o">:</span><span class="kc">true</span><span class="p">,</span><span class="p">},</span><span class="nx">proxy</span><span class="o">:</span><span class="p">{</span><span class="p">}</span><span class="p">};</span></code></pre>
</div>
<p>通過這樣的方式，很容易實現添加<code>loader</code> ， <code>plugins</code> ，從而很容易把<a href="https://link.zhihu.com/?target=https%3A//ant.design/" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">ant-design</a>等組件庫集成到項目中來，詳細可參考<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris/blob/master/docs/guide.md" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">用戶文檔</a>。</p>
<h2>極致的開發體驗</h2>
<p>在開發體驗上主要有兩個亮點，</p>
<p>第一，開發環境中自動使用了<b>DLL</b> ，使得項目代碼的二次編譯速度有了質的飛躍。</p>
<p>在開發環境中，自動將所有第三方依賴打入<code>DLL</code> ，和業務邏輯代碼進行分離，不參與二次編譯，所以開發過程中構建速度會非常快。</p>
<p>當然，可以自由配置哪些需要打入<code>DLL</code> ，甚至可以禁用此功能，詳細參考<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris/blob/master/docs/build/dll.md" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文檔</a>。</p>
<p><code>tools/risrc.js</code></p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="o">=</span><span class="p">{</span><span class="nx">dll</span><span class="o">:</span><span class="kc">true</span><span class="p">,</span><span class="nx">dllPlugin</span><span class="o">:</span><span class="p">{</span><span class="nx">path</span><span class="o">:</span><span class="s1">'node_modules/ris-react-boilerplate-dlls'</span><span class="p">,</span><span class="nx">exclude</span><span class="o">:</span><span class="p">[],</span><span class="nx">include</span><span class="o">:</span><span class="p">[],</span><span class="nx">dlls</span><span class="o">:</span><span class="kc">null</span><span class="p">,</span><span class="p">},</span><span class="p">};</span></code></pre>
</div>
<p>第二，提供了<b>生成器（generators）</b>功能，可以快速創建組件和頁面。</p>
<p>很多時候，我們新建一個<b>頁面</b>或<b>組件</b>，都是通過拷貝的方式，把之前的頁面代碼拷貝過來刪除修改，這樣效率非常低而且不可控。</p>
<p>這裡我們可以通過<b>add</b>命令根據設置好的模板快速創建組建和頁面，</p>
<div class="highlight">
<pre><code class="language-bash">npm run add</code></pre>
</div>
<p class="ztext-empty-paragraph"></p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-c1cd3c24d653ffe42870175e872091e7_r.jpg" data-caption="" data-size="normal" data-rawwidth="1490" data-rawheight="856" class="origin_image zh-lightbox-thumb" width="1490" data-original="https://pic4.zhimg.com/v2-c1cd3c24d653ffe42870175e872091e7_b.jpg" title="v2-c1cd3c24d653ffe42870175e872091e7_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-c1cd3c24d653ffe42870175e872091e7_r.jpg" data-caption="" data-size="normal" data-rawwidth="1490" data-rawheight="856" class="origin_image zh-lightbox-thumb lazy" width="1490" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1490'%20height='856'&gt;&lt;/svg&gt;" data-actualsrc="https://pic4.zhimg.com/v2-c1cd3c24d653ffe42870175e872091e7_b.jpg" title="v2-c1cd3c24d653ffe42870175e872091e7_r"></figure>
<p class="ztext-empty-paragraph"></p>
<p>組件和頁面的模板可以在項目中<code>tools/generators</code>裡進行配置，詳細參考<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris/blob/master/docs/development/generator.md" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文檔</a>。</p>
<p>這兩塊功能的想法來源於<a href="https://link.zhihu.com/?target=https%3A//github.com/react-boilerplate/react-boilerplate" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">react-boilerplate</a> 。</p>
<h2>高性能</h2>
<p>出於性能上考慮，單頁應用龐大起來的時候， <code>bundle</code>的體積是很大的，所以使用了<a href="https://link.zhihu.com/?target=https%3A//github.com/jamiebuilds/react-loadable" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">react-loadable</a>對模塊進行動態加載。</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">import</span><span class="nx">Loadable</span><span class="nx">from</span><span class="s1">'react-loadable'</span><span class="p">;</span><span class="kr">export</span><span class="k">default</span><span class="nx">Loadable</span><span class="p">({</span><span class="nx">loader</span><span class="o">:</span><span class="p">()</span><span class="p">=&gt;</span><span class="kr">import</span><span class="p">(</span><span class="s1">'./index'</span><span class="p">),</span><span class="nx">loading</span><span class="o">:</span><span class="p">()</span><span class="p">=&gt;</span><span class="kc">null</span><span class="p">,</span><span class="p">});</span></code></pre>
</div>
<p>詳細可參考<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris/blob/master/docs/performance/code-splitting.md" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文檔</a>。</p>
<h2>強大的數據模擬功能</h2>
<p>數據模擬在開發過程中是非常重要的一環，在服務端沒有準備好接口數據的時候，我們往往需要自己在本地模擬數據，這裡我寫了個小工具<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris/tree/master/packages/ris-mock" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">@ris/mock</a>來實現這個功能。</p>
<p>只要在項目根目錄下的<code>mock</code>文件夾配置下<code>rules.js</code>文件即可。詳細參考<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris/blob/master/docs/development/mock-data.md" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文檔</a>。</p>
<p><code>mock/rules.js</code></p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="o">=</span><span class="p">{</span><span class="s1">'GET /api/user'</span><span class="o">:</span><span class="p">{</span><span class="nx">name</span><span class="o">:</span><span class="s1">'beyondxgb'</span><span class="p">},</span><span class="s1">'POST /api/form/create'</span><span class="o">:</span><span class="p">{</span><span class="nx">success</span><span class="o">:</span><span class="kc">true</span><span class="p">},</span><span class="s1">'GET /api/cases/list'</span><span class="o">:</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="nx">res</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span><span class="nx">res</span><span class="p">.</span><span class="nx">end</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([{</span><span class="nx">id</span><span class="o">:</span><span class="mi">1</span><span class="p">,</span><span class="nx">name</span><span class="o">:</span><span class="s1">'demo'</span><span class="p">}]));</span><span class="p">},</span><span class="s1">'GET /api/user/list'</span><span class="o">:</span><span class="s1">'user/list.json'</span><span class="p">,</span><span class="s1">'GET /api/user/create'</span><span class="o">:</span><span class="s1">'user/create.js'</span><span class="p">,</span><span class="p">};</span></code></pre>
</div>
<h2>高效的數據流處理</h2>
<p>在數據流上的處理，沒有直接使用<a href="https://link.zhihu.com/?target=https%3A//redux.js.org/" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">redux</a> ，因為使用過都知道，建太多文件太繁瑣了，而且沒有很好地處理異步數據流，在此之前， <a href="https://link.zhihu.com/?target=https%3A//github.com/dvajs/dva" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">dva</a>提出了<b>model</b>的概念，我覺得這已經是非常好地解決了<code>redux</code>的問題，後來出的一些庫也離不開<code>model</code>的概念，但<code>dva</code>或者<code>mirrorx</code> ，它們的定位是大而全的框架，其實是解決<code>redux</code>的問題，卻要混著其他東西打包成一個框架，這樣顯得不純，這裡我自己寫了個<a href="https://link.zhihu.com/?target=https%3A//github.com/beyondxgb/xredux" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">xredux</a> ，參考了<a href="https://link.zhihu.com/?target=https%3A//github.com/dvajs/dva" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">dva</a>和<a href="https://link.zhihu.com/?target=https%3A//github.com/mirrorjs/mirror" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">mirrorx</a>的一些想法，封裝出一個庫，只是單純解決<code>reudx</code>的問題，不依賴於任何框架，可以看作是<code>redux</code>的升級版。</p>
<p>用法上和<code>dva</code> 、 <code>mirrorx</code>差不多，大概如下：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kr">import</span><span class="nx">xredux</span><span class="nx">from</span><span class="s1">'xredux'</span><span class="p">;</span><span class="kr">const</span><span class="nx">store</span><span class="o">=</span><span class="nx">xredux</span><span class="p">.</span><span class="nx">createStore</span><span class="p">();</span><span class="nx">xredux</span><span class="p">.</span><span class="nx">model</span><span class="p">({</span><span class="nx">namespace</span><span class="o">:</span><span class="s1">'app'</span><span class="p">,</span><span class="nx">initialState</span><span class="o">:</span><span class="p">{</span><span class="p">},</span><span class="nx">reducers</span><span class="o">:</span><span class="p">{</span><span class="p">},</span><span class="nx">effects</span><span class="o">:</span><span class="p">{</span><span class="p">},</span><span class="p">});</span></code></pre>
</div>
<p>很好地把<code>action</code> 、 <code>reducers</code> 、 <code>state</code>結合到一個<code>model</code>裡面，在異步請求的處理上也非常地優雅，詳細使用可參考<a href="https://link.zhihu.com/?target=https%3A//github.com/beyondxgb/xredux" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文檔</a>。</p>
<h2>結語</h2>
<p><b>RIS</b>是我自己工作上的沉澱，開源出來給大家互相學習，希望能給部分人帶來幫助和啟發，裡面所使用的技術也是個人的主觀想法，不喜勿噴。後面會逐漸將自己的一些最佳實踐沉澱在這裡，也歡迎大家貢獻自己的想法。</p>
<p>如果喜歡<b>RIS</b>的話，歡迎使用，有問題可以提<a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris/issues" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">issues</a> ，也非常歡迎進行共建。</p>
<p>如果不喜歡<b>RIS</b>的話，可以好好閱讀下源碼，做前端構建工具的模式也就是這樣子，可以參照自己做一下。</p>
<h2>資料</h2>
<ul>
<li><a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/ris" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">ris</a></li>
<li> <a href="https://link.zhihu.com/?target=https%3A//github.com/risjs/create-ris" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">create-ris</a></li>
<li> <a href="https://link.zhihu.com/?target=https%3A//github.com/beyondxgb/xredux" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">xredux</a></li>
</ul>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14344/topic-57281453/" data-wpel-link="internal">RIS，創建React 應用的新選擇</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>JS趣味算法學習- 實現二叉排序樹</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/14152/topic-46920212/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 17:50:57 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/14152/topic-46920212/</guid>

					<description><![CDATA[<p>前言我們知道dom結構也是以樹的形式存在的，所以了解樹的這種數據結構對於我們分析前端代碼還是很重要的。 （當然這裡跟前端沾邊也是為了吸引大家學習的興趣，真相其實是我就單純的想寫這一章...(｡•ˇ‸ˇ•｡)…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14152/topic-46920212/" data-wpel-link="internal">JS趣味算法學習- 實現二叉排序樹</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">JS趣味算法學習- 實現二叉排序樹</h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic4.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2><b>前言</b></h2>
<p>我們知道dom結構也是以樹的形式存在的，所以了解樹的這種數據結構對於我們分析前端代碼還是很重要的。</p>
<p>（當然這裡跟前端沾邊也是為了吸引大家學習的興趣，真相其實是我就單純的想寫這一章...(｡•ˇ‸ˇ•｡) ...）</p>
<p>廢話不多說，我們先從二叉排序樹開始學起吧。</p>
<h2><b>二叉排序樹（BST Binary Search Tree）</b></h2>
<p>什麼是二叉排序樹？</p>
<p>簡單來說，就是每一個點只能最多有兩個子節點。它有下面三個屬性：</p>
<ul>
<li>若左子樹不空，則左子樹上所有結點的值均小於它的根結點的值；</li>
<li>若右子樹不空，則右子樹上所有結點的值均大於或等於它的根結點的值；</li>
<li><b>左、右子樹也分別為二叉排序樹，這點很重要，希望大家記住。</b></li>
</ul>
<figure data-size="normal"><noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="409" data-rawheight="373" class="content_image" width="409" data-original="https://pic2.zhimg.com/v2-56b0128f74e9a366ca35b2162538b249_b.jpg"></noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="409" data-rawheight="373" class="content_image lazy" width="409" data-actualsrc="https://pic2.zhimg.com/v2-56b0128f74e9a366ca35b2162538b249_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='409'%20height='373'&gt;&lt;/svg&gt;"></figure>
<p>對於這顆樹來說，23是它的根節點。而一個父節點的兩個子節點分別稱為<b>左節點</b>跟<b>右節點。</b>在</p>
<p>二叉樹中，左邊的節點一定都小於23，右邊節點都大於等於23。</p>
<h2><b>實現BST</b></h2>
<p>BST由節點組成。所以先來看看節點的實現：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">Node</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span><span class="nx">left</span><span class="p">,</span><span class="nx">right</span><span class="p">)</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="o">=</span><span class="nx">data</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">left</span><span class="o">=</span><span class="nx">left</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">right</span><span class="o">=</span><span class="nx">right</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">show</span><span class="o">=</span><span class="kd">function</span><span class="p">()</span><span class="p">{</span><span class="k">return</span><span class="k">this</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>這個節點裡，傳入的data就是節點的數據。 left，right分別為左右子節點。</p>
<p>現在需要定義一個二叉查找樹（下面簡稱BST）的類，這個類有一個根節點root，和一個插入函數insert</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">BST</span><span class="p">()</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">root</span><span class="o">=</span><span class="kc">null</span><span class="p">;</span><span class="k">this</span><span class="p">.</span><span class="nx">insert</span><span class="o">=</span><span class="nx">insert</span><span class="p">;</span><span class="p">}</span></code></pre>
</div>
<p>難點在於這個插入函數，先拋開這個函數是如何寫的，我們先來想想BST是如何長成的。假設我需要在BST裡依次插入23,45,16,37,3,99,22這些數字，那麼插入的順序如下：</p>
<ol>
<li>第一個數字為根節點，即this.root = 23</li>
<li>第二個數字為45，對比23，如果比23大，放在23的右邊節點。此時BST如下：</li>
</ol>
<figure data-size="normal"><noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="236" data-rawheight="233" class="content_image" width="236" data-original="https://pic1.zhimg.com/v2-df0f901a3d2ac6b8af737843be585974_b.jpg"></noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="236" data-rawheight="233" class="content_image lazy" width="236" data-actualsrc="https://pic1.zhimg.com/v2-df0f901a3d2ac6b8af737843be585974_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='236'%20height='233'&gt;&lt;/svg&gt;"></figure>
<p>3.第三個數字為16，對比23，比23小，成為23的左節點。此時BST如下：</p>
<figure data-size="normal"><noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="231" data-rawheight="233" class="content_image" width="231" data-original="https://pic1.zhimg.com/v2-cc9c157f79aace6586f4913346d65c70_b.jpg"></noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="231" data-rawheight="233" class="content_image lazy" width="231" data-actualsrc="https://pic1.zhimg.com/v2-cc9c157f79aace6586f4913346d65c70_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='231'%20height='233'&gt;&lt;/svg&gt;"></figure>
<p>4.第四個數字為37，先對比23，比23大，放在右邊。右邊再看23的右節點有值，是45，</p>
<p>37比45小，所以放在45的左邊。</p>
<figure data-size="normal"><noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="269" data-rawheight="363" class="content_image" width="269" data-original="https://pic3.zhimg.com/v2-410e9a7c6a09e0a8a6c7956ef63337f6_b.jpg"></noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="269" data-rawheight="363" class="content_image lazy" width="269" data-actualsrc="https://pic3.zhimg.com/v2-410e9a7c6a09e0a8a6c7956ef63337f6_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='269'%20height='363'&gt;&lt;/svg&gt;"></figure>
<p>5.第五個數字為3，對比23，比23小，放左邊，再看左邊23的左節點16，3比16小，</p>
<p>所以放在16的左邊。</p>
<figure data-size="normal"><noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="366" data-rawheight="399" class="content_image" width="366" data-original="https://pic3.zhimg.com/v2-998be70af905a787fcfe675de01f3332_b.jpg"></noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="366" data-rawheight="399" class="content_image lazy" width="366" data-actualsrc="https://pic3.zhimg.com/v2-998be70af905a787fcfe675de01f3332_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='366'%20height='399'&gt;&lt;/svg&gt;"></figure>
<p>6.第六個數字為99，對比23，比23大，看23的右節點45，比45大，放在45的右節點</p>
<figure data-size="normal"><noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="416" data-rawheight="381" class="content_image" width="416" data-original="https://pic3.zhimg.com/v2-f595340843abe192fdf15602e965ca8a_b.jpg"></noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="416" data-rawheight="381" class="content_image lazy" width="416" data-actualsrc="https://pic3.zhimg.com/v2-f595340843abe192fdf15602e965ca8a_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='416'%20height='381'&gt;&lt;/svg&gt;"></figure>
<p>7. 第七個數字為22，對比23，比23小，看23的左節點16，比16大，放在16的右節點。</p>
<figure data-size="normal"><noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="409" data-rawheight="373" class="content_image" width="409" data-original="https://pic1.zhimg.com/v2-c7fa0a6af9260e6bc930e0aa9eca1850_b.jpg"></noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="409" data-rawheight="373" class="content_image lazy" width="409" data-actualsrc="https://pic1.zhimg.com/v2-c7fa0a6af9260e6bc930e0aa9eca1850_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='409'%20height='373'&gt;&lt;/svg&gt;"></figure>
<p>8.如果最後一個數字是100，對比23，比23大，看23的右節點45，比45大，再看45的右節點99，比99大，所以放在99的右節點處。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-815568e185f9d2b3dff4760fc8a833d9_r.jpg" data-caption="" data-size="normal" data-rawwidth="512" data-rawheight="519" class="origin_image zh-lightbox-thumb" width="512" data-original="https://pic2.zhimg.com/v2-815568e185f9d2b3dff4760fc8a833d9_b.jpg" title="v2-815568e185f9d2b3dff4760fc8a833d9_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-815568e185f9d2b3dff4760fc8a833d9_r.jpg" data-caption="" data-size="normal" data-rawwidth="512" data-rawheight="519" class="origin_image zh-lightbox-thumb lazy" width="512" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='512'%20height='519'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-815568e185f9d2b3dff4760fc8a833d9_b.jpg" title="v2-815568e185f9d2b3dff4760fc8a833d9_r"></figure>
<p>以上，就是一顆BST生成的過程。</p>
<p>接下來我們用計算機的語言描述一下上述過程:（以插入右邊節點為例）</p>
<ol>
<li>針對第一個節點，直接將它設置成根節點。</li>
<li>設置一個指針parent用於追踪當前的父節點。</li>
<li>節點跟當前的parent進行對比，如果&gt; parent, 看parent.right 是否有值，沒有的話，成功找到當前節點n的位置，即parent.right = n，此時終止程序。</li>
<li>如果parent.right有值，這麼這個parent.right就會成為最新的parent，在重複3步驟。</li>
</ol>
<p>下面我們用JS來實現一下上述過程：</p>
<h2><b>JS代碼實現</b></h2>
<div class="highlight">
<pre><code class="language-js"><span class="kd">function</span><span class="nx">insert</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span><span class="p">{</span><span class="kd">var</span><span class="nx">n</span><span class="o">=</span><span class="k">new</span><span class="nx">Node</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span><span class="kc">null</span><span class="p">,</span><span class="kc">null</span><span class="p">);</span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">root</span><span class="p">)</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">root</span><span class="o">=</span><span class="nx">n</span><span class="p">;</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="c1">//使用current的指針來探索子節點</span><span class="kd">var</span><span class="nx">current</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">root</span><span class="p">;</span><span class="k">while</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="p">{</span><span class="kd">var</span><span class="nx">parent</span><span class="o">=</span><span class="nx">current</span><span class="p">;</span><span class="k">if</span><span class="p">(</span><span class="nx">data</span><span class="o">&lt;</span><span class="nx">parent</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span><span class="p">{</span><span class="nx">current</span><span class="o">=</span><span class="nx">current</span><span class="p">.</span><span class="nx">left</span><span class="p">;</span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">current</span><span class="p">)</span><span class="p">{</span><span class="nx">parent</span><span class="p">.</span><span class="nx">left</span><span class="o">=</span><span class="nx">n</span><span class="p">;</span><span class="k">break</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="nx">current</span><span class="o">=</span><span class="nx">current</span><span class="p">.</span><span class="nx">right</span><span class="p">;</span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">current</span><span class="p">)</span><span class="p">{</span><span class="nx">parent</span><span class="p">.</span><span class="nx">right</span><span class="o">=</span><span class="nx">n</span><span class="p">;</span><span class="k">break</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="p">}</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>這樣我們這個BST的類就完成啦~~</p>
<p>運行上面的代碼：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">var</span><span class="nx">nums</span><span class="o">=</span><span class="k">new</span><span class="nx">BST</span><span class="p">();</span><span class="nx">nums</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="mi">23</span><span class="p">);</span><span class="nx">nums</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="mi">45</span><span class="p">);</span><span class="nx">nums</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="mi">16</span><span class="p">);</span><span class="nx">nums</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="mi">37</span><span class="p">);</span><span class="nx">nums</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span><span class="nx">nums</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="mi">99</span><span class="p">);</span><span class="nx">nums</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">nums</span><span class="p">)</span></code></pre>
</div>
<p>在將結果nums打印出來,如果你得到了這樣的數據結構，那麼恭喜你，你的BST生成成功啦。</p>
<p>( &gt;﹏&lt;。)～</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-d6c6752577d1e848f935682cb078f061_r.jpg" data-caption="" data-size="normal" data-rawwidth="567" data-rawheight="327" class="origin_image zh-lightbox-thumb" width="567" data-original="https://pic2.zhimg.com/v2-d6c6752577d1e848f935682cb078f061_b.jpg" title="v2-d6c6752577d1e848f935682cb078f061_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-d6c6752577d1e848f935682cb078f061_r.jpg" data-caption="" data-size="normal" data-rawwidth="567" data-rawheight="327" class="origin_image zh-lightbox-thumb lazy" width="567" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='567'%20height='327'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-d6c6752577d1e848f935682cb078f061_b.jpg" title="v2-d6c6752577d1e848f935682cb078f061_r"></figure>
<h2><b>遍歷</b></h2>
<p>遍歷分為前序，中序，以及後序遍歷，他們的命名是以<b>根節點</b>的訪問次序劃分的：</p>
<blockquote><p>前序遍歷：根節點-&gt;左子樹-&gt;右子樹中序遍歷：左子樹-&gt;根節點-&gt;右子樹後序遍歷：左子樹-&gt;右子樹-&gt;根節點</p></blockquote>
<p>以我們剛剛生成的BST為例：</p>
<figure data-size="normal"><noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="409" data-rawheight="373" class="content_image" width="409" data-original="https://pic2.zhimg.com/v2-56b0128f74e9a366ca35b2162538b249_b.jpg"></noscript><img decoding="async" src="" data-caption="" data-size="normal" data-rawwidth="409" data-rawheight="373" class="content_image lazy" width="409" data-actualsrc="https://pic2.zhimg.com/v2-56b0128f74e9a366ca35b2162538b249_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='409'%20height='373'&gt;&lt;/svg&gt;"></figure>
<p>前序遍歷：23 16 3 22 45 37 99</p>
<p>中序遍歷：3 16 22 23 37 45 99 （這時候就會發現：臥槽，6的飛起啊，這不就是升序的排列麼）</p>
<p>後序遍歷：3 22 16 37 99 45 23</p>
<p>怎麼理解呢？以中序遍歷為例，由於中序遍歷遵循：<b>左子樹-&gt;根節點-&gt;右子樹</b></p>
<p>所以這顆樹的排列順序為16（左子樹） -&gt; 23(根節點) -&gt; 45(右子樹)</p>
<p>/  / </p>
<p>3 22 37 99</p>
<p>在看左子樹，開篇的時候我們說過，左右字數都依舊是BST，所以對於左右子樹，都還是遵循這個排序：<b>左子樹-&gt;根節點-&gt;右子樹</b>；</p>
<p>那麼，對於左字樹來說，排序順序為3 -&gt; 16 -&gt; 22, 對於右字樹，排列順序是37 -&gt; 45 -&gt; 99。</p>
<p>連起來就是3 16 22 23 37 45 99。</p>
<p>前序跟後序同理。</p>
<p>下一篇，我們將了解如何使用算法完成這三種遍歷，而且要從遞歸函數講起，大家可以先自己思考一下~</p>
<p class="ztext-empty-paragraph"></p>
<p>本文有幫助的話，點個贊鼓勵一下作者唄ლ(＾ω＾ლ)</p>
<p>參考資料：</p>
<p>1、《數據結構與算法JS描述》</p>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14152/topic-46920212/" data-wpel-link="internal">JS趣味算法學習- 實現二叉排序樹</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>趣味算法思想</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/14144/topic-46223775/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 17:50:11 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/14144/topic-46223775/</guid>

					<description><![CDATA[<p>隨意的前言好久都沒有寫文章啦，React系列的文章還差一篇，但是感覺講+寫已經很膩了，所以先放一放，換一下口味吧！今天我們來看看算法在實際問題中的應用，還是很有意思的哇~~~ 對撞指針思想：對撞指針的意思就…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14144/topic-46223775/" data-wpel-link="internal">趣味算法思想</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">趣味算法思想</h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic4.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2><b>隨意的前言</b></h2>
<p>好久都沒有寫文章啦，React系列的文章還差一篇，但是感覺講+寫已經很膩了，所以先放一放，換一下口味吧！</p>
<p>今天我們來看看算法在實際問題中的應用，還是很有意思的哇~~~</p>
<h2><b>對撞指針</b></h2>
<p><b>思想</b>：對撞指針的意思就是有兩個指針，一個從開頭，一個從結尾，兩個指針分別++，--直到碰撞<b>。</b></p>
<p>這個思想可以解決什麼問題呢？</p>
<p><b>題目</b>：leedcode 167</p>
<p>給定一個從小到大<b>有序</b>的數組，從數組裡找到兩個數字可以等於target,並返回索引。</p>
<p>即：</p>
<blockquote><p><b>Input:</b> numbers = [2,7,11,15], target = 9<br /><b>Output:</b> [1,2]</p></blockquote>
<p><b>分析</b>：這道題可以直接用查找的方法，一層層循環去找。但是這樣做時間複雜度為O(n2),而且完全沒有利用到我們這個數組有序的特性。</p>
<p>更好的解法是：<b>兩個指針，分別從兩邊去找，相加&gt;target，右邊指針--前移，如果&lt;target,左邊指針++前移，一直到這兩個指針碰撞，如果找到結果可以返回相應的索引。</b></p>
<p>代碼實現：</p>
<div class="highlight">
<pre><code class="language-js"><span class="kd">var</span><span class="nx">twoSum</span><span class="o">=</span><span class="kd">function</span><span class="p">(</span><span class="nx">nums</span><span class="p">,</span><span class="nx">target</span><span class="p">)</span><span class="p">{</span><span class="kd">var</span><span class="nx">left</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="kd">var</span><span class="nx">right</span><span class="o">=</span><span class="nx">nums</span><span class="p">.</span><span class="nx">length</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span><span class="c1">//對撞的循環條件：左邊指針小於右邊指針</span><span class="k">while</span><span class="p">(</span><span class="nx">left</span><span class="o">&lt;</span><span class="nx">right</span><span class="p">)</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="nx">nums</span><span class="p">[</span><span class="nx">left</span><span class="p">]</span><span class="o">+</span><span class="nx">nums</span><span class="p">[</span><span class="nx">right</span><span class="p">]</span><span class="o">===</span><span class="nx">target</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="p">[</span><span class="nx">left</span><span class="p">,</span><span class="nx">right</span><span class="p">]</span><span class="p">}</span><span class="k">else</span><span class="k">if</span><span class="p">(</span><span class="nx">nums</span><span class="p">[</span><span class="nx">left</span><span class="p">]</span><span class="o">+</span><span class="nx">nums</span><span class="p">[</span><span class="nx">right</span><span class="p">]</span><span class="o">&gt;</span><span class="nx">target</span><span class="p">)</span><span class="p">{</span><span class="nx">right</span><span class="o">--</span><span class="p">;</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="nx">left</span><span class="o">++</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="p">};</span></code></pre>
</div>
<h2><b>滑動窗口</b></h2>
<p><b>思想：</b></p>
<p>有兩個指針構成一個區間，通過往<b>同一個方向</b>不停的移動，來找到滿足條件的區間。</p>
<p>滑動窗口關鍵的是想想初始時候，兩個指針在哪，在滑動的時候，兩個指針基於什麼條件移動，移動後的位置又在哪裡。</p>
<p>下面來分析一個題目來看看如何用這個思想。</p>
<p><b>題目</b>：leedcode 209</p>
<p>給定一個整形數組和一個數字s,找到數組中最短的一個<b>連續</b>子數組，使得連續子數組的數組和sum &gt;= s,返回這個最短的連續子數組的長度值。</p>
<p>即：</p>
<blockquote><p><b>Input:</b> s = 7, nums = [2,3,1,2,4,3]<br /><b>Output:</b> 2</p></blockquote>
<p><b>分析：</b>這裡依舊可以採取暴力解法，找出所有的子數組，再分別相加求解。但是這裡介紹更好的解法。</p>
<p>這裡可以看到由於需要找<b>連續的子數組</b>，所以依舊可以設置兩個指針，往同一方向移動。</p>
<p>如果兩個指針中間的值加起來&gt;sum的時候，記錄此時數組的長度，接著左指針移動，減小sum的值；</p>
<p>如果&lt; sum的話，右指針移動擴大範圍。</p>
<p>最後返回最短的長度值。</p>
<p><b>代碼實現：</b></p>
<div class="highlight">
<pre><code class="language-js"><span class="cm">/**</span><span class="cm">* @param {number} s</span><span class="cm">* @param {number[]} nums</span><span class="cm">* @return {number}</span><span class="cm">*/</span><span class="kd">var</span><span class="nx">minSubArrayLen</span><span class="o">=</span><span class="kd">function</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span><span class="nx">nums</span><span class="p">)</span><span class="p">{</span><span class="kd">var</span><span class="nx">left</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="kd">var</span><span class="nx">right</span><span class="o">=</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span><span class="c1">// right的起始位置很重要，這裡選擇-1 [left, right]這個區間剛開始是沒有值的</span><span class="kd">var</span><span class="nx">tmpSum</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="kd">var</span><span class="nx">minLength</span><span class="p">;</span><span class="c1">//循環停止的條件是左指針小於長度</span><span class="k">while</span><span class="p">(</span><span class="nx">left</span><span class="o">&lt;</span><span class="nx">nums</span><span class="p">.</span><span class="nx">length</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="nx">tmpSum</span><span class="o">&lt;</span><span class="nx">s</span><span class="p">)</span><span class="p">{</span><span class="c1">//這裡要注意邊界的處理，當右指針移動到最後一個元素的時候結束</span><span class="k">if</span><span class="p">(</span><span class="nx">right</span><span class="o">&gt;=</span><span class="nx">nums</span><span class="p">.</span><span class="nx">length</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="nx">minLength</span><span class="o">||</span><span class="mi">0</span><span class="p">;</span><span class="p">}</span><span class="nx">right</span><span class="o">++</span><span class="p">;</span><span class="c1">//這裡tmpSum的計算也很巧妙，直接用累加的方式，節省計算量</span><span class="nx">tmpSum</span><span class="o">=</span><span class="nx">tmpSum</span><span class="o">+</span><span class="nx">nums</span><span class="p">[</span><span class="nx">right</span><span class="p">]</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="kd">var</span><span class="nx">tmp</span><span class="o">=</span><span class="nx">right</span><span class="o">-</span><span class="nx">left</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span><span class="k">if</span><span class="p">(</span><span class="nx">minLength</span><span class="p">)</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="nx">tmp</span><span class="o">&lt;</span><span class="nx">minLength</span><span class="p">)</span><span class="p">{</span><span class="nx">minLength</span><span class="o">=</span><span class="nx">tmp</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="nx">minLength</span><span class="o">=</span><span class="nx">tmp</span><span class="p">;</span><span class="p">}</span><span class="c1">//左邊指針移動減少sum的值</span><span class="nx">tmpSum</span><span class="o">=</span><span class="nx">tmpSum</span><span class="o">-</span><span class="nx">nums</span><span class="p">[</span><span class="nx">left</span><span class="p">];</span><span class="nx">left</span><span class="o">++</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">minLength</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="mi">0</span><span class="p">;</span><span class="p">}</span><span class="k">return</span><span class="nx">minLength</span><span class="p">;</span><span class="p">};</span></code></pre>
</div>
<h2><b>查找</b></h2>
<p>這裡先介紹兩種數據結構<a href="https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">Set</a> <a href="https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">Map</a> ，不了解的同學先自行學習一下，其實很簡單，Set類型就是只有key的一種數據結構，而Map類型有key，value。</p>
<p>而在JS中，Object類型基本跟Map類型的一樣使用，但是還是有些不同的，也可以在這邊<a href="https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">文章</a>裡了解不同。</p>
<p>接下來通過具體的例子來看看如何使用它們。</p>
<p><b>題目</b>：leedcode 1</p>
<p>這個問題跟開頭的問題一樣，給定一個數組numbers，從數組裡找到兩個數字可以等於target,並返回索引。</p>
<p>即：</p>
<blockquote><p><b>Input:</b> numbers = [2,7,11,15], target = 9 <b>Output:</b> [1,2]</p></blockquote>
<p><b>分析：</b></p>
<p>除了暴力的解法，我們還可以用剛剛介紹的對撞指針的思路，先給它排序，在使用對撞指針，但是我們這裡由於要返回索引，所以還要記錄排序前的索引。其實這個思路也是有點麻煩的。</p>
<p>這裡在介紹巧妙查找表的思路：</p>
<p>我們可以先定義一個Object類型的數據結構obj，它的key為target - numbers[i]（比如數組第一項為2），value為索引。然後每次都看看obj[numbers[i]] 是否存在，如果存在，那我們就找到了這樣的一組數據，返回當前索引以及obj[numbers[i]]。</p>
<p><b>代碼實現：</b></p>
<div class="highlight">
<pre><code class="language-js"><span class="cm">/**</span><span class="cm">* @param {number[]} nums</span><span class="cm">* @param {number} target</span><span class="cm">* @return {number[]}</span><span class="cm">*/</span><span class="kd">var</span><span class="nx">twoSum</span><span class="o">=</span><span class="kd">function</span><span class="p">(</span><span class="nx">nums</span><span class="p">,</span><span class="nx">target</span><span class="p">)</span><span class="p">{</span><span class="kd">var</span><span class="nx">obj</span><span class="o">=</span><span class="p">{};</span><span class="k">for</span><span class="p">(</span><span class="kd">var</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;</span><span class="nx">nums</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="p">{</span><span class="kr">const</span><span class="nx">item</span><span class="o">=</span><span class="nx">nums</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span><span class="k">if</span><span class="p">(</span><span class="nx">obj</span><span class="p">[</span><span class="nx">item</span><span class="p">]</span><span class="o">&gt;=</span><span class="mi">0</span><span class="p">)</span><span class="p">{</span><span class="k">return</span><span class="p">[</span><span class="nx">obj</span><span class="p">[</span><span class="nx">item</span><span class="p">],</span><span class="nx">i</span><span class="p">]</span><span class="p">}</span><span class="k">else</span><span class="p">{</span><span class="nx">obj</span><span class="p">[</span><span class="nx">target</span><span class="o">-</span><span class="nx">item</span><span class="p">]</span><span class="o">=</span><span class="nx">i</span><span class="p">;</span><span class="p">}</span><span class="p">}</span><span class="p">};</span></code></pre>
</div>
<p><b>面試中常考的數組去重的問題，也可以藉助Object的查找思路，可以自行練習。</b></p>
<h2><b>總結</b></h2>
<p>因為在公司有後台處理數據，所以總覺得算法跟前端沒有太大的關係，在看React源碼的時候，裡面使用了深度查找的思想。所以我覺得學好算法對我們還是很重要的。後面接著~</p>
<p class="ztext-empty-paragraph"></p>
<p>本文對你有幫助的話，點個贊鼓勵一下作者唄(੭ु≧▽≦)੭ु</p>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14144/topic-46223775/" data-wpel-link="internal">趣味算法思想</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>從渲染原理到性能優化（一）&#8211; 首次渲染</title>
		<link>https://hypergrowths.com/software-engineering/front-end-dev/14078/topic-43145754/</link>
		
		<dc:creator><![CDATA[marketer]]></dc:creator>
		<pubDate>Sat, 30 Jan 2021 17:46:51 +0000</pubDate>
				<category><![CDATA[前端開發]]></category>
		<category><![CDATA[前端這件小事]]></category>
		<guid isPermaLink="false">https://hypergrowths.com/software-engineering/front-end-dev/14078/topic-43145754/</guid>

					<description><![CDATA[<p>前言以下，是我在2018 React Conf 的分享內容，希望對大家有所幫助。可以先在官網下載我的ppt對照看，效果更佳哦~☺。很多人都使用過React，但是很少人能說出它內部的渲染原理。有人會說，會用就行了，知道渲染原…</p>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14078/topic-43145754/" data-wpel-link="internal">從渲染原理到性能優化（一）&#8211; 首次渲染</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></description>
										<content:encoded><![CDATA[<article class="Post-Main Post-NormalMain" tabindex="-1">
<header class="Post-Header">
<h1 class="Post-Title">從渲染原理到性能優化（一）-- 首次渲染</h1>
<div class="Post-Author">
<div class="AuthorInfo" itemprop="author" itemscope="" itemtype="http://schema.org/Person"><meta itemprop="name" content="daisy"><meta itemprop="image" content="https://pic2.zhimg.com/v2-7d40ac4f2f59ac2c9f93d80332b334f1_l.jpg?source=172ae18b"><meta itemprop="url" content="https://www.zhihu.com/people/huang-qiong-50-1"><meta itemprop="zhihu:followerCount"></div>
</div>
</header>
<div class="Post-RichTextContainer">
<div class="RichText ztext Post-RichText">
<h2><b>前言</b></h2>
<blockquote><p>以下，是我在<a href="https://www.zhihu.com/question/291225885/answer/475114262" class="internal" data-wpel-link="external" rel="nofollow external noopener noreferrer">2018 React Conf</a>的分享內容，希望對大家有所幫助。可以先在<a href="https://link.zhihu.com/?target=https%3A//react.w3ctech.com/%23schedule" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">官網</a>下載我的<a href="https://link.zhihu.com/?target=https%3A//img.w3ctech.com/%25E4%25BB%258E%25E6%25B8%25B2%25E6%259F%2593%25E5%258E%259F%25E7%2590%2586%25E5%2588%25B0%25E6%2580%25A7%25E8%2583%25BD%25E4%25BC%2598%25E5%258C%2596%25E4%25BF%25AE%25E6%2594%25B9%25E7%2589%2588.pptx" class=" wrap external" target="_blank" rel="noreferrer noopener nofollow external" data-wpel-link="external">ppt</a>對照看，效果更佳哦~<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/263a.png" alt="☺" class="wp-smiley" style="height: 1em; max-height: 1em;" />。</p></blockquote>
<p>很多人都使用過React，但是很少人能說出它內部的渲染原理。有人會說，會用就行了，知道渲染原理有必要么？其實渲染原理決定著性能優化的方法，只有在了解原理之後，才能完全理解為什麼這樣做可以優化性能。正所謂：知其然，然後知其所以然。</p>
<p>廢話不多說，下面我們就開始吧~</p>
<p>本篇文章，將會分為四部分介紹：</p>
<ul>
<li><b>JSX如何生成element</b></li>
</ul>
<p>當我們寫下一段JSX代碼的時候，react是如何根據我們的JSX代碼來生成虛擬DOM的組成元素element的。</p>
<ul>
<li><b>element如何生成真實DOM節點</b></li>
</ul>
<p>再生成elment之後，react又如何將其轉成瀏覽器的真實節點。這裡會通過介紹首次渲染以及更新渲染的流程來幫助大家理解這個渲染流程，</p>
<ul>
<li><b>性能優化</b></li>
</ul>
<p>結合渲染原理，通過實際例子，看看如何優化組件。</p>
<ul>
<li><b>React 16異步渲染方案</b></li>
</ul>
<p>到目前為止，這些優化組件的方法還不能解決什麼問題，所以我們需要引入異步渲染，以及異步渲染的原理是什麼。</p>
<hr>
<h2><b>一、JSX如何生成element</b></h2>
<p>這裡是一段寫在render裡的jsx代碼。</p>
<div class="highlight">
<pre><code class="language-js"><span class="k">return</span><span class="p">(</span><span class="o">&lt;</span><span class="nx">div</span><span class="nx">className</span><span class="o">=</span><span class="s2">"cn"</span><span class="o">&gt;</span><span class="o">&lt;</span><span class="nx">Header</span><span class="o">&gt;</span><span class="nx">Hello</span><span class="p">,</span><span class="nx">This</span><span class="nx">is</span><span class="nx">React</span><span class="o">&lt;</span><span class="err">/Header&gt;</span><span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="nx">Start</span><span class="nx">to</span><span class="nx">learn</span><span class="nx">right</span><span class="nx">now</span><span class="o">!&lt;</span><span class="err">/div&gt;</span><span class="nx">Right</span><span class="nx">Reserve</span><span class="p">.</span><span class="o">&lt;</span><span class="err">/div&gt;</span><span class="p">)</span></code></pre>
</div>
<p>首先，它會經過babel編譯成React.createElement的表達式。</p>
<div class="highlight">
<pre><code class="language-js"><span class="k">return</span><span class="p">(</span><span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">'div'</span><span class="p">,</span><span class="p">{</span><span class="nx">className</span><span class="o">:</span><span class="s1">'cn'</span><span class="p">},</span><span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="nx">Header</span><span class="p">,</span><span class="kc">null</span><span class="p">,</span><span class="s1">'Hello, This is React'</span><span class="p">),</span><span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">'div'</span><span class="p">,</span><span class="kc">null</span><span class="p">,</span><span class="s1">'Start to learn right now!'</span><span class="p">),</span><span class="s1">'Right Reserve'</span><span class="p">)</span><span class="p">)</span></code></pre>
</div>
<p>這個createElement方法是做什麼的呢？</p>
<p>其實從它的名字就可以看出，這是用來生成element的。 <b>element在React裡，其實就是組成虛擬DOM樹的節點，它用來描述你想要在瀏覽器上看到什麼</b>。</p>
<p>它的參數有三個：</p>
<p>1、type -&gt; 標籤</p>
<p>2、attributes -&gt; 標籤屬性，沒有的話，可以為null</p>
<p>3、children -&gt; 標籤的子節點</p>
<p>這個React.createElement的表達式會在render函數被調用的時候執行，換句話說，<b>當render函數被調用的時候，會返回一個element</b> 。</p>
<p>說了那麼久element，這個element究竟長什麼樣呢？</p>
<p>其實，它就是一個對象，如下:</p>
<div class="highlight">
<pre><code class="language-js"><span class="p">{</span><span class="nx">type</span><span class="o">:</span><span class="s1">'div'</span><span class="p">,</span><span class="nx">props</span><span class="o">:</span><span class="p">{</span><span class="nx">className</span><span class="o">:</span><span class="s1">'cn'</span><span class="p">,</span><span class="nx">children</span><span class="o">:</span><span class="p">[</span><span class="p">{</span><span class="nx">type</span><span class="o">:</span><span class="kd">function</span><span class="nx">Header</span><span class="p">,</span><span class="nx">props</span><span class="o">:</span><span class="p">{</span><span class="nx">children</span><span class="o">:</span><span class="s1">'Hello, This is React'</span><span class="p">}</span><span class="p">},</span><span class="p">{</span><span class="nx">type</span><span class="o">:</span><span class="s1">'div'</span><span class="p">,</span><span class="nx">props</span><span class="o">:</span><span class="p">{</span><span class="nx">children</span><span class="o">:</span><span class="s1">'start to learn right now！'</span><span class="p">}</span><span class="p">},</span><span class="s1">'Right Reserve'</span><span class="p">]</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>我們來觀察一下這個對象的children，現在有三種類型：</p>
<p>1、string</p>
<p>2、原生DOM節點</p>
<p>3、React Component - 自定義組件</p>
<p>除了這三種，還有兩種類型：</p>
<p>4、false ,null, undefined,number</p>
<p>5、數組- 使用map方法的時候</p>
<p>這裡需要記住一個點： <b><i>element不一定是Object類型。</i></b></p>
<hr>
<h2><b>二、element如何生成真實節點</b></h2>
<p>順利得到element之後，我們再來看看React是如何把element轉化成真實DOM節點的。</p>
<p>首先，需要去初始化element,初始化的規則如下：</p>
<p>先判斷是否為Object類型，是的話，看它的type是否是原生DOM標籤，是的話，給它創建ReactDOMComponent的實例對象，其他同理。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-0cd91ebc0874096f42ab1ce14e1441a2_r.jpg" data-caption="" data-size="normal" data-rawwidth="1146" data-rawheight="356" class="origin_image zh-lightbox-thumb" width="1146" data-original="https://pic3.zhimg.com/v2-0cd91ebc0874096f42ab1ce14e1441a2_b.jpg" title="v2-0cd91ebc0874096f42ab1ce14e1441a2_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-0cd91ebc0874096f42ab1ce14e1441a2_r.jpg" data-caption="" data-size="normal" data-rawwidth="1146" data-rawheight="356" class="origin_image zh-lightbox-thumb lazy" width="1146" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1146'%20height='356'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-0cd91ebc0874096f42ab1ce14e1441a2_b.jpg" title="v2-0cd91ebc0874096f42ab1ce14e1441a2_r"></figure>
<p>這時候有的人可能會有所疑問：這些個ReactDOMComponent, ReactCompositeComponentWrapper怎麼開發的時候都沒有見過？</p>
<p>其實這些都是React的私有類，React自己使用，不會暴露給用戶的。它們的常用方法有：mountComponent,updateComponent等。其中mountComponent 用於創建組件，而updateComponent用於用戶更新組件。而我們自定義組件的<b>生命週期函數</b>以及<b>render</b>函數都是在這些私有類的方法裡被調用的。</p>
<p>既然這些私有類的方法那麼重要我們就先來簡單了解一下吧~</p>
<p><b>ReactDOMComponent</b></p>
<p>首先是ReactMComponent的mountComponent方法，這個方法的作用是：<b>將element轉成真實DOM節點，並且插入到相應的container裡，然後返回markup（realDOM）。</b></p>
<p><b>由此可知ReactDOMComponent的mountComponent是element生成真實節點的關鍵</b>。</p>
<p>下面看個栗子它是怎麼做到的吧。</p>
<p class="ztext-empty-paragraph"></p>
<figure data-size="normal"><noscript><img decoding="async" src="" data-size="normal" data-rawwidth="323" data-rawheight="426" class="content_image" width="323" data-original="https://pic1.zhimg.com/v2-0426d404dbe3e3dbc9eceeb0290b79d8_b.jpg"></noscript><img decoding="async" src="" data-size="normal" data-rawwidth="323" data-rawheight="426" class="content_image lazy" width="323" data-actualsrc="https://pic1.zhimg.com/v2-0426d404dbe3e3dbc9eceeb0290b79d8_b.jpg" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='323'%20height='426'&gt;&lt;/svg&gt;"><figcaption>圖片來源：https://www.fabiaoqing.com/biaoqing/detail/id/53371.html</figcaption></figure>
<p>假設有這樣一個type類型是原生DOM的element:</p>
<div class="highlight">
<pre><code class="language-js"><span class="p">{</span><span class="nx">type</span><span class="o">:</span><span class="s1">'div'</span><span class="p">,</span><span class="nx">props</span><span class="o">:</span><span class="p">{</span><span class="nx">className</span><span class="o">:</span><span class="s1">'cn'</span><span class="p">,</span><span class="nx">children</span><span class="o">:</span><span class="s1">'Hello world'</span><span class="p">,</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>簡單mountComponent的實現：</p>
<div class="highlight">
<pre><code class="language-js"><span class="nx">mountComponent</span><span class="p">(</span><span class="nx">container</span><span class="p">)</span><span class="p">{</span><span class="kr">const</span><span class="nx">domElement</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_currentElement</span><span class="p">.</span><span class="nx">type</span><span class="p">);</span><span class="kr">const</span><span class="nx">textNode</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">createTextNode</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_currentElement</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">children</span><span class="p">);</span><span class="nx">domElement</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">textNode</span><span class="p">);</span><span class="nx">container</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">domElement</span><span class="p">);</span><span class="k">return</span><span class="nx">domElement</span><span class="p">;</span><span class="p">}</span></code></pre>
</div>
<p>其實實現的過程很簡單，就是根據type生成domElement,再將子節點append進來返回。當然，真實的mountComponent沒有那麼簡單，感興趣的可以自己去看源碼啦。</p>
<p>這裡需要記住的一個點是：<b><i>這個類的mountComponent方法會自己操作瀏覽器DOM元素</i></b>。</p>
<p>講完ReactDOMComponent，再來看看ReactCompositeComponentWrapper。</p>
<p><b>ReactCompositeComponentWrapper</b></p>
<p>這個類的mountComponent方法作用是：<b>實例化自定義組件</b>，最後是通過<b>遞歸</b>調用到ReactDOMComponent的mountComponent方法來得到真實DOM。</p>
<p><b>注意：也就是說他自己是不直接生成DOM節點的</b><i>。</i></p>
<p>那這個遞歸是一個怎樣的過程呢？我們通過首次渲染來看下。</p>
<hr>
<p><b>首次渲染</b></p>
<p>假設我們有一個Example的組件，它返回&lt;div&gt;hello world&lt;/div&gt; 這樣一個標籤。</p>
<p>首次渲染的過程如下：</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-080a2549922a6a03fbbb87ddc266801e_r.jpg" data-caption="" data-size="normal" data-rawwidth="1438" data-rawheight="863" class="origin_image zh-lightbox-thumb" width="1438" data-original="https://pic3.zhimg.com/v2-080a2549922a6a03fbbb87ddc266801e_b.jpg" title="v2-080a2549922a6a03fbbb87ddc266801e_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-080a2549922a6a03fbbb87ddc266801e_r.jpg" data-caption="" data-size="normal" data-rawwidth="1438" data-rawheight="863" class="origin_image zh-lightbox-thumb lazy" width="1438" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1438'%20height='863'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-080a2549922a6a03fbbb87ddc266801e_b.jpg" title="v2-080a2549922a6a03fbbb87ddc266801e_r"></figure>
<p>首先從React.render開始，由於我們剛剛說，render函數被調用的時候會返回一個element，所以此時返回給我們的element是：</p>
<div class="highlight">
<pre><code class="language-js"><span class="p">{</span><span class="nx">type</span><span class="o">:</span><span class="kd">function</span><span class="nx">Example</span><span class="p">,</span><span class="nx">props</span><span class="o">:</span><span class="p">{</span><span class="nx">children</span><span class="o">:</span><span class="kc">null</span><span class="p">}</span><span class="p">}</span></code></pre>
</div>
<p>由於這個type是一個自定義組件類，此時要初始化的類是ReactCompositeComponentWrapper,接著調用它的mountComponent方法。這裡面會做四件事情，詳情可以看上圖。其中，第二步的render的得到的element為：</p>
<div class="highlight">
<pre><code class="language-text">{ type: 'div', props: { children: 'Hello World' } }</code></pre>
</div>
<p class="ztext-empty-paragraph"></p>
<p>由於這個type是一個原生DOM標籤，此時要初始化的類是ReactDOMComponent。接下來它的mountComponent方法就可以幫我們生成對應的DOM節點放在瀏覽器裡啦。</p>
<p>這時候有人可能會有疑問，如果第二步render出來的element 類型也是自定義組件呢？</p>
<p>這時候它就會去調用ReactCompositeComponentWrapper的mountComponent方法，從而形成了一個遞歸。不管你的自定義組件嵌套多少層，最後總會生成原生dom類型的element，所以最後一定能調用到ReactDOMComponent的mountComponent方法。</p>
<p>感興趣的可以自己在打斷點看下這個遞歸的過程。</p>
<p>由我打的斷點圖可以看出在ReactCompositeComponent的mountComponent被調用多次之後，最後調用到了ReactDOMComponent的mountComponent方法。</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-f0377bd120b48439f880cc7d73bc6679_r.jpg" data-caption="" data-size="normal" data-rawwidth="1408" data-rawheight="636" class="origin_image zh-lightbox-thumb" width="1408" data-original="https://pic2.zhimg.com/v2-f0377bd120b48439f880cc7d73bc6679_b.jpg" title="v2-f0377bd120b48439f880cc7d73bc6679_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-f0377bd120b48439f880cc7d73bc6679_r.jpg" data-caption="" data-size="normal" data-rawwidth="1408" data-rawheight="636" class="origin_image zh-lightbox-thumb lazy" width="1408" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1408'%20height='636'&gt;&lt;/svg&gt;" data-actualsrc="https://pic2.zhimg.com/v2-f0377bd120b48439f880cc7d73bc6679_b.jpg" title="v2-f0377bd120b48439f880cc7d73bc6679_r"></figure>
<p>到這裡，首次渲染的過程就基本講完了:-D。</p>
<p>但是還有一個問題：前面我們說自定義組件的生命週期跟render函數都是在私有類的方法裡被調用的，現在只看到render函數被調用了，那麼首次渲染時候生命週期函數componentWillMount 跟componentDidMount在哪被調用呢？</p>
<figure data-size="normal"><noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-080a2549922a6a03fbbb87ddc266801e_r.jpg" data-caption="" data-size="normal" data-rawwidth="1438" data-rawheight="863" class="origin_image zh-lightbox-thumb" width="1438" data-original="https://pic3.zhimg.com/v2-080a2549922a6a03fbbb87ddc266801e_b.jpg" title="v2-080a2549922a6a03fbbb87ddc266801e_r"></noscript><img decoding="async" src="https://hypergrowths.com/wp-content/uploads/v2-080a2549922a6a03fbbb87ddc266801e_r.jpg" data-caption="" data-size="normal" data-rawwidth="1438" data-rawheight="863" class="origin_image zh-lightbox-thumb lazy" width="1438" data-original="data:image/svg+xml;utf8,&lt;svg%20xmlns='http://www.w3.org/2000/svg'%20width='1438'%20height='863'&gt;&lt;/svg&gt;" data-actualsrc="https://pic3.zhimg.com/v2-080a2549922a6a03fbbb87ddc266801e_b.jpg" title="v2-080a2549922a6a03fbbb87ddc266801e_r"></figure>
<p>由圖可知，在第一步得到instance對象之後，就會去看instance.componentWillMount是否有被定義，有的話調用，而在整個渲染過程結束之後調用componentDidMount。</p>
<p>以上，就是渲染原理的部分，讓我們來總結以下：</p>
<ol>
<li>JSX代碼經過babel編譯之後變成React.createElement的表達式，這個表達式在render函數被調用的時候執行生成一個element。</li>
<li>在首次渲染的時候，先去按照規則初始化element，接著ReactComponentComponentWrapper通過遞歸，最終調用ReactDOMComponent的mountComponent方法來幫助生成真實DOM節點。</li>
</ol>
</div>
</div>
</article>
<p>The post <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com/software-engineering/front-end-dev/14078/topic-43145754/" data-wpel-link="internal">從渲染原理到性能優化（一）&#8211; 首次渲染</a> appeared first on <a rel="nofollow noopener noreferrer" href="https://hypergrowths.com" data-wpel-link="internal">成長駭客交流第一站 - HyperGrowths™</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
