-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
1842 lines (1664 loc) · 279 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>SunCafe</title>
<subtitle>著意寻春不肯香 香在无寻处。</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://suncafe.cc/"/>
<updated>2017-10-09T15:42:09.000Z</updated>
<id>http://suncafe.cc/</id>
<author>
<name>SunCafe</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>离线友好的表单</title>
<link href="http://suncafe.cc/2017/09/10/%E7%A6%BB%E7%BA%BF%E5%8F%8B%E5%A5%BD%E7%9A%84%E8%A1%A8%E5%8D%95/"/>
<id>http://suncafe.cc/2017/09/10/离线友好的表单/</id>
<published>2017-09-09T16:31:43.000Z</published>
<updated>2017-10-09T15:42:09.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://mxb.at/blog/offline-forms/" target="_blank" rel="external">Offline-Friendly Forms</a></li>
<li>原文作者:<a href="https://twitter.com/intent/follow?screen_name=mxbck" target="_blank" rel="external">mxbck</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>本文永久链接:<a href="https://github.com/xitu/gold-miner/blob/master/TODO/offline-friendly-forms.md" target="_blank" rel="external">https://github.com/xitu/gold-miner/blob/master/TODO/offline-friendly-forms.md</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:<a href="https://github.com/yanyixin" target="_blank" rel="external">yanyixin</a>、<a href="https://github.com/Tina92" target="_blank" rel="external">Tina92</a></li>
</ul>
</blockquote>
<p>网络不佳时网页表单的表现通常并不理想。如果你试图在离线状态下提交表单,那就很可能丢失刚刚填好的数据。下面就看看我们是如何修复这个问题的。</p>
<p>太长,勿点:这里是本文的 <a href="https://codepen.io/mxbck/pen/ayYGGO/" target="_blank" rel="external">CodePen Demo</a>。</p>
<p>随着 Service Workers 的推行,现在开发者们甚至可以实现离线版的网页了。静态资源的缓存相对容易,而像表单这样需要服务器交互的情况就很难优化了。即使这样,提供一些有用的离线回退方案还是有可能的。</p>
<p>首先,我们为离线友好的表单创建一个新的类。接着我们保存一些 <code><form></code> 元素的属性然后绑定一个触发 submit 事件的函数:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">class OfflineForm {</div><div class="line"> // 配置实例。</div><div class="line"> constructor(form) {</div><div class="line"> this.id = form.id;</div><div class="line"> this.action = form.action;</div><div class="line"> this.data = {};</div><div class="line"> </div><div class="line"> form.addEventListener('submit', e => this.handleSubmit(e));</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>在 submit 处理函数中,我们使用 <code>navigator.onLine</code> 属性内置一个简单的网络检查器。<a href="http://caniuse.com/online-status/embed/" target="_blank" rel="external">浏览器对它的支持</a>很好,而且实现它也不难。</p>
<p>⚠️ 但它还是有一定<a href="https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine" target="_blank" rel="external">误报</a>的可能,因为这个属性只能检查客户端是否连接到网络,而不能检测实际的网络连通性。另一方面,一个 <code>false</code> 值意味着“离线”是相对确定的。因此,比起其他方式这个判断方法是最好的。</p>
<p>如果一个用户当前处于离线状态,我们就暂停表单的提交,把数据存储在本地。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line">handleSubmit(e) {</div><div class="line"> e.preventDefault();</div><div class="line"> // 解析表单输入,存储到对象中</div><div class="line"> this.getFormData();</div><div class="line"> </div><div class="line"> if (!navigator.onLine) {</div><div class="line"> // 用户离线,在设备中存储数据</div><div class="line"> this.storeData();</div><div class="line"> } else {</div><div class="line"> // 用户在线,通过 ajax 发送数据 </div><div class="line"> this.sendData();</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h2 id="存储表单数据"><a href="#存储表单数据" class="headerlink" title="存储表单数据"></a>存储表单数据</h2><p>存储数据到用户设备有<a href="https://developer.mozilla.org/en-US/docs/Web/API/Storage" target="_blank" rel="external">几种不同的方式</a>。根据数据的不同,如果你不希望本地副本持久存储在内存中,可以使用 <code>sessionStorage</code>。在我们的例子中,我们可以一起使用 <code>localStorage</code>。</p>
<p>我们可以给表单数据附上时间戳,把它赋值给一个新的对象,并且使用 <code>localStorage.setItem</code> 保存。这个方法接受两个参数:<strong>key</strong>(表单 id)和 <strong>value</strong>(数据的 JSON 串)。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line">storeData() {</div><div class="line"> // 检测 localStorage 是否可用</div><div class="line"> if (typeof Storage !== 'undefined') {</div><div class="line"> const entry = {</div><div class="line"> time: new Date().getTime(),</div><div class="line"> data: this.data,</div><div class="line"> };</div><div class="line"> // 把数据存储为 JSON 串</div><div class="line"> localStorage.setItem(this.id, JSON.stringify(entry));</div><div class="line"> return true;</div><div class="line"> }</div><div class="line"> return false;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>提示:你可以在 Chrome 的开发者工具 “Application” 中查看存储数据。如果不出差错,你可以看到内容如下:</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/19/0bfa980df3c23f848fd97cdc53fc4a5d" alt=""></p>
<p>通知用户发生了什么也是个好主意,这样他们会知道他们的数据不会丢失。我们可以扩展 <code>handleSubmit</code> 函数来显示某些反馈信息。</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/19/a05e72751558fcf383e7417031b1821d" alt=""></p>
<p>多么周到的表单!</p>
<h2 id="检查保存的数据"><a href="#检查保存的数据" class="headerlink" title="检查保存的数据"></a>检查保存的数据</h2><p>一旦用户联网,我们想检查一下是否有被存储的提交。我们可以监听 <code>online</code> 事件来捕获网络链接的改变,还有页面刷新时的 <code>load</code> 事件:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">constructor(form){</div><div class="line"> ...</div><div class="line"> window.addEventListener('online', () => this.checkStorage());</div><div class="line"> window.addEventListener('load', () => this.checkStorage());</div><div class="line">}</div></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line">checkStorage() {</div><div class="line"> if (typeof Storage !== 'undefined') {</div><div class="line"> // 检测我们是否在 localStorage 之中存储了数据</div><div class="line"> const item = localStorage.getItem(this.id);</div><div class="line"> const entry = item && JSON.parse(item);</div><div class="line"></div><div class="line"> if (entry) {</div><div class="line"> // 舍弃超过一天的提交。 (可选)</div><div class="line"> const now = new Date().getTime();</div><div class="line"> const day = 24 * 60 * 60 * 1000;</div><div class="line"> if (now - day > entry.time) {</div><div class="line"> localStorage.removeItem(this.id);</div><div class="line"> return;</div><div class="line"> }</div><div class="line"></div><div class="line"> // 我们已经验证了表单数据,尝试提交它</div><div class="line"> this.data = entry.data;</div><div class="line"> this.sendData();</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>一旦我们成功提交了表单,那最后一步就是移除 <code>localStorage</code> 中的数据,来避免重复提交。假设是一个 ajax 表单,我们可以在服务器响应成功的回调里做这件事。很简单,这里我们可以使用 storage 对象的 <code>removeItem()</code> 方法。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line">sendData() {</div><div class="line"> // 向服务器发送 ajax 请求</div><div class="line"> axios.post(this.action, this.data)</div><div class="line"> .then((response) => {</div><div class="line"> if (response.status === 200) {</div><div class="line"> // 成功时移除存储的数据</div><div class="line"> localStorage.removeItem(this.id);</div><div class="line"> }</div><div class="line"> })</div><div class="line"> .catch((error) => {</div><div class="line"> console.warn(error);</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如果你不想使用 ajax 提交,另一个方案是将存储的数据回填到表单,然后调用 <code>form.submit()</code> 或让用户自己点击提交按钮。</p>
<p>☝️ 注意:简单起见,我在这个案例中省略了一些其他部分,比如表单验证和安全 token 验证等,这些东西在真正的生产环境是必不可少的。这里的另一个问题是处理敏感数据,就是说你不能在本地存储一些密码或者信用卡数据等私密信息。</p>
<p>如果你感兴趣,请查阅 <a href="https://codepen.io/mxbck/pen/ayYGGO" target="_blank" rel="external">CodePen 上的全部示例</a>。</p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi" target="_blank" rel="external">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi" target="_blank" rel="external">知乎专栏</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://mxb.at/blog/offline-forms/" target="_blank" rel="external">Offline-Friendly Forms</a></li>
<li>原
</summary>
<category term="前端" scheme="http://suncafe.cc/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>在HTTP/2的世界里管理CSS和JS</title>
<link href="http://suncafe.cc/2017/08/27/%E5%9C%A8HTTP2%E7%9A%84%E4%B8%96%E7%95%8C%E9%87%8C%E7%AE%A1%E7%90%86CSS%E5%92%8CJS/"/>
<id>http://suncafe.cc/2017/08/27/在HTTP2的世界里管理CSS和JS/</id>
<published>2017-08-27T14:49:50.000Z</published>
<updated>2017-10-09T15:37:36.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://www.viget.com/articles/managing-css-js-http-2/" target="_blank" rel="external">Managing CSS & JS in an HTTP/2 World</a></li>
<li>原文作者:<a href="https://www.viget.com/about/team/tdavis" target="_blank" rel="external"><br>Trevor Davis</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>本文永久链接:<a href="https://github.com/xitu/gold-miner/blob/master/TODO/managing-css-js-http-2.md" target="_blank" rel="external">https://github.com/xitu/gold-miner/blob/master/TODO/managing-css-js-http-2.md</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:<a href="https://github.com/Usey95" target="_blank" rel="external">Usey95</a>、<a href="https://github.com/alfred-zhong" target="_blank" rel="external">alfred-zhong</a></li>
</ul>
</blockquote>
<p>使用了 HTTP/2,在网站中传输 CSS 和 JS 将变得完全不同,本文是结合我实践的一份指南。</p>
<p>我们已经听说 HTTP/2 很多年了。我们甚至写了<a href="https://www.viget.com/articles/getting-started-with-http-2-part-1" target="_blank" rel="external">一些</a><a href="https://www.viget.com/articles/getting-started-with-http-2-part-2" target="_blank" rel="external">关于它的博客</a>。但我们的真正实践并不多。一直到现在。在一些最近的项目中,我把使用 HTTP/2 作为一个目标,并弄清楚如何更好地应用<a href="https://http2.github.io/faq/#why-is-http2-multiplexed" target="_blank" rel="external">多路复用</a>。本文并不会主要去讲你为什么应该使用 HTTP/2,而是要讨论我是如何管理 CSS 和 JS 的从而解释这一范式转变。</p>
<h2 id="拆分-CSS"><a href="#拆分-CSS" class="headerlink" title="拆分 CSS"></a>拆分 CSS</h2><p>这是我们多年来作为最佳实践的反例。但为了汲取多路复用的好处,最好的方式还是把你的 CSS 拆分成更小的文件,这样在每一页只加载必要的CSS。应该像这个例子这样:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">html</span>></span></div><div class="line"><span class="tag"><<span class="name">head</span>></span></div><div class="line"> <span class="comment"><!--每一页都是用的全局样式, header/footer/etc --></span></div><div class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">href</span>=<span class="string">"stylesheets/global/index.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>></span></div><div class="line"><span class="tag"></<span class="name">head</span>></span></div><div class="line"><span class="tag"><<span class="name">body</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">href</span>=<span class="string">"stylesheets/modules/text-block/index.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>></span></div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"text-block"</span>></span></div><div class="line"> ...</div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">href</span>=<span class="string">"stylesheets/modules/two-column-block/index.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>></span></div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"two-column-block"</span>></span></div><div class="line"> ...</div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">href</span>=<span class="string">"stylesheets/modules/image-promos-block/index.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>></span></div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"image-promos-block"</span>></span></div><div class="line"> ...</div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"></div><div class="line"><span class="tag"></<span class="name">body</span>></span></div><div class="line"><span class="tag"></<span class="name">html</span>></span></div></pre></td></tr></table></figure>
<p>没错,<code><link></code> 标签放在了 <code><body></code> 内部,但不必惊慌,这完全<a href="https://html.spec.whatwg.org/multipage/semantics.html#allowed-in-the-body" target="_blank" rel="external">合规</a>。因此对于每一个小的标签块,都可以拥有一个独立的只包含相应 CSS 的样式。假如你正在使用模块化风格构建你的页面,这很容易设置。</p>
<h3 id="管理-SCSS-文件"><a href="#管理-SCSS-文件" class="headerlink" title="管理 SCSS 文件"></a>管理 SCSS 文件</h3><p>经过一些实践,这是我整理的 SCSS 文件结构:</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/15/cb090dd1567a20824dc2ffa3bb1950f2" alt=""></p>
<p><strong>CONFIG 文件夹</strong></p>
<p>我使用这个文件夹设置一堆变量:</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/15/d1844e9b9edbd94360fe799553c72e71" alt=""></p>
<p>这里的入口文件是 <code>_index.scss</code>,它引入了所有其他 SCSS 文件,所以我可以访问到一些变量和 mixins。它是这样的:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">@<span class="keyword">import</span> <span class="string">"variables"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"../functions/*"</span>;</div></pre></td></tr></table></figure>
<p><strong>FUNCTIONS 文件夹</strong></p>
<p>顾名思义,它包含了一些常见的 mixins 和函数,每一个 mixin 或函数都对应一个文件。</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/15/8b3766817c6ac244b5d961a73c26b9e9" alt=""></p>
<p><strong>GLOBAL 文件夹</strong></p>
<p>这个文件夹包含我每一页都使用的 CSS。特别适合放一些类似网站的 header、footer、reset、字体和其他通用样式之类的东西。</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/15/fdd61ca28a4854b6599c183a4f747f21" alt=""></p>
<p><code>index.scss</code> 看起来是这样的:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">@<span class="keyword">import</span> <span class="string">"../config/index"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"_fonts.scss"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"_reset.scss"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"_base.scss"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"_utility.scss"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"_skip-link.scss"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"_header.scss"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"_content.scss"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"_footer.scss"</span>;</div><div class="line">@<span class="keyword">import</span> <span class="string">"components/*"</span>;</div></pre></td></tr></table></figure>
<p>最后一行引入了所有 components 的子目录,这是将额外全局样式模块化的捷径。</p>
<p><strong>MODULES 文件夹</strong></p>
<p>这是我们 HTTP/2 体系中最重要的文件夹。当我拆分样式到对应的模块,这个文件夹会包含非常非常多的文件。所以我从拆分每一个模块到子目录开始:</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/15/d5b0474363bd8ae233712ecba43665a1" alt=""></p>
<p>每个模块中的 <code>index.scss</code> 是这样的:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">// 导入所有的全局变量和 mixin</div><div class="line">@import "../../config/index";</div><div class="line"></div><div class="line">// 导入这个模块文件夹中的所有部分</div><div class="line">@import "_*.scss";</div></pre></td></tr></table></figure>
<p>这样我可以访问到变量和 mixin,然后我可以把模块的 CSS 拆分为许多部分,它们组合成一个单独的 CSS 模块文件。</p>
<p><strong>PAGES 文件夹</strong></p>
<p>实质上这个文件夹和 modules 文件夹一样,但我为了页面特定的内容使用它”。这种更模块化的方式在我们最近做的东西里绝对罕见,但是它很好地把页面的特殊样式拆分出来了。</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/15/3ad7dcf0b199c3cd6404e68453b5f88b" alt=""></p>
<h3 id="适配-Blendid"><a href="#适配-Blendid" class="headerlink" title="适配 Blendid"></a>适配 Blendid</h3><p>最近所有的项目我们都是用 <a href="https://github.com/vigetlabs/blendid" target="_blank" rel="external">Blendid</a> 来构建的 。为了实现上文描述的 SCSS 配置,我需要添加 <a href="https://www.npmjs.com/package/node-sass-glob-importer" target="_blank" rel="external">node-sass-glob-importer</a>。一旦装好它,我只需把它添加到 Blendid 的 <code>task-config.js</code> 中。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">var globImporter = require('node-sass-glob-importer');</div><div class="line"></div><div class="line">module.exports = {</div><div class="line"> stylesheets: {</div><div class="line"> ...</div><div class="line"> sass: {</div><div class="line"> importer: globImporter()</div><div class="line"> },</div><div class="line"> ...</div><div class="line">}</div></pre></td></tr></table></figure>
<p>duang,这样就完成了管理 SCSS 的 HTTP/2 配置。 </p>
<h3 id="彩蛋:Craft-宏"><a href="#彩蛋:Craft-宏" class="headerlink" title="彩蛋:Craft 宏"></a>彩蛋:Craft 宏</h3><p>很长一段时间以来,我们在 Viget 都主张使用 Craft,我就写了一个宏来减少这种引入样式的方式:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">{%- macro css(stylesheet) -%}</div><div class="line"> <link rel="stylesheet" href="/stylesheets{{ stylesheet }}/index.css" media="not print"></div><div class="line">{%- endmacro -%}</div></pre></td></tr></table></figure>
<p>当我想要引入一个模块的 CSS 文件,我只需这样:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{{ macros.css('/modules/image-block') }}</div></pre></td></tr></table></figure>
<p>如果我需要在整个网站上放置样式表引用,这就更简单了。</p>
<h2 id="管理-JS"><a href="#管理-JS" class="headerlink" title="管理 JS"></a>管理 JS</h2><p>就像 CSS 一样,我想要把 JS 拆分为模块,这样每一页只加载必要的 JS。一样的,使用 <a href="https://github.com/vigetlabs/blendid" target="_blank" rel="external">Blendid 配置</a>,为了一切正常运转我只需要做一点点微调。</p>
<p>我使用的是 <code>import()</code>,而非 Webpack 的<code>require()</code>,。因此现在的 <code>modules/index.js</code> 文件需要看起来是这样的:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line">const moduleElements = document.querySelectorAll('[data-module]');</div><div class="line"></div><div class="line">for (var i = 0; i < moduleElements.length; i++) {</div><div class="line"> const el = moduleElements[i];</div><div class="line"> const name = el.getAttribute('data-module');</div><div class="line"></div><div class="line"> import(`./${name}`).then(Module => {</div><div class="line"> new Module.default(el);</div><div class="line"> });</div><div class="line">}</div></pre></td></tr></table></figure>
<p>正如 Webpack 文档中所说:”这个特性内部依赖 Promise。如果你在旧版本浏览器使用 <code>import()</code>,记得使用一个 polyfill 来兼容 Promise,比如 es6-promise 或者 promise-polyfill“。</p>
<p>因此我把 <a href="https://www.npmjs.com/package/es6-promise" target="_blank" rel="external">es6-promise polyfill</a> 加入到我的入口文件 <code>app.js</code> 中,使其自动兼容。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">require('es6-promise/auto');</div></pre></td></tr></table></figure>
<p>是的,然后你就可以在 Blendid 开箱即用的模式触发模块生成对应特定的 JS。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><div data-module="carousel"></div></pre></td></tr></table></figure>
<h2 id="这很完美吗?"><a href="#这很完美吗?" class="headerlink" title="这很完美吗?"></a>这很完美吗?</h2><p>还不,但至少可以引领你开始以合理的方式管理 HTTP/2 资源。随着我们对如何拆分代码来更好地使用 HTTP/2 的思考,我真切地希望这个配置将会越来越完善。</p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi" target="_blank" rel="external">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi" target="_blank" rel="external">知乎专栏</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://www.viget.com/articles/managing-css-js-http-2/" target="_blank" rel="external">Managing CSS &amp
</summary>
<category term="前端" scheme="http://suncafe.cc/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>使用CSS栅格和Flexbox打造Trello布局</title>
<link href="http://suncafe.cc/2017/08/19/%E4%BD%BF%E7%94%A8CSS%E6%A0%85%E6%A0%BC%E5%92%8CFlexbox%E6%89%93%E9%80%A0Trello%E5%B8%83%E5%B1%80/"/>
<id>http://suncafe.cc/2017/08/19/使用CSS栅格和Flexbox打造Trello布局/</id>
<published>2017-08-19T11:38:09.000Z</published>
<updated>2017-10-09T15:35:07.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://www.sitepoint.com/building-trello-layout-css-grid-flexbox/" target="_blank" rel="external">Building a Trello Layout with CSS Grid and Flexbox</a></li>
<li>原文作者:<a href="https://www.sitepoint.com/author/gmainardi/" target="_blank" rel="external">Giulio Mainardi</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>本文永久链接:<a href="https://github.com/xitu/gold-miner/blob/master/TODO/building-trello-layout-css-grid-flexbox.md" target="_blank" rel="external">https://github.com/xitu/gold-miner/blob/master/TODO/building-trello-layout-css-grid-flexbox.md</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:<a href="https://github.com/Aladdin-ADD" target="_blank" rel="external">Aladdin-ADD</a>、<a href="https://github.com/ahonn" target="_blank" rel="external">ahonn</a></li>
</ul>
</blockquote>
<p>通过本教程,我将带你完成 <a href="https://trello.com/" target="_blank" rel="external">Trello</a> 看板 (<a href="https://trello.com/b/nC8QJJoZ/trello-development-roadmap" target="_blank" rel="external">查看示例</a>)的基本布局。这是一个响应式的、纯 CSS 的解决方案,并且我们将只开发布局的结构特性。</p>
<p><a href="https://codepen.io/SitePoint/pen/brmXRX?editors=0100" target="_blank" rel="external">这是一个 CodePen demo</a>,可预览一下最终结果。</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/15/e89c41435c114108c104881882a60a62" alt=""></p>
<p>除了<a href="https://www.sitepoint.com/introduction-css-grid-layout-module/" target="_blank" rel="external">栅格布局</a>和 <a href="https://www.sitepoint.com/flexbox-css-flexible-box-layout/" target="_blank" rel="external">Flexbox</a>,这个方案还采用了 <a href="https://www.sitepoint.com/css3-calc-function/" target="_blank" rel="external">calc</a> 和<a href="https://www.sitepoint.com/css-viewport-units-quick-start/" target="_blank" rel="external">视图单位</a>。我们也将利用 <a href="http://sass-lang.com/documentation/file.SASS_REFERENCE.html#variables_" target="_blank" rel="external">Sass 变量</a>,让代码更可读和高效。</p>
<p>不提供向下兼容,所以请确保在支持的浏览器上运行。一切就绪,就让我们开始一步一步开发看板组件吧。</p>
<h2 id="屏幕布局"><a href="#屏幕布局" class="headerlink" title="屏幕布局"></a>屏幕布局</h2><p>一个 Trello 看板由一个 app 栏、一个 board 栏和一个包含卡片列表的部分组成。我使用以下标签骨架搭建出这一结构:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"ui"</span>></span></div><div class="line"> <span class="tag"><<span class="name">nav</span> <span class="attr">class</span>=<span class="string">"navbar app"</span>></span>...<span class="tag"></<span class="name">nav</span>></span></div><div class="line"> <span class="tag"><<span class="name">nav</span> <span class="attr">class</span>=<span class="string">"navbar board"</span>></span>...<span class="tag"></<span class="name">nav</span>></span></div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"lists"</span>></span></div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"list"</span>></span></div><div class="line"> <span class="tag"><<span class="name">header</span>></span>...<span class="tag"></<span class="name">header</span>></span></div><div class="line"> <span class="tag"><<span class="name">ul</span>></span></div><div class="line"> <span class="tag"><<span class="name">li</span>></span>...<span class="tag"></<span class="name">li</span>></span></div><div class="line"> ...</div><div class="line"> <span class="tag"><<span class="name">li</span>></span>...<span class="tag"></<span class="name">li</span>></span></div><div class="line"> <span class="tag"></<span class="name">ul</span>></span></div><div class="line"> <span class="tag"><<span class="name">footer</span>></span>...<span class="tag"></<span class="name">footer</span>></span></div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"><span class="tag"></<span class="name">div</span>></span></div></pre></td></tr></table></figure>
<p>这个布局将通过 CSS 栅格实现。确切地说是 3×1 栅格(就是指一列三行)。第一行用于 app 栏,第二行用于 board 栏,第三行用于 <code>.lists</code> 元素。</p>
<p>前两行各自有一个固定的高度,而第三行将撑起可变窗口高度的其余部分:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="selector-class">.ui</span> {</div><div class="line"> <span class="attribute">height</span>: <span class="number">100vh</span>;</div><div class="line"> <span class="attribute">display</span>: grid;</div><div class="line"> <span class="attribute">grid-template-rows</span>: $appbar-height $navbar-height <span class="number">1</span>fr;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>视图单位可以确保 <code>.ui</code> 容器总是和浏览器的窗口高度一致。</p>
<p>一个栅格化的上下文被分配给容器,并且指定了上文说的行和列。确切地说,是只指定了行,因为声明单独的列是没有必要的。一对 Sass 变量指定了两个栏目的高度,使用 <code>fr</code> 单位指定 <code>.lists</code> 元素高度使其撑起可变窗口高度的其余部分,这样每行的大小就设定完成了。</p>
<h2 id="卡片列表部分"><a href="#卡片列表部分" class="headerlink" title="卡片列表部分"></a>卡片列表部分</h2><p>如上所述,屏幕栅格的第三行托管着卡片列表的容器。这是标签的轮廓:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"lists"</span>></span></div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"list"</span>></span></div><div class="line"> ...</div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"> ...</div><div class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"list"</span>></span></div><div class="line"> ...</div><div class="line"> <span class="tag"></<span class="name">div</span>></span></div><div class="line"><span class="tag"></<span class="name">div</span>></span></div></pre></td></tr></table></figure>
<p>我用一个满屏宽的 Flexbox 单行行容器来格式化列表:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">.lists {</div><div class="line"> display: flex;</div><div class="line"> overflow-x: auto;</div><div class="line"> > * {</div><div class="line"> flex: 0 0 auto; // 'rigid' lists</div><div class="line"> margin-left: $gap;</div><div class="line"> }</div><div class="line"> &::after {</div><div class="line"> content: '';</div><div class="line"> flex: 0 0 $gap;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>给 <code>overflow-x</code> 指定 auto 值,当列表不适合视口提供的宽度时,浏览器会在屏幕底部显示一个水平滚动条。</p>
<p><code>flex</code> 简写属性用于 flex item 使列表更严格。<code>flex-basis</code> (简写的方式使用)的 auto 值指示布局引擎从 <code>.list</code> 元素的宽度属性取值,<code>flex-grow</code> 和 <code>flex-shrink</code> 的 0 值可以防止宽度的改变。</p>
<p>接下来我将在列表之间添加一个水平分隔。如果给列表设置右间距,当水平溢出时看板上最后一个列表之后的间距不会被渲染。为了解决这个问题,列表被一个左间距分隔并且最后一个列表和窗口右边缘的间距通过给每个 <code>.lists</code> 元素添加一个伪元素 <code>::after</code> 来实现。默认值 <code>flex-shrink: 1</code> 一定要被重写,否则这个伪元素会”吸收“所有的负空间,然后消失。</p>
<p>注意在 Firefox < 54 的版本上要给 <code>.lists</code> 指定 <code>width: 100%</code> 以确保正确的布局渲染。</p>
<h2 id="卡片列表"><a href="#卡片列表" class="headerlink" title="卡片列表"></a>卡片列表</h2><p>每个卡片列表由一个 header 栏、一个卡片序列和一个 footer 栏目组成。以下 HTML 代码段实现了这一结构:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"list"</span>></span></div><div class="line"> <span class="tag"><<span class="name">header</span>></span>List header<span class="tag"></<span class="name">header</span>></span></div><div class="line"> <span class="tag"><<span class="name">ul</span>></span></div><div class="line"> <span class="tag"><<span class="name">li</span>></span>...<span class="tag"></<span class="name">li</span>></span></div><div class="line"> ...</div><div class="line"> <span class="tag"><<span class="name">li</span>></span>...<span class="tag"></<span class="name">li</span>></span></div><div class="line"> <span class="tag"></<span class="name">ul</span>></span></div><div class="line"> <span class="tag"><<span class="name">footer</span>></span>Add a card...<span class="tag"></<span class="name">footer</span>></span></div><div class="line"><span class="tag"></<span class="name">div</span>></span></div></pre></td></tr></table></figure>
<p>这里的关键任务是如何管理列表的高度。header 和 footer 有固定的高度(未必相等)。然后有一些不定数量的卡片,每个卡片都有不定量的内容。因此随着卡片的添加和移除,这个列表也会增大和缩小。</p>
<p>但是高度不能无限增大,它需要有一个取决于 <code>.lists</code> 元素高度的上限。一旦突破上线,我想有一个垂直滚动条出现来允许访问溢出列表的卡片。</p>
<p>这听起来是 <code>max-height</code> 和 <code>overflow</code> 属性能做的。但如果根容器 <code>.list</code> 提供了这些属性,一旦列表达到了它的最大高度,所有的 <code>.list</code> 元素包括 header 和 footer 在内都会出现滚动条。下图左右两边分别显示错误的和正确的侧边条:</p>
<p><img src="https://user-gold-cdn.xitu.io/2017/9/15/f9224e1661712636d2127622032d94f8" alt=""></p>
<p>因此,让我们把 <code>max-height</code> 约束给内部的 <code><ul></code>。应该提供什么值呢?header 和 footer 的高度必须从列表父容器(<code>.lists</code>)的高度之中扣除:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">ul {</div><div class="line"> max-height: calc(100% - #{$list-header-height} - #{$list-footer-height});</div><div class="line">}</div></pre></td></tr></table></figure>
<p>但还有一个问题。百分比数值并不参照 <code>.lists</code> 而是参照 <code><ul></code> 元素的父元素 <code>.list</code>,并且这个元素没有定义高度,因此这个百分比不能确定。这个问题可以通过设置 <code>.list</code> 和 <code>.lists</code> 同样高度来解决:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">.list {</div><div class="line"> height: 100%;</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这样,既然 <code>.list</code> 和 <code>.lists</code> 总是一样高,它的 <code>background-color</code> 属性不能用于列表背景色,但可以使用它的子元素(header, footer 和卡片)来实现这一目的。</p>
<p>最后一个 list 高度的调整很有必要,可用来计算列表底部和窗口底部的一点空间(<code>$gap</code>)。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">.list {</div><div class="line"> height: calc(100% - #{$gap} - #{$scrollbar-thickness});</div><div class="line">}</div></pre></td></tr></table></figure>
<p>还有一个 <code>$scrollbar-thickness</code> 需要被减去,防止列表触及 <code>.list</code> 元素的水平滚动条。 事实上这个滚动条”增长“在 <code>.lists</code> 盒子内部。也就是说,100% 这个值是指包括滚动条在内的 <code>.lists</code> 的高度。</p>
<p>而在火狐中,这个滚动条被”附加“给 <code>.lists</code> 高度的外部,就是说 <code>.lists</code> 高度的 100% 并不包含滚动条。所以这个减法就没什么必要了。结果是当滚动条可见时,在火狐中已经触及最大高度的底部边框和滚动条的顶部之间的可视空间会稍大一些。</p>
<p>这是这个组件相应的 CSS 规则:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div></pre></td><td class="code"><pre><div class="line">.list {</div><div class="line"> width: $list-width;</div><div class="line"> height: calc(100% - #{$gap} - #{$scrollbar-thickness});</div><div class="line"></div><div class="line"> > * {</div><div class="line"> background-color: $list-bg-color;</div><div class="line"> color: #333;</div><div class="line"> padding: 0 $gap;</div><div class="line"> }</div><div class="line"></div><div class="line"> header {</div><div class="line"> line-height: $list-header-height;</div><div class="line"> font-size: 16px;</div><div class="line"> font-weight: bold;</div><div class="line"> border-top-left-radius: $list-border-radius;</div><div class="line"> border-top-right-radius: $list-border-radius;</div><div class="line"> }</div><div class="line"></div><div class="line"> footer {</div><div class="line"> line-height: $list-footer-height;</div><div class="line"> border-bottom-left-radius: $list-border-radius;</div><div class="line"> border-bottom-right-radius: $list-border-radius;</div><div class="line"> color: #888;</div><div class="line"> }</div><div class="line"></div><div class="line"> ul {</div><div class="line"> list-style: none;</div><div class="line"> margin: 0;</div><div class="line"> max-height: calc(100% - #{$list-header-height} - #{$list-footer-height});</div><div class="line"> overflow-y: auto;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>如上所述,列表背景色通过给每一个 <code>.list</code> 元素的子元素的 <code>background-color</code> 属性指定 <code>$list-bg-color</code> 值而被渲染。<code>overflow-y</code> 使得卡片滚动条只有按需显示。最后,给 header 和 footer 添加一些简单的样式。</p>
<h2 id="完成收尾"><a href="#完成收尾" class="headerlink" title="完成收尾"></a>完成收尾</h2><p>单个卡片包含的一个列表元素 HTML:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><li>Lorem ipsum dolor sit amet, consectetur adipiscing elit</li></div></pre></td></tr></table></figure>
<p>卡片也有可能包含一个封面图片:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">li</span>></span></div><div class="line"> <span class="tag"><<span class="name">img</span> <span class="attr">src</span>=<span class="string">"..."</span> <span class="attr">alt</span>=<span class="string">"..."</span>></span></div><div class="line"> Lorem ipsum dolor sit amet</div><div class="line"><span class="tag"></<span class="name">li</span>></span></div></pre></td></tr></table></figure>
<p>这是相应的样式:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line">li {</div><div class="line"> background-color: #fff;</div><div class="line"> padding: $gap;</div><div class="line"></div><div class="line"> &:not(:last-child) {</div><div class="line"> margin-bottom: $gap;</div><div class="line"> }</div><div class="line"></div><div class="line"> border-radius: $card-border-radius;</div><div class="line"> box-shadow: 0 1px 1px rgba(0,0,0, 0.1);</div><div class="line"></div><div class="line"> img {</div><div class="line"> display: block;</div><div class="line"> width: calc(100% + 2 * #{$gap});</div><div class="line"> margin: -$gap 0 $gap (-$gap);</div><div class="line"> border-top-left-radius: $card-border-radius;</div><div class="line"> border-top-right-radius: $card-border-radius;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<p>设置完一个背景、填充、和底部间距就差背景图片的布局了。这个图片宽度一定是跨越整个卡片的,从左填充的边缘到右填充的边缘:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">width: calc(100% + 2 * #{$gap});</div></pre></td></tr></table></figure>
<p>然后,指定负边距以使图片水平和垂直对齐:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">margin: -$gap 0 $gap (-$gap);</div></pre></td></tr></table></figure>
<p>第三个正边距的值用于指定封面图片和文字之间的空间。</p>
<p>最后我给占据屏幕布局第一行的两条添加了一个 flex 格式化上下文,但它们只是草图。通过<a href="https://codepen.io/SitePoint/pen/brmXRX?editors=0100" target="_blank" rel="external">扩展 demo</a> 自由构建你自己的实现吧。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这只是实现这种设计的一种可行方法,如果能看见其他方式那一定很有趣。此外,如果能完成整个布局那就更好了,比如完成最后的两个栏目。</p>
<p>另一个潜在的改进是能够为卡片列表实现自定义的滚动条。</p>
<p>所以,<a href="https://codepen.io/SitePoint/pen/brmXRX?editors=0100" target="_blank" rel="external">fork 这个 demo</a> 尽情发挥吧,记得在下面的讨论区留下你的链接哦。</p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi" target="_blank" rel="external">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi" target="_blank" rel="external">知乎专栏</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://www.sitepoint.com/building-trello-layout-css-grid-flexbox/" target="_blank" rel="external">Build
</summary>
<category term="前端" scheme="http://suncafe.cc/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>高性能React:3个新工具加速你的应用</title>
<link href="http://suncafe.cc/2017/08/14/%E9%AB%98%E6%80%A7%E8%83%BDReact%EF%BC%9A3%E4%B8%AA%E6%96%B0%E5%B7%A5%E5%85%B7%E5%8A%A0%E9%80%9F%E4%BD%A0%E7%9A%84%E5%BA%94%E7%94%A8/"/>
<id>http://suncafe.cc/2017/08/14/高性能React:3个新工具加速你的应用/</id>
<published>2017-08-14T15:01:30.000Z</published>
<updated>2017-10-09T15:32:51.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://medium.freecodecamp.org/make-react-fast-again-tools-and-techniques-for-speeding-up-your-react-app-7ad39d3c1b82" target="_blank" rel="external">High Performance React: 3 New Tools to Speed Up Your Apps</a></li>
<li>原文作者:<a href="https://medium.freecodecamp.org/@edelstein" target="_blank" rel="external">Ben Edelstein</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>本文永久链接:<a href="https://github.com/xitu/gold-miner/blob/master/TODO/make-react-fast-again-tools-and-techniques-for-speeding-up-your-react-app.md" target="_blank" rel="external">https://github.com/xitu/gold-miner/blob/master/TODO/make-react-fast-again-tools-and-techniques-for-speeding-up-your-react-app.md</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:<a href="https://github.com/yzgyyang" target="_blank" rel="external">yzgyyang</a>、<a href="https://github.com/reid3290" target="_blank" rel="external">reid3290</a></li>
</ul>
</blockquote>
<p><img src="https://cdn-images-1.medium.com/max/2000/1*mJFYp7LKVzZM3PPjFb0QXQ.png" alt=""></p>
<p>通常来说 React 是相当快的,但开发者也很容易犯一些错误导致出现性能问题。组件挂载过慢、组件树过深和一些非必要的渲染周期可以迅速地联手拉低你的应用速度。</p>
<p>幸运的是有大量的工具,甚至有些是 React 内置的,可以帮助我们检测性能问题。本文将着重介绍一些加快 React 应用的工具和技术。每一部分都配有一个可交互而且(希望是)有趣的 demo!</p>
<h3 id="工具-1-性能时间轴"><a href="#工具-1-性能时间轴" class="headerlink" title="工具 #1: 性能时间轴"></a>工具 #1: 性能时间轴</h3><p>React 15.4.0 引入了一个新的性能时间轴特性,可以精确展示组件何时挂载、更新和卸载。也可以让你可视化地观察组件生命周期相互之间的关系。</p>
<p><strong>注意:</strong> 目前,这一特性仅支持 Chrome、Edge 和 IE,因为它调用的 User Timing API 还没有在所有浏览器中实现。</p>
<h4 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h4><ol>
<li>打开你的应用并追加一个参数:<code>react_perf</code>。例如, <a href="http://localhost:3000?react_perf" target="_blank" rel="external"><code>http://localhost:3000?react_perf</code></a></li>
<li>打开 Chrome 开发者工具 <strong>Performance</strong> 栏并点击 <strong>Record</strong>。</li>
<li>执行你想要分析的操作。</li>
<li>停止记录。</li>
<li>观察 <strong>User Timing</strong> 选项下的可视化视图。</li>
</ol>
<p><img src="https://cdn-images-1.medium.com/max/1000/1*cOO5vUnbkdDUcqMW8ebJqA.png" alt=""></p>
<h4 id="理解输出结果"><a href="#理解输出结果" class="headerlink" title="理解输出结果"></a>理解输出结果</h4><p>每一个色条显示的是一个组件做“处理”的时间。由于 JavaScript 是单线程的,每当一个组件正在挂载或渲染,它都会霸占主线程,并阻塞其他代码运行。</p>
<p>像 <code>[update]</code> 这样中括号内的文字描述的是生命周期的哪一个阶段正在发生。把时间轴按照步骤分解,你可以看到依据方法的细粒度的计时,比如 <code>[componentDidMount]</code> <code>[componentWillReceiveProps]</code> <code>[ctor]</code> (constructor) 和 <code>[render]</code>。</p>
<p>堆叠的色条代表组件树,虽然在 React 拥有过深的组件树也比较典型,但如果你想优化一个频繁挂载的组件,减少嵌套组件的数量也是有帮助的,因为每一层都会增加少量的性能和内存消耗。</p>
<p>这里需要注意的是时间轴中的计时时长是针对 React 的开发环境构建的,会比生产环境慢很多。实际上性能时间轴本身也会拖慢你的应用。虽然这些时长不能代表真正的性能指标,但不同组件间的<strong>相对</strong>时间是精确的。而且一个组件是否完全被更新不取决于是否是生产环境的构建。</p>
<h4 id="Demo-1"><a href="#Demo-1" class="headerlink" title="Demo #1"></a>Demo #1</h4><p>出于乐趣,我故意写了一个具有<strong>严重</strong>性能问题的 TodoMVC 应用。你可以<a href="https://perf-demo.firebaseapp.com/?react_perf" target="_blank" rel="external">在这里尝试</a>。</p>
<p>打开 Chrome 开发者工具,切换到 “Performance” 栏,点击 Record 开始记录时间轴。然后在应用中添加一些 TODO,停止记录,检查时间轴。看看你能不能找出造成性能问题的组件 :)</p>
<h3 id="Tool-2-why-did-you-update"><a href="#Tool-2-why-did-you-update" class="headerlink" title="Tool #2: why-did-you-update"></a>Tool #2: why-did-you-update</h3><p>在 React 中最影响性能的问题之一就是非必要的渲染周期。默认情况下,一旦父组件渲染,React 组件就会跟着重新渲染,即使它们的 props 没有变化也是如此。</p>
<p>举个例子,如果我有一个简单的组件长这样:</p>
<pre><code>class DumbComponent extends Component {
render() {
return <div> {this.props.value} </div>;
}
}
</code></pre><p>它的父组件是这样:</p>
<pre><code>class Parent extends Component {
render() {
return <div>
<DumbComponent value={3} />
</div>;
}
}
</code></pre><p>每当父组件渲染,<code>DumbComponent</code> 就会重新渲染,尽管它的 props 没有改变。</p>
<p>一般来讲,如果 <code>render</code> 运行,并且虚拟 DOM 没有改变,而且既然 <code>render</code> 应该是个纯净的没有任何副作用的方法,那么这就是一个不必要的渲染周期。在一个大型应用中检测这种事情是非常困难的,但幸运的是有一个工具可以帮得上忙。</p>
<h4 id="使用-why-did-you-update"><a href="#使用-why-did-you-update" class="headerlink" title="使用 why-did-you-update"></a>使用 why-did-you-update</h4><p><img src="https://cdn-images-1.medium.com/max/1000/1*Lb4nr_WLwnLt63jUoszrnQ.png" alt=""></p>
<p><code>why-did-you-update</code> 是一个 React 钩子工具,用来检测潜在的非必要组件渲染。它会检测到被调用但 props 没有改变的组件 <code>render</code>。</p>
<h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><ol>
<li>使用 npm 安装: <code>npm i --save-dev why-did-you-update</code></li>
<li><p>在你应用中的任何地方添加下面这个片段:</p>
<p> import React from ‘react’</p>
<p> if (process.env.NODE_ENV !== ‘production’) {<br> const {whyDidYouUpdate} = require(‘why-did-you-update’)<br> whyDidYouUpdate(React)<br> }</p>
</li>
</ol>
<p><strong>注意:</strong> 这个工具在本地开发环境使用起来非常棒,但是要确保生产环境要禁用掉,因为它会拖慢你的应用。</p>
<h4 id="理解输出结果-1"><a href="#理解输出结果-1" class="headerlink" title="理解输出结果"></a>理解输出结果</h4><p><code>why-did-you-update</code> 在运行时监听你的应用,并用日志输出可能存在非必要更新的组件。它让你看到一个渲染周期前后的 props 对比,来决定是否可能存在非必要的更新。</p>
<h4 id="Demo-2"><a href="#Demo-2" class="headerlink" title="Demo #2"></a>Demo #2</h4><p>为了演示 <code>why-did-you-update</code>,我在 TodoMVC 中安装了这个库并放在 Code Sandbox 网站上,这是一个在线的 React 练习场。 打开浏览器控制台,并添加一些 TODO 来查看输出。</p>
<p><a href="https://codesandbox.io/s/xGJP4QExn" target="_blank" rel="external">这里查看 demo</a>。</p>
<p>注意这个应用中很少的组件存在非必要渲染。尝试执行上述的技术来避免非必要渲染,如果操作正确,<code>why-did-you-update</code> 不会在控制台输出任何内容。</p>
<h3 id="Tool-3-React-Developer-Tools"><a href="#Tool-3-React-Developer-Tools" class="headerlink" title="Tool #3: React Developer Tools"></a>Tool #3: React Developer Tools</h3><p><img src="https://cdn-images-1.medium.com/max/1000/1*1Ih6h8djFyH13tfFK3D1sw.png" alt=""></p>
<p>React Developer Tools 这个 Chrome 扩展有一个内置特性用来可视化组件更新。这有助于防止非必要的渲染周期。使用它,首先要确保<a href="https://codesandbox.io/s/xGJP4QExn" target="_blank" rel="external">在这里安装了这个扩展</a>。</p>
<p>然后点击 Chrome 开发者工具中的 “React” 选项卡打开扩展并勾选“Highlight Updates”。</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*GP4vXvW3WO0vTbggDfus4Q.png" alt=""></p>
<p>然后简单操作你的应用。和不同的组件交互并观察 DevTools 施展它的魔法。</p>
<h4 id="理解输出结果-2"><a href="#理解输出结果-2" class="headerlink" title="理解输出结果"></a>理解输出结果</h4><p>React Developer Tools 在给定的时间点高亮正在重新渲染的组件。根据更新的频率,使用不同的颜色。蓝色显示罕见更新,经过绿色、黄色的过渡,一直到红色用来显示更新频繁的组件。</p>
<p>看到黄色或红色并不<strong>必要</strong>觉得一定是坏事。它可能发生在调整一个滑块或频繁触发更新的其他 UI 元素,这属于意料之中。但如果当你点击一个简单的按钮并且看到了红色这可能就意味着事情不对了。这个工具的目的就是识破正在发生<strong>非必要</strong>更新的组件。作为应用的开发者,你应该对给定时间内哪个组件应该被更新有一个大体的概念。</p>
<h4 id="Demo-3"><a href="#Demo-3" class="headerlink" title="Demo #3"></a>Demo #3</h4><p>为了演示高亮,我故意让 TodoMVC 应用更新一些非必要的组件。</p>
<p><a href="https://highlight-demo.firebaseapp.com/" target="_blank" rel="external">这里查看 demo</a>。</p>
<p>打开上面的链接,然后打开 React Developer Tools 并启用更新高亮。当你在上面的文字输入框中输入内容时,你将看到所有的 TODO 非必要地高亮。你输入得越快,你会看到颜色变化指示更新越来越频繁。</p>
<h3 id="修复非必要渲染"><a href="#修复非必要渲染" class="headerlink" title="修复非必要渲染"></a>修复非必要渲染</h3><p>一旦你已经确定应用中非必要重新渲染的组件,有几种简单的方法来修复。</p>
<h4 id="使用-PureComponent"><a href="#使用-PureComponent" class="headerlink" title="使用 PureComponent"></a>使用 PureComponent</h4><p>在上面的例子中,<code>DumbComponent</code> 是只接收属性的纯函数。这样,组件就只有当它的 props 变化的时候才重新渲染。React 有一个特殊的内置组件类型叫做 <code>PureComponent</code>,就是适用这种情况的用例。</p>
<p>与继承自 React.Component 相反,像这样使用 React.PureComponent:</p>
<pre><code>class DumbComponent extends PureComponent {
render() {
return <div> {this.props.value} </div>;
}
}
</code></pre><p>那么只有当这个组件的 props 实际发生变化时它才会被重新渲染了。就是这样!</p>
<p>注意 <code>PureComponent</code> 对 props 做了一个浅对比,因此如果你使用复杂的数据结构,它可能会错失一些属性变化而不会更新你的组件。</p>
<h4 id="调用-shouldComponentUpdate"><a href="#调用-shouldComponentUpdate" class="headerlink" title="调用 shouldComponentUpdate"></a>调用 shouldComponentUpdate</h4><p><code>shouldComponentUpdate</code> 是一个在 <code>render</code> 之前 <code>props</code> 或 <code>state</code> 发生改变时被调用的组件方法。如果 <code>shouldComponentUpdate</code> 返回 true,<code>render</code> 将会被调用,如果返回 false 什么也不会发生。</p>
<p>通过执行这个方法,你可以命令 React 在 props 没有发生改变的时候避免给定组件的重新渲染。</p>
<p>例如,我们可以在上文中的 DumbComponent 中这样调用 <code>shouldComponentUpdate</code>。</p>
<pre><code>class DumbComponent extends Component {
shouldComponentUpdate(nextProps) {
if (this.props.value !== nextProps.value) {
return true;
} else {
return false;
}
}
render() {
return <div>foo</div>;
}
}
</code></pre><h3 id="在生产环境中调试性能问题"><a href="#在生产环境中调试性能问题" class="headerlink" title="在生产环境中调试性能问题"></a>在生产环境中调试性能问题</h3><p>React Developer Tools 只能在你自己的机器上运行的应用中使用。如果您有兴趣了解用户在生产中看到的性能问题,试试 <a href="https://logrocket.com" target="_blank" rel="external">LogRocket</a>。</p>
<p><img src="https://cdn-images-1.medium.com/max/1000/1*s_rMyo6NbrAsP-XtvBaXFg.png" alt=""></p>
<p><a href="https://logrocket.com" target="_blank" rel="external">LogRocket</a> 就像是 web 应用的 DVR,会记录发生在你的站点上的<strong>所有的一切</strong>。你可以重现带有 bug 或性能问题的会话来快速了解问题的根源,而不用猜测问题发生的原因。</p>
<p>LogRocket 工具为你的应用记录性能数据、Redux actions/state、日志、带有请求头和请求体的网络请求和响应以及浏览器的元数据。它也能记录页面上的 HTML 和 CSS,甚至可以为最复杂的单页面应用重新创建完美像素的视频。</p>
<p><a href="https://logrocket.com/" target="_blank" rel="external"><strong>LogRocket | 为 JavaScript 应用而生的日志记录和会话回放工具</strong><br>LogRocket 帮助你了解用影响你用户的问题,这样你就可以回过头来构建伟大的软件了。<br>logrocket.com</a></p>
<hr>
<p>感谢阅读,希望这些工具和技术能在你的下一个 React 项目中帮到你!</p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://medium.freecodecamp.org/make-react-fast-again-tools-and-techniques-for-speeding-up-your-react-ap
</summary>
<category term="前端" scheme="http://suncafe.cc/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>即将到来的正则表达式新特性</title>
<link href="http://suncafe.cc/2017/07/29/%E5%8D%B3%E5%B0%86%E5%88%B0%E6%9D%A5%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%96%B0%E7%89%B9%E6%80%A7/"/>
<id>http://suncafe.cc/2017/07/29/即将到来的正则表达式新特性/</id>
<published>2017-07-29T13:21:40.000Z</published>
<updated>2017-10-09T15:30:02.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://developers.google.com/web/updates/2017/07/upcoming-regexp-features" target="_blank" rel="external">Upcoming Regular Expression Features</a></li>
<li>原文作者:<a href="https://developers.google.com/web/resources/contributors#jgruber" target="_blank" rel="external">Jakob Gruber</a>、<a href="https://developers.google.com/web/resources/contributors#yangguo" target="_blank" rel="external">Yang Guo</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>本文永久链接:<a href="https://github.com/xitu/gold-miner/blob/master/TODO/upcoming-regexp-features.md" target="_blank" rel="external">https://github.com/xitu/gold-miner/blob/master/TODO/upcoming-regexp-features.md</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:<a href="https://github.com/atuooo" target="_blank" rel="external">atuooo</a>、<a href="https://github.com/Tina92" target="_blank" rel="external">Tina92</a></li>
</ul>
</blockquote>
<p>ES2015 给 JavaScript 语言引入了许多新特性,其中包括正则表达式语法的一些重大改进,新增了 Unicode 编码 (<code>/u</code>) 和粘滞位 (<code>/y</code>)两个修饰符。而在那之后,发展也并未停止。经过与 TC39(ECMAScript 标准委员会)的其他成员的紧密合作,V8 团队提议并共同设计了让正则表达式更强大的几个新特性。</p>
<p>这些新特性目前已经计划包含在 JavaScript 标准中。虽然提案还没有完全通过,但是它们已经进入 <a href="https://tc39.github.io/process-document/" target="_blank" rel="external">TC39 流程的候选阶段</a>了。我们已经以试验功能(见下文)在浏览器实现了这些特性,以便在最终定稿之前提供及时的设计和实现反馈给各自的提案作者。</p>
<p>本文给您预览一下这个令人兴奋的未来。如果您愿意跟着体验这些即将到来的示例,可以在 <code>chrome://flags/#enable-javascript-harmony</code> 页面中开启实验性 JavaScript 功能。</p>
<h2 id="命名捕获"><a href="#命名捕获" class="headerlink" title="命名捕获"></a>命名捕获</h2><p>正则表达式可以包含所谓的捕获(或捕获组),它可以捕获一部分匹配的文本。到目前为止,开发者只能通过索引来引用这些捕获,这取决于其在正则匹配中的位置。</p>
<pre><code>const pattern =/(\d{4})-(\d{2})-(\d{2})/u;
const result = pattern.exec('2017-07-10');
// result[0] === '2017-07-10'
// result[1] === '2017'
// result[2] === '07'
// result[3] === '10'
</code></pre><p>但正则表达式已经因难于读、写和维护而臭名昭著,并且数字引用会使事情进一步复杂化。例如,在一个更长的表达式中判断一个独特捕获的索引是很困难的事:</p>
<pre><code>/(?:(.)(.(?<=[^(])(.)))/ // 最后一个捕获组的索引是?
</code></pre><p>更糟糕的是,更改一个表达式可能会潜在地转变所有已存在的捕获的索引:</p>
<pre><code>/(a)(b)(c)\3\2\1/ // 一些简单的有序的反向引用。
/(.)(a)(b)(c)\4\3\2/ // 所有都需要更新。
</code></pre><p>命名捕获是一个即将到来的特性,它允许开发者给捕获组分配名称来帮助尽可能地解决这些问题。语法类似于 Perl、Java、.Net 和 Ruby:</p>
<pre><code>const pattern =/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = pattern.exec('2017-07-10');
// result.groups.year === '2017'
// result.groups.month === '07'
// result.groups.day === '10'
</code></pre><p>命名捕获组也可以被命名的反向引用来引用,并传入 <code>String.prototype.replace</code>:</p>
<pre><code>// 命名反向引用。
/(?<LowerCaseX>x)y\k<LowerCaseX>/.test('xyx'); //true
// 字符串替换。
const pattern =/(?<fst>a)(?<snd>b)/;
'ab'.replace(pattern,'$<snd>$<fst>'); // 'ba'
'ab'.replace(pattern,(m, p1, p2, o, s,{fst, snd})=> fst + snd); // 'ba'
</code></pre><p>关于这个新特性的全部详情可以在<a href="https://github.com/tc39/proposal-regexp-named-groups" target="_blank" rel="external">规范提案</a>中查看。</p>
<h2 id="dotAll-修饰符"><a href="#dotAll-修饰符" class="headerlink" title="dotAll 修饰符"></a>dotAll 修饰符</h2><p>默认情况下,元字符 <code>.</code> 在正则表达式中匹配除了换行符以外的任何字符:</p>
<pre><code>/foo.bar/u.test('foo\nbar'); // false
</code></pre><p>一个提案引入了 dotAll 模式,通过 <code>/s</code> 修饰符来开启。在 dotAll 模式中,<code>.</code> 也可以匹配换行符。</p>
<pre><code>/foo.bar/su.test('foo\nbar'); // true
</code></pre><p>关于这个新特性的全部详情可以在<a href="https://github.com/tc39/proposal-regexp-dotall-flag" target="_blank" rel="external">规范提案</a>中查看。</p>
<h2 id="Unicode-属性逃逸(Unicode-Property-Escapes)"><a href="#Unicode-属性逃逸(Unicode-Property-Escapes)" class="headerlink" title="Unicode 属性逃逸(Unicode Property Escapes)"></a>Unicode 属性逃逸(Unicode Property Escapes)</h2><p>正则表达式语法已经包含了特定字符类的简写。<code>\d</code> 代表数字并且只能是 <code>[0-9]</code>;<code>\w</code> 是单词字符的简写,或者写成 <code>[A-Za-z0-9_]</code>。</p>
<p>自从 ES2015 引入了 Unicode,突然间大量的字符可以被认为是数字,例如圈一:①;或者被认为是字符的,例如中文字符:雪。</p>
<p>它们都不会被 <code>\d</code> 或 <code>\w</code> 匹配。而改变这些简写的含义将会破坏已经存在的正则表达式模式。</p>
<p>于是,新的字串类被<a href="https://github.com/tc39/proposal-regexp-unicode-property-escapes" target="_blank" rel="external">引入</a>。注意它们只在使用 <code>/u</code> 修饰符的 Unicode-aware 正则表达式中可用。</p>
<pre><code>/\p{Number}/u.test('①'); // true
/\p{Alphabetic}/u.test('雪'); // true
</code></pre><p>排除型字符可以使用 <code>\P</code> 匹配。</p>
<pre><code>/\P{Number}/u.test('①'); // false
/\P{Alphabetic}/u.test('雪'); // false
</code></pre><p>统一码联盟还定义了许多方式来分类码位,例如数学符号和日语平假名字符:</p>
<pre><code>/^\p{Math}+$/u.test('∛∞∉'); // true
/^\p{Script_Extensions=Hiragana}+$/u.test('ひらがな'); // true
</code></pre><p>全部受支持的 Unicode 属性类列表可以在目前的<a href="https://tc39.github.io/proposal-regexp-unicode-property-escapes/#sec-static-semantics-unicodematchproperty-p" target="_blank" rel="external">规范提案</a>中找到。更多示例请查看<a href="https://mathiasbynens.be/notes/es-unicode-property-escapes" target="_blank" rel="external">这篇内容丰富的文章</a>。</p>
<h2 id="后行断言"><a href="#后行断言" class="headerlink" title="后行断言"></a>后行断言</h2><p>先行断言从一开始就已经是 JavaScript 正则表达式语法的一部分。与之相对的后行断言也终于将被<a href="https://github.com/tc39/proposal-regexp-lookbehind" target="_blank" rel="external">引入</a>。你们中的一些人可能记得,这成为 V8 的一部分已经有一段时间了。我们甚至在底层已经用后行断言实现了 ES2015 规定的 Unicode 修饰符。</p>
<p>“后行断言”这个名字已经很好地描述了它的涵义。它提供一个方式来限制一个正则,只有后行组匹配通过之后才继续匹配。它提供匹配和非匹配两种选择:</p>
<pre><code>/(?<=\$)\d+/.exec('$1 is worth about ¥123'); // ['1']
/(?<!\$)\d+/.exec('$1 is worth about ¥123'); //['123']
</code></pre><p>更多详细信息,查看我们<a href="https://v8project.blogspot.com/2016/02/regexp-lookbehind-assertions.html" target="_blank" rel="external">之前的一篇博文</a>,专门介绍了后行断言。相关示例可以查看<a href="https://github.com/v8/v8/blob/master/test/mjsunit/harmony/regexp-lookbehind.js" target="_blank" rel="external">V8 测试用例</a>。</p>
<h2 id="致谢"><a href="#致谢" class="headerlink" title="致谢"></a>致谢</h2><p>本文的完成有幸得到了很多相关人士的帮助,他们的辛勤工作造就了这一切:特别是语言之王<a href="https://twitter.com/mathias" target="_blank" rel="external">Mathias Bynens</a>、<a href="https://twitter.com/littledan" target="_blank" rel="external">Dan Ehrenberg</a>、<a href="https://github.com/claudepache" target="_blank" rel="external">Claude Pache</a>、<a href="https://twitter.com/bterlson" target="_blank" rel="external">Brian Terlson</a>、<a href="https://twitter.com/IgnoredAmbience" target="_blank" rel="external">Thomas Wood</a>、Gorkem Yakin、和正则大师 <a href="https://twitter.com/erikcorry" target="_blank" rel="external">Erik Corry</a>;还有为语言规范作出努力的每一个人以及 V8 团队对这些特性的实施。</p>
<p>希望您能像我们一样为这些新的正则表达式特性而感到兴奋!</p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://developers.google.com/web/updates/2017/07/upcoming-regexp-features" target="_blank" rel="externa
</summary>
<category term="前端" scheme="http://suncafe.cc/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>JavaScript 的函数式编程是一种反模式</title>
<link href="http://suncafe.cc/2017/07/06/JavaScript%E7%9A%84%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E6%98%AF%E4%B8%80%E7%A7%8D%E5%8F%8D%E6%A8%A1%E5%BC%8F/"/>
<id>http://suncafe.cc/2017/07/06/JavaScript的函数式编程是一种反模式/</id>
<published>2017-07-06T15:32:49.000Z</published>
<updated>2017-10-09T15:28:31.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://hackernoon.com/functional-programming-in-JavaScript-is-an-antipattern-58526819f21e" target="_blank" rel="external">Functional programming in JavaScript is an antipattern</a></li>
<li>原文作者:<a href="https://hackernoon.com/@alexdixon" target="_blank" rel="external">Alex Dixon</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>本文永久链接:<a href="https://github.com/xitu/gold-miner/blob/master/TODO/functional-programming-in-javascript-is-an-antipattern.md" target="_blank" rel="external">https://github.com/xitu/gold-miner/blob/master/TODO/functional-programming-in-javascript-is-an-antipattern.md</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:<a href="https://github.com/leviding" target="_blank" rel="external">LeviDing</a>、<a href="https://github.com/xekri" target="_blank" rel="external">xekri</a></li>
</ul>
</blockquote>
<hr>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*Y6orLTOgb6JFfjVdANVgCQ.png" alt=""></p>
<h2 id="其实-Clojure-更简单些"><a href="#其实-Clojure-更简单些" class="headerlink" title="其实 Clojure 更简单些"></a>其实 Clojure 更简单些</h2><p>写了几个月 Clojure 之后我再次开始写 JavaScript。就在我试着写一些很普通的东西的时候,我总会想下面这些问题:</p>
<blockquote>
<p>“这是 ImmutableJS 变量还是 JavaScript 变量?”</p>
<p>“我如何 map 一个对象并且返回一个对象?”</p>
<p>“如果它是不可变的,要么使用 <这种语法> 的 <这个函数>,否则使用 <不同的语法和完全不同行为> 的 <同一个函数的另一个版本>”</p>
<p>“一个 React 组件的 state 可以是一个不可变的 Map 吗?”</p>
<p>“引入 lodash 了吗?”</p>
<p>“<code>fromJS</code> 然后 <写代码> 然后 <code>.toJS()</code>?”</p>
</blockquote>
<p>这些问题似乎没什么必要。但我猜想我已经思考这些问题上百万次了只是没有注意到,因为这些都是我知道的。</p>
<p>当使用 React、Redux、ImmutableJS、lodash、和像 lodash/fp、ramda 这样的函数式编程库的任意组合写 JavaScript 的时候,我觉得没什么方法能避免这种思考。</p>
<p>我需要一直把下面这些事记在脑海里:</p>
<ul>
<li>lodash 的 API、Immutable 的 API、lodash/fp 的 API、ramda 的 API、还有原生 JS 的 API 或一些组合的 API</li>
<li>处理 JavaScript 数据结构的可变编程技术</li>
<li>处理 Immutable 数据结构的不可变编程技术</li>
<li>使用 Redux 或 React 时,可变的 JavaScript 数据结构的不可变编程</li>
</ul>
<p>就算我能够记住这些东西,我依然会遇到上面那一堆问题。不可变数据、可变数据和某些情况下不能改变的可变数据。一些常用函数的签名和返回值也是这样,几乎每一行代码都有不同的情况要考虑。我觉得在 JavaScript 中使用函数式编程技术很棘手。</p>
<p>按照惯例像 Redux 和 React 这种库需要不可变性。所以即使我不使用 ImmutableJS,我也得记得“这个地方不能改变”。在 JavaScript 中不可变的转换比它本身的使用更难。我感觉这门语言给我前进的道路下了一路坑。此外,JavaScript 没有像 Object.map 这样的基本函数。所以像<a href="https://www.npmjs.com/package/lodash" target="_blank" rel="external">上个月 4300 多万人</a>一样,我使用 lodash,它提供大量 JavaScript 自身没有的函数。不过它的 API 也不是友好支持不可变的。一些函数返回新的数值,而另一些会更改已经存在的数据。再次强调,花时间来区分它们是很不划算的。事实大概如此,想要处理 JavaScript,我需要了解 lodash、它的函数名称、它的签名、它的返回值。更糟糕的是,它的<a href="https://www.youtube.com/watch?v=m3svKOdZijA" target="_blank" rel="external">“collection 在先, arguments 在后”</a>的方式对函数式编程来说也并不理想。</p>
<p>如果我使用 ramda 或者 lodash/fp 会好一些,可以很容易地组合函数并且写出清晰整洁的代码。但是它不能和 Immutable 数据结构一起使用。我可能还是要写一些参数集合在后而其他时候在前的代码。我必须知道更多的函数名、签名、返回值,并引入更多的基本函数。</p>
<p>当我单独使用 ImmutableJS,一些事变得容易些了。Map.set 返回全新的值。一切都返回全新的值!这就是我想要的。不幸的是,ImmutableJS 也有一些纠结的事情。我不可避免地要处理两套不同的数据结构。所以我不得不清楚 <code>x</code> 是 Immutable 的还是 JavaScript 的。通过学习其 API 和整体思维方式,我可以使用 Immutable 在 2 秒内知道如何解决问题。当我使用原生 JS 时,我必须跳过该解决方案,用另一种方式来解决问题。就像 ramda 和 lodash 一样,有大量的函数需要我了解 —— 它们返回什么、它们的签名、它们的名称。我也需要把我所知的所有函数分成两类:一类用于 Immutable 的,另一类用于其它。这往往也会影响我解决问题的方式。我有时会不自主地想到柯里化和组合函数的解决方案。但不能和 ImmutableJS 一起使用。所以我跳过这个解决方案,想想其他的。</p>
<p>当我全部想清楚以后,我才能尝试写一些代码。然后我转移到另一个文件,做一遍同样的事情。</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*FVBc2DWB09sW6QJwMxm_fw.png" alt=""></p>
<p>JavaScript 中的函数式编程。</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*MVU4TWwrkRMpQlmgkU9TuQ.png" alt=""></p>
<p>反模式的可视化。</p>
<p>我已孤立无援,并且把 JavaScript 的函数式编程称为一种反模式。这是一条迷人之路却将我引入迷宫。它似乎解决了一些问题,最终却创造了更多的问题。重点是这些问题似乎没有更高层次的解决方案能避免我一次有又一次地处理问题。</p>
<h3 id="这件事的长期成本是什么"><a href="#这件事的长期成本是什么" class="headerlink" title="这件事的长期成本是什么?"></a>这件事的长期成本是什么?</h3><p>我没有确切的数字,但我敢说如果不必去想“在这里我可以用什么函数?”和“我可否改变这个变量”这样的问题,我可以更高效地开发。这些问题对我想要解决的问题或者我想要增加的功能没有任何意义。它们是语言本身造成的。我能想到避免这个问题的唯一办法就是在路的起点就不要走下去 —— 不要使用 ImmutableJS 、ImmutableJS 数据结构、Redux/React 概念中的不可变数据,以及 ramda 表达式和 lodash。总之就是写 JavaScript 不要使用函数式编程技术,它看似不是什么好的解决方案。</p>
<p>如果你确定并同意我所说的(如果不同意,也很好),那么我认为值得花 5 分钟或一天甚至一周时间来考虑:保持在 JavaScript 路子上相比用一个不同的东西取代,耗费的长期成本是什么?</p>
<p>这个所谓不同的东西对于我来说就是 Clojurescript。它是一门像 ES6 一样的 “compile-to-JS” 语言。大体上说,它是一种使用不同语法的 JavaScript。它的底层是被设计成用于函数式编程的语言,操作不可变的数据结构。对我来说,它比 JavaScript 更容易,更有前途。</p>
<p><img src="https://cdn-images-1.medium.com/max/1200/1*_bhmf-j96fW9qSuPm7yEsw.png" alt=""></p>
<h3 id="Clojure-Clojurescript-是什么?"><a href="#Clojure-Clojurescript-是什么?" class="headerlink" title="Clojure/Clojurescript 是什么?"></a>Clojure/Clojurescript 是什么?</h3><p>Clojurescript 类似 Clojure,除了它的宿主语言是 JavaScript 而不是 Java。它们的语法完全相同:如果你学 Clojurescript,其实你就在学 Clojure。这意味着如果你了解了 Clojurescript,你就可以写 JavaScript 和 Java。“30 亿的设备上运行着 Java”;我非常确定其他设备上运行着 JavaScript。</p>
<p>和 JavaScript 一样,Clojure 和 Clojurescript 也是动态类型的。你可以 100% 地使用 Clojurescript 语言用 Node 写服务端的全栈应用。与单独编译成 JavaScript 的语言不同,你也可以选择写一个基于 Java 的 servrer 来支持多线程。</p>
<p>作为一个普通的 JavaScript/Node 开发者,学习这门语言及其生态系统对我来说并不困难。</p>
<h3 id="是什么使得-Clojurescript-更简单?"><a href="#是什么使得-Clojurescript-更简单?" class="headerlink" title="是什么使得 Clojurescript 更简单?"></a>是什么使得 Clojurescript 更简单?</h3><p><img src="https://cdn-images-1.medium.com/max/1600/1*cxIhT4wHooj6Cl50sryKIA.gif" alt=""></p>
<p>在编辑器中执行任意你想要执行的代码。</p>
<ol>
<li><strong>你可以在编辑器中一键执行任何代码。</strong> 的确如此,你可以在编辑器中输入任何你想写的代码,选中它(或者把光标放在上面)然后运行并查看结果。你可以定义函数,然后用你想用的参数调用它。你可以在应用运行的时候做这些事。所以,如果你不知道一些东西如何运作,你可以在你的编辑器的 REPL 里求值,看看会发生什么。</li>
<li><strong>函数可以作用于数组和对象。</strong> Map、reduce、filter 等对数组和对象的作用都相同。设计就是如此。我们毋须再纠结于 <code>map</code> 对数组和对象作用的不同之处。</li>
<li><strong>不可变的数据结构。</strong> 所有 Clojurescript 数据结构都是不可变的。因此你再也不必纠结一些东西是否可变了。你也不需要切换编程范式,从可变到不可变。你完全在不可变数据结构的领地上。</li>
<li><strong>一些基本函数是语言本身包含的。</strong> 像 map、filter、reduce、compose 和<a href="https://clojure.github.io/clojure/" target="_blank" rel="external">很多其他</a>函数都是核心语言的一部分,不需要外界引入。因此你的脑子里不必记着 4 种不同版本的“map”了(Array.map、lodash.map、ramda.map、Immutable.map)。你只需要知道一个。</li>
<li><strong>它很简洁。</strong> 相对于其他任何编程语言,它只需要短短几行的代码就能表达你的想法。(通常少得多)</li>
<li><strong>函数式编程。</strong> Clojurescript 是一门彻底的函数式编程语言 —— 支持隐式返回声明、函数是一等公民、lambda 表达式等等。</li>
<li><strong>使用 JavaScript 中所需的任何内容。</strong> 你可以使用 JavaScript 的一切以及它的生态系统,从 <code>console.log</code> 到 npm 库都可以。</li>
<li><strong>性能。</strong> Clojurescript 使用 Google Closure 编译器来优化输出的 JavaScript。Bundle 体积小到极致。用于生产的打包过程不需要从设置优化到 <code>:advanced</code> 的复杂配置。</li>
<li><strong>可读的库代码。</strong> 有时候了解“这个库的功能是干嘛的?”很有用。当我使用 JavaScript 中的“跳转到定义处”,我通常都会看到被压缩或错位的源代码。Clojure 和 Clojurescript 的库都直接被显示成写出来的样子,因此不需离开你的编辑器去看一些东西如何工作就很简单,因为你可以直接阅读源码。</li>
<li><strong>是一种 LISP 方言。</strong> 很难列举出这方面的好处,因为太多了。我喜欢的一点是它的公式化,(有这么一种模式可以依靠)代码是用语言的数据结构来表达的。(这使得元编程很容易)。Clojure 不同于 LISP 因为它并不是 100% 的 <code>()</code>。它的代码和数据结构中可以使用 <code>[]</code> 和 <code>{}</code>,就像大多数编程语言那样。</li>
<li><strong>元编程。</strong> Clojurescript 允许你编写生成代码的代码。这一点有我不想掩盖的巨大内涵。其中之一是你可以高效地扩展语言本身。这是一个出自 <a href="http://www.braveclojure.com/writing-macros/" target="_blank" rel="external">Clojure for the Brave and True</a> 的例子:</li>
</ol>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">(defmacro infix</div><div class="line"> [infixed]</div><div class="line"> (list (second infixed) (first infixed) (last infixed)))</div><div class="line">(infix (1 + 1))</div><div class="line">=> 2</div><div class="line">(macroexpand '(infix (1 + 1)))</div><div class="line">=> (+ 1 1)</div><div class="line">; 这个宏把它传入 Clojure,Clojure 可以正确执行,因为是 Clojure 的原生语法。</div></pre></td></tr></table></figure>
<h3 id="为什么它并不流行?"><a href="#为什么它并不流行?" class="headerlink" title="为什么它并不流行?"></a>为什么它并不流行?</h3><p>既然说它这么棒,可它怎么不上天呢?有人指出它已经很流行了,它只是不如 lodash、React、Redux 等等那么流行而已。但既然它更好,不应该和它们一样流行吗?为什么偏爱函数式编程、不可变性和 React 的 JS 开发者还没有迁移到 Clojurescript?</p>
<p><strong>因为缺少工作机会吗?</strong> Clojure 可以编译成 JavaScript 和 Java。它实际上也可以编译成 C#。因此大量的 JavaScript 工作都可以当作 Clojurescript 工作。它是一种函数式语言,用于为所有编译目标完成所有的任务。先不论它的价值如何体现,2017 StackOverflow 的调查表明 <a href="http://www.techrepublic.com/article/what-are-the-highest-paid-jobs-in-programming-the-top-earning-languages-in-2017/" target="_blank" rel="external">Clojure 开发者的薪资水平是所有语言中全球平均最高的</a>。</p>
<p><strong>因为 JS 开发者很懒吗?</strong> 并不是。正如我在上面所展示的,我们做了大量的工作。有个词叫 <a href="https://medium.com/@ericclemmons/javascript-fatigue-48d4011b6fc4" target="_blank" rel="external">JavaScript 疲劳</a>,你可能已经听说过了。</p>
<p><strong>我们很抗拒,不想学点新东西吗?</strong> 并不是。 <a href="https://hackernoon.com/how-it-feels-to-learn-javascript-in-2016-d3a717dd577f" target="_blank" rel="external">我们已经因采用新技术而臭名昭著。</a></p>
<p><strong>因为缺乏熟悉的框架和工具吗?</strong> 这感觉上可能是个原因,但 Javascript 中有的东西, Clojurescript 都有与之对应的: <a href="https://github.com/Day8/re-frame" target="_blank" rel="external">re-frame</a> 对应 Redux、<a href="https://github.com/reagent-project/reagent" target="_blank" rel="external">reagent</a> 对应 React、<a href="https://github.com/bhauman/lein-figwheel" target="_blank" rel="external">figwheel</a> 对应 Webpack/热加载、<a href="https://github.com/technomancy/leiningen" target="_blank" rel="external">leiningen</a> 对应 yarn/npm、Clojurescript 对应 Underscore/Lodash。</p>
<p><strong>是因为括号的问题使得这门语言太难写了吗?</strong> 这方面也许谈的还不够多,但<a href="https://shaunlebron.github.io/parinfer/" target="_blank" rel="external">我们不必自己来区分圆括号方括号</a> 。基本上,Parinfer 使得 Clojure 成为了空格语言。</p>
<p><strong>因为在工作中很难使用?</strong> 可能是吧。它是一种新技术,就像 React 和 Redux 曾经那样,在某些时候也是很难推广的。即使也没什么技术限制 —— Clojurescript 集成到现有代码库和集成 React 的方式是类似的。你可以把 Clojurescript 加入到已经存在的代码库中,每次重写一个文件的旧代码,新代码依然可以和未更改的旧代码交互。</p>
<p><strong>没有足够受欢迎?</strong> 很不幸,我想这就是它的原因。我使用 JavaScript 一部分原因就是它拥有庞大的社区。Clojurescript 太小众了。我使用 React 的部分原因是它是由 Facebook 维护的。而 Clojure 的维护者是<a href="https://avatars2.githubusercontent.com/u/34045?v=3&s=400" target="_blank" rel="external">花大量时间思考的留着长发的家伙</a>。</p>
<p>有数量上的劣势,我认了。但“人多势众”否决了所有其他可能的因素。</p>
<p>假设有一条路通向 100 美元,它很不受欢迎,而另一条路通向 10 美元,它极其受欢迎,我会选择受欢迎的那条路吗?</p>
<p>恩,也许会的吧!那里有成功的先例。它一定比另一条路安全,因为更多的人选择了它。他们一定不会遇到什么可怕的事。而另一条路听起来美好,但我确定那一定是个陷阱。如果它像看起来那么美好,那么它就是最受欢迎的那条路了。</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*Y6orLTOgb6JFfjVdANVgCQ.png" alt=""></p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://hackernoon.com/functional-programming-in-JavaScript-is-an-antipattern-58526819f21e" target="_bla
</summary>
<category term="前端" scheme="http://suncafe.cc/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>如何充分利用JavaScript控制台</title>
<link href="http://suncafe.cc/2017/06/28/%5B%E8%AF%91%5D%E5%A6%82%E4%BD%95%E5%85%85%E5%88%86%E5%88%A9%E7%94%A8JavaScript%E6%8E%A7%E5%88%B6%E5%8F%B0/"/>
<id>http://suncafe.cc/2017/06/28/[译]如何充分利用JavaScript控制台/</id>
<published>2017-06-27T16:09:19.000Z</published>
<updated>2017-06-26T17:03:04.809Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://medium.freecodecamp.com/how-to-get-the-most-out-of-the-javascript-console-b57ca9db3e6d" target="_blank" rel="external">How to get the most out of the JavaScript console</a></li>
<li>原文作者:<a href="https://medium.freecodecamp.com/@darrylpargeter" target="_blank" rel="external">Darryl Pargeter</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:<a href="https://github.com/reid3290" target="_blank" rel="external">reid3290</a>、<a href="https://github.com/Aladdin-ADD" target="_blank" rel="external">Aladdin-ADD</a></li>
</ul>
</blockquote>
<hr>
<p><img src="https://cdn-images-1.medium.com/max/2000/1*mM2AMk0TRENA2zF2RMEebA.jpeg" alt=""></p>
<p>JavaScript 中最基本的调试工具之一就是 <code>console.log()</code>。<code>console</code> 还附带了一些其他好用的方法,可以添加到开发人员的调试工具包中。</p>
<p>你可以使用 <code>console</code> 执行以下任务:</p>
<ul>
<li>输出一个计时器来协助进行简单的基准测试</li>
<li>输出一个表格来以易读的格式显示一个数组或对象</li>
<li>使用 CSS 将颜色和其他样式选项应用于输出</li>
</ul>
<h3 id="Console-对象"><a href="#Console-对象" class="headerlink" title="Console 对象"></a>Console 对象</h3><p><code>console</code> 对象允许您访问浏览器的控制台。它允许你输出有助于调试代码的字符串、数组和对象。<code>console</code> 是 <code>window</code> 对象的属性,由<a href="https://www.w3schools.com/js/js_window.asp" target="_blank" rel="external">浏览器对象模型(BOM)</a>提供。</p>
<p>我们可以通过这两种方法之一访问 <code>console</code>:</p>
<ol>
<li><code>window.console.log('This works')</code></li>
<li><code>console.log('So does this')</code></li>
</ol>
<p>第二个选项本质上是对前者的引用,所以我们使用后者以精简代码。</p>
<p>关于 BOM 的快速提示:它没有设定标准,所以每家浏览器都以稍微不同的方式实现。我在 Chrome 和 Firefox 测试了所有示例,但你的输出可能有所不同,这取决于你使用的浏览器。</p>
<h3 id="输出文本"><a href="#输出文本" class="headerlink" title="输出文本"></a>输出文本</h3><p><img src="https://cdn-images-1.medium.com/max/800/1*eEnUT7quS8oCeOsoGn1Kxw.png" alt=""></p>
<p>将文本记录到控制台<br><code>console</code> 对象最常见的元素是 <code>console.log</code>,对于大多数情况,使用它就可以完成任务。</p>
<p>输出信息到控制台的四种方式:</p>
<ol>
<li><code>log</code></li>
<li><code>info</code></li>
<li><code>warn</code></li>
<li><code>error</code></li>
</ol>
<p>他们四个工作方式相同。你唯一要做的是给选择的方法传递一个或更多的参数。控制台会显示不同的图标来指示其记录级别。下面的例子中你可以看到 info 级别的记录和 warning/error 级别的不同之处。</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*AKbeddGNDqLYaJOMQlrrMw.png" alt=""></p>
<p>简单易读的输出</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*3yKUiYLyju8f9gE71w1Sxw.png" alt=""></p>
<p>输出东西太多将变得难以阅读</p>
<p>你可能注意到了 error 日志消息 —— 它比其他消息更显眼。它显示着红色的背景和<a href="https://en.wikipedia.org/wiki/Stack_trace" target="_blank" rel="external">堆栈跟踪</a>,而 <code>info</code> 和 <code>warn</code> 就不会。但是在 Chrome 中 <code>warn</code> 确实有一个黄色的背景。</p>
<p>视觉上的区分有助于你在控制台快速浏览辨别出错误或警告信息。你应该确保在准备生产的应用中移除它们,除非你打算让它们来警示其他操作你的代码的开发者。</p>
<h3 id="字符串替换"><a href="#字符串替换" class="headerlink" title="字符串替换"></a>字符串替换</h3><p>这个技术可以使用字符串中的占位符来替换你向方法中传入的其他参数。</p>
<p><strong>输入</strong>: <code>console.log('string %s', 'substitutions')</code></p>
<p><strong>输出</strong>: <code>string substitutions</code></p>
<p><code>%s</code> 是逗号后面第二个参数 <code>'substitutions'</code> 的占位符。任何的字符串、整数或数组都将被转换成字符串并替换 <code>%s</code>。如果你传入一个对象,它将显示为 <code>[object Object]</code>。</p>
<p>如果你想传入对象,你需要使用 <code>%o</code> 或者 <code>%O</code>,而不是 <code>%s</code>。</p>
<p><code>console.log('this is an object %o', { obj: { obj2: 'hello' }})</code></p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*WhqTGnch8S2kAIQYxXOLhw.png" alt=""></p>
<h4 id="数字"><a href="#数字" class="headerlink" title="数字"></a>数字</h4><p>字符串替换可以与整数和浮点数一起使用:</p>
<ul>
<li>整数使用 <code>%i</code> 或 <code>%d</code>,</li>
<li>浮点数使用 <code>%f</code>。</li>
</ul>
<p><strong>输入</strong>: <code>console.log('int: %d, floating-point: %f', 1, 1.5)</code></p>
<p><strong>输出</strong>:<code>int: 1, floating-point: 1.500000</code></p>
<p>可以使用 <code>%.1f</code> 来格式化浮点数,使小数点后仅显示一位小数。你可以用 <code>%.nf</code> 来显示小数点后 n 位小数。</p>
<p>如果我们使用上述例子显示小数点后一位小数来格式化浮点数值,它看起来这样:</p>
<p><strong>输入</strong>: <code>console.log('int: %d, floating-point: %.1f', 1, 1.5)</code></p>
<p><strong>输出</strong>: <code>int: 1, floating-point: 1.5</code></p>
<h4 id="格式化说明符"><a href="#格式化说明符" class="headerlink" title="格式化说明符"></a>格式化说明符</h4><ol>
<li><code>%s</code> | 使用字符串替换元素</li>
<li><code>%(d|i)</code>| 使用整数替换元素</li>
<li><code>%f</code>| 使用浮点数替换元素</li>
<li><code>%(o|O)</code> | 元素显示为一个对象</li>
<li><code>%c</code> | 应用提供的 CSS</li>
</ol>
<h4 id="字符串模板"><a href="#字符串模板" class="headerlink" title="字符串模板"></a>字符串模板</h4><p>随着 ES6 的出现,模板字符串是替换或连接的替代品。他们使用反引号(``)来代替引号,变量包裹在 <code>${}</code> 中:</p>
<pre><code>const a = 'substitutions';
console.log(`bear: ${a}`);
// bear: substitutions
</code></pre><p>对象在模板字符串中显示为 <code>[object Object]</code>,所以你将需要使用 <code>%o</code> 或 <code>%O</code> 替换以看到详情,或单独记录。</p>
<p>比起使用字符串连接:<code>console.log('hello' + str + '!');</code>,使用替换或模板可以创建更易读的代码。</p>
<h4 id="美妙的彩色插曲!"><a href="#美妙的彩色插曲!" class="headerlink" title="美妙的彩色插曲!"></a>美妙的彩色插曲!</h4><p>现在,是时候来点更有趣而多彩的东西了!</p>
<p>是时候用字符串替换让我们的 <code>console</code> 弹出丰富多彩的颜色了。</p>
<p>我将使用一个模仿 Ajax 的例子,给我们显示一个请求成功(用绿色)和失败(用红色)。这是输出和代码:</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*BRAhnRn9GpZgrUf_SQfi3A.png" alt=""></p>
<p>成功的小熊和失败的蝙蝠</p>
<pre><code>const success = [
'background: green',
'color: white',
'display: block',
'text-align: center'
].join(';');
const failure = [
'background: red',
'color: white',
'display: block',
'text-align: center'
].join(';');
console.info('%c /dancing/bears was Successful!', success);
console.log({data: {
name: 'Bob',
age: 'unknown'
}}); // "mocked" data response
console.error('%c /dancing/bats failed!', failure);
console.log('/dancing/bats Does not exist');
</code></pre><p>在字符串替换中使用 <code>%c</code> 占位符来应用你的样式规则。</p>
<pre><code>console.error('%c /dancing/bats failed!', failure);
</code></pre><p>然后把你的 CSS 元素作为参数,你就能看到应用 CSS 的日志了。 你也可以给你的字符串添加多个 <code>%c</code>。</p>
<pre><code>console.log('%cred %cblue %cwhite','color:red;','color:blue;', 'color: white;')
</code></pre><p>这将按照他们的代表的颜色输出字符 “red”、“blue” 和 “white”。</p>
<p>控制台仅仅支持少数 CSS 属性,建议你试验一下哪些支持哪些不支持。重申一下,你的输出结果可能因你的浏览器而异。</p>
<h3 id="其他可用的方法"><a href="#其他可用的方法" class="headerlink" title="其他可用的方法"></a>其他可用的方法</h3><p>还有几个其他可用的 <code>console</code> 方法。注意下面有几项还不是 API 标准,所以可能浏览器间互不兼容。这个例子使用的是 Firefox 51.0.1。</p>
<h4 id="Assert"><a href="#Assert" class="headerlink" title="Assert()"></a>Assert()</h4><p><code>Assert</code> 携带两个参数 —— 如果第一个参数计算为 false,那么它将显示第二个参数。</p>
<pre><code>let isTrue = false;
console.assert(isTrue, 'This will display');
isTrue = true;
console.assert(isTrue, 'This will not');
</code></pre><p>如果断言为 false,控制台将输出内容。它显示为一个上文提到的 error 级别的日志,给你显示一个红色的错误消息和堆栈跟踪。</p>
<h4 id="Dir"><a href="#Dir" class="headerlink" title="Dir()"></a>Dir()</h4><p><code>dir</code> 方法显示一个传入对象的可交互属性列表。</p>
<pre><code>console.dir(document.body);
</code></pre><p><img src="https://cdn-images-1.medium.com/max/800/1*4Zj5EuPTHcQH5-K0NWHb7g.png" alt=""></p>
<p>Chrome 会显示不同的层级<br>最终,<code>dir</code> 仅仅能节省一两次点击,如果你需要检查一个 API 响应返回的对象,你可以用它结构化地显示出来以节约一些时间。</p>
<h4 id="Table"><a href="#Table" class="headerlink" title="Table()"></a>Table()</h4><p><code>table</code> 方法用一个表格显示数组或对象</p>
<pre><code>console.table(['Javascript', 'PHP', 'Perl', 'C++']);
</code></pre><p><img src="https://cdn-images-1.medium.com/max/800/1*nza7ZWxYG-_X47VJ54FtZg.png" alt=""></p>
<p>输出数组</p>
<p>数组的索引或对象的属性名位于左侧的索引栏,值显示在右侧列栏。</p>
<pre><code>const superhero = {
firstname: 'Peter',
lastname: 'Parker',
}
console.table(superhero);
</code></pre><p><img src="https://cdn-images-1.medium.com/max/800/1*BXhY3PzulYFzzcW-Qwga8Q.png" alt=""></p>
<p>输出对象</p>
<p><strong>Chrome 用户需要注意:</strong> 这是我的同事提醒我的,上述 <code>table</code> 方法的例子在 Chrome 中貌似不能工作。你可以通过将项目放入数组或对象数组中来解决此问题。</p>
<pre><code>console.table([['Javascript', 'PHP', 'Perl', 'C++']]);
const superhero = {
firstname: 'Peter',
lastname: 'Parker',
}
console.table([superhero]);
</code></pre><h4 id="Group"><a href="#Group" class="headerlink" title="Group()"></a>Group()</h4><p><code>console.group()</code> 由至少三个 <code>console</code> 调用组成,它可能是使用时需要打最多字的方法。但它也是最有用的方法之一(特别对使用 <a href="https://github.com/evgenyrodionov/redux-logger" target="_blank" rel="external">Redux Logger</a> 的开发者)。</p>
<p>稍基础的调用看起来是这样的:</p>
<pre><code>console.group();
console.log('I will output');
console.group();
console.log('more indents')
console.groupEnd();
console.log('ohh look a bear');
console.groupEnd();
</code></pre><p>这将输出多个层级,显示效果因你的显示器而异。</p>
<p>Firefox 显示成缩进列表:</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*xFU0AtDqgwLJVUwE4Yo9_w.png" alt=""></p>
<p>Chrome 显示成对象的风格:</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*9hJkBrf4uEXaC1PYe8bomQ.png" alt=""></p>
<p>每次调用 <code>console.group()</code> 都将开启一个新的组,如果在一个组内会创建一个新的层级。每次调用 <code>console.groupEnd()</code> 都会结束当前组或层级并向上移动一个层级。</p>
<p>我发现 Chrome 的输出样式更易读,因为它看起来像一个可折叠的对象。</p>
<p>你可以给 <code>group</code> 传入一个 header 参数,它将被显示并替代 <code>console.group</code>:</p>
<pre><code>console.group('Header');
</code></pre><p>如果你调用 <code>console.groupCollapsed()</code>,你可以从一开始就将这个组显示为折叠。据我所知,这个方法可能只有 Chrome 支持。</p>
<h4 id="Time"><a href="#Time" class="headerlink" title="Time()"></a>Time()</h4><p><code>time</code> 方法和上文的 <code>group</code> 方法类似,由两部分组成。</p>
<p>一个用于启动计时器的方法和一个停止它的方法。</p>
<p>一旦计时器完成,它将以毫秒为单位输出总运行时间。</p>
<p>启动计时器使用 <code>console.time('id for timer')</code>,结束计时器使用 <code>console.timeEnd('id for timer')</code>。您可以同时运行多达 10,000 个定时器。</p>
<p>输出结果可能有点像这样: <code>timer: 0.57ms</code>。</p>
<p>当你需要做一个快速的基准测试时,它非常有用。</p>
<h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>我们已经更深入地了解了 console 对象以及其中附带的其他一些方法。当我们需要调试代码时,这些方法是可用的好工具。</p>
<p>仍然有几种方法我没有谈论,因为他们的 API 依然在变动。具体可以阅读 <a href="https://developer.mozilla.org/en/docs/Web/API/console" target="_blank" rel="external">MDN Web API</a> 和 <a href="https://console.spec.whatwg.org/" target="_blank" rel="external">WHATWG 规范</a>。</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*0SNCJfem2WVKSJIDzConxg.png" alt=""></p>
<p><a href="https://developer.mozilla.org/en/docs/Web/API/console" target="_blank" rel="external">https://developer.mozilla.org/en/docs/Web/API/console</a></p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://medium.freecodecamp.com/how-to-get-the-most-out-of-the-javascript-console-b57ca9db3e6d" target="
</summary>
<category term="前端" scheme="http://suncafe.cc/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>JavaScript:回调是什么鬼?</title>
<link href="http://suncafe.cc/2017/06/22/%5B%E8%AF%91%5DJavaScript%EF%BC%9A%E5%9B%9E%E8%B0%83%E6%98%AF%E4%BB%80%E4%B9%88%E9%AC%BC/"/>
<id>http://suncafe.cc/2017/06/22/[译]JavaScript:回调是什么鬼/</id>
<published>2017-06-22T14:56:41.000Z</published>
<updated>2017-06-26T17:03:04.809Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://codeburst.io/javascript-what-the-heck-is-a-callback-aba4da2deced" target="_blank" rel="external">JavaScript: What the heck is a Callback?</a></li>
<li>原文作者:<a href="https://codeburst.io/@bmorelli25" target="_blank" rel="external">Brandon Morelli</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:<a href="https://github.com/reid3290" target="_blank" rel="external">reid3290</a>、<a href="https://github.com/wilsonandusa" target="_blank" rel="external">wilsonandusa</a></li>
</ul>
</blockquote>
<hr>
<p>配合简单的示例,用短短 6 分钟学习和理解回调的基本知识。</p>
<p><img src="https://cdn-images-1.medium.com/max/1000/1*pWGJIKats-zuumA3RQNEWQ.jpeg" alt=""></p>
<p>回调 —— 题图来自 <a href="https://unsplash.com/search/call?photo=qXn5L9BqRbE" target="_blank" rel="external">unsplash</a></p>
<h3 id="回调是什么?"><a href="#回调是什么?" class="headerlink" title="回调是什么?"></a>回调是什么?</h3><p><strong>简单讲:</strong> 回调是指在另一个函数执行完成<strong>之后</strong>被调用的函数 —— 因此得名“回调”。</p>
<p><strong>稍复杂地讲:</strong> 在 JavaScript 中,函数也是对象。因此,函数可以传入函数作为参数,也可以被其他函数返回。这样的函数称为<strong>高阶函数</strong>。被作为参数传入的函数就叫做<strong>回调函数</strong>。</p>
<p>^ 这听起来有点啰唆,让我们来看一些例子来简化一下。</p>
<h3 id="为什么我们需要回调?"><a href="#为什么我们需要回调?" class="headerlink" title="为什么我们需要回调?"></a>为什么我们需要回调?</h3><p>有一个非常重要的原因 —— JavaScript 是事件驱动的语言。这意味着,JavaScript 不会因为要等待一个响应而停止当前运行,而是在监听其他事件时继续执行。来看一个基本的例子:</p>
<pre><code>function first(){
console.log(1);
}
function second(){
console.log(2);
}
first();
second();
</code></pre><p>正如你所料,<code>first</code> 函数首先被执行,随后 <code>second</code> 被执行 —— 控制台输出下面内容:</p>
<pre><code>// 1
// 2
</code></pre><p>一切都如此美好。</p>
<p>但如果函数 <code>first</code> 包含某种不能立即执行的代码会如何呢?例如我们必须发送请求然后等待响应的 API 请求?为了模拟这种状况,我们将使用 <code>setTimeout</code>,它是一个在一段时间之后调用函数的 JavaScript 函数。我们将函数延迟 500 毫秒来模拟一个 API 请求,新代码长这样:</p>
<pre><code>function first(){
// 模拟代码延迟
setTimeout( function(){
console.log(1);
}, 500 );
}
function second(){
console.log(2);
}
first();
second();
</code></pre><p>现在理解 <code>setTimeout()</code> 是如何工作的并不重要,重要的是你看到了我们已经把 <code>console.log(1);</code> 移动到了 500 秒延迟函数内部。那么现在调用函数会发生什么呢?</p>
<pre><code>first();
second();
// 2
// 1
</code></pre><p>即使我们首先调用了 <code>first()</code> 函数,我们记录的输出结果却在 <code>second()</code> 函数之后。</p>
<p>这不是 JavaScript 没有按照我们想要的顺序执行函数的问题,而是 <strong>JavaScript 在继续向下执行 <code>second()</code> 之前没有等待 <code>first()</code> 响应</strong>的问题。</p>
<p>所以为什么给你看这个?因为你不能一个接一个地调用函数并希望它们按照正确的顺序执行。回调正是确保一段代码执行完毕之后再执行另一段代码的方式。</p>
<h3 id="创建一个回调"><a href="#创建一个回调" class="headerlink" title="创建一个回调"></a>创建一个回调</h3><p>好了,说了这么多,让我们创建一个回调!</p>
<p>首先,打开你的 Chrome 开发者工具(<strong>Windows: Ctrl + Shift + J</strong>)(<strong>Mac: Cmd + Option + J</strong>),在控制台输入下面的函数声明:</p>
<pre><code>function doHomework(subject) {
alert(`Starting my ${subject} homework.`);
}
</code></pre><p>上面,我们已经创建了 <code>doHomework</code> 函数。我们的函数携带一个变量,是我们正在研究的课题。在控制台输入下面内容调用你的函数:</p>
<pre><code>doHomework('math');
// Alerts: Starting my math homework.
</code></pre><p>现在把我们的回调加进来,我们传入 <code>callback</code> 作为 <code>doHomework()</code> 的最后一个参数。这个回调函数是我们定义在接下来要调用的 <code>doHomework()</code> 函数的第二个参数。</p>
<pre><code>function doHomework(subject**, callback**) {
alert(`Starting my ${subject} homework.`);
**callback();**
}
doHomework('math'**, function() {
alert('Finished my homework');
}**);
</code></pre><p>如你所见,如果你将上面的代码输入控制台,你将依次得到两个警告:第一个是“starting homework”,接着是“finished homework”。</p>
<p>但是你的回调函数并不总是必须定义在函数调用里面,它们也可以定义在你代码中的其他位置,比如这样:</p>
<pre><code>function doHomework(subject, callback) {
alert(`Starting my ${subject} homework.`);
callback();
}
function alertFinished(){
alert('Finished my homework');
}
**doHomework('math', alertFinished);**
</code></pre><p>这个例子的结果和之前的例子完全一致。如你所见,我们在 <code>doHomework()</code> 函数调用中传入了 <code>alertFinished</code> 函数定义作为参数!</p>
<h3 id="实际应用案例"><a href="#实际应用案例" class="headerlink" title="实际应用案例"></a>实际应用案例</h3><p>上周我发表了一篇关于如何<a href="https://hackernoon.com/build-a-simple-twitter-bot-with-node-js-in-just-38-lines-of-code-ed92db9eb078" target="_blank" rel="external">用 38 行代码构建一个 Twitter 机器人</a>的文章。文中的代码可以实现的唯一原因就是我使用了 <a href="https://dev.twitter.com/rest/public" target="_blank" rel="external">Twitters API</a>。当你向一个 API 发送请求,在你操作响应内容之前你必须等待这个响应。这是回调在实际应用中的绝佳案例。请求长这样:</p>
<pre><code>T.get('search/tweets', params, function(err, data, response) {
if(!err){
// 这里是施展魔法之处
} else {
console.log(err);
}
})
</code></pre><ul>
<li><code>T.get</code> 仅仅意味着我们将要向 Twitter 发送一个 get 请求</li>
<li>这个请求中有三个参数:<code>‘search/tweets’</code> 是请求的路径,<code>params</code> 是搜索参数,随后的一个匿名函数是我们的回调。</li>
</ul>
<p>回调在这里很重要,因为在我们的代码继续运行之前我们需要等待一个来自服务端的响应。我们并不知道 API 请求会成功还是会失败,所以通过 get 向 search/tweets 发送了请求参数以后,我们要等待。一旦 Twitter 响应,我们的回调函数就被调用。Twitter 要么发送一个 <code>err</code>(error)对象,要么发送一个 <code>response</code> 对象返回给我们。在我们的回调函数中我们可以使用 <code>if()</code> 语句来区分请求是否成功,然后相应地处理新数据。</p>
<h3 id="你做到了"><a href="#你做到了" class="headerlink" title="你做到了"></a>你做到了</h3><p>干得漂亮!你现在(理想状况下)已经理解了回调是什么,回调如何工作。这只是回调的冰山一角,记住学无止境啊!我每周都会更新一些文章/教程,如果你愿意接收每周一次的推送,<a href="https://docs.google.com/forms/d/e/1FAIpQLSeQYYmBCBfJF9MXFmRJ7hnwyXvMwyCtHC5wxVDh5Cq--VT6Fg/viewform" target="_blank" rel="external">点击这里</a>输入你的邮箱订阅吧!</p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://codeburst.io/javascript-what-the-heck-is-a-callback-aba4da2deced" target="_blank" rel="external"
</summary>
<category term="前端" scheme="http://suncafe.cc/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>Airbnb 的前端重构</title>
<link href="http://suncafe.cc/2017/06/04/%5B%E8%AF%91%5DAirbnb%E7%9A%84%E5%89%8D%E7%AB%AF%E9%87%8D%E6%9E%84/"/>
<id>http://suncafe.cc/2017/06/04/[译]Airbnb的前端重构/</id>
<published>2017-06-04T13:16:07.000Z</published>
<updated>2017-06-26T17:03:04.809Z</updated>
<content type="html"><![CDATA[<blockquote>
<ul>
<li>原文地址:<a href="https://medium.com/airbnb-engineering/rearchitecting-airbnbs-frontend-5e213efc24d2" target="_blank" rel="external">Rearchitecting Airbnb’s Frontend</a></li>
<li>原文作者:<a href="https://medium.com/@AdamRNeary" target="_blank" rel="external">Adam Neary</a></li>
<li>译文出自:<a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a></li>
<li>译者:<a href="https://github.com/sunui" target="_blank" rel="external">sunui</a></li>
<li>校对者:</li>
</ul>
</blockquote>
<p>概述:最近,我们重新思考了 Airbnb 代码库中 JavaScript 端的架构。本文将讨论:(1)催生一些变化的产品驱动因素,(2)我们如何一步步摆脱遗留的 Rails 解决方案,(3)一些新技术栈的关键性支柱。彩蛋:我们将讨论接下来要做的事。</p>
<p>Airbnb 每天接收超过 7500 万次搜索,这使得搜索页面成为我们流量最高的页面。近十年来,工程师们一直在发展、加强、和优化 Rails 输出页面的方式。</p>
<p>最近,我们转移到了主页以外的垂直页面,<a href="https://www.airbnb.com/new" target="_blank" rel="external">来介绍一些体验和去处</a>。作为 web 端新增产品的一部分,我们花时间重新思考了搜索体验本身。</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*VMRwDmHVeYC3YnJhhtKn4Q.gif" alt=""></p>
<p>用于一个广泛搜索的路由间的过渡</p>
<p>我们希望用户体验流畅,要去斟酌用户在浏览页面和缩小搜索范围时遇到的内容,而不是从 <a href="http://www.airbnb.com" target="_blank" rel="external">www.airbnb.com</a> 着陆页导航,(1)访问一个搜索结果页,(2)访问一个单一列表页,(3)访问预订流程,(4)<strong>每个页面都由 Rails 单独传送</strong>。</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*epBwi0kxrcW5a6Wv-T4rSg.gif" alt=""></p>
<p>设计三种浏览搜索页的状态:新用户,老用户,和营销页。</p>
<p>在标签页之间切换和与列表进行交互应该感到惬意而轻松。事实上,如今没有什么可以阻止我们致力于在中小屏幕上提供与本地应用相符的体验。</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*y_gKoEDVvBvJpGq7hfcr_g.gif" alt=""></p>
<p>再标签页之间切换的未来概念,考虑异步加载内容</p>
<p>要开发这种类型的体验,我们需要摆脱传统的页面切换方法,最终我们兴奋地全面重构了前端代码。</p>
<p><a href="https://medium.com/@intelligibabble" target="_blank" rel="external">Leland Richardson</a> <a href="https://www.youtube.com/watch?v=tWitQoPgs8w" target="_blank" rel="external">最近在 React Conf 大会上发表了关于 React Native 的存在于高访问量 native 应用中的“褐色地带”。 </a>这篇文章将会探讨如何在类似的约束下进行强制性升级,不过是在 web 端。如果你遇到类似的情况,希望对你有帮助。</p>
<h3 id="从-Rails-之中解脱"><a href="#从-Rails-之中解脱" class="headerlink" title="从 Rails 之中解脱"></a>从 Rails 之中解脱</h3><p>在我们的烧烤开火之前,因为我们的线路图上存在所有有趣的<a href="https://developers.google.com/web/progressive-web-apps/" target="_blank" rel="external">渐进式 web 应用</a>(WPA),我们需要从 Rails 中解脱出来(或者至少在 Airbnb 用 Rails 提供单独页面的这种方式)。</p>
<p>不幸的是,就在几个月前,我们的搜索页还包含一些非常老旧的代码,像指环王一样,触碰它就要小心自负后果。有趣的事实:我曾尝试用一个简单的 React 组件替换一个 Rails presenter 备份过的小巧的 <a href="http://handlebarsjs.com/" target="_blank" rel="external">Handlebars</a> 模板,突然很多完全不相关的部分都崩掉了——甚至 API 响应都除了问题。原来,presenter 改变了后备 Rails 模型,多年来即使在 UI 没有渲染的时候,它也影响着所有的下游数据。</p>
<p>简而言之,我们在这个项目中,像 Indiana Jone 用自己的宝物交换了一袋沙子,突然间庙宇开始崩塌,我们正在从石块中奔跑。</p>
<h4 id="第-1-步:-调整-API-数据"><a href="#第-1-步:-调整-API-数据" class="headerlink" title="第 1 步: 调整 API 数据"></a>第 1 步: 调整 API 数据</h4><p>当使用 Rails 在服务器端渲染页面时,你可以用任何你喜欢的方式把数据丢给服务器端的 React 组件。Controllers、helpers 和 presenters 能生成任何形式的数据,甚至当你把部分页面迁移到 React 时,每个组件都能处理它所需的任何数据。</p>
<p>但一旦你想渲染客户端路由,你需要能够以预定的形式动态请求所需的数据。将来我们可能用类似 <a href="http://graphql.org/" target="_blank" rel="external">GraphQL</a> 的东西解决这个问题,但是现在暂且把它放到一边吧,因为这件事和重构代码没太大关系。相反,我们选择在我们的 API 的 “v2” 上进行调整,我们需要我们所有的组件来开始处理规范的数据格式。</p>
<p>如果你发现你自己和我们情况类似并且是一个大型的应用,你可能发现我们像我们这样做,规划迁移现有的服务器端数据管道是很容易的。简单地在任何地方用 Rails 渲染一个React组件,并确保数据输入是 API 所规定的类型。你可以用客户端的 React PropTypes 来进一步验证数据类型是否与 API v2 一致。</p>
<p>对我们来说棘手的问题是和那些参与客户预定流程交互的团队协作:商业旅游、发展、度假租赁团队;中国和印度市场团队,灾难恢复团队…等等,我们需要重新培训所有这些人,即使在技术上可以将数据直接传递到正在呈现的组件上(“是的,我明白,这仅仅是一种实验,但是…”),所有的数据都要通过 API。</p>
<h4 id="第-2-步:-非-API-数据-配置、试验、惯用语、本地化、-国际化…"><a href="#第-2-步:-非-API-数据-配置、试验、惯用语、本地化、-国际化…" class="headerlink" title="第 2 步: 非 API 数据: 配置、试验、惯用语、本地化、 国际化…"></a>第 2 步: 非 API 数据: 配置、试验、惯用语、本地化、 国际化…</h4><p>有一类独特的数据和我们设想的 API 化的数据不同,包括应用配置,用户试验任务,国际化,本地化等等类似的问题。近年来,Airbnb 已经建立了一套难以置信的工具来支持这些功能,但是把这些数据传送到前端的机制就不那么令人愉快了(在革命开始之前,或许就已经很蹩脚了!)。</p>
<p>我们使用 <a href="https://www.npmjs.com/package/hypernova" target="_blank" rel="external">Hypernova</a> 来服务端渲染 React,但是在我们此次重构深入之前,无论服务端渲染时 React 组件中的试验交付会不会爆发或者客户端上提供的字符串转换是否都可以在服务器上可靠地使用,这些都还有点模糊。最重要的是,如果服务器和客户端输出匹配不到位,页面不仅会不断闪烁刷新 diff,还可以在加载后重新渲染整个页面,这对于性能来说很可怕。</p>
<p>更糟糕的是,我们有很久以前写过一些神奇的 Rails 功能,比如 <code>add_bootstrap_data(key, value)</code> 表面上可以在 Rails 中的任何地方调用,通过 <code>BootstrapData.get(key)</code> 使数据在客户端的全局可用(再次强调,对 Hypernova 来说已经不必要了)。这作为小团队的一个实用程序开始成为对大团队和应用来说不可溯源的巫术。由于每个团队拥有不同的页面或功能,因此“数据清洗”变得越来越棘手,因此每个团队都会培养出一种不同的加载配置的机制,以满足其独特需求。</p>
<p>显然,这已经崩溃了,所以我们融合了一个用于引导非 API 数据的规范机制,我们开始将所有应用程序和页面迁移到 Rails 和 React/Hypernova 之间的这种切换。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div></pre></td><td class="code"><pre><div class="line">import React, { PropTypes } from 'react';</div><div class="line">import { compose } from 'redux';</div><div class="line"></div><div class="line">import AirbnbUser from '[our internal user management library]';</div><div class="line">import BootstrapData from '[our internal bootstrap library]';</div><div class="line">import Experiments from '[our internal experiment library]';</div><div class="line">import KillSwitch from '[our internal kill switch library]';</div><div class="line">import L10n from '[our internal l10n library]';</div><div class="line">import ImagePaths from '[our internal CDN pipeline library]';</div><div class="line">import withPhrases from '[our internal i18n library]';</div><div class="line">import { forbidExtraProps } from '[our internal propTypes library]';</div><div class="line"></div><div class="line">const propTypes = forbidExtraProps({</div><div class="line"> behavioralUid: PropTypes.string,</div><div class="line"> bootstrapData: PropTypes.object,</div><div class="line"> experimentConfig: PropTypes.object,</div><div class="line"> i18nInit: PropTypes.object,</div><div class="line"> images: PropTypes.object,</div><div class="line"> killSwitches: PropTypes.objectOf(PropTypes.bool),</div><div class="line"> phrases: PropTypes.object,</div><div class="line"> userAttributes: PropTypes.object,</div><div class="line">});</div><div class="line"></div><div class="line">const defaultProps = {</div><div class="line"> behavioralUid: null,</div><div class="line"> bootstrapData: {},</div><div class="line"> experimentConfig: {},</div><div class="line"> i18nInit: null,</div><div class="line"> images: {},</div><div class="line"> killSwitches: {},</div><div class="line"> phrases: {},</div><div class="line"> userAttributes: null,</div><div class="line">};</div><div class="line"></div><div class="line">function withHypernovaBootstrap(App) {</div><div class="line"> class HypernovaBootstrap extends React.Component {</div><div class="line"> constructor(props) {</div><div class="line"> super(props);</div><div class="line"></div><div class="line"> const {</div><div class="line"> behavioralUid,</div><div class="line"> bootstrapData,</div><div class="line"> experimentConfig,</div><div class="line"> i18nInit,</div><div class="line"> images,</div><div class="line"> killSwitches,</div><div class="line"> userAttributes,</div><div class="line"> } = props;</div><div class="line"></div><div class="line"> // 清除服务器上的引导数据,以避免泄露数据</div><div class="line"> if (!global.document) {</div><div class="line"> BootstrapData.clear();</div><div class="line"> }</div><div class="line"> BootstrapData.extend(bootstrapData);</div><div class="line"> ImagePaths.extend(images);</div><div class="line"></div><div class="line"> // 在测试中用空对象调用 L10n.init 是不安全的</div><div class="line"> if (i18nInit) {</div><div class="line"> L10n.init(i18nInit);</div><div class="line"> }</div><div class="line"></div><div class="line"> if (userAttributes) {</div><div class="line"> AirbnbUser.setCurrent(userAttributes);</div><div class="line"> }</div><div class="line"></div><div class="line"> if (userAttributes && behavioralUid) {</div><div class="line"> Experiments.initializeGlobalConfiguration({</div><div class="line"> experiments: experimentConfig,</div><div class="line"> userId: userAttributes.id,</div><div class="line"> visitorId: behavioralUid,</div><div class="line"> });</div><div class="line"> } else {</div><div class="line"> Experiments.setExperiments(experimentConfig);</div><div class="line"> }</div><div class="line"></div><div class="line"> KillSwitches.extend(killSwitches);</div><div class="line"> }</div><div class="line"></div><div class="line"> render() {</div><div class="line"> // 理想情况下,我们只想传输 bootstrapData</div><div class="line"> // 如果你有从 redux 或 alt 数据 从服务端到 bootstrap</div><div class="line"> // 你当然可以只传输一个在 bootstrapData 中的 key</div><div class="line"> // 其他属性被处理但是不会传入应用</div><div class="line"> return <App bootstrapData={this.props.bootstrapData} />;</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> Bootstrap.propTypes = propTypes;</div><div class="line"> Bootstrap.defaultProps = defaultProps;</div><div class="line"> const wrappedComponentName = App.displayName || App.name || 'Component';</div><div class="line"> Bootstrap.displayName = `withHypernovaBootstrap(${wrappedComponentName})`;</div><div class="line"></div><div class="line"> return Bootstrap;</div><div class="line">}</div><div class="line"></div><div class="line">export default compose(withPhrases, withHypernovaBootstrap);</div></pre></td></tr></table></figure>
<p>用于引导非 API 数据规范的更高阶的组件</p>
<p>这个更高阶的组件做了两件更重要的事情:</p>
<ol>
<li>它接收一个引导数据作为普通的旧对象的规范形式,并且正确地初始化所有支持的工具,用于服务器渲染和客户端渲染。</li>
<li>它吞噬除了一切除了 <code>bootstrapData</code> ,它是另一个简单的对象,必要时把 <code><App></code> 组件传入 Redux 作为 children 使用。</li>
</ol>
<p>单纯来看,我们删除了 <code>add_bootstrap_data</code>,并阻止工程师将任意键传递到顶级的 React 组件。秩序被重新恢复,以前我们在客户端中动态地导航到路由,并且渲染材料复杂的 content,而不需要Rails来支持它。</p>
<h3 id="进击的前端"><a href="#进击的前端" class="headerlink" title="进击的前端"></a>进击的前端</h3><p>服务端的重构已经有了头绪,现在我们把目光转向客户端。</p>
<h4 id="懒加载的单页面应用"><a href="#懒加载的单页面应用" class="headerlink" title="懒加载的单页面应用"></a>懒加载的单页面应用</h4><p>那段日子已经过去了,朋友们,初始化时带着可怕 loading 的巨型单页面应用(SPA)已经不复存在了。当我们提出用 React Router 做客户端路由的方案时,可怕的 loading 是很多人提出拒绝的理由。</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*O2fK16vfyWaDT-IR61drPw.png" alt=""></p>
<p>在 chrome Timeline 中 route 包的懒加载</p>
<p>但是,如果你看到上面的内容,你就会发现<a href="https://webpack.github.io/docs/code-splitting.html" target="_blank" rel="external">代码分割</a> 和<a href="https://webpack.js.org/guides/lazy-load-react/" target="_blank" rel="external">延迟加载</a> 捆绑路由的影响。实质上,我们是在服务端渲染的页面并且仅仅传输最低限度的一部分用于在浏览器端交互的 Javascript 代码,然后我们利用浏览器的空余时间主动下载其余部分。</p>
<p>在 Rails 端,我们有一个 controller 用于通过 SPA 交付的所有路由。每一个 action 只负责:(1)出发客户端导航中的一切请求,(2)将数据和配置引导到 Hypernova。我们把每个 action (controller、helpers 和 presenters 之间)上千行的 Ruby 代码缩减到 20-30 行。实力碾压。</p>
<p>但这不仅仅是代码的不同…</p>
<p><img src="https://cdn-images-1.medium.com/max/800/1*EpKNHdS4Xzl9fRdGekUgEA.gif" alt=""></p>
<p>两种方式加载东京主页的对比(4-5 倍的差距)</p>
<p>…现在页面间的过渡像奶油般顺滑,并且这一步大幅提升了速度(约 5 倍)。而且我们我们可以实现文章开头的那张动画特性。</p>
<h4 id="异步组件"><a href="#异步组件" class="headerlink" title="异步组件"></a>异步组件</h4><p>之前的 React ,我们需要一次渲染整个页面,我们以前的 React 都是这么做的。但现在我们使用异步组件,类似<a href="https://medium.com/@thejameskyle/react-loadable-2674c59de178" target="_blank" rel="external">这种</a>方式, mount 以后加载组件层次结构的部分。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line">export default class AsyncComponent extends React.Component {</div><div class="line"> constructor(props) {</div><div class="line"> super(props);</div><div class="line"> this.state = {</div><div class="line"> Component: null,</div><div class="line"> };</div><div class="line"> }</div><div class="line"></div><div class="line"> componentDidMount() {</div><div class="line"> this.props.loader().then((Component) => {</div><div class="line"> this.setState({ Component });</div><div class="line"> });</div><div class="line"> }</div><div class="line"></div><div class="line"> render() {</div><div class="line"> const { Component } = this.state;</div><div class="line"> // `loader` 属性没有被使用。 它被提取,所以我们不会将其传递给包装的组件</div><div class="line"> // eslint-disable-next-line no-unused-vars</div><div class="line"> const { renderPlaceholder, placeholderHeight, loader, ...rest } = this.props;</div><div class="line"> if (Component) {</div><div class="line"> return <Component {...rest} />;</div><div class="line"> }</div><div class="line"></div><div class="line"> return renderPlaceholder ?</div><div class="line"> renderPlaceholder() :</div><div class="line"> <WrappedPlaceholder height={placeholderHeight} />;</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line">AsyncComponent.propTypes = {</div><div class="line"> // 注意 loader 是返回一个 promise 的函数。</div><div class="line"> // 这个 promise 应该处理一个可渲染的组件。</div><div class="line"> loader: PropTypes.func.isRequired,</div><div class="line"> placeholderHeight: PropTypes.number,</div><div class="line"> renderPlaceholder: PropTypes.func,</div><div class="line">};</div></pre></td></tr></table></figure>
<p>这对于最初不可见的重量级元素尤其有用,比如 Modals 和 Panels。我们的明确目标是精确地提供初始化页面可见部分所需的 所需的 JavaScript,并使其可交互,而不只一行。这也意味着如果,比方说团队想使用 D3 用于页面弹窗的一个图表,而其他部分不使用 D3,这时候他们就可以权衡一下下载仓库的代码,可以把他们的弹窗代码和其他代码隔离出来。</p>
<p>最重要的是,它可以简单地在任何需要的地方使用:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line">import React from 'react';</div><div class="line">import AsyncComponent from '../../../components/AsyncComponent';</div><div class="line">import scheduleAsyncLoad from '../../../utils/scheduleAsyncLoad';</div><div class="line"></div><div class="line">function mapLoader() {</div><div class="line"> return new Promise((resolve) => {</div><div class="line"> if (process.env.LAZY_LOAD) {</div><div class="line"> return airPORT('./Map', 'HomesSearchMap')</div><div class="line"> .then(x => x.default || x);</div><div class="line"> }</div><div class="line"> });</div><div class="line">}</div><div class="line"></div><div class="line">export function scheduleMapLoad() {</div><div class="line"> scheduleAsyncLoad(searchResultsMapLoader);</div><div class="line">}</div><div class="line"></div><div class="line">export default function MapAsync(props) {</div><div class="line"> return <AsyncComponent loader={mapLoader} {...props} />;</div><div class="line">}</div><div class="line">view raw</div></pre></td></tr></table></figure>
<p>这里我们可以简单地把我们的同步版本的地图换成异步版本,这在小断点上特别有用,用户通过点击按钮显示地图。考虑到大多数用户用手机,在担心 Google 地图之前,让他们进入互动这样会缩短加载时的焦虑感。</p>
<p>另外,注意 <code>scheduleAsyncLoad()</code> 的效率,在用户交互之前就要请求包。考虑到地图如此频繁的使用,我们不需要等待用户交互就去请求它。而是在用户进入主页和搜索页的时候就把它加入队列,如果用户在下载完成之前就请求了它,他们会看到一个 <code><Loader /></code> 直到组件可用。没毛病。</p>
<p>这种方法的最后一个好处是 <code>HomesSearch_Map</code> 成为浏览器可以缓存的命名包。当我们分解较大的基于路由的捆绑包时,应用程序中 slowly-changing 的部分在更新时保持不变,从而进一步节省了 JavaScript 下载时间。</p>
<h4 id="构建无障碍的设计语言"><a href="#构建无障碍的设计语言" class="headerlink" title="构建无障碍的设计语言"></a>构建无障碍的设计语言</h4><p>毫无疑问,它保证的是一个专有的需求,但是我们已经开始构建内部组件库,其中辅助功能被强制为一个严格的约束。在接下来的几个月中,我们将替换所有与屏幕阅读器不兼容的 UI。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div></pre></td><td class="code"><pre><div class="line">import React, { PropTypes } from 'react';</div><div class="line"></div><div class="line">import { forbidExtraProps } from 'airbnb-prop-types';</div><div class="line"></div><div class="line">import CheckBox from '../CheckBox';</div><div class="line">import FlexBar from '../FlexBar';</div><div class="line">import Label from '../Label';</div><div class="line">import HideAt from '../HideAt';</div><div class="line">import ShowAt from '../ShowAt';</div><div class="line">import Spacing from '../Spacing';</div><div class="line">import Text from '../Text';</div><div class="line">import CheckBoxOnly from '../../private/CheckBoxOnly';</div><div class="line">import toggleArrayItem from '../../utils/toggleArrayItem';</div><div class="line"></div><div class="line">import ROOM_TYPES from '../../constants/roomTypes';</div><div class="line"></div><div class="line">const propTypes = forbidExtraProps({</div><div class="line"> id: PropTypes.string.isRequired,</div><div class="line"> roomTypes: PropTypes.arrayOf(PropTypes.oneOf(ROOM_TYPES.map(roomType => roomType.filterKey))),</div><div class="line"> onUpdate: PropTypes.func,</div><div class="line">});</div><div class="line"></div><div class="line">const defaultProps = {</div><div class="line"> roomTypes: [],</div><div class="line"> onUpdate() {},</div><div class="line">};</div><div class="line"></div><div class="line">export default function RoomTypeFilter({ id, roomTypes, onUpdate }) {</div><div class="line"> return (</div><div class="line"> <div></div><div class="line"> {ROOM_TYPES.map(({ id: roomTypeId, filterKey, iconClass: IconClass, title, subtitle }) => {</div><div class="line"> const inputId = `${id}-${roomTypeId}-Checkbox`;</div><div class="line"> const titleId = `${id}-${roomTypeId}-title`;</div><div class="line"> const subtitleId = `${id}-${roomTypeId}-subtitle`;</div><div class="line"> const selected = roomTypes.includes(filterKey);</div><div class="line"> const checkbox = (</div><div class="line"> <Spacing top={0.5} right={1}></div><div class="line"> <CheckBoxOnly</div><div class="line"> id={inputId}</div><div class="line"> describedById={subtitleId}</div><div class="line"> name={`${roomTypeId}-only`}</div><div class="line"> checked={selected}</div><div class="line"> onChange={() => onUpdate({ roomTypes: toggleArrayItem(roomTypes, filterKey) })}</div><div class="line"> /></div><div class="line"> </Spacing></div><div class="line"> );</div><div class="line"> return (</div><div class="line"> <div key={roomTypeId}></div><div class="line"> <ShowAt breakpoint="mediumAndAbove"></div><div class="line"> <Label htmlFor={inputId}></div><div class="line"> <FlexBar align="top" before={checkbox} after={<IconClass size={28} />}></div><div class="line"> <Spacing right={2}></div><div class="line"> <div id={titleId}></div><div class="line"> <Text light>{title}</Text></div><div class="line"> </div></div><div class="line"> <div id={subtitleId}></div><div class="line"> <Text small light>{subtitle}</Text></div><div class="line"> </div></div><div class="line"> </Spacing></div><div class="line"> </FlexBar></div><div class="line"> </Label></div><div class="line"> </ShowAt></div><div class="line"> <HideAt breakpoint="mediumAndAbove"></div><div class="line"> <Spacing vertical={2}></div><div class="line"> <CheckBox</div><div class="line"> id={roomTypeId}</div><div class="line"> name={roomTypeId}</div><div class="line"> checked={selected}</div><div class="line"> label={title}</div><div class="line"> onChange={() => onUpdate({ roomTypes: toggleArrayItem(roomTypes, filterKey) })}</div><div class="line"> subtitle={subtitle}</div><div class="line"> /></div><div class="line"> </Spacing></div><div class="line"> </HideAt></div><div class="line"> </div></div><div class="line"> );</div><div class="line"> })}</div><div class="line"> </div></div><div class="line"> );</div><div class="line">}</div><div class="line">RoomTypeFilter.propTypes = propTypes;</div><div class="line">RoomTypeFilter.defaultProps = defaultProps;</div></pre></td></tr></table></figure>
<p>通过我们的设计语言系统加入的无障碍设计到产品的例子</p>
<p>这个 UI 非常丰富,我们希望将 CheckBox 不仅与 title 相关联,还可以使用 <code>aria-describedby</code> 与 subtitle 关联。为了实现这一点,需要 DOM 中唯一的标识符,这意味着强制关联一个必须的 ID 作为任何调用方需要提供的属性。如果一个组件被用于生产,这些是 UI 是可以强制约束类型的,它提供内置的可访问性。</p>
<p>上面的代码也演示了我们的响应式实体 HideAt 和 ShowAt,它使我们能够大幅度地改变用户在不同屏幕尺寸下的体验,而无需使用 CSS 控制隐藏和显示。这造就了更精简的页面。</p>
<h4 id="关于状态的“外科”和“哲学”"><a href="#关于状态的“外科”和“哲学”" class="headerlink" title="关于状态的“外科”和“哲学”"></a>关于状态的“外科”和“哲学”</h4><p>不涉及关于如何处理应用程序状态的争论的前端文章不是完整的前端文章。</p>
<p>我们使用 Redux 来处理所有的 API 数据和“全局”数据比如认证状态和体验配置。个人来讲我喜欢 <a href="https://github.com/lelandrichardson/redux-pack" target="_blank" rel="external">redux-pack</a> 处理异步,你会发现新大陆。</p>
<p>然而,当遇到页面上所有的复杂性——特别是围绕搜索的——对于一些像表单元素这样低级的用户交互使用 redux 就没那么好用了。我们发现无论如何优化,Redux 循环依然会造成输入体验的卡顿。</p>
<p><img src="https://cdn-images-1.medium.com/max/600/1*12LgecpKz8HA2e2evkYacw.png" alt=""></p>
<p>我们的房间类型筛选器 (代码在上面)</p>
<p>所以对于用户的所有操作我们使用组件的本地状态,除非触发路由变化或者网络请求才是用 Redux,并且我们没再遇到什么麻烦。</p>
<p>同时,我喜欢 Redux container 组件的那种感觉,并且我们即使带有本地状态,我们依然可以构建可以共享的高阶组件。一个伟大的例子就是我们的筛选功能。搜索<a href="https://www.airbnb.com/s/Detroit--MI--United-States/homes" target="_blank" rel="external">在底特律的家</a>,你会在页面上看见几个不同的面板,每一个都可以独立操作,你可以更改你的搜索条件。在不同的断点之间,实际上有几十个组件需要知道当前应用的搜索过滤器以及如何更新它们,在用户交互期间被暂时或z正式地被用户接受。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div></pre></td><td class="code"><pre><div class="line">import React, { PropTypes } from 'react';</div><div class="line">import { connect } from 'react-redux';</div><div class="line"></div><div class="line">import SearchFiltersShape from '../../shapes/SearchFiltersShape';</div><div class="line">import { isDirty } from '../utils/SearchFiltersUtils';</div><div class="line"></div><div class="line">function mapStateToProps({ exploreTab }) {</div><div class="line"> const {</div><div class="line"> responseFilters,</div><div class="line"> } = exploreTab;</div><div class="line"></div><div class="line"> return {</div><div class="line"> responseFilters,</div><div class="line"> };</div><div class="line">}</div><div class="line"></div><div class="line">export const withFiltersPropTypes = {</div><div class="line"> stagedFilters: SearchFiltersShape.isRequired,</div><div class="line"> responseFilters: SearchFiltersShape.isRequired,</div><div class="line"> updateFilters: PropTypes.func.isRequired,</div><div class="line"> clearFilters: PropTypes.func.isRequired,</div><div class="line">};</div><div class="line"></div><div class="line">export const withFiltersDefaultProps = {</div><div class="line"> stagedFilters: {},</div><div class="line"> responseFilters: {},</div><div class="line"> updateFilters() {},</div><div class="line"> clearFilters() {},</div><div class="line">};</div><div class="line"></div><div class="line">export default function withFilters(WrappedComponent) {</div><div class="line"> class WithFiltersHOC extends React.Component {</div><div class="line"> constructor(props) {</div><div class="line"> super(props);</div><div class="line"> this.state = {</div><div class="line"> stagedFilters: props.responseFilters,</div><div class="line"> };</div><div class="line"> }</div><div class="line"></div><div class="line"> componentWillReceiveProps(nextProps) {</div><div class="line"> if (isDirty(nextProps.responseFilters, this.props.responseFilters)) {</div><div class="line"> this.setState({ stagedFilters: nextProps.responseFilters });</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> render() {</div><div class="line"> const { responseFilters } = this.props;</div><div class="line"> const { stagedFilters } = this.state;</div><div class="line"> return (</div><div class="line"> <WrappedComponent</div><div class="line"> {...this.props}</div><div class="line"> stagedFilters={stagedFilters}</div><div class="line"> updateFilters={({ updateObj, keysToRemove }, callback) => {</div><div class="line"> const newStagedFilters = omit({ ...stagedFilters, ...updateObj }, keysToRemove);</div><div class="line"> this.setState({</div><div class="line"> stagedFilters: newStagedFilters,</div><div class="line"> }, () => {</div><div class="line"> if (callback) {</div><div class="line"> // setState callback can be called before withFilter state</div><div class="line"> // propagates to child props.</div><div class="line"> callback(newStagedFilters);</div><div class="line"> }</div><div class="line"> });</div><div class="line"> }}</div><div class="line"> clearFilters={() => {</div><div class="line"> this.setState({</div><div class="line"> stagedFilters: responseFilters,</div><div class="line"> });</div><div class="line"> }}</div><div class="line"> /></div><div class="line"> );</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> const wrappedComponentName = WrappedComponent.displayName</div><div class="line"> || WrappedComponent.name</div><div class="line"> || 'Component';</div><div class="line"></div><div class="line"> WithFiltersHOC.WrappedComponent = WrappedComponent;</div><div class="line"> WithFiltersHOC.displayName = `withFilters(${wrappedComponentName})`;</div><div class="line"> if (WrappedComponent.propTypes) {</div><div class="line"> WithFiltersHOC.propTypes = {</div><div class="line"> ...omit(WrappedComponent.propTypes, 'stagedFilters', 'updateFilters', 'clearFilters'),</div><div class="line"> responseFilters: SearchFiltersShape,</div><div class="line"> };</div><div class="line"> }</div><div class="line"> if (WrappedComponent.defaultProps) {</div><div class="line"> WithFiltersHOC.defaultProps = { ...WrappedComponent.defaultProps };</div><div class="line"> }</div><div class="line"></div><div class="line"> return connect(mapStateToProps)(WithFiltersHOC);</div><div class="line">}</div></pre></td></tr></table></figure>
<p>这里我们有一个利落的技巧。每一个需要和筛选交互的组件只需被 HOC 包裹起来,你就能做到了。它甚至还有属性类型。每个组件都通过 Redux 连接到<strong>responseFilters</strong>(与当前显示的结果相关联的那些),并同时保有一个本地 stagedFilters 状态对象用于更改。</p>
<p>通过以这种方式处理状态,与我们的价格滑块进行交互对页面的其余部分没有影响,所以表现很好。而且但所有过滤器面板都具有相同的功能签名,因此开发也很简单。</p>
<h3 id="未来做些什么"><a href="#未来做些什么" class="headerlink" title="未来做些什么?"></a>未来做些什么?</h3><p>既然现在已经良策在手,我们可以把目光转向未来。</p>
<ul>
<li><a href="https://www.ampproject.org/" target="_blank" rel="external">AMP</a> 核心预订流程中的所有页面的 AMP 版本将会实现亚秒级(某些情况下)在手机 web 上 Google 搜索的 <strong>可交互时间</strong>,通过移动网络和桌面网络,所需的许多更改将在 P50 / P90 / P95 冷负载时间内实现显着改善。</li>
<li><a href="https://developers.google.com/web/progressive-web-apps/" target="_blank" rel="external">PWA</a> 功能将实现亚秒级(在某些情况下)返回访客的<strong>可交互时间</strong>,并将打开离线优先功能的大门,因此对于具有脆弱网络连接的用户非常关键。</li>
<li>将最终的锤子应用到传统的技术/框架上将会将包大小减少一半。这不是华而不实的工作,我们最终翻出 jQuery、Alt、Bootstrap、Underscore 以及所有额外的 CSS 请求(他们使渲染停滞,并且将近 97% 的规则是不会被使用!)不仅精简了我们的代码,还精简了新员工在上升时需要学习的足迹。</li>
<li>最后,yeoman 的手动捕捉瓶颈的工作、异步加载代码在初始渲染时不可见、避免不必要的重新渲染、并降低重新渲染的成本,这些改进正是拖拉机和顶级跑车之间的区别。</li>
</ul>
<p>下次请收听我们将追逐的这些机会的成果。因为这么多的成果会有一些数量上的冲突,我们将尽量选择一些具体的成果在下篇文章中总结。</p>
<p><strong>自然,如果你欣赏本文并觉得这是一个有趣的挑战,我们一直在寻找优秀出色的人<a href="https://www.airbnb.com/careers/departments/engineering" target="_blank" rel="external">加入团队</a>。如果你只想做一些交流,那么随时可以点击我的 twitter <a href="https://twitter.com/AdamRNeary" target="_blank" rel="external">@adamrneary</a>。</strong></p>
<p>最后,深切地向 <a href="https://twitter.com/therealsalih" target="_blank" rel="external">Salih Abdul-Karim</a> 和 <a href="https://twitter.com/hugoahlberg" target="_blank" rel="external">Hugo Ahlberg</a> 两位体验设计师致敬,他们的令人动容的动画至今让我目不转睛。许多工程师在他们的领域值得赞美,作出努力人的名单难以一一列出的,但绝对包括 Nick Sorrentino、<a href="https://medium.com/@lencioni" target="_blank" rel="external">Joe Lencioni</a>、<a href="https://medium.com/@mikeland86" target="_blank" rel="external">Michael Landau</a>、Jack Zhang、Walker Henderson 和 Nico Moschopoulos.</p>
<hr>
<blockquote>
<p><a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区,文章来源为 <a href="https://juejin.im" target="_blank" rel="external">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android" target="_blank" rel="external">Android</a>、<a href="https://github.com/xitu/gold-miner#ios" target="_blank" rel="external">iOS</a>、<a href="https://github.com/xitu/gold-miner#react" target="_blank" rel="external">React</a>、<a href="https://github.com/xitu/gold-miner#前端" target="_blank" rel="external">前端</a>、<a href="https://github.com/xitu/gold-miner#后端" target="_blank" rel="external">后端</a>、<a href="https://github.com/xitu/gold-miner#产品" target="_blank" rel="external">产品</a>、<a href="https://github.com/xitu/gold-miner#设计" target="_blank" rel="external">设计</a> 等领域,想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner" target="_blank" rel="external">掘金翻译计划</a>。</p>
</blockquote>
]]></content>
<summary type="html">
<blockquote>
<ul>
<li>原文地址:<a href="https://medium.com/airbnb-engineering/rearchitecting-airbnbs-frontend-5e213efc24d2" target="_blank" rel=
</summary>