<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>undefined</title>
  
  <subtitle>everything starts &amp; ends</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://undefinedblog.com/"/>
  <updated>2019-11-03T13:07:47.961Z</updated>
  <id>https://undefinedblog.com/</id>
  
  <author>
    <name>jasonslyvia</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>我的 2018</title>
    <link href="https://undefinedblog.com/my-2018/"/>
    <id>https://undefinedblog.com/my-2018/</id>
    <published>2019-01-06T07:20:55.000Z</published>
    <updated>2019-11-03T13:07:47.961Z</updated>
    
    <content type="html"><![CDATA[<p>在构思怎么给今年的年度总结写个开头时，愕然发现<a href="https://undefinedblog.com/my-2017/">去年的开头</a>完全可以拿来复用。依然只写了三两篇博客（还是一个烂尾的系列文章），依然是 <em>忙</em> 和 <em>懒</em>。但是相比于 2017 年，更重要的一个关键字应该是 <em>变</em>。</p><h2 id="工作变了"><a href="#工作变了" class="headerlink" title="工作变了"></a>工作变了</h2><p>2018 年，不同于之前的被拥抱变化，我第一次主动更换了工作。从待了 4 年的<a href="https://dt.alibaba.com/" target="_blank" rel="noopener">阿里集团数据技术及产品部</a>（后称 DT），跳到了<a href="https://xtech.antfin.com/" target="_blank" rel="noopener">蚂蚁金服体验技术部</a>（后称 AFX）。虽然还是在大的阿里集团体系下面，不过干的事情、团队的风格、个人的心态都发生了天翻地覆的变化。</p><p>DT 的 4 年，一直在跟数据产品打交道。从商家数据分析产品<a href="https://sycm.taobao.com" target="_blank" rel="noopener">生意参谋</a>，到 BI 工具孔明灯（后来合并为 QuickBI），再到智能数仓建模工具 Dataphin，作为一个前端工程师我一直被数据的魅力感染着。虽然做的是前端的工作，但是耳濡目染下也对大数据体系初窥门径，甚至自己从零开始搭建了一套实时监控系统 —— Clue。数据采集、清洗、实时处理、离线处理、落库查询、数据可视化、异常识别与报警，每件事都是一点点摸索、尝试并最终攻克的。</p><p>也正是 Clue 的诞生，让我充分感受到自己对数据开发的热情以及能力上的不足。尽管旁人看来，身为一个前端工程师能把这么多后端以及大数据的技术用起来并完成的拼装成一个产品已经很了不起，但我仍觉得自己是管中窥豹，不得要领。这种不上不下的状态让我非常难受，于是我想要做出一些改变。</p><p>说起来也有些不可思议，虽然我所在的部门叫做「数据技术及产品部」，但转型做数据的空间并不大、机会也并不多。为什么？因为 DT 本来就是一个专业的数据部门，即使是校招的数据工程师都有很厚的数据研发底子，Hadoop、Spark、HDFS 张口就来。作为一个已经背上 <em>专家</em> 头衔的人，在一个大企业里仅凭自己的偏好就想转向从头开始干另外一个方向并不是易事，也不会有团队养得起这样的「闲人」。也正是因为 DT 太专业，很多底层的技术早已经被封装屏蔽，所有的最佳实践都已经被自动化、沉淀为产品。你需要做的很大程度上就是勾勾选选，写写简单的 SQL。对于我这样一个半路出家希望系统化学习大数据的人来说，还是希望自己脚踏实地一步一个坑把所有问题都解决一遍。</p><p>这里不得不提一句，我所在的团队却认真的、诚恳的给了我这个机会，允许我转型，并在若干时间内不考核绩效与产出。在这里除了感激涕零外没有任何方式能表达的我的心情，但基于上述原因，我依然选择了改变。</p><p>感恩 DT。</p><h2 id="岗位变了"><a href="#岗位变了" class="headerlink" title="岗位变了"></a>岗位变了</h2><p>转岗到 AFX，没提升职也没提加薪，只跟新老板提了一个要求，我要做数据。对了，值得一提的是我的新老板就是林峰，那个在数据可视化领域赫赫威风鼎鼎大名的、那个创造了 ECharts 的、那个在百度就要给他升职加薪前毅然决然出去创业最后经验惨淡沦落到蚂蚁金服的男人。</p><p>于是我现在是数据技术专家。</p><p>专家的 title 不好背，我开始诚惶诚恐的恶补各种大数据知识。从最底层的 HDFS，到整个 Hadoop 生态，再到集团内的 MaxCompute，说明文档来回的看，例子反复的跑，团队已有的数据任务仔细的读。总的来说，还可以跟上团队需求的节奏。</p><p>说起来现在大数据领域有点开始像前端圈子一样蓬勃发展的趋势，虽然我也经常调侃团队里的前端说「贵圈真乱」，但我也明显感受到数据圈子里新轮子开始越造越快、越造越多。新名词也是五花八门、百花齐放，什么 HTAP、什么 NewSQL、什么 Data Lakes，对于刚刚正式加入贵圈的我来说还是有点消化不过来。</p><p>总的来说，我跟数据岗位依然处于蜜月期，大数据领域还有很多很多很多内容值得我去学习和探索，还有很多概念让我眼前一亮，还有很多技术让我仍不住喊 AHa 原来如此！我想这样的状态，让我满足。</p><h2 id="重心变了"><a href="#重心变了" class="headerlink" title="重心变了"></a>重心变了</h2><p>不知道从什么时候开始，我被打上了「做前端监控的」的标签。刚刚转行做数据的时候，我极力避免别人依然把我当前端看。然而时间久了我发现，前端监控和数据开发并不矛盾，我为什么不能既熟悉前端监控又能做好数据开发呢？况且在前端监控领域，我也确实积累了很多经验，有很多自己的想法和思考，应该让它们发光发热。</p><p>于是我决定继续把前端监控做深。在前几年做 Clue 监控平台的时候，我的研究重点在 JS 异常的捕获和分析上，而新团队的主要业务是一个数据分析产品而非监控工具。基于以上两点原因，我认为前端性能监控与数据分析更适合团队现阶段的需求。</p><p>我开始研读 W3C 上各种 Web Performance 相关的规范，并且顺手发了几个 PR 修复一些 specs 里的小问题，最终阴差阳错加入了 W3C Web Performance Working Group。当然不得不承认，目前整个 WG 的核心还是 Google、Mozilla 等浏览器厂商的专家，尽管国内也有浏览器厂商在 WG 里，但是一般不参与讨论。作为一个非厂商代表的我，只能更多的从数据采集、分析的角度提供规范制定时的一些思考和想法。</p><p>18 年 12 月，我还应邀参加了 2018 ArchSummit 架构师峰会，作为为数不多的<a href="https://bj2018.archsummit.com/presentation/1229" target="_blank" rel="noopener">前端议题</a>，给大家分享了做前端性能监控的一些方法论和最佳实践。</p><h2 id="生活变了"><a href="#生活变了" class="headerlink" title="生活变了"></a>生活变了</h2><p>转岗到 AFX 后，我每天上班单程通勤时间从 5 分钟暴增到一个小时。为了让这段上班路程不再枯燥，也为了纪念这一次重大的转变，咬咬牙买了一辆新车（「新」是相对于原来的车，其实这辆车已经是大龄二手车了），也是我心仪已久的 Dream Car。不记得哪个汽车节目里说过，如果每次锁车时你不会忍不住多看几眼你的车，那么这个车并不值得买。这么评价的话，这个车对我来说还是很值得买的。</p><p>2018 年，也做了人生中第一次手术，住院将近两周。住在医院的时候时常告诫自己，身体最重要，可不能乱来了，然而一出院又开始活蹦乱跳声色犬马。总是不长记性，总是需要警醒。</p><p>最后，经过 5 年的长跑，我终于决定要结婚了。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>2018 年没立什么 Flag，然而过的却是风流涌动。2019 年依然不立什么 Flag。</p><center><img src="/images/porshce-911.jpg" alt="porshce-911"></center><br><center>我知道你想看 Dream Car 是哪台</center>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;在构思怎么给今年的年度总结写个开头时，愕然发现&lt;a href=&quot;https://undefinedblog.com/my-2017/&quot;&gt;去年的开头&lt;/a&gt;完全可以拿来复用。依然只写了三两篇博客（还是一个烂尾的系列文章），依然是 &lt;em&gt;忙&lt;/em&gt; 和 &lt;em&gt;懒&lt;/em&gt;
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>怎么使用 Service Worker</title>
    <link href="https://undefinedblog.com/how-to-use-service-worker/"/>
    <id>https://undefinedblog.com/how-to-use-service-worker/</id>
    <published>2018-01-28T14:24:05.000Z</published>
    <updated>2019-11-03T13:07:47.961Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文是 <a href="/dive-into-service-worker">深入理解 Service Worker</a> 系列文章中的第二篇</p></blockquote><p>本周苹果官方发布了 Safari 11.1 的 TP （技术预览）版，据<a href="https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_11_1.html" target="_blank" rel="noopener">更新日志</a>显示，iOS 11.3 及 macOS 10.13.4 中的 Safari 将全面支持 Service Worker，这为 PWA 的推广扫除了最后的兼容性问题。</p><p>如果 iOS 能够完美的支持 Service Worker，那么目前 App Store 中至少一半以上纯信息展示型的 App 将无需通过 App Store 就能呈现给用户，无疑将引起新一轮的移动 App 开发革命。当然，根据部分开发者的<a href="https://medium.com/@firt/pwas-are-coming-to-ios-11-3-cupertino-we-have-a-problem-2ff49fd7d6ea" target="_blank" rel="noopener">反馈</a>，Safari 技术预览版中 Service Worker 的实现还存在不少问题。</p><p>借着这个振奋人心的消息，本文开始聊聊如何使用 Service Worker。</p><h2 id="注册一个-Service-Worker"><a href="#注册一个-Service-Worker" class="headerlink" title="注册一个 Service Worker"></a>注册一个 Service Worker</h2><p>Service Worker 虽然是 Web Worker 的一种，但注册和使用方式与 Web Worker 却不尽相同。Web Worker 有一个全局构造函数 <code>Worker</code>，你可以通过 <code>new Worker(&#39;worker.js&#39;)</code> 的方式新建一个 Web Worker。</p><p>而 Service Worker 则需要需要 <code>navigator.serviceWorker.register()</code> 方法进行注册。这里还有个有意思的思考点，为什么同样是 Web Worker，注册方式甚至是挂载的层级都不同（一个是 <code>window</code>，一个是 <code>navigator</code>）呢？</p><p>下面的代码片段中，我们在页面里注册了一个 Service Worker，Service Worker 的具体行为在 <code>sw.js</code> 中进行定义。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">// index.html</span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript"><span class="keyword">if</span> (navigator.serviceWorker) &#123;</span></span><br><span class="line"><span class="javascript">  navigator.serviceWorker.register(<span class="string">'sw.js'</span>)</span></span><br><span class="line"><span class="javascript">    .then(<span class="function"><span class="params">registration</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">      <span class="comment">// 注册成功</span></span></span><br><span class="line"><span class="undefined">    &#125;)</span></span><br><span class="line"><span class="javascript">    .catch(<span class="function"><span class="params">error</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">      <span class="comment">// 注册失败</span></span></span><br><span class="line"><span class="undefined">    &#125;);</span></span><br><span class="line"><span class="undefined">&#125;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在深入使用 Service Worker 后你会发现，所有相关的 API 都是 Promise-based 的，因此在调用注册方法后，我们需要使用 Promise 的方式来处理注册的结果。</p><p>若注册成功，则 Promise resolve 一个 <a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration" target="_blank" rel="noopener">ServiceWorkerRegistration</a> 对象，通过这个对象你可以获取到当前注册的 Service Worker 的相关状态，或者订阅新的 Service Worker 注册事件，亦或者是注销一个 Service Worker。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line">.then(<span class="function"><span class="params">registration</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (registration.installing) &#123;</span><br><span class="line">    <span class="comment">// 当前存在处于 installing 状态的 Service Worker</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (registration.active) &#123;</span><br><span class="line">    <span class="comment">// 当前存在处于 active 状态的 Service Worker，即控制当前页面的那个 Service Worker</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>若注册失败，在 catch 中你会捕获到具体的错误。比如说 <code>sw.js</code> 中存在语法错误甚至压根不存在，或者没有使用 https 协议等，都会导致 Service Worker 注册失败。</p><h2 id="注销一个-Service-Worker"><a href="#注销一个-Service-Worker" class="headerlink" title="注销一个 Service Worker"></a>注销一个 Service Worker</h2><p>很多刚刚接触 Service Worker 的同学都会犯的一个错误就是，不知道如何注销一个 Service Worker。由于 Service Worker 有非常强大的网络拦截能力，如果错误的或者过时的 Service Worker 没有被正确注销，将在特定的场景下导致非常严重的问题。</p><p>在注册 Service Worker 时我们在代码中调用 <code>navigator.serviceWorker.register()</code> 方法，那么是不是只要删除这个注册语句，Service Worker 就会被注销掉呢？答案是否定的。</p><p><strong>你必须显式的调用 <code>unregister()</code> 方法才能正确的注销 Service Worker，除此之外无论你是删除了 <code>register()</code> 调用还是删除了注册时的那个 <code>sw.js</code>，都不会对已经注册成功的 Service Worker 起到任何作用。</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line">.then(<span class="function"><span class="params">registration</span> =&gt;</span> &#123;</span><br><span class="line">  registration.unregister().then(<span class="function"><span class="params">result</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (result) &#123;</span><br><span class="line">      <span class="comment">// 注销成功</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 注销失败</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="更新一个-Service-Worker"><a href="#更新一个-Service-Worker" class="headerlink" title="更新一个 Service Worker"></a>更新一个 Service Worker</h2><p>上面我们讲到了如何注册或是注销一个 Service Worker，那如果我们的 Service Worker 需要更新呢？</p><p>更新时并<strong>没有</strong>一个类似于 <code>navigator.serviceWorker.update()</code> 的方法，我们只需要更改 Service Worker 中的内容（修改 <code>sw.js</code>），然后再次调用 <code>register()</code> 方法即可。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一次注册</span></span><br><span class="line">navigator.serviceWorker.register(<span class="string">'sw.js'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改 sw.js 中的内容</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 第二次注册，实则为更新过程</span></span><br><span class="line">navigator.serviceWorker.register(<span class="string">'sw.js'</span>);</span><br></pre></td></tr></table></figure><p>你无需修改 <code>sw.js</code> 的文件名，只要保证其中的内容发生变化，则浏览器会自动启动更新程序。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>在本文中我们讲到了如何注册、注销和更新 Service Worker，这既是大家非常容易忽略的基础知识，也引出了下一章我们要讲的内容：Service Worker 的生命周期。</p><p>因为在你调用 <code>register()</code> 后，发生的事情远远没有一行代码那么简单。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;本文是 &lt;a href=&quot;/dive-into-service-worker&quot;&gt;深入理解 Service Worker&lt;/a&gt; 系列文章中的第二篇&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本周苹果官方发布了 Safari 11.1 的 TP （技
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>什么是 Service Worker</title>
    <link href="https://undefinedblog.com/what-is-service-worker/"/>
    <id>https://undefinedblog.com/what-is-service-worker/</id>
    <published>2018-01-14T09:14:26.000Z</published>
    <updated>2019-11-03T13:07:47.957Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文是 <a href="/dive-into-service-worker">深入理解 Service Worker</a> 系列文章中的第一篇</p></blockquote><h2 id="Service-Worker-的定义"><a href="#Service-Worker-的定义" class="headerlink" title="Service Worker 的定义"></a>Service Worker 的定义</h2><p>Service Worker 听起来很像 Web Worker。</p><p>根据 <a href="https://w3c.github.io/ServiceWorker/#service-worker-concept" target="_blank" rel="noopener">Service Worker 的规范定义</a>，它正是 Web Worker 中的一种（严格的说，Service Worker 应该是 <a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker" target="_blank" rel="noopener">Shared Worker</a> 的一种），但更专注于对网络请求的拦截、后台数据更新等，这些能力正是为构建一个 PWA 打好了坚实的基础。</p><p><img src="service worker.png" alt="service worker 概念"></p><p class="caption">一个简化的 Service Worker 工作示意图</p><p>如果你还不清楚什么是 Web Worker，可以把它简单的理解为一个独立于主线程执行的 JavaScript 模块（无论是一个独立的 <code>.js</code> 文件或者 <code>type=&quot;module&quot;</code>类型的 <code>&lt;script&gt;</code>标签）。之所以需要独立于主线程运行，是为了解决一些计算密集型的操作阻塞界面渲染和用户交互的问题。由于工作在不同的线程中，Web Worker 和主线程之间通过 postMessage 等方式进行通信。</p><h2 id="Service-Worker-到底是什么"><a href="#Service-Worker-到底是什么" class="headerlink" title="Service Worker 到底是什么"></a>Service Worker 到底是什么</h2><p>只看定义可能很难对 Service Worker 有比较感性的认知，那我们直接上代码：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// service-worker.js</span></span><br><span class="line">self.addEventListener(<span class="string">'install'</span>, e =&gt; &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'installing service worker'</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">self.addEventListener(<span class="string">'activate'</span>, e =&gt; &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'activating service worker'</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">self.addEventListener(<span class="string">'fetch'</span>, e =&gt; &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`fetching <span class="subst">$&#123;e.request.url&#125;</span>`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>上面的代码片段定义了一个很简单的 Service Worker，通过在页面上调用 <code>navigator.serviceWorker.register(&#39;/service-worker.js&#39;)</code> 即可将这个 Service Worker 注册到当前域下。</p><p>关于 Service Worker 的注册及使用，会在第二篇《怎么使用 Service Worker》中进行详细介绍。现在，你只需要对 Service Worker 有一个初步的认识即可。</p><h2 id="Service-Worker-的性质"><a href="#Service-Worker-的性质" class="headerlink" title="Service Worker 的性质"></a>Service Worker 的性质</h2><p>1. <strong>与 Web Worker 一样，因为运行在独立的线程中，Service Worker 无法直接进行任何 DOM 操作</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//service-worker.js</span></span><br><span class="line">self.addEventListener(<span class="string">'install'</span>, e =&gt; &#123;</span><br><span class="line">  alert(<span class="string">'注册成功'</span>);  <span class="comment">// TypeError, alert is not a function</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>2. <strong>Service Worker 运行在独立的全局上下文环境（<a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope" target="_blank" rel="noopener">ServiceWorkerGlobalScope</a>）中</strong></p><p>你可以通过 <code>self</code> 关键字在 Service Worker 代码的任意位置来指向这个全局环境，有点类似 node.js 中的 <code>global</code> 关键字。</p><p>当然，这不意味着你只能在 Service Worker 中使用 <code>self</code>，<code>this</code> 依然可以被使用，只是考虑到各种作用域的变更（如箭头函数、bind 等），<code>self</code> 能够在任何位置、任何环境下永远指向这个全局作用域。</p><p>3. <strong>Service Worker 仅能运行在 HTTPS 环境下</strong></p><p>主要是考虑到网络请求拦截等能力的危险程度，但为了调试方便可以在 HTTP 环境的 localhost 中进行调试。</p><p>4. <strong>Service Worker 的注册必选遵守「同源策略（Same Origin Policy）」</strong></p><p>这也是很多人疑惑为什么注册一个 CDN 上的 Service Worker 会报错的问题所在，比如页面是 <code>https://www.exmaple.com</code>，注册一个 <code>https://cdn.example.com/service-worker.js</code>，就会导致跨域错误。</p><p>5. <strong>Service Worker 有固定的作用范围，可以在注册时通过 <code>scope</code> 参数指定，若留空则自动根据 Service Worker 的 location 来判断</strong></p><p>举个例子，若没有特别设置 <code>scope</code>， <code>https://www.example.com/sw.js</code> 的作用域就是 <code>/</code>，因此这个 Service Worker 可以拦截该域下页面的任意请求；但是 <code>https://www.example.com/folder/sw.js</code> 的作用域只能是 <code>/folder/</code>，若访问的页面是 <code>https://www.example.com/another-folder/index.html</code>，这个 Service Worker 不会拦截到任何的请求。</p><p>因此一般来说，Service Worker 应该注册在根目录下，根据应用的需要指定 <code>scope</code> 即可。</p><p>6. <strong>基于第 5 点性质，一个域下可以注册若干个 Service Worker，只要他们的 <code>scope</code> 不同即可</strong></p><p>7. <strong>Service Worker 是事件驱动的</strong></p><p>当事件触发时会执行 Service Worker 里注册的事件回调函数，当事件处理完成后会这个 Service Worker 对应的线程可能会被销毁。因此你无法在 Service Worker 中定义全局变量，如果需要持久化一些内容在不同的事件触发时进行传递，可以考虑使用 <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API" target="_blank" rel="noopener">IndexedDB</a>。</p><p>8. <strong>Service Worker 一旦注册成功，便不依托于某个具体的页面存在</strong></p><p>举个例子，你在 <code>https://www.example.com/index.html</code> 使用 <code>register(&#39;service-worker.js&#39;)</code> 成功注册了一个 Service Worker。关闭浏览器后重新访问 <code>https://www.example.com/index.html</code>，移除掉 <code>register(&#39;service-worker.js&#39;)</code> 的调用，之前注册的 Service Worker 依然会正常运行。</p><p>9. <strong>Service Worker 在同源页面下是共享的</strong></p><p>这一点从 「Service Worker 是 <a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker" target="_blank" rel="noopener">Shared Worker</a> 的一种」可以推导出，不再单独解释。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>在这篇文章中，我们介绍了什么是 Service Worker、Service Worker 的性质，以及通过一个简短的例子让大家了解到了一个 Service Worker 长什么样子。</p><p>由于 Service Worker 是从 Web Worker 和 Shared Worker 演化而来的 API，很多 Web Worker 的性质、用法等还需要读者再仔细研究。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;本文是 &lt;a href=&quot;/dive-into-service-worker&quot;&gt;深入理解 Service Worker&lt;/a&gt; 系列文章中的第一篇&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;Service-Worker-的定义&quot;&gt;&lt;a h
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>深入理解 Service Worker</title>
    <link href="https://undefinedblog.com/dive-into-service-worker/"/>
    <id>https://undefinedblog.com/dive-into-service-worker/</id>
    <published>2018-01-14T08:57:33.000Z</published>
    <updated>2019-11-03T13:07:47.961Z</updated>
    
    <content type="html"><![CDATA[<p>PWA 作为 2018 年前端领域最火的技术之一已经引起了越来越多开发者的关注，其媲美 native app 的交互体验以及对前端开发者友好的入门难度让人不得不怀疑它将掀起新一轮的「App 技术革命」。而 PWA 最核心的「离线」能力，正是通过对 ServiceWorker 这个概念的灵活使用实现的。</p><p>在没有深入理解 Service Worker 之前，我总觉得 PWA 无非是在构建的时候使用类似 webpack OfflinePlugin 之类的插件把所有的静态资源缓存起来做成离线应用即可。但是当深入学习了 ServiceWorker 及 CacheStorage 的设计思想、API 规范甚至是发展历程后，我才真正了解到做一个完整的 PWA 是一件多么复杂的事情。</p><p>基于我最近一个月对 ServiceWorker 的学习，结合在几个实际业务中的使用情况，我将通过一个系列文章来为大家深入解读 Service Worker 的概念、原理、设计思想、实战玩法以及如何在 PWA 中发挥关键作用等等。</p><p>系列文章将划分为如下三个部分：</p><h3 id="理论部分"><a href="#理论部分" class="headerlink" title="理论部分"></a>理论部分</h3><ul><li><a href="/what-is-service-worker">什么是 Service Worker</a></li><li><a href="/how-to-use-service-worker">怎么使用 Service Worker</a></li><li>Service Worker 的生命周期</li><li>什么是 CacheStorage</li><li>如何在 Service Worker 中使用 CacheStorage</li><li>Service Worker 是否足够成熟（截止 2018 年 1 月）</li></ul><h3 id="实践部分"><a href="#实践部分" class="headerlink" title="实践部分"></a>实践部分</h3><ul><li>不同离线策略的差异与适用场景</li><li>如何避免设计出永远不会更新的 Service Worker</li><li>Service Worker 最佳实践</li></ul><h3 id="延伸阅读"><a href="#延伸阅读" class="headerlink" title="延伸阅读"></a>延伸阅读</h3><ul><li>Service Worker 的前世今生</li><li>PWA 和 AMP 的异同</li></ul><p>那么，让我们一起开始 Service Worker 的探索旅程吧！</p><blockquote><p>Service Worker 是一个相对比较新的技术，规范和浏览器厂商的落地都在不断的变化和升级。我力争在行文过程中遵循「先实践后结论」的方式，保证所有的内容都经过真实环境测试。</p><p>如果文章存在任何错误，请点击文章标题下方的「查看源码」直接对本文编辑并提交 PR。</p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;PWA 作为 2018 年前端领域最火的技术之一已经引起了越来越多开发者的关注，其媲美 native app 的交互体验以及对前端开发者友好的入门难度让人不得不怀疑它将掀起新一轮的「App 技术革命」。而 PWA 最核心的「离线」能力，正是通过对 ServiceWorker
      
    
    </summary>
    
    
      <category term="ServiceWorker" scheme="https://undefinedblog.com/tags/ServiceWorker/"/>
    
      <category term="PWA" scheme="https://undefinedblog.com/tags/PWA/"/>
    
  </entry>
  
  <entry>
    <title>我的 2017</title>
    <link href="https://undefinedblog.com/my-2017/"/>
    <id>https://undefinedblog.com/my-2017/</id>
    <published>2018-01-06T15:52:30.000Z</published>
    <updated>2019-11-03T13:07:47.961Z</updated>
    
    <content type="html"><![CDATA[<p>博客的更新频率与我的工作时间呈现严格的负相关，14 年 13 篇、15 年 10 篇、16 年 4 篇、17 年 3 篇。最近一次更新博客，已经是将近 8 个月之前的事情了。上学的时候还经常感叹，为什么关注的很多大牛的博客都渐渐不更新了呢？工作几年后终于有了答案：<em>忙</em> 和 <em>懒</em>。</p><p>当然如果你观察的周期足够长，你会发现有些大牛的博客在沉寂若干年后又开始焕发第二春。那是因为他们已经成功跻身中层管理，阶段性的目标被调整为团队管理和影响力提升，自然又更新的勤快了起来。</p><p>言归正传，2017 年发生了很多有意思的事情。无论是技术上，工作上还是生活上，都值得被记录下来，与大家分享。算是对过去一年的一个交代，也算是对新的一年的一个期许。</p><h2 id="TypeScript-与-VSCode"><a href="#TypeScript-与-VSCode" class="headerlink" title="TypeScript 与 VSCode"></a>TypeScript 与 VSCode</h2><p>2017 年最兴奋的事情就是上手了 TypeScript。</p><p>大概从 17 年 6 月份开始，因为一个新项目的契机我开始全面详细的学习 TypeScript。虽然自诩不是一个抵触新鲜事物的人，但是第一次让我去学习 TypeScript，其实我是比较抗拒的。</p><p>第一，我始终觉得「静态类型 + OOP」 的设计完美的体现了冗余的仪式感之「美」。明明一行代码就能解决的问题，非要先定义一个接口，再实现一下，最后再实例化一下才能使用。</p><p>其次，微软在开源届的名声一直不算太好，各种 .NET 的生态衰亡、SilverLight 等技术的昙花一现以及 IE 本身对前端工程师的一万点伤害，让我十分怀疑 TypeScript 是否是一个值得学习的技术。</p><p>最后，在学习 TypeScript 之前我已经非常熟练的使用 ES 6 甚至是 ES 7 中的诸多特性，感觉 TypeScript 除了静态类型检查并没有带来太多的红利，学习的投入产出比不高。</p><p>但是最终让我心甘情愿爱上 TypeScript 的原因也很简单：<strong>TypeScript + VSCode 的开发模式非常适合团队协作模型下的大型项目开发</strong>。</p><p>正是因为新项目是一个非常大型的 React + Redux 单页应用，有大量的组件和模块需要被各个场景复用，TypeScript 强大的类型检查功能帮助我们基本上很少浪费时间在公共组件或者模块的调用方式讨论上，要用什么公共组件，直接鼠标悬浮在对应的地方就能有完整的提示，参数传的不对、传少了、返回值用法不对等等问题都能很直接的在编辑器中体现出来（VSCode 功不可没）。这些特性在团队协作的大型项目开发工作中显得尤为重要。</p><p>当然，也不是说 TypeScript 不能用在小型单人项目中。我也正在计划把自己 2、3 年前个人开发的很多技术项目逐渐往 TypeScript 上迁移。因为强大的静态检查类型可以帮助我们检查很多低级的运行时错误，比如变量名拼错。</p><p>还要多提一句的就是 VSCode。</p><p>在 VSCode 之前我一直是 Sublime 的死忠，期间 3 次尝试切换到 VSCode 都以失败告终。但使用 TypeScript 开发后，配合 VSCode 做各种代码间（甚至是 <code>node_modules</code>中的文件）跳转、类型定义查看、错误提示等功能，简直流畅至极。</p><p>同时由于 VSCode 对开发者暴露的是 TypeScript 的 API，使得你想编写一个插件简直易如反掌（对比下当年想写一个 Sublime 插件苦兮兮的学了很久 Python）。</p><h2 id="大数据体系"><a href="#大数据体系" class="headerlink" title="大数据体系"></a>大数据体系</h2><p>都说大数据像 teenage sex，但是作为在阿里数据中台的直接参与一线数据产品开发的前端工程师，2017 年我自认为还是摸到了一些大数据入门级知识，如果类比 teenage sex 的话不知是否达到二垒。</p><p>我是怎么和大数据结缘的呢？故事还要从阿里内部的 <a href="https://clue.alibaba-inc.com" target="_blank" rel="noopener">Clue 前端监控平台</a>说起（互联网无法直接访问😢）。大约在 2015 年上半年的时候，我注意到我们的很多前端故障都是可以在用户向客服发起投诉之前被捕捉到的（比如点击某个按钮时有 JS 报错导致弹框没有打开等），但是我们对自己写的代码在用户浏览器中运行的情况如何是完全没有方法感知的。</p><p>于是我开始规划做一个异常监控平台，名字就叫<strong>前端稳定性平台（Frontend Stability Platform，FSP）</strong>，一个非常符合程序员风格的名字。</p><p>要做一个监控平台，首先要解决数据从哪里来的问题。对于前端监控来说，很显然需要在客户端部署采集代码，监听全局 JS 异常，然后上报的日志服务器。在日志服务器上，数据以文件的形式按照约定的分隔符储存。然后需要类似于 logstash（阿里内部叫 TT）的工具定期去日志服务器上拉取日志文件，再传递给实时或离线数据处理服务。经过一系列的过滤、汇总后，最终生成的数据根据查询的需要储存在不同类型的数据库中，供给监控平台的 Web 服务进行查询。</p><p>这个监控平台的工作原理我在团队的知乎专栏中<a href="https://zhuanlan.zhihu.com/p/32262716" target="_blank" rel="noopener">有一篇文章</a>进行了更详细的描述，这里就不再赘述。</p><p><strong>当然真正的大数据可没有上文三言两语讲的那么简单，除了技术上如何处理，目前企业大数据面临更大的问题是如何规范大数据建设、数据如何发挥价值（从业务数据化到数据业务化）等等</strong>。但很高兴我起码算入了门，能把流程走通，还能最终产品化落地（最终命名为 「Clue 前端监控平台」，寓意为业务发现异常的线索）。</p><p>说到大数据不得不提的就是机器学习和人工智能。我也很自不量力的参加了 Andrew Ng 在 Coursera 上非常出名的<a href="https://www.coursera.org/learn/machine-learning" target="_blank" rel="noopener">机器学习课程</a>，并成功卡在了理解逻辑回归损失函数（Loss Function）的那一大堆公式里（FYI，逻辑回归仅仅是第二章的内容）。</p><h2 id="其它技术"><a href="#其它技术" class="headerlink" title="其它技术"></a>其它技术</h2><p>2017 年在技术上还有很多琐碎的事情，比如年中闹的满城风雨的 React License 事件，虽然最终以 React 使用 MIT 协议暂告一段落，但是也间接捧红了 preact 等不少类 React 但协议无害的替代产品。据我所知百度已经全线禁用 React，即使协议已经不再是头等主要的问题。</p><p>此外 2017 年我还研究了一段时间的 ServiceWorker，以及现在越来越热的 PWA 概念。感觉这是比 ReactNative、Cordova、Weex 等方案更激进但也能更进一步降低前端工程师写移动 App 门槛的方案。如果标准制定的够完善，各大浏览器厂商跟进足够给力，相信会引起新一轮技术革命的高潮。</p><p>另外值得一提的是 Chrome Headless 的发布，终于把很多自动化场景从 PhantomJS 或 Selenium 的噩梦中解救了出来。在 Chrome Headless 发布不久，我们团队就开发了一款基于该技术（及 pupeteer）的自动化截图技术产品，无论从性能还是使用体验上来说，都称得上是质的飞跃。</p><h2 id="拥抱变化与可持续发展"><a href="#拥抱变化与可持续发展" class="headerlink" title="拥抱变化与可持续发展"></a>拥抱变化与可持续发展</h2><p>很多人说到阿里巴巴脑海里第一个反映出来的词就是「拥抱变化」。没错，今年我也被「拥抱变化」了。</p><p>坦白说当变化真正发生的时候，我不可避免的还是有一些心理上的起伏。这让我想到了生活大爆炸里面 Sheldon 对内稳态（hemeostasis）的痴迷，谁不希望做自己擅长的、早已驾轻就熟的工作呢？谁不希望面对的同事是合作多年、默契深厚的好伙伴呢？谁不希望负责的项目运行稳定，不会有任何大麻烦呢？</p><p>内稳态固然让人痴迷，但是阿里内部有句土话我也很认同：<strong>当你觉得困难的时候，就是你在成长的时候</strong>。如何定义公司里的老油条？就是那种<em>工作三年全靠入职前三个月经验的人</em>。</p><p>我不希望成为这样的人。</p><p>在变化之外，2017 年我悟到最重要的一个道理就是要<em>坚持可持续发展</em>。</p><p>以前总觉得自己是个热血青年，熬夜写代码完全不在话下，有时候睡着睡着脑海里突然冒出来一个想法，一个鲤鱼打挺就坐起来开始编码，第二天一杯浓咖啡就能再战一整天。</p><p>而现在，偶尔业务上线或赶关键时间点熬到 2、3 点，第二天整个人都会精神不振，多少杯咖啡都没有救。</p><p>身体是革命的本钱，毛主席诚不我欺也！</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>今年侥幸晋升到了 P7，背上了「专家」的 title，心里依然很惶恐。</p><p>对技术，深度和广度都需要继续拓展；<br>对个人，调整心态，健康工作，坚持可持续发展成了第一目标；<br>对 2018，不立任何 flag，佛系生活，免的年终打脸。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;博客的更新频率与我的工作时间呈现严格的负相关，14 年 13 篇、15 年 10 篇、16 年 4 篇、17 年 3 篇。最近一次更新博客，已经是将近 8 个月之前的事情了。上学的时候还经常感叹，为什么关注的很多大牛的博客都渐渐不更新了呢？工作几年后终于有了答案：&lt;em&gt;忙
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>如何拦截 fetch 请求</title>
    <link href="https://undefinedblog.com/how-to-intercept-fetch/"/>
    <id>https://undefinedblog.com/how-to-intercept-fetch/</id>
    <published>2017-04-23T12:47:58.000Z</published>
    <updated>2017-04-23T13:08:17.000Z</updated>
    
    <content type="html"><![CDATA[<p>在前端工程实践中，经常会有拦截 Ajax 请求的需求，比如统一添加 CSRF token，或者统一实现缓存处理等。在前 fetch 时代，如果使用了 jQuery，可以直接通过配置 <code>jQuery.ajaxPrefilter</code> 实现；如果用的是原生 API，也可以通过 hack XMLHttpRequest 完成同样的功能。</p><p>然而在 fetch API 出现后，事情就没那么简单了。在我上一篇文章<a href="https://undefinedblog.com/window-fetch-is-not-as-good-as-you-imagined/">《fetch 没有你想象的那么美》</a>中，我提到了对 fetch 进行 Monkey Patch 时遇到的奇怪报错：</p><blockquote><p>Uncaught TypeError: Already read</p></blockquote><p>同时解释了这是 fetch 使用 Stream API 导致的限制，那这是不是意味着我们无法 hack 原生的 fetch 从而实现对 fetch 结果的统一拦截过滤了呢？答案是否定的。</p><p>在仔细阅读完 fetch 相关的文档后，我在 <code>Response</code> 对象的<a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" target="_blank" rel="noopener">文档页面</a>找到了一个有意思的属性和一个更有意思的方法。</p><h3 id="Reponse-bodyUsed"><a href="#Reponse-bodyUsed" class="headerlink" title="Reponse.bodyUsed"></a>Reponse.bodyUsed</h3><p>这个属性用于标示这个 Response 对象是否已经被读取过，比如下面的代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'/api/user.json'</span>)</span><br><span class="line">.then(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(response.bodyUsed); <span class="comment">// false</span></span><br><span class="line">  <span class="keyword">return</span> response.json().then(<span class="function"><span class="params">json</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(response.bodyUsed); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果这里再次尝试对 response 的 Stream 数据进行读取，则会报错</span></span><br><span class="line">    <span class="comment">// response.text();   // Uncaught TypeError: Already read</span></span><br><span class="line">    <span class="keyword">return</span> json;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;)</span><br><span class="line">.then(<span class="function"><span class="params">json</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 拿到服务端响应的 JSON 对象</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>就很好的解释了 bodyUsed 属性的作用和使用方法。但是从实用角度来说，这个布尔值并没有太大的作用，真正帮我们解决拦截并统一响应 fetch 请求的功能在下面。</p><h3 id="Reponse-prototype-clone"><a href="#Reponse-prototype-clone" class="headerlink" title="Reponse.prototype.clone()"></a>Reponse.prototype.clone()</h3><p>这个 API 顾名思义就是对 response 对象的一个复制，根据 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/clone" target="_blank" rel="noopener">文档</a> 描述，复制出来的 response 对象和原来的对象数据和行为完全一致，只是保存在不同的变量中而已。</p><p>那么 clone 方法如何解决 Already Read 报错的问题呢，让我们稍微修改一下上面的代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'/api/user.json'</span>)</span><br><span class="line">.then(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(response.bodyUsed); <span class="comment">// false</span></span><br><span class="line">  <span class="keyword">return</span> response.clone().json().then(<span class="function"><span class="params">json</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(response.bodyUsed); <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果这里再次尝试对 response 的 Stream 数据进行读取，没有问题</span></span><br><span class="line">    <span class="comment">// response.text().then(text =&gt; console.log(text));</span></span><br><span class="line">    <span class="keyword">return</span> json;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>注意第 4 行我们在 <code>response.json()</code> 调用前添加了 <code>response.clone().json()</code>，这样就完成了对 response 对象的复制。后续的 Stream API 读取发生在一个全新的 response 对象上，所以原来的 response 对象 bodyUsed 属性为 false，也可以对其调用不同的读取方法，如 .text() 或 .blob()。</p><p>唯一需要注意的是，clone 方法的调用一定要发生在对 response 调用任何读取之前，比如下面的代码依然会报错：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'/api/user.json'</span>)</span><br><span class="line">.then(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(response.bodyUsed); <span class="comment">// false</span></span><br><span class="line">  <span class="keyword">return</span> response.json().then(<span class="function"><span class="params">json</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 如果这里先尝试 clone，再对 response 的 Stream 数据进行读取，依然会报错</span></span><br><span class="line">    <span class="comment">// response.clone().text().then(text =&gt; console.log(text));</span></span><br><span class="line">    <span class="keyword">return</span> json;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;在前端工程实践中，经常会有拦截 Ajax 请求的需求，比如统一添加 CSRF token，或者统一实现缓存处理等。在前 fetch 时代，如果使用了 jQuery，可以直接通过配置 &lt;code&gt;jQuery.ajaxPrefilter&lt;/code&gt; 实现；如果用的是原生 A
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>fetch 没有你想象的那么美</title>
    <link href="https://undefinedblog.com/window-fetch-is-not-as-good-as-you-imagined/"/>
    <id>https://undefinedblog.com/window-fetch-is-not-as-good-as-you-imagined/</id>
    <published>2017-03-18T09:37:11.000Z</published>
    <updated>2017-03-18T12:25:46.000Z</updated>
    
    <content type="html"><![CDATA[<p>前端工程中发送 HTTP 请求从来都不是一件容易的事，前有骇人的 <code>ActiveXObject</code>，后有 API 设计十分别扭的 <code>XMLHttpRequest</code>，甚至这些原生 API 的用法至今仍是很多大公司前端校招的考点之一。</p><p>也正是如此，fetch 的出现在前端圈子里一石激起了千层浪，大家欢呼雀跃弹冠相庆恨不得马上把项目中的 <code>$.ajax</code> 全部干掉。然而，在新鲜感过后， fetch 真的有你想象的那么美好吗？</p><blockquote><p>如果你还不了解 fetch，可以参考我的同事 @camsong 在 2015 年写的文章 <a href="https://github.com/camsong/blog/issues/2" target="_blank" rel="noopener">《传统 Ajax 已死，Fetch 永生》</a></p></blockquote><p>在开始「批斗」fetch之前，大家需要明确 fetch 的定位：<strong>fetch 是一个 low-level 的 API，它注定不会像你习惯的 <code>$.ajax</code> 或是 <code>axios</code> 等库帮你封装各种各样的功能或实现。</strong>也正是因为这个定位，在学习或使用 fetch API 时，你会遇到不少的挫折。</p><p>（对于没有耐心看完全文的同学，请先记住本文的主旨不在于批评 fetch，事实上 fetch 的出现绝对是前端领域的进步体现。在了解主旨的前提下，关注<strong>加黑</strong>部分即可。）</p><h2 id="发请求，比你想象的要复杂"><a href="#发请求，比你想象的要复杂" class="headerlink" title="发请求，比你想象的要复杂"></a>发请求，比你想象的要复杂</h2><p>很多人看到 fetch 的第一眼肯定会被它简洁的 API 吸引：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'http://abc.com/tiger.png'</span>);</span><br></pre></td></tr></table></figure><p>原来需要 <code>new XMLHttpRequest</code> 等小十行代码才能实现的功能如今一行代码就能搞定，能不让人动心吗！</p><p>但是当你真正在项目中使用时，少不了需要向服务端发送数据的过程，那么使用 fetch 发送一个对象到服务端需要几行代码呢？（出于兼容性考虑，大部分的项目在发送 POST 请求时都会使用 <code>application/x-www-form-urlencoded</code> 这种 <code>Content-Type</code>）</p><p>先来看看使用 jQuery 如何实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$.post(<span class="string">'/api/add'</span>, &#123;<span class="attr">name</span>: <span class="string">'test'</span>&#125;);</span><br></pre></td></tr></table></figure><p>然后再看看 fetch 如何处理：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'/api/add'</span>, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  headers: &#123;</span><br><span class="line">    <span class="string">'Content-Type'</span>: <span class="string">'application/x-www-form-urlencoded;charset=UTF-8'</span></span><br><span class="line">  &#125;,</span><br><span class="line">  body: <span class="built_in">Object</span>.keys(&#123;<span class="attr">name</span>: <span class="string">'test'</span>&#125;).map(<span class="function">(<span class="params">key</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">encodeURIComponent</span>(key) + <span class="string">'='</span> + <span class="built_in">encodeURIComponent</span>(params[key]);</span><br><span class="line">  &#125;).join(<span class="string">'&amp;'</span>)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>等等，<code>body</code> 字段那一长串代码在干什么？<strong>因为 fetch 是一个 low-level 的 API，所以你需要自己 encode HTTP 请求的 payload，还要自己指定 HTTP Header 中的 <code>Content-Type</code> 字段。</strong></p><p>这样就结束了吗？如果你在自己的项目中这样发送 POST 请求，很可能会得到一个 <code>401 Unauthorized</code> 的结果（视你的服务端如何处理无权限的情况而定）。如果你在仔细看一遍文档，会发现<strong>原来  fetch 在发送请求时默认不会带上 Cookie！</strong></p><p>好，我们让 fetch 带上 Cookie：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'/api/add'</span>, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  credentials: <span class="string">'include'</span>,</span><br><span class="line">  ...</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这样，一个最基础的 POST 请求才算能够发出去。</p><p>同理，如果你需要 POST 一个 JSON 到服务端，你需要这样做：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'/api/add'</span>, &#123;</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  credentials: <span class="string">'include'</span>,</span><br><span class="line">  headers: &#123;</span><br><span class="line">    <span class="string">'Content-Type'</span>: <span class="string">'application/json;charset=UTF-8'</span></span><br><span class="line">  &#125;,</span><br><span class="line">  body: <span class="built_in">JSON</span>.stringify(&#123;<span class="attr">name</span>: <span class="string">'test'</span>&#125;)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>相比于 <code>$.ajax</code> 的封装，是不是复杂的不是一点半点呢？</p><h2 id="错误处理，比你想象的复杂"><a href="#错误处理，比你想象的复杂" class="headerlink" title="错误处理，比你想象的复杂"></a>错误处理，比你想象的复杂</h2><p>按理说，fetch 是基于 <code>Promise</code> 的 API，每个 fetch 请求会返回一个 Promise 对象，而 Promise 的异常处理且不论是否方便，起码大家是比较熟悉的了。然而 fetch 的异常处理，还是有不少门道。</p><p>假如我们用 fetch 请求一个不存在的资源：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'xx.png'</span>)</span><br><span class="line">.then(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'ok'</span>);</span><br><span class="line">&#125;)</span><br><span class="line">.catch(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'error'</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>按照我们的惯例 console 应该要打印出 「error」才对，可事实又如何呢？有图有真相：</p><p><img src="https://img.alicdn.com/tfs/TB1qBeJQXXXXXcoXpXXXXXXXXXX-1168-158.png" alt="fetch response ok fail"></p><p>为什么会打印出 「ok」呢？</p><p><strong>按照 MDN 的<a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful" target="_blank" rel="noopener">说法</a>，fetch 只有在遇到网络错误的时候才会 reject 这个 promise，比如用户断网或请求地址的域名无法解析等。只要服务器能够返回 HTTP 响应（甚至只是 CORS preflight 的 OPTIONS 响应），promise 一定是 resolved 的状态。</strong></p><p>所以要怎么判断一个 fetch 请求是不是成功呢？你得用 <code>response.ok</code> 这个字段：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'xx.png'</span>)</span><br><span class="line">.then(<span class="function">(<span class="params">response</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (response.ok) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'ok'</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'error'</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line">.catch(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'error'</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>再执行一次，终于看到了正确的日志：</p><p><img src="https://img.alicdn.com/tfs/TB1DmSmQXXXXXbcaXXXXXXXXXXX-1214-426.png" alt="fetch response ok success"></p><h2 id="Stream-API，比你想象的复杂"><a href="#Stream-API，比你想象的复杂" class="headerlink" title="Stream API，比你想象的复杂"></a>Stream API，比你想象的复杂</h2><p><strong>当你的服务端返回的数据是 JSON 格式时，你肯定希望 fetch 返回给你的是一个普通 JavaScript 对象，然而你拿到的是一个 <code>Response</code> 对象，而真正的请求结果 —— 即 <code>response.body</code> —— 则是一个 <code>ReadableStream</code>。</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">fetch(<span class="string">'/api/user.json?id=2'</span>)   <span class="comment">// 服务端返回 &#123;"name": "test", "age": 1&#125; 字符串</span></span><br><span class="line">.then(<span class="function">(<span class="params">response</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 这里拿到的 response 并不是一个 &#123;name: 'test', age: 1&#125; 对象</span></span><br><span class="line">  <span class="keyword">return</span> response.json();  <span class="comment">// 将 response.body 通过 JSON.parse 转换为 JS 对象</span></span><br><span class="line">&#125;)</span><br><span class="line">.then(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(data); <span class="comment">// &#123;name: 'test', age: 1&#125;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>你可能觉得，这些写在规范里的技术细节使用 fetch 的人无需关心，然而在实际使用过程中你会遇到各种各样的问题迫使你不得不了解这些细节。</p><p>首先需要承认，fetch 将 <code>response.body</code> 设计成 ReadableStream 其实是非常有前瞻性的，这种设计让你在请求大体积文件时变得非常有用。然而，在我们的日常使用中，还是短小的 JSON 片段更加常见。而为了兼容不常见的设计，我们不得不多一次 <code>response.json()</code> 的调用。</p><p>不仅是调用变得麻烦，如果你的服务端采用了严格的 REST 风格，<strong>对于某些特殊情况并没有返回 JSON 字符串，而是用了 HTTP 状态码（如：<code>204 No Content</code>），那么在调用 <code>response.json()</code> 时则会抛出异常。</strong></p><p>此外，<strong><code>Response</code> 还限制了响应内容的重复读取和转换</strong>，例如如下代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> prevFetch = <span class="built_in">window</span>.fetch;</span><br><span class="line"><span class="built_in">window</span>.fetch = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  prevFetch.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>)</span><br><span class="line">  .then(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">      response.json().then(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (data.hasError === <span class="literal">true</span>) &#123;</span><br><span class="line">          tracker.log(<span class="string">'API Error'</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        resolve(response);</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fetch(<span class="string">'/api/user.json?id=1'</span>)</span><br><span class="line">.then(<span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> response.json();  <span class="comment">// 先将结果转换为 JSON 对象</span></span><br><span class="line">&#125;)</span><br><span class="line">.then(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(data);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>是对 fetch 做了一个简单的 AOP，试图拦截所有的请求结果，并当返回的 JSON 对象中 <code>hasError</code> 字段如果为 <code>true</code> 的话，打点记录出错的接口。</p><p>然而这样的代码会导致如下错误：</p><blockquote><p>Uncaught TypeError: Already read</p></blockquote><p>调试一番后，你会发现是因为我们在切面中已经调用了 <code>response.json()</code>，这个时候重复调用该方法时就会报错。（实际上，再次调用其它任何转换方法，如 <code>.text()</code> 也会报错）</p><p>因此，想要在 fetch 上实现 AOP 仍需另辟蹊径。</p><h2 id="其它问题"><a href="#其它问题" class="headerlink" title="其它问题"></a>其它问题</h2><p>1. fetch 不支持同步请求</p><p>大家都知道同步请求阻塞页面交互，但事实上仍有不少项目在使用同步请求，可能是历史架构等等原因。如果你切换了 fetch 则无法实现这一点。</p><p>2. fetch 不支持取消一个请求</p><p>使用 XMLHttpRequest 你可以用 <code>xhr.abort()</code> 方法取消一个请求（虽然这个方法也不是那么靠谱，同时是否真的「取消」还依赖于服务端的实现），但是使用 fetch 就无能为力了，至少目前是这样的。</p><p>3. fetch 无法查看请求的进度</p><p>使用 XMLHttpRequest 你可以通过 <code>xhr.onprogress</code> 回调来动态更新请求的进度，而这一点目前 fetch 还没有原生支持。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>还是要再次明确，fetch API 的出现绝对是推动了前端在请求发送功能方面的进步。</p><p>然而，也需要意识到，<strong>fetch 是一个相当底层的 API，在实际项目使用中，需要做各种各样的封装和异常处理，而并非开箱即用</strong>，更做不到直接替换 <code>$.ajax</code> 或其他请求库。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li>fetch spec <a href="https://fetch.spec.whatwg.org/#body" target="_blank" rel="noopener">https://fetch.spec.whatwg.org/#body</a></li><li>fetch 实现 <a href="https://github.com/github/fetch" target="_blank" rel="noopener">https://github.com/github/fetch</a></li><li>什么是 Already Read 报错 <a href="http://stackoverflow.com/questions/34786358/what-does-this-error-mean-uncaught-typeerror-already-read" target="_blank" rel="noopener">http://stackoverflow.com/questions/34786358/what-does-this-error-mean-uncaught-typeerror-already-read</a></li><li>使用 fetch 处理 HTTP 请求失败 <a href="https://www.tjvantoll.com/2015/09/13/fetch-and-errors/" target="_blank" rel="noopener">https://www.tjvantoll.com/2015/09/13/fetch-and-errors/</a></li><li><a href="https://jakearchibald.com/2015/thats-so-fetch/" target="_blank" rel="noopener">https://jakearchibald.com/2015/thats-so-fetch/</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;前端工程中发送 HTTP 请求从来都不是一件容易的事，前有骇人的 &lt;code&gt;ActiveXObject&lt;/code&gt;，后有 API 设计十分别扭的 &lt;code&gt;XMLHttpRequest&lt;/code&gt;，甚至这些原生 API 的用法至今仍是很多大公司前端校招的考点之一。&lt;
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Mocha 测试设置 timeout 不生效的问题排查</title>
    <link href="https://undefinedblog.com/set-timeout-for-mocha-with-karma/"/>
    <id>https://undefinedblog.com/set-timeout-for-mocha-with-karma/</id>
    <published>2017-01-16T11:47:25.000Z</published>
    <updated>2017-01-16T11:56:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>Mocha 提供了针对不同粒度的测试超时配置项，但是最近在某个使用了 Karma + Mocha 的项目中遇到无论怎么设置 <code>this.timeout()</code> Mocha 都顽固的在 2000ms 时报超时错误的问题，经排查疑似为当 Mocha 和 Karma 一起使用时，需要通过 <code>karma.conf.js</code> 来配置 Mocha 超时时间。</p><p>当没有正确设置 Karma + Mocha 超时时间时，会报出如下错误：</p><blockquote><p>Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure “done()” is called; if returning a Promise, ensure it resolves.</p></blockquote><p>此时即使在测试代码里显式添加了超时配置也没有用：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">describe(<span class="string">'Suite'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  it(<span class="string">'Case'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.timeout(<span class="number">60000</span>); <span class="comment">// 无用</span></span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这里还需要注意，一些新手经常犯的错误为这里使用了 ES2015 的箭头函数，this 这个 context 发生了改变，导致超时配置不生效。</p><p>最后经过一番 Google，发现当 Mocha 和 Karma 一起使用时，要在 <code>karma.conf.js</code> 里对 Mocha 进行配置，配置如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// karma.conf.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span>(<span class="params">config</span>) </span>&#123;</span><br><span class="line">  config.set(&#123;</span><br><span class="line">    client: &#123;</span><br><span class="line">      mocha: &#123;</span><br><span class="line">        timeout : <span class="number">6000</span> </span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>其它需要传递给 Mocha 的参数也可以在这里配置。此处配置的超时时间为全局级别，因此设置时要考虑最长的 Case 需要的时间。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Mocha 提供了针对不同粒度的测试超时配置项，但是最近在某个使用了 Karma + Mocha 的项目中遇到无论怎么设置 &lt;code&gt;this.timeout()&lt;/code&gt; Mocha 都顽固的在 2000ms 时报超时错误的问题，经排查疑似为当 Mocha 和 Ka
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>ReactRouter 4 前瞻</title>
    <link href="https://undefinedblog.com/reactrouter-4-foresee/"/>
    <id>https://undefinedblog.com/reactrouter-4-foresee/</id>
    <published>2016-09-17T20:44:57.000Z</published>
    <updated>2016-09-17T22:28:09.000Z</updated>
    
    <content type="html"><![CDATA[<p>要问用 React 技术栈的前端同学对哪个库的感情最复杂，恐怕非 ReactRouter 莫属了。早在 React 0.x 时代，ReactRouter 就凭借与 React 核心思想一致的声明式 API 获得了大量开发者的喜爱。后续更是并入 reactjs group 并有 React 核心开发成员参与，俨然是 React 官方路由套件一样的存在。</p><p>然而从 1.x 版本起，ReactRouter 的洪荒之力就开始慢慢爆发，目前最稳定的版本已经是  <code>2.8.1</code>，而下一个正式版本将是 —— 没错，正如你在标题里看到的那样 —— <code>4.0.0</code>。先收拾一下你崩溃的心情，让我们来看看这一切到底是怎么回事。</p><h2 id="ReactRouter-3-去哪儿了"><a href="#ReactRouter-3-去哪儿了" class="headerlink" title="ReactRouter 3 去哪儿了"></a>ReactRouter 3 去哪儿了</h2><p>从 <code>2.x</code> 直接跨入 <code>4.x</code>，ReactRouter 的 maintainer 数学还不至于那么差，那这一切都是为什么呢？事实上 <code>3.x</code> 版本相比于 <code>2.x</code> 并没有引入任何新的特性，只是将 <code>2.x</code> 版本中部分废弃 API 的 warning 移除掉而已。按照规划，没有历史包袱的新项目想要使用稳定版的 ReactRouter 时，应该使用 ReactRouter 3.x。</p><p>目前 <code>3.x</code> 版本也还处于 beta 阶段，不过会先于 <code>4.x</code> 版本正式发布。<strong>如果你已经在使用 <code>2.x</code> 的版本，那么升级 <code>3.x</code> 将不会有任何额外的代码变动。</strong></p><h2 id="为什么又要大改-API"><a href="#为什么又要大改-API" class="headerlink" title="为什么又要大改 API"></a>为什么又要大改 API</h2><p>ReactRouter 的 API 是出了名的善变，这一点我已经在<a href="https://undefinedblog.com/react-v0-14/">之前的博文</a>中吐槽过一次了。没想到连 React 都稳定的在 <code>15.x</code> 版本中维护了这么久，ReactRouter 还是想要搞点大新闻。</p><p>根据 ReactRouter 核心开发者 <a href="https://github.com/ryanflorence" target="_blank" rel="noopener">ryanflorence</a> 的说法，ReactRouter 4.x 之所以要大刀阔斧的修改 API，主要是为了『声明式的可组合性（Declarative Composability）』。怎么理解声明式的可组合性呢？实际上当你在写 React 组件时，就不知不觉的利用了这一特性。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ComponentA</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &lt;div&gt;A&lt;/div&gt;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ComponentB</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &lt;div&gt;B&lt;/div&gt;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Page</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &lt;div&gt;&lt;ComponentA /&gt;&lt;ComponentB /&gt;&lt;/div&gt;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>正如上面的代码片段，在 Page 组件中你可以方便的将任意组件在 render 方法中渲染出来。但是这种特性遇到 ReactRouter 时，你只能把匹配当前路由的组件用<code>this.props.children</code>（最早的时候是 <code>this.props.routerHandler</code>，有多少人还有印象）渲染 。</p><p>此外，ReactRouter 虽然宣称是声明式的路由库，但仍然提供了不少非声明式的 API，其中路由组件的生命周期方法就是最典型的例子。其实我们不难想到，明明用来渲染的 React 组件本身已经有了一套完备的生命周期方法（componentWillMount、componentWillUnmount 等），为什么还需要 ReactRouter 提供的什么 <code>onEnter</code> 和 <code>onLeave</code> 呢？</p><p>当然，这一切都是 ryanflorence 个人的说法，不知道又有多少人真的会为了追求纯正的 declarative 花时间去重构代码兼容 ReactRouter 4 呢？</p><h2 id="ReactRouter-4-有什么变化"><a href="#ReactRouter-4-有什么变化" class="headerlink" title="ReactRouter 4 有什么变化"></a>ReactRouter 4 有什么变化</h2><p>虽然目前还是 alpha 版本，但是 ReactRouter 4 的 API 已经有了雏形，其<a href="https://react-router-website-xvufzcovng.now.sh/" target="_blank" rel="noopener">官方文档</a>也有不少带 demo 的例子，下面简单总结一下 ReactRouter 4 的变化。</p><h3 id="更少的-API"><a href="#更少的-API" class="headerlink" title="更少的 API"></a>更少的 API</h3><p>先来看看目前 ReactRouter 2 的 API 列表：</p><p><img src="http://ww3.sinaimg.cn/mw690/831e9385gw1f7xaeadze7j209d0j5t9v.jpg" alt="ReactRouter 2.x API 列表"></p><p>再看看 4.x 计划中的 API：</p><p><img src="http://ww3.sinaimg.cn/mw690/831e9385gw1f7xaf8ju4lj204406f3yj.jpg" alt="ReactRouter 4.x API 列表"></p><p>直观看起来 API 确实少了不少，但这也意味着使用了被废弃的 API 将会有更大的重构工作量。不，实际上即使用了没有被废弃的 API，也会有很大的重构工作量。但是对于新手来说，学习成本相对会比较低。</p><h3 id="lt-Router-gt-变身为容器"><a href="#lt-Router-gt-变身为容器" class="headerlink" title="&lt;Router&gt;变身为容器"></a>&lt;Router&gt;变身为容器</h3><p>在目前的 API 中，&lt;Router&gt; 组件的 children 只能是 ReactRouter 提供的各种组件，如 <code>&lt;Route&gt;、&lt;IndexRoute&gt;、&lt;Redirect&gt;</code>等。而在 ReactRouter 4 中，你可以将各种组件及标签放进 <code>&lt;Router&gt;</code> 组件中，他的角色也更像是 Redux 中的 <code>&lt;Provider&gt;</code>。</p><p>不同的是 <code>&lt;Provider&gt;</code> 是用来保持与 store 的更新，而 <code>&lt;Router&gt;</code> 是用来保持与 location 的同步。</p><p>以下为新版 ReactRouter 可能的用法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;Router&gt;</span><br><span class="line">  &lt;div&gt;</span><br><span class="line">    &lt;h2&gt;Accounts&lt;<span class="regexp">/h2&gt;</span></span><br><span class="line"><span class="regexp">    &lt;ul&gt;</span></span><br><span class="line"><span class="regexp">      &lt;li&gt;&lt;Link to="/</span>netflix<span class="string">"&gt;Netflix&lt;/Link&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="string">      &lt;li&gt;&lt;Link to="</span>/zillow-group<span class="string">"&gt;Zillow Group&lt;/Link&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="string">      &lt;li&gt;&lt;Link to="</span>/yahoo<span class="string">"&gt;Yahoo&lt;/Link&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="string">      &lt;li&gt;&lt;Link to="</span>/modus-create<span class="string">"&gt;Modus Create&lt;/Link&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="string">    &lt;/ul&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    &lt;Match pattern="</span>/:id<span class="string">" component=&#123;Child&#125; /&gt;</span></span><br><span class="line"><span class="string">  &lt;/div&gt;</span></span><br><span class="line"><span class="string">&lt;/Router&gt;</span></span><br></pre></td></tr></table></figure><p><strong>不过这样的变动将会导致一个很严重的问题：由于路由定义里不再是纯粹的路由相关的组件，你无法在一个文件中（通常是用来定义路由的 <code>routes.js</code>）看到整个 App 的路由设计及分布。</strong></p><p>目前 ReactRouter 提供了一个<a href="https://github.com/ReactTraining/react-router-addons-routes" target="_blank" rel="noopener">工具库</a>来解决将完整的路由定义转换为 <code>&lt;Match&gt;</code> 组件的问题，但是粗略的看了一下用法感觉还是比较别扭，期待正式发布时能有更好的解决方案。</p><h3 id="再见-lt-Route-gt-，你好-lt-Match-gt"><a href="#再见-lt-Route-gt-，你好-lt-Match-gt" class="headerlink" title="再见 &lt;Route&gt;，你好&lt;Match&gt;"></a>再见 &lt;Route&gt;，你好&lt;Match&gt;</h3><p>ReactRouter 中被使用最多的 <code>&lt;Route&gt;</code> 组件这次没有幸免，取而代之的是 <code>&lt;Match&gt;</code> 组件。那么 <code>&lt;Match&gt;</code> 究竟有什么不同呢？</p><p>1. <code>pattern</code> 取代 <code>path</code></p><p>很大程度上这只是为了配合 Match 这个名称的转换而已，实际功能与 path 无异。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;Match pattern=<span class="string">"/users/:id"</span> component=&#123;User&#125;/&gt;</span><br></pre></td></tr></table></figure><p>2. <code>component</code> 还是 <code>component</code>，<code>components</code> 没了</p><p>判断路由命中时该渲染哪个组件的 props 还是叫 <code>component</code>，但是为一个路由同时提供多个命名组件的 props <code>components</code> 没有了。下文会详细介绍在 ReactRouter 中如何解决一个路由下渲染多个组件的问题。</p><p>3. 使用 <code>render</code> 渲染行内路由组件</p><p>能够更自由的控制当前命中组件的渲染，以及可以方便的添加进出的动画。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// convenient inline rendering</span></span><br><span class="line">&lt;Match pattern=<span class="string">"/home"</span> render=&#123;() =&gt; &lt;div&gt;Home&lt;/div&gt;&#125;/&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// wrapping/composing</span></span><br><span class="line"><span class="keyword">const</span> MatchWithFade = <span class="function">(<span class="params">&#123; component:Component, ...rest &#125;</span>) =&gt;</span> (</span><br><span class="line">  &lt;Match &#123;...rest&#125; render=&#123;(matchProps) =&gt; (</span><br><span class="line">    &lt;FadeIn&gt;</span><br><span class="line">      &lt;Component &#123;...matchProps&#125;/&gt;</span><br><span class="line">    &lt;<span class="regexp">/FadeIn&gt;</span></span><br><span class="line"><span class="regexp">  )&#125;/</span>&gt;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">&lt;MatchWithFade pattern=<span class="string">"/cool"</span> component=&#123;Something&#125;/&gt;</span><br></pre></td></tr></table></figure><h3 id="lt-Miss-gt-取代曾经的-lt-NotFoundRoute-gt"><a href="#lt-Miss-gt-取代曾经的-lt-NotFoundRoute-gt" class="headerlink" title="&lt;Miss&gt; 取代曾经的 &lt;NotFoundRoute&gt;"></a>&lt;Miss&gt; 取代曾经的 &lt;NotFoundRoute&gt;</h3><p>早在 0.x 时代，ReactRouter 提供了 <code>&lt;NotFountRoute&gt;</code> 来解决渲染 404 页面的问题。不过这一组件在 <code>1.x</code> 时就被移除了，你不得不多写若干行代码来解决这个问题。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 如果想要展示一个 404 页面（当前 URL 不变）</span></span><br><span class="line">&lt;Route path=<span class="string">'*'</span> component=&#123;My404Component&#125; /&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果想要展示一个 404 页面（当前 URL 重定向到 /404 ）</span></span><br><span class="line">&lt;Route path=<span class="string">'/404'</span> component=&#123;My404Component&#125; /&gt;</span><br><span class="line">&lt;Redirect <span class="keyword">from</span>=<span class="string">'*'</span> to=<span class="string">'/404'</span> /&gt;</span><br></pre></td></tr></table></figure><p>在 ReactRouter 4 中，你可以省点儿事直接用 <code>&lt;Miss&gt;</code> 组件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> App = <span class="function"><span class="params">()</span> =&gt;</span> (</span><br><span class="line">  &lt;Router&gt;</span><br><span class="line">    &lt;Match pattern=<span class="string">"/foo"</span>/&gt;</span><br><span class="line">    &lt;Match pattern=<span class="string">"/bar"</span>/&gt;</span><br><span class="line">    &lt;Miss component=&#123;NoMatch&#125;/&gt;</span><br><span class="line">  &lt;<span class="regexp">/Router&gt;</span></span><br><span class="line"><span class="regexp">)</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">const NoMatch = (&#123; location &#125;) =&gt; (</span></span><br><span class="line"><span class="regexp">  &lt;div&gt;Nothing matched &#123;location.pathname&#125;.&lt;/</span>div&gt;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h2 id="ReactRouter-4-的大杀器：一个路由，多次匹配"><a href="#ReactRouter-4-的大杀器：一个路由，多次匹配" class="headerlink" title="ReactRouter 4 的大杀器：一个路由，多次匹配"></a>ReactRouter 4 的大杀器：一个路由，多次匹配</h2><p>在上文中我们提到过，目前 ReactRouter 匹配到某个路由时，将直接渲染通过 <code>component</code> 定义的组件，并把命中的子路由对应的组件作为 <code>this.props.children</code> 传入。</p><p>在前端 App 日益复杂的今天，一条路由的改变不仅仅简单的是页面的切换，甚至可能精细到页面上某些组件的切换。</p><p>让我们直接看下面的代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 每条路由有两个组件，一个用于渲染 sidebar，一个用于渲染主页面</span></span><br><span class="line"><span class="keyword">const</span> routes = [</span><br><span class="line">  &#123; <span class="attr">pattern</span>: <span class="string">'/'</span>,</span><br><span class="line">    exactly: <span class="literal">true</span>,</span><br><span class="line">    sidebar: <span class="function"><span class="params">()</span> =&gt;</span> &lt;div&gt;Home!&lt;/div&gt;,</span><br><span class="line">    main: <span class="function"><span class="params">()</span> =&gt;</span> &lt;h2&gt;Main&lt;<span class="regexp">/h2&gt;</span></span><br><span class="line"><span class="regexp">  &#125;,</span></span><br><span class="line"><span class="regexp">  &#123; pattern: '/</span>foo<span class="string">',</span></span><br><span class="line"><span class="string">    sidebar: () =&gt; &lt;div&gt;foo!&lt;/div&gt;,</span></span><br><span class="line"><span class="string">    main: () =&gt; &lt;h2&gt;Foo&lt;/h2&gt;</span></span><br><span class="line"><span class="string">  &#125;,</span></span><br><span class="line"><span class="string">  &#123; pattern: '</span>/bar<span class="string">',</span></span><br><span class="line"><span class="string">    sidebar: () =&gt; &lt;div&gt;Bar!&lt;/div&gt;,</span></span><br><span class="line"><span class="string">    main: () =&gt; &lt;h2&gt;Bar&lt;/h2&gt;</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;Router history=&#123;history&#125;&gt;</span></span><br><span class="line"><span class="string">  &lt;div className="page"&gt;</span></span><br><span class="line"><span class="string">    &lt;div className="sidebar"&gt;</span></span><br><span class="line"><span class="string">      &lt;ul&gt;</span></span><br><span class="line"><span class="string">        &lt;li&gt;&lt;Link to="/"&gt;Home&lt;/Link&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="string">        &lt;li&gt;&lt;Link to="/foo"&gt;Foo&lt;/Link&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="string">        &lt;li&gt;&lt;Link to="/bar"&gt;Bar&lt;/Link&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="string">      &lt;/ul&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">      /* 对当前路由进行匹配，并渲染侧边栏 */</span></span><br><span class="line"><span class="string">      &#123;routes.map((route, index) =&gt; (</span></span><br><span class="line"><span class="string">        &lt;Match</span></span><br><span class="line"><span class="string">          key=&#123;index&#125;</span></span><br><span class="line"><span class="string">          pattern=&#123;route.pattern&#125;</span></span><br><span class="line"><span class="string">          component=&#123;route.sidebar&#125;</span></span><br><span class="line"><span class="string">          exactly=&#123;route.exactly&#125;</span></span><br><span class="line"><span class="string">        /&gt;</span></span><br><span class="line"><span class="string">      ))&#125;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    &lt;div className="main"&gt;</span></span><br><span class="line"><span class="string">      /* 对当前路由进行匹配，并渲染主界面 */</span></span><br><span class="line"><span class="string">      &#123;routes.map((route, index) =&gt; (</span></span><br><span class="line"><span class="string">        &lt;Match</span></span><br><span class="line"><span class="string">          key=&#123;index&#125;</span></span><br><span class="line"><span class="string">          pattern=&#123;route.pattern&#125;</span></span><br><span class="line"><span class="string">          component=&#123;route.main&#125;</span></span><br><span class="line"><span class="string">          exactly=&#123;route.exactly&#125;</span></span><br><span class="line"><span class="string">        /&gt;</span></span><br><span class="line"><span class="string">      ))&#125;</span></span><br><span class="line"><span class="string">    &lt;/div&gt;</span></span><br><span class="line"><span class="string">  &lt;/div&gt;</span></span><br><span class="line"><span class="string">&lt;/Router&gt;</span></span><br></pre></td></tr></table></figure><p>通过上面的代码片段我们可以看出，<code>&lt;Match&gt;</code> 的设计理念完全不同于 <code>&lt;Route&gt;</code>。使用 <code>&lt;Match&gt;</code> 可以让我们在当前路由中尽情的匹配并渲染需要的组件，无论是需要渲染 Sidebar、BreadCrumb 还是主界面。</p><p>若使用现有的 ReactRouter API，只能通过当前路由匹配主界面对应的组件，至于 Sidebar 和 BreadCrumb，只能传入当前的 pathname 手动进行匹配。由此可见 <code>&lt;Match&gt;</code> 将会为此类需求带来极大的便利。</p><h2 id="其他疑惑"><a href="#其他疑惑" class="headerlink" title="其他疑惑"></a>其他疑惑</h2><p>当然，还有几个大家普遍关心的问题官方文档中也有所提及：</p><p>1. ReactRouter 的 API 是否还会有大的变化？</p><p>答：只要 React 的 API 不变，这一版的 API 也不会变。</p><p>2. 是否有对 Redux 的支持？</p><p>答：将提供一个受控的 <code>&lt;ControlledRouter&gt;</code>，支持传入一个 location。（作者注：这下真的不需要 react-router-redux 了，撒花！）</p><p>3. 新版是否对滚动位置管理添加支持？</p><p>答：将会对 <code>window</code> 和独立组件的滚动位置提供管理功能。（作者注：目前在不添加额外配置的情况下，路由切换时并不会恢复滚动位置）</p><h2 id="ReactRouter-4-小结"><a href="#ReactRouter-4-小结" class="headerlink" title="ReactRouter 4 小结"></a>ReactRouter 4 小结</h2><p>了解了这么多 ReactRouter 4 的内容，你是否喜欢新版的 API 设计呢？</p><p>其实从 ReactRouter 4 的这些变动不难看出，ReactRouter 正在努力的朝纯声明式的 API 方向迈进，一方面减少不必要的命令式 API，一方面也提供更语义化的路由匹配方案。</p><p>此外新的 ReactRouter 也提供了非常强大的动态路由能力，甚至对嵌套路由也有所支持，这也是一个复杂的前端应用所必须的功能。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://react-router-website-xvufzcovng.now.sh/" target="_blank" rel="noopener">ReactRouter 4 文档</a></li><li><a href="https://github.com/ReactTraining/react-router/tree/v4" target="_blank" rel="noopener">ReactRouter 4 Github repo</a></li><li><a href="https://github.com/ReactTraining/react-router" target="_blank" rel="noopener">ReactRouter</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;要问用 React 技术栈的前端同学对哪个库的感情最复杂，恐怕非 ReactRouter 莫属了。早在 React 0.x 时代，ReactRouter 就凭借与 React 核心思想一致的声明式 API 获得了大量开发者的喜爱。后续更是并入 reactjs group 并
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Angular2 中那些我看不懂的地方</title>
    <link href="https://undefinedblog.com/angular-the-confusing-part/"/>
    <id>https://undefinedblog.com/angular-the-confusing-part/</id>
    <published>2016-04-17T02:17:56.000Z</published>
    <updated>2016-05-01T12:01:10.000Z</updated>
    
    <content type="html"><![CDATA[<p>博客停更了近 3 个月，实在是愧对很多在微博上推荐的同学。因为最近大部分时间都投入在公司里一个比较复杂的项目中，直到本周才算正式发布，稍得解脱。</p><p>说这个项目复杂，不仅是因为需求设计复杂，更是因为在这个项目里我们使用了很多新技术 —— Angular2（是的，beta 版），Webpack2（是的，beta 版），RxJS（这两个 <a href="https://github.com/Reactive-Extensions/RxJS" target="_blank" rel="noopener">RxJS1</a>、<a href="https://github.com/ReactiveX/rxjs" target="_blank" rel="noopener">RxJS2</a> 傻傻分不清楚）还有 TypeScript。</p><p>关于为什么一个大型线上项目会选择这么冒进的技术选型这里不多做评价，只能说最终团队还是成功将整个产品发布到了线上，我也对 Angular2 这个全新的 Angular 有了更全面的认识。</p><p>这篇文章不想对比 React 和 Angular2（不要急，这些东西肯定会出现在项目总结里），而是想站在熟悉 React + Redux 开发模式的前端工程师角度说一说 Angular2，尤其是那些我不太理解的地方。</p><p>剧透：这篇文章无意引起骂战，因此如果你看到我哪里写的不对，请直接在评论区里纠正。</p><h2 id="看不懂的依赖注入"><a href="#看不懂的依赖注入" class="headerlink" title="看不懂的依赖注入"></a>看不懂的依赖注入</h2><p>我知道这其实不仅仅针对 Angular2，早在 Angular1 中就存在依赖注入的概念，甚至这还是 Angular 引以为傲的特性。每当谈到依赖注入的时候，我就看到一帮 OOP（以 Java 为首）爱好者眼里闪烁着奇异的光芒。</p><p>然而依赖注入的问题一直困扰着我，倒不是说不理解依赖注入的使用方法或是原理，而是不理解<strong>为什么我的代码里需要依赖注入？</strong></p><p>当我把这个问题抛给项目组中强推 Angular 的同学时，他也一时语塞。</p><p>在 Angular2 中，一个常见的依赖注入是注入一个 Http 对象，用于发送请求。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;Component, OnInit&#125; <span class="keyword">from</span> <span class="string">'angular2/core'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;Http&#125; <span class="keyword">from</span> <span class="string">'angular2/http'</span>;</span><br><span class="line"></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: <span class="string">'test'</span>,</span><br><span class="line">  providers: [Http]</span><br><span class="line">&#125;)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Test</span> <span class="title">implements</span> <span class="title">OnInit</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(private http: Http) &#123;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ngOnInit() &#123;</span><br><span class="line">    <span class="keyword">this</span>.http.get(<span class="string">'/api/user.json'</span>).map(<span class="function"><span class="params">res</span> =&gt;</span> res.json()).subscribe(<span class="function"><span class="params">result</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 这里拿到请求的结果</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码是在 Angular 中使用内置 Http 模块发送请求的一段示例代码。具体依赖注入发生在 constructor 函数中。按照 Angular 宣传的那样，你只用简单的在 constructor 中声明这些参数，Angular 将自动为你处理依赖注入的问题。</p><p>注意到我们要使用 Http 来发请求，首先需要从 <code>angular2/http</code> 中将 Http import 进来，然后需要在组件的配置中添加对应的 providers，最后还需要在 constructor 中声明一个参数。</p><p>这个时候我其实是有点懵逼的，因为在一般应用里，发请求大概是这样的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> request = <span class="built_in">require</span>(<span class="string">'superagent'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getUser</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> request.get(<span class="string">'/api/user.json'</span>).end(<span class="function">(<span class="params">err, res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 这里是拿到请求的结果</span></span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了公平起见，我还是假装依赖了一个第三方的库来实现 Ajax 请求。在这份代码里没有什么高大上的依赖注入，更没有什么 constructor，只有简单的引入并使用。</p><p>所以我不太明白，Angular 中的依赖注入究竟在多大程度上发挥着作用？</p><h3 id="语法糖、语法糖和语法糖"><a href="#语法糖、语法糖和语法糖" class="headerlink" title="语法糖、语法糖和语法糖"></a>语法糖、语法糖和语法糖</h3><p>很多人在第一眼看到 React 的时候，都在叫嚣 JSX 是「在 JS 里嵌入 HTML」，是邪门歪道，吃枣药丸。我只想说，相比于 Angular2 里的这些语法糖，JSX 简直就是 JavaScript 里的纯情小处男。</p><p>以下引用部分 Angular2 文档中关于模板语法的部分：</p><blockquote><p>Expanding *ngFor</p><p>The <em>ngFor undergoes a similar transformation. We begin with an </em>ngFor example:</p><p><code>&amp;lt;hero-detail *ngFor=&quot;#hero of heroes; trackBy:trackByHeroes&quot; [hero]=&quot;hero&quot;&gt;&lt;/hero-detail&gt;</code></p></blockquote><p>Here’s the same example after transporting the ngFor to the template directive:</p><blockquote><p><code>&amp;lt;hero-detail template=&quot;ngFor #hero of heroes; trackBy:trackByHeroes&quot; [hero]=&quot;hero&quot;&gt;&lt;/hero-detail&gt;</code></p><p>And here it is expanded further into a &lt;template&gt; tag wrapping the original &lt;hero-detail&gt; element:</p><p><code>&amp;lt;template ngFor #hero [ngForOf]=&quot;heroes&quot; [ngForTrackBy]=&quot;trackByHeroes&quot;&gt;  &lt;hero-detail [hero]=&quot;hero&quot;&gt;&lt;/hero-detail&gt;&amp;lt;/template&gt;</code></p></blockquote><p>读完之后你会发现，<code>*ngFor</code> 这个指令，原来是个语法糖。好嘛，其实语法糖在很多框架设计里都有啊。但是当你展开这个语法糖之后，你会发现这个语法糖其实还是另一个语法的语法糖。这也就是说，<code>*ngFor</code> 是语法糖的语法糖。</p><p>这种例子在 Angular2 中还有不少，任何一个正常的 JavaScript 开发者如果不想把自己逼疯的话，都会喜欢一个原始的 for 循环，或者一个 <code>[].map</code> 吧……</p><p>看到某篇文章争论说 Angular2 的模板用的是 「Valid HTML」，我只想说管你 Valid 不 Valid，臣妾看不懂啊。</p><h3 id="模板里面到底发生了什么"><a href="#模板里面到底发生了什么" class="headerlink" title="模板里面到底发生了什么"></a>模板里面到底发生了什么</h3><p>其实刚开始写 Angular 应用的时候，心里还是对双向绑定挺期待的。因为在 React + Redux 架构里面，表单处理永远都是一个痛点（这里安利一下自己写的 <a href="https://github.com/jasonslyvia/redux-form-utils" target="_blank" rel="noopener">redux-form-utils</a> 良心工具，线上产品背书）。但是等我真的在写 Component 中的 template 时，就开始慢慢崩溃了。</p><p>首先最崩溃的是，我没办法在模板中断点调试（还是我没有找到正确的方法），这导致在一些模板中存在复杂逻辑的场景我只能默默的在心里推倒运行的过程。</p><p>然后是在模板里面，你还是可以写大量的逻辑，比如这样</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">@Component(&#123;</span><br><span class="line">  template: <span class="string">`</span></span><br><span class="line"><span class="string">    &lt;ul #listEl [class.rendered]="listEl.rendered"&gt;</span></span><br><span class="line"><span class="string">      &lt;li *ngFor="#item of list; if(last) &#123;listEl.rendered = true;&#125;" (click)="list.push(Math.random()); $event.stopPropagation(); $event.preventDefault(); // Do other fancy stuff"&gt;</span></span><br><span class="line"><span class="string">        &#123;&#123; item.text &#125;&#125;</span></span><br><span class="line"><span class="string">      &lt;/li&gt;</span></span><br><span class="line"><span class="string">    &lt;/ul&gt;</span></span><br><span class="line"><span class="string">  `</span>,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>这些在模板中的代码都是完全合法的 Angular2 语法。有人可能觉得，允许你这么写不代表你一定要这么写。事实上，我已开始很多组件都是这么写的，因为很快，因为网上很多 demo 也这么写，因为不知道该怎么更好的组织代码。</p><p>这样下去，一个 Angular 组件中，你不仅要阅读组件本身的各种对数据的处理，还要看模板中的各种逻辑。尤其是一些比较复杂的组件，模板和组件本身放在不同的文件中时，这种来回上下文的切换真是酸爽。</p><h3 id="组件通信操碎了心"><a href="#组件通信操碎了心" class="headerlink" title="组件通信操碎了心"></a>组件通信操碎了心</h3><p>在 Angular 中，父组件想要传递一些信息给子组件，还是比较简单的，直接使用属性绑定即可。在子组件中稍微麻烦一点，多声明一个 @Input。</p><blockquote><p>这里还想吐槽两句，一个组件想要引用另一个组件，要先 import 进来，然后还要加到当前组件 Component 配置的 directives 属性中。开发过程中好多次莫名其妙的报错都是因为忘了这一步……</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Parent.js</span></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: <span class="string">'parent'</span>,</span><br><span class="line">  template: <span class="string">`</span></span><br><span class="line"><span class="string">    &lt;h1&gt;Parent&lt;/h1&gt;</span></span><br><span class="line"><span class="string">    &lt;child [data]="data"&gt;&lt;/child&gt;</span></span><br><span class="line"><span class="string">  `</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parent</span> </span>&#123;</span><br><span class="line">  data = <span class="string">'react'</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Child.js</span></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: <span class="string">'child'</span>,</span><br><span class="line">  template: <span class="string">`</span></span><br><span class="line"><span class="string">    &lt;h4&gt;Child&lt;/h4&gt;</span></span><br><span class="line"><span class="string">    &lt;p&gt;Data from parent: &#123;&#123; data &#125;&#125;</span></span><br><span class="line"><span class="string">  `</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Child</span> </span>&#123;</span><br><span class="line">  @Input() data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个时候问题来了，当子组件发生某些变化父组件想要知道的时候，你有这么几种选择。</p><ol><li>在子组件中定一个 @Output 属性，然后父组件用 (event)=”handler()” 的语法来监听这个事件</li><li>将父组件的一个 event handler 当做属性传给子组件，子组件通过调用这个方法来通知父组件（在 Redux 里，这很常见）</li><li>设计一个 Service 然后分别注入到父组件和子组件之中进行通信</li></ol><p>在我开发这个项目的时候，Angular2 的官方文档中还没有任何教程说明组件之间沟通该如何进行（现在<a href="https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-child" target="_blank" rel="noopener">有了</a>），所以我果断选择了最 Redux 的那种。</p><p>结果发现，后来更新的官方文档里明确说明了不提倡使用这样的方式。</p><p>我只能说很心累……</p><h3 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h3><p>奇葩的路由设定，用 <code>/...</code> 定义一个非最终页面路由，以及没有一个全局的路由结构，让 Angular2 里面的路由变得非常难推导。如果你不一层一层跟踪下去，根本就无法了解整个 Angular2 应用的路由结构。</p><p>莫名其妙的报错，而且很多错误都 Google 不到答案，只能 Google 到一群绝望的开发者提出绝望的问题。看看 Github 上 Angular Repo 里那些绝望的 issue，你就知道后悔当初自己为什么要选 Angular2 作为线上项目的架构了。我自己 Watch 了很多遇到的问题，然而收到最多的更新都是「+1」。</p><p>复杂的生命周期，官方文档看了一遍又一遍，网上的例子搜了很多，又不能确定是不是最新的 API。至今没有全部弄明白 Angular2 所有的生命周期都是干嘛用的，只用过几个简单的 OnInt、AfterViewInit 和 OnChanges。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>写着写着快要变成吐槽 Angular2 了，其实 Angular2 有很多设计的非常不错的地方值得其它框架学习。比如内置的 ViewEncapsulation 解决了 CSS 冲突的问题，完善的 NgForm 系列表单指令能够高效的解决表单校验和提交（前提是那些语法你都学会），底层设计与 DOM 无关以便运行在 WebWorker、服务器等不同环境等等。</p><p>不管怎么样，从项目开始的 Angular2 Alpha，用到现在的 Angular2 Beta 15，学习到了很多新知识，但学到更多的还是自己原来还有这么多不知道。</p><p>后面还是会投入更多的精力在 Redux 生态建设上，毕竟团队内 React + Redux 已经全面开花结果，有必要寻找到 Redux 最优的开发实践。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;博客停更了近 3 个月，实在是愧对很多在微博上推荐的同学。因为最近大部分时间都投入在公司里一个比较复杂的项目中，直到本周才算正式发布，稍得解脱。&lt;/p&gt;
&lt;p&gt;说这个项目复杂，不仅是因为需求设计复杂，更是因为在这个项目里我们使用了很多新技术 —— Angular2（是的，b
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>记一次使用 git bisect 快速定位 bug 的过程</title>
    <link href="https://undefinedblog.com/git-bisect/"/>
    <id>https://undefinedblog.com/git-bisect/</id>
    <published>2016-01-19T15:43:57.000Z</published>
    <updated>2016-01-19T16:01:28.000Z</updated>
    
    <content type="html"><![CDATA[<p>前一阵子跟三个同事一起合作开发了基于 Redux 的单页应用，我负责的部分完成的比较早，所有功能测试通过之后代码就没有改动过。</p><p>结果项目上线后不久接到反馈说我开发的某个功能突然用不了了，我自己一试果然不行。但是自己明明已经做过功能测试，甚至用户也试用过，怎么会突然用不了呢？</p><p>因为是一个单页应用，我开始怀疑是别人把我的代码搞坏了。</p><p>于是我尝试 checkout 到一个比较早的 commit，发现一切功能正常，所以肯定是这个 commit 一直到 HEAD 之间的某个（或某几个）commit 把功能搞坏了。</p><p>这个时候我想起了强大的 git bisect 功能。</p><p>不知道为什么，git 的官方文档总是写的让人感觉晦涩难懂，包括 <a href="https://git-scm.com/docs/git-bisect" target="_blank" rel="noopener">bisect 的文档</a>。于是经过一番 Google，我摸索到了核心的使用流程：</p><p>首先，尽可能找到第一个出现问题的 commit，记录其 hash 值；然后，尽可能找到最后一个正常的 commit，记录其 hash 值。</p><p>然后我们就开始了探案过程：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 开始 bisect</span></span><br><span class="line">$ git bisect start</span><br><span class="line"></span><br><span class="line"><span class="comment"># 录入正确的 commit</span></span><br><span class="line">$ git bisect good xxxxxx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 录入出错的 commit</span></span><br><span class="line">$ git bisect bad xxxxxx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 然后 git 开始在出错的 commit 与正确的 commit 之间开始二分查找，这个过程中你需要不断的验证你的应用是否正常</span></span><br><span class="line">$ git bisect bad</span><br><span class="line">$ git bisect good</span><br><span class="line">$ git bisect good</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="comment"># 直到定位到出错的 commit，退出 bisect</span></span><br><span class="line">$ git bisect reset</span><br></pre></td></tr></table></figure><p>简直是查 bug 小能手！</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;前一阵子跟三个同事一起合作开发了基于 Redux 的单页应用，我负责的部分完成的比较早，所有功能测试通过之后代码就没有改动过。&lt;/p&gt;
&lt;p&gt;结果项目上线后不久接到反馈说我开发的某个功能突然用不了了，我自己一试果然不行。但是自己明明已经做过功能测试，甚至用户也试用过，怎么会
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Lets Encrypt 花三分钟免费接入ssl证书</title>
    <link href="https://undefinedblog.com/lets-encrypt/"/>
    <id>https://undefinedblog.com/lets-encrypt/</id>
    <published>2016-01-02T21:58:24.000Z</published>
    <updated>2016-05-01T11:59:54.000Z</updated>
    
    <content type="html"><![CDATA[<p>2016-05-01 19:58:46 更新：最近很多朋友说我的 https 证书过期了，于是重新按照 DO 的<a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04" target="_blank" rel="noopener">这篇教程</a>重新配置了一遍。不得不说，DO 写教程的水平确实一流！建议移步学习。</p><hr><p>很久很久以前我还在当个人站长的时候，就一直琢磨着给网站买个 ssl 证书，这样用户访问的时候浏览器上的小绿锁能给人一种非常安全的印象。然而导致我最终没有上 https 的原因有：</p><ol><li>证书好贵 </li><li>https 会影响 SEO（据说） </li><li>懒（<del>这才是真正的原因</del>）</li></ol><p>直到前段时间发现了 <a href="https://letsencrypt.org/" target="_blank" rel="noopener">Lets Encrypt 项目</a>，一个倡导互联网上所有网站都该使用 https 的组织，提供免费的 ssl 证书服务。</p><p><img src="http://ww3.sinaimg.cn/bmiddle/831e9385gw1ezlvzdzwsoj20a70fb75d.jpg" alt="ssl certificate price in godaddy"></p><p>上图是 GoDaddy 的 ssl 证书报价，换句话说，Lets Encrypt 免费送你价值 62.99 刀的证书。</p><p>当然，这里提到价格并不是为了让大家薅羊毛，而是为了体现 Lets Encrypt 组织的高尚目标。希望看到本文的同学，能够享受免费 ssl 证书带来了安全和稳定，而不是产生牟利等不当想法。</p><p>使用的方法也很简单，以 nginx 为例，直接使用 Github 上 xdtianyu 同学提供的<a href="https://github.com/xdtianyu/scripts/tree/master/lets-encrypt" target="_blank" rel="noopener">脚本</a>即可。</p><h2 id="操作步骤"><a href="#操作步骤" class="headerlink" title="操作步骤"></a>操作步骤</h2><p>切换到你希望保存证书及私钥的目录，我选择当前用户的 home，即 <code>~</code>。</p><blockquote><p>下文中所有 <code>/path/to/</code> 请替换成你服务器上的实际路径；<code>your_domain</code> 同理替换成你自己的域名</p></blockquote><p>下载脚本及配置文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~</span><br><span class="line">wget https://raw.githubusercontent.com/xdtianyu/scripts/master/lets-encrypt/letsencrypt.conf</span><br><span class="line">wget https://raw.githubusercontent.com/xdtianyu/scripts/master/lets-encrypt/letsencrypt.sh</span><br><span class="line">chmod +x letsencrypt.sh</span><br></pre></td></tr></table></figure><p>编辑配置文件，将第2、3、4行中的 Example.com 替换成你自己的域名</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim letsencrypt.conf</span><br></pre></td></tr></table></figure><p>执行 letsencrypt 脚本，获取证书及私钥</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./letsencrypt.sh ./letsencrypt.conf</span><br></pre></td></tr></table></figure><p>添加 nginx alias</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">  ...</span><br><span class="line"></span><br><span class="line">  location /.well-known/ &#123;</span><br><span class="line">    <span class="built_in">alias</span> /path/to/.well-known/;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改 nginx 配置，加载私钥和证书<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">   listen 443 ssl;</span><br><span class="line">   ssl_certificate /path/to/your_domain.chained.crt;</span><br><span class="line">   ssl_certificate_key /path/to/your_domain.com.key;</span><br><span class="line"></span><br><span class="line">   ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>全部操作完成后，重启 nginx 即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">service nginx restart</span><br><span class="line"><span class="comment">#或 </span></span><br><span class="line">/etc/init.d/nginx -s reload</span><br></pre></td></tr></table></figure><h2 id="额外配置"><a href="#额外配置" class="headerlink" title="额外配置"></a>额外配置</h2><p>首先，Lets Encrypt 的证书需要每月更新，因此你需要一个 cron 命令定时运行上述脚本</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0 0 1 * * ~/letsencrypt.sh ~/letsencrypt.conf &gt;&gt; /var/<span class="built_in">log</span>/lets-encrypt.log 2&gt;&amp;1</span><br></pre></td></tr></table></figure><p>其次，如果你希望重定向所有的非 https 到 https 访问，可以在 nginx 配置中添加如下 server block</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    server_name your_domain.com;</span><br><span class="line">    <span class="built_in">return</span> https://your_domain.com<span class="variable">$request_uri</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>总的来说，Lets Encrypt 所倡导免费及普遍的 ssl 证书有利于整个互联网环境的安全化。对于某些靠流量劫持广告的 ISP 来说，苦日子要来了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;2016-05-01 19:58:46 更新：最近很多朋友说我的 https 证书过期了，于是重新按照 DO 的&lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-wit
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Redux 在实践中的一些问题及思考</title>
    <link href="https://undefinedblog.com/some-thoughts-in-redux/"/>
    <id>https://undefinedblog.com/some-thoughts-in-redux/</id>
    <published>2015-12-12T19:04:31.000Z</published>
    <updated>2015-12-12T20:35:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>React 绝对是 2015 年前端领域的关键词，基于 React 的 Flux 架构也被越来越多的人所熟识。然而 Flux 作为一套架构思想而不是框架让许多开发者在实践中摸不着头脑，因此社区里也诞生了很多基于 Flux 的「轮子」。而今天要说的就是其中最火、逼格最高的轮子 —— Redux。</p><p>本文不是一篇介绍 Redux 的入门文章（如果大家有兴趣的话，后面可以写写），因此阅读本文至少需要以下几点基础：</p><ol><li><a href="http://facebook.github.io/react/" target="_blank" rel="noopener">React</a>（熟悉）</li><li><a href="http://facebook.github.io/flux/" target="_blank" rel="noopener">Flux</a>（了解）</li><li><a href="https://github.com/rackt/redux" target="_blank" rel="noopener">Redux</a>（了解）</li><li><a href="https://github.com/lukehoban/es6features" target="_blank" rel="noopener">ES 6</a>（了解）</li></ol><p>既然不是基础，那么本文着重要讲的是什么呢？其实是我在实际业务中使用 Redux 遇到的一些问题及我自己的思考。促使我写这篇文章的另一个原因是，知乎上有一位朋友私信咨询了我一些 Redux 相关的问题，让我了解到原来我遇到的问题大家也会有疑惑，因此总结成文，方便后人参考。</p><p>本文将会用类似 cookbook 的形式，通过一问一答来尝试解释 Redux 在实际应用中的问题。</p><h3 id="问题一：一个-action-被-reducer1-处理完之后，希望-reducer2-也对这个-action-做出响应，该怎么处理？"><a href="#问题一：一个-action-被-reducer1-处理完之后，希望-reducer2-也对这个-action-做出响应，该怎么处理？" class="headerlink" title="问题一：一个 action 被 reducer1 处理完之后，希望 reducer2 也对这个 action 做出响应，该怎么处理？"></a>问题一：一个 action 被 reducer1 处理完之后，希望 reducer2 也对这个 action 做出响应，该怎么处理？</h3><p>这个问题其实要从两个方向考虑：即 reducer1 和 reducer2 对某个 action 的响应是否有先后顺序的限制。</p><p>若没有，则在 reducer1 和 reducer2 的 <code>swtich..case..</code> 中针对同一个 <code>ACTION_TYPE</code> 做出处理即可。至于 <code>ACTION_TYPE</code>，则可以在触发这个 action 的 actionCreator.js 里 export 出来。如</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// actionCreator.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> SOME_TYPE = <span class="string">'SOME_ACTION_TYPE'</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">doSomeAction</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    type: SOME_TYPE,</span><br><span class="line">    payload: <span class="number">123</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// reducer1.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; SOME_TYPE &#125; <span class="keyword">from</span> <span class="string">'./actionCreator'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">reducer</span>(<span class="params">state, action</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">switch</span>(action.type) &#123;</span><br><span class="line">    <span class="keyword">case</span> SOME_TYPE: &#123;</span><br><span class="line">      <span class="comment">// 这这个 action 做出响应</span></span><br><span class="line">    &#125;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// reducer2.js 同 reducer1.js</span></span><br></pre></td></tr></table></figure><p>若有先后顺序限制，则比较复杂。我们站在整个应用的角度考虑，其实我们的需求是当触发一个 action 时，希望 reducer1 先响应这个 action 更新 state，然后 reducer2 再响应这个 action，也许还需要基于 reducer1 中最新的 store 来更新自己的 state。</p><p>这个时候，可以用我自己写的一个 Redux 中间件 —— <a href="https://github.com/jasonslyvia/redux-sequence-action" target="_blank" rel="noopener">redux-combine-action</a>。使用这种中间件后在 actionCreator 中的逻辑大概是这样的：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// actionCreator.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">doSomeAction</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> [</span><br><span class="line">    (getState, dispatch) =&gt; &#123;</span><br><span class="line">      dispatch(&#123;</span><br><span class="line">        type: ACTION_TYPE_1,</span><br><span class="line">        payload: <span class="number">123</span></span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">    (getState, dispatch) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> state = getState();</span><br><span class="line">      dispatch(&#123;</span><br><span class="line">        type: ACTION_TYPE_2,</span><br><span class="line">        payload: state.some.useful.data.from.state</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  ];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到我们在 actionCreator 中返回了一个函数数组，每个函数都获得了 getState 和 dispatch 方法。在上述代码中，首先会 dispatch 一个 <code>ACTION_TYPE_1</code> 的 action，假设会被 reducer1 处理，然后会 dispatch 一个 <code>ACTION_TYPE_2</code> 类型的 action，则会被 reducer2 处理。</p><p>对于 reducer 来说，并不关系你的业务逻辑是先处理哪一个后处理哪一个，它们只是单纯的响应每一个 action 而已。</p><p>至于其中的原理，可以自行查看源代码。（写到这的时候，自觉滚去把这个库的测试补上了）需要说明的是，这个中间件的核心思想基本是参考自 <a href="https://github.com/itsmepetrov/redux-combine-actions" target="_blank" rel="noopener">redux-combine-actions</a> 库。</p><h3 id="问题二：Redux-中如何实现一个公共业务组件？"><a href="#问题二：Redux-中如何实现一个公共业务组件？" class="headerlink" title="问题二：Redux 中如何实现一个公共业务组件？"></a>问题二：Redux 中如何实现一个公共业务组件？</h3><p>一个所谓的「公共业务组件」，应该包含了 UI 和数据两部分。比如淘宝上的收货地址选择控件，应该就包含了三个 <code>select</code> 选择器和对应的省市区数据。这样一个地址选择控件的逻辑在各个模块中都是一致的，但是怎么设计才能实现复用呢？</p><blockquote><p>一个关于 Flux/Redux 的基础概念：任何一个 actionCreator 触发的 action 都会通知到所有的 store/reducer。</p></blockquote><p>考虑一种极端情况，页面上同时有两个模块都使用了这个公共的地址选择组件。当在这个组件里选择了一个省份时，会 dispatch 一个 <code>SELECT_PROVINCE</code> 的 action，那么两个模块怎么知道用户到底是选择了哪个模块里公共组件的省份呢？</p><p>再考虑另外一个问题，当用户选择了一个省份后，某一个业务模块希望能够再做一些别的数据处理（比如根据当前选择的省份加载对应的热销宝贝）该怎么操作呢？总不能在 reducer 中 dispatch 一个 action 吧？（<strong>注意，这是 anti-pattern，不要在 reducer 中 dispatch action！！</strong>）</p><p>其实这两个问题的解决思路是类似的，就是在公共组件中，数据传递的过程中加上更多的限制，我能想到的有这么两种解决方案：</p><ul><li>公共组件自己维护状态，业务模块通过给公共组件传入回调来获得公共组件的数据变动，再 dispatch 响应的 action 更新自己的 state。这样设计的问题在于，违背了 Redux 倡导的无状态原则，公共组件内部存在了 state 和 setState 操作。</li><li>通过 props 传入指定的 actionType 或 actionCreator（传入 actionCreator 是我同事奇阳的想法）来让公共组件 dispatch 不同的 action，以此达到区分的效果。</li></ul><p>一个简单的例子：<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 公共模块自己定义的 actionCreator</span></span><br><span class="line"><span class="keyword">import</span> &#123; provinceActionCreator &#125; <span class="keyword">from</span> <span class="string">'./actionCreator'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一个公共的地址选择组件</span></span><br><span class="line"><span class="keyword">const</span> AddressPicker = React.createClass(&#123;</span><br><span class="line">  handleChangeProvince(e) &#123;</span><br><span class="line">    <span class="comment">// 优先使用 props 中传入的 actionCreator</span></span><br><span class="line">    <span class="keyword">const</span> provinceActionCreator = <span class="keyword">this</span>.props.provinceActionCreator || provinceActionCreator;</span><br><span class="line">    <span class="comment">// dispatch 一个选择省份的 action</span></span><br><span class="line">    <span class="keyword">this</span>.props.dispatch(provinceActionCreator(e.target.value));</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;select onChange=&#123;<span class="keyword">this</span>.handleChangeProvince&#125;&gt;</span><br><span class="line">        &lt;option&gt;北京&lt;<span class="regexp">/option&gt;</span></span><br><span class="line"><span class="regexp">        &lt;option&gt;浙江&lt;/</span>option&gt;</span><br><span class="line">      &lt;<span class="regexp">/select&gt;</span></span><br><span class="line"><span class="regexp">    );</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;);</span></span><br></pre></td></tr></table></figure></p><h3 id="问题三：Redux-中跟路由有关的状态应该怎么维护？"><a href="#问题三：Redux-中跟路由有关的状态应该怎么维护？" class="headerlink" title="问题三：Redux 中跟路由有关的状态应该怎么维护？"></a>问题三：Redux 中跟路由有关的状态应该怎么维护？</h3><p>现在用 React 做路由的话大家基本都会选择 react-router（这个库我已经吐槽过无数次了，经常改 API），而用 Redux 的话自然也是选择基于 react-router 封装的 redux-router。</p><blockquote><p>在讲详细的问题之前，先要阐明一个观点，即「路由也是应用状态的一部分」，应该也的确有一个单独的 store 在维护它。</p></blockquote><p>既然有一个单独的 store，那么其中的状态就能轻松的获取和设置了，具体的操作方法可以参考 redux-router 中的 API。</p><p>如果抛开 redux-router 不谈，react-router 中针对路由切换时的数据传递提供了一种全新的思路，即在切换路由时，将「state」存入 session-storage 中，借用这个功能你就可以轻松的实现「回退」操作了。</p><p>详细说明见 <a href="https://github.com/rackt/history/blob/master/docs/Glossary.md#locationstate" target="_blank" rel="noopener">history 模块的文档</a>。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>总的来说，使用 Redux 开发业务功能还是很爽的，尤其是对于数据流动复杂的应用，单一的数据流动、类 Immutable 的数据变动方式以及酷炫的 devtool 这些特性简直堪称神器。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;React 绝对是 2015 年前端领域的关键词，基于 React 的 Flux 架构也被越来越多的人所熟识。然而 Flux 作为一套架构思想而不是框架让许多开发者在实践中摸不着头脑，因此社区里也诞生了很多基于 Flux 的「轮子」。而今天要说的就是其中最火、逼格最高的轮子
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>setState 之后发生了什么 —— 浅谈 React 中的 Transaction</title>
    <link href="https://undefinedblog.com/what-happened-after-set-state/"/>
    <id>https://undefinedblog.com/what-happened-after-set-state/</id>
    <published>2015-10-25T17:28:22.000Z</published>
    <updated>2015-10-25T17:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文系对 <a href="http://undefinedblog.com/understand-react-batch-update/">深入理解 React 的 batchUpdate 机制</a> 的更新，根据 React v0.14 版源码添加并修改了部分内容。同时增加了一张看起来并不容易理解的示意图。</p></blockquote><p>之前在我的博客里有篇文章写了「<a href="http://undefinedblog.com/why-react-is-so-fast/">为什么 React 这么快</a>」，其中说到一点很重要的特性就是 <code>batchUpdate</code>。今天就简单的分析一下 batchUpdate 究竟是怎么实现的？</p><p>首先明确一个基础概念，在调用 this.setState 之后，React 会自动重新调用 render 方法。但是如果我们多次调用 setState 方法呢？考虑下面的代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Example = React.createClass(&#123;</span><br><span class="line">  getInitialState: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      clicked: <span class="number">0</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  handleClick: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.setState(&#123;<span class="attr">clicked</span>: <span class="keyword">this</span>.state.clicked + <span class="number">1</span>&#125;);</span><br><span class="line">    <span class="keyword">this</span>.setState(&#123;<span class="attr">clicked</span>: <span class="keyword">this</span>.state.clicked + <span class="number">1</span>&#125;);</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  render: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &lt;button onClick=&#123;this.handleClick&#125;&gt;&#123;this.state.clicked&#125;&lt;/button&gt;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>考虑这两个问题，当点击 button 时：1、 render 方法被调用了几次？2、 handleClick 中第 2 次 setState 时 this.state.clicked 值是多少？</p><p>如果你还没有把握，可以先打开这个 <a href="http://jsbin.com/zowuve/1/edit?js,console,output" target="_blank" rel="noopener">DEMO</a> 查看结果。</p><p>知道结果后，你能解释为什么 render 方法只被调用了 1 次，且第二次 setState 时 this.state.clicked 依然是 0 么？</p><p>这一切，都要归功于 React 的 batchUpdate 设计，但是 batchUpdate 究竟是怎么实现的呢？让我们深入到 React 的源码中一探究竟。</p><blockquote><p>以下内容由于本人能力及精力均有限，尚未深究，仅供参考。如有错误，尽请斧正！源码分析基于 React v0.14 版。</p></blockquote><p>一个简单的 setState 方法，其简化的调用栈如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">this.setState                                           // ReactComponent.js</span><br><span class="line">...</span><br><span class="line">this.updater.enqueueSetState                            // ReactUpdateQueue.js</span><br><span class="line">...</span><br><span class="line">获取当前组件的 pendingStateQueue，并将新的 state push 进去 // ReactUpdateQueue.js</span><br><span class="line">...</span><br><span class="line">enqueueUpdate                                           // ReactUpdates.js</span><br><span class="line">...</span><br><span class="line">if(当前不在一次 batchUpdate 的过程中)                      // ReactUpates.js</span><br><span class="line">  执行 batchingStreategy.batchUpdates 方法</span><br><span class="line">else</span><br><span class="line">  将当前 component 存入 dirtyComponents 数组中</span><br><span class="line">...</span><br><span class="line">if (setState 方法存在 callback)                          // ReactComponent.js</span><br><span class="line">  调用 this.updater.enqueueCallback 将 callback 存入队列中</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>一次 setState 的过程大概就是这样，那么问题来了，setState 只是把变化存入一个临时队列中，那么新的 state 究竟是如何生效的呢？</p><p>让我们从 DOM 事件触发开发对整个链路进行一下梳理：</p><p>当一次 DOM 事件触发后，ReactEventListener.dispatchEvent 方法会被调用。而这个方法并不是急着去调用我们在 JSX 中指定的各种回调，而是调用了</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ReactUpdates.batchedUpdates</span><br></pre></td></tr></table></figure><p>该方法会执行一个 transaction，而要执行的内容被定义在 handleTopLevelImpl 中。再看 handleTopLevelImpl，其核心内容就是找到事件对应的所有节点并依次对这些节点 trigger 事件。</p><p>在进一步介绍之前，我们需要先了解一下 Transaction（事务）。</p><p>React 为 Transaction 封装了一个简单的 Mixin，并在源码中生动的介绍了一个 Transaction 是怎么工作的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 详见 Transaction.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *                       wrappers (injected at creation time)</span></span><br><span class="line"><span class="comment"> *                                      +        +</span></span><br><span class="line"><span class="comment"> *                                      |        |</span></span><br><span class="line"><span class="comment"> *                    +-------------------------------------+</span></span><br><span class="line"><span class="comment"> *                    |                 v        |              |</span></span><br><span class="line"><span class="comment"> *                    |      +---------------+   |              |</span></span><br><span class="line"><span class="comment"> *                    |   +--|    wrapper1   -----+         |</span></span><br><span class="line"><span class="comment"> *                    |   |  +---------------+   v    |         |</span></span><br><span class="line"><span class="comment"> *                    |   |          +-------------+  |         |</span></span><br><span class="line"><span class="comment"> *                    |   |     +----|   wrapper2  -------+   |</span></span><br><span class="line"><span class="comment"> *                    |   |     |    +-------------+  |     |   |</span></span><br><span class="line"><span class="comment"> *                    |   |     |                     |     |   |</span></span><br><span class="line"><span class="comment"> *                    |   v     v                     v     v   | wrapper</span></span><br><span class="line"><span class="comment"> *                    | +---+ +---+   +---------+   +---+ +---+ | invariants</span></span><br><span class="line"><span class="comment"> * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained</span></span><br><span class="line"><span class="comment"> * +-----------------&gt;-----&gt;|anyMethod-------------&gt;</span></span><br><span class="line"><span class="comment"> *                    | |   | |   |   |         |   |   | |   | |</span></span><br><span class="line"><span class="comment"> *                    | |   | |   |   |         |   |   | |   | |</span></span><br><span class="line"><span class="comment"> *                    | |   | |   |   |         |   |   | |   | |</span></span><br><span class="line"><span class="comment"> *                    | +---+ +---+   +---------+   +---+ +---+ |</span></span><br><span class="line"><span class="comment"> *                    |  initialize                    close    |</span></span><br><span class="line"><span class="comment"> *                    +-----------------------------------------+</span></span><br><span class="line"><span class="comment"> **/</span></span><br></pre></td></tr></table></figure><p>实际上，Transacation 就是给需要执行的函数封装了两个 wrapper，每个 wrapper 都有 initialize 和 close 方法。当一个 transaction 需要执行（perform）的时候，会先调用对应的 initialize 方法。同样的，当一个 transaction 执行完成后，会调用对应的 close 方法。</p><p>回到刚才的问题，存入临时队列的 state 究竟是怎么被改变成真正的状态的呢？秘密就在 <code>ReactUpdates.batchedUpdates</code> 调用中（还记得它吗？DOM 事件的回调中被调用的方法）。这个方法实际上就是执行了一个 transaction，具体实现定义在 ReactDefaultBatchingStrategy.js 中。</p><p>这个 transaction 具体的代码就不贴了，简单的说，它定义了一个事务，当这个事务结束时，需要调用 ReactUpdates.flushBatchedUpdates 方法。</p><p>好，说到这里可能大家已经被各种名称搞昏了头，下面给大家看一个简单的流程图。</p><p><img src="http://ww1.sinaimg.cn/large/831e9385gw1exdvqnke7nj21be0fwtc9.jpg" alt="react transaction 图例"></p><p>上面的流程图中只保留了部分核心的过程，看到这里大家应该明白了，所有的 batchUpdate 功能都是通过执行各种 transaction 实现的。this.setState 调用后，新的 state 并没有马上生效，而是通过 ReactUpdates.batchedUpdate 方法存入临时队列中。当一个 transaction 完成后，才通过 ReactUpdates.flushBatchedUpdates 方法将所有的临时 state merge 并计算出最新的 props 及 state。</p><p>纵观 React 源码，使用 Transaction 之处非常之多，React 源码注释中也列举了很多可以使用 Transaction 的地方，比如</p><ul><li>在一次 DOM reconciliation（调和，即 state 改变导致 Virtual DOM 改变，计算真实 DOM 该如何改变的过程）的前后，保证 input 中选中的文字范围（range）不发生变化</li><li>当 DOM 节点发生重新排列时禁用事件，以确保不会触发多余的 blur/focus 事件。同时可以确保 DOM 重拍完成后事件系统恢复启用状态。</li><li>当 worker thread 的 DOM reconciliation 计算完成后，由 main thread 来更新整个 UI</li><li>在渲染完新的内容后调用所有 <code>componentDidUpdate</code> 的回调</li><li>等等</li></ul><p>值得一提的是，React 还将 batchUpdate 方法暴露了出来：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> batchedUpdates = <span class="built_in">require</span>(<span class="string">'react-dom'</span>).unstable_batchedUpdates;</span><br></pre></td></tr></table></figure><p>当你需要在一些非 DOM 事件回调的函数中多次调用 setState 等方法时，可以将你的逻辑封装后调用 batchedUpdates 执行，以此保证 render 方法不会被多次调用。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;本文系对 &lt;a href=&quot;http://undefinedblog.com/understand-react-batch-update/&quot;&gt;深入理解 React 的 batchUpdate 机制&lt;/a&gt; 的更新，根据 React v0.14 版源
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>React v0.14 概览</title>
    <link href="https://undefinedblog.com/react-v0-14/"/>
    <id>https://undefinedblog.com/react-v0-14/</id>
    <published>2015-10-07T18:21:24.000Z</published>
    <updated>2015-10-07T19:27:25.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>基本上本文就是对 <a href="http://facebook.github.io/react/blog/2015/10/07/react-v0.14.html" target="_blank" rel="noopener">React 官方 v0.14 博文</a> 的翻译加上一小部分个人理解。</p></blockquote><p>正文开始之前先讲个笑话，随着 React 的风靡许多基于 React 的衍生库也火得一塌糊涂，比如 <a href="https://github.com/rackt/react-router" target="_blank" rel="noopener">React Router</a>。如果没记错的话 React Router 的作者说过版本号永远和 React 保持一致，从 v0.10 开始确实如此。然后 v0.11，v0.12，v0.13，眼看着时间已经过去了一年多……怎么着也该出 v1.0 了吧，结果 React 发布了 v0.14，而且还规划了 v0.15，让 React Router 的若干个 v1.0-beta 版本哭瞎在厕所。</p><p><img src="http://ww3.sinaimg.cn/bmiddle/831e9385gw1ewt4ti4nevj20c50kqq4s.jpg" alt="react router github repo"></p><p>好了，回到 React v0.14 上。</p><h3 id="React-「一分为二」"><a href="#React-「一分为二」" class="headerlink" title="React 「一分为二」"></a>React 「一分为二」</h3><p>原本的 <code>react</code> package 被拆分为 <code>react</code> 及 <code>react-dom</code> 两个 package。其中 <code>react</code> package 中包含 <code>React.createElement</code>、 <code>.createClass</code>、 <code>.Component</code>， <code>.PropTypes</code>， <code>.Children</code> 这些 API，而 <code>react-dom</code> package 中包含 <code>ReactDOM.render</code>、 <code>.unmountComponentAtNode</code>、 <code>.findDOMNode</code>。</p><p>原本在服务端渲染用的两个 API <code>.renderToString</code> 和 <code>.renderToStaticMarkup</code> 被放在了 <code>react-dom/server</code> 中。</p><p>改变之后的结构，一个基本的 React 组件变成了这样：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> React = <span class="built_in">require</span>(<span class="string">'react'</span>);</span><br><span class="line"><span class="keyword">var</span> ReactDOM = <span class="built_in">require</span>(<span class="string">'react-dom'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> MyComponent = React.createClass(&#123;</span><br><span class="line">  render: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &lt;div&gt;Hello World&lt;/div&gt;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">ReactDOM.render(&lt;MyComponent /&gt;, node);</span><br></pre></td></tr></table></figure><p>此外，原本 <code>React.addons</code> 下面的工具全部变成了独立的 package：</p><ul><li>react-addons-clone-with-props</li><li>react-addons-create-fragment</li><li>react-addons-css-transition-group</li><li>react-addons-linked-state-mixin</li><li>react-addons-perf</li><li>react-addons-pure-render-mixin</li><li>react-addons-shallow-compare</li><li>react-addons-test-utils</li><li>react-addons-transition-group</li><li>react-addons-update</li><li>ReactDOM.unstable_batchedUpdates （在 <code>react-dom</code> 中）</li></ul><p>当然，原本的 API 在 v0.14 版中仍然可以使用，只不过会有 warning，最终会在 v0.15 版的时候完全移除。</p><h3 id="refs-变成了真正的-DOM-节点"><a href="#refs-变成了真正的-DOM-节点" class="headerlink" title="refs 变成了真正的 DOM 节点"></a>refs 变成了真正的 DOM 节点</h3><p>当我们需要获取 React 组件上某个 DOM 节点时，React 提供了 refs 方法方便我们快速引用。为了方便我们使用，React 还「贴心」地对 refs 做了一层封装，使用 <code>this.refs.xxx.getDOMNode()</code> 或 <code>React.findDOMNode(this.refs.xxx)</code> 可以获取到真正的 DOM 节点。</p><p>结果发现大家真正需要的就是 DOM 节点本身，封装了半天完全是浪费感情。</p><p>于是在 v0.14 版中 refs 指向的就是 DOM 节点，同时也会保留 <code>.getDOMNode()</code> 方法（带 warning），最终在 v0.15 版中去除该方法。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Zoo = React.createClass(&#123;</span><br><span class="line">  render: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &lt;div&gt;Giraffe name: &lt;input ref="giraffe" /&gt;&lt;/div&gt;;</span><br><span class="line">  &#125;,</span><br><span class="line">  showName: function() &#123;</span><br><span class="line">    // 之前：</span><br><span class="line">    // var input = this.refs.giraffe.getDOMNode();</span><br><span class="line">    //</span><br><span class="line">    // v0.14 版：</span><br><span class="line">    var input = this.refs.giraffe;</span><br><span class="line">    alert(input.value);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>需要注意的是，如果你给自定义的 React 组件（除了 DOM 自带的标签，如 <code>div</code>、<code>p</code> 等）添加 refs，表现和行为与之前一致。</p><h3 id="无状态的函数式组件"><a href="#无状态的函数式组件" class="headerlink" title="无状态的函数式组件"></a>无状态的函数式组件</h3><p>其实在实际业务系统中使用 React 时，我们会写很多只有 <code>render</code> 方法的 React 组件。为了减少冗余的代码量，React v0.14 中引入了 <code>无状态的函数式组件（Stateless functional components）</code> 的概念。先看看长啥样：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一个 ES6 箭头函数定义的无状态函数式组件</span></span><br><span class="line"><span class="keyword">var</span> Aquarium = <span class="function">(<span class="params">props</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">var</span> fish = getFish(props.species);</span><br><span class="line">  <span class="keyword">return</span> &lt;Tank&gt;&#123;fish&#125;&lt;/Tank&gt;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或者更加简化的版本</span></span><br><span class="line"><span class="keyword">var</span> Aquarium = <span class="function">(<span class="params">&#123;species&#125;</span>) =&gt;</span> (</span><br><span class="line">  &lt;Tank&gt;</span><br><span class="line">    &#123;getFish(species)&#125;</span><br><span class="line">  &lt;<span class="regexp">/Tank&gt;</span></span><br><span class="line"><span class="regexp">);</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/ 最终使用方式: &lt;Aquarium species="rainbowfish" /</span>&gt;</span><br></pre></td></tr></table></figure><p>可以看到，没有 <code>React.createClass</code>，也没有显式的 <code>render</code>，写起来更加轻松了。</p><p>当然，新语法也有需要注意的地方：</p><ol><li>没有任何生命周期方法，如 <code>componentDidMount</code> 等</li><li>不能添加 refs</li><li>可以通过给函数添加属性定义 <code>propTypes</code> 和 <code>defaultProps</code></li></ol><h3 id="react-tools-及-JSXTransformer-js-已弃用"><a href="#react-tools-及-JSXTransformer-js-已弃用" class="headerlink" title="react-tools 及 JSXTransformer.js 已弃用"></a>react-tools 及 JSXTransformer.js 已弃用</h3><p>拥抱 Babel 吧同学们！</p><h3 id="编译器优化"><a href="#编译器优化" class="headerlink" title="编译器优化"></a>编译器优化</h3><p>在 Babel 5.8.23 及更新的版本中，新增了两项专门针对 React 的优化配置，<strong>仅推荐在生产环境中开启</strong>，因为优化后会导致代码的报错更加扑朔迷离（本来报错就已经很难定位了……）。</p><ul><li><code>optimisation.react.inlineElements</code> 将 JSX 元素转换为对象而非使用 <code>React.createElement</code></li><li><code>optimisation.react.constantElements</code> 针对拥有完全静态子树的组件，将其创建过程提升到顶层（Top level），从而减少对 <code>React.createElement</code> 方法的调用</li></ul><h3 id="其它变化"><a href="#其它变化" class="headerlink" title="其它变化"></a>其它变化</h3><ul><li><code>React.initializeTouchEvents</code> 已弃用</li><li>由于 refs 的相关变化（见上文），<code>TestUtils.findAllInRenderedTree</code> 及相关的方法不再接受 DOM 组件作为参数，只能传入自定义的 React 组件</li><li><code>props</code> 一旦创建永远不可修改，因此 <code>.setProps</code> 及 <code>.replaceProps</code> 已废弃</li><li><code>children</code> 不可以传对象类型，推荐传入数组，或使用 <code>React.createFragment</code> 方法（其实就是转换为了数组）</li><li><code>React.addons.classSet</code> 已经移除，使用 <code>classnames</code> package 替代</li></ul><h3 id="将要发生的改变"><a href="#将要发生的改变" class="headerlink" title="将要发生的改变"></a>将要发生的改变</h3><p>在 v0.15 版中，下列内容将会发生改变：</p><ul><li><code>this.getDOMNode()</code> 方法将会废弃，推荐使用 <code>React.findDOMNode()</code></li><li><code>setProps</code> 及 <code>replaceProps</code> 将会废弃</li><li><code>React.addons.cloneWithProps</code> 已废弃，推荐使用 <code>React.cloneElements</code>，新方法不会自动 merge <code>className</code> 及 <code>style</code></li><li><code>React.addons.CSSTransitionGroup</code> 将不再监听 transition 事件，因此使用者需要显式指定动画的 timeout，如：<code>transitionEnterTimeout={500}</code>。</li><li>ES6 组件类必须 extends React.Component（如果使用 React.createClass 语法则不受影响）</li><li>在多次 render 中重用并改变 style 对象已经被弃用（这一点不是太明白，中心思想貌似是不要 mutate object？）</li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;基本上本文就是对 &lt;a href=&quot;http://facebook.github.io/react/blog/2015/10/07/react-v0.14.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React 官方 
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>再谈 React Router 使用方法</title>
    <link href="https://undefinedblog.com/react-router-0-13-3/"/>
    <id>https://undefinedblog.com/react-router-0-13-3/</id>
    <published>2015-08-18T13:17:10.000Z</published>
    <updated>2015-08-18T16:26:48.000Z</updated>
    
    <content type="html"><![CDATA[<p>去年 9 月份写了一篇 <a href="http://undefinedblog.com/react-router/">ReactRouter 使用指南</a>，不小心在百度搜索「react-router」关键词排到了第一名。最近收到很多同学反馈说这篇文章里的例子挂了让我补一下。</p><p>其实例子里的代码已经很老了，React Router 的 API 也发生了很多变化。因此今天抽出一晚上的时间，再以最新的 React Router 稳定版（截止 2015年08月18日21:23:40 为 v0.13.3 版，与 React 版本号一致）为基础讲讲如何使用 React Router。</p><blockquote><p>阅读本文需要你有一定的 ReactJS 基础，如果你正在寻找 ReactJS 中文入门教程，推荐我参与翻译的 <a href="http://book.douban.com/subject/26378583/" target="_blank" rel="noopener">React - 引领未来的用户界面开发框架</a> 一书。</p></blockquote><h2 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h2><p>一个最基本的页面，菜单有「图书」和「电影」两个菜单项，点击「图书」显示图书列表（链接变为/books），点击「电影」显示电影列表（链接变为/movies）。</p><p><a href="http://jsbin.com/duduta/14/edit?js,output" target="_blank" rel="noopener">Demo</a></p><p>说实话，这个例子并不简单。下面逐步分析一下用到的代码和它们分别是干什么的。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Router = ReactRouter; <span class="comment">// 由于是html直接引用的库，所以 ReactRouter 是以全局变量的形式挂在 window 上</span></span><br><span class="line"><span class="keyword">var</span> Route = ReactRouter.Route; </span><br><span class="line"><span class="keyword">var</span> RouteHandler = ReactRouter.RouteHandler;</span><br><span class="line"><span class="keyword">var</span> Link = ReactRouter.Link;</span><br><span class="line"><span class="keyword">var</span> StateMixin = ReactRouter.State;</span><br></pre></td></tr></table></figure><p>由于 Demo 需要直接从网页上引用 React 和 React Router，因此它们都被挂在了 window 对象上（现在但凡有点追求的前端都上 webpack 了，但是例子的话大家就将就着看吧）。这几行就是获取 ReactRouter 提供的几个组件和 mixin。</p><p>接下来声明了 4 个组件，都是最基本的只有 render 方法的 React 组件，分别是：<code>Movies 电影列表</code>、<code>Movie 电影详情</code>、<code>Books 图书列表</code>、<code>Book 图书详情</code>。</p><p>关于组件唯一需要说明的是用到了 ReactRouter 提供的 <code>State</code> 这个 mixin，主要功能就是让组件能够通过 this.getParams() 或 this.getQuery() 等方法获取到当前路由的各种值或参数。</p><p>然后是应用的入口，也是我们渲染菜单的地方：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 应用入口</span></span><br><span class="line"><span class="keyword">var</span> App = React.createClass(&#123;</span><br><span class="line">  render: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;div className=<span class="string">"app"</span>&gt;</span><br><span class="line">        &lt;nav&gt;</span><br><span class="line">          &lt;Link to=<span class="string">"movies"</span>&gt;电影&lt;<span class="regexp">/Link&gt;</span></span><br><span class="line"><span class="regexp">          &lt;Link to="books"&gt;图书&lt;/</span>Link&gt;</span><br><span class="line">        &lt;<span class="regexp">/nav&gt;</span></span><br><span class="line"><span class="regexp">        &lt;section&gt;</span></span><br><span class="line"><span class="regexp">          &lt;RouteHandler /</span>&gt;</span><br><span class="line">        &lt;<span class="regexp">/section&gt;</span></span><br><span class="line"><span class="regexp">      &lt;/</span>div&gt;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这里用到了两个 ReactRouter 提供的组件：<code>Link</code> 和 <code>RouteHandler</code>。</p><p>Link 组件可以认为是 ReactRouter 提供的对 <code>&lt;a&gt;</code> 标签进行封装的组件，你可以查看 Link 组件渲染到 DOM 上其实就是 a 标签。它接受的 props 有 to、params 和 query。to 可以是一个路由的名称（下文会讲到），也可以是一个完整的 http 地址（类似 href 属性）；params 和 query 都是这个链接带的参数，下文细讲。</p><p>此外，Link 组件还有一个小特点，就是如果这个 Link 组件指向的路由被激活的话，组件会自动添加一个值为 <code>active</code> 的 className，方便你对当前激活的菜单项设置不同的样式（注意 demo 中红色的菜单项）。</p><p>而 RouteHandler 组件是 ReactRouter 的核心组件之一，它代表着当前路由匹配到的 React 组件。假设当前的路由为 <code>/books</code>，那么 App 这个组件里的 RouteHandler 其实就是 Books 组件。</p><p>那么这个关系是怎么得来的呢？就要看下面定义的页面路由了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义页面上的路由</span></span><br><span class="line"><span class="keyword">var</span> routes = (</span><br><span class="line">  &lt;Route handler=&#123;App&#125;&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"movies"</span> handler=&#123;Movies&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"movie"</span> path=<span class="string">"/movie/:id"</span> handler=&#123;Movie&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"books"</span> handler=&#123;Books&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"book"</span> path=<span class="string">"/book/:id"</span> handler=&#123;Book&#125; /&gt;</span><br><span class="line">  &lt;<span class="regexp">/Route&gt;</span></span><br><span class="line"><span class="regexp">);</span></span><br></pre></td></tr></table></figure><p>这里又出现了另外一个 ReactRouter 提供的组件 <code>Route</code>，这个组件就是定义整个页面路由的基础，可以嵌套。</p><p>Route 接受的 props 包括 name、path、handler 等等。其中 name 就是上文提到的路由名称，可以通过 <code>&lt;Link to=&quot;路由的名称&quot;&gt;</code> 来生成一个跳转到该路由的链接。path 指明的是当前路由对应的 url，如果不指定，那么默认就是 <code>name</code> 对应的值；如果 <code>name</code> 也不指定，那默认是 <code>/</code>，即根目录。另外 path 还支持指定 params（上文有提到），就是上面的例子中 <code>:</code> 及后面跟着的名称。</p><blockquote><p>params 和 query 的区别在于，params 定义的是「路由」中的参数，比如 /movies/:id ，params 为 id；而 query 是 「URL」中的参数，是跟在 URL 中「?」后面的。定义路由时一般不考虑也不能限制 query 会是什么。</p></blockquote><p>比如 <code>&lt;Route name=&quot;movies&quot; handler={Movie} /&gt;</code> 就指明了一条指向 <code>/movies</code> 的路由，当该路由激活时，调用 <code>Movies</code> 这个组件进行渲染。</p><p>接下来就是最后一步，根据上面定义的路由判断出当前该渲染哪个组件，并将其渲染到 DOM 中。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将匹配的路由渲染到 DOM 中</span></span><br><span class="line">Router.run(routes, Router.HashLocation, <span class="function"><span class="keyword">function</span>(<span class="params">Root</span>)</span>&#123;</span><br><span class="line">  React.render(&lt;Root /&gt;, document.body);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Router 即 ReactRouter，run 方法接受 2 - 3个参数，其中第一个参数必填，即我们指定的路由规则。第二个参数选填，即路由的实现方式，<code>Router.HashLocation</code> 指明了当前页面使用 hash 变化来实现路由，反映在浏览器的地址栏中就是类似 <code>xxx.com/#/movies</code> 这样的地址。使用这种 Location 的好处是兼容 IE 8，如果你的应用不需要兼容 IE 8，可以使用更高级的 <code>Router.HistoryLocation</code>。</p><p>最后一个参数是一个回调函数，函数的第一个参数是 ReactRouter 判断出的当前路由中需要渲染的根节点组件，在这里就是 <code>&lt;App /&gt;</code> 这个组件（虽然名字叫做 Root，但在本例中 Root 指的就是 App）。</p><p>最后的最后，就是熟悉的 React API，将 Root 渲染到 DOM 中，看到的结果如下：</p><p><img src="http://ww2.sinaimg.cn/bmiddle/831e9385gw1ev76w5qp08j205b01iwea.jpg" alt="react router demo"></p><p>尝试点击一下各种链接看看效果吧！</p><p><a href="http://jsbin.com/duduta/14/edit?js,output" target="_blank" rel="noopener">Demo</a></p><h2 id="进阶使用"><a href="#进阶使用" class="headerlink" title="进阶使用"></a>进阶使用</h2><p>上面贴了这么多代码分析了这么一大段，只实现了一个非常基础的 ReactRouter 路由例子。你在把玩这个例子的时候应该会发现，页面默认打开的时候（即路由为 <code>/</code> 的时候），除了菜单什么也没有显示，显得比较单调。</p><p>这个时候你有两种选择：使用 <code>DefaultRoute</code> 或 <code>Redirect</code>。</p><p>如果我们希望页面默认进来的时候除了菜单之外再显示一个类似首页的组件，那么可以用 ReactRouter 提供的 <code>DefaultRoute</code>。</p><p><a href="http://jsbin.com/duduta/15/edit?js,output" target="_blank" rel="noopener">Demo</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义页面上的路由</span></span><br><span class="line"><span class="keyword">var</span> routes = (</span><br><span class="line">  &lt;Route handler=&#123;App&#125;&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"movies"</span> handler=&#123;Movies&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"movie"</span> path=<span class="string">"/movie/:id"</span> handler=&#123;Movie&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"books"</span> handler=&#123;Books&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"book"</span> path=<span class="string">"/book/:id"</span> handler=&#123;Book&#125; /&gt;</span><br><span class="line">    &lt;DefaultRoute handler=&#123;Index&#125; /&gt;</span><br><span class="line">  &lt;<span class="regexp">/Route&gt;</span></span><br><span class="line"><span class="regexp">);</span></span><br></pre></td></tr></table></figure><p>注意 routes 中定义的路由，多了一个 DefaultRoute，它的 handler 是 Index 组件，即我们希望用户在默认打开页面时看到的组件内容。</p><p>如果你不想这么麻烦，想用户进来默认就看到电影列表，则可以使用 Redirect 组件。</p><p><a href="http://jsbin.com/duduta/18/edit?js" target="_blank" rel="noopener">Demo</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义页面上的路由</span></span><br><span class="line"><span class="keyword">var</span> routes = (</span><br><span class="line">  &lt;Route handler=&#123;App&#125;&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"movies"</span> handler=&#123;Movies&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"movie"</span> path=<span class="string">"/movie/:id"</span> handler=&#123;Movie&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"books"</span> handler=&#123;Books&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"book"</span> path=<span class="string">"/book/:id"</span> handler=&#123;Book&#125; /&gt;</span><br><span class="line">    &lt;Redirect to=<span class="string">"movies"</span> /&gt;</span><br><span class="line">  &lt;<span class="regexp">/Route&gt;</span></span><br><span class="line"><span class="regexp">);</span></span><br></pre></td></tr></table></figure><p>可以看到，当我们直接访问这个 URL 的时候，自动被重定向到了 <code>/movies</code>。Redirect 同时接受 from 这个 props，可以指定当什么规则下才进行重定向。</p><h2 id="再优化"><a href="#再优化" class="headerlink" title="再优化"></a>再优化</h2><p>配合 DefaultRoute 和 Redirect，我们的路由已经基本成型了，但是目前还遇到一个问题：在显示电影详情或图书详情时，对应的菜单项没有高亮。</p><p><img src="http://ww4.sinaimg.cn/bmiddle/831e9385gw1ev76wlk6anj205r049wei.jpg" alt="react-router-demo"></p><p><img src="http://ww2.sinaimg.cn/bmiddle/831e9385gw1ev76wtyoqoj208c03cmx3.jpg" alt="react-router-demo"></p><p>这是为什么呢？上文说到了 Link 组件会在指向的路由被激活时自动添加值为 「active」 的 className。而我们的两个 Link 分别指向的是 <code>/movies</code> 和 <code>/books</code>。而详情页的 URL 是 <code>/movie/1</code> 或 <code>/book/1</code> 这种形式，显然不满足 Link 被激活的条件。</p><p>这个时候又有两个方案可以选择了……「嵌套路由」或是「动态判断」。</p><h3 id="嵌套路由"><a href="#嵌套路由" class="headerlink" title="嵌套路由"></a>嵌套路由</h3><p>让我们对路由进行一些改造：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义页面上的路由</span></span><br><span class="line"><span class="keyword">var</span> routes = (</span><br><span class="line">  &lt;Route handler=&#123;App&#125;&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"movies"</span> handler=&#123;Movies&#125;&gt;</span><br><span class="line">      &lt;Route name=<span class="string">"movie"</span> path=<span class="string">":id"</span> handler=&#123;Movie&#125; /&gt;</span><br><span class="line">    &lt;<span class="regexp">/Route&gt;</span></span><br><span class="line"><span class="regexp">    &lt;Route name="books" handler=&#123;Books&#125;&gt;</span></span><br><span class="line"><span class="regexp">      &lt;Route name="book" path=":id" handler=&#123;Book&#125; /</span>&gt;</span><br><span class="line">    &lt;<span class="regexp">/Route&gt;</span></span><br><span class="line"><span class="regexp">  &lt;/</span>Route&gt;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>同时改造一下图书列表和电影列表组件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 图书列表组件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">var</span> Books = React.createClass(&#123;</span><br><span class="line">  render: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;div&gt;</span><br><span class="line">        &lt;ul&gt;</span><br><span class="line">          &lt;li key=&#123;<span class="number">1</span>&#125;&gt;&lt;Link to="book" params=&#123;&#123;id: 1&#125;&#125;&gt;活着&lt;/Link&gt;&lt;/li&gt;</span><br><span class="line">          &lt;li key=&#123;<span class="number">2</span>&#125;&gt;&lt;Link to="book" params=&#123;&#123;id: 2&#125;&#125;&gt;挪威的森林&lt;/Link&gt;&lt;/li&gt;</span><br><span class="line">          &lt;li key=&#123;<span class="number">3</span>&#125;&gt;&lt;Link to="book" params=&#123;&#123;id: 3&#125;&#125;&gt;从你的全世界走过&lt;/Link&gt;&lt;/li&gt;</span><br><span class="line">        &lt;<span class="regexp">/ul&gt;</span></span><br><span class="line"><span class="regexp">        &lt;RouteHandler /</span>&gt;</span><br><span class="line">      &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">    );</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;);</span></span><br></pre></td></tr></table></figure><p>注意到首先我们的路由变成了嵌套模式，原本电影列表和电影详情的路由是平级的，一个指向 <code>/movies</code>，一个指向 <code>/movie/:id</code>。改造之后，路由形成了嵌套关系，电影列表的路由依然是 <code>/movies</code>，而电影详情变成了 <code>/movies/:id</code>。</p><p><a href="http://jsbin.com/duduta/20/edit?js" target="_blank" rel="noopener">Demo</a></p><p>经过改造之后显示详情的时候菜单可以正确的高亮了，然而列表内容也被显示了出来，很多时候我们并不需要列表和详情同时出现，这个时候就需要另一种路由处理方式：动态判断。</p><blockquote><p>其实我们的路由从最开始就存在嵌套模式，记得最外层 handler 是 App 的 Route 吗？里面所有的路由都是被嵌套的。</p></blockquote><h3 id="动态判断"><a href="#动态判断" class="headerlink" title="动态判断"></a>动态判断</h3><p>让我们再次对路由进行改造：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义页面上的路由</span></span><br><span class="line"><span class="keyword">var</span> routes = (</span><br><span class="line">  &lt;Route handler=&#123;App&#125;&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"movies"</span> path=<span class="string">"/movies/:id?"</span> handler=&#123;Movies&#125; /&gt;</span><br><span class="line">    &lt;Route name=<span class="string">"books"</span> path=<span class="string">"/books/:id?"</span> handler=&#123;Books&#125; /&gt;</span><br><span class="line">  &lt;<span class="regexp">/Route&gt;</span></span><br><span class="line"><span class="regexp">);</span></span><br></pre></td></tr></table></figure><p>取消了嵌套在列表路由下的详情页路由，同时改造了列表页路由的 path，从原来的 <code>/movies</code> 改为 <code>/movies/:id?</code>。这样的 path 代表着这条路由匹配所有 <code>/movies/</code> 开头的 URL，同时可能存在一个 id 参数，也可能不存在。</p><p>看起来路由变简单了不少，不过对应的代价是组件中的代码需要大改。</p><p>主要的变动包括：</p><p>列表组件中渲染增加逻辑动态判断</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 图书列表组件</span><br><span class="line"> */</span><br><span class="line">var Books = React.createClass(&#123;</span><br><span class="line">  mixins: [StateMixin],</span><br><span class="line">  </span><br><span class="line">  render: function() &#123;</span><br><span class="line">    </span><br><span class="line">    var id = this.getParams().id;</span><br><span class="line">    return id ? &lt;Book id=&#123;id&#125; /&gt; : (</span><br><span class="line">      &lt;div&gt;</span><br><span class="line">        &lt;ul&gt;</span><br><span class="line">          &lt;li key=&#123;1&#125;&gt;&lt;Link to=&quot;books&quot; params=&#123;&#123;id: 1&#125;&#125;&gt;活着&lt;/Link&gt;&lt;/li&gt;</span><br><span class="line">          &lt;li key=&#123;2&#125;&gt;&lt;Link to=&quot;books&quot; params=&#123;&#123;id: 2&#125;&#125;&gt;挪威的森林&lt;/Link&gt;&lt;/li&gt;</span><br><span class="line">          &lt;li key=&#123;3&#125;&gt;&lt;Link to=&quot;books&quot; params=&#123;&#123;id: 3&#125;&#125;&gt;从你的全世界走过&lt;/Link&gt;&lt;/li&gt;</span><br><span class="line">        &lt;/ul&gt;</span><br><span class="line">        &lt;RouteHandler /&gt;</span><br><span class="line">      &lt;/div&gt;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>详情页组件不再从路由中获取数据，而是根据父组件提供的 props 进行渲染：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 单本图书组件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">var</span> Book = React.createClass(&#123;</span><br><span class="line">  render: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;article&gt;</span><br><span class="line">       &lt;h1&gt;这里是图书 id 为 &#123;<span class="keyword">this</span>.props.id&#125; 的详情介绍&lt;<span class="regexp">/h1&gt;</span></span><br><span class="line"><span class="regexp">      &lt;/</span>article&gt;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><a href="http://jsbin.com/duduta/24/edit?js" target="_blank" rel="noopener">Demo</a></p><h2 id="静态方法"><a href="#静态方法" class="headerlink" title="静态方法"></a>静态方法</h2><p>ReactRouter 还提供了 <code>willTransitionTo</code> 和 <code>willTransitionFrom</code> 两个静态方法，用于某个组件将要被激活和某个组件将要被取消激活时进行拦截或进行相关操作。</p><p>由于这两个 API 很可能在 1.0 正式版中被砍掉，这里就不多做介绍了。</p><h2 id="ReactRouter-小结"><a href="#ReactRouter-小结" class="headerlink" title="ReactRouter 小结"></a>ReactRouter 小结</h2><p>花了一个晚上的时间把自己一年前挖下的坑松了松土，看到这里你以为自己已经学会 ReactRouter 怎么用了吗？呵呵，偷偷告诉你，ReactRouter 1.0 的 API 变化挺大的（目前已有 1.0 beta3 版）。这个库非常激进，维护的非常频繁，带来的后果就是 API 变动也很频繁。</p><p>不管怎么样，拥抱变化吧！</p><p><a href="http://rackt.github.io/react-router/" target="_blank" rel="noopener">React Router 官方文档</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;去年 9 月份写了一篇 &lt;a href=&quot;http://undefinedblog.com/react-router/&quot;&gt;ReactRouter 使用指南&lt;/a&gt;，不小心在百度搜索「react-router」关键词排到了第一名。最近收到很多同学反馈说这篇文章里的例子挂了让我
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>最近踩得两个 IE 深坑</title>
    <link href="https://undefinedblog.com/recent-ie-quirks/"/>
    <id>https://undefinedblog.com/recent-ie-quirks/</id>
    <published>2015-08-12T14:49:17.000Z</published>
    <updated>2015-08-12T15:05:55.000Z</updated>
    
    <content type="html"><![CDATA[<p>如果这个世界上没有 IE，前端程序员的寿命至少延长十年。作为一个有节操的程序员，对于 IE 下的各种 quirks 就算不说了如指掌也应该略有了解。什么 IE 6 下 margin 双边距啦、IE 的 Object.defineProperty 只支持 DOM 节点啦，都是小意思！最近踩了两个不太常见的 IE 坑，记录下来跟大家分享，也希望后人能避免再次踩坑。</p><h2 id="IE-9-的样式表限制"><a href="#IE-9-的样式表限制" class="headerlink" title="IE 9 的样式表限制"></a>IE 9 的样式表限制</h2><p>IE 对 CSS 选择器的支持非常有限，按理说 IE 9 应该基本算是一个可以用的浏览器了，然而，它对于样式表也有变态的限制。具体限制规则如下：</p><ul><li>一个样式表（.css 文件）最多只能包含 4,095 个选择器</li><li>一个样式表最多只能 <code>@import</code> 31 个样式表</li><li><code>@import</code> 指令最多只能嵌套 4 层</li></ul><p>其中我就结结实实的踩中了第一个坑，因为我们的应用非常复杂，所以单独抽出了一个基础库，包含了多个页面公用的 js 和 css，谁曾想公共的 css 文件选择器数量不知不觉已经达到了 4,300 多个。这样直接导致的后果就是部分选择器及样式被 IE 9 无情的抛弃了。</p><p>解决方案：不要触犯以上规则，拆分样式表，去除不必要的选择器</p><p>解决工具：<a href="https://github.com/jasonslyvia/gulp-legacy-ie-css-lint" target="_blank" rel="noopener">gulp-legacy-ie-css-lint</a></p><p>来源参考：<a href="http://stackoverflow.com/questions/9906794/internet-explorers-css-rules-limits" target="_blank" rel="noopener">so</a></p><h2 id="IE-8-下-resize-事件的频繁触发"><a href="#IE-8-下-resize-事件的频繁触发" class="headerlink" title="IE 8 下 resize 事件的频繁触发"></a>IE 8 下 resize 事件的频繁触发</h2><p>最近因为业务需要写了一个 <a href="">react-lazyload</a> 组件（非常好用喔，已经在阿里的对外业务上全面铺开使用了），但是在 IE 8 下性能巨差。经过艰难的断点调试发现，lazyload 的回调被调用的太过频繁了（即使已经加了 300ms 的 debounce）。经过一番 Google 后发现，IE 8 处理 resize 事件的方式简直是变态。</p><p>根据 MDN 的描述：</p><blockquote><p>The resize event is fired when the document view has been resized.</p></blockquote><p>resize 事件应该是当文档可视区域发生变化时触发的（即当你放大或缩小浏览器窗口时），而 IE 8 下却不是。IE 8 下触发 resize 事件的逻辑是：</p><p>当<strong>一个元素的尺寸</strong>发生改变的时候，就触发 resize 事件</p><p>这样当你更新 DOM 节点的时候，resize 事件的触发频率及次数可想而知。</p><p>解决方案：不需要使用 resize 事件的时候尽量不要绑定这个事件；实在需要的话在 DOM 可能发生改变前解除 resize 事件的绑定，在 DOM 更新完成后重新监听 resize 事件</p><p>来源参考：<a href="http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer" target="_blank" rel="noopener">so</a></p><p>这里多说一句，很多应用在 IE 8 下觉得卡顿，也可以检查一下是否因为绑定了 resize 事件的缘故。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;如果这个世界上没有 IE，前端程序员的寿命至少延长十年。作为一个有节操的程序员，对于 IE 下的各种 quirks 就算不说了如指掌也应该略有了解。什么 IE 6 下 margin 双边距啦、IE 的 Object.defineProperty 只支持 DOM 节点啦，都是
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>使用 react-menu-aim 打造更友好的多级菜单</title>
    <link href="https://undefinedblog.com/react-menu-aim/"/>
    <id>https://undefinedblog.com/react-menu-aim/</id>
    <published>2015-07-11T18:59:44.000Z</published>
    <updated>2015-07-16T07:02:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>写过多级菜单的同学应该都知道当年亚马逊的黑科技：鼠标从一级菜单滑向二级菜单时，如果中间经过了另一个一级菜单，并不会马上切换。这也避免了用户想看二级菜单的时候，必须先精准的横向移动到对应二级菜单的不便。</p><p>详细的原理在<a href="http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown" target="_blank" rel="noopener">这篇博文</a> 里都有解释，本文不再赘述。简单地说，就是在一级菜单项 mouseenter 的时候，根据鼠标之前的移动坐标，判断用户当前是否是想移动到二级菜单。若是，则 setTimeout 一段时间，再重新判断；若不是，则直接激活当前一级菜单项。</p><p>一图胜千言：</p><p><img src="https://cloud.githubusercontent.com/assets/1336484/8591773/198d1d4a-265d-11e5-94b1-97071a591ab1.gif" alt="react-menu-aim"></p><p><a href="http://jasonslyvia.github.io/react-menu-aim/demo/index.html" target="_blank" rel="noopener">Demo</a></p><p>目前已经有人根据这个原理写了一个 jQuery 插件 <a href="https://github.com/kamens/jQuery-menu-aim/" target="_blank" rel="noopener">jQuery-menu-aim</a>，而我由于在最近的 React 项目中需要使用这个功能，就顺手写了一个 React 版本。</p><p>使用的方法也很简单，引入 <a href="https://github.com/jasonslyvia/react-menu-aim" target="_blank" rel="noopener">react-menu-aim</a> 作为一个 mixin，然后在 <code>componentWillMount</code> 的时候配置一下，最后在 render 的时候调用 mixin 提供的一些事件响应方法即可。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">var React = require(&apos;react&apos;);</span><br><span class="line">var ReactMenuAim = require(&apos;react-menu-aim&apos;);</span><br><span class="line"></span><br><span class="line">var Menu = React.createClass(&#123;</span><br><span class="line">  // 使用 ReactMenuAim mixin</span><br><span class="line">  mixins: [ReactMenuAim],</span><br><span class="line"></span><br><span class="line">  componentWillMount: function() &#123;</span><br><span class="line">    // 在这里配置 ReactMenuAim</span><br><span class="line">    this.initMenuAim(&#123;</span><br><span class="line">      submenuDirection: &apos;right&apos;,</span><br><span class="line">      menuSelector: &apos;.menu&apos;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  // 由于 ReactMenuAim 需要响应组件的 mouseenter 等事件，所以若你也需要</span><br><span class="line">  // 响应这些事件，可以把自己的 event handler 作为参数传给 ReactMenuAim</span><br><span class="line">  // 提供的 event handler。</span><br><span class="line">  //</span><br><span class="line">  // 例如下面就是组件自己的 event handler</span><br><span class="line">  handleSwitchMenuIndex: function(index) &#123;</span><br><span class="line">    // ...</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  // this.handleMouseLeaveMenu 和 this.handleMouseEnterRow 是 </span><br><span class="line">  // ReactMenuAim 提供的方法，让它们分别响应鼠标离开菜单和鼠标进入每一个</span><br><span class="line">  // 一级菜单选项的事件。</span><br><span class="line">  //</span><br><span class="line">  // 若 ReactMenuAim 判定应该激活当前 mouseenter 的一级菜单时，会调用</span><br><span class="line">  // 传入的回调。在这个例子里，就是我们自己的 this.handleSwitchMenuIndex 方法</span><br><span class="line">  render: function() &#123;</span><br><span class="line">    return (</span><br><span class="line">      &lt;div className=&quot;menu-container&quot;&gt;</span><br><span class="line">        &lt;ul className=&quot;menu&quot; onMouseLeave=&#123;this.handleMouseLeaveMenu&#125;&gt;</span><br><span class="line">          &lt;li className=&quot;menu-item&quot; onMouseEnter=&#123;this.handleMouseEnterRow.bind(this, 0, this.handleSwitchMenuIndex)&#125;&gt;Menu Item 1&lt;/li&gt;</span><br><span class="line">          &lt;li className=&quot;menu-item&quot; onMouseEnter=&#123;this.handleMouseEnterRow.bind(this, 1, this.handleSwitchMenuIndex)&#125;&gt;Menu Item 1&lt;/li&gt;</span><br><span class="line">        &lt;/ul&gt;</span><br><span class="line">      &lt;/div&gt;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>更多的配置项见 <a href="https://github.com/jasonslyvia/react-menu-aim" target="_blank" rel="noopener">github repo</a>。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;写过多级菜单的同学应该都知道当年亚马逊的黑科技：鼠标从一级菜单滑向二级菜单时，如果中间经过了另一个一级菜单，并不会马上切换。这也避免了用户想看二级菜单的时候，必须先精准的横向移动到对应二级菜单的不便。&lt;/p&gt;
&lt;p&gt;详细的原理在&lt;a href=&quot;http://bjk5.co
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>深入理解 React 的 batchUpdate 机制</title>
    <link href="https://undefinedblog.com/understand-react-batch-update/"/>
    <id>https://undefinedblog.com/understand-react-batch-update/</id>
    <published>2015-06-22T06:43:02.000Z</published>
    <updated>2015-06-22T15:47:52.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前有篇文章写了「<a href="http://undefinedblog.com/why-react-is-so-fast/">为什么 React 这么快</a>」，其中说到一点很重要的特性就是 <code>batchUpdate</code>。我一直以为 batchUpdate 是 Virtual DOM 的什么黑科技，直到上周跑去支付宝跟承玉等大牛交流后才直到自己理解的有偏差。</p><p>废话少说，直接上代码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">var Parent = React.createClass(&#123;</span><br><span class="line">  getInitialState: function() &#123;</span><br><span class="line">     return &#123;</span><br><span class="line">       text: &apos;default&apos;</span><br><span class="line">     &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  handleChildClick: function()&#123;</span><br><span class="line">    this.setState(&#123;</span><br><span class="line">      text: Math.random() * 1000</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  render: function()&#123;</span><br><span class="line">    console.log(&apos;parent render&apos;);</span><br><span class="line">    </span><br><span class="line">    return (</span><br><span class="line">      &lt;div className=&quot;parent&quot;&gt;</span><br><span class="line">       this is parent!</span><br><span class="line">       &lt;Child text=&#123;this.state.text&#125; onClick=&#123;this.handleChildClick&#125; /&gt;</span><br><span class="line">      &lt;/div&gt;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">var Child = React.createClass(&#123;</span><br><span class="line">  getInitialState: function() &#123;</span><br><span class="line">    return &#123;</span><br><span class="line">     text: this.props.text + &apos;~&apos;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  componentWillReceiveProps: function(nextProps) &#123;</span><br><span class="line">    this.setState(&#123;</span><br><span class="line">      text: nextProps.text + &apos;~&apos;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  handleClick: function()&#123;</span><br><span class="line">    this.setState(&#123;</span><br><span class="line">      text: &apos;clicked&apos;</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    this.props.onClick();</span><br><span class="line">  &#125;,</span><br><span class="line">  </span><br><span class="line">  render: function() &#123;</span><br><span class="line">    console.log(&apos;child render&apos;);</span><br><span class="line">    </span><br><span class="line">    return (</span><br><span class="line">     &lt;div className=&quot;child&quot;&gt;</span><br><span class="line">       I&apos;m child</span><br><span class="line">       &lt;p&gt;something from parent:&lt;/p&gt;</span><br><span class="line">       &lt;p&gt;&#123;this.state.text&#125;&lt;/p&gt;</span><br><span class="line">       &lt;button onClick=&#123;this.handleClick&#125;&gt;click me&lt;/button&gt;</span><br><span class="line">     &lt;/div&gt;</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">React.render(&lt;Parent/&gt;, document.body);</span><br></pre></td></tr></table></figure><p><a href="http://jsbin.com/yaqimo/2/edit?js,console,output" target="_blank" rel="noopener">DEMO</a></p><p>当组件首次渲染时，console 输出内容如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">parent render</span><br><span class="line">child render</span><br></pre></td></tr></table></figure><p>下面考虑点击 <code>Child</code> 中的 button，问 console 中输出的内容是怎样的？</p><p>让我们看看 Child 的 <code>handleClick</code>，首先调用了一次 <code>setState</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">this.setState(&#123;</span><br><span class="line">  text: &apos;clciked&apos;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>然后调用了 Parent 的 <code>onClick</code> 回调。而 Parent 的 handleChildClick 方法更新了自己的 state，从而触发了新一轮的 render。</p><p>那么，console 中输出的内容莫非是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">child render   // Child setState</span><br><span class="line">parent render  // Parent setState</span><br><span class="line">child render   // Parent state 更新，Child 跟着 re-render</span><br></pre></td></tr></table></figure><p>但实际情况是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">parent render</span><br><span class="line">child render</span><br></pre></td></tr></table></figure><p>我们预期的 Child 调用 setState 导致的 render 似乎并没有发生。</p><p>下面再来一个简单点的例子：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">//...</span><br><span class="line">  handleClick: function() &#123;</span><br><span class="line">    this.setState(&#123;</span><br><span class="line">      text: &apos;clicked&apos;</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    var newText = this.state.text + &apos; agian&apos;;</span><br><span class="line">    this.setState(&#123;</span><br><span class="line">      text: newText</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">//...</span><br></pre></td></tr></table></figure><p>如果我们把 Child 的 handleClick 改写成上面这样，render 的触发情况又是怎样的呢？Child 的 state 中 <code>text</code> 的值又是什么呢？</p><p>答案见 <a href="http://jsbin.com/yaqimo/3/edit?js,console,output" target="_blank" rel="noopener">DEMO</a>，本文不再赘述。</p><p>到这里，我们已经领会了 batchUpdate 的强大之处，但是 batchUpdate 究竟是怎么实现的呢？让我们深入到 React 的源码中一探究竟。</p><blockquote><p>以下内容由于本人能力及精力均有限，尚未深究，仅供参考。如有错误，尽请斧正！</p></blockquote><p>首先，我们在 Child 的 handleClick 中调用了 setState，其简化的调用栈如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">this.setState</span><br><span class="line">...</span><br><span class="line">this.replaceState</span><br><span class="line">...</span><br><span class="line">将更新后的 state 保存为 this._pendingState</span><br><span class="line">...</span><br><span class="line">if (不是在 componentWillMount 的生命周期中)</span><br><span class="line">ReactUpdates.enqueneUpdate</span><br><span class="line">...</span><br><span class="line">将当前 component 存入 dirtyComponents 数组中</span><br><span class="line">...</span><br><span class="line">if (存在 callback)</span><br><span class="line">将 callback 保存到 component._pendingCallbacks 数组中</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>一次 setState 调用就这么结束了，而下一行调用 <code>this.props.onClick()</code> 触发的也是 Parent 的 setState，整体调用栈类似。</p><p>那么问题来了，两次 setState 都是把变化存入一个 pending 数组中，那么变化最后究竟是怎么起作用的呢？</p><p>实际上我们看看一次事件触发的完整调用栈就能大概明白了。</p><p>当一次 DOM 事件触发后，ReactEventListener.dispatchEvent 方法会被调用。而这个方法并不是急着去调用我们在 JSX 中指定的各种回调，而是调用了 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ReactUpdates.batchedUpdates</span><br></pre></td></tr></table></figure><p>这个方法就是 React 整个 batchUpdate 思想的核心。该方法会执行一个 transaction，而要执行的内容被定义在 handleTopLevelImpl，其实就是找到事件对应的所有节点并依次对这些节点触发事件。</p><p>在进一步介绍之前，我们需要先了解一下 Transaction。</p><p>React 为 Transaction 封装了一个简单的 Mixin，并在源码中生动的介绍了一个 Transaction 是怎么工作的。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> *                       wrappers (injected at creation time)</span><br><span class="line"> *                                      +        +</span><br><span class="line"> *                                      |        |</span><br><span class="line"> *                    +-------------------------------------+</span><br><span class="line"> *                    |                 v        |              |</span><br><span class="line"> *                    |      +---------------+   |              |</span><br><span class="line"> *                    |   +--|    wrapper1   -----+         |</span><br><span class="line"> *                    |   |  +---------------+   v    |         |</span><br><span class="line"> *                    |   |          +-------------+  |         |</span><br><span class="line"> *                    |   |     +----|   wrapper2  -------+   |</span><br><span class="line"> *                    |   |     |    +-------------+  |     |   |</span><br><span class="line"> *                    |   |     |                     |     |   |</span><br><span class="line"> *                    |   v     v                     v     v   | wrapper</span><br><span class="line"> *                    | +---+ +---+   +---------+   +---+ +---+ | invariants</span><br><span class="line"> * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained</span><br><span class="line"> * +-----------------&gt;-----&gt;|anyMethod-------------&gt;</span><br><span class="line"> *                    | |   | |   |   |         |   |   | |   | |</span><br><span class="line"> *                    | |   | |   |   |         |   |   | |   | |</span><br><span class="line"> *                    | |   | |   |   |         |   |   | |   | |</span><br><span class="line"> *                    | +---+ +---+   +---------+   +---+ +---+ |</span><br><span class="line"> *                    |  initialize                    close    |</span><br><span class="line"> *                    +-----------------------------------------+</span><br><span class="line"> **/</span><br></pre></td></tr></table></figure><p>实际上，Transacation 就是给需要执行的函数封装了两个 wrapper，每个 wrapper 都有 initialize 和 close 方法。当一个 transaction 需要执行（perform）的时候，会先调用对应的 initialize 方法。同样的，当一个 transaction 执行完成后，会调用对应的 close 方法。</p><p>回到刚才的问题，pending 的状态究竟是怎么被改变成真正的状态的呢？其实在刚开始的时候，一个事件触发后 ReactEventListener.dispatchEvent 被调用，这个函数中调用了 batchingStrategy 的 batchUpdate 方法。而 batchingStrategy 中使用了一个 ReactDefaultBatchingStrategyTransaction 的示例 transaction， 那么这个名字超长的类究竟干了什么？</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">function ReactDefaultBatchingStrategyTransaction() &#123;</span><br><span class="line">  this.reinitializeTransaction();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">assign(</span><br><span class="line">  ReactDefaultBatchingStrategyTransaction.prototype,</span><br><span class="line">  Transaction.Mixin,</span><br><span class="line">  &#123;</span><br><span class="line">    getTransactionWrappers: function() &#123;</span><br><span class="line">      return TRANSACTION_WRAPPERS;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>注意它被 assgin 的 getTransactionWrappers 方法，返回了一个常量 TRANSACTION_WRAPPERS。</p><p>看到这里你应该就明白了，这个常量里定义了最后把 pendingState 改写为真正的 state 的方法。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">var NESTED_UPDATES = &#123;</span><br><span class="line">  initialize: function() &#123;</span><br><span class="line">    this.dirtyComponentsLength = dirtyComponents.length;</span><br><span class="line">  &#125;,</span><br><span class="line">  close: function() &#123;</span><br><span class="line">    if (this.dirtyComponentsLength !== dirtyComponents.length) &#123;</span><br><span class="line">      // Additional updates were enqueued by componentDidUpdate handlers or</span><br><span class="line">      // similar; before our own UPDATE_QUEUEING wrapper closes, we want to run</span><br><span class="line">      // these new updates so that if A&apos;s componentDidUpdate calls setState on</span><br><span class="line">      // B, B will update before the callback A&apos;s updater provided when calling</span><br><span class="line">      // setState.</span><br><span class="line">      dirtyComponents.splice(0, this.dirtyComponentsLength);</span><br><span class="line">      flushBatchedUpdates();</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      dirtyComponents.length = 0;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">var UPDATE_QUEUEING = &#123;</span><br><span class="line">  initialize: function() &#123;</span><br><span class="line">    this.callbackQueue.reset();</span><br><span class="line">  &#125;,</span><br><span class="line">  close: function() &#123;</span><br><span class="line">    this.callbackQueue.notifyAll();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];</span><br></pre></td></tr></table></figure><p>我们需要关注的就是 NESTED_WRAPPER 中的 close 方法，也就是这个方法指明了当所有的事件触发响应结束后，flushBatchUpdates()。至于究竟是怎么 flush 的，本文暂不深究。</p><p>纵观 React 源码，使用 Transaction 之处非常之多，React 源码注释中也列举了很多可以使用 Transaction 的地方，比如</p><ul><li>在一次 DOM reconciliation（调和，即 state 改变导致 Virtual DOM 改变，计算真实 DOM 该如何改变的过程）的前后，保证 input 中选中的文字范围（range）不发生变化</li><li>当 DOM 节点发生重新排列时禁用事件，以确保不会触发多余的 blur/focus 事件。同时可以确保 DOM 重拍完成后事件系统恢复启用状态。</li><li>当 worker thread 的 DOM reconciliation 计算完成后，由 main thread 来更新整个 UI</li><li>在渲染完新的内容后调用所有 <code>componentDidUpdate</code> 的回调</li><li>等等</li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;之前有篇文章写了「&lt;a href=&quot;http://undefinedblog.com/why-react-is-so-fast/&quot;&gt;为什么 React 这么快&lt;/a&gt;」，其中说到一点很重要的特性就是 &lt;code&gt;batchUpdate&lt;/code&gt;。我一直以为 batchU
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>解决使用 ES6 写测试时 chai 的报错</title>
    <link href="https://undefinedblog.com/chai-throw-error-when-use-babel-transform-es6-syntax/"/>
    <id>https://undefinedblog.com/chai-throw-error-when-use-babel-transform-es6-syntax/</id>
    <published>2015-04-23T02:30:56.000Z</published>
    <updated>2015-04-23T02:38:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近基本所有的项目都开始使用 ES6 语法来写。一是因为很多 ES6 语法确实简洁很多（如箭头函数），二是因为向 ES5 甚至 ES3 兼容的 transpiler 都比较成熟，不用担心兼容性的问题。</p><p>但是最近在用 ES 6 写测试代码时，chai 会报如下错误：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TypeError: Unable to access callee of strict mode <span class="keyword">function</span></span><br></pre></td></tr></table></figure><p>经过一番研究我发现如下几个点：</p><ol><li>chai 不支持 strict mode</li><li>Babel 在将 ES6 module 转换为 ES5 时会自动添加 <code>&quot;use strict&quot;;</code> 指令（这也是 ES 6 的规范）</li></ol><p>因为我的测试代码也是用 ES 6 编写，Babel 在转换时给测试代码添加了严格模式的指令，导致 chai 的 expect 方法报错了。</p><p>解决方法比较简单：</p><ol><li>在使用 Babel 转换 ES6 代码时可以传入参数 <code>{blacklist: strict}</code>，如果使用命令行，可以使用 <code>babel xxx.js --blacklist=strict</code> （在正常的业务代码中不建议使用该参数）</li><li>如果使用了 Webpack 或 Browserify，要在配置文件中忽略掉 <code>node_modules</code> 文件夹</li></ol><p>最近工作繁忙，博客更新的太慢……</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最近基本所有的项目都开始使用 ES6 语法来写。一是因为很多 ES6 语法确实简洁很多（如箭头函数），二是因为向 ES5 甚至 ES3 兼容的 transpiler 都比较成熟，不用担心兼容性的问题。&lt;/p&gt;
&lt;p&gt;但是最近在用 ES 6 写测试代码时，chai 会报如下错
      
    
    </summary>
    
    
  </entry>
  
</feed>
