-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1101 lines (1079 loc) · 324 KB
/
search.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"?>
<search>
<entry>
<title>Antd的Form组件配合Mobx双向数据绑定</title>
<url>/2019/08/09/Antd%E7%9A%84Form%E7%BB%84%E4%BB%B6%E9%85%8D%E5%90%88Mobx%E5%8F%8C%E5%90%91%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A/</url>
<content><![CDATA[<p>最近忙于赶项目,一直没有时间去更新博客,今天得空,正好把最近在项目中使用Mobx和Antd中的Form组件进行双向数据绑定的方法记录一下。</p>
<h1 id="为什么需要双向数据绑定?"><a href="#为什么需要双向数据绑定?" class="headerlink" title="为什么需要双向数据绑定?"></a>为什么需要双向数据绑定?</h1><p>在后台管理系统中,表单是及其重要的,因为我们的增、改、查三个操作都需要用到表单。增加和查询一般是手动填入的,而修改则是需要将已有的数据先反显到表单中,再进行修改。这个反显的过程就需要对表单进行双向数据绑定了。</p>
<h1 id="怎么进行双向数据绑定?"><a href="#怎么进行双向数据绑定?" class="headerlink" title="怎么进行双向数据绑定?"></a>怎么进行双向数据绑定?</h1><p>在Antd的Form组件中,使用Form.create()包装组件后,该组件的props属性就会多一个form对象,该对象提供了一些api,具体的可以查看Antd的官方文档进行查看,其中<code>getFieldDecorator</code>是该组件提供的双向数据绑定的api。直接上代码来看用法:</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><Form></span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">Form.Item</span>></span></span></span><br><span class="line"><span class="xml"> {getFieldDecorator('username', {</span></span><br><span class="line"><span class="xml"> rules: [{ required: true, message: 'Please input your username!' }],</span></span><br><span class="line"><span class="xml"> })(</span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">Input</span></span></span></span><br><span class="line"><span class="tag"><span class="xml"> <span class="attr">placeholder</span>=<span class="string">"Username"</span></span></span></span><br><span class="line"><span class="tag"><span class="xml"> /></span></span></span><br><span class="line"><span class="xml"> )}</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">Form.Item</span>></span></span></span><br><span class="line"></Form></span><br></pre></td></tr></table></figure>
<p>用法很简单,将控件对应的key值和配置项传入到<code>getFieldDecorator</code>函数中,然后再使用<code>getFieldDecorator</code>包装控件。这个配置项具体有哪些参数可以去查看官方文档,目前我在项目中用的比较多的就是校验功能,也就是rules配置,使用该参数可以配置校验规则和提示。<br>但是使用<code>getFieldDecorator</code>函数也有antd定制的规则:</p>
<ul>
<li>经过<code>getFieldDecorator</code>包装的控件,表单控件会自动添加<code>value</code>和<code>onChange</code>,数据同步将被Form接管。</li>
</ul>
<ul>
<li>不再需要也不应该用<code>onChange</code>来做同步,但还是可以继续监听<code>onChange</code>等事件。</li>
</ul>
<ul>
<li>不能用控件的<code>value</code>、<code>defaultValue</code>等属性来设置表单域的值,默认值可以用<code>getFieldDecorator</code>里的<code>initialValue</code>。</li>
</ul>
<ul>
<li>不应该用<code>setState</code>,可以使用<code>this.props.form.setFieldsValue</code>来动态改变表单值。<br>有了这些规则后,说明我们不能去使用value和onChange配合使用进行双向数据绑定了,而是要使用<code>this.props.form.setFieldsValue</code>来设置值,但是这样通过onChange又是及其的繁琐。</li>
</ul>
<h1 id="使用Mobx数据双向数据绑定"><a href="#使用Mobx数据双向数据绑定" class="headerlink" title="使用Mobx数据双向数据绑定"></a>使用Mobx数据双向数据绑定</h1><p>在我的项目中,用的状态管理库为Mobx,如何将Mobx中观察的数据传入到Form组件中,我们走了很多弯路,开始我们发现<code>getFieldDecorator</code>函数的配置项中提供了一个<code>initialValue</code>的属性,使用该属性可以将Mobx的数据反显到表单控件中,但是该属性仅是表单控件的初始值,类似于<code>defaultValue</code>的功能,所以只要我们在页面中手动触发了控件的<code>value</code>改变,<code>initialValue</code>就不会再生效了,也就是说反显是ok的,但是如果要操作就会出问题。后面仔细阅读文档后,发现Form.create()函数中提供了<code>mapPropsToFields</code>和<code>onFieldsChange</code>两个api,这两个api配合使用可以将Mobx观察的数据传入到Form组件中,而且组件修改时也能实时传入到Mobx中。具体用法如下:</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 将mobx中观察的数据 转换为mapPropsToFields所需要的结构</span></span><br><span class="line"><span class="keyword">const</span> objToForm = <span class="function">(<span class="params">obj = {}</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> target = {};</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">const</span> [key,value] <span class="keyword">of</span> <span class="built_in">Object</span>.entries(obj)){</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">typeof</span> value == <span class="string">'object'</span> && !<span class="built_in">Array</span>.isArray(value)){</span><br><span class="line"> target[key] = Form.createFormField(value);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> target[key] = Form.createFormField({value});</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> target;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> onFieldsChange = <span class="function">(<span class="params">props, changedFields</span>) =></span> {</span><br><span class="line"> props.setQueryData({...props.queryData, ...changedFields});</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mapPropsToFields = <span class="function">(<span class="params">props</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> objToForm(props.queryData);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">@Form.create({</span><br><span class="line"> mapPropsToFields,</span><br><span class="line"> onFieldsChange</span><br><span class="line">})</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">this</span>.state={};</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">const</span> {getFieldDecorator} = <span class="built_in">this</span>.props.form;</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">Form</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">Form.Item</span>></span></span></span><br><span class="line"><span class="xml"> {getFieldDecorator('username', {</span></span><br><span class="line"><span class="xml"> rules: [{ required: true, message: 'Please input your username!' }],</span></span><br><span class="line"><span class="xml"> })(</span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">Input</span></span></span></span><br><span class="line"><span class="tag"><span class="xml"> <span class="attr">placeholder</span>=<span class="string">"Username"</span></span></span></span><br><span class="line"><span class="tag"><span class="xml"> /></span></span></span><br><span class="line"><span class="xml"> )}</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">Form.Item</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">Form</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>主要是要将<code>mapPropsToFields</code>方法中接受的Mobx数据转换为Form组件可接受的数据结构,并且使用<code>Form.createFormField</code>包装数据,而<code>onFieldsChange</code>方法则是将当前改变的控件数据再提供给Mobx去使用,这样就形成了一个双向数据绑定的效果。<br>但是需要注意的是,<code>onFieldsChange</code>函数提供的数据并不是简单的key: value这种直接可以使用的键值对了,如果需要拿到表单数据进行接口交互的话,需要再将Mobx中的数据进行转换一下,或者直接使用<code>this.props.form.validateFields</code>方法拿到校验通过的所有表单数据来进行交互。</p>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>React</tag>
<tag>Antd</tag>
<tag>Mobx</tag>
<tag>Form</tag>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>HelloWorld</title>
<url>/2019/03/26/HelloWorld/</url>
<content><![CDATA[<h2 id="新的开始"><a href="#新的开始" class="headerlink" title="新的开始"></a>新的开始</h2><p>经历了一些事情,终于狠下心来买了人生的第一台MacBook Pro,也折腾了两三天,把自己的博客又重新搭了起来;<br>希望从今天开始,自己能够坚持记录生活和工作上的一些事!<br>加油!!!</p>
]]></content>
<categories>
<category>生活</category>
</categories>
<tags>
<tag>生活</tag>
</tags>
</entry>
<entry>
<title>Git使用记录</title>
<url>/2019/03/29/Git%E4%BD%BF%E7%94%A8%E8%AE%B0%E5%BD%95/</url>
<content><![CDATA[<p>最近工作中使用Git来进行代码管理,由于之前一个人折腾,并不会遇到太多冲突代码的问题,所以遇到冲突的情况,就会导致需要去各种百度查文档才能解决。<br>因此写一篇文章记录自己常用的命令和流程,以及遇到的问题和解决方法。</p>
<h1 id="基础语法"><a href="#基础语法" class="headerlink" title="基础语法"></a>基础语法</h1><h2 id="设置用户名和邮箱"><a href="#设置用户名和邮箱" class="headerlink" title="设置用户名和邮箱"></a>设置用户名和邮箱</h2><p><code>git config --global user.name '用户名'</code><br><code>git config --global user.email '用户邮箱'</code></p>
<h2 id="初始化仓库"><a href="#初始化仓库" class="headerlink" title="初始化仓库"></a>初始化仓库</h2><p>初始化仓库有两种方法:第一种是直接在当前目录把所有文件导入到Git中;第二种是从远程服务器克隆一个Git仓库到本地。<br> <code>git init</code><br> 执行该命令会在在当前目录中初始化一个Git仓库,创建一个.git的文件夹,这个.git文件夹中包含了所有初始化Git仓库必须的文件,是Git的核心。<br> <code>git clone [远程服务器仓库地址] [本地目录名]</code><br> 如果想获取一份已经存在远程服务器上的Git仓库,则执行该命令即可。可在服务器url参数后添加一个本地仓库的名字。</p>
<h2 id="查看文件状态"><a href="#查看文件状态" class="headerlink" title="查看文件状态"></a>查看文件状态</h2><p><code>git status</code><br>执行该命令可以看到当前哪些文件处于什么状态。目前我所知道的有以下几个状态: </p>
<ul>
<li>modified: 文件被修改</li>
</ul>
<ul>
<li>new file: 新增的文件</li>
</ul>
<ul>
<li>deleted: 文件被删除</li>
</ul>
<ul>
<li>renamed: 文件重命名</li>
</ul>
<ul>
<li>both modified: 文件有冲突</li>
</ul>
<h2 id="提交代码到本地仓库"><a href="#提交代码到本地仓库" class="headerlink" title="提交代码到本地仓库"></a>提交代码到本地仓库</h2><p>在使用查看完文件状态后,如果文件状态有所改变,我们可以将文件提交到本地仓库。<br><code>git add [option]</code><br>使用该命令可以将修改的文件放到暂存区,或者追踪新的文件并放到暂存区。option参数可以传.、*、-A、-u、文件夹、文件。 </p>
<ul>
<li>git add .: 会把工作区所有变化提交到暂存区,包括modified和new file状态的,但是不包括deleted的状态。</li>
</ul>
<ul>
<li>git add *: 和git add .功能一样,但是不会提交以.开头的文件。</li>
</ul>
<ul>
<li>git add -u: 会把工作区已经放到暂存区的文件进行更新(包括modified和deleted),但是不会提交新增文件(new file)。即只监控已经add的文件。</li>
</ul>
<ul>
<li>git add -A: 提交工作区所有变化,git add .和git add -u的合集。 </li>
</ul>
<ul>
<li>git add 文件夹: 提交当前文件夹所有更改到暂存区。</li>
</ul>
<ul>
<li>git add 文件: 提交当前文件到暂存区。</li>
</ul>
<p><code>git commit -m 'remark'</code><br>使用该命令可以将暂存区的文件,提交到本地仓库。remark代表本次提交说明。<br><code>git commit -a -m 'remark'</code><br>使用该命令可以省略git add命令,直接把所有已经追踪过的文件暂存起来提交到本地仓库(是否包含新文件,待验证)。</p>
<h2 id="查看提交历史"><a href="#查看提交历史" class="headerlink" title="查看提交历史"></a>查看提交历史</h2><p><code>git log</code><br>执行该命令后,可以看到根据提交时间排序的所有信息,这些信息包括提交的SHA-1值(commit_id),作者,提交时间,提交说明等。</p>
<h2 id="取消暂存文件或者文件的修改"><a href="#取消暂存文件或者文件的修改" class="headerlink" title="取消暂存文件或者文件的修改"></a>取消暂存文件或者文件的修改</h2><p><code>git rest HEAD [文件名]</code><br>执行该命令后,可以把已经add的文件取消暂存。<br><code>git checkout -- [文件名]</code><br>执行该命令后,可以撤销该文件所有的修改(撤销后无法恢复)。</p>
<h2 id="撤销commit提交"><a href="#撤销commit提交" class="headerlink" title="撤销commit提交"></a>撤销commit提交</h2><p><code>git reset --[option] [commit_id]</code><br>执行该命令后,可以将已经commit的修改进行撤销,根据option的不同,会出现不同的结果:</p>
<ul>
<li>soft: 会将已经commit的修改,撤销到暂存区</li>
</ul>
<ul>
<li>mixed: 不会把已经commit的修改,撤销到暂存区</li>
</ul>
<ul>
<li>hard: 撤销已经commit的修改,并且不会保留。</li>
</ul>
<h2 id="拉取远程仓库代码"><a href="#拉取远程仓库代码" class="headerlink" title="拉取远程仓库代码"></a>拉取远程仓库代码</h2><p><code>git pull</code><br>如果本地分支已经跟踪了远程分支,执行该命令后,会拉取远程分支代码并且合并到本地分支。<br><code>git fetch [远程仓库名]</code><br>执行该命令后,会拉取远程仓库所有本地没有的数据到本地仓库,但是并不会自动合并和修改当前的分支,需要手动合并到当前分支。(目前还没有使用过,后续使用会再进行深入理解。)</p>
<h2 id="提交当前代码到远程仓库"><a href="#提交当前代码到远程仓库" class="headerlink" title="提交当前代码到远程仓库"></a>提交当前代码到远程仓库</h2><p><code>git push [远程仓库] [分支名]</code><br>使用该命令,会将已经commit到本地仓库的代码提交到远程仓库中。如果本地分支与远程分支关联了,直接git push即可,如果没有,则需要加上远程仓库名和分支名称,例如: git push origin master。</p>
<h2 id="添加远程仓库"><a href="#添加远程仓库" class="headerlink" title="添加远程仓库"></a>添加远程仓库</h2><p><code>git remote add [仓库名] [远程仓库地址]</code><br>如果使用了git init初始化一个Git仓库后,想要把代码提交到远程仓库,可以使用该命令将本地仓库和远程仓库关联起来。<br>如果已经有远程仓库了,也可以使用该命令再新增一个远程仓库,不过在拉取代码的时候需要指定仓库名。</p>
<h2 id="使用分支"><a href="#使用分支" class="headerlink" title="使用分支"></a>使用分支</h2><p><code>git branch [option] [分支名] [远程仓库名/远程分支名]</code><br>执行该命令后,可以查看到当前本地所有分支列表,分支名前带*的是当前所处的分支。目前我所知道的option参数有以下:</p>
<ul>
<li>-a: 展示所有分支,包括本地和远程。</li>
</ul>
<ul>
<li>-v: 查看本地每一个分支最后一次提交。</li>
</ul>
<ul>
<li>-d: 删除分支。</li>
</ul>
<ul>
<li>-D: 强制删除分支。</li>
</ul>
<ul>
<li>-vv: 查看本地分支和其所关联的远程分支。</li>
</ul>
<p>如果git branch后直接写分支名,则是创建一个新分支;如果分支名后加上远程仓库名/远程分支名,则可以创建一个关联到远程分支的本地分支。例如:<code>git branch test origin/test</code>使用该命令后则可以在本地创建一个关联到远程test分支的test分支。使用<code>git branch -u [远程仓库名/远程分支名]</code>可以使当前分支关联到远程分支,如果当前分支已经关联远程分支,那么该操作则会更改当前分支关联的远程分支。<br><code>git checkout [分支名]</code><br>执行该命令后,可以切换到对应的分支下。使用<code>git checkout -b [分支名]</code>可以快速创建一个新分支,并且切换到该分支。使用<code>git checkout -b [分支名] [远程仓库名/分支名]</code>可以新建一个关联到远程分支的分支,并且切换到该分支;也可以使用<code>git checkout --track [远程仓库名/分支名]</code>,但是创建的分支名会和远程分支相同。<br><code>git checkout [分支名] [文件目录]</code><br>在当前分支下执行该命令,会将对应分支名下的文件目录覆盖到当前分支对应的文件目录。<br><code>git push [远程仓库名] [分支名]</code><br>执行该命令可以将当前分支代码提交到对应远程分支,如果远程没有对应分支,则会新建一个远程分支并且提交。</p>
<h2 id="储藏"><a href="#储藏" class="headerlink" title="储藏"></a>储藏</h2><p><code>git stash</code><br>执行该命令后,会将当前分支下的所有修改和暂存进行储藏,保存到一个栈上。储藏后的分支是一个干净的目录,可以直接拉取远程代码,并且不会出现任何冲突。<br><code>git stash list</code><br>执行该命令后,会列出所有进行过的储藏。stash@{数字}代表储藏的名字。<br><code>git stash apply [储藏名]</code><br>执行该命令后,可以将对应储藏名的储藏修改应用到当前分支上,不传储藏名则默认应用最新的储藏。该操作不会删除储藏,如果想要在应用储藏后移除储藏则可以使用<code>git stash drop [储藏名]</code>来移除储藏。<br><code>git stash pop [储藏名]</code><br>执行该命令后,会将对应储藏应用到当前分支,并且移除储藏。不传储藏名,则默认应用最新的储藏,并且移除它。</p>
<h2 id="删除没有被追踪的文件"><a href="#删除没有被追踪的文件" class="headerlink" title="删除没有被追踪的文件"></a>删除没有被追踪的文件</h2><p><code>git clean [option]</code><br>执行该命令后,会将没有add的文件和文件夹删除,具体option参数如下:</p>
<ul>
<li>-n:会展示哪些文件要被删除,但是不会去做删除操作,只是用来提示</li>
</ul>
<ul>
<li>-f:删除当前目录下没有被追踪过的文件,不会删除.gitignore文件里面指定的文件</li>
</ul>
<ul>
<li>-f <path>:删除对应路径下的文件</li>
</ul>
<ul>
<li>-df:删除当前目录下没有被追踪过的文件和文件夹</li>
</ul>
<ul>
<li>-xf:删除当前目录下所有没有追踪过的文件. 不管他是否是.gitignore文件里面指定的文件夹和文件</li>
</ul>
<h1 id="目前工作中的使用流程"><a href="#目前工作中的使用流程" class="headerlink" title="目前工作中的使用流程"></a>目前工作中的使用流程</h1><h2 id="当前我所使用的提交流程"><a href="#当前我所使用的提交流程" class="headerlink" title="当前我所使用的提交流程"></a>当前我所使用的提交流程</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git status // 查看当前分支状态</span><br><span class="line">git stash // 储藏当前修改</span><br><span class="line">git pull // 拉取远程分支代码</span><br><span class="line">git stash pop // 应用上次储藏</span><br><span class="line">git status // 查看应用储藏后的状态</span><br><span class="line">********* // 解决冲突和合并</span><br><span class="line">git add [目录] // 暂存对应目录下的修改</span><br><span class="line">git commit -m 'remark' // 提交到本地仓库</span><br><span class="line">git push // 提交到远程仓库</span><br></pre></td></tr></table></figure>
<h1 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h1><p>以上只是我目前所使用过和了解的一些语法和流程,目前只是很浅显的知道如何使用git去管理代码,其中的原理还不知其所以然,后续会抽时间去研究一下做补充,也会把自己遇到的问题和解决方法也记录在这里。</p>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>CSS知识点记录</title>
<url>/2021/08/06/CSS%E7%9F%A5%E8%AF%86%E7%82%B9%E8%AE%B0%E5%BD%95/</url>
<content><![CDATA[<p>最近在开发中,发现一些css样式自己处理的不是很好,但是在网上搜索后都找到了解决方案,在此文章里记录下。</p>
<h2 id="父元素设置min-height,子元素设置高度百分比继承无效"><a href="#父元素设置min-height,子元素设置高度百分比继承无效" class="headerlink" title="父元素设置min-height,子元素设置高度百分比继承无效"></a>父元素设置min-height,子元素设置高度百分比继承无效</h2><p><strong>表现</strong>:对父元素使用<code>min-height</code>为100px,在子元素不脱离文档流的情况下,对子元素设置为100%,但实际显示的时候,子元素的<code>height</code>是0。<br><strong>原因</strong>:子元素设置高度百分比时,是相对于父元素的高度进行继承计算的。当父元素只有<code>min-height</code>,但是没有<code>height</code>属性时,则无法获取活继承父元素高度,所以子元素的<code>height</code>为0。这是<code>webkit</code>的bug,有最小高度的父盒子里面的子元素不能继承高度。详见<a href="https://bugs.webkit.org/show_bug.cgi?id=26559">该链接</a>。</p>
<p><strong>解决方案</strong></p>
<ol>
<li><p>在当前父元素外层再包一层div,并且设置为flex弹性布局。</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'wrapper'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'parent'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'child'</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.wrapper</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.parent</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">min-height</span>: <span class="number">100px</span>;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.child</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>该方案利用的是弹性盒子会默认拉伸高度。</p>
</li>
<li><p>给父元素加绝对定位,然后子元素加相对定位,这样子元素的高度就会根据父元素的高度进行计算。</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'parent'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">'child'</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.parent</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">min-height</span>: <span class="number">100px</span>;</span><br><span class="line"> <span class="attribute">position</span>: relative;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.child</span> {</span><br><span class="line"> <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">height</span>: <span class="number">100%</span>;</span><br><span class="line"> <span class="attribute">position</span>: absolute;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
</ol>
<h2 id="flex布局和margin的结合使用"><a href="#flex布局和margin的结合使用" class="headerlink" title="flex布局和margin的结合使用"></a>flex布局和margin的结合使用</h2><p><strong>表现</strong>:在页面布局中,经常会出现3列子项,前两项左对齐,最后一项右对齐。往常我都会将前两项放入一个元素中,来转换成两列布局,然后使用<code>justify-content: space-between</code>来完成布局,但是这样并不合理。<br><strong>解决方案</strong></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"><div class='parent'></span><br><span class="line"> <div class='box1'></div></span><br><span class="line"> <div class='box2'></div></span><br><span class="line"> <div class='box3'></div></span><br><span class="line"></div></span><br></pre></td></tr></table></figure>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-class">.parent</span> {</span><br><span class="line"> <span class="attribute">display</span>: flex;</span><br><span class="line">}</span><br><span class="line"><span class="selector-class">.box3</span> {</span><br><span class="line"> <span class="attribute">margin-left</span>: auto;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>只要将最后一列的<code>margin-left</code>设置为<code>auto</code>,则该子项会被挤到右对齐的位置,前两项依然为左对齐。</p>
<h3 id="flex布局中flex-wrap换行逻辑"><a href="#flex布局中flex-wrap换行逻辑" class="headerlink" title="flex布局中flex-wrap换行逻辑"></a>flex布局中flex-wrap换行逻辑</h3><p><strong>表现</strong>:当父元素设置为<code>flex</code>时,且使用了<code>flex-wrap: wrap</code>,如果子元素某一子项文本内容过长,则会将该子项整体自动换行。<br><strong>解决方案</strong><br>该问题记录的原因是因为在开发中有使用<code>contenteditable</code>属性做评论功能,动态渲染其子元素,出现文本换行,查询后发现自己在外层父元素中使用了<code>flex-wrap: wrap</code>,去除该属性后便无问题。查询了<code>flex</code>文档后才发现,自己之前对<code>flex</code>的了解不够深入,特此记录。</p>
]]></content>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title>JS事件循环(Event Loop)</title>
<url>/2019/04/11/JS%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/</url>
<content><![CDATA[<p> 日常开发中,总会出现各种因为同步异步行为导致的问题。最近和同事们讨论React中setState同步异步的问题,在网上看到一些解析和总结,发现自己其实对js的执行机制并不太理解。因此在网上查阅了一些资料,来完善一下自己的知识,本文是自己记录一下对js执行机制的理解。<br>先看一段代码:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'a'</span>)</span><br><span class="line">},<span class="number">0</span>)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'b'</span>)</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'c'</span>)</span><br><span class="line">},<span class="number">1000</span>)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'d'</span>)</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'e'</span>)</span><br><span class="line">},<span class="number">0</span>)</span><br></pre></td></tr></table></figure>
<p>按照js是一门单线程语言,代码是从上到下的顺序执行的,我们会很正常的认为打印的结果是<code>abdec</code>,但是去chrome中验证下,却发现结果并不是这样。</p>
<h2 id="同步和异步"><a href="#同步和异步" class="headerlink" title="同步和异步"></a>同步和异步</h2><p>原来js中将代码执行分为了同步和异步,当同步代码执行完毕后,才会去执行异步代码。<code>setTimeout、setInterval、callback、Promise</code>等,都属于异步操作。但是具体的执行机制是什么呢?我们可以看下面这张图(出处在文章尾部):<br><img src="https://raw.githubusercontent.com/zk8080/blog-picture/master/img/event-loop2.jpg" alt="引用地址在文章结尾"><br>从这张图里我们可以知道:</p>
<ul>
<li>js会将任务分为同步任务和异步任务,同步任务进入主线程,异步任务进入Event Table,并且注册回调函数,</li>
</ul>
<ul>
<li>回调函数进入Event Queue;</li>
</ul>
<ul>
<li>主线程的任务执行完毕,到Event Queue中读取对应的函数,然后进入主线程去执行。<br>以上会重复执行,这就是js的执行机制,也是事件循环(Event Loop)。 </li>
</ul>
<p>知道了这些后,再看上面的代码,我们知道了<code>setTimeout</code>属于异步操作,会进入Event Table,等待主线程任务执行完毕才会去执行。但是<code>setTimeout(function(){},0)</code>是不是会立即呢?答案是不会。因为<code>setTimeout(fn, 0)</code>是指当主线程任务执行完毕后会立即执行,不需要再等待多少秒后再执行,所以我们会看到这段代码的结果是<code>bdaec</code>。</p>
<p>接着我们再看一段代码:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">setTimeout</span>(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'a'</span>)</span><br><span class="line">},<span class="number">0</span>)</span><br><span class="line"><span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">resolve</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'b'</span>)</span><br><span class="line"> resolve()</span><br><span class="line">}).then(<span class="function"><span class="params">res</span> =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'c'</span>)</span><br><span class="line">})</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'d'</span>)</span><br></pre></td></tr></table></figure>
<p>按照我们上面的同步异步的执行机制,我们可以知道这段代码会进行以下操作:</p>
<ul>
<li>异步代码<code>setTimeout</code>进入Event Table</li>
</ul>
<ul>
<li>执行同步代码<code>Promise</code>,打印b</li>
</ul>
<ul>
<li><code>Promise.then</code>进入Event Table</li>
</ul>
<ul>
<li>执行同步代码,打印d</li>
</ul>
<ul>
<li>主线程执行完毕,到Event Queue中读取对应的函数,并放到主线程中执行</li>
</ul>
<p>按照之前的理解,这个时候应该是先打印a再打印c,但是我们到chrome中验证一下发现结果是先打印c再打印a,这是为什么呢?</p>
<h2 id="宏任务和微任务"><a href="#宏任务和微任务" class="headerlink" title="宏任务和微任务"></a>宏任务和微任务</h2><p>查阅了一些文章后,原来js将任务分为了更精细的宏任务(macro-task)和微任务(micro-task): </p>
<ul>
<li>宏任务:包括整体代码的script、setTimeout、setInterval</li>
</ul>
<ul>
<li>微任务:Promise、process.nextTick(暂不了解,后续学习)</li>
</ul>
<p>宏任务和微任务之间的执行顺序又是什么呢?我们看下面这张图(出处在文章尾部):<br><img src="https://raw.githubusercontent.com/zk8080/blog-picture/master/img/cfbd39ddcbf42a5bc18725ea0560dc10.png" alt="引用地址在文章结尾"><br>从这张图里我们可以知道:</p>
<ul>
<li>js将代码分为宏任务和微任务,先进入宏任务中执行</li>
</ul>
<ul>
<li>宏任务执行结束后,查看是否有可执行的微任务</li>
</ul>
<ul>
<li>有微任务就执行微任务,然后进行下一个宏任务</li>
</ul>
<ul>
<li>没有微任务则直接进行下一个宏任务</li>
</ul>
<p>那么再看我们之前的代码,我们就可以明白为什么结果是先打印c再打印a:</p>
<ul>
<li>整段代码作为宏任务进入主线程</li>
</ul>
<ul>
<li><code>setTimeout</code>进入Event Table,然后注册回调函数,分发宏任务到Event Queue</li>
</ul>
<ul>
<li><code>new Promise</code>立即执行,打印b,<code>then</code>进入Event Table, 注册回调函数,分发微任务到Event Queue</li>
</ul>
<ul>
<li><code>console.log()</code>立即执行,打印d</li>
</ul>
<ul>
<li>到此第一个宏任务执行完毕,然后查看是否有微任务</li>
</ul>
<ul>
<li>有微任务<code>then</code>函数,则执行<code>then</code>函数,打印c</li>
</ul>
<ul>
<li>微任务执行完毕,开始执行新的宏任务<code>setTimeout</code>的回调函数,打印a</li>
</ul>
<p>由此我们就了解到js的执行机制。</p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>本文是在看到一些文章后,自己所理解的js执行机制,如有不对,请指出。写完这篇文章后,自己对js的事件循环也有了一个大概的了解,虽然具体的原理和底层的堆、栈等还一知半解,但是后续会努力学习加深自己的理解。</p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><blockquote>
<p><a href="https://juejin.im/post/59e85eebf265da430d571f89">这一次,彻底弄懂 JavaScript 执行机制</a></p>
</blockquote>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>React手动重新渲染子组件</title>
<url>/2019/08/15/React%E6%89%8B%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B8%B2%E6%9F%93%E5%AD%90%E7%BB%84%E4%BB%B6/</url>
<content><![CDATA[<h2 id="2019-08-15历史记录"><a href="#2019-08-15历史记录" class="headerlink" title="2019/08/15历史记录"></a>2019/08/15历史记录</h2><p>昨天在开发APP项目时,需要实现一个上传图片和视频,然后上传后的图片和视频进行轮播的功能,轮播图使用的是第三方插件<code>react-native-snap-carousel</code>,在ios中使用时没有出现过兼容性问题,只是需要在轮播的数据更新时,需要手动调用一下<code>snapToItem</code>方法,展示其对应的下标。然而在android中发现该方法并不生效,并且组件的<code>firstItem</code>属性也不会生效,就导致了上传后数据更新的时候下标依然保留在上一次,但是需求方想要每次展示最新上传的图片或视频。后来发现该组件的<code>firstItem</code>属性会在组件第一次渲染的时候生效,所以想到了一个方法就是将该组件进行手动卸载和挂载使其重新渲染。自己捣鼓了半天没有好的方法,后来在老大的指点下搞定了,然后在这记录一下,如何手动重新渲染子组件。直接上代码:</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">this</span>.state={</span><br><span class="line"> <span class="attr">isShow</span>: <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentDidUpdate</span>(<span class="params">prevProps</span>)</span>{</span><br><span class="line"> <span class="comment">// 判断是否需要更新</span></span><br><span class="line"> <span class="keyword">if</span>(<span class="built_in">this</span>.props.data != prevProps.data){</span><br><span class="line"> <span class="comment">// 先将组件卸载</span></span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">isShow</span>: <span class="literal">false</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 利用setTimeout异步操作,再将组件重新渲染, 不过会有一下抖动</span></span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">isShow</span>: <span class="literal">true</span></span><br><span class="line"> })</span><br><span class="line"> }, <span class="number">100</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">const</span> {isShow} = <span class="built_in">this</span>.state;</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> {</span></span><br><span class="line"><span class="xml"> isShow? <span class="tag"><<span class="name">Children</span>/></span>:null</span></span><br><span class="line"><span class="xml"> }</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="2021-11-23更新"><a href="#2021-11-23更新" class="headerlink" title="2021/11/23更新"></a>2021/11/23更新</h2><p>今天在解决一个APP内嵌WebView的登录状态刷新问题时,又遇到了父组件要强制更新子组件的操作。回头来看两年前的解决方案,发现当时只是知其然不知其所以然,今天来更新下我现在的做法。利用props和key来控制children是否重新更新,直接上代码:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">import React, { cloneElement, FC, isValidElement, useEffect, useState } from 'react';</span><br><span class="line"></span><br><span class="line">// 父组件</span><br><span class="line">const Layout: FC<{}> = (props) => {</span><br><span class="line"> const { children } = props;</span><br><span class="line"> const [count, setCount] = useState(0);</span><br><span class="line"></span><br><span class="line"> useEffect(() => {</span><br><span class="line"> console.log('全局layout')</span><br><span class="line"> }, [])</span><br><span class="line"></span><br><span class="line"> const handleClick = () => {</span><br><span class="line"> setCount(c => c + 1)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> {count}</span><br><span class="line"> <button onClick={handleClick}>修改全局动态</button></span><br><span class="line"> { React.Children.map(children, (element, i) => {</span><br><span class="line"> return isValidElement(element) && cloneElement(element, { key: count})</span><br><span class="line"> }) }</span><br><span class="line"> </div></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 子组件</span><br><span class="line">function Children(props) {</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> console.log('---子组件重新渲染---')</span><br><span class="line"> }, [])</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> 我是子组件</span><br><span class="line"> </div></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>这段代码中,父组件利用<code>cloneElement</code>操作<code>children</code>的<code>key</code>属性,由于React将<code>key</code>属性作为组件更新的唯一标识,所以当我们手动更新<code>children</code>的<code>key</code>属性时,便会直接重新渲染整个<code>children</code>。</p>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>React生命周期执行顺序详解</title>
<url>/2019/05/15/React%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F%E8%AF%A6%E8%A7%A3/</url>
<content><![CDATA[<p> 不管是使用React还是Vue,生命周期都是我们必须掌握的。最近在面试的时候,很多人搞不清楚React的生命周期的执行顺序,所以写一篇文章来解析一下React的生命周期函数的执行顺序。本文只介绍常用的生命周期函数的执行顺序,所以有部分不常用的执行顺序可以去看React官方文档。<br>我们先看一张官方文档中的生命周期图谱(v16.4):<br><img src="https://raw.githubusercontent.com/zk8080/blog-picture/master/img/WechatIMG16.png" alt="React生命周期图谱"><br>根据这个图,我们可以分三个阶段来解析React的生命周期执行顺序:</p>
<h2 id="组件初始化创建-Mounting-阶段"><a href="#组件初始化创建-Mounting-阶段" class="headerlink" title="组件初始化创建(Mounting)阶段"></a>组件初始化创建(Mounting)阶段</h2><p>此阶段的顺序为:constructor -> componentWillMount -> render -> ComponentDidMount。<br>由于React新的版本中将一些生命周期函数标记为‘过时’,所以在图谱中我们没有看到componentWillMount,但是现在依然可以使用这个生命周期函数。<br>我们可以看下面这行代码来检验这个阶段的执行顺序:</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'constructor'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentWillMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillMount'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidMount'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'render'</span>);</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> render</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>打开chrome浏览器检验一下结果:<br><img src="https://raw.githubusercontent.com/zk8080/blog-picture/master/img/WechatIMG17.png" alt="初始化执行结果"><br>这个顺序没有什么疑问,但是如果是嵌套组件呢?父子组件之前的顺序是什么样的呢?</p>
<h3 id="嵌套组件初始化创建阶段"><a href="#嵌套组件初始化创建阶段" class="headerlink" title="嵌套组件初始化创建阶段"></a>嵌套组件初始化创建阶段</h3><p>我们再看一段代码:</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'constructor---Parent'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentWillMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillMount---Parent'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidMount---Parent'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'render---Parent'</span>);</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">Child</span> /></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Child</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'constructor---Child'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentWillMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillMount---Child'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidMount---Child'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'render---Child'</span>)</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> Child</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这是一个最普通的嵌套组件,我们在chrome中检验一下结果:<br><img src="https://raw.githubusercontent.com/zk8080/blog-picture/master/img/WechatIMG18.png" alt="嵌套组件初始化结果"><br>我们可以看到嵌套组件的执行顺序为:</p>
<ul>
<li>父组件:constructor -> componentWillMount -> render;</li>
</ul>
<ul>
<li>在父组件render的时候开始初始化创建子组件;</li>
</ul>
<ul>
<li>子组件:constructor -> componentWillMount -> render -> componentDidMount;</li>
</ul>
<ul>
<li>最后子组件执行完毕后,再回到父组件执行componentDidMount。</li>
</ul>
<h2 id="组件更新-Updating-阶段"><a href="#组件更新-Updating-阶段" class="headerlink" title="组件更新(Updating)阶段"></a>组件更新(Updating)阶段</h2><p>如果要触发组件更新有两种情况(不考虑Redux、Mobx等数据流管理工具):</p>
<ul>
<li>setState更新state后触发组件自身更新</li>
</ul>
<ul>
<li>父组件更新后触发子组件的更新</li>
</ul>
<p>第一种情况是当前组件内部的更新,第二种情况是由于父组件更新导致子组件的更新。这两种情况都会触发更新,但是有部分生命周期函数的执行不一样,我们先看第一种情况:</p>
<h3 id="setState触发的组件自身更新"><a href="#setState触发的组件自身更新" class="headerlink" title="setState触发的组件自身更新"></a>setState触发的组件自身更新</h3><p>先看下面一段代码:</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'constructor'</span>);</span><br><span class="line"> <span class="built_in">this</span>.state={</span><br><span class="line"> <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentWillMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillMount'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidMount'</span>);</span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">count</span>: <span class="number">1</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentWillUpdate</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillUpdate'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentDidUpdate</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidUpdate'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'render'</span>);</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> Index</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>接着在chrome中验证一下结果:<br><img src="https://raw.githubusercontent.com/zk8080/blog-picture/master/img/WechatIMG21.png" alt="setState更新的执行顺序"><br>通过这个结果我们可以得到以下的结论:<br>更新state后的组件自身更新生命周期执行顺序:componentWillUpdate -> render -> componentDidUpdate</p>
<h3 id="父组件更新触发的子组件更新"><a href="#父组件更新触发的子组件更新" class="headerlink" title="父组件更新触发的子组件更新"></a>父组件更新触发的子组件更新</h3><p>这种情况是在嵌套组件中,直接上代码:</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'constructor---Parent'</span>);</span><br><span class="line"> <span class="built_in">this</span>.state={</span><br><span class="line"> <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentWillMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillMount---Parent'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidMount---Parent'</span>);</span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">count</span>: <span class="number">1</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentWillUpdate</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillUpdate---Parent'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentWillReceiveProps</span>(<span class="params">nextProps</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillReceiveProps---Parent'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">componentDidUpdate</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidUpdate---Parent'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'render---Parent'</span>);</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">Child</span> /></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Child</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props)</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'constructor---Child'</span>)</span><br><span class="line"> <span class="built_in">this</span>.state = {}</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentWillMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillMount---Child'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidMount---Child'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentWillReceiveProps</span>(<span class="params">nextProps</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillReceiveProps---Child'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentWillUpdate</span>(<span class="params">nextProps, nextState</span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentWillUpdate---Child'</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentDidUpdate</span>(<span class="params">prevProps, prevState</span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'componentDidUpdate---Child'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'render---Child'</span>)</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> Child</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在chrome中验证一下结果:<br><img src="https://raw.githubusercontent.com/zk8080/blog-picture/master/img/WechatIMG22.png" alt="嵌套组件更新执行顺序"><br>从这个结果中我们可以看到,只有子组件的componentWillReceiveProps方法执行了,我们可以得到以下结论:</p>
<ul>
<li>父组件执行更新:componentWillUpdate -> render</li>
</ul>
<ul>
<li>父组件render后开始更新子组件</li>
</ul>
<ul>
<li>子组件执行更新:componentWillReceiveProps -> componentWillUpdate -> render -> componentDidUpdate</li>
</ul>
<ul>
<li>子组件更新完毕后,父组件执行componentDidUpdate,结束更新。</li>
</ul>
<h2 id="组件卸载-Unmounting-阶段"><a href="#组件卸载-Unmounting-阶段" class="headerlink" title="组件卸载(Unmounting)阶段"></a>组件卸载(Unmounting)阶段</h2><p>在这个阶段,组件只会执行一个生命周期函数:componentWillUnmount,在这个函数中,我们可以做一些初始化数据的操作。防止组件下次渲染的时候保留了上次的数据。<br>同上面两个阶段,这个阶段,也会出现在嵌套组件中,但是却和之前的执行顺序不太一样:</p>
<ul>
<li>父组件执行componentWillUnmount</li>
</ul>
<ul>
<li>子组件执行componentWillUnmount</li>
</ul>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过上面的解析,我们可以清晰的知道组件的生命周期执行顺序了:</p>
<ul>
<li>初始化创建组件阶段:<br> 父组件:constructor -> componentWillMount -> render -><br> 子组件:constructor -> componentWillMount -> render -> componentDidMount -><br> 父组件:componentDidMount</li>
</ul>
<ul>
<li>更新阶段:<br> 父组件:componentWillUpdate -> render -><br> 子组件:componentWillReceiveProps -> componentWillUpdate -> render -> componentDidUpdate -><br> 父组件:componentDidUpdate</li>
</ul>
<ul>
<li>卸载阶段:<br> 父组件:componentWillUnmount<br> 子组件:componentWillUnmount</li>
</ul>
<p>通过这些代码的检验,得到了以上的结论,当然这些生命周期函数只是常用的,还有一些不常用的大家可以参考一下官网文档。其中有一个shouldComponentUpdate函数是React控制组件更新进行优化的一个生命周期函数,大家可以了解一下。</p>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>在React项目中使用TypeScript实践指南</title>
<url>/2022/03/06/%E5%9C%A8React%E9%A1%B9%E7%9B%AE%E4%B8%AD%E4%BD%BF%E7%94%A8TypeScript%E5%AE%9E%E8%B7%B5%E6%8C%87%E5%8D%97/</url>
<content><![CDATA[<p>本文主要记录我如何在React项目中优雅的使用TypeScript,来提高开发效率及项目的健壮性。</p>
<span id="more"></span>
<h2 id="项目目录及ts文件划分"><a href="#项目目录及ts文件划分" class="headerlink" title="项目目录及ts文件划分"></a>项目目录及ts文件划分</h2><p>由于实际项目中大部分是使用<code>umi</code>来进行开发项目,所以使用<code>umi</code>生成的目录来做案例。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.</span><br><span class="line">├── README.md</span><br><span class="line">├── global.d.ts</span><br><span class="line">├── mock</span><br><span class="line">├── package.json</span><br><span class="line">├── src</span><br><span class="line">│ ├── assets</span><br><span class="line">│ ├── components</span><br><span class="line">│ │ └── PublicComA</span><br><span class="line">│ │ ├── index.d.ts</span><br><span class="line">│ │ ├── index.less</span><br><span class="line">│ │ └── index.tsx</span><br><span class="line">│ ├── layouts</span><br><span class="line">│ ├── models</span><br><span class="line">│ ├── pages</span><br><span class="line">│ │ ├── PageA</span><br><span class="line">│ │ │ ├── index.d.ts</span><br><span class="line">│ │ │ ├── index.less</span><br><span class="line">│ │ │ └── index.tsx</span><br><span class="line">│ │ ├── index.less</span><br><span class="line">│ │ └── index.tsx</span><br><span class="line">│ └── utils</span><br><span class="line">├── tsconfig.json</span><br><span class="line">├── typings.d.ts</span><br><span class="line">└── yarn.lock</span><br></pre></td></tr></table></figure>
<p>在项目根目录下有typings.d.ts和global.d.ts这两个文件, 前者我们可以放置一些全局的导出模块,比如css,less, 图片的导出声明;后者可以放一些全局声明的变量, 接口等, 比如说window下全局变量的声明等。如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// typings.d.ts</span></span><br><span class="line"><span class="keyword">declare</span> <span class="built_in">module</span> <span class="string">'*.css'</span>;</span><br><span class="line"><span class="keyword">declare</span> <span class="built_in">module</span> <span class="string">'*.less'</span>;</span><br><span class="line"><span class="keyword">declare</span> <span class="built_in">module</span> <span class="string">"*.png"</span>;</span><br><span class="line"><span class="keyword">declare</span> <span class="built_in">module</span> <span class="string">"*.jpeg"</span>;</span><br><span class="line"><span class="keyword">declare</span> <span class="built_in">module</span> <span class="string">'*.svg'</span> {</span><br><span class="line"> <span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">ReactComponent</span>(<span class="params">props: React.SVGProps<SVGSVGElement></span>): <span class="title">React</span>.<span class="title">ReactElement</span></span></span><br><span class="line"><span class="function"> <span class="title">const</span> <span class="title">url</span>: <span class="title">string</span></span></span><br><span class="line"><span class="function"> <span class="title">export</span> <span class="title">default</span> <span class="title">url</span></span></span><br><span class="line"><span class="function">}</span></span><br></pre></td></tr></table></figure>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// global.d.ts</span></span><br><span class="line"><span class="keyword">interface</span> Window {</span><br><span class="line"> <span class="attr">appStateChange</span>: <span class="function">() =></span> <span class="built_in">void</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>接下来介绍一下src目录:</p>
<ul>
<li>assets 存放静态资源如图片/视频/音频等, 参与webpack的打包过程</li>
<li>layouts 存放公共布局</li>
<li>components 存放全局共同组件</li>
<li>models dva的models文件夹, 处理redux流</li>
<li>pages 存放页面的目录, 内部可以有页面组件components, 结构类似于全局的components</li>
<li>utils 存放js工具库, 请求库等公共js文件</li>
</ul>
<p>在pages和components中有存放当前组件/页面所需要的类型和接口声明的index.d.ts。另外如models中的文件由于是每个model私有类型和接口声明,所以可以直接在文件内部去声明。<br>具体的目录规划如上,可以根据实际项目来做更合理的划分。</p>
<h2 id="在项目中使用TypeScript具体实践"><a href="#在项目中使用TypeScript具体实践" class="headerlink" title="在项目中使用TypeScript具体实践"></a>在项目中使用TypeScript具体实践</h2><h3 id="组件声明"><a href="#组件声明" class="headerlink" title="组件声明"></a>组件声明</h3><ol>
<li>函数组件<br>推荐使用<code>React.FC<P={}></code>来表示函数类型,当使用该类型定义组件时,props中会默认带有children属性。<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> IProps {</span><br><span class="line"> <span class="attr">count</span>: <span class="built_in">number</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> App: React.FC<IProps> = <span class="function">(<span class="params">props</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> {count} = props;</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span> <span class="attr">className</span>=<span class="string">"App"</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">span</span>></span>count: {count}<span class="tag"></<span class="name">span</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
<li>类组件<br>类组件接受两个参数,第一个是props的定义,第二个是state的定义,如果使用<code>React.PureComponent<P, S={} SS={}></code>定义组件,则还有第三个参数,表示<code>getSnapshotBeforeUpdate</code>的返回值。<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> IProps {</span><br><span class="line"> <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> IState {</span><br><span class="line"> <span class="attr">count</span>: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span><<span class="title">IProps</span>, <span class="title">IState</span>> </span>{</span><br><span class="line"> state = {</span><br><span class="line"> <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line"> };</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> {this.state.count}</span></span><br><span class="line"><span class="xml"> {this.props.name}</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
</ol>
<h3 id="React-Hooks使用"><a href="#React-Hooks使用" class="headerlink" title="React Hooks使用"></a>React Hooks使用</h3><h4 id="useState"><a href="#useState" class="headerlink" title="useState"></a>useState</h4><p>声明定义:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useState</span><<span class="title">S</span>>(<span class="params">initialState: S | (() => S)</span>): [<span class="title">S</span>, <span class="title">Dispatch</span><<span class="title">SetStateAction</span><<span class="title">S</span>>>]</span>;</span><br><span class="line"><span class="comment">// convenience overload when first argument is omitted</span></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns a stateful value, and a function to update it.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version </span>16.8.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see </span>https://reactjs.org/docs/hooks-reference.html#usestate</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useState</span><<span class="title">S</span> = <span class="title">undefined</span>>(<span class="params"></span>): [<span class="title">S</span> | <span class="title">undefined</span>, <span class="title">Dispatch</span><<span class="title">SetStateAction</span><<span class="title">S</span> | <span class="title">undefined</span>>>]</span>;</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * An alternative to `useState`.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * `useReducer` is usually preferable to `useState` when you have complex state logic that involves</span></span><br><span class="line"><span class="comment"> * multiple sub-values. It also lets you optimize performance for components that trigger deep</span></span><br><span class="line"><span class="comment"> * updates because you can pass `dispatch` down instead of callbacks.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version </span>16.8.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see </span>https://reactjs.org/docs/hooks-reference.html#usereducer</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>如果初始值能够体现出类型,那么可以不用手动声明类型,TS会自动推断出类型。如果初始值为null或者undefined则需要通过泛型显示声明类型。如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> [count, setCount] = useState(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> [user, setUser] = useState<IUser | <span class="literal">null</span>>(<span class="literal">null</span>);</span><br></pre></td></tr></table></figure>
<h4 id="useRef"><a href="#useRef" class="headerlink" title="useRef"></a>useRef</h4><p>声明定义:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useRef</span><<span class="title">T</span>>(<span class="params">initialValue: T</span>): <span class="title">MutableRefObject</span><<span class="title">T</span>></span>;</span><br><span class="line"><span class="comment">// convenience overload for refs given as a ref prop as they typically start with a null value</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument</span></span><br><span class="line"><span class="comment"> * (`initialValue`). The returned object will persist for the full lifetime of the component.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable</span></span><br><span class="line"><span class="comment"> * value around similar to how you’d use instance fields in classes.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type</span></span><br><span class="line"><span class="comment"> * of the generic argument.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version </span>16.8.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see </span>https://reactjs.org/docs/hooks-reference.html#useref</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>
<p>使用该Hook时,要根据使用场景来判断传入泛型类型,如果是获取DOM节点,则传入对应DOM类型即可;如果需要的是一个可变对象,则需要在泛型参数中包含’| null’。如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不可变DOM节点,只读</span></span><br><span class="line"><span class="keyword">const</span> inputRef = useRef<HTMLInputElement>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可变,可重新复制</span></span><br><span class="line"><span class="keyword">const</span> idRef = useRef<<span class="built_in">string</span> | <span class="literal">null</span>>(<span class="literal">null</span>);</span><br><span class="line">idRef.current = <span class="string">"abc"</span>;</span><br></pre></td></tr></table></figure>
<h4 id="useCallback"><a href="#useCallback" class="headerlink" title="useCallback"></a>useCallback</h4><p>声明定义:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useCallback</span><<span class="title">T</span> <span class="title">extends</span> (<span class="params">...args: <span class="built_in">any</span>[]</span>) => <span class="title">any</span>>(<span class="params">callback: T, deps: DependencyList</span>): <span class="title">T</span></span>;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * `useMemo` will only recompute the memoized value when one of the `deps` has changed.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in</span></span><br><span class="line"><span class="comment"> * the second argument.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * ```ts</span></span><br><span class="line"><span class="comment"> * function expensive () { ... }</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * function Component () {</span></span><br><span class="line"><span class="comment"> * const expensiveResult = useMemo(expensive, [expensive])</span></span><br><span class="line"><span class="comment"> * return ...</span></span><br><span class="line"><span class="comment"> * }</span></span><br><span class="line"><span class="comment"> * ```</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version </span>16.8.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see </span>https://reactjs.org/docs/hooks-reference.html#usememo</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>
<p>useCallback会根据返回值自动推断出类型,如果传入的参数不指定类型,则会默认为<code>any</code>,所以为了严谨和可维护性,一定要指定入参的类型。也可以手动传入泛型指定函数类型。如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 会自动推导出类型: (a: number, b: number) => number;</span></span><br><span class="line"><span class="keyword">const</span> add = useCallback(<span class="function">(<span class="params">a: <span class="built_in">number</span>, b: <span class="built_in">number</span></span>) =></span> a + b, [a, b])</span><br><span class="line"></span><br><span class="line"><span class="comment">// 传入泛型,则指定函数类型</span></span><br><span class="line"><span class="keyword">const</span> toggle = useCallback<<span class="function">(<span class="params">a: <span class="built_in">number</span></span>) =></span> <span class="built_in">number</span>>(<span class="function">(<span class="params">a: <span class="built_in">number</span></span>) =></span> a * <span class="number">2</span>, [a])</span><br></pre></td></tr></table></figure>
<h4 id="useMemo"><a href="#useMemo" class="headerlink" title="useMemo"></a>useMemo</h4><p>声明定义:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useMemo</span><<span class="title">T</span>>(<span class="params">factory: () => T, deps: DependencyList | <span class="literal">undefined</span></span>): <span class="title">T</span></span>;</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * `useDebugValue` can be used to display a label for custom hooks in React DevTools.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">NOTE:</span> We don’t recommend adding debug values to every custom hook.</span></span><br><span class="line"><span class="comment"> * It’s most valuable for custom hooks that are part of shared libraries.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version </span>16.8.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see </span>https://reactjs.org/docs/hooks-reference.html#usedebugvalue</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>useMemo和useCallback类似,只是定义类型为具体返回值的类型,而不是函数的类型。如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 会自动推导出类型: number;</span></span><br><span class="line"><span class="keyword">const</span> add = useCallback(<span class="function">(<span class="params">a: <span class="built_in">number</span>, b: <span class="built_in">number</span></span>) =></span> a + b, [a, b])</span><br><span class="line"></span><br><span class="line"><span class="comment">// 传入泛型,则指定函数类型</span></span><br><span class="line"><span class="keyword">const</span> toggle = useCallback<<span class="built_in">number</span>>(<span class="function">(<span class="params">a: <span class="built_in">number</span></span>) =></span> a * <span class="number">2</span>, [a])</span><br></pre></td></tr></table></figure>
<h4 id="useContext"><a href="#useContext" class="headerlink" title="useContext"></a>useContext</h4><p>声明定义:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useContext</span><<span class="title">T</span>>(<span class="params">context: Context<T><span class="comment">/*, (not public API) observedBits?: number|boolean */</span></span>): <span class="title">T</span></span>;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns a stateful value, and a function to update it.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version </span>16.8.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see </span>https://reactjs.org/docs/hooks-reference.html#usestate</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>
<p>useContext会根据传入的上下文对象自动推导出context的类型,当然也可以使用泛型来设置context的类型,如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> ITheme {</span><br><span class="line"> <span class="attr">color</span>: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">const</span> ThemeContext = React.createContext<ITheme>({ <span class="attr">color</span>: <span class="string">"red"</span> });</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自动推导出类型为ITheme</span></span><br><span class="line"><span class="keyword">const</span> theme = useContext(ThemeContext); <span class="comment">// 等同于const theme = useContext<ITheme>(ThemeContext);</span></span><br></pre></td></tr></table></figure>
<h4 id="useReducer"><a href="#useReducer" class="headerlink" title="useReducer"></a>useReducer</h4><p>声明定义:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useReducer</span><<span class="title">R</span> <span class="title">extends</span> <span class="title">Reducer</span><<span class="title">any</span>, <span class="title">any</span>>>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function"> reducer: R,</span></span></span><br><span class="line"><span class="params"><span class="function"> initialState: ReducerState<R>,</span></span></span><br><span class="line"><span class="params"><span class="function"> initializer?: <span class="literal">undefined</span></span></span></span><br><span class="line"><span class="params"><span class="function"></span>): [<span class="title">ReducerState</span><<span class="title">R</span>>, <span class="title">Dispatch</span><<span class="title">ReducerAction</span><<span class="title">R</span>>>]</span>;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument</span></span><br><span class="line"><span class="comment"> * (`initialValue`). The returned object will persist for the full lifetime of the component.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable</span></span><br><span class="line"><span class="comment"> * value around similar to how you’d use instance fields in classes.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version </span>16.8.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see </span>https://reactjs.org/docs/hooks-reference.html#useref</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>
<p>上面只列出了一种类型定义,我在项目中也是使用这种定义去指定<code>useReducer</code>的类型。普通的案例如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> StateType = {</span><br><span class="line"> <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line"> age: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Actions = {</span><br><span class="line"> <span class="attr">type</span>: <span class="string">'Change_Name'</span>;</span><br><span class="line"> payload: <span class="built_in">string</span>;</span><br><span class="line">} | {</span><br><span class="line"> <span class="attr">type</span>: <span class="string">'Change_Age'</span>;</span><br><span class="line"> payload: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = {</span><br><span class="line"> <span class="attr">name</span>: <span class="string">'小明'</span>,</span><br><span class="line"> <span class="attr">age</span>: <span class="number">18</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> reducerAction: Reducer<StateType, Actions> = <span class="function">(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function"> state,</span></span></span><br><span class="line"><span class="params"><span class="function"> action,</span></span></span><br><span class="line"><span class="params"><span class="function"></span>) =></span> {</span><br><span class="line"> <span class="keyword">switch</span> (action.type) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'Change_Name'</span>:</span><br><span class="line"> <span class="keyword">return</span> { ...state, <span class="attr">name</span>: action.payload };</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'Change_Age'</span>:</span><br><span class="line"> <span class="keyword">return</span> { ...state, <span class="attr">age</span>: action.payload };</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> state;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Index</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [state, dispatch] = useReducer(reducerAction, initialState);</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">div</span>></span>姓名:{state.name}<span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">div</span>></span>年龄:{state.age}<span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>可以看到,这样能够得到正确的类型推断,但是略微繁琐。在这篇<a href="https://medium.com/hackernoon/finally-the-typescript-redux-hooks-events-blog-you-were-looking-for-c4663d823b01">文章</a>中,<br>学到了一个泛型定义,可以稍微简化一下定义<code>Actions</code>的过程。案例如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 定义一个生成Action类型的泛型</span></span><br><span class="line"><span class="keyword">type</span> ActionMap<M <span class="keyword">extends</span> Record<<span class="built_in">string</span>, <span class="built_in">any</span>>> = {</span><br><span class="line"> [Key <span class="keyword">in</span> keyof M]: M[Key] <span class="keyword">extends</span> <span class="literal">undefined</span></span><br><span class="line"> ? {</span><br><span class="line"> <span class="attr">type</span>: Key</span><br><span class="line"> }</span><br><span class="line"> : {</span><br><span class="line"> <span class="attr">type</span>: Key</span><br><span class="line"> <span class="attr">payload</span>: M[Key]</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> StateType = {</span><br><span class="line"> <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line"> age: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义具体的Action类型</span></span><br><span class="line"><span class="keyword">type</span> PayloadType = {</span><br><span class="line"> <span class="attr">Change_Name</span>: <span class="built_in">string</span>;</span><br><span class="line"> Change_Age: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/** </span></span><br><span class="line"><span class="comment"> ActionMap<PayloadType>会生成类型</span></span><br><span class="line"><span class="comment"> {</span></span><br><span class="line"><span class="comment"> Change_Name: {</span></span><br><span class="line"><span class="comment"> type: Types.Name;</span></span><br><span class="line"><span class="comment"> payload: string;</span></span><br><span class="line"><span class="comment"> };</span></span><br><span class="line"><span class="comment"> Change_Age: {</span></span><br><span class="line"><span class="comment"> type: Types.Age;</span></span><br><span class="line"><span class="comment"> payload: number;</span></span><br><span class="line"><span class="comment"> };</span></span><br><span class="line"><span class="comment"> }</span></span><br><span class="line"><span class="comment"> 而keyof ActionMap<PayloadType>则会生成 'Change_Name' | 'Change_Age'的类型。</span></span><br><span class="line"><span class="comment"> 所以Action最终的类型便为:</span></span><br><span class="line"><span class="comment"> type Actions = {</span></span><br><span class="line"><span class="comment"> type: Types.Name;</span></span><br><span class="line"><span class="comment"> payload: string;</span></span><br><span class="line"><span class="comment"> } | {</span></span><br><span class="line"><span class="comment"> type: Types.Age;</span></span><br><span class="line"><span class="comment"> payload: number;</span></span><br><span class="line"><span class="comment"> }</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">type</span> Actions = ActionMap<PayloadType>[keyof ActionMap<PayloadType>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = {</span><br><span class="line"> <span class="attr">name</span>: <span class="string">'小明'</span>,</span><br><span class="line"> <span class="attr">age</span>: <span class="number">18</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> reducerAction: Reducer<StateType, Actions> = <span class="function">(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function"> state,</span></span></span><br><span class="line"><span class="params"><span class="function"> action,</span></span></span><br><span class="line"><span class="params"><span class="function"></span>) =></span> {</span><br><span class="line"> <span class="keyword">switch</span> (action.type) {</span><br><span class="line"> <span class="keyword">case</span> Types.Name:</span><br><span class="line"> <span class="keyword">return</span> { ...state, <span class="attr">name</span>: action.payload };</span><br><span class="line"> <span class="keyword">case</span> Types.Age:</span><br><span class="line"> <span class="keyword">return</span> { ...state, <span class="attr">age</span>: action.payload };</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> state;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>我们定义了一个<code>ActionMap</code>泛型,该泛型会将传入的类型<code>{key: value}</code>生成为新的<code>{key: {type: key, payload: value }</code>类型。然后我们利用<code>keyof</code>关键字获取到所有的key,就可以得到我们所需要的<code>{type: key1, payload: value1} | {type: key2, payload: value2}</code>的类型了。只要我们定义好<code>PayloadType</code>类型,则可以自动推导出我们需要的<code>Actions</code>类型。<br>如果你觉得这样写还是很繁琐,那么可以去看我的这篇文章<a href="https://zkat.site/2021/11/03/%E5%9C%A8TypeScript%E4%B8%AD%E4%BD%BF%E7%94%A8useReducer/#%E7%AE%80%E5%8C%96%E7%9A%84useReducer%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F">在TypeScript中使用useReducer</a>,里面介绍了简化的useReducer使用方式。</p>
<h4 id="useImperativeHandle"><a href="#useImperativeHandle" class="headerlink" title="useImperativeHandle"></a>useImperativeHandle</h4><p>声明定义:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useImperativeHandle</span><<span class="title">T</span>, <span class="title">R</span> <span class="title">extends</span> <span class="title">T</span>>(<span class="params">ref: Ref<T>|<span class="literal">undefined</span>, init: () => R, deps?: DependencyList</span>): <span class="title">void</span></span>;</span><br><span class="line"><span class="comment">// <span class="doctag">NOTE:</span> this does not accept strings, but this will have to be fixed by removing strings from type Ref<T></span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * `useImperativeHandle` customizes the instance value that is exposed to parent components when using</span></span><br><span class="line"><span class="comment"> * `ref`. As always, imperative code using refs should be avoided in most cases.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * `useImperativeHandle` should be used with `React.forwardRef`.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@version </span>16.8.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see </span>https://reactjs.org/docs/hooks-reference.html#useimperativehandle</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>
<p><code>useImperativeHandle</code>可以让自定义组件通过<code>ref</code>属性,将内部属性暴露给父组件进行访问。因为是函数式组件,所以需要结合<code>forwardRef</code>一起使用。案例如下:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> FancyProps {}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> FancyRef {</span><br><span class="line"> <span class="attr">focus</span>: <span class="function">() =></span> <span class="built_in">void</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> FancyInput = forwardRef<FancyRef, FancyProps>(<span class="function">(<span class="params">props, ref</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> inputRef = useRef<HTMLInputElement>(<span class="literal">null</span>);</span><br><span class="line"> useImperativeHandle(ref, <span class="function">() =></span> ({</span><br><span class="line"> <span class="attr">focus</span>: <span class="function">() =></span> {</span><br><span class="line"> inputRef.current?.focus();</span><br><span class="line"> }</span><br><span class="line"> }));</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">input</span> <span class="attr">ref</span>=<span class="string">{inputRef}</span> {<span class="attr">...props</span>} /></span></span></span><br><span class="line"> );</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Parent = <span class="function">() =></span> {</span><br><span class="line"> <span class="comment">// 定义子组件ref</span></span><br><span class="line"> <span class="keyword">const</span> inputRef = useRef<FancyRef>(<span class="literal">null</span>);</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">FancyInput</span> </span></span></span><br><span class="line"><span class="tag"><span class="xml"> <span class="attr">ref</span>=<span class="string">{inputRef}</span></span></span></span><br><span class="line"><span class="tag"><span class="xml"> /></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">button</span> </span></span></span><br><span class="line"><span class="tag"><span class="xml"> <span class="attr">onClick</span>=<span class="string">{()</span> =></span> {</span></span><br><span class="line"><span class="xml"> // 调用子组件方法</span></span><br><span class="line"><span class="xml"> inputRef.current?.focus();</span></span><br><span class="line"><span class="xml"> }}</span></span><br><span class="line"><span class="xml"> >聚焦<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="Axios请求-响应定义封装"><a href="#Axios请求-响应定义封装" class="headerlink" title="Axios请求/响应定义封装"></a>Axios请求/响应定义封装</h3><p><code>axios</code>是很流行的http库,他的ts封装已经很完美了,我们只做简单的二次封装,返回通用的数据响应格式。<br>首先在<code>utils/request.ts</code>中创建一个构造axios实例的生成器:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } <span class="keyword">from</span> <span class="string">'axios'</span>;</span><br><span class="line"><span class="keyword">import</span> { Loading } <span class="keyword">from</span> <span class="string">'zarm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 拦截器定义</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> RequestInterceptors {</span><br><span class="line"> <span class="comment">// 请求拦截</span></span><br><span class="line"> requestInterceptors?: <span class="function">(<span class="params">config: AxiosRequestConfig</span>) =></span> AxiosRequestConfig</span><br><span class="line"> requestInterceptorsCatch?: <span class="function">(<span class="params">err: <span class="built_in">any</span></span>) =></span> <span class="built_in">any</span></span><br><span class="line"> <span class="comment">// 响应拦截</span></span><br><span class="line"> responseInterceptors?: <span class="function">(<span class="params">config: AxiosResponse</span>) =></span> AxiosResponse</span><br><span class="line"> responseInterceptorsCatch?: <span class="function">(<span class="params">err: <span class="built_in">any</span></span>) =></span> <span class="built_in">any</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成axios实例的参数,实例可以单独传入拦截器</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> RequestConfig <span class="keyword">extends</span> AxiosRequestConfig {</span><br><span class="line"> interceptorsObj?: RequestInterceptors</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// loading请求数量</span></span><br><span class="line"><span class="keyword">let</span> loadingCount: <span class="built_in">number</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 打开loading</span></span><br><span class="line"><span class="keyword">const</span> showLoading = <span class="function">() =></span> {</span><br><span class="line"> loadingCount ++;</span><br><span class="line"> <span class="keyword">if</span>(loadingCount > <span class="number">0</span>) {</span><br><span class="line"> Loading.show()</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 关闭loading</span></span><br><span class="line"><span class="keyword">const</span> hideLoading = <span class="function">() =></span> {</span><br><span class="line"> loadingCount --;</span><br><span class="line"> <span class="keyword">if</span>(loadingCount <= <span class="number">0</span>) {</span><br><span class="line"> Loading.hide();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">RequestBuilder</span>(<span class="params">config: RequestConfig</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> { interceptorsObj, ...res } = config;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> instance: AxiosInstance = axios.create(res);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 全局请求拦截器</span></span><br><span class="line"> instance.interceptors.request.use(</span><br><span class="line"> <span class="function">(<span class="params">request: AxiosRequestConfig</span>) =></span> {</span><br><span class="line"> <span class="comment">// 显示loading</span></span><br><span class="line"> showLoading();</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'全局请求拦截器'</span>);</span><br><span class="line"> <span class="comment">// TODO:全局的请求头操作等等</span></span><br><span class="line"> <span class="keyword">return</span> request;</span><br><span class="line"> },</span><br><span class="line"> <span class="function">(<span class="params">err: <span class="built_in">any</span></span>) =></span> err,</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 实例请求拦截器 </span></span><br><span class="line"><span class="comment"> * 要注意 axios请求拦截器为倒序执行,所以要将实例请求拦截器注册在全局请求拦截器后面</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> instance.interceptors.request.use(</span><br><span class="line"> interceptorsObj?.requestInterceptors,</span><br><span class="line"> interceptorsObj?.requestInterceptorsCatch,</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 实例响应拦截器</span></span><br><span class="line"><span class="comment"> * axios响应拦截器为正序执行,所以要将实例响应拦截器注册在全局响应拦截器前面</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> instance.interceptors.response.use(</span><br><span class="line"> interceptorsObj?.responseInterceptors,</span><br><span class="line"> interceptorsObj?.responseInterceptorsCatch,</span><br><span class="line"> )</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 全局响应拦截器</span></span><br><span class="line"> instance.interceptors.response.use(</span><br><span class="line"> <span class="function">(<span class="params">response: AxiosResponse</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'全局响应拦截器'</span>);</span><br><span class="line"> <span class="comment">// 关闭loading</span></span><br><span class="line"> hideLoading();</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> 通用的全局响应处理,token过期重定向登录等等</span></span><br><span class="line"> <span class="comment">// 返回值为res.data,即后端接口返回的数据,减少解构的层级,以及统一响应数据格式。</span></span><br><span class="line"> <span class="keyword">return</span> response.data</span><br><span class="line"> },</span><br><span class="line"> <span class="function">(<span class="params">err: <span class="built_in">any</span></span>) =></span> {</span><br><span class="line"> <span class="comment">// 关闭loading</span></span><br><span class="line"> hideLoading();</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> 错误提示等</span></span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line"> },</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> http = RequestBuilder({<span class="attr">baseURL</span>: <span class="string">'/api'</span>});</span><br></pre></td></tr></table></figure>
<p>该生成器可以实现每个实例有单独的拦截器处理逻辑,并且实现全局的loading加载效果,全局拦截器的具体实现可以根据项目实际需求进行填充。生成器已经完成,但是还没法定制我们的通用响应数据,接下来我们在<code>typings.d.ts</code>中重新定义axios模块:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> axios <span class="keyword">from</span> <span class="string">'axios'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">declare</span> <span class="built_in">module</span> <span class="string">'axios'</span> {</span><br><span class="line"> <span class="comment">// 定制业务相关的网络请求响应格式, T 是具体的接口返回类型数据</span></span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">interface</span> CustomSuccessData<T> {</span><br><span class="line"> <span class="attr">code</span>: <span class="built_in">number</span>;</span><br><span class="line"> msg?: <span class="built_in">string</span>;</span><br><span class="line"> message?: <span class="built_in">string</span>;</span><br><span class="line"> data: T;</span><br><span class="line"> [keys: <span class="built_in">string</span>]: <span class="built_in">any</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">export</span> <span class="keyword">interface</span> AxiosInstance {</span><br><span class="line"> <span class="comment">// <T = any>(config: AxiosRequestConfig): Promise<CustomSuccessData<T>>;</span></span><br><span class="line"> request<T = <span class="built_in">any</span>, R = CustomSuccessData<T>, D = <span class="built_in">any</span>>(config: AxiosRequestConfig<D>): <span class="built_in">Promise</span><R>;</span><br><span class="line"> get<T = <span class="built_in">any</span>, R = CustomSuccessData<T>, D = <span class="built_in">any</span>>(url: <span class="built_in">string</span>, config?: AxiosRequestConfig<D>): <span class="built_in">Promise</span><R>;</span><br><span class="line"> <span class="keyword">delete</span><T = <span class="built_in">any</span>, R = CustomSuccessData<T>, D = <span class="built_in">any</span>>(url: <span class="built_in">string</span>, config?: AxiosRequestConfig<D>): <span class="built_in">Promise</span><R>;</span><br><span class="line"> head<T = <span class="built_in">any</span>, R = CustomSuccessData<T>, D = <span class="built_in">any</span>>(url: <span class="built_in">string</span>, config?: AxiosRequestConfig<D>): <span class="built_in">Promise</span><R>;</span><br><span class="line"> post<T = <span class="built_in">any</span>, R = CustomSuccessData<T>, D = <span class="built_in">any</span>>(</span><br><span class="line"> url: <span class="built_in">string</span>,</span><br><span class="line"> data?: D,</span><br><span class="line"> config?: AxiosRequestConfig<D>,</span><br><span class="line"> ): <span class="built_in">Promise</span><R>;</span><br><span class="line"> put<T = <span class="built_in">any</span>, R = CustomSuccessData<T>, D = <span class="built_in">any</span>>(</span><br><span class="line"> url: <span class="built_in">string</span>,</span><br><span class="line"> data?: D,</span><br><span class="line"> config?: AxiosRequestConfig<D>,</span><br><span class="line"> ): <span class="built_in">Promise</span><R>;</span><br><span class="line"> patch<T = <span class="built_in">any</span>, R = CustomSuccessData<T>, D = <span class="built_in">any</span>>(</span><br><span class="line"> url: <span class="built_in">string</span>,</span><br><span class="line"> data?: D,</span><br><span class="line"> config?: AxiosRequestConfig<D>,</span><br><span class="line"> ): <span class="built_in">Promise</span><R>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>完成以上操作后,我们在业务代码中具体使用:</p>
<figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> { http } <span class="keyword">from</span> <span class="string">'@/utils/request'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Req {</span><br><span class="line"> <span class="attr">userId</span>: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Res {</span><br><span class="line"> <span class="attr">userName</span>: <span class="built_in">string</span>;</span><br><span class="line"> userId: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取用户信息接口</span></span><br><span class="line"><span class="keyword">const</span> getUserInfo = <span class="keyword">async</span> (params: Req) => {</span><br><span class="line"> <span class="keyword">return</span> http.get<Res>(<span class="string">'/getUserInfo'</span>, {params})</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这个时候<code>getUserInfo</code>返回的就是<code>CustomSuccessData<Res></code>类型的数据了。至此我们对<code>axios</code>简单的封装也就完成了。</p>
]]></content>
<tags>
<tag>React</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>setState同步异步问题</title>
<url>/2019/05/04/setState%E5%90%8C%E6%AD%A5%E5%BC%82%E6%AD%A5%E9%97%AE%E9%A2%98/</url>
<content><![CDATA[<p> 在React开发中,setState一般都会被认为是异步的。但是最近在掘金上看到一篇文章 <a href="https://juejin.im/post/5b45c57c51882519790c7441">你真的理解setState吗?</a> ,看完后发现setState并不是想象中的异步那么简单。</p>
<h1 id="setState是异步的吗?"><a href="#setState是异步的吗?" class="headerlink" title="setState是异步的吗?"></a>setState是异步的吗?</h1><p>在React官方文档中,有这样一段话:</p>
<blockquote>
<p>setState()不总是立刻更新组件。其可能是成批处理或推迟更新。这使得在调用setState()后立刻读取this.state变成一个潜在陷阱。代替地,使用componentDidUpdate或一个setState回调(setState(updater, callback)),当中的每个方法都会保证在更新被应用之后触发。</p>
</blockquote>
<p>读完这段话,我们本能的认为setState就是异步的,因为连官方都推荐我们在componentDidUpdate生命周期和setState的callback中去读取this.state。<br>接下来我们用代码去检验一下setState的同步异步:</p>
<h2 id="生命周期中的setState"><a href="#生命周期中的setState" class="headerlink" title="生命周期中的setState"></a>生命周期中的setState</h2><pre><code><figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">this</span>.state={</span><br><span class="line"> <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">count</span>: <span class="number">1</span></span><br><span class="line"> })</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="built_in">this</span>.state.count )</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentDidUpdate</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="built_in">this</span>.state.count)</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> 测试</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</code></pre>
<p>在上面这个例子中,我们在componentDidMount中使用setState去更新了count,然后立马打印了一下count;接着在componentDidUpdate中我们也打印了一下count,然后我们在chrome中查看一下控制台,输出为:0 1。这个结果我们并不意外,在开发中这种代码是我们经常遇到,这也说明在生命周期中setState是异步的。</p>
<h2 id="合成事件中的setState"><a href="#合成事件中的setState" class="headerlink" title="合成事件中的setState"></a>合成事件中的setState</h2><figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">this</span>.state={</span><br><span class="line"> <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> onClick = <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">count</span>: <span class="number">1</span></span><br><span class="line"> })</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="built_in">this</span>.state.count)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">button</span></span></span></span><br><span class="line"><span class="tag"><span class="xml"> <span class="attr">onClick</span>=<span class="string">{this.onClick}</span></span></span></span><br><span class="line"><span class="tag"><span class="xml"> ></span></span></span><br><span class="line"><span class="xml"> 测试</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>看一下这个例子中,我们在onClick事件中使用setState去更新count,接着立马打印了count;我们到chrome中去验证一下,发现打印的还是0,这说明在合成事件中setState也是异步的。</p>
<h2 id="setTimeout中的setState"><a href="#setTimeout中的setState" class="headerlink" title="setTimeout中的setState"></a>setTimeout中的setState</h2><figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">this</span>.state={</span><br><span class="line"> <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">count</span>: <span class="number">1</span></span><br><span class="line"> })</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="built_in">this</span>.state.count)</span><br><span class="line"> }, <span class="number">0</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> 测试</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在这个例子中,我们在componentDidMount中定义了一个setTimeout,并且在回调函数中使用setState更新count,然后打印了count;我们在chrome中验证一下,发现控制台中输出的是:1。这个和前面的结果不同,count被实时读取到了,也说明在setTimeout中setState是同步的。</p>
<h2 id="原生事件中的setState"><a href="#原生事件中的setState" class="headerlink" title="原生事件中的setState"></a>原生事件中的setState</h2><figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>(props);</span><br><span class="line"> <span class="built_in">this</span>.state={</span><br><span class="line"> <span class="attr">count</span>: <span class="number">0</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">componentDidMount</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">document</span>.querySelector(<span class="string">'button'</span>).addEventListener(<span class="string">'click'</span>, <span class="built_in">this</span>.onClick, <span class="literal">false</span>)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> onClick = <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">count</span>: <span class="number">1</span></span><br><span class="line"> })</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="built_in">this</span>.state.count)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">button</span>></span></span></span><br><span class="line"><span class="xml"> 测试</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上面的例子中,我们给button按钮使用原生<code>addEventListener</code>的方式,绑定了click事件,并在click事件中使用setState去更新count,然后立马打印了count,接下来我们在chrome中验证一下,发现控制台中输出的是:1。这个结果和setTimeout中setState的结果一致,说明在原生事件中setState也是同步的。</p>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>通过上面的例子,我们可以得到一下结论:</p>
<ul>
<li>在生命周期函数中,setState是异步的;</li>
</ul>
<ul>
<li>在合成事件中,setState是异步的;</li>
</ul>
<ul>
<li>在异步操作中,setState是同步的;</li>
</ul>
<ul>
<li>在原生事件中,setState是同步的。</li>
</ul>
<p>目前得到了这些结论,但是我们并不清楚其中的原理是什么,我查阅了一些资料,发现自己还是不能理解其中奥妙,所以就不在此误导大家,但是自己会努力学习理解这个问题。不过大家可以参考下面文章去发现更深层次的原理:</p>
<blockquote>
<p><a href="https://juejin.im/post/5b45c57c51882519790c7441">你真的理解setState吗?</a></p>
</blockquote>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>实现一个获取上一轮props和state的自定义Hook</title>
<url>/2020/04/17/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%8E%B7%E5%8F%96%E4%B8%8A%E4%B8%80%E8%BD%AEprops%E5%92%8Cstate%E7%9A%84%E8%87%AA%E5%AE%9A%E4%B9%89Hook/</url>
<content><![CDATA[<p>最近在项目中使用了React Hook,刚上手感觉很好用,不用在写class组件中那么多的生命周期模板语法,尤其是一些需要在componentDidMount和componentDidUpdate中进行的副作用操作,可以直接使用useEffect去替代。写完感觉代码清爽很多,也更乐意去使用React Hook了。当然,在不熟练的和不去深入了解的情况下会出现很多意想不到的问题。本文是我在将Class组件转换成Hook的过程中,发现需要在组件中获取上一轮的props和state,但是内置的Hook Api中并没有提供这个方法,在文档中找到一个可以自定义的Hook,在此记录下。直接上代码:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> usePrevious = <span class="function">(<span class="params">preValue</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> ref = useRef();</span><br><span class="line"> useEffect(<span class="function">() =></span> {</span><br><span class="line"> ref.current = preValue;</span><br><span class="line"> }, [preValue])</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> ref.current;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果不太能理解这段代码为什么可以取到上一次的值,那么可以把这段代码放在我们的业务代码中去看一下:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Counter</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [count, setCount] = useState(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> prevCountRef = useRef();</span><br><span class="line"> useEffect(<span class="function">() =></span> {</span><br><span class="line"> prevCountRef.current = count;</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">const</span> prevCount = prevCountRef.current;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="xml"><span class="tag"><<span class="name">h1</span>></span>Now: {count}, before: {prevCount}<span class="tag"></<span class="name">h1</span>></span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>另外还要记住两点:</p>
<ul>
<li>useRef会保持引用不变,ref.current的值改变并不会引起组件重新渲染</li>
</ul>
<ul>
<li>React Hook中函数式组件的生命周期中,useEffect是在jsx渲染之后执行的</li>
</ul>
<p>只要记住这两点,再结合上面这段代码,你就会知道为什么我们可以获取到上一次的props和state了。</p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><blockquote>
<p><a href="https://react.docschina.org/docs/hook-faq.html#how-to-get-the-previous-props-or-state">React官方文档</a><br><a href="https://www.zhihu.com/question/346140951">useRef为什么可以用来封装成usePrevious?(知乎)</a><br><a href="https://zhuanlan.zhihu.com/p/117577458">一篇文章,带你学会useRef、useCallback、useMemo(知乎)</a></p>
</blockquote>
]]></content>
<tags>
<tag>React</tag>
<tag>React Hook</tag>
</tags>
</entry>
<entry>
<title>iOS内嵌H5页面,返回上一页出现白屏的解决方案</title>
<url>/2021/10/18/iOS%E5%86%85%E5%B5%8CH5%E9%A1%B5%E9%9D%A2%EF%BC%8C%E8%BF%94%E5%9B%9E%E4%B8%8A%E4%B8%80%E9%A1%B5%E5%87%BA%E7%8E%B0%E7%99%BD%E5%B1%8F%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/</url>
<content><![CDATA[<h2 id="问题场景描述"><a href="#问题场景描述" class="headerlink" title="问题场景描述"></a>问题场景描述</h2><p>在iOS内嵌的H5页面中,从带滚动的A页面底部点击->跳转B页面后返回->A页面出现白屏,触摸或滚动后页面恢复。</p>
<h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="修改history-scrollRestoration属性"><a href="#修改history-scrollRestoration属性" class="headerlink" title="修改history.scrollRestoration属性"></a>修改history.scrollRestoration属性</h3><p>使用<code>history.back</code>返回上一页的时候,浏览器会记录页面的滚动位置,而在iOS上面,滚动后返回的时候页面渲染会出现问题,导致白屏。可以修改<code>history.scrollRestoration</code>属性,它默认是<code>auto</code>,也就是会记录滚动位置;将其设置为<code>manual</code>,则不会再记录滚动位置。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// History.scrollRestoration</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义:允许web应用程序在历史导航上显式地设置默认滚动恢复行为</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 参数值:</span></span><br><span class="line"><span class="comment">// 1. auto 将恢复用户已滚动到的页面上的位置</span></span><br><span class="line"><span class="comment">// 2. manual 未还原页上的位置。用户必须手动滚动到该位置</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="string">'scrollRestoration'</span> <span class="keyword">in</span> <span class="built_in">window</span>.history) {</span><br><span class="line"> <span class="built_in">window</span>.history.scrollRestoration = <span class="string">'manual'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="触发滚动"><a href="#触发滚动" class="headerlink" title="触发滚动"></a>触发滚动</h3><p>在返回A页面并且加载数据完成后,使用js方法自动触发滚动。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 加载数据代码...</span></span><br><span class="line"><span class="built_in">window</span>.scrollTo(<span class="number">0</span>, <span class="number">1</span>); </span><br><span class="line"><span class="built_in">window</span>.scrollTo(<span class="number">0</span>, <span class="number">0</span>);</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>H5</tag>
</tags>
</entry>
<entry>
<title>实现遵循Promise/A+规范的自定义Promise对象</title>
<url>/2019/11/14/%E5%AE%9E%E7%8E%B0%E9%81%B5%E5%BE%AAPromise-A-%E8%A7%84%E8%8C%83%E7%9A%84%E8%87%AA%E5%AE%9A%E4%B9%89Promise%E5%AF%B9%E8%B1%A1/</url>
<content><![CDATA[<p>最近几天看了一些关于promise的文章,自己也根据理解总结一下,怎么手写一个符合Promise/A+规范的Promise。</p>
<h1 id="什么是Promise"><a href="#什么是Promise" class="headerlink" title="什么是Promise"></a>什么是Promise</h1><p>阮一峰老师的《ECMAScript 6 入门》中是这样解释的:</p>
<blockquote>
<p>Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。</p>
</blockquote>
<h1 id="什么是Promise-A-规范"><a href="#什么是Promise-A-规范" class="headerlink" title="什么是Promise/A+规范"></a>什么是Promise/A+规范</h1><blockquote>
<p>一个开放的,可实现的,可互操作的JavaScript Promise的标准。</p>
</blockquote>
<p>具体的规范可以看一下官方文档:</p>
<ul>
<li><a href="https://promisesaplus.com/">英文原文文档</a></li>
</ul>
<ul>
<li><a href="https://juejin.im/post/5c4b0423e51d4525211c0fbc">中文翻译文档</a></li>
</ul>
<h1 id="实现一个简单的Promise"><a href="#实现一个简单的Promise" class="headerlink" title="实现一个简单的Promise"></a>实现一个简单的Promise</h1><p>根据文档我们知道Promise有几条基本要求:</p>
<ul>
<li>Promise是一个构造函数,接受一个回调函数为参数,并且该回调函数有两个函数类型的参数resolve、reject</li>
</ul>
<ul>
<li>Promise共有三种状态:pengding、fulfilled、rejected,但Promise的当前状态只能是这三种状态中的一种,并且状态只能由pending转换为fulfilled/rejected这两种状态,改变为fulfilled/rejected状态后不可再改变。</li>
</ul>
<ul>
<li>状态改变只能通过调用resolve、reject两个函数进行。</li>
</ul>
<ul>
<li>需要提供一个then方法去访问当前值或者返回结果,then方法接受两个函数类型参数:onFulfilled、onRejected。</li>
</ul>
<ul>
<li>onFulfilled:当Promise状态为fulfilled时进行调用,并且接受Promise成功的值为参数。</li>
</ul>
<ul>
<li>onRejected:当Promise状态为rejected时进行调用,并且接受Promise失败的值为参数。</li>
</ul>
<ul>
<li>then方法返回一个新的Promise</li>
</ul>
<p>根据这些要求我们可以先实现一个简易版的Promise:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> PENDING = <span class="string">"pengding"</span>;</span><br><span class="line"><span class="keyword">const</span> FULFILLED = <span class="string">"fulfilled"</span>;</span><br><span class="line"><span class="keyword">const</span> REJECTED = <span class="string">"rejected"</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">MyPromise</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> self = <span class="built_in">this</span>;</span><br><span class="line"> self.status = PENDING;</span><br><span class="line"> self.value = <span class="literal">null</span>;</span><br><span class="line"> self.reason = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">resolve</span>(<span class="params">value</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (self.status === PENDING) {</span><br><span class="line"> self.status = FULFILLED;</span><br><span class="line"> self.value = value;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">reject</span>(<span class="params">reason</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (self.status === PENDING) {</span><br><span class="line"> self.status = REJECTED;</span><br><span class="line"> self.reason = reason;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> fn(resolve, reject);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">MyPromise.prototype.then = <span class="function"><span class="keyword">function</span> (<span class="params">onFulfilled, onRejected</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> self = <span class="built_in">this</span>;</span><br><span class="line"> <span class="keyword">let</span> bridgePromise = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (self.status === FULFILLED) {</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onFulfilled(self.value);</span><br><span class="line"> resolve(x);</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (self.status === REJECTED) {</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onRejected(self.reason);</span><br><span class="line"> resolve(x);</span><br><span class="line"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们实现了这个简易的Promise,但是可以发现这个Promise是不支持异步操作的,所以接下来我们按照Promise/A+的规范,实现一个符合规范的Promise。</p>
<h1 id="实现符合Promise-A-规范的Promise"><a href="#实现符合Promise-A-规范的Promise" class="headerlink" title="实现符合Promise/A+规范的Promise"></a>实现符合Promise/A+规范的Promise</h1><p>根据规范,我们上面实现的简易版,除了不能进行异步操作外,还缺少了一个resolvePromise函数去处理then函数中的回调函数的返回值。所以接下来我们一步步来实现。</p>
<h2 id="增加异步操作"><a href="#增加异步操作" class="headerlink" title="增加异步操作"></a>增加异步操作</h2><p>首先我们需要定义两个保存回调函数的数组,分别保存onFulfilled回调函数和onRejected回调函数,并且在执行resolve或reject后,进行调用回调函数。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">MyPromise</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> self = <span class="built_in">this</span>;</span><br><span class="line"> self.status = PENDING;</span><br><span class="line"> self.value = <span class="literal">null</span>;</span><br><span class="line"> self.reason = <span class="literal">null</span>;</span><br><span class="line"> <span class="comment">// 存放onFulfilled回调处理函数集合</span></span><br><span class="line"> self.onFulfilledCallbacks = [];</span><br><span class="line"> <span class="comment">// 存放onRejected回调处理函数集合</span></span><br><span class="line"> self.onRejectedCallbacks = [];</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">resolve</span>(<span class="params">value</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (self.status === PENDING) {</span><br><span class="line"> self.status = FULFILLED;</span><br><span class="line"> self.value = value;</span><br><span class="line"> <span class="comment">// 修改状态后执行回调函数集合</span></span><br><span class="line"> self.onFulfilledCallbacks.forEach(<span class="function">(<span class="params">callback</span>) =></span> callback(value));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">reject</span>(<span class="params">reason</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (self.status === PENDING) {</span><br><span class="line"> self.status = REJECTED;</span><br><span class="line"> self.reason = reason;</span><br><span class="line"> <span class="comment">//执行reject的回调函数,将reason传递到callback中</span></span><br><span class="line"> self.onRejectedCallbacks.forEach(<span class="function">(<span class="params">callback</span>) =></span> callback(reason));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> fn(resolve, reject);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>然后修改一下then方法,需要加一个pengding状态的判断,将对应的回调函数,加入到回调函数集合中:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span>(self.status === PENDING){</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>)</span>{</span><br><span class="line"> self.onFulfilledCallbacks.push(<span class="function"><span class="params">value</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> <span class="keyword">let</span> x = onFulfilled(value);</span><br><span class="line"> resolve(x);</span><br><span class="line"> }<span class="keyword">catch</span>(e){</span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> self.onRejectedCallbacks.push(<span class="function"><span class="params">reason</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> <span class="keyword">let</span> x = onRejected(reason);</span><br><span class="line"> resolve(x);</span><br><span class="line"> }<span class="keyword">catch</span>(e){</span><br><span class="line"> reject(e)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> })</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>经过我们的改善,我们实现的简易版Promise已经支持异步操作了,接下来我们实现resolvePromise这个方法。</p>
<h2 id="实现resolvePromise函数,处理回调函数结果"><a href="#实现resolvePromise函数,处理回调函数结果" class="headerlink" title="实现resolvePromise函数,处理回调函数结果"></a>实现resolvePromise函数,处理回调函数结果</h2><p>resolvePromise函数是Promise/A+规范中,规定对Promise结果进行解析的处理程序,其中判断了多种Promise返回结果的情况,具体判断规则可以参考文档,这里我们来根据文档实现这个函数:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">resolvePromise</span>(<span class="params">bridgePromise, x, resolve, reject</span>) </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 2.3.1 如果返回的 bridgePromise 和 x 是指向同一个引用(循环引用),则抛出错误</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span>(bridgePromise === x){</span><br><span class="line"> <span class="keyword">return</span> reject(<span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">'循环调用'</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 2.3.2 如果 x 是一个 promise 实例,则采用它的状态:</span></span><br><span class="line"><span class="comment"> * 2.3.2.1 如果 x 是 pending 状态,那么保留它(递归执行这个 promise 处理程序),直到 pending 状态转为 fulfilled 或 rejected 状态</span></span><br><span class="line"><span class="comment"> * 2.3.2.2 如果或当 x 状态是 fulfilled,resolve 它,并且传入和 promise1 一样的值 value</span></span><br><span class="line"><span class="comment"> * 2.3.2.3 如果或当 x 状态是 rejected,reject 它,并且传入和 promise1 一样的值 reason</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(x <span class="keyword">instanceof</span> MyPromise){</span><br><span class="line"> <span class="keyword">if</span>(x.status === PENDING){</span><br><span class="line"> x.then(<span class="function"><span class="params">y</span> =></span> {</span><br><span class="line"> resolvePromise(bridgePromise, y, resolve, reject);</span><br><span class="line"> }, <span class="function"><span class="params">error</span> =></span> {</span><br><span class="line"> reject(error);</span><br><span class="line"> })</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> x.then(resolve, reject);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(x != <span class="literal">null</span> && ((<span class="keyword">typeof</span> x === <span class="string">'object'</span>) || (<span class="keyword">typeof</span> x === <span class="string">'function'</span>))){</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 2.3.3 此外,如果 x 是个对象或函数类型</span></span><br><span class="line"><span class="comment"> * 2.3.3.1 把 x.then 赋值给 then 变量</span></span><br><span class="line"><span class="comment"> * 2.3.3.2 如果捕获(try,catch)到 x.then 抛出的错误的话,需要 reject 这个promise</span></span><br><span class="line"><span class="comment"> * 2.3.3.3 如果 then 是函数类型,那个用 x 调用它(将 then 的 this 指向 x),第一个参数传 resolvePromise ,第二个参数传 rejectPromise:</span></span><br><span class="line"><span class="comment"> * 2.3.3.3.1 如果或当 resolvePromise 被调用并接受一个参数 y 时,执行[[Resolve]](promise, y)</span></span><br><span class="line"><span class="comment"> * 2.3.3.3.2 如果或当 rejectPromise 被调用并接受一个参数 r 时,执行 reject(r)</span></span><br><span class="line"><span class="comment"> * 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 已经被调用或以相同的参数多次调用的话吗,优先第一次的调用,并且之后的调用全部被忽略(避免多次调用)</span></span><br><span class="line"><span class="comment"> * 2.3.3.4 如果 then 执行过程中抛出了异常,</span></span><br><span class="line"><span class="comment"> * 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略异常</span></span><br><span class="line"><span class="comment"> * 2.3.3.3.4.2 否则,则 reject 这个异常</span></span><br><span class="line"><span class="comment"> * 2.3.3.4 如果 then 不是函数类型,直接 resolve x(resolve(x))</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="comment">// 判断是否已经调用的标识</span></span><br><span class="line"> <span class="keyword">let</span> called = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> then = x.then;</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">typeof</span> then === <span class="string">'function'</span>){</span><br><span class="line"> then.call(x, <span class="function"><span class="params">y</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span>(called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> resolvePromise(bridgePromise, y, resolve, reject);</span><br><span class="line"> },<span class="function"><span class="params">error</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span>(called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> reject(error);</span><br><span class="line"> })</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> resolve(x);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="keyword">if</span>(called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> reject(error);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> resolve(x);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>resolvePromise函数已经实现,接下来我们完善一下then方法:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">MyPromise.prototype.then = <span class="function"><span class="keyword">function</span> (<span class="params">onFulfilled, onRejected</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> self = <span class="built_in">this</span>;</span><br><span class="line"></span><br><span class="line"> onFulfilled = <span class="keyword">typeof</span> onFulfilled === <span class="string">'function'</span> ? onFulfilled : <span class="function"><span class="params">value</span> =></span> value;</span><br><span class="line"> onRejected = <span class="keyword">typeof</span> onRejected === <span class="string">'function'</span> ? onRejected : <span class="function"><span class="params">reason</span> =></span> {<span class="keyword">throw</span> reason};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> bridgePromise = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(self.status === FULFILLED){</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>)</span>{</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onFulfilled(self.value);</span><br><span class="line"> resolvePromise(bridgePromise, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(self.status === REJECTED){</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>)</span>{</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onRejected(self.reason);</span><br><span class="line"> resolvePromise(bridgePromise, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(self.status === PENDING){</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>)</span>{</span><br><span class="line"> self.onFulfilledCallbacks.push(<span class="function"><span class="params">value</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onFulfilled(value);</span><br><span class="line"> resolvePromise(bridgePromise, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> self.onRejectedCallbacks.push(<span class="function"><span class="params">reason</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onRejected(reason);</span><br><span class="line"> resolvePromise(bridgePromise, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>完善后的then方法中,你会发现我们多加了一些代码:</p>
<ol>
<li>增加了onFulfilled和onRejected函数的判断,没有传入函数则给一个自定义函数,返回之前resolve的值,或者抛出异常,这也是Promise/A+的规范所要求的。</li>
<li>在fulfilled和rejected的状态返回值中都包了一层setTimeout,因为Promise的then方法都需要在下一轮的事件循环中被异步调用,所以我们模拟使用了setTimeout,原生的Promise则是使用微任务实现的,而pengding状态中没有加setTimeout,则是要在resolve和reject函数中进行异步调用回调函数集合,这是为了满足Promise/A+规范的2.2.4和2.2.6。</li>
</ol>
<p>到此,我们已经把then方法完整的实现了,接下来我们将整个代码进行整合,然后进行测试。</p>
<h1 id="符合Promise-A-规范的完整代码"><a href="#符合Promise-A-规范的完整代码" class="headerlink" title="符合Promise/A+规范的完整代码"></a>符合Promise/A+规范的完整代码</h1><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> PENDING = <span class="string">'pengding'</span>;</span><br><span class="line"><span class="keyword">const</span> FULFILLED = <span class="string">"fulfilled"</span>;</span><br><span class="line"><span class="keyword">const</span> REJECTED = <span class="string">"rejected"</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">MyPromise</span>(<span class="params">fn</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> self = <span class="built_in">this</span>;</span><br><span class="line"> self.status = PENDING;</span><br><span class="line"> self.value = <span class="literal">null</span>;</span><br><span class="line"> self.reason = <span class="literal">null</span>;</span><br><span class="line"> self.onFulfilledCallbacks = [];</span><br><span class="line"> self.onRejectedCallbacks = [];</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">resolve</span>(<span class="params">value</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (value <span class="keyword">instanceof</span> MyPromise) {</span><br><span class="line"> <span class="keyword">return</span> value.then(resolve, reject);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(self.status === PENDING){</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> self.value = value;</span><br><span class="line"> self.status = FULFILLED;</span><br><span class="line"> self.onFulfilledCallbacks.forEach(<span class="function">(<span class="params">callback</span>) =></span> callback(self.value));</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">reject</span>(<span class="params">reason</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(self.status === PENDING){</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> self.reason = reason;</span><br><span class="line"> self.status = REJECTED;</span><br><span class="line"> self.onRejectedCallbacks.forEach(<span class="function">(<span class="params">callback</span>) =></span> callback(self.reason));</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> fn(resolve, reject);</span><br><span class="line"> }<span class="keyword">catch</span>(e){</span><br><span class="line"> reject(e);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">resolvePromise</span>(<span class="params">bridgePromise, x, resolve, reject</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(bridgePromise === x){</span><br><span class="line"> <span class="keyword">return</span> reject(<span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">'循环调用'</span>))</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(x <span class="keyword">instanceof</span> MyPromise){</span><br><span class="line"> <span class="keyword">if</span>(x.status === PENDING){</span><br><span class="line"> x.then(<span class="function"><span class="params">y</span> =></span> {</span><br><span class="line"> resolvePromise(bridgePromise, y, resolve, reject);</span><br><span class="line"> }, <span class="function"><span class="params">error</span> =></span> {</span><br><span class="line"> reject(error);</span><br><span class="line"> })</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> x.then(resolve, reject);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(x != <span class="literal">null</span> && ((<span class="keyword">typeof</span> x === <span class="string">'object'</span>) || (<span class="keyword">typeof</span> x === <span class="string">'function'</span>))){</span><br><span class="line"> <span class="keyword">let</span> called = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> then = x.then;</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">typeof</span> then === <span class="string">'function'</span>){</span><br><span class="line"> then.call(x, <span class="function"><span class="params">y</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span>(called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> resolvePromise(bridgePromise, y, resolve, reject);</span><br><span class="line"> },<span class="function"><span class="params">error</span> =></span> {</span><br><span class="line"> <span class="keyword">if</span>(called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> reject(error);</span><br><span class="line"> })</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> resolve(x);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="keyword">if</span>(called) <span class="keyword">return</span>;</span><br><span class="line"> called = <span class="literal">true</span>;</span><br><span class="line"> reject(error);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> resolve(x);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">MyPromise.prototype.then = <span class="function"><span class="keyword">function</span> (<span class="params">onFulfilled, onRejected</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> self = <span class="built_in">this</span>;</span><br><span class="line"> onFulfilled = <span class="keyword">typeof</span> onFulfilled === <span class="string">'function'</span> ? onFulfilled : <span class="function"><span class="params">value</span> =></span> value;</span><br><span class="line"> onRejected = <span class="keyword">typeof</span> onRejected === <span class="string">'function'</span> ? onRejected : <span class="function"><span class="params">reason</span> =></span> {<span class="keyword">throw</span> reason};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> bridgePromise = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(self.status === FULFILLED){</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>)</span>{</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onFulfilled(self.value);</span><br><span class="line"> resolvePromise(bridgePromise, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(self.status === REJECTED){</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>)</span>{</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onRejected(self.reason);</span><br><span class="line"> resolvePromise(bridgePromise, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">0</span>);</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(self.status === PENDING){</span><br><span class="line"> <span class="keyword">return</span> bridgePromise = <span class="keyword">new</span> MyPromise(<span class="function"><span class="keyword">function</span>(<span class="params">resolve, reject</span>)</span>{</span><br><span class="line"> self.onFulfilledCallbacks.push(<span class="function"><span class="params">value</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onFulfilled(value);</span><br><span class="line"> resolvePromise(bridgePromise, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"></span><br><span class="line"> self.onRejectedCallbacks.push(<span class="function"><span class="params">reason</span> =></span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> x = onRejected(reason);</span><br><span class="line"> resolvePromise(bridgePromise, x, resolve, reject)</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> reject(error)</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行测试用例需要用到的代码</span></span><br><span class="line">MyPromise.deferred = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> defer = {};</span><br><span class="line"> defer.promise = <span class="keyword">new</span> MyPromise(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> defer.resolve = resolve;</span><br><span class="line"> defer.reject = reject;</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> defer;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="built_in">module</span>.exports = MyPromise</span><br><span class="line">} <span class="keyword">catch</span> (e) {}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>代码已经整合完毕,我们可以利用Promise的测试脚本,对我们的代码进行测试:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">npm i promises-aplus-tests -g</span><br><span class="line">promises-aplus-tests promise.js</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>实现防抖和节流</title>
<url>/2020/04/26/%E5%AE%9E%E7%8E%B0%E9%98%B2%E6%8A%96%E5%92%8C%E8%8A%82%E6%B5%81/</url>
<content><![CDATA[<p>在业务开发场景中,我们经常会使用到防抖和节流,我一般都是使用<code>Lodash</code>工具库直接调用这两个方法,今天自己来实现一下这两个方法。</p>
<h2 id="防抖-debounce"><a href="#防抖-debounce" class="headerlink" title="防抖(debounce)"></a>防抖(debounce)</h2><p>防抖的含义:触发高频事件在n秒以后执行,如果在n秒之内再次触发事件,则重新计算时间<br>防抖的应用场景:</p>
<ul>
<li>autoComplete组件</li>
</ul>
<ul>
<li>实时搜索框</li>
</ul>
<p>实现思路:<br>利用闭包缓存一个时间为n秒计时器,每次触发事件时,清除上一个计时器。<br>代码:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">debounce</span>(<span class="params">fn, time</span>) </span>{</span><br><span class="line"> <span class="comment">// 利用闭包缓存一个定时器</span></span><br><span class="line"> <span class="keyword">let</span> timeout = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 每次执行 则先清除上一个定时器</span></span><br><span class="line"> <span class="built_in">clearTimeout</span>(timeout);</span><br><span class="line"> timeout = <span class="built_in">setTimeout</span>(<span class="function">(<span class="params">a</span>) =></span> {</span><br><span class="line"> <span class="comment">// 执行事件</span></span><br><span class="line"> fn.apply(<span class="built_in">this</span>, <span class="built_in">arguments</span>)</span><br><span class="line"> }, time);</span><br><span class="line"> }</span><br><span class="line">} </span><br></pre></td></tr></table></figure>
<h2 id="节流-throttle"><a href="#节流-throttle" class="headerlink" title="节流(throttle)"></a>节流(throttle)</h2><p>节流的含义:触发高频事件后,在n秒内只执行一次<br>节流的应用场景:</p>
<ul>
<li>监听滚动事件,实现懒加载等</li>
</ul>
<ul>
<li>防止用户连续频繁的点击事件,如提交按钮等</li>
</ul>
<p>实现节流有两种方案:</p>
<ul>
<li>利用闭包缓存一个是否可以执行函数的变量,配合定时器去修改变量来执行函数。</li>
</ul>
<ul>
<li>利用闭包缓存上一次的时间戳,判断当前执行的时间和上一次时间的间隔是否大于规定时间。</li>
</ul>
<p>定时器代码实现:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">throttle</span>(<span class="params">fn, time</span>) </span>{</span><br><span class="line"> <span class="comment">// 设置一个标识 来判断延时函数是否可以执行,默认可以执行</span></span><br><span class="line"> <span class="keyword">let</span> isCanRun = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 每次执行先判断是否可以执行延迟函数</span></span><br><span class="line"> <span class="keyword">if</span>(!isCanRun) <span class="keyword">return</span>;</span><br><span class="line"> <span class="comment">// 将标识设置为false,表示有延时函数需要执行</span></span><br><span class="line"> isCanRun = <span class="literal">false</span>;</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> fn.apply(<span class="built_in">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line"> <span class="comment">// 在函数执行完后 延时将标识设置为true,</span></span><br><span class="line"> isCanRun = <span class="literal">true</span>;</span><br><span class="line"> }, time);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>时间戳代码实现:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">throttle</span>(<span class="params">fn, time</span>) </span>{</span><br><span class="line"> <span class="comment">// 缓存上次执行时间的变量</span></span><br><span class="line"> <span class="keyword">let</span> activeTime = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 获取每次执行的时间戳</span></span><br><span class="line"> <span class="keyword">const</span> curTime = +<span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line"> <span class="comment">// 判断当前执行的时间和缓存的时间戳的时间差是否大于要延时的时间 </span></span><br><span class="line"> <span class="keyword">if</span>( curTime - activeTime > time ){</span><br><span class="line"> fn.apply(<span class="built_in">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line"> <span class="comment">// 缓存当前时间</span></span><br><span class="line"> activeTime = curTime;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>我所理解的闭包</title>
<url>/2020/04/29/%E6%88%91%E6%89%80%E7%90%86%E8%A7%A3%E7%9A%84%E9%97%AD%E5%8C%85/</url>
<content><![CDATA[<p>在我的上一篇文章<a href="https://zkat.site/2020/04/26/%E5%AE%9E%E7%8E%B0%E9%98%B2%E6%8A%96%E5%92%8C%E8%8A%82%E6%B5%81/">实现防抖和节流</a>中,都是利用闭包缓存变量去实现的,那么今天就来说一下我所理解的闭包是什么。这也是面试中必不可少的部分。本文参考了冴羽大神的JavaScript深入系列的<a href="https://github.com/mqyqingfeng/Blog/issues/8">JavaScript深入之执行上下文</a>和<a href="https://github.com/mqyqingfeng/Blog/issues/9">JavaScript深入之闭包</a>,大家可以深入了解下。</p>
<h2 id="闭包的概念"><a href="#闭包的概念" class="headerlink" title="闭包的概念"></a>闭包的概念</h2><p>MDN中对闭包的定义为:</p>
<blockquote>
<p>函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。</p>
</blockquote>
<p>按照这段话的理解,那么每个函数都会形成一个闭包,这和我们平时所理解的闭包不太一样,但是这是理论上的闭包定义,还有一个实践角度上的闭包:</p>
<ul>
<li>即使创建该函数的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)</li>
</ul>
<ul>
<li>在代码中引用了自由变量</li>
</ul>
<blockquote>
<p>自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。</p>
</blockquote>
<h2 id="闭包是怎么形成的"><a href="#闭包是怎么形成的" class="headerlink" title="闭包是怎么形成的"></a>闭包是怎么形成的</h2><p>我们来看一段MDN上的示例代码:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">makeFunc</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> name = <span class="string">"Mozilla"</span>;</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">displayName</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(name);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> displayName;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> myFunc = makeFunc();</span><br><span class="line">myFunc();</span><br></pre></td></tr></table></figure>
<p>我们来分析一下这段代码的执行上下文栈和执行上下文的变化情况,不了解代码是怎么执行代码并创建对应执行上下文的,可以去看一下冴羽大神的<a href="https://github.com/mqyqingfeng/Blog/issues/8">JavaScript深入之执行上下文</a>。执行过程如下:</p>
<ol>
<li>执行全局代码,创建全局上下文,全局上下文被压入执行上下文栈:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ECStack = [</span><br><span class="line"> globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure></li>
<li>全局上下文初始化:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">globalContext = {</span><br><span class="line"> VO: [global],</span><br><span class="line"> Scope: [globalContext.VO],</span><br><span class="line"> this: globalContext.VO</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
<li>初始化的同时,创建makeFunc函数,保存作用域链到函数的内部属性[[scope]]:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">makeFunc.[[scope]] = [</span><br><span class="line"> globalContext.VO</span><br><span class="line">]</span><br></pre></td></tr></table></figure></li>
<li>执行makeFunc函数,创建makeFunc函数上下文,makeFunc函数上下文被压入执行上下文栈:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ECStack = [</span><br><span class="line"> globalContext,</span><br><span class="line"> makeFuncContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure></li>
<li>makeFunc函数上下文初始化,合并作用域链,同时displayName函数也被创建:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">makeFuncContext = {</span><br><span class="line"> AO: {</span><br><span class="line"> arguments: {</span><br><span class="line"> length: 0</span><br><span class="line"> },</span><br><span class="line"> name: undefined,</span><br><span class="line"> displayName: reference to function displayName(){}</span><br><span class="line"> },</span><br><span class="line"> Scope: [AO, globalContext],</span><br><span class="line"> this: undefined</span><br><span class="line">}</span><br><span class="line">displayName.[[scope]] = [</span><br><span class="line"> makeFuncContext.AO,</span><br><span class="line"> globalContext.VO</span><br><span class="line">]</span><br></pre></td></tr></table></figure></li>
<li>makeFunc函数执行过程中更新AO:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">makeFuncContext = {</span><br><span class="line"> AO: {</span><br><span class="line"> arguments: {</span><br><span class="line"> length: 0</span><br><span class="line"> },</span><br><span class="line"> name: "Mozilla",</span><br><span class="line"> displayName: reference to function displayName(){}</span><br><span class="line"> },</span><br><span class="line"> Scope: [AO, globalContext.VO],</span><br><span class="line"> this: undefined</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
<li>makeFunc函数执行完毕,makeFunc函数上下文从执行上下文栈中弹出:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ECStack = [</span><br><span class="line"> globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure></li>
<li>执行displayName函数,创建displayName函数上下文,displayName函数上下文压入执行上文栈中:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ECStack = [</span><br><span class="line"> globalContext,</span><br><span class="line"> displayNameContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure></li>
<li>初始化displayName函数上下文,合并作用域链:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">displayNameContext = {</span><br><span class="line"> AO: {</span><br><span class="line"> arguments: {</span><br><span class="line"> length: 0</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> Scope: [AO, makeFuncContext.AO, globalContext.VO],</span><br><span class="line"> this: undefined</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
<li>执行displayName函数,发现函数内部用到变量name,而displayNameContext.AO中并没有name值,沿着作用域链往上找,在makeFuncContext.AO中找到name值,为Mozilla,则打印Mozilla。displayName函数执行完毕,displayName函数上下文从执行上下文中弹出:<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ECStack = [</span><br><span class="line"> globalContext</span><br><span class="line">]</span><br></pre></td></tr></table></figure></li>
</ol>
<p>通过上面的解析,我们可以发现,在makeFunc函数上下文从执行上下文栈中弹出后,displayName函数依然存在,并且拥有一个作用域链。所以当makeFuncContext被销毁时,makeFuncContext.AO依然存在于内存中。这就是为什么displayName函数可以读取到makeFuncContext中的值。再结合我们对闭包的实践角度上的定义对比一下,我们发现,displayName函数就是一个典型的闭包。</p>
<h2 id="闭包经典面试题"><a href="#闭包经典面试题" class="headerlink" title="闭包经典面试题"></a>闭包经典面试题</h2><p>面试中提到闭包,那么这道题必不可少:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> data = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < <span class="number">3</span>; i++) {</span><br><span class="line"> data[i] = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(i);</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line">data[<span class="number">0</span>]();</span><br><span class="line">data[<span class="number">1</span>]();</span><br><span class="line">data[<span class="number">2</span>]();</span><br></pre></td></tr></table></figure>
<p>毋庸置疑,打印的都是3,因为当函数执行的时候,for循环已经执行结束,全局变量i值已经是3了。所以函数执行的时候,去查找变量i,当前函数上下文的AO中没有i,查找globalContext.VO,i为3,所以打印3。<br>那么如何让这段代码打印成0,1,2? 那么答案就是闭包。利用闭包缓存变量,使其在每次执行的时候都能查找到对应作用域链中缓存的变量。代码如下:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> data = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < <span class="number">3</span>; i++) {</span><br><span class="line"> data[i] = (<span class="function"><span class="keyword">function</span>(<span class="params">i</span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(i);</span><br><span class="line"> };</span><br><span class="line"> })(i)</span><br><span class="line">}</span><br><span class="line">data[<span class="number">0</span>]();</span><br><span class="line">data[<span class="number">1</span>]();</span><br><span class="line">data[<span class="number">2</span>]();</span><br></pre></td></tr></table></figure>
<p>这样修改后,data[0]函数中的作用域链就是:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">data[0]Context = {</span><br><span class="line"> Scope: [AO, 匿名函数Context.AO, globalContext.VO]</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>匿名函数上下文的AO:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">匿名函数Context = {</span><br><span class="line"> AO: {</span><br><span class="line"> arguments: {</span><br><span class="line"> 0: 0</span><br><span class="line"> length: 1,</span><br><span class="line"> }</span><br><span class="line"> i: 0</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>所以当打印i值的时候,去作用域链中查找的时候,在匿名函数Context.AO找到了i为0,找了i值后就不会再往globalContext.VO中去查找了。data[1]和data[2]函数同理。<br>当然这道题还有别的解法:</p>
<ol>
<li>函数内部使用局部变量保存i值<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> data = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < <span class="number">3</span>; i++) {</span><br><span class="line"> data[i] = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> j = i;</span><br><span class="line"> <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(j);</span><br><span class="line"> };</span><br><span class="line"> })()</span><br><span class="line">}</span><br><span class="line">data[<span class="number">0</span>]();</span><br><span class="line">data[<span class="number">1</span>]();</span><br><span class="line">data[<span class="number">2</span>]();</span><br></pre></td></tr></table></figure></li>
<li>使用ES6中let关键字定义i值:<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> data = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="number">3</span>; i++) {</span><br><span class="line"> data[i] = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(i);</span><br><span class="line"> };</span><br><span class="line">}</span><br><span class="line">data[<span class="number">0</span>]();</span><br><span class="line">data[<span class="number">1</span>]();</span><br><span class="line">data[<span class="number">2</span>]();</span><br></pre></td></tr></table></figure></li>
</ol>
<p>这两种方案都可以达到效果,第一种和我们上面的讲解类似,因为AO中存的就是当前函数上下文的函数参数、函数内部声明的变量等。第二种使用let是比较推荐的方式,let关键字会将for循环的块隐式地声明为块作用域。而for循环头部的let不仅将i绑定到了for循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。而且let使用babel转义后的代码和我们第一种方案基本一致:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> data = [];</span><br><span class="line"><span class="keyword">var</span> _loop_1 = <span class="function"><span class="keyword">function</span> (<span class="params">i</span>) </span>{</span><br><span class="line"> data[i] = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(i);</span><br><span class="line"> };</span><br><span class="line">};</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i < <span class="number">3</span>; i++) {</span><br><span class="line"> _loop_1(i);</span><br><span class="line">}</span><br><span class="line">data[<span class="number">0</span>]();</span><br><span class="line">data[<span class="number">1</span>]();</span><br><span class="line">data[<span class="number">2</span>]();</span><br></pre></td></tr></table></figure>
<p>至此,我对闭包的理解因为结合了执行上下文,又更加清晰了。接下来我们讲一下闭包的优点和缺点。</p>
<h2 id="闭包的优缺点"><a href="#闭包的优缺点" class="headerlink" title="闭包的优缺点"></a>闭包的优缺点</h2><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul>
<li>缓存变量,被引用的变量长期保持在内存中</li>
</ul>
<ul>
<li>避免全局变量的污染</li>
</ul>
<ul>
<li>模拟私有化成员</li>
</ul>
<h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ul>
<li>使用不当,会导致内存泄漏</li>
</ul>
]]></content>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title>使用IntersectionObserver实现简易的图片懒加载组件</title>
<url>/2022/01/06/%E4%BD%BF%E7%94%A8IntersectionObserver%E5%AE%9E%E7%8E%B0%E7%AE%80%E6%98%93%E7%9A%84%E5%9B%BE%E7%89%87%E6%87%92%E5%8A%A0%E8%BD%BD%E7%BB%84%E4%BB%B6/</url>
<content><![CDATA[<p>最近在项目中准备做一下首屏加载时间优化,发现大量的图片资源加载,这些静态资源阻塞了页面onload。打算对这些图片进行懒加载处理,图片懒加载成熟的库有很多,今天来自己实现一个简单的懒加载图片组件,加深自己的理解,以及对<code>IntersectionObserver</code>API的使用。</p>
<span id="more"></span>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p>传统方式通过监听容器元素的<code>scroll</code>事件,来获取目标元素的坐标判断是否可见,然后加载静态资源。这种实现方式不仅要处理目标元素坐标位置的换算,还要使用防抖或者节流优化<code>scroll</code>事件频发触发,防止造成性能问题。<br>由于<code>IntersectionObserver</code>API的出现,我们可以简单的实现一个懒加载的功能。具体API解释可以查看MDN的<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver">IntersectionObserver介绍</a>。直接上代码:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">import React, { forwardRef, useEffect, useRef, useState } from 'react';</span><br><span class="line"></span><br><span class="line">interface IProps extends React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement> {}</span><br><span class="line"></span><br><span class="line">interface ComProps extends IProps {</span><br><span class="line"> customizeRef?: React.ForwardedRef<HTMLImageElement></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const ImgComponent = (props: ComProps) => {</span><br><span class="line"> const { customizeRef, src, ...rest } = props;</span><br><span class="line"> // 是否可见</span><br><span class="line"> const [loaded, setLoaded] = useState<boolean>(false);</span><br><span class="line"> // 图片ref</span><br><span class="line"> const imgRef = useRef<HTMLImageElement>(null);</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 转发ref</span><br><span class="line"> if(customizeRef && typeof customizeRef === "object") {</span><br><span class="line"> customizeRef.current = imgRef.current;</span><br><span class="line"> }</span><br><span class="line"> if(customizeRef && typeof customizeRef === 'function') {</span><br><span class="line"> customizeRef(imgRef.current);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if(!imgRef.current) return;</span><br><span class="line"> </span><br><span class="line"> // 图片懒加载</span><br><span class="line"> let intersectionObserver: IntersectionObserver = new IntersectionObserver((entries) => {</span><br><span class="line"> if (entries[0].isIntersecting) {</span><br><span class="line"> // isIntersecting为true,则代表当前元素可见</span><br><span class="line"> if(imgRef.current && intersectionObserver) {</span><br><span class="line"> // 加载静态资源</span><br><span class="line"> setLoaded(true);</span><br><span class="line"> // 加载过后,禁止监听该元素</span><br><span class="line"> intersectionObserver.unobserve(imgRef.current);</span><br><span class="line"> intersectionObserver.disconnect();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> // 监听target元素</span><br><span class="line"> intersectionObserver.observe(imgRef.current);</span><br><span class="line"></span><br><span class="line"> return () => {</span><br><span class="line"> // 组件卸载时停止监听</span><br><span class="line"> if (intersectionObserver && imgRef.current) {</span><br><span class="line"> intersectionObserver.unobserve(imgRef.current);</span><br><span class="line"> intersectionObserver.disconnect();</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> // 图片src更新时,重新执行监听操作</span><br><span class="line"> }, [src])</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <img {...rest} src={loaded ? src : undefined} ref={imgRef} /></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// 使用forwardRef转发ref</span><br><span class="line">const LazyLoadImg = forwardRef<HTMLImageElement, IProps>(( props,ref )=><ImgComponent {...props} customizeRef={ref} />)</span><br><span class="line"></span><br><span class="line">export default LazyLoadImg;</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>在TypeScript中使用useReducer</title>
<url>/2021/11/03/%E5%9C%A8TypeScript%E4%B8%AD%E4%BD%BF%E7%94%A8useReducer/</url>
<content><![CDATA[<p>在项目中已经使用了很久的TypeScript和Hooks,记录一下如何在TypeScript中友好的使用<code>useReducer</code>。</p>
<span id="more"></span>
<h2 id="简单的例子🌰"><a href="#简单的例子🌰" class="headerlink" title="简单的例子🌰"></a>简单的例子🌰</h2><p>先实现一个简单的例子</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">type StateType = {</span><br><span class="line"> name: string;</span><br><span class="line"> age: number;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">type Actions = {</span><br><span class="line"> type: 'Change_Name';</span><br><span class="line"> payload: string;</span><br><span class="line">} | {</span><br><span class="line"> type: 'Change_Age';</span><br><span class="line"> payload: number;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const initialState = {</span><br><span class="line"> name: '小明',</span><br><span class="line"> age: 18</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const reducerAction: Reducer<StateType, Actions> = (</span><br><span class="line"> state,</span><br><span class="line"> action,</span><br><span class="line">) => {</span><br><span class="line"> switch (action.type) {</span><br><span class="line"> case 'Change_Name':</span><br><span class="line"> return { ...state, name: action.payload };</span><br><span class="line"> case 'Change_Age':</span><br><span class="line"> return { ...state, age: action.payload };</span><br><span class="line"> default:</span><br><span class="line"> return state;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">function Index() {</span><br><span class="line"> const [state, dispatch] = useReducer(reducerAction, initialState);</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <div>姓名:{state.name}</div></span><br><span class="line"> <div>年龄:{state.age}</div></span><br><span class="line"> </div></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>在这个例子中,我们的TypeScript类型已经全部加入,并且可以得到正确的类型推断。但是大家可以发现<code>Actions</code>类型在每增加一个状态时都需要再手动增加一个类型,我们可以优化一下该步骤,定义一个<code>ActionMap</code>泛型和一个<code>PayloadType</code>类型来自动生成这个联合类型。</p>
<h2 id="自动生成Actions联合类型"><a href="#自动生成Actions联合类型" class="headerlink" title="自动生成Actions联合类型"></a>自动生成Actions联合类型</h2><p>优化上处代码如下:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 定义一个生成Action类型的泛型</span><br><span class="line">type ActionMap<M extends { [index: string]: any }> = {</span><br><span class="line"> [Key in keyof M]: M[Key] extends undefined</span><br><span class="line"> ? {</span><br><span class="line"> type: Key</span><br><span class="line"> }</span><br><span class="line"> : {</span><br><span class="line"> type: Key</span><br><span class="line"> payload: M[Key]</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">enum Types {</span><br><span class="line"> Name = 'Change_Name',</span><br><span class="line"> Age = 'Change_Age',</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">type StateType = {</span><br><span class="line"> name: string;</span><br><span class="line"> age: number;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 定义具体的Action类型</span><br><span class="line">type PayloadType = {</span><br><span class="line"> [Types.Name]: string;</span><br><span class="line"> [Types.Age]: number;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">type Action = ActionMap<PayloadType>[keyof ActionMap<PayloadType>]</span><br><span class="line"></span><br><span class="line">const initialState = {</span><br><span class="line"> name: '小明',</span><br><span class="line"> age: 18</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const reducerAction: Reducer<StateType, Action> = (</span><br><span class="line"> state,</span><br><span class="line"> action,</span><br><span class="line">) => {</span><br><span class="line"> switch (action.type) {</span><br><span class="line"> case Types.Name:</span><br><span class="line"> return { ...state, name: action.payload };</span><br><span class="line"> case Types.Age:</span><br><span class="line"> return { ...state, age: action.payload };</span><br><span class="line"> default:</span><br><span class="line"> return state;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>这样优化后,我们新增状态后,只需要增加<code>Types</code>和<code>PayloadType</code>的类型定义,便可自动生成相关的<code>Action</code>类型。</p>
<h2 id="简化的useReducer使用方式"><a href="#简化的useReducer使用方式" class="headerlink" title="简化的useReducer使用方式"></a>简化的useReducer使用方式</h2><p>看完上面的代码,你可能会说使用<code>useReducer</code>过于繁琐。那我们可以简化使用成本,放弃<code>action.type</code>的判断。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">type StateType = {</span><br><span class="line"> name: string;</span><br><span class="line"> age: number;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const initialState = {</span><br><span class="line"> name: '小明',</span><br><span class="line"> age: 18</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const simpleReducer = (prevState: StateType, updatedProperty: Partial<StateType>): StateType => ({</span><br><span class="line"> ...prevState,</span><br><span class="line"> ...updatedProperty</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line">function Index() {</span><br><span class="line"> const [state, setState] = useReducer(simpleReducer, initialState);</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <div>姓名:{state.name}</div></span><br><span class="line"> <div>年龄:{state.age}</div></span><br><span class="line"> <button</span><br><span class="line"> onClick={() => {</span><br><span class="line"> setState({</span><br><span class="line"> name: '小李'</span><br><span class="line"> })</span><br><span class="line"> }}</span><br><span class="line"> ></span><br><span class="line"> 修改姓名</span><br><span class="line"> </button></span><br><span class="line"> </div></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们的reducer函数,接受一个<code>Partial<StateType></code>类型的对象,每次调用时使用扩展运算符的方式返回一个新的State。这样可以略过<code>ActionType</code>的定义和匹配调用更新,减少模板代码的冗余。但是也会带来代码逻辑不清晰的缺点,可以根据自己的喜好来决定使用哪一种方式。</p>
]]></content>
<tags>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useAsync</title>
<url>/2021/09/15/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseAsync/</url>
<content><![CDATA[<p>今天来实现一个简单的用来处理<code>async</code>函数或者<code>Promise</code>函数的自定义Hook。<code>useAsync</code>只会单纯的处理异步操作,返回请求的状态和数据,如果想要使用功能齐全和成熟的的异步请求自定义Hook,可以使用<a href="https://swr.vercel.app/zh-CN">useSWR</a>或者<a href="https://ahooks.js.org/zh-CN/hooks/async">useRequest</a>。接下来来实现我们的<code>useAsync</code>,直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">interface AsyncData<T> {</span><br><span class="line"> data: T | null;</span><br><span class="line"> isLoading: boolean;</span><br><span class="line"> error: any;</span><br><span class="line">} </span><br><span class="line"></span><br><span class="line">const useAsync = <T>(</span><br><span class="line"> fn: () => Promise<T>,</span><br><span class="line"> deps: any[]</span><br><span class="line">): AsyncData<T> => {</span><br><span class="line"> // 异步操作需要返回的state</span><br><span class="line"> const [data, setData] = useState<T | null>(null);</span><br><span class="line"> const [isLoading, setIsLoading] = useState(false);</span><br><span class="line"> const [error, setError] = useState();</span><br><span class="line"> </span><br><span class="line"> // 利用useCallback缓存异步操作函数</span><br><span class="line"> const callback = useCallback(() => {</span><br><span class="line"> // loading状态</span><br><span class="line"> setIsLoading(true);</span><br><span class="line"> return fn().then((response: T) => {</span><br><span class="line"> // success</span><br><span class="line"> setIsLoading(false);</span><br><span class="line"> setData(response);</span><br><span class="line"> })</span><br><span class="line"> .catch((error: any) => {</span><br><span class="line"> // error</span><br><span class="line"> setIsLoading(false);</span><br><span class="line"> setError(error);</span><br><span class="line"> });</span><br><span class="line"> }, deps);</span><br><span class="line"></span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 依赖改变时重新执行异步操作</span><br><span class="line"> callback();</span><br><span class="line"> }, [callback]);</span><br><span class="line"></span><br><span class="line"> return {</span><br><span class="line"> data,</span><br><span class="line"> isLoading,</span><br><span class="line"> error,</span><br><span class="line"> };</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useEventListener</title>
<url>/2021/09/07/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseEventListener/</url>
<content><![CDATA[<p>今天实现一个封装<code>addEventListener</code>绑定事件的自定义Hook,逻辑简单。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">type TargetElement = HTMLElement | Element | Document | Window;</span><br><span class="line"></span><br><span class="line">type Options = {</span><br><span class="line"> target?: TargetElement || MutableRefObject;</span><br><span class="line"> capture?: boolean;</span><br><span class="line"> once?: boolean;</span><br><span class="line"> passive?: boolean;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">function useEventListener(eventName: string, handler: Function, options: Options = {}) {</span><br><span class="line"> // 存储事件回调的ref</span><br><span class="line"> const handlerRef = useRef<Function>();</span><br><span class="line"> // 每次渲染更新ref回调</span><br><span class="line"> handlerRef.current = handler;</span><br><span class="line"></span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 判断是否存在目标元素</span><br><span class="line"> const targetElement = options.target || window;</span><br><span class="line"> if (!targetElement.addEventListener) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> // 事件回调</span><br><span class="line"> const eventListener = (</span><br><span class="line"> event: Event,</span><br><span class="line"> ): EventListenerOrEventListenerObject | AddEventListenerOptions => {</span><br><span class="line"> return handlerRef.current && handlerRef.current(event);</span><br><span class="line"> };</span><br><span class="line"> // 绑定事件</span><br><span class="line"> targetElement.addEventListener(eventName, eventListener, {</span><br><span class="line"> capture: options.capture,</span><br><span class="line"> once: options.once,</span><br><span class="line"> passive: options.passive,</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> return () => {</span><br><span class="line"> // 销毁事件</span><br><span class="line"> targetElement.removeEventListener(eventName, eventListener, {</span><br><span class="line"> capture: options.capture,</span><br><span class="line"> });</span><br><span class="line"> };</span><br><span class="line"> // 依赖</span><br><span class="line"> }, [eventName, options.target, options.capture, options.once, options.passive]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useHover</title>
<url>/2021/09/01/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseHover/</url>
<content><![CDATA[<p>今天来实现一个获取Dom元素Hover状态的自定义Hooks,功能简单,逻辑也很简单。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">function useHover<T>(): [MutableRefObject<T>, boolean] {</span><br><span class="line"> // hover状态</span><br><span class="line"> const [value, setValue] = useState<boolean>(false); </span><br><span class="line"> // 要hover的dom元素Ref</span><br><span class="line"> const ref: any = useRef<T | null>(null);</span><br><span class="line"> // hover时</span><br><span class="line"> const handleMouseOver = (): void => setValue(true);</span><br><span class="line"> // 取消hover</span><br><span class="line"> const handleMouseOut = (): void => setValue(false);</span><br><span class="line"></span><br><span class="line"> useEffect(</span><br><span class="line"> () => {</span><br><span class="line"> const node: any = ref.current;</span><br><span class="line"> if (node) {</span><br><span class="line"> // 绑定事件</span><br><span class="line"> node.addEventListener("mouseover", handleMouseOver);</span><br><span class="line"> node.addEventListener("mouseout", handleMouseOut);</span><br><span class="line"> return () => {</span><br><span class="line"> // 取消绑定</span><br><span class="line"> node.removeEventListener("mouseover", handleMouseOver);</span><br><span class="line"> node.removeEventListener("mouseout", handleMouseOut);</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> // Dom改变时重新执行绑定逻辑</span><br><span class="line"> [ref.current] </span><br><span class="line"> );</span><br><span class="line"> return [ref, value];</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useDebounce</title>
<url>/2021/08/30/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseDebounce/</url>
<content><![CDATA[<p>很久没有更新博客了,突发一个想法,每天实现一个简单实用的自定义Hooks,来加深自己对React Hooks的理解。今天实现一个简单的防抖Hooks。本文只实现了最简单的防抖原理,如果想实现一个完整可用的,可以参考<code>ahooks</code>的<a href="https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useDebounce/index.ts">useDebounce</a>。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const useDebounce = (value: any, delay = 300) => {</span><br><span class="line"> // 要防抖的值</span><br><span class="line"> const [debounceVal, setDebounceVal] = useState<any>(value);</span><br><span class="line"></span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 创建一个setTimeout,在delay(300)毫秒后设置debounceVal</span><br><span class="line"> const handler = setTimeout(() => {</span><br><span class="line"> setDebounceVal(value);</span><br><span class="line"> }, delay);</span><br><span class="line"></span><br><span class="line"> // 在delay(300)毫秒内value发生更新,则清空定时器,然后重新创建一个setTimeout</span><br><span class="line"> // 直到delay(300)毫秒内value未发生更新,则执行handler,设置debounceVal的值</span><br><span class="line"> return () => {</span><br><span class="line"> clearTimeout(handler);</span><br><span class="line"> };</span><br><span class="line"> }, [value, delay]);</span><br><span class="line"></span><br><span class="line"> return debounceVal;</span><br><span class="line">};</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>上面代码只适用于基础的防抖情景,例如input输入框<code>onChange</code>事件等,下面实现一个支持传入函数的防抖Hooks。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">interface CbRef {</span><br><span class="line"> fn: (...args: any[]) => any;</span><br><span class="line"> timer: ReturnType<typeof setTimeout> | null;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const useDebounce = <T extends (...args: any[]) => any>(fn: T, wait: number, deps = []) => {</span><br><span class="line"> const DebounceRef = useRef<CbRef>({ fn, timer: null });</span><br><span class="line"></span><br><span class="line"> // 每次执行hooks 重新赋值函数,可以保证每次执行的函数都能拿到组件内最新的state</span><br><span class="line"> DebounceRef.current.fn = fn;</span><br><span class="line"></span><br><span class="line"> return useCallback(</span><br><span class="line"> (...args: any[]) => {</span><br><span class="line"> // 每次执行先判断是否存在上次的定时器,如果有则先清除定时器</span><br><span class="line"> if (DebounceRef.current.timer) {</span><br><span class="line"> clearTimeout(DebounceRef.current.timer);</span><br><span class="line"> }</span><br><span class="line"> // 超过wait秒后,执行setTimeout</span><br><span class="line"> DebounceRef.current.timer = setTimeout(() => {</span><br><span class="line"> DebounceRef.current.fn.apply(undefined, args);</span><br><span class="line"> DebounceRef.current.timer = null;</span><br><span class="line"> }, wait);</span><br><span class="line"> },</span><br><span class="line"> deps</span><br><span class="line"> ) as T;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useClickAway</title>
<url>/2021/09/06/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseClickAway/</url>
<content><![CDATA[<p>今天来实现一个判断是否在目标元素外点击的自定义Hook,逻辑比较简单。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">function useClickAway(ref: RefObject<HTMLElement>, handler: Function) {</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 监听事件执行的回调</span><br><span class="line"> const listener = (event: MouseEvent) => {</span><br><span class="line"> // 如果不存在目标元素,或者当前点击的元素在目标元素内,则不处理传入的handler</span><br><span class="line"> if (!ref.current || ref.current.contains(event.target as HTMLElement)) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> // 执行传入的handler</span><br><span class="line"> handler(event);</span><br><span class="line"> };</span><br><span class="line"> // 注册监听事件</span><br><span class="line"> document.addEventListener('click', listener);</span><br><span class="line"> return () => {</span><br><span class="line"> // 卸载监听事件</span><br><span class="line"> document.removeEventListener('click', listener);</span><br><span class="line"> };</span><br><span class="line"> }, [ref, handler]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useOnScreen</title>
<url>/2021/09/12/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseOnScreen/</url>
<content><![CDATA[<p>今天来实现一个可以检测元素何时可见的自定义Hook,该Hook实现逻辑依赖了<code>IntersectionObserver</code>API,对其不了解的可以去<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API">MDN</a>上查看详细介绍,主要是提供了一种异步检测目标元素与祖先元素或 viewport相交情况变化的方法,后续打算整理一篇详细的使用情况。这里先实现我们的自定义Hook,直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">function useOnScreen<T extends Element>(ref: MutableRefObject<T>, rootMargin: string = "0px"): boolean {</span><br><span class="line"> // 目标元素是否可见的状态state</span><br><span class="line"> const [isIntersecting, setIntersecting] = useState<boolean>(false);</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 创建一个 IntersectionObserver 对象</span><br><span class="line"> const observer = new IntersectionObserver(</span><br><span class="line"> ([entry]) => {</span><br><span class="line"> // entry代表当前元素IntersectionObserverEntry对象,isIntersecting属性代表其是否在当前窗口是否显示</span><br><span class="line"> setIntersecting(entry.isIntersecting);</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> // 配置root元素的外边距,即元素距离窗口多少距离时开始触发回调函数</span><br><span class="line"> rootMargin,</span><br><span class="line"> }</span><br><span class="line"> );</span><br><span class="line"> if (ref.current) {</span><br><span class="line"> // 开始监听目标元素</span><br><span class="line"> observer.observe(ref.current);</span><br><span class="line"> }</span><br><span class="line"> return () => {</span><br><span class="line"> // 停止监听目标元素</span><br><span class="line"> observer.unobserve(ref.current);</span><br><span class="line"> };</span><br><span class="line"> }, []); </span><br><span class="line"></span><br><span class="line"> // 返回目标元素可见状态</span><br><span class="line"> return isIntersecting;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useInterval</title>
<url>/2021/09/09/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseInterval/</url>
<content><![CDATA[<p>今天来实现一个可以处理<code>setInterval</code>的自定义Hook。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">function useInterval(fn: () => void, delay: number | null | undefined, options?: { immediate?: boolean }): void {</span><br><span class="line"> // 是否首次立即执行</span><br><span class="line"> const immediate = options?.immediate;</span><br><span class="line"> // 要执行的函数</span><br><span class="line"> const fnRef = useRef<() => void>();</span><br><span class="line"> // 每次更新 拿到最新的函数重新赋值</span><br><span class="line"> fnRef.current = fn;</span><br><span class="line"></span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 如果没有传入间隔 则不执行定时器</span><br><span class="line"> if (delay === undefined || delay === null) return;</span><br><span class="line"> // 是否立即执行函数</span><br><span class="line"> if (immediate) {</span><br><span class="line"> fnRef.current?.();</span><br><span class="line"> }</span><br><span class="line"> // 创建定时器</span><br><span class="line"> const timer = setInterval(() => {</span><br><span class="line"> fnRef.current?.();</span><br><span class="line"> }, delay);</span><br><span class="line"> return () => {</span><br><span class="line"> // 清除定时器</span><br><span class="line"> clearInterval(timer);</span><br><span class="line"> };</span><br><span class="line"> }, [delay]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useLockFn</title>
<url>/2021/09/05/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseLockFn/</url>
<content><![CDATA[<p>今天来实现一个可以对异步函数增加竞态锁的自定义Hook,这个在平时开发中也有很多场景可以使用,例如表单提交,当然也可以使用防抖、节流、Loading等方案。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) {</span><br><span class="line"> // 用于判断异步函数是否执行完成的标识</span><br><span class="line"> const lockRef = useRef(false);</span><br><span class="line"></span><br><span class="line"> // 使用useCallback来优化函数定义,防止不必要的渲染</span><br><span class="line"> return useCallback(</span><br><span class="line"> async (...args: P) => {</span><br><span class="line"> // 如果异步函数在执行中,则直接return 相当于Promise.resolve(undefined)</span><br><span class="line"> if (lockRef.current) return;</span><br><span class="line"> // 加锁</span><br><span class="line"> lockRef.current = true;</span><br><span class="line"> try {</span><br><span class="line"> // 执行异步函数</span><br><span class="line"> const ret = await fn(...args);</span><br><span class="line"> // 解锁</span><br><span class="line"> lockRef.current = false;</span><br><span class="line"> return ret;</span><br><span class="line"> } catch (e) {</span><br><span class="line"> // 出现异常解锁</span><br><span class="line"> lockRef.current = false;</span><br><span class="line"> throw e;</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> [fn],</span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useLocalStorage</title>
<url>/2021/09/02/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseLocalStorage/</url>
<content><![CDATA[<p>今天来实现一个可以将<code>state</code>同步存储在<code>localStorage</code>中的自定义Hook。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 接受要设置key和初始值</span><br><span class="line">function useLocalStorage<T>(key: string, initialValue: T) {</span><br><span class="line"> // 将接受的初始值传入state</span><br><span class="line"> const [storageValue, setStorageValue] = useState<T>(() => {</span><br><span class="line"> try {</span><br><span class="line"> // 先从localStorage中获取key,判断是否有值,有则用本地,否则使用初始值</span><br><span class="line"> const localValue = window.localStorage.getItem(key);</span><br><span class="line"> return localValue ? JSON.parse(localValue) : initialValue;</span><br><span class="line"> } catch (error) {</span><br><span class="line"> console.log(error);</span><br><span class="line"> return initialValue;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> // 封装setStorageValue, 将传入的值存入localStorage</span><br><span class="line"> const setValue = (value: T | ((val: T) => T)) => {</span><br><span class="line"> try {</span><br><span class="line"> // 支持传入回调函数,并将storageValue传入回调</span><br><span class="line"> const newValue =</span><br><span class="line"> value instanceof Function ? value(storageValue) : value;</span><br><span class="line"> // 更新state</span><br><span class="line"> setStorageValue(newValue);</span><br><span class="line"> // 保存到localStorage</span><br><span class="line"> window.localStorage.setItem(key, JSON.stringify(newValue));</span><br><span class="line"> } catch (error) {</span><br><span class="line"> console.log(error);</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> return [storageValue, setValue] as const;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useThrottle</title>
<url>/2021/08/31/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseThrottle/</url>
<content><![CDATA[<p>昨天实现了防抖的自定义Hooks<code>useDebounce</code>,那么今天来实现一下节流的自定义Hooks<code>useThrottle</code>。关于节流,之前的文章中有提到过可以使用定时器和时间戳两种方式来实现,那么我们的hooks也可以使用两种方案来实现。</p>
<span id="more"></span>
<h3 id="使用定时器实现"><a href="#使用定时器实现" class="headerlink" title="使用定时器实现"></a>使用定时器实现</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">type Nullable<T> = T | null;</span><br><span class="line"></span><br><span class="line">interface CbRef {</span><br><span class="line"> fn: (...args: any[]) => any;</span><br><span class="line"> timer: Nullable<ReturnType<typeof setTimeout>>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const useThrottle = <T extends (...args: any[]) => any>(fn: T, delay: number, dep = []) => {</span><br><span class="line"> const ThrottleRef = useRef<CbRef>({ fn, timer: null });</span><br><span class="line"></span><br><span class="line"> // 每次执行hooks 重新赋值函数,可以保证每次执行的函数都能拿到组件内最新的state</span><br><span class="line"> ThrottleRef.current.fn = fn;</span><br><span class="line"></span><br><span class="line"> return useCallback((...args: any[]) => {</span><br><span class="line"> // 不存在定时器时才执行函数</span><br><span class="line"> if (!ThrottleRef.current.timer) {</span><br><span class="line"> ThrottleRef.current.timer = setTimeout(() => {</span><br><span class="line"> // 每隔delay毫秒后 清空定时器</span><br><span class="line"> ThrottleRef.current.timer = null;</span><br><span class="line"> }, delay);</span><br><span class="line"> // 执行节流函数</span><br><span class="line"> ThrottleRef.current.fn.call(undefined, ...args);</span><br><span class="line"> }</span><br><span class="line"> }, dep);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h3 id="使用时间戳"><a href="#使用时间戳" class="headerlink" title="使用时间戳"></a>使用时间戳</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const useThrottle = <T extends (...args: any[]) => any>(</span><br><span class="line"> fn: T,</span><br><span class="line"> wait: number,</span><br><span class="line"> deps = []</span><br><span class="line">) => {</span><br><span class="line"> const throttleRef = useRef({ fn, prev: 0 });</span><br><span class="line"> throttleRef.current.fn = fn;</span><br><span class="line"></span><br><span class="line"> return useCallback(</span><br><span class="line"> (...args: any[]) => {</span><br><span class="line"> const now = +Date.now();</span><br><span class="line"> // 判断当前时间与上次事件差是否大于延时,大于则执行函数</span><br><span class="line"> if (now - throttleRef.current.prev >= wait) {</span><br><span class="line"> throttleRef.current.fn.apply(undefined, args);</span><br><span class="line"> // 缓存当前时间</span><br><span class="line"> throttleRef.current.prev = now;</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> deps</span><br><span class="line"> ) as T;</span><br><span class="line">};</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useTitle</title>
<url>/2021/09/12/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseTitle/</url>
<content><![CDATA[<p>今天来实现一个设置页面标题的自定义Hook,逻辑简单。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">function useTitle(title: string, restore?: boolean) {</span><br><span class="line"> // 原始的也页面标题</span><br><span class="line"> const titleRef = useRef(document.title);</span><br><span class="line"> </span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 将页面标题更新为传入的标题</span><br><span class="line"> document.title = title;</span><br><span class="line"> }, [title]);</span><br><span class="line"></span><br><span class="line"> useEffect(() => {</span><br><span class="line"> return () => {</span><br><span class="line"> if(restore) {</span><br><span class="line"> // 如果需要在页面卸载时恢复原始标题</span><br><span class="line"> document.title = titleRef.current;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }, []);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useUpdateEffect</title>
<url>/2021/09/10/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseUpdateEffect/</url>
<content><![CDATA[<p>今天来实现一个只在依赖更新时执行的<code>useEffect</code>,逻辑简单。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const useUpdateEffect = (effect, deps) => {</span><br><span class="line"> // 判断组件是否已经挂载的ref</span><br><span class="line"> const isMounted = useRef(false);</span><br><span class="line"></span><br><span class="line"> useEffect(() => {</span><br><span class="line"> </span><br><span class="line"> if (!isMounted.current) {</span><br><span class="line"> // 初次执行时</span><br><span class="line"> isMounted.current = true;</span><br><span class="line"> } else {</span><br><span class="line"> // 组件已经挂载过</span><br><span class="line"> return effect();</span><br><span class="line"> }</span><br><span class="line"> }, deps);</span><br><span class="line">};</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useToggle</title>
<url>/2021/09/03/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseToggle/</url>
<content><![CDATA[<p>今天来实现一个切换布尔状态值的自定义Hook。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">const useToggle = (initialState: boolean = false): [boolean, any] => {</span><br><span class="line"> // 接受默认布尔状态,默认为false</span><br><span class="line"> const [state, setState] = useState<boolean>(initialState);</span><br><span class="line"> // 创建切换函数,布尔值取反</span><br><span class="line"> // 使用useCallback优化函数定义,防止函数重新定义 导致组件不必要的渲染</span><br><span class="line"> const toggle = useCallback((): void => setState(state => !state), []);</span><br><span class="line"> return [state, toggle];</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之useWindowSize</title>
<url>/2021/09/13/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BuseWindowSize/</url>
<content><![CDATA[<p>今天来实现一个获取当前window窗口的宽高的自定义Hook。逻辑简单,直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">interface Size {</span><br><span class="line"> width: number;</span><br><span class="line"> height: number;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function useWindowSize(): Size {</span><br><span class="line"></span><br><span class="line"> const [windowSize, setWindowSize] = useState<Size>({</span><br><span class="line"> width: window.clientWidth,</span><br><span class="line"> height: window.clientHeight,</span><br><span class="line"> });</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // resize的回调函数</span><br><span class="line"> function handleResize() {</span><br><span class="line"> // 设置窗口大小</span><br><span class="line"> setWindowSize({</span><br><span class="line"> width: window.clientWidth,</span><br><span class="line"> height: window.clientHeight,</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> // 监听resize</span><br><span class="line"> window.addEventListener("resize", handleResize);</span><br><span class="line"> // 取消监听</span><br><span class="line"> return () => window.removeEventListener("resize", handleResize);</span><br><span class="line"> }, []);</span><br><span class="line"> return windowSize;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之usePrevious</title>
<url>/2021/09/04/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BusePrevious/</url>
<content><![CDATA[<p>今天来实现一个可以获取组件更新前一轮的<code>props</code>和<code>state</code>的自定义Hook,这个Hook在React文档中有描述,我在之前的文章中也有单独解读,这里为了记录每天的自定义Hook,就重新实现一次。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">function usePrevious<T>(value: T): T {</span><br><span class="line"> // 使用ref存储要获取的props或者state</span><br><span class="line"> const ref: any = useRef<T>();</span><br><span class="line"> // 在useEffect中重新设置ref的值</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> // 因为useEffect是在Render后才执行的,所以会先返回ref.current 再执行useEffect把新的value更新到ref上,</span><br><span class="line"> // 下次更新时获取的是上一轮更新的值</span><br><span class="line"> ref.current = value;</span><br><span class="line"> }, [value]); </span><br><span class="line"> // 返回上一轮的值</span><br><span class="line"> return ref.current;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>每天实现一个自定义Hook之usePersistFn</title>
<url>/2021/09/08/%E6%AF%8F%E5%A4%A9%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89Hook%E4%B9%8BusePersistFn/</url>
<content><![CDATA[<p>今天来实现一个可以持久化<code>function</code>,保证函数引用地址不会改变的自定义Hook。直接上代码:</p>
<span id="more"></span>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">export type noop = (...args: any[]) => any;</span><br><span class="line"></span><br><span class="line">function usePersistFn<T extends noop>(fn: T) {</span><br><span class="line"> // 存储传入函数的ref变量</span><br><span class="line"> const fnRef = useRef<T>(fn);</span><br><span class="line"> // 每次渲染fn的最新值都会记录在fnRef中</span><br><span class="line"> fnRef.current = fn;</span><br><span class="line"> </span><br><span class="line"> // 返回给组件使用的函数</span><br><span class="line"> const persistFn = useRef<T>();</span><br><span class="line"> // 首次渲染时给persistFn赋值,后续渲染persistFn不会更新</span><br><span class="line"> if (!persistFn.current) {</span><br><span class="line"> // persistFn的引用地址一直在这个匿名函数中</span><br><span class="line"> persistFn.current = function (...args) {</span><br><span class="line"> return fnRef.current!.apply(this, args);</span><br><span class="line"> } as T;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 返回persistFn</span><br><span class="line"> return persistFn.current!;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag>React</tag>
<tag>JavaScript</tag>
<tag>React Hook</tag>
<tag>TypeScript</tag>
</tags>
</entry>
<entry>
<title>浅析React性能优化</title>
<url>/2020/05/09/%E6%B5%85%E6%9E%90React%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/</url>
<content><![CDATA[<p>使用React开发也有两年了,今天来总结一下怎么去优化我们的React应用。</p>
<h2 id="减少render的次数"><a href="#减少render的次数" class="headerlink" title="减少render的次数"></a>减少render的次数</h2><p>因为React的渲染机制,也就是Reconciliation的过程,每次render函数的执行,React会对比本次render的虚拟Dom和上次render的虚拟Dom,并且对有差异的地方进行更新,最后渲染为真实Dom。虽然React有自己优化后的Diff算法加持,但是只要对比就会出现性能消耗。所以我们首要的优化就是减少render的次数。</p>
<h3 id="1-使用纯组件-纯函数"><a href="#1-使用纯组件-纯函数" class="headerlink" title="1.使用纯组件/纯函数"></a>1.使用纯组件/纯函数</h3><p>React中提供了<code>PurComponent</code>和<code>memo</code>两个API,这两个API的功能类似,都是对props进行浅比较(<code>PurComponent</code>还会对比state),如果props浅比较相等,则跳过渲染,复用上一次的渲染结果。所以这两个API可以达到我们减少render次数的目的。<code>memo</code>方法有第二个参数可以传入一个自定义对比函数,可以用来自己控制对比过程,具体使用方法可以参考官方文档。这两个API的使用方法如下:<br>PurComponent:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Children</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">PureComponent</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'子节点渲染!'</span>)</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> 子节点</span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> state = {</span><br><span class="line"> <span class="attr">test</span>: <span class="string">'123'</span>,</span><br><span class="line"> <span class="attr">testObj</span>: {<span class="attr">a</span>: <span class="number">123</span>}</span><br><span class="line"> }</span><br><span class="line"> testClick = <span class="function">() =></span> {</span><br><span class="line"> <span class="built_in">this</span>.setState({</span><br><span class="line"> <span class="attr">test</span>: <span class="string">'456'</span></span><br><span class="line"> })</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">p</span>></span>{this.state.test}<span class="tag"></<span class="name">p</span>></span></span></span><br><span class="line"><span class="xml"> {/* 点击按钮并不会触发Children组件的重新渲染 */}</span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">{this.testClick}</span>></span>点击<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">Children</span> <span class="attr">testObj</span>=<span class="string">{this.state.testObj}</span>></span><span class="tag"></<span class="name">Children</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>React.memo:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> Children = React.memo(<span class="function">(<span class="params">{testObj}</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span>子节点<span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line">}) </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [ test, setTest ] = useState(<span class="string">'123'</span>)</span><br><span class="line"> <span class="keyword">const</span> [ testObj, setTestObj ] = useState({});</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> testClick = <span class="function">() =></span> {</span><br><span class="line"> setTest(<span class="string">'456'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">p</span>></span>{test}<span class="tag"></<span class="name">p</span>></span></span></span><br><span class="line"><span class="xml"> {/* 点击按钮并不会触发Children组件的重新渲染 */}</span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">{testClick}</span>></span>点击<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">Children</span> <span class="attr">testObj</span>=<span class="string">{testObj}</span>></span><span class="tag"></<span class="name">Children</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></></span></span></span><br><span class="line"> )</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="2-使用shouldComponentUpdate生命周期函数"><a href="#2-使用shouldComponentUpdate生命周期函数" class="headerlink" title="2.使用shouldComponentUpdate生命周期函数"></a>2.使用shouldComponentUpdate生命周期函数</h3><p>该函数是用来决定是否重新组件的,也就是是否进行render。该函数接收nextProps和nextState为参数,可以用来对比this.props和this.state来确定是否需要重新更新组件。需要注意的是该函数的返回值和<code>React.memo</code>方法的第二个参数<code>areEqual</code>函数的返回值相反,如果<code>shouldComponentUpdate</code>返回true则重新渲染组件,否则,不渲染。而<code>areEqual</code>函数返回true则代表props对比相等,不进行渲染,否则,重新渲染。使用如下:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="title">shouldComponentUpdate</span>(<span class="params">nextProps, nextState</span>)</span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">this</span>.props.color !== nextProps.color) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">this</span>.state.count !== nextState.count) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="3-使用正确的事件处理器"><a href="#3-使用正确的事件处理器" class="headerlink" title="3.使用正确的事件处理器"></a>3.使用正确的事件处理器</h3><p>我们经常会在父组件中传递事件给子组件,但是不同的传递方式会导致不同的渲染过程。因为函数也属于引用类型,所以如果使用不当会导致每次都是新的引用,就导致不必要的渲染diff过程。这里列出类组件和函数组件传递事件比较好的方式:<br>类组件:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 类组件中使用Public Class Fields 箭头函数直接作为公共类方法</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> handleClick = <span class="function">() =></span> {</span><br><span class="line"> <span class="comment">/*...*/</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">Children</span> <span class="attr">onClick</span>=<span class="string">{this.handleClick}</span>></span><span class="tag"></<span class="name">Children</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类组件中在构造函数中手动bind一次,那么每次都是使用都是同一个函数</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span>{</span><br><span class="line"> <span class="built_in">super</span>(props)</span><br><span class="line"> <span class="built_in">this</span>.handleClick = <span class="built_in">this</span>.handleClick.bind(<span class="built_in">this</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">handleClick</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="comment">/*...*/</span></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">Children</span> <span class="attr">onClick</span>=<span class="string">{this.handleClick}</span>></span><span class="tag"></<span class="name">Children</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="string">``</span><span class="string">` </span></span><br><span class="line"><span class="string">函数式组件:</span></span><br><span class="line"><span class="string">`</span><span class="string">``</span>js</span><br><span class="line"><span class="comment">// 利用hooks API useCallback缓存函数 只要deps没有改变 这样每次组件执行的都是同一个函数引用</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> handleClick = useCallback(<span class="function">() =></span> {</span><br><span class="line"> <span class="comment">/*...*/</span></span><br><span class="line"> }, [deps])</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">Children</span> <span class="attr">onClick</span>=<span class="string">{handleClick}</span>></span><span class="tag"></<span class="name">Children</span>></span></span></span><br><span class="line"> )</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="4-使用不可变数据"><a href="#4-使用不可变数据" class="headerlink" title="4.使用不可变数据"></a>4.使用不可变数据</h3><p>使用第三方工具库<a href="https://github.com/immutable-js/immutable-js">immutable.js</a>、<a href="https://github.com/immerjs/immer">immer.js</a>,使状态变得可预测,而且配合<code>shouldComponentUpdate</code>、<code>PurComponent</code>、<code>React.memo</code>使用,会让浅比对变得更精确和高效。具体使用方法,大家感兴趣的话可以参考这两个库的文档。</p>
<h2 id="减少渲染的计算量"><a href="#减少渲染的计算量" class="headerlink" title="减少渲染的计算量"></a>减少渲染的计算量</h2><p>上面减少render的次数,是为了diff的过程,但是如果组件达到的渲染的条件,那么优化的点就是减少diff的性能消耗了。</p>
<h3 id="1-列表组件增加唯一标识key"><a href="#1-列表组件增加唯一标识key" class="headerlink" title="1.列表组件增加唯一标识key"></a>1.列表组件增加唯一标识key</h3><p>我们知道React对比虚拟Dom的子节点时,使用递归同时遍历两个子节点列表,当产生差异时就会生成一个新的节点。如果我们对原有子节点列表尾部增加元素,那么之前的节点就会被复用。但是如果在头部增加节点,那么diff时React发现每个节点都不相同就会重新创建所有节点,导致不必要的性能消耗。<br>官方推荐我们给组件增加一个<code>key</code>属性,React会使用<code>key</code>的值来比对,判断节点是否改变,如果发现有已存在的<code>key</code>值,就会复用该节点。例如我们在头部新增节点,通过<code>key</code>值可以发现原有节点都可以复用,只是移动位置,那就比重新创建节点的性能好很多。<br>需要注意的是<code>key</code>值的选取原则:<em><strong>不需要全局唯一,但必须列表中保持唯一</strong></em>。另外使用数组元素的下标作为<code>key</code>值时,如果元素顺序不改变的情况下是没有问题的,但是如果元素顺序改变的话,会导致当前元素<code>key</code>值改变,也就会使diff效率下降,达不到我们优化的目的。<br>这个小结的代码大家可以参考官方文档<a href="https://react.docschina.org/docs/reconciliation.html">协调</a>,在这就不贴代码了。</p>
<h3 id="2-减少嵌套层级"><a href="#2-减少嵌套层级" class="headerlink" title="2.减少嵌套层级"></a>2.减少嵌套层级</h3><p>使用<code>React.Fragment</code>代替不必要的父节点标签。在React组件中,我们render中必须具有一个公共父标签,而有时我们并不需要这个父标签,但是又不得不为了满足这个条件增加一个<code>div</code>标签。例如:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [ test, setTest ] = useState(<span class="string">'123'</span>)</span><br><span class="line"> <span class="keyword">const</span> testClick = <span class="function">() =></span> {</span><br><span class="line"> setTest(<span class="string">'456'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">p</span>></span>{test}<span class="tag"></<span class="name">p</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">{testClick}</span>></span>点击<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> )</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可以看到这个外层<code>div</code>标签其实并没有任何意义,但是我们必须要额外提供一个父级标签,因为多了一个层级也会导致额外渲染元素的性能消耗。我们可以把代码改成如下方式:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> [ test, setTest ] = useState(<span class="string">'123'</span>)</span><br><span class="line"> <span class="keyword">const</span> testClick = <span class="function">() =></span> {</span><br><span class="line"> setTest(<span class="string">'456'</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">p</span>></span>{test}<span class="tag"></<span class="name">p</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">{testClick}</span>></span>点击<span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></></span></span></span><br><span class="line"> )</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>这样就节省了渲染不必要的元素,<code><></></code>是<code><React.Fragment></code>的短语法,但是该语法不支持<code>key</code>属性,所以如果有需要还是使用<code><React.Fragment></code>语法。</p>
<h3 id="3-使用虚拟列表"><a href="#3-使用虚拟列表" class="headerlink" title="3.使用虚拟列表"></a>3.使用虚拟列表</h3><p>虚拟列表可以对我们的长列表组件或者复杂组件树进行优化,减少渲染的节点,只渲染显示在当前视图的节点。可以应用在以下组件场景中:</p>
<ul>
<li>无限滚动列表,表格,下拉列表,spreadsheets</li>
</ul>
<ul>
<li>无限切换的日历或轮播图</li>
</ul>
<ul>
<li>大数据量或无限嵌套的树</li>
</ul>
<ul>
<li>聊天窗,数据流(feed), 时间轴等</li>
</ul>
<p>不过由于该方式我并没有使用过,大家可以参考对应组件方案的文档:</p>
<ul>
<li><a href="https://github.com/bvaughn/react-virtualized">react-virtualized</a></li>
</ul>
<ul>
<li><a href="https://github.com/bvaughn/react-window">react-window</a></li>
</ul>
<h3 id="4-组件懒加载"><a href="#4-组件懒加载" class="headerlink" title="4.组件懒加载"></a>4.组件懒加载</h3><p>使用<code>React.lazy</code>和<code>Suspense</code>组件,可以让我们实现组件懒加载的效果,从而减少渲染的节点。例如我们的<code>Tab</code>组件,并不需要一次性把所有的子组件都渲染出来,而是当前子节点被激活时再渲染,这样也会提升我们的渲染性能。懒加载的使用例子:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> React, { Suspense } <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> OtherComponent = React.lazy(<span class="function">() =></span> <span class="keyword">import</span>(<span class="string">'./OtherComponent'</span>));</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">MyComponent</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="xml"><span class="tag"><<span class="name">div</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">Suspense</span> <span class="attr">fallback</span>=<span class="string">{</span><<span class="attr">div</span>></span>Loading...<span class="tag"></<span class="name">div</span>></span>}></span></span><br><span class="line"><span class="xml"> <span class="tag"><<span class="name">OtherComponent</span> /></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">Suspense</span>></span></span></span><br><span class="line"><span class="xml"> <span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>当然<code>React.lazy</code>配合<code>import()</code>使用,然后利用<code>webpack</code>打包的时候,也可以达到代码分割的效果。</p>
<h3 id="5-减少计算量"><a href="#5-减少计算量" class="headerlink" title="5.减少计算量"></a>5.减少计算量</h3><p>在hooks中,React提供了<code>useMemo</code>API,可以对我们的计算量较大函数的结果进行缓存,只要依赖项没有改变,那么该计算函数就不会再次执行,这样就大大的提升了我们渲染的效率,减少了计算量。当然如果计算量很小,可以不使用这个API,因为任何优化都是有成本的,如果使用错误,可能会导致更多的问题。使用如下:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> memoizedValue = useMemo(<span class="function">() =></span> computeExpensiveValue(a, b), [a, b]);</span><br></pre></td></tr></table></figure>
<p>需要注意的是,如果没有给<code>useMemo</code>提供依赖项,那么每次渲染都会导致计算函数重新执行。另外<code>useCallback(fn, [deps])</code>相当于<code>useMemo(() => fn, [deps])</code>。</p>
<p>以上是我所总结的React性能优化的方式,当然还有更多的优化方案,待下次整理后再补充上。</p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><blockquote>
<p><a href="https://juejin.im/post/5d045350f265da1b695d5bf2#heading-12">浅谈React性能优化的方向</a><br><a href="https://mp.weixin.qq.com/s/mpL1MxLjBqSO49TRijeyeg">React函数式组件性能优化指南</a><br><a href="https://jancat.github.io/post/2019/translation-usememo-and-usecallback/">【译】什么时候使用 useMemo 和 useCallback</a><br><a href="https://www.infoq.cn/article/KVE8xtRs-uPphptq5LUz">21个React性能优化技巧</a></p>
</blockquote>
]]></content>