-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
2031 lines (1856 loc) · 584 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>什么是ssr</title>
<url>/2024/03/12/%20%E4%BB%80%E4%B9%88%E6%98%AFssr/</url>
<content><![CDATA[<h1 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h1><p>服务器端渲染(Server-Side Rendering,SSR)指的是在服务器上完成网页渲染并将将其发送给客户端的过程。</p>
<h1 id="为什么需要SSR"><a href="#为什么需要SSR" class="headerlink" title="为什么需要SSR?"></a>为什么需要SSR?</h1><p>SSR 发送给客户端的是包含了完整内容的网页,这样用户可以先看到网页的内容,而不需要等待“网页加载>执行JS>加载数据>渲染到网页”,从而提升用户体验。另一方面,因为网页内已经包含了具体内容,对搜索引擎也更加友好。</p>
<h1 id="SSR的优势"><a href="#SSR的优势" class="headerlink" title="SSR的优势"></a>SSR的优势</h1><p>SSR 能改善用户体验。<br>与传统服务器语言的对比很久之前,我们用 PHP 之类的语言输出 HTML,但是我们并不称其为”服务器端渲染”,因为现在的SSR 有更多的优化:</p>
<ul>
<li>语言同构化:开发难度降低。</li>
<li>数据传递与状态管理:虽然还是 JSON,但框架尽量帮我们做好了。</li>
<li>渲染函数由边缘计算负责:更快的速度、</li>
<li>页面切换时不会重新加载-</li>
</ul>
<h1 id="与传统服务器语言的对比"><a href="#与传统服务器语言的对比" class="headerlink" title="与传统服务器语言的对比"></a>与传统服务器语言的对比</h1><p>很久之前,我们用PHP之类的语言输出HTML,但是我们并不称其为”服务器端渲染”,因为现在的服务器端渲染有更多的优:</p>
<ul>
<li>语言同构化:开发难度降低。</li>
<li>数据传递与状态管理:虽然还是 JSON,但框架尽量帮我们做好了。</li>
<li>渲染函数由边缘计算负责:更快的速度、</li>
<li>页面切换时不会重新加载。</li>
</ul>
<h1 id="SSR的一般构成"><a href="#SSR的一般构成" class="headerlink" title="SSR的一般构成"></a>SSR的一般构成</h1><p>当谈论服务器端渲染(SSR)时,一般构成包掊以下几个关键部分:</p>
<ul>
<li>服务器端应用程序:这是运行在服务器上的应用程序,负责接收客户端请求,执行渲染过程,并返回渲染后的页面给客户端。</li>
<li>路由:服务器端应用程序需要能够根据客户端请求的不同路径,调用对应的渲染逻辑和数据获取方法。</li>
<li>模板引擎:用于将页面模板和数据结合,生成最终的 HTML 内容。我们目前会使用的模版引擎都基于某款 MVVM 框架。</li>
<li>数据获取:在渲染页面之前,服务器端应用程序通常需要获取页面所需的数据,这可能涉及到从<br>数据库、API或其他来源获取数据。</li>
<li>状态管理:在 SSR 应用中,需要考虑如何管理客户端和服务器端的状态同步,以避免出现不一致的情况。</li>
<li>客户端交互:尽管整个页面的初始渲染是在服务器端完成的,但在客户端加载后仍可能需要进行交互,如使用 JavaScript 添加动态内容或处理用户操作。</li>
</ul>
<h1 id="Nuxt3-的SSR-组件"><a href="#Nuxt3-的SSR-组件" class="headerlink" title="Nuxt3 的SSR 组件"></a>Nuxt3 的SSR 组件</h1><p>对于大部分应用而言,<strong>服务器端渲染=加载数据+渲染HTML</strong>。所以理解 Nuxt3 里复杂加载数据的组件非常重要。</p>
<ul>
<li>与异步组件<suspense></suspense></li>
<li>和<code>useAsyncData</code> <code>useLazyAsyncData</code></li>
<li><code>useFetch</code>和<code>useLazyFetchi</code></li>
<li>用 <code>process.client</code> 和 <code><client-only></code>来处理仅限浏览器内部使用的功能</li>
<li>用 <code>process.server </code>来处理仅限服务器使用的功能5</li>
<li>https:/nuxt.com/docs/getting-started/data-fetching#serializing-data-from-api-routes</li>
</ul>
<h1 id="Nuxt3-的渲染规则与缓存处理"><a href="#Nuxt3-的渲染规则与缓存处理" class="headerlink" title="Nuxt3 的渲染规则与缓存处理"></a>Nuxt3 的渲染规则与缓存处理</h1><p>Nuxt3 提供三种不同的渲染模式:</p>
<ul>
<li>SSR:默认。即在服务器端渲染之后再发给客户端</li>
<li>ISR:部署后,渲染之后即保留缓存至下次渲染(渐进式渲染)</li>
<li>SWR:保留缓存,并在指定时间后校验缓存</li>
<li>prerender:部署时生成静态页面<br><a href="https://nuxt.com/docs/guide/concepts/rendering">https://nuxt.com/docs/guide/concepts/rendering</a>!</li>
</ul>
<h1 id="如何鉴别用户身份"><a href="#如何鉴别用户身份" class="headerlink" title="如何鉴别用户身份"></a>如何鉴别用户身份</h1><p>在 vue,或者说传统 SPA 里,所有请求都是<strong>后</strong>请求,即完成网页加载、JS 执行完毕后,再发起请求。这些请求,可以认为完全由开发者控制,即你知道什么时候该请求,然后发起请求。一般来说所有的请求都是<strong>数据交互</strong>类。</p>
<p>在 Nuxt 里,因为 SSR 的存在,<em>所以请求至少可以分成两类:页面渲染类,数据交互类。</em>前者会影响到 HTML 的内容。在网络环境里,存在大量缓存节点,假如跟用户相关的敏感数据渲染成HTML,缓存到 CDN 当中,会是非常大的安全问题。所以 Nuxt 在 SSR 及其内部发起请求时,不会携带 cookie;在用户主动发起的请求里,才会携带 cookie。官方文档 useRequestHeaders</p>
<p>如果要使用 localstorage 保存用户数据,则势必会存在两次渲染,请处理好loading 状态。</p>
]]></content>
<categories>
<category>SSR</category>
</categories>
<tags>
<tag>SSR</tag>
</tags>
</entry>
<entry>
<title>nuxt-尝鲜</title>
<url>/2024/03/12/%20nuxt-%E5%B0%9D%E9%B2%9C/</url>
<content><![CDATA[<h1 id="runtimeConfig与app-confg"><a href="#runtimeConfig与app-confg" class="headerlink" title="runtimeConfig与app.confg"></a>runtimeConfig与app.confg</h1><p>回看<code>app.config.ts</code>与<code>nuxt.config.ts</code>,俩者都是向应用程序的其余部分公开变量</p>
<p> runtimeConfig:使用环境变量构建后需要指定的私有或公共令牌。<br> app.config:在构建时确定的公共令牌、网站配置(如主题变体、标题和任何不敏感的项目配置)。</p>
<p> 以下有使用准则,请参考以下标准:</p>
<h2 id="特征比较"><a href="#特征比较" class="headerlink" title="特征比较"></a>特征比较</h2><table>
<thead>
<tr>
<th>特征</th>
<th>runtimeConfig</th>
<th>app.config</th>
</tr>
</thead>
<tbody><tr>
<td>客户端</td>
<td>水合</td>
<td>捆绑</td>
</tr>
<tr>
<td>环境变量</td>
<td>✅是的</td>
<td>不</td>
</tr>
<tr>
<td>反应性</td>
<td>✅是的</td>
<td>✅是的</td>
</tr>
<tr>
<td>类型支持</td>
<td>✅部分</td>
<td>✅是的</td>
</tr>
<tr>
<td>每个请求的配置</td>
<td>✅不</td>
<td>✅是的</td>
</tr>
<tr>
<td>热模块更换</td>
<td>✅不</td>
<td>✅是的</td>
</tr>
<tr>
<td>非原始 JS 类型</td>
<td>✅不</td>
<td>✅是的</td>
</tr>
</tbody></table>
<h1 id="样式"><a href="#样式" class="headerlink" title="样式"></a>样式</h1><h2 id="本地样式表"><a href="#本地样式表" class="headerlink" title="本地样式表"></a>本地样式表</h2><p> 本地样式表存在<code>assets/</code>目录</p>
<h2 id="组件内导入"><a href="#组件内导入" class="headerlink" title="组件内导入"></a>组件内导入</h2><p> 通过<code>@import</code>导入</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script></span><br><span class="line">// Use a static import for server-side compatibility</span><br><span class="line">import '~/assets/css/first.css'</span><br><span class="line"></span><br><span class="line">// Caution: Dynamic imports are not server-side compatible</span><br><span class="line">import('~/assets/css/first.css')</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><style></span><br><span class="line">@import url("~/assets/css/second.css");</span><br><span class="line"></style></span><br></pre></td></tr></tbody></table></figure>
<h2 id="通过npm分发下载"><a href="#通过npm分发下载" class="headerlink" title="通过npm分发下载"></a>通过npm分发下载</h2><p>终端下载</p>
<figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">npm install animate.css</span><br></pre></td></tr></tbody></table></figure>
<p>页面、组件、布局引用</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script></span><br><span class="line">import 'animate.css'</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><style></span><br><span class="line">@import url("animate.css");</span><br><span class="line"></style></span><br></pre></td></tr></tbody></table></figure>
<p>下载的包也可以在Nuxt配置中<code>CSS</code>属性中作为字符串引用</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineNuxtConfig</span>({</span><br><span class="line"> <span class="attr">css</span>: [<span class="string">'animate.css'</span>]</span><br><span class="line">})</span><br></pre></td></tr></tbody></table></figure>
<h2 id="外部样式表"><a href="#外部样式表" class="headerlink" title="外部样式表"></a>外部样式表</h2><p>可以通过<code>Nuxt.config.ts</code>文件的head添加link元素,实例如下:</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineNuxtConfig</span>({</span><br><span class="line"> <span class="attr">app</span>: {</span><br><span class="line"> <span class="attr">head</span>: {</span><br><span class="line"> <span class="attr">link</span>: [{ <span class="attr">rel</span>: <span class="string">'stylesheet'</span>, <span class="attr">href</span>: <span class="string">'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css'</span> }]</span><br><span class="line"> }</span><br><span class="line">}})</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="动态添加样式表"><a href="#动态添加样式表" class="headerlink" title="动态添加样式表"></a>动态添加样式表</h2><p>使用<code>useHead</code>可组合项进行动态设置head的值</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><span class="title function_">useHead</span>({</span><br><span class="line"> <span class="attr">link</span>: [{ <span class="attr">rel</span>: <span class="string">'stylesheet'</span>, <span class="attr">href</span>: <span class="string">'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css'</span> }]</span><br><span class="line">})</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="使用Nitro插件修改渲染的头部"><a href="#使用Nitro插件修改渲染的头部" class="headerlink" title="使用Nitro插件修改渲染的头部"></a>使用Nitro插件修改渲染的头部</h2><p>这个属于更高级别的控制,可以使用钩子拦截渲染的html,以编程的方式修改头部。<br>创建插件如下:</p>
<figure class="highlight ts"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineNitroPlugin</span>(<span class="function">(<span class="params">nitro</span>) =></span> {</span><br><span class="line"> nitro.<span class="property">hooks</span>.<span class="title function_">hook</span>(<span class="string">'render:html'</span>, <span class="function">(<span class="params">html</span>) =></span> {</span><br><span class="line"> html.<span class="property">head</span>.<span class="title function_">push</span>(<span class="string">'<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">'</span>)</span><br><span class="line"> })</span><br><span class="line">})</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>外部样式表是阻止呈现的资源:必须在浏览器呈现页面之前加载和处理它们。包含不必要的大样式的网页需要更长的时间来呈现。</p>
<h2 id="使用预处理器"><a href="#使用预处理器" class="headerlink" title="使用预处理器"></a>使用预处理器</h2><ul>
<li>首先确保安装<code>sass</code>或者<code>less</code></li>
<li>知悉编写样式表的目录,通过预处理器的语法将原文件导入需要使用的地方<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><style lang="scss"></span><br><span class="line"> @use "~/assets/scss/main.scss";</span><br><span class="line"></style></span><br></pre></td></tr></tbody></table></figure></li>
<li>或者在<code>nuxt.config.ts</code>中配置预处理器<figure class="highlight ts"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineNuxtConfig</span>({</span><br><span class="line"><span class="attr">css</span>: [<span class="string">'~/assets/scss/main.scss'</span>]</span><br><span class="line">})</span><br></pre></td></tr></tbody></table></figure></li>
</ul>
<h2 id="单文件组件动态样式"><a href="#单文件组件动态样式" class="headerlink" title="单文件组件动态样式"></a>单文件组件动态样式</h2><ul>
<li>通过<code>ref</code>或者<code>reactive</code> 变量动态依赖设置</li>
<li>通过<code>computed</code>计算属性动态设置</li>
<li>通过三目运算以数组的形式设置</li>
<li>通过动态字符串</li>
</ul>
<h2 id="动态v-bind"><a href="#动态v-bind" class="headerlink" title="动态v-bind"></a>动态v-bind</h2><p><code>v-bind</code>函数在样式块中可以引用JavaScript变量和表达式。这种绑定是动态的,当依赖值变化时,样式也会更新。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">const color = ref("red")</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><template></span><br><span class="line"> <div class="text">hello</div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><style></span><br><span class="line">.text {</span><br><span class="line"> color: v-bind(color);</span><br><span class="line">}</span><br><span class="line"></style></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="作用域组件"><a href="#作用域组件" class="headerlink" title="作用域组件"></a>作用域组件</h2><p>通过在<code>style</code>标签声明<code>scoped</code>属性,声明后的样式将仅应用于当前组件。</p>
<h2 id="css模块"><a href="#css模块" class="headerlink" title="css模块"></a>css模块</h2><p>可以使用css模块替换为<code>module</code>属性。使用注入的变量访问。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><template></span><br><span class="line"> <p :class="$style.red">This should be red</p></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><style module></span><br><span class="line">.red {</span><br><span class="line"> color: red;</span><br><span class="line">}</span><br><span class="line"></style></span><br></pre></td></tr></tbody></table></figure>
<h2 id="预处理器"><a href="#预处理器" class="headerlink" title="预处理器"></a>预处理器</h2><p>SFC样式快支持预处理器语法,Vite内置了对 .scss、.sass、.less、.styl 和 .stylus 文件的支持,无需配置。通过下载他们,直接在SFC中使用lang属性提供</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><style lang="scss||sass||less||stylus||stylus"></span><br></pre></td></tr></tbody></table></figure>
<h1 id="路由"><a href="#路由" class="headerlink" title="路由"></a>路由</h1><p>Nuxt文件路由为<code>pages/</code>目录下的文件提供路由。<br>Nuxt核心功能是将页面路由映射到文件。每个Vue文件都位于<code>pages/</code>目录中,<strong>文件名</strong>将映射到URL。</p>
<h2 id="页面"><a href="#页面" class="headerlink" title="页面"></a>页面</h2><p>Nuxt路由基于vue-router,从<code>pages</code>目录,基于文件名。<br>文件系统通过使用命名约定来创建动态路由和嵌套路由,如:文件目录为<br>| pages/<br>—| about.vue<br>—| index.vue<br>—| posts.vue<br>—-| [id].vue<br>映射出来路由文件为:</p>
<figure class="highlight json"><table><tbody><tr><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"routes"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"path"</span><span class="punctuation">:</span> <span class="string">"/about"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"component"</span><span class="punctuation">:</span> <span class="string">"pages/about.vue"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"path"</span><span class="punctuation">:</span> <span class="string">"/"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"component"</span><span class="punctuation">:</span> <span class="string">"pages/index.vue"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"path"</span><span class="punctuation">:</span> <span class="string">"/posts/:id"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"component"</span><span class="punctuation">:</span> <span class="string">"pages/posts/[id].vue"</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure>
<h2 id="导航"><a href="#导航" class="headerlink" title="导航"></a>导航</h2><p>**<code><NuxtLink></code>**组件,类似于vue-router中<router-link :to="xxx"><router-link>,通过该组件链接之间页面,他呈现的是一个标签,属性设置为页面路由。<br><code><NuxtLink></code>进入客户端的视口,Nuxt会自动提前加载链接页面组件和页面,从而快速地呈现页面。</router-link></router-link></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><template></span><br><span class="line"> <header></span><br><span class="line"> <nav></span><br><span class="line"> <ul></span><br><span class="line"> <li><NuxtLink to="/about">About</NuxtLink></li></span><br><span class="line"> <li><NuxtLink to="/posts/1">Post 1</NuxtLink></li></span><br><span class="line"> <li><NuxtLink to="/posts/2">Post 2</NuxtLink></li></span><br><span class="line"> </ul></span><br><span class="line"> </nav></span><br><span class="line"> </header></span><br><span class="line"></template></span><br></pre></td></tr></tbody></table></figure>
<h2 id="路由参数"><a href="#路由参数" class="headerlink" title="路由参数"></a>路由参数</h2><p>通过<code>useRoute()</code>api在Vue组件的块或者方法中使用,以便于访问当前路由的详细信息。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">const route = useRoute()</span><br><span class="line"></span><br><span class="line">// When accessing /posts/1, route.params.id will be 1</span><br><span class="line">console.log(route.params.id)</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="路由中间件"><a href="#路由中间件" class="headerlink" title="路由中间件"></a>路由中间件</h2><p>Nuxt提供了可以自定义的路由中间件,<strong>用于导航到特定路由之前提取要运行的代码</strong>。</p>
<p>可以使用<code>middleware</code>属性在<code>nuxt.config.ts</code>中配置,或者在<code>pages/</code>目录下创建中间件文件,文件名必须为<code>middleware.js</code>或者<code>middleware.ts</code>,文件中必须导出一个函数,函数参数为<code>context</code>,返回一个Promise,当函数执行完成时,返回的Promise会作为路由执行结果。</p>
<p>路由中间件有三种:</p>
<ul>
<li>匿名(或内联)路由中间件,直接在使用它们的页面中定义。</li>
<li>命名路由中间件,放置在middleware/目录,当在页面上使用时,将通过异步导入自动加载。(注意:路由中间件名称被规范化为 kebab-case,因此变为 。someMiddlewaresome-middleware</li>
<li>全局路由中间件,放置在middleware/目录(带后缀),并将在每次路线更改时自动运行。.global</li>
</ul>
<h2 id="路由验证"><a href="#路由验证" class="headerlink" title="路由验证"></a>路由验证</h2><p>Nuxt通过<code>validate</code>属性在路由中配置,**通过<code>**definePageMeta**()</code>**。<br>当路由匹配时,验证函数将被调用,如果返回false,路由将不会被激活,路由将被重定向到其他页面。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">definePageMeta({</span><br><span class="line"> validate: async (route) => {</span><br><span class="line"> // Check if the id is made up of digits</span><br><span class="line"> return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id)</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></script></span><br></pre></td></tr></tbody></table></figure>
<h1 id="SEO和Meta"><a href="#SEO和Meta" class="headerlink" title="SEO和Meta"></a>SEO和Meta</h1><h2 id="默认设置"><a href="#默认设置" class="headerlink" title="默认设置"></a>默认设置</h2><p>使用强大的头部配置、可组合项和组件可以改善Nuxt应用的SEO。<br>Nuxt提供了一些默认的SEO配置,可以在<code>nuxt.config.ts</code>中配置。</p>
<figure class="highlight ts"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineNuxtConfig</span>({</span><br><span class="line"> <span class="attr">app</span>: {</span><br><span class="line"> <span class="attr">head</span>: {</span><br><span class="line"> <span class="attr">charset</span>: <span class="string">'utf-8'</span>,</span><br><span class="line"> <span class="attr">viewport</span>: <span class="string">'width=device-width, initial-scale=1'</span>,</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></tbody></table></figure>
<p><code>app.head</code>可以通过<code>nuxt.config.ts</code>配置文件中配置,允许我们为整个应用程序自定义头部。</p>
<h2 id="useHead"><a href="#useHead" class="headerlink" title="useHead"></a>useHead</h2><p><code>useHead()</code>可组合功能可以在组件中使用,我们可以方便的通过编程和响应式方法管理应用头部标签。跟其他可组合项一样,只能与组件和生命周期钩子一起使用。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">useHead({</span><br><span class="line"> title: 'My App',</span><br><span class="line"> meta: [</span><br><span class="line"> { name: 'description', content: 'My amazing site.' }</span><br><span class="line"> ],</span><br><span class="line"> bodyAttrs: {</span><br><span class="line"> class: 'test'</span><br><span class="line"> },</span><br><span class="line"> script: [ { innerHTML: 'console.log(\'Hello world\')' } ]</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="useSeoMeta"><a href="#useSeoMeta" class="headerlink" title="useSeoMeta"></a>useSeoMeta</h2><p><code>useSeoMeta()</code>是<code>useHead()</code>的别名,它允许我们使用与<code>useHead()</code>相同的配置,但是它将自动处理<code>title</code>和<code>meta</code>属性,这使得配置SEO更简单。<br>它的主要作用是可以将站点的SEO元标记标为具备ts支持的平面对象,便于更好的配置。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">useSeoMeta({</span><br><span class="line"> title: 'My Amazing Site',</span><br><span class="line"> ogTitle: 'My Amazing Site',</span><br><span class="line"> description: 'This is my amazing site, let me tell you all about it.',</span><br><span class="line"> ogDescription: 'This is my amazing site, let me tell you all about it.',</span><br><span class="line"> ogImage: 'https://example.com/image.png',</span><br><span class="line"> twitterCard: 'summary_large_image',</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="组件中"><a href="#组件中" class="headerlink" title="组件中"></a>组件中</h2><p>Nuxt提供 <code><Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html> 和<Head></code>,便于直接于组件间模板元数据交互。</p>
<p><strong>注意,这些组件名称在模板中必须将他们大写。</strong>。</p>
并且可以接受嵌套的元标记,但这不会影响哪里嵌套的元标记在最终的 HTML 中呈现。
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">const title = ref('Hello World')</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <Head></span><br><span class="line"> <Title>{{ title }}</Title></span><br><span class="line"> <Meta name="description" :content="title" /></span><br><span class="line"> <Style type="text/css" children="body { background-color: green; }" ></Style></span><br><span class="line"> </Head></span><br><span class="line"></span><br><span class="line"> <h1>{{ title }}</h1></span><br><span class="line"> </div></span><br><span class="line"></template>```</span><br><span class="line"></span><br><span class="line">## 用于`useHead()`,`app.head`和组件传值类型</span><br><span class="line">```ts</span><br><span class="line">interface MetaObject {</span><br><span class="line"> title?: string</span><br><span class="line"> titleTemplate?: string | ((title?: string) => string)</span><br><span class="line"> templateParams?: Record<string, string | Record<string, string>></span><br><span class="line"> base?: Base</span><br><span class="line"> link?: Link[]</span><br><span class="line"> meta?: Meta[]</span><br><span class="line"> style?: Style[]</span><br><span class="line"> script?: Script[]</span><br><span class="line"> noscript?: Noscript[];</span><br><span class="line"> htmlAttrs?: HtmlAttributes;</span><br><span class="line"> bodyAttrs?: BodyAttributes;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="特征"><a href="#特征" class="headerlink" title="特征"></a>特征</h2><h3 id="反应"><a href="#反应" class="headerlink" title="反应"></a>反应</h3><p>可以通过计算值或响应式变量或对象来动态更新元标记,所有的属性都支持反应式。<br>建议使用<code>getter()</code>而不是<code>computed()</code>,因为<code>computed()</code>将创建一个新对象,这将导致不必要的重新渲染。<br>以下是三种更改元标记的三种写法。</p>
<ul>
<li>使用<code>useHead()</code><br><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">const description = ref('My amazing site.')</span><br><span class="line">useHead({meta: [{ name: 'description', content: description }], })</span><br><span class="line"></script> ```</span><br><span class="line">+ 使用SeoMeta</span><br><span class="line"> ```vue</span><br><span class="line"><script setup lang="ts"></span><br><span class="line">const description = ref('My amazing site.')</span><br><span class="line">useSeoMeta({</span><br><span class="line"> description</span><br><span class="line">})</span><br><span class="line"></script> ```</span><br><span class="line">+ 组件</span><br><span class="line"> ```vue</span><br><span class="line"><script setup lang="ts"></span><br><span class="line">const description = ref('My amazing site.')</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <Meta name="description" :content="description" /></span><br><span class="line"> </div></span><br><span class="line"></template>```</span><br><span class="line"></span><br><span class="line">### 标题模板</span><br><span class="line">可以通过`titleTemplate`~属性自定义动态标题模板,该属性可以是字符串或函数。</span><br><span class="line">这样可以将网站的名称添加到每个页面的标题中。</span><br><span class="line"></span><br><span class="line">如果需要使用某个函数完全控制标题,应该在`nuxt.config.ts`中进行设置。</span><br><span class="line">```vue</span><br><span class="line"><script setup lang="ts"></span><br><span class="line">useHead({</span><br><span class="line"> title:'sdf'</span><br><span class="line"> titleTemplate: (titleChunk) => {</span><br><span class="line"> return titleChunk ? `${titleChunk} - Site Title` : 'Site Title';</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure></li>
</ul>
<h3 id="Body标签"><a href="#Body标签" class="headerlink" title="Body标签"></a>Body标签</h3><p>可以使用标签的选项将他们附加到标签的末尾</p>
<figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang=<span class="string">"ts"</span>></span><br><span class="line"><span class="title function_">useHead</span>({</span><br><span class="line"> <span class="attr">script</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">src</span>: <span class="string">'https://third-party-script.com'</span>,</span><br><span class="line"> <span class="comment">// valid options are: 'head' | 'bodyClose' | 'bodyOpen'</span></span><br><span class="line"> <span class="attr">tagPosition</span>: <span class="string">'bodyClose'</span> <span class="comment">// 标签闭合的末尾</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="动态标题"><a href="#动态标题" class="headerlink" title="动态标题"></a>动态标题</h2><p>以下示例,设置带有占位符的字符串,这样就可以灵活的为Nuxt应用程序设置每个路由动态设置页面标题:<code>titleTemplate %s || function</code></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"> <!-- 字符串匹配 --></span><br><span class="line"><script setup lang="ts"></span><br><span class="line">useHead({</span><br><span class="line"> // as a string,</span><br><span class="line"> // where `%s` is replaced with the title</span><br><span class="line"> titleTemplate: '%s - Site Title',</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><!-- 函数匹配 --></span><br><span class="line"><script setup lang="ts"></span><br><span class="line">useHead({</span><br><span class="line"> // or as a function</span><br><span class="line"> titleTemplate: (productCategory) => {</span><br><span class="line"> return productCategory</span><br><span class="line"> ? `${productCategory} - Site Title`</span><br><span class="line"> : 'Site Title'</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>nuxt.config也用于设置页面标题的替代方法,但是不允许页面标题是是动态的。因此,一般建议在文件中使用添加一个动态标题,然后将其应用月Nuxt应用程序的所有路由。</p>
<h2 id="外部CSS"><a href="#外部CSS" class="headerlink" title="外部CSS"></a>外部CSS</h2><p>以下是可以使用<code>useHead</code>和<code>link</code>可组合或使用组件</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><!-- 使用头 --></span><br><span class="line"><script setup lang="ts"></span><br><span class="line">useHead({</span><br><span class="line"> link: [</span><br><span class="line"> {</span><br><span class="line"> rel: 'preconnect',</span><br><span class="line"> href: 'https://fonts.googleapis.com'</span><br><span class="line"> },</span><br><span class="line"> {</span><br><span class="line"> rel: 'stylesheet',</span><br><span class="line"> href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',</span><br><span class="line"> crossorigin: ''</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><!-- 在组件中 --></span><br><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <Link rel="preconnect" href="https://fonts.googleapis.com" /></span><br><span class="line"> <Link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" crossorigin="" /></span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h1 id="过渡"><a href="#过渡" class="headerlink" title="过渡"></a>过渡</h1><h2 id="页面过渡"><a href="#页面过渡" class="headerlink" title="页面过渡"></a>页面过渡</h2><ul>
<li>全部生效<br>可以在Nuxt.config.ts中配置页面过渡,这个设置会将过渡应用于所有页面。如下所示:<figure class="highlight ts"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineNuxtConfig</span>({</span><br><span class="line"> <span class="attr">app</span>: {</span><br><span class="line"> <span class="attr">pageTransition</span>: { <span class="attr">name</span>: <span class="string">'page'</span>, <span class="attr">mode</span>: <span class="string">'out-in'</span> }</span><br><span class="line"> },</span><br><span class="line">})</span><br></pre></td></tr></tbody></table></figure></li>
<li>页面之间<br>只需要在页面之间添加过渡,则在<code>app.vue</code>设置过渡效果</li>
<li>页面设置不同的<br>将需要设置的页面通过<code>definePageMeta</code>键入<code>pageTransition</code>属性,例如:<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">definePageMeta({</span><br><span class="line"> pageTransition: {</span><br><span class="line"> name: 'rotate'</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></script>```</span><br><span class="line">## 布局过渡</span><br><span class="line">通过`defingNuxtConfig`配置启用`layout`布局过渡</span><br><span class="line">```js</span><br><span class="line">export default defineNuxtConfig({</span><br><span class="line"> app: {</span><br><span class="line"> layoutTransition: { name: 'layout', mode: 'out-in' }</span><br><span class="line"> },</span><br><span class="line">})</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
也可以通过<code>definePageMeta</code>属性在页面中设置<code>layoutTransition</code>布局过渡<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">definePageMeta({</span><br><span class="line"> layout: 'orange',</span><br><span class="line"> layoutTransition: {</span><br><span class="line"> name: 'slide-in'</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure></li>
</ul>
<h2 id="全局设置"><a href="#全局设置" class="headerlink" title="全局设置"></a>全局设置</h2><p>可以使用<code>nuxt.confg</code>全局自定义默认这些过渡名称<br>密钥都接受<code>pageTransition</code>、<code>layoutTransition</code>和<code>transitionProps</code>可作为序列化值,其中都可以传递自定义CSS转换的、和其他有效的过渡道具。</p>
<figure class="highlight ts"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineNuxtConfig</span>({</span><br><span class="line"> <span class="attr">app</span>: {</span><br><span class="line"> <span class="attr">pageTransition</span>: {</span><br><span class="line"> <span class="attr">name</span>: <span class="string">'fade'</span>,</span><br><span class="line"> <span class="attr">mode</span>: <span class="string">'out-in'</span> <span class="comment">// default</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">layoutTransition</span>: {</span><br><span class="line"> <span class="attr">name</span>: <span class="string">'slide'</span>,</span><br><span class="line"> <span class="attr">mode</span>: <span class="string">'out-in'</span> <span class="comment">// default</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></tbody></table></figure>
<p>覆盖全局过渡属性,为单个nuxt页面设置不同的过渡属性,可以使用<code>definePageMeta</code></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">definePageMeta({</span><br><span class="line"> pageTransition: {</span><br><span class="line"> name: 'bounce',</span><br><span class="line"> mode: 'out-in' // default</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="禁用过渡"><a href="#禁用过渡" class="headerlink" title="禁用过渡"></a>禁用过渡</h2><p><code>pageTransition</code>和<code>layoutTransition</code>属性可以设置为<code>false</code>,以禁用页面和布局过渡。<br>单个页面中</p>
<figure class="highlight ts"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang=<span class="string">"ts"</span>></span><br><span class="line"><span class="title function_">definePageMeta</span>({</span><br><span class="line"> <span class="attr">pageTransition</span>: <span class="literal">false</span>,</span><br><span class="line"> <span class="attr">layoutTransition</span>: <span class="literal">false</span></span><br><span class="line">})</span><br><span class="line"></script></span><br></pre></td></tr></tbody></table></figure>
<p>全局设置</p>
<figure class="highlight ts"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineNuxtConfig</span>({</span><br><span class="line"> <span class="attr">app</span>: {</span><br><span class="line"> <span class="attr">pageTransition</span>: <span class="literal">false</span>,</span><br><span class="line"> <span class="attr">layoutTransition</span>: <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="通过JavaScript钩子精准控制过渡"><a href="#通过JavaScript钩子精准控制过渡" class="headerlink" title="通过JavaScript钩子精准控制过渡"></a>通过JavaScript钩子精准控制过渡</h2><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">definePageMeta({</span><br><span class="line"> pageTransition: {</span><br><span class="line"> name: 'custom-flip',</span><br><span class="line"> mode: 'out-in',</span><br><span class="line"> onBeforeEnter: (el) => {</span><br><span class="line"> console.log('Before enter...')</span><br><span class="line"> },</span><br><span class="line"> onEnter: (el, done) => {},</span><br><span class="line"> onAfterEnter: (el) => {}</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="动态过渡"><a href="#动态过渡" class="headerlink" title="动态过渡"></a>动态过渡</h2><p>如果需要通过条件控制过渡,可以使用内联中间件分配不同的过渡名。<code>to.meta.pageTransition</code></p>
<figure class="highlight ts"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang=<span class="string">"ts"</span>></span><br><span class="line"><span class="title function_">definePageMeta</span>({</span><br><span class="line"> <span class="attr">pageTransition</span>: {</span><br><span class="line"> <span class="attr">name</span>: <span class="string">'slide-right'</span>,</span><br><span class="line"> <span class="attr">mode</span>: <span class="string">'out-in'</span></span><br><span class="line"> },</span><br><span class="line"><span class="comment">// 中间件</span></span><br><span class="line"> middleware (to, <span class="keyword">from</span>) {</span><br><span class="line"> <span class="keyword">if</span> (to.<span class="property">meta</span>.<span class="property">pageTransition</span> && <span class="keyword">typeof</span> to.<span class="property">meta</span>.<span class="property">pageTransition</span> !== <span class="string">'boolean'</span>)</span><br><span class="line"> to.<span class="property">meta</span>.<span class="property">pageTransition</span>.<span class="property">name</span> = +to.<span class="property">params</span>.<span class="property">id</span> > +<span class="keyword">from</span>.<span class="property">params</span>.<span class="property">id</span> ? <span class="string">'slide-left'</span> : <span class="string">'slide-right'</span></span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag"><<span class="name">template</span>></span></span></span><br><span class="line"><span class="language-xml"> <span class="tag"><<span class="name">h1</span>></span>#{{ $route.params.id }}<span class="tag"></<span class="name">h1</span>></span></span></span><br><span class="line"><span class="language-xml"><span class="tag"></<span class="name">template</span>></span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag"><<span class="name">style</span>></span><span class="language-css"></span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.slide-left-enter-active</span>,</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.slide-left-leave-active</span>,</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.slide-right-enter-active</span>,</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.slide-right-leave-active</span> {</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">transition</span>: all <span class="number">0.2s</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">}</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.slide-left-enter-from</span> {</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">opacity</span>: <span class="number">0</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">transform</span>: <span class="built_in">translate</span>(<span class="number">50px</span>, <span class="number">0</span>);</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">}</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.slide-left-leave-to</span> {</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">opacity</span>: <span class="number">0</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">transform</span>: <span class="built_in">translate</span>(-<span class="number">50px</span>, <span class="number">0</span>);</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">}</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.slide-right-enter-from</span> {</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">opacity</span>: <span class="number">0</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">transform</span>: <span class="built_in">translate</span>(-<span class="number">50px</span>, <span class="number">0</span>);</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">}</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"><span class="selector-class">.slide-right-leave-to</span> {</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">opacity</span>: <span class="number">0</span>;</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"> <span class="attribute">transform</span>: <span class="built_in">translate</span>(<span class="number">50px</span>, <span class="number">0</span>);</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml">}</span></span></span><br><span class="line"><span class="language-css"><span class="language-xml"></span><span class="tag"></<span class="name">style</span>></span></span></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="使用NuxtPage过渡"><a href="#使用NuxtPage过渡" class="headerlink" title="使用NuxtPage过渡"></a>使用NuxtPage过渡</h2><p>在<code>app.vue</code>中使用时,<code>transitionProps</code>可以作为组件props传递以激活全局转换</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <NuxtLayout></span><br><span class="line"> <NuxtPage :transition="{</span><br><span class="line"> name: 'bounce',</span><br><span class="line"> mode: 'out-in'</span><br><span class="line"> }" /></span><br><span class="line"> </NuxtLayout></span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h1 id="数据获取"><a href="#数据获取" class="headerlink" title="数据获取"></a>数据获取</h1><p>Nuxt提供了可组合项来处理应用程序中的数据获取。<br>Nuxt提供俩个组合项:</p>
<ul>
<li><code>useFetch</code>:组件设置函数中处理数据获取的最直接方法。</li>
<li><code>useAsyncData</code>:结合使用,提供了更细颗粒度的控制<br>Nuxt提供内置库:</li>
<li><code>$fetch</code>:非常适合根据用户交互发出网络请求。</li>
</ul>
<h2 id="useFetch"><a href="#useFetch" class="headerlink" title="useFetch"></a>useFetch</h2><p><code>useFetch</code>该组合项是执行数据提取的最直接方法。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">const { data: count } = await useFetch('/api/count')</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><template></span><br><span class="line"> <p>Page visits: {{ count }}</p></span><br><span class="line"></template></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="fetch"><a href="#fetch" class="headerlink" title="$fetch"></a>$fetch</h2><p>Nuxt内置了<code>$fetch</code>包含该库,用于页面数据异步加载时候获取数据。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">async function addTodo() {</span><br><span class="line"> const todo = await $fetch('/api/todos', {</span><br><span class="line"> method: 'POST',</span><br><span class="line"> body: {</span><br><span class="line"> // My todo data</span><br><span class="line"> }</span><br><span class="line"> })</span><br><span class="line">}</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="useAsyncData"><a href="#useAsyncData" class="headerlink" title="useAsyncData"></a>useAsyncData</h2><p><code>useAsyncData</code>是<code>useFetch</code>的更细颗粒度的控制。负责包装异步逻辑,并解析后返回结果。</p>
<p>当CMS或第三方提供自己的API时,可以使用<code>useAsyncData</code>。例如:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">const { data, error } = await useAsyncData('users', () => myGetFunction('users'))</span><br><span class="line"></span><br><span class="line">// This is also possible:</span><br><span class="line">const { data, error } = await useAsyncData(() => myGetFunction('users'))</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>第一个参数<code>useAsyncData</code>是<code>key</code>,用于缓存数据。这个key可以通过直接传递查询函数来忽略,key会自动生成。因为自动生成键只考虑调用的文件和行,为了避免产生不必要的行为因此,可以传递一个自定义的key。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">const { id } = useRoute().params</span><br><span class="line"></span><br><span class="line">const { data, error } = await useAsyncData(`user:${id}`, () => {</span><br><span class="line"> return myGetFunction('users', { id })</span><br><span class="line">})</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>可组合项是个可以等待多个完成,然后检索每个结果的好方法。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><script setup lang="ts"></span><br><span class="line">const { data: discounts, pending } = await useAsyncData('cart-discount', async () => {</span><br><span class="line"> const [coupons, offers] = await Promise.all([</span><br><span class="line"> $fetch('/cart/coupons'),</span><br><span class="line"> $fetch('/cart/offers')</span><br><span class="line"> ])</span><br><span class="line"></span><br><span class="line"> return { coupons, offers }</span><br><span class="line">})</span><br><span class="line">// discounts.value.coupons</span><br><span class="line">// discounts.value.offers</span><br><span class="line"></script></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h2 id="返回值"><a href="#返回值" class="headerlink" title="返回值"></a>返回值</h2><p><code>useFetch</code>具有下面列出相同的返回值。</p>
<ul>
<li><code>data</code>:传入的异步函数结果</li>
<li><code>peding</code>:布尔值,指示这个异步函数是否执行完毕</li>
<li><code>refresh/execute</code>:可用于刷新函数返回的数据的函数</li>
<li><code>error</code>:数据获取失败返回的错误信息</li>
<li><code>status</code>:返回数据请求状态的字符串</li>
</ul>
]]></content>
<categories>
<category>SSR</category>
</categories>
<tags>
<tag>SSR</tag>
</tags>
</entry>
<entry>
<title>React-状态管理</title>
<url>/2023/11/20/%20React-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86/</url>
<content><![CDATA[<h1 id="react之状态管理"><a href="#react之状态管理" class="headerlink" title="react之状态管理"></a>react之状态管理</h1><p>随着应用的不断加大,我们应该更有意识的的去关注我们的应用状态如何去组织,以及数据如何在组件中流动。我们应该去避免冗余或重复的状态,这样可以避免一些缺陷的产生。这时候,如何组织好状态,如何保持状态更新逻辑的可维护性,以及如何跨组件共享状态就变得尤为重要。</p>
<h2 id="使用状态响应输入"><a href="#使用状态响应输入" class="headerlink" title="使用状态响应输入"></a>使用状态响应输入</h2><p>在使用React时,我们不需要直接代码层面去修改UI。我们可以直接通过组件的不同状态去展现的UI,然后根据用户输入触发状态修改。</p>
<h3 id="声明式UI与命令式UI的比较"><a href="#声明式UI与命令式UI的比较" class="headerlink" title="声明式UI与命令式UI的比较"></a>声明式UI与命令式UI的比较</h3><p>当设计UI交互时,会思考UI如何根据用户的操作而<strong>响应变化</strong>。想象一个允许用户提交一个答案的表单:</p>
<ul>
<li>当表单输入的时候,“提交”按钮会变成<strong>可用状态</strong></li>
<li>点击“提交”后,表单和提交按钮都会随之变成<strong>不可用状态</strong></li>
<li>如果网络请求成功,表单会随之<strong>隐藏</strong>,同时会出现“提交成功”提示</li>
<li>如果网络请求失败,会出现错误提示信息,表单又变为可用状态</li>
</ul>
<p><strong>命令式编程</strong>中,我们需要根据具体的场景去设计如何实现交互。我们必须根据可能会发生的事情去写一些明确的命令去操作UI。也就是我们需要“命令”每个元素(操作dom),告诉计算机应该<strong>如何</strong>去更新UI的编程方式被称为<strong>命令式编程</strong>。</p>
<p>对于独立系统来说,命令式控制用户界面的效果也不错,但是如果要实现更为复杂的系统时,代码的组织就会指数级难度增长。</p>
<p>而React就是为了解决这样的问题而诞生。</p>
<p>在React中,不比直接去操作UI(不需要去直接操作dom)。相反,我们<strong>只需要声明我们想要显示的内容。</strong>React就会通过计算如何去更新UI。</p>
<h3 id="声明式考虑UI"><a href="#声明式考虑UI" class="headerlink" title="声明式考虑UI"></a>声明式考虑UI</h3><p>通过上面的思考方式,我们来看看React时如何去实现这个UI。</p>
<ol>
<li><strong>定位</strong>组件中不同的视图状态</li>
<li><strong>确定</strong>是什么触发这些<code>state</code>的改变</li>
<li><strong>表示</strong>内存中的<code>state</code>(需要使用<code>useState</code>)</li>
<li><strong>删除</strong>任何不必要的<code>state</code>变量</li>
<li><strong>连接</strong>事件处理函数去设置<code>state</code></li>
</ol>
<h4 id="步骤1-定位组件中不同的视图状态"><a href="#步骤1-定位组件中不同的视图状态" class="headerlink" title="步骤1:定位组件中不同的视图状态"></a>步骤1:定位组件中不同的视图状态</h4><p>在React中,不同的可视化UI界面中用户所有看到的都是不同的“状态”。</p>
<ul>
<li><strong>无数据</strong>:表单有一个不可用状态的“提交”按钮。</li>
<li><strong>输入中</strong>:表单有一个可用状态的“提交”按钮。</li>
<li><strong>提交中</strong>:表单完全处于不可用状态,加载动画出现。</li>
<li><strong>成功时</strong>:显示“成功”的消息而非表单。</li>
<li><strong>错误时</strong>:与输入状态类似,但会多错误的消息。</li>
</ul>
<h4 id="步骤2-确定是什么触发了这些状态的改变"><a href="#步骤2-确定是什么触发了这些状态的改变" class="headerlink" title="步骤2:确定是什么触发了这些状态的改变"></a>步骤2:确定是什么触发了这些状态的改变</h4><p>触发<code>state</code>的更新来响应俩种输入:</p>
<ul>
<li><strong>人为</strong>输入。比如点击按钮、在表单中输入内容,或导航到链接。</li>
<li><strong>计算机</strong>输入。比如网络请求得到反馈、定时器被触发,或加载一张图片。</li>
</ul>
<p>以上两种情况中,<strong>你必须设置 <a href="https://react.docschina.org/learn/state-a-components-memory#anatomy-of-usestate">state 变量</a> 去更新 UI</strong>。对于正在开发中的表单来说,你需要改变 state 以响应几个不同的输入:</p>
<ul>
<li><strong>改变输入框中的文本时</strong>(人为)应该根据输入框的内容是否是<strong>空值</strong>,从而决定将表单的状态从空值状态切换到<strong>输入中</strong>或切换回原状态。</li>
<li><strong>点击提交按钮时</strong>(人为)应该将表单的状态切换到<strong>提交中</strong>的状态。</li>
<li><strong>网络请求成功后</strong>(计算机)应该将表单的状态切换到<strong>成功</strong>的状态。</li>
<li><strong>网络请求失败后</strong>(计算机)应该将表单的状态切换到<strong>失败</strong>的状态,与此同时,显示错误信息。</li>
</ul>
<h4 id="步骤3:通过useState表示内存中的state"><a href="#步骤3:通过useState表示内存中的state" class="headerlink" title="步骤3:通过useState表示内存中的state"></a>步骤3:通过<code>useState</code>表示内存中的<code>state</code></h4><p>接下来,我们会需要在内存中通过 <a href="https://react.docschina.org/reference/react/useState"><code>useState</code></a> 表示组件中的视图状态。<code>state</code>的每个部分都是“处在变化中的”,并且需要<strong>让“变化大的部分”尽可能的少</strong>。</p>
<ol>
<li>先从<strong>绝对必须存在</strong>的状态开始。</li>
<li>接下来,创建一个状态变量去代表想要显示的可时状态</li>
<li>在很难想出最好的办法时,就从添加足够多的<code>state</code>开始,<strong>确保</strong>所有可能的视图状态都包含。</li>
</ol>
<p>最初的想法或许不是最好的,必要时,**重构<code>state</code>**也是步骤中的一部分。</p>
<h4 id="步骤4-删除任何不必要的state变量"><a href="#步骤4-删除任何不必要的state变量" class="headerlink" title="步骤4:删除任何不必要的state变量"></a>步骤4:删除任何不必要的state变量</h4><p>我们想要避免<code>state</code>内容中的重复,从而只需要关注必要的部分。这就需要我们花费时间去重构我们的<code>state</code>结构,这样会让我们的组件更容易被理解,从而减少重复避免歧义。<strong>主要的目的是防止出现在内存中的state不代表任何我们让用户看到的有效UI的情况。</strong></p>
<p>在创建<code>state</code>变量的时候,我们应该反问自己以下这些问题:</p>
<ul>
<li><strong>这个<code>state</code>是否会导致矛盾?</strong>例如,<code>isTyping</code> 与 <code>isSubmitting</code> 的状态不能同时为 <code>true</code>。矛盾的产生通常说明了这个 state 没有足够的约束条件。两个布尔值有四种可能的组合,但是只有三种对应有效的状态。为了将“不可能”的状态移除,可以将 <code>'typing'</code>、<code>'submitting'</code> 以及 <code>'success'</code> 这三个中的其中一个与 <code>status</code> 结合。</li>
<li><strong>相同的信息是否已经在另一个 state 变量中存在</strong>?另一个矛盾:<code>isEmpty</code> 和 <code>isTyping</code> 不能同时为 <code>true</code>。通过使它们成为独立的 state 变量,可能会导致它们不同步并导致 bug。幸运的是,可以移除 <code>isEmpty</code> 转而用 <code>message.length === 0</code>。</li>
<li><strong>是否可以通过另一个 state 变量的相反值得到相同的信息</strong>?<code>isError</code> 是多余的,因为你可以检查 <code>error !== null</code>。</li>
</ul>
<p>正是因为在不破坏功能的情况下删除其中任何一个状态变量,才可以确定这些都是必要的。</p>
<h4 id="步骤5-连接事件处理函数以设置state"><a href="#步骤5-连接事件处理函数以设置state" class="headerlink" title="步骤5:连接事件处理函数以设置state"></a>步骤5:连接事件处理函数以设置state</h4><p>最后,创建事件处理函数去设置<code>state</code>变量。</p>
<h2 id="选择状态结构"><a href="#选择状态结构" class="headerlink" title="选择状态结构"></a>选择状态结构</h2><p>良好的状态组织,可以区分易于修改和调试的组件和频繁出问题的组件。<strong>最重要的原则是,状态不应该包含冗余或重复的信息。</strong></p>
<h3 id="构建state的原则"><a href="#构建state的原则" class="headerlink" title="构建state的原则"></a>构建state的原则</h3><p>当编写一个存有<code>state</code>的组件时,需要我们去选择使用多少个<code>state</code>变量以及他们都是什么数据格式。以下是构建<code>state</code>的原则来指导哦们做出更好的决策:</p>
<ol>
<li>**合并关联的<code>state</code>**。如果存在同时更新俩个或者多个的<code>state</code>变量,就该考虑将他们合并为一个单独的<code>state</code>变量。</li>
<li>**避免互相矛盾的<code>state</code>**。当<code>state</code>结构存在多个互相矛盾或者“不一致”的<code>state</code>时,就应该避免这个情况。</li>
<li>**避免冗余的<code>state</code>**。如果在渲染期间从组件的<code>props</code>或现有的<code>state</code>变量中计算一些信息,那么这些信息不因该放在该组件的<code>state</code>中。</li>
<li>**避免重复的<code>state</code>**。当统一数据在多个<code>state</code>变量之间或在多个嵌套对象总重复时,会很难保持同步。因此应尽量减少重复。</li>
<li>**避免深层嵌套的<code>state</code>**。深度分层的<code>state</code>更新起来会不方便,最好时构建扁平化的<code>state</code>。</li>
</ol>
<p>这些原则背后的目标是<strong>使<code>state</code>易于更新而不引入错误</strong>。从<code>state</code>中删除冗余和重复数据有助于所有部分保持同步。这类似于数据库工程师想要 <a href="https://docs.microsoft.com/zh-CN/office/troubleshoot/access/database-normalization-description">“规范化”数据库结构</a>,以减少出现错误的机会。用爱因斯坦的话说,<strong>“让你的状态尽可能简单,但不要过于简单。”</strong></p>
<h3 id="合并关联的state"><a href="#合并关联的state" class="headerlink" title="合并关联的state"></a>合并关联的state</h3><p>有时候我们可能不确定使用单个<code>state</code>变量还是多个<code>state</code>变量。</p>
<p>例如下面的存在x、y</p>
<p>是这样做?</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const [x, setX] = useState(0);</span><br><span class="line">const [y, setY] = useState(0);</span><br></pre></td></tr></tbody></table></figure>
<p>还是这样做?</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const [position, setPosition] = useState({ x: 0, y: 0 });</span><br></pre></td></tr></tbody></table></figure>
<p>从技术实现上来说,可以使用任何一种方法。但是,<strong>如果俩个<code>state</code>变量总是一起变化,则将它们统一成state变量可能会更好</strong>。这样我们就不会忘记让它们始终保持同步。</p>
<p>另一种情况是,我们将数据整合到一个对象或一个数组中时,不知道需要多少个<code>state</code>片段。例如,用户可以自定义字段的表单,这样就很有帮助。</p>
<p><strong>注意</strong>:如果 state 变量是一个对象时,请记住,<a href="https://react.docschina.org/learn/updating-objects-in-state">不能只更新其中的一个字段</a> 而不显式复制其他字段。例如,在上面的例子中,不能写成 <code>setPosition({ x: 100 })</code>,因为它根本就没有 <code>y</code> 属性! 相反,如果你想要仅设置 <code>x</code>,则可执行 <code>setPosition({ ...position, x: 100 })</code>,或将它们分成两个 state 变量,并执行 <code>setX(100)</code>。</p>
<h3 id="避免矛盾的state"><a href="#避免矛盾的state" class="headerlink" title="避免矛盾的state"></a>避免矛盾的state</h3><p>看以下例子代码:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function FeedbackForm() {</span><br><span class="line"> const [text, setText] = useState('');</span><br><span class="line"> // 俩个状态去控制 很容易去忘记更新</span><br><span class="line"> const [isSending, setIsSending] = useState(false);</span><br><span class="line"> const [isSent, setIsSent] = useState(false);</span><br><span class="line"></span><br><span class="line"> async function handleSubmit(e) {</span><br><span class="line"> e.preventDefault();</span><br><span class="line"> setIsSending(true);</span><br><span class="line"> await sendMessage(text);</span><br><span class="line"> setIsSending(false);</span><br><span class="line"> setIsSent(true);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (isSent) {</span><br><span class="line"> return <h1>Thanks for feedback!</h1></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <form onSubmit={handleSubmit}></span><br><span class="line"> <p>How was your stay at The Prancing Pony?</p></span><br><span class="line"> <textarea</span><br><span class="line"> disabled={isSending}</span><br><span class="line"> value={text}</span><br><span class="line"> onChange={e => setText(e.target.value)}</span><br><span class="line"> /></span><br><span class="line"> <br /></span><br><span class="line"> <button</span><br><span class="line"> disabled={isSending}</span><br><span class="line"> type="submit"</span><br><span class="line"> ></span><br><span class="line"> Send</span><br><span class="line"> </button></span><br><span class="line"> {isSending && <p>Sending...</p>}</span><br><span class="line"> </form></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 sendMessage(text) {</span><br><span class="line"> return new Promise(resolve => {</span><br><span class="line"> setTimeout(resolve, 2000);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>观察这段代码,其实是有效的,但是会存在一些<code>state</code>“极难处理”。如果忘记 <code>setIsSent</code> 和 <code>setIsSending</code>,则可能会出现 <code>isSending</code> 和 <code>isSent</code> 同时为 <code>true</code> 的情况。组件越复杂,就很难理解发生了什么。</p>
<p><strong>因为 <code>isSending</code> 和 <code>isSent</code> 不应同时为 <code>true</code>,所以最好用一个 <code>status</code> 变量来代替它们,这个 state 变量可以采取三种有效状态其中之一</strong>:<code>'typing'</code> (初始), <code>'sending'</code>, 和 <code>'sent'</code>:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function FeedbackForm() {</span><br><span class="line"> const [text, setText] = useState('');</span><br><span class="line"> // 将state合并为三个状态 动态修改去做条件判断</span><br><span class="line"> const [status, setStatus] = useState('typing');</span><br><span class="line"></span><br><span class="line"> async function handleSubmit(e) {</span><br><span class="line"> e.preventDefault();</span><br><span class="line"> setStatus('sending');</span><br><span class="line"> await sendMessage(text);</span><br><span class="line"> setStatus('sent');</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 它们不是 state 变量,所以不必担心它们彼此失去同步。</span><br><span class="line"> const isSending = status === 'sending';</span><br><span class="line"> const isSent = status === 'sent';</span><br><span class="line"></span><br><span class="line"> if (isSent) {</span><br><span class="line"> return <h1>Thanks for feedback!</h1></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <form onSubmit={handleSubmit}></span><br><span class="line"> <p>How was your stay at The Prancing Pony?</p></span><br><span class="line"> <textarea</span><br><span class="line"> disabled={isSending}</span><br><span class="line"> value={text}</span><br><span class="line"> onChange={e => setText(e.target.value)}</span><br><span class="line"> /></span><br><span class="line"> <br /></span><br><span class="line"> <button</span><br><span class="line"> disabled={isSending}</span><br><span class="line"> type="submit"</span><br><span class="line"> ></span><br><span class="line"> Send</span><br><span class="line"> </button></span><br><span class="line"> {isSending && <p>Sending...</p>}</span><br><span class="line"> </form></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 sendMessage(text) {</span><br><span class="line"> return new Promise(resolve => {</span><br><span class="line"> setTimeout(resolve, 2000);</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h3 id="避免冗余的state"><a href="#避免冗余的state" class="headerlink" title="避免冗余的state"></a>避免冗余的state</h3><p>如果我们在渲染期间从组件的<code>props</code>或现有的<code>state</code>变量中计算出一些信息,则不应该把这些信息放到该组件的<code>state</code>中。</p>
<p>仔细看看以下代码。他可以允许,仔细观察就能发现其中的冗余之处。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function Form() {</span><br><span class="line"> const [firstName, setFirstName] = useState('');</span><br><span class="line"> const [lastName, setLastName] = useState('');</span><br><span class="line"> // 没错就是这里 这里fullName其实可以通过firstName 和 lastName计算得出</span><br><span class="line"> // const [fullName, setFullName] = useState('');</span><br><span class="line"> // 就可以替换成以下这段代码</span><br><span class="line"> // 这样就不需要每次在依赖的state中去重新修改状态</span><br><span class="line"> const fullName = firstName + ''+lastName</span><br><span class="line"> </span><br><span class="line"> function handleFirstNameChange(e) {</span><br><span class="line"> setFirstName(e.target.value);</span><br><span class="line"> // setFullName(e.target.value + ' ' + lastName);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> function handleLastNameChange(e) {</span><br><span class="line"> setLastName(e.target.value);</span><br><span class="line"> //setFullName(firstName + ' ' + e.target.value);</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"> <h2>Let’s check you in</h2></span><br><span class="line"> <label></span><br><span class="line"> First name:{' '}</span><br><span class="line"> <input</span><br><span class="line"> value={firstName}</span><br><span class="line"> onChange={handleFirstNameChange}</span><br><span class="line"> /></span><br><span class="line"> </label></span><br><span class="line"> <label></span><br><span class="line"> Last name:{' '}</span><br><span class="line"> <input</span><br><span class="line"> value={lastName}</span><br><span class="line"> onChange={handleLastNameChange}</span><br><span class="line"> /></span><br><span class="line"> </label></span><br><span class="line"> <p></span><br><span class="line"> Your ticket will be issued to: <b>{fullName}</b></span><br><span class="line"> </p></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></tbody></table></figure>
<p>为什么能用常量去做呢?这是因为事件处理程序不需要任何操作去更新它,当我们调用<code>setFirstName</code> 或 <code>setLastName</code> 时,你会触发一次重新渲染,然后下一个 <code>fullName</code> 将从新数据中计算出来。</p>
<h5 id="深入探讨-不要在-state-中镜像-props"><a href="#深入探讨-不要在-state-中镜像-props" class="headerlink" title="深入探讨:不要在 state 中镜像 props"></a>深入探讨:不要在 state 中镜像 props</h5><p>看看以下代码:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">function Message({ messageColor }) {</span><br><span class="line"> const [color, setColor] = useState(messageColor);</span><br></pre></td></tr></tbody></table></figure>
<p>这里,一个 <code>color</code> state 变量被初始化为 <code>messageColor</code> 的 prop 值。这段代码的问题在于,<strong>如果父组件稍后传递不同的 <code>messageColor</code> 值(例如,将其从 <code>'blue'</code> 更改为 <code>'red'</code>),则 <code>color</code></strong> state 变量<strong>将不会更新!</strong> state 仅在第一次渲染期间初始化。</p>
<p>这就是为什么在 state 变量中,“镜像”一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 <code>messageColor</code> 属性。如果想给它起一个更短的名称,请使用常量:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">function Message({ messageColor }) {</span><br><span class="line"></span><br><span class="line"> const color = messageColor;</span><br></pre></td></tr></tbody></table></figure>
<p>这种写法就不会与从父组件传递的属性失去同步。</p>
<p>只有当你 <strong>想要</strong> 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 <code>initial</code> 或 <code>default</code> 开头,以阐明该 prop 的新值将被忽略:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">function Message({ initialColor }) {</span><br><span class="line"> // 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。</span><br><span class="line"> // 对于 `initialColor` 属性的进一步更改将被忽略。</span><br><span class="line"> const [color, setColor] = useState(initialColor);</span><br></pre></td></tr></tbody></table></figure>
<h3 id="避免重复的state"><a href="#避免重复的state" class="headerlink" title="避免重复的state"></a>避免重复的state</h3><p>下面这是一个可以选择的菜单列表:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">const initialItems = [</span><br><span class="line"> { title: 'pretzels', id: 0 },</span><br><span class="line"> { title: 'crispy seaweed', id: 1 },</span><br><span class="line"> { title: 'granola bar', id: 2 },</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line">export default function Menu() {</span><br><span class="line"> const [items, setItems] = useState(initialItems);</span><br><span class="line"> const [selectedItem, setSelectedItem] = useState(</span><br><span class="line"> items[0]</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"> <h2>What's your travel snack?</h2></span><br><span class="line"> <ul></span><br><span class="line"> {items.map(item => (</span><br><span class="line"> <li key={item.id}></span><br><span class="line"> {item.title}</span><br><span class="line"> {' '}</span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> setSelectedItem(item);</span><br><span class="line"> }}>Choose</button></span><br><span class="line"> </li></span><br><span class="line"> ))}</span><br><span class="line"> </ul></span><br><span class="line"> <p>You picked {selectedItem.title}.</p></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></tbody></table></figure>
<p> 这里的所选元素作为对象存储在<code>selectedItem</code> state变量中。然而,**<code>selecteItem</code>的内容与<code>items</code>列表中的某一项是同一个对象**,这意味着关于该项本身的信息在俩个地方产生了重复。</p>
<p>我们在将我们的组件进行修改,让每个项目都可以编辑,看看会出现什么问题?</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">const initialItems = [</span><br><span class="line"> { title: 'pretzels', id: 0 },</span><br><span class="line"> { title: 'crispy seaweed', id: 1 },</span><br><span class="line"> { title: 'granola bar', id: 2 },</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line">export default function Menu() {</span><br><span class="line"> const [items, setItems] = useState(initialItems);</span><br><span class="line"> const [selectedItem, setSelectedItem] = useState(</span><br><span class="line"> items[0]</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> function handleItemChange(id, e) {</span><br><span class="line"> setItems(items.map(item => {</span><br><span class="line"> if (item.id === id) {</span><br><span class="line"> return {</span><br><span class="line"> ...item,</span><br><span class="line"> title: e.target.value,</span><br><span class="line"> };</span><br><span class="line"> } else {</span><br><span class="line"> return item;</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 (</span><br><span class="line"> <></span><br><span class="line"> <h2>What's your travel snack?</h2> </span><br><span class="line"> <ul></span><br><span class="line"> {items.map((item, index) => (</span><br><span class="line"> <li key={item.id}></span><br><span class="line"> <input</span><br><span class="line"> value={item.title}</span><br><span class="line"> onChange={e => {</span><br><span class="line"> handleItemChange(item.id, e)</span><br><span class="line"> }}</span><br><span class="line"> /></span><br><span class="line"> {' '}</span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> setSelectedItem(item);</span><br><span class="line"> }}>Choose</button></span><br><span class="line"> </li></span><br><span class="line"> ))}</span><br><span class="line"> </ul></span><br><span class="line"> <p>You picked {selectedItem.title}.</p></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></tbody></table></figure>
<p>注意,当我们选择“choose”按钮时,<strong>然后编辑</strong>它,<strong>输入会更新</strong>,<strong>但是底部的标签不会反应编辑的内容</strong>。这是因为我们存在了重复的<code>state</code>,并且忘记去更新了<code>selectedItem</code>。</p>
<p>我们也可以选择去更新<code>selectedItem</code>,但更简单的办法是去消除重复项。在下面对代码中,我们就可以将<code>selectedId</code>保存在state中,而不是在<code>selectedItem</code>对象中(它创建了一个与<code>items</code>内重复的对象),然后通过<code>items</code>数组去过滤出具有该ID的项,以此来获取<code>selectedItem</code>:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">const initialItems = [</span><br><span class="line"> { title: 'pretzels', id: 0 },</span><br><span class="line"> { title: 'crispy seaweed', id: 1 },</span><br><span class="line"> { title: 'granola bar', id: 2 },</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line">export default function Menu() {</span><br><span class="line"> const [items, setItems] = useState(initialItems);</span><br><span class="line"> const [selectedId, setSelectedId] = useState(0);</span><br><span class="line"></span><br><span class="line"> const selectedItem = items.find(item =></span><br><span class="line"> item.id === selectedId</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> function handleItemChange(id, e) {</span><br><span class="line"> setItems(items.map(item => {</span><br><span class="line"> if (item.id === id) {</span><br><span class="line"> return {</span><br><span class="line"> ...item,</span><br><span class="line"> title: e.target.value,</span><br><span class="line"> };</span><br><span class="line"> } else {</span><br><span class="line"> return item;</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 (</span><br><span class="line"> <></span><br><span class="line"> <h2>What's your travel snack?</h2></span><br><span class="line"> <ul></span><br><span class="line"> {items.map((item, index) => (</span><br><span class="line"> <li key={item.id}></span><br><span class="line"> <input</span><br><span class="line"> value={item.title}</span><br><span class="line"> onChange={e => {</span><br><span class="line"> handleItemChange(item.id, e)</span><br><span class="line"> }}</span><br><span class="line"> /></span><br><span class="line"> {' '}</span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> setSelectedId(item.id);</span><br><span class="line"> }}>Choose</button></span><br><span class="line"> </li></span><br><span class="line"> ))}</span><br><span class="line"> </ul></span><br><span class="line"> <p>You picked {selectedItem.title}.</p></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></tbody></table></figure>
<p>在或者,我们可以将所选择的索引保存在<code>state</code>中。</p>
<p>state 过去常常是这样赋值的:</p>
<ul>
<li><code>items = [{ id: 0, title: 'pretzels'}, ...]</code></li>
<li><code>selectedItem = {id: 0, title: 'pretzels'}</code></li>
</ul>
<p>改了之后是这样的:</p>
<ul>
<li><code>items = [{ id: 0, title: 'pretzels'}, ...]</code></li>
<li><code>selectedId = 0</code></li>
</ul>
<p>这样,就不存在重复的<code>state</code>,只保留了必要的<code>state</code>。这样的话,当我们编辑<code>selected</code>元素,下面的标签就会立即更新。这是因为<code>setItem</code>会触发重新渲染,而<code>item.find(...)</code>会找到带有更新文本的元素,我们不需要在state中保存<strong>选定的元素</strong>,因为<strong>只有选定的ID</strong>是必要的。其余的都可以在渲染期间计算得出。</p>
<h3 id="避免深度的嵌套state"><a href="#避免深度的嵌套state" class="headerlink" title="避免深度的嵌套state"></a>避免深度的嵌套state</h3><p>想象一下,一个由行星、大陆和国家组成的旅行计划。你可能会尝试使用嵌套对象和数组来构建它的 state,就像下面这个例子:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// app.js</span><br><span class="line">import { useState } from 'react';</span><br><span class="line">import { initialTravelPlan } from './places.js';</span><br><span class="line"></span><br><span class="line">// 递归展示</span><br><span class="line">function PlaceTree({ place }) {</span><br><span class="line"> const childPlaces = place.childPlaces;</span><br><span class="line"> return (</span><br><span class="line"> <li></span><br><span class="line"> {place.title}</span><br><span class="line"> {childPlaces.length > 0 && (</span><br><span class="line"> <ol></span><br><span class="line"> {childPlaces.map(place => (</span><br><span class="line"> <PlaceTree key={place.id} place={place} /></span><br><span class="line"> ))}</span><br><span class="line"> </ol></span><br><span class="line"> )}</span><br><span class="line"> </li></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default function TravelPlan() {</span><br><span class="line"> const [plan, setPlan] = useState(initialTravelPlan);</span><br><span class="line"> const planets = plan.childPlaces;</span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h2>Places to visit</h2></span><br><span class="line"> <ol></span><br><span class="line"> {planets.map(place => (</span><br><span class="line"> <PlaceTree key={place.id} place={place} /></span><br><span class="line"> ))}</span><br><span class="line"> </ol></span><br><span class="line"> </></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line">// place.js</span><br><span class="line">export const initialTravelPlan = {</span><br><span class="line"> id: 0,</span><br><span class="line"> title: '(Root)',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 1,</span><br><span class="line"> title: 'Earth',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 2,</span><br><span class="line"> title: 'Africa',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 3,</span><br><span class="line"> title: 'Botswana',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 4,</span><br><span class="line"> title: 'Egypt',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 5,</span><br><span class="line"> title: 'Kenya',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 6,</span><br><span class="line"> title: 'Madagascar',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 7,</span><br><span class="line"> title: 'Morocco',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 8,</span><br><span class="line"> title: 'Nigeria',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 9,</span><br><span class="line"> title: 'South Africa',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }]</span><br><span class="line"> }, {</span><br><span class="line"> id: 10,</span><br><span class="line"> title: 'Americas',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 11,</span><br><span class="line"> title: 'Argentina',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 12,</span><br><span class="line"> title: 'Brazil',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 13,</span><br><span class="line"> title: 'Barbados',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 14,</span><br><span class="line"> title: 'Canada',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 15,</span><br><span class="line"> title: 'Jamaica',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 16,</span><br><span class="line"> title: 'Mexico',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 17,</span><br><span class="line"> title: 'Trinidad and Tobago',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 18,</span><br><span class="line"> title: 'Venezuela',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }]</span><br><span class="line"> }, {</span><br><span class="line"> id: 19,</span><br><span class="line"> title: 'Asia',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 20,</span><br><span class="line"> title: 'China',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 21,</span><br><span class="line"> title: 'India',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 22,</span><br><span class="line"> title: 'Singapore',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 23,</span><br><span class="line"> title: 'South Korea',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 24,</span><br><span class="line"> title: 'Thailand',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 25,</span><br><span class="line"> title: 'Vietnam',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }]</span><br><span class="line"> }, {</span><br><span class="line"> id: 26,</span><br><span class="line"> title: 'Europe',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 27,</span><br><span class="line"> title: 'Croatia',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 28,</span><br><span class="line"> title: 'France',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 29,</span><br><span class="line"> title: 'Germany',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 30,</span><br><span class="line"> title: 'Italy',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 31,</span><br><span class="line"> title: 'Portugal',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 32,</span><br><span class="line"> title: 'Spain',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 33,</span><br><span class="line"> title: 'Turkey',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }]</span><br><span class="line"> }, {</span><br><span class="line"> id: 34,</span><br><span class="line"> title: 'Oceania',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 35,</span><br><span class="line"> title: 'Australia',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 36,</span><br><span class="line"> title: 'Bora Bora (French Polynesia)',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 37,</span><br><span class="line"> title: 'Easter Island (Chile)',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 38,</span><br><span class="line"> title: 'Fiji',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 39,</span><br><span class="line"> title: 'Hawaii (the USA)',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 40,</span><br><span class="line"> title: 'New Zealand',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }, {</span><br><span class="line"> id: 41,</span><br><span class="line"> title: 'Vanuatu',</span><br><span class="line"> childPlaces: [],</span><br><span class="line"> }]</span><br><span class="line"> }]</span><br><span class="line"> }, {</span><br><span class="line"> id: 42,</span><br><span class="line"> title: 'Moon',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 43,</span><br><span class="line"> title: 'Rheita',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 44,</span><br><span class="line"> title: 'Piccolomini',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 45,</span><br><span class="line"> title: 'Tycho',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }]</span><br><span class="line"> }, {</span><br><span class="line"> id: 46,</span><br><span class="line"> title: 'Mars',</span><br><span class="line"> childPlaces: [{</span><br><span class="line"> id: 47,</span><br><span class="line"> title: 'Corn Town',</span><br><span class="line"> childPlaces: []</span><br><span class="line"> }, {</span><br><span class="line"> id: 48,</span><br><span class="line"> title: 'Green Hill',</span><br><span class="line"> childPlaces: [] </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></tbody></table></figure>
<p>这时候如果我们想添加一个按钮来删除已经去过的地方。该如何做呢?<a href="https://react.docschina.org/learn/updating-objects-in-state#updating-a-nested-object">更新嵌套的 state</a> 需要从更改部分一直向上复制对象。删除一个深度嵌套的地点将涉及复制其整个父级地点链。这样的代码可能非常冗长。</p>
<p><strong>如果 state 嵌套太深,难以轻松更新,可以考虑将其“扁平化”。</strong> 这里可以通过一个方法来重构上面这个数据:不同树状结构,每个节点的<code>place</code>都是一个<strong>包含其子节点</strong>的数组,我们可以让每个节点的<code>place</code><strong>作为数组保存其子节点的ID</strong>。然后存储一个节点ID与相应节点的映射关系。</p>
<p>看看下面重组的数据:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// app.js</span><br><span class="line">import { useState } from 'react';</span><br><span class="line">import { initialTravelPlan } from './places.js';</span><br><span class="line"></span><br><span class="line">// 先找到该节点的 孩子id 再去递归id去寻找</span><br><span class="line">function PlaceTree({ id, placesById }) {</span><br><span class="line"> const place = placesById[id];</span><br><span class="line"> const childIds = place.childIds;</span><br><span class="line"> return (</span><br><span class="line"> <li></span><br><span class="line"> {place.title}</span><br><span class="line"> {childIds.length > 0 && (</span><br><span class="line"> <ol></span><br><span class="line"> {childIds.map(childId => (</span><br><span class="line"> <PlaceTree</span><br><span class="line"> key={childId}</span><br><span class="line"> id={childId}</span><br><span class="line"> placesById={placesById}</span><br><span class="line"> /></span><br><span class="line"> ))}</span><br><span class="line"> </ol></span><br><span class="line"> )}</span><br><span class="line"> </li></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default function TravelPlan() {</span><br><span class="line"> const [plan, setPlan] = useState(initialTravelPlan);</span><br><span class="line"> const root = plan[0];</span><br><span class="line"> const planetIds = root.childIds;</span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h2>Places to visit</h2></span><br><span class="line"> <ol></span><br><span class="line"> {planetIds.map(id => (</span><br><span class="line"> <PlaceTree</span><br><span class="line"> key={id}</span><br><span class="line"> id={id}</span><br><span class="line"> placesById={plan}</span><br><span class="line"> /></span><br><span class="line"> ))}</span><br><span class="line"> </ol></span><br><span class="line"> </></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line">// places.js</span><br><span class="line">export const initialTravelPlan = {</span><br><span class="line"> 0: {</span><br><span class="line"> id: 0,</span><br><span class="line"> title: '(Root)',</span><br><span class="line"> childIds: [1, 42, 46],</span><br><span class="line"> },</span><br><span class="line"> 1: {</span><br><span class="line"> id: 1,</span><br><span class="line"> title: 'Earth',</span><br><span class="line"> childIds: [2, 10, 19, 26, 34]</span><br><span class="line"> },</span><br><span class="line"> 2: {</span><br><span class="line"> id: 2,</span><br><span class="line"> title: 'Africa',</span><br><span class="line"> childIds: [3, 4, 5, 6 , 7, 8, 9]</span><br><span class="line"> }, </span><br><span class="line"> 3: {</span><br><span class="line"> id: 3,</span><br><span class="line"> title: 'Botswana',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 4: {</span><br><span class="line"> id: 4,</span><br><span class="line"> title: 'Egypt',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 5: {</span><br><span class="line"> id: 5,</span><br><span class="line"> title: 'Kenya',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 6: {</span><br><span class="line"> id: 6,</span><br><span class="line"> title: 'Madagascar',</span><br><span class="line"> childIds: []</span><br><span class="line"> }, </span><br><span class="line"> 7: {</span><br><span class="line"> id: 7,</span><br><span class="line"> title: 'Morocco',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 8: {</span><br><span class="line"> id: 8,</span><br><span class="line"> title: 'Nigeria',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 9: {</span><br><span class="line"> id: 9,</span><br><span class="line"> title: 'South Africa',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 10: {</span><br><span class="line"> id: 10,</span><br><span class="line"> title: 'Americas',</span><br><span class="line"> childIds: [11, 12, 13, 14, 15, 16, 17, 18], </span><br><span class="line"> },</span><br><span class="line"> 11: {</span><br><span class="line"> id: 11,</span><br><span class="line"> title: 'Argentina',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 12: {</span><br><span class="line"> id: 12,</span><br><span class="line"> title: 'Brazil',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 13: {</span><br><span class="line"> id: 13,</span><br><span class="line"> title: 'Barbados',</span><br><span class="line"> childIds: []</span><br><span class="line"> }, </span><br><span class="line"> 14: {</span><br><span class="line"> id: 14,</span><br><span class="line"> title: 'Canada',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 15: {</span><br><span class="line"> id: 15,</span><br><span class="line"> title: 'Jamaica',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 16: {</span><br><span class="line"> id: 16,</span><br><span class="line"> title: 'Mexico',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 17: {</span><br><span class="line"> id: 17,</span><br><span class="line"> title: 'Trinidad and Tobago',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 18: {</span><br><span class="line"> id: 18,</span><br><span class="line"> title: 'Venezuela',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 19: {</span><br><span class="line"> id: 19,</span><br><span class="line"> title: 'Asia',</span><br><span class="line"> childIds: [20, 21, 22, 23, 24, 25], </span><br><span class="line"> },</span><br><span class="line"> 20: {</span><br><span class="line"> id: 20,</span><br><span class="line"> title: 'China',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 21: {</span><br><span class="line"> id: 21,</span><br><span class="line"> title: 'India',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 22: {</span><br><span class="line"> id: 22,</span><br><span class="line"> title: 'Singapore',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 23: {</span><br><span class="line"> id: 23,</span><br><span class="line"> title: 'South Korea',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 24: {</span><br><span class="line"> id: 24,</span><br><span class="line"> title: 'Thailand',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 25: {</span><br><span class="line"> id: 25,</span><br><span class="line"> title: 'Vietnam',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 26: {</span><br><span class="line"> id: 26,</span><br><span class="line"> title: 'Europe',</span><br><span class="line"> childIds: [27, 28, 29, 30, 31, 32, 33], </span><br><span class="line"> },</span><br><span class="line"> 27: {</span><br><span class="line"> id: 27,</span><br><span class="line"> title: 'Croatia',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 28: {</span><br><span class="line"> id: 28,</span><br><span class="line"> title: 'France',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 29: {</span><br><span class="line"> id: 29,</span><br><span class="line"> title: 'Germany',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 30: {</span><br><span class="line"> id: 30,</span><br><span class="line"> title: 'Italy',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 31: {</span><br><span class="line"> id: 31,</span><br><span class="line"> title: 'Portugal',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 32: {</span><br><span class="line"> id: 32,</span><br><span class="line"> title: 'Spain',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 33: {</span><br><span class="line"> id: 33,</span><br><span class="line"> title: 'Turkey',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 34: {</span><br><span class="line"> id: 34,</span><br><span class="line"> title: 'Oceania',</span><br><span class="line"> childIds: [35, 36, 37, 38, 39, 40, 41], </span><br><span class="line"> },</span><br><span class="line"> 35: {</span><br><span class="line"> id: 35,</span><br><span class="line"> title: 'Australia',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 36: {</span><br><span class="line"> id: 36,</span><br><span class="line"> title: 'Bora Bora (French Polynesia)',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 37: {</span><br><span class="line"> id: 37,</span><br><span class="line"> title: 'Easter Island (Chile)',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 38: {</span><br><span class="line"> id: 38,</span><br><span class="line"> title: 'Fiji',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 39: {</span><br><span class="line"> id: 40,</span><br><span class="line"> title: 'Hawaii (the USA)',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 40: {</span><br><span class="line"> id: 40,</span><br><span class="line"> title: 'New Zealand',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 41: {</span><br><span class="line"> id: 41,</span><br><span class="line"> title: 'Vanuatu',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 42: {</span><br><span class="line"> id: 42,</span><br><span class="line"> title: 'Moon',</span><br><span class="line"> childIds: [43, 44, 45]</span><br><span class="line"> },</span><br><span class="line"> 43: {</span><br><span class="line"> id: 43,</span><br><span class="line"> title: 'Rheita',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 44: {</span><br><span class="line"> id: 44,</span><br><span class="line"> title: 'Piccolomini',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 45: {</span><br><span class="line"> id: 45,</span><br><span class="line"> title: 'Tycho',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 46: {</span><br><span class="line"> id: 46,</span><br><span class="line"> title: 'Mars',</span><br><span class="line"> childIds: [47, 48]</span><br><span class="line"> },</span><br><span class="line"> 47: {</span><br><span class="line"> id: 47,</span><br><span class="line"> title: 'Corn Town',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 48: {</span><br><span class="line"> id: 48,</span><br><span class="line"> title: 'Green Hill',</span><br><span class="line"> childIds: []</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>此刻,<strong>state 已经“扁平化”(也称为“规范化”),更新嵌套项会变得更加容易。</strong></p>
<p>现在删除一个地点的话,只需要更新两个 state 级别:</p>
<ul>
<li>其 <strong>父级</strong> 地点的更新版本应该从其 <code>childIds</code> 数组中排除已删除的 ID。</li>
<li>其根级“表”对象的更新版本应包括父级地点的更新版本。</li>
</ul>
<p>看以下代码处理的实例:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// app.js</span><br><span class="line">import { useState } from 'react';</span><br><span class="line">import { initialTravelPlan } from './places.js';</span><br><span class="line"></span><br><span class="line">export default function TravelPlan() {</span><br><span class="line"> const [plan, setPlan] = useState(initialTravelPlan);</span><br><span class="line"></span><br><span class="line"> function handleComplete(parentId, childId) {</span><br><span class="line"> const parent = plan[parentId];</span><br><span class="line"> // 创建一个其父级地点的新版本</span><br><span class="line"> // 但不包括子级 ID。</span><br><span class="line"> const nextParent = {</span><br><span class="line"> ...parent,</span><br><span class="line"> childIds: parent.childIds</span><br><span class="line"> .filter(id => id !== childId)</span><br><span class="line"> };</span><br><span class="line"> // 更新根 state 对象...</span><br><span class="line"> setPlan({</span><br><span class="line"> ...plan,</span><br><span class="line"> // ...以便它拥有更新的父级。</span><br><span class="line"> [parentId]: nextParent</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> const root = plan[0];</span><br><span class="line"> const planetIds = root.childIds;</span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h2>Places to visit</h2></span><br><span class="line"> <ol></span><br><span class="line"> {planetIds.map(id => (</span><br><span class="line"> <PlaceTree</span><br><span class="line"> key={id}</span><br><span class="line"> id={id}</span><br><span class="line"> parentId={0}</span><br><span class="line"> placesById={plan}</span><br><span class="line"> onComplete={handleComplete}</span><br><span class="line"> /></span><br><span class="line"> ))}</span><br><span class="line"> </ol></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 PlaceTree({ id, parentId, placesById, onComplete }) {</span><br><span class="line"> const place = placesById[id];</span><br><span class="line"> const childIds = place.childIds;</span><br><span class="line"> return (</span><br><span class="line"> <li></span><br><span class="line"> {place.title}</span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> onComplete(parentId, id);</span><br><span class="line"> }}></span><br><span class="line"> Complete</span><br><span class="line"> </button></span><br><span class="line"> {childIds.length > 0 &&</span><br><span class="line"> <ol></span><br><span class="line"> {childIds.map(childId => (</span><br><span class="line"> <PlaceTree</span><br><span class="line"> key={childId}</span><br><span class="line"> id={childId}</span><br><span class="line"> parentId={id}</span><br><span class="line"> placesById={placesById}</span><br><span class="line"> onComplete={onComplete}</span><br><span class="line"> /></span><br><span class="line"> ))}</span><br><span class="line"> </ol></span><br><span class="line"> }</span><br><span class="line"> </li></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line">// place.js</span><br><span class="line">export const initialTravelPlan = {</span><br><span class="line"> 0: {</span><br><span class="line"> id: 0,</span><br><span class="line"> title: '(Root)',</span><br><span class="line"> childIds: [1, 42, 46],</span><br><span class="line"> },</span><br><span class="line"> 1: {</span><br><span class="line"> id: 1,</span><br><span class="line"> title: 'Earth',</span><br><span class="line"> childIds: [2, 10, 19, 26, 34]</span><br><span class="line"> },</span><br><span class="line"> 2: {</span><br><span class="line"> id: 2,</span><br><span class="line"> title: 'Africa',</span><br><span class="line"> childIds: [3, 4, 5, 6 , 7, 8, 9]</span><br><span class="line"> }, </span><br><span class="line"> 3: {</span><br><span class="line"> id: 3,</span><br><span class="line"> title: 'Botswana',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 4: {</span><br><span class="line"> id: 4,</span><br><span class="line"> title: 'Egypt',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 5: {</span><br><span class="line"> id: 5,</span><br><span class="line"> title: 'Kenya',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 6: {</span><br><span class="line"> id: 6,</span><br><span class="line"> title: 'Madagascar',</span><br><span class="line"> childIds: []</span><br><span class="line"> }, </span><br><span class="line"> 7: {</span><br><span class="line"> id: 7,</span><br><span class="line"> title: 'Morocco',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 8: {</span><br><span class="line"> id: 8,</span><br><span class="line"> title: 'Nigeria',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 9: {</span><br><span class="line"> id: 9,</span><br><span class="line"> title: 'South Africa',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 10: {</span><br><span class="line"> id: 10,</span><br><span class="line"> title: 'Americas',</span><br><span class="line"> childIds: [11, 12, 13, 14, 15, 16, 17, 18], </span><br><span class="line"> },</span><br><span class="line"> 11: {</span><br><span class="line"> id: 11,</span><br><span class="line"> title: 'Argentina',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 12: {</span><br><span class="line"> id: 12,</span><br><span class="line"> title: 'Brazil',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 13: {</span><br><span class="line"> id: 13,</span><br><span class="line"> title: 'Barbados',</span><br><span class="line"> childIds: []</span><br><span class="line"> }, </span><br><span class="line"> 14: {</span><br><span class="line"> id: 14,</span><br><span class="line"> title: 'Canada',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 15: {</span><br><span class="line"> id: 15,</span><br><span class="line"> title: 'Jamaica',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 16: {</span><br><span class="line"> id: 16,</span><br><span class="line"> title: 'Mexico',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 17: {</span><br><span class="line"> id: 17,</span><br><span class="line"> title: 'Trinidad and Tobago',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 18: {</span><br><span class="line"> id: 18,</span><br><span class="line"> title: 'Venezuela',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 19: {</span><br><span class="line"> id: 19,</span><br><span class="line"> title: 'Asia',</span><br><span class="line"> childIds: [20, 21, 22, 23, 24, 25], </span><br><span class="line"> },</span><br><span class="line"> 20: {</span><br><span class="line"> id: 20,</span><br><span class="line"> title: 'China',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 21: {</span><br><span class="line"> id: 21,</span><br><span class="line"> title: 'India',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 22: {</span><br><span class="line"> id: 22,</span><br><span class="line"> title: 'Singapore',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 23: {</span><br><span class="line"> id: 23,</span><br><span class="line"> title: 'South Korea',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 24: {</span><br><span class="line"> id: 24,</span><br><span class="line"> title: 'Thailand',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 25: {</span><br><span class="line"> id: 25,</span><br><span class="line"> title: 'Vietnam',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 26: {</span><br><span class="line"> id: 26,</span><br><span class="line"> title: 'Europe',</span><br><span class="line"> childIds: [27, 28, 29, 30, 31, 32, 33], </span><br><span class="line"> },</span><br><span class="line"> 27: {</span><br><span class="line"> id: 27,</span><br><span class="line"> title: 'Croatia',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 28: {</span><br><span class="line"> id: 28,</span><br><span class="line"> title: 'France',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 29: {</span><br><span class="line"> id: 29,</span><br><span class="line"> title: 'Germany',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 30: {</span><br><span class="line"> id: 30,</span><br><span class="line"> title: 'Italy',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 31: {</span><br><span class="line"> id: 31,</span><br><span class="line"> title: 'Portugal',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 32: {</span><br><span class="line"> id: 32,</span><br><span class="line"> title: 'Spain',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 33: {</span><br><span class="line"> id: 33,</span><br><span class="line"> title: 'Turkey',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 34: {</span><br><span class="line"> id: 34,</span><br><span class="line"> title: 'Oceania',</span><br><span class="line"> childIds: [35, 36, 37, 38, 39, 40, 41], </span><br><span class="line"> },</span><br><span class="line"> 35: {</span><br><span class="line"> id: 35,</span><br><span class="line"> title: 'Australia',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 36: {</span><br><span class="line"> id: 36,</span><br><span class="line"> title: 'Bora Bora (French Polynesia)',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 37: {</span><br><span class="line"> id: 37,</span><br><span class="line"> title: 'Easter Island (Chile)',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 38: {</span><br><span class="line"> id: 38,</span><br><span class="line"> title: 'Fiji',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 39: {</span><br><span class="line"> id: 39,</span><br><span class="line"> title: 'Hawaii (the USA)',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 40: {</span><br><span class="line"> id: 40,</span><br><span class="line"> title: 'New Zealand',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 41: {</span><br><span class="line"> id: 41,</span><br><span class="line"> title: 'Vanuatu',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 42: {</span><br><span class="line"> id: 42,</span><br><span class="line"> title: 'Moon',</span><br><span class="line"> childIds: [43, 44, 45]</span><br><span class="line"> },</span><br><span class="line"> 43: {</span><br><span class="line"> id: 43,</span><br><span class="line"> title: 'Rheita',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 44: {</span><br><span class="line"> id: 44,</span><br><span class="line"> title: 'Piccolomini',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 45: {</span><br><span class="line"> id: 45,</span><br><span class="line"> title: 'Tycho',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 46: {</span><br><span class="line"> id: 46,</span><br><span class="line"> title: 'Mars',</span><br><span class="line"> childIds: [47, 48]</span><br><span class="line"> },</span><br><span class="line"> 47: {</span><br><span class="line"> id: 47,</span><br><span class="line"> title: 'Corn Town',</span><br><span class="line"> childIds: []</span><br><span class="line"> },</span><br><span class="line"> 48: {</span><br><span class="line"> id: 48,</span><br><span class="line"> title: 'Green Hill',</span><br><span class="line"> childIds: []</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>这样就可以随心所欲嵌套state,“扁平化”可以解决很多问题。这使得state更容易更新,确保嵌套对象的不同部分中没有重复。</p>
<h2 id="在组件间共享状态"><a href="#在组件间共享状态" class="headerlink" title="在组件间共享状态"></a>在组件间共享状态</h2><p>如果我们需要俩个组件的状态始终同步修改。我们可以将相关状态从这俩个组件上移除,将这些状态移到最近的父级组件,然后通过<code>props</code>将这些状态传递给这俩个组件。<strong>这被称为“状态提升”。</strong></p>
<h2 id="对state进行保留和重置状态"><a href="#对state进行保留和重置状态" class="headerlink" title="对state进行保留和重置状态"></a>对state进行保留和重置状态</h2><p>当我们重新渲染一个组件时,React需要决定组件树哪些部分要保留和更新,以及丢弃或重新创建。在大多数情况下,React的自动处理机制已经做了大部分工作。默认情况下,React会保留树中与先前渲染组件树“匹配”的部分。</p>
<p>React允许覆盖默认行为,这时候可以通过向组件传递一个唯一<code>key</code>来<em>强制</em>重置其状态。这将告诉React,<strong>组件需要重新渲染。</strong></p>
<h2 id="提取状态到reducer中"><a href="#提取状态到reducer中" class="headerlink" title="提取状态到reducer中"></a>提取状态到reducer中</h2><p>对于需要更新多个状态的组件来说,会存在过于分散的事件处理程序。对于这种情况,我们可以在组件外部将所有的状态更新逻辑合并到一个称为“reducer”的函数中。这样,事件处理程序就会变的简洁,只需要我们指定对应的“action”。同时定义,<code>reducer</code>函数指定状态因该如何更新去响应每个<code>action</code>!</p>
<h2 id="使用Context进行深层数据传递"><a href="#使用Context进行深层数据传递" class="headerlink" title="使用Context进行深层数据传递"></a>使用Context进行深层数据传递</h2><p>通常,我们会存在需要通过<code>props</code>将信息从父组件传递给子组件。如果需要在组件树中深入传递<code>prop</code>,或者树中许多组件都需要使用相同的<code>prop</code>,那么传递<code>prop</code>可能会变得麻烦。<code>Context</code>允许父组件将一些信息提供给它下层的任何组件,不管组件多深层也无需通过<code>props</code>逐层透传。</p>
<h2 id="使用Reducer和Context进行状态扩展"><a href="#使用Reducer和Context进行状态扩展" class="headerlink" title="使用Reducer和Context进行状态扩展"></a>使用Reducer和Context进行状态扩展</h2><p>Reducer可以帮助我们合并组件的状态更新逻辑。Context可以帮助我们将信息深处传递给其他组件。可以将二者结合使用,以管理复杂应用的状态。</p>
]]></content>
<categories>
<category>前端框架</category>
</categories>
<tags>
<tag>react</tag>
</tags>
</entry>
<entry>
<title>React之Hook篇</title>
<url>/2023/11/08/%20React%E4%B9%8Bhook%E7%AF%87/</url>
<content><![CDATA[<h1 id="React之Hook篇"><a href="#React之Hook篇" class="headerlink" title="React之Hook篇"></a>React之Hook篇</h1><h2 id="useState"><a href="#useState" class="headerlink" title="useState"></a>useState</h2><h3 id="什么是state?"><a href="#什么是state?" class="headerlink" title="什么是state?"></a>什么是state?</h3><p>在react中,数据不称为<code>data</code>,而称为<code>state</code></p>
<p> <code>data</code> = > <code>state</code>(状态)</p>
<p>React,其实是一个<code>view library </code>(view库—只关注视图)</p>
<p> <code>view </code>=> <code>update</code> => 视图的具体状态</p>
<p> <code>state</code> <=> <code>view</code></p>
<p> state和视图是相关联的。</p>
<p>视图是某一个状态发生了变化,所以视图要进行相应的更新。</p>
<p><code>useState()</code>=> <code>state setState</code></p>
<p>这个可以解释为,视图需要<code>state</code>状态。通过<code>useState()</code>创建了一个状态和设置状态的方法。 </p>
<h3 id="react设计理念"><a href="#react设计理念" class="headerlink" title="react设计理念"></a>react设计理念</h3><ul>
<li><p>react设计理念:一切操作函数化。</p>
</li>
<li><p>在react中,贯彻js—–“函数是一等公民”的理念。</p>
</li>
<li><p>react提供的东西都是朴素的,简单的。</p>
</li>
<li><p>react大部分都是运行时;vue都是编译时的行为。</p>
</li>
</ul>
<h3 id="使用姿势"><a href="#使用姿势" class="headerlink" title="使用姿势"></a>使用姿势</h3><p>根据状态变更,有以下俩种:</p>
<ul>
<li><code>setXxx(xxx)</code> <em>简单情况</em></li>
<li><code>setXxx((x)=>{ return 表达式})</code> <em>复杂情况</em></li>
</ul>
<ol>
<li><p>常用写法一:<code>setXxx(xxx)</code></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from "react";</span><br><span class="line"></span><br><span class="line">let initialState = 0;</span><br><span class="line">export default function useStateHook() {</span><br><span class="line"> const [count, setCount] = useState(initialState);</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <h1>{count}</h1></span><br><span class="line"> <button onClick={() => setCount(count + 1)}>+ </button></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">import { useState } from "react";</span><br><span class="line"></span><br><span class="line">let initialState = 0;</span><br><span class="line">export default function useStateHook() {</span><br><span class="line"> const [count, setCount] = useState(initialState);</span><br><span class="line"></span><br><span class="line"> function handleClick() {</span><br><span class="line"> return setCount(count + 1);</span><br><span class="line"> }</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <h1>{count}</h1></span><br><span class="line"> <button onClick={handleClick}>+</button></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></tbody></table></figure>
</li>
<li><p><code>setXxx((x)=>{ return 表达式})</code></p>
</li>
</ol>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from "react";</span><br><span class="line"></span><br><span class="line">let initialState = 1;</span><br><span class="line">export default function useStateHook() {</span><br><span class="line"> const [count, setCount] = useState(initialState);</span><br><span class="line"></span><br><span class="line"> function handleClick() {</span><br><span class="line"> return setCount((count) => {</span><br><span class="line"> return count * 2;</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"> <h1>{count}</h1></span><br><span class="line"> <button onClick={handleClick}>X</button></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></tbody></table></figure>
<h2 id="useReducer"><a href="#useReducer" class="headerlink" title="useReducer"></a>useReducer</h2><h3 id="这个hook存在的价值"><a href="#这个hook存在的价值" class="headerlink" title="这个hook存在的价值"></a>这个hook存在的价值</h3><p>当存在逻辑分支如:<code>+ — * /</code> => 都是为了计算<code>count</code>,也就是<code>count</code>=>多种操作方案,每一种方案可能有很多地方都需要使用。</p>
<p><code>reducer</code>是一个非常好去解决,集成、对状态修改的方案集合的一种方法。</p>
<h3 id="useReducer是什么"><a href="#useReducer是什么" class="headerlink" title="useReducer是什么"></a><code>useReducer</code>是什么</h3><h4 id="useReducer含义、包含的概念"><a href="#useReducer含义、包含的概念" class="headerlink" title="useReducer含义、包含的概念"></a>useReducer含义、包含的概念</h4><h5 id="含义"><a href="#含义" class="headerlink" title="含义"></a>含义</h5><p><code>useReducer</code> => 会收集<strong>所有操作</strong>某<strong>一个数据</strong>的方案。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const [ count , dispatch ] = useReducer(reducer,0)</span><br></pre></td></tr></tbody></table></figure>
<h5 id="包含的概念"><a href="#包含的概念" class="headerlink" title="包含的概念"></a>包含的概念</h5><h6 id="dispath"><a href="#dispath" class="headerlink" title="dispath"></a>dispath</h6><p><code>dispatch</code>派发器 => 传入的不同操作类型 => 调用不同的逻辑</p>
<p><code>dispatch({ type, payload })</code></p>
<p>以下为拿计算器<code>+ - * /</code>为例:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">/**</span><br><span class="line"> *</span><br><span class="line"> * @param {*} count --- 初始值</span><br><span class="line"> * @param {*} action --- { type, payload }</span><br><span class="line"> * * type --- 动作类型</span><br><span class="line"> * * payload --- 动作携带的数据</span><br><span class="line"> * @returns</span><br><span class="line"> */</span><br><span class="line">function countReducer(count, { type, payload }) {</span><br><span class="line"> switch (type) {</span><br><span class="line"> case "PLUS":</span><br><span class="line"> return count + payload;</span><br><span class="line"> case "MINUS":</span><br><span class="line"> return count - payload;</span><br><span class="line"> case "TIMES":</span><br><span class="line"> return count * payload;</span><br><span class="line"> case "DIVIDE":</span><br><span class="line"> return count / payload;</span><br><span class="line"> default:</span><br><span class="line"> return count;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h4 id="useReducer用法"><a href="#useReducer用法" class="headerlink" title="useReducer用法"></a>useReducer用法</h4><p><code>useReducer(reducer,initialState)</code>接受俩个参数,第一个是<code>reducer</code>的函数,第二个是<code>initialState</code>初始值。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const [ count , dispatch ] = useReducer(reducer,0)</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// usReducer</span><br><span class="line"></span><br><span class="line">export default function Hook() {</span><br><span class="line"> /**</span><br><span class="line"> *</span><br><span class="line"> * @param {*} count --- 初始值</span><br><span class="line"> * @param {*} action --- { type, payload }</span><br><span class="line"> * * type --- 动作类型</span><br><span class="line"> * * payload --- 动作携带的数据</span><br><span class="line"> * @returns</span><br><span class="line"> */</span><br><span class="line"> function countReducer(count, { type, payload }) {</span><br><span class="line"> switch (type) {</span><br><span class="line"> case "PLUS":</span><br><span class="line"> return count + payload;</span><br><span class="line"> case "MINUS":</span><br><span class="line"> return count - payload;</span><br><span class="line"> case "TIMES":</span><br><span class="line"> return count * payload;</span><br><span class="line"> case "DIVIDE":</span><br><span class="line"> return count / payload;</span><br><span class="line"> default:</span><br><span class="line"> return count;</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"> * * count --- 状态</span><br><span class="line"> * * dispatch --- 触发动作 --dispatch({ type,payload});</span><br><span class="line"> * * type --- 动作类型</span><br><span class="line"> * * payload --- 动作携带的数据</span><br><span class="line"> * @returns</span><br><span class="line"> */</span><br><span class="line"> const [count, dispatch] = useReducer(countReducer, 0);</span><br><span class="line"></span><br><span class="line"> /**</span><br><span class="line"> * dispatch 触发动作</span><br><span class="line"> * dispatch({ type: "PLUS", payload: 1 });</span><br><span class="line"> */</span><br><span class="line"> function plus() {</span><br><span class="line"> dispatch({ type: "PLUS", payload: 1 });</span><br><span class="line"> }</span><br><span class="line"> function minus() {</span><br><span class="line"> dispatch({ type: "MINUS", payload: 1 });</span><br><span class="line"> }</span><br><span class="line"> function times() {</span><br><span class="line"> dispatch({ type: "TIMES", payload: 2 });</span><br><span class="line"> }</span><br><span class="line"> function divide() {</span><br><span class="line"> dispatch({ type: "DIVIDE", payload: 2 });</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"> <h1>Count: {count}</h1></span><br><span class="line"> <button onClick={plus}>+</button></span><br><span class="line"> <button onClick={minus}>-</button></span><br><span class="line"> <button onClick={times}>x</button></span><br><span class="line"> <button onClick={divide}>÷</button></span><br><span class="line"> </div></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="useEffect"><a href="#useEffect" class="headerlink" title="useEffect"></a>useEffect</h2><h3 id="含义-1"><a href="#含义-1" class="headerlink" title="含义"></a>含义</h3><p><code>effect</code>—副作用 => 处理视图状态不相关的逻辑</p>
<p>例如:</p>
<ul>
<li>记时器;</li>
<li><code>console.log()</code>;</li>
<li>数据获取;</li>
<li>修改、操作<code>DOM;</code></li>
</ul>
<p>在<code>React</code>中所有副作用都必须在<code>useEffect()</code>中执行。</p>
<p>在组件挂载的时候存在一系列生命周期函数。在函数式组件中使用<code>useEffect()</code>代替类组件中生命周期的函数(简化)。</p>
<h3 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h3><ul>
<li><p>手动收集依赖;</p>
</li>
<li><p>处理渲染副作用;</p>
<p>使用一个返回值(返回值)去处理副作用清理 </p>
</li>
<li><p>代替生命周期函数;</p>
<p>需要根据传递依赖来去代替哪一个生命周期函数</p>
</li>
</ul>
<h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p>回调函数 + 参数:<code>useEffect(callback,depArr)</code></p>
<p>第二个参数为:<code>depArr</code> => <code> the Array of dependencies</code></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">useEffect(() => {</span><br><span class="line"> </span><br><span class="line"> // []:`depArr` => ` the Array of dependencies`</span><br><span class="line"> </span><br><span class="line"> // 清除函数</span><br><span class="line"> return(()=>{})</span><br><span class="line">},[])</span><br></pre></td></tr></tbody></table></figure>
<p>回调函数<code>callback</code>中的逻辑是否执行,是依赖<code>depArr</code>数组里面状态是否改变。因此<code>depArr</code>一定要在外界保存。</p>
<ul>
<li><p>如果第二个参数为<code>undefined</code> => 任何状态改变时,都会重新执行。=> <strong>组件更新的生命周期。</strong></p>
<p>以下代码:每当按钮点击时,<code>count</code>变更时,<code>useEffect()</code>都会执行。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">export default function useEffectHook() {</span><br><span class="line"> const [count, setCount] = useState(0);</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> console.log("useEffect");</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"> <div>{count}</div></span><br><span class="line"> <button onClick={() => setCount((count) => count + 1)}>+</button></span><br><span class="line"> </></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
</li>
<li><p>如果第二个参数不是一个数组则 => <strong>报警告。</strong></p>
<p>当按钮点击时,会报以下<code>warning</code>:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// react-dom.development.js:86 Warning: useEffect received a final argument that is not an array (instead, received `object`). When specified, the final argument must be an array.</span><br><span class="line"></span><br><span class="line">// Warning: useEffect received a final argument that is not an array (instead, received `object`). When specified, the final argument must be an array.</span><br><span class="line">export default function useEffectHook() {</span><br><span class="line"> const [count, setCount] = useState(0);</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> console.log("useEffect");</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"> <div>{count}</div></span><br><span class="line"> <button onClick={() => setCount((count) => count + 1)}>+</button></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></tbody></table></figure>
</li>
<li><p>如果第二个参数是一个<strong>数组</strong>,且是<strong>一个空数组</strong> => <strong>回调只会在函数组件调用时执行一次。</strong>(是在根组件执行的时候在执行) => <strong><code>componentDidMount</code></strong></p>
<p>以下执行,只会执行一次。打印一次。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">export default function useEffectHook() {</span><br><span class="line"> const [count, setCount] = useState(0);</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> console.log("useEffect");</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"> <div>{count}</div></span><br><span class="line"> <button onClick={() => setCount((count) => count + 1)}>+</button></span><br><span class="line"> </></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
</li>
<li><p>如果第二个参数是一个有元素的数组 => 元素为状态的话,状态更新,回调重新执行一次。=> <strong><code>componentDidUpdate</code></strong></p>
<p>以下代码会先执行一次,当按钮点击时,<code>count</code>更新的同时,也会执行打印。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">export default function useEffectHook() {</span><br><span class="line"> const [count, setCount] = useState(0);</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> console.log("useEffect");</span><br><span class="line"> }, [count]);</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <div>{count}</div></span><br><span class="line"> <button onClick={() => setCount((count) => count + 1)}>+</button></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></tbody></table></figure></li>
</ul>
<h3 id="清楚副作用"><a href="#清楚副作用" class="headerlink" title="清楚副作用"></a>清楚副作用</h3><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">useEffect(() => {</span><br><span class="line"> // 当页面卸载时:compoentWillUnmount</span><br><span class="line"> return(()=>{})</span><br><span class="line">},[])</span><br></pre></td></tr></tbody></table></figure>
<p>例子:清楚记时器</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">export default function useEffectHook() {</span><br><span class="line"> const [count, setCount] = useState(0);</span><br><span class="line"> useEffect(() => {</span><br><span class="line"> let t = setInterval(() => {</span><br><span class="line"> setCount((count) => count + 1);</span><br><span class="line"> }, 1000);</span><br><span class="line"></span><br><span class="line"> return () => {</span><br><span class="line"> clearInterval(t);</span><br><span class="line"> t = null;</span><br><span class="line"> };</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"> <div>{count}</div></span><br><span class="line"> </></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h3 id="对比Vue中watchEffect"><a href="#对比Vue中watchEffect" class="headerlink" title="对比Vue中watchEffect"></a>对比Vue中watchEffect</h3><table>
<thead>
<tr>
<th align="center">不同</th>
<th align="center">Vue <code>watchEffect</code></th>
<th align="center">React <code>useEffect</code></th>
</tr>
</thead>
<tbody><tr>
<td align="center">依赖收集</td>
<td align="center"><code>watchEffect</code>自动收集,直接执行我们的回调</td>
<td align="center">需要开发者手动收集</td>
</tr>
<tr>
<td align="center">参数</td>
<td align="center">没有第二个参数,第二个参数是<code>watchEffect</code>自动收集、提供的</td>
<td align="center">有第二个参数,需手动追踪依赖</td>
</tr>
<tr>
<td align="center">清楚副作用</td>
<td align="center">回调不返回任何,<br>清楚通过提供的宏(函数执行)<code>onCleanup(xxx)</code></td>
<td align="center">通过回调<code> return () => { };</code>去清楚副作用</td>
</tr>
<tr>
<td align="center">设计理念</td>
<td align="center">观察副作用</td>
<td align="center">更多去代替生命周期函数</td>
</tr>
</tbody></table>
<p>相同点:<br>都是观察我们的副作用,执行我们的回调</p>
]]></content>
<categories>
<category>前端框架</category>
</categories>
<tags>
<tag>react</tag>
</tags>
</entry>
<entry>
<title>React入门之添加交互</title>
<url>/2023/11/02/%20React-%E6%B7%BB%E5%8A%A0%E4%BA%A4%E4%BA%92/</url>
<content><![CDATA[<hr>
<h1 id="React入门之添加交互"><a href="#React入门之添加交互" class="headerlink" title="React入门之添加交互"></a>React入门之添加交互</h1><p>界面上的控件对随着用户的输入而更新。例如点击按钮切换轮播图的展示。在React中,随着时间变化的数据称为<strong>状态(state)</strong>。可以向任何组件添加状态,<strong>按需去进行更新</strong>。</p>
<span id="more"></span>
<h2 id="响应事件"><a href="#响应事件" class="headerlink" title="响应事件"></a>响应事件</h2><h3 id="什么是响应事件"><a href="#什么是响应事件" class="headerlink" title="什么是响应事件"></a>什么是响应事件</h3><p>React允许我们在JSX中添加时间处理程序。事件处理程序是我们自己定义的函数。比如我们界面交互时:点击、悬停、焦点聚焦等交互事件。</p>
<p>我们在自己的组件中可以定义我们自己的<strong>事件处理程序</strong>。<strong>做法是往我们的组件时间处理程序props指定特定应用的名称</strong>。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// 定义button组件 通过props接收onclick事件</span><br><span class="line">// {children} 作为插槽接收</span><br><span class="line">function Button({ onClick, children }) {</span><br><span class="line"> return (</span><br><span class="line"> <button onClick={onClick}></span><br><span class="line"> {children}</span><br><span class="line"> </button></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 定义Toolbar 通过props接收onPlayMovie、onUploadImage事件</span><br><span class="line">// 这里的"Play Movie、 Upload Image" 作为子组件插入</span><br><span class="line">function Toolbar({ onPlayMovie, onUploadImage }) {</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <Button onClick={onPlayMovie}></span><br><span class="line"> Play Movie</span><br><span class="line"> </Button></span><br><span class="line"> <Button onClick={onUploadImage}></span><br><span class="line"> Upload Image</span><br><span class="line"> </Button></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">// 这里的onPlayMovie、onUploadImage就是对应Toolbar组件接收的事件处理程序、最终传入Button的onClick事件中</span><br><span class="line">export default function App() {</span><br><span class="line"> return (</span><br><span class="line"> <Toolbar</span><br><span class="line"> onPlayMovie={() => alert('Playing!')}</span><br><span class="line"> onUploadImage={() => alert('Uploading!')}</span><br><span class="line"> /></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h3 id="事件处理函数"><a href="#事件处理函数" class="headerlink" title="事件处理函数"></a>事件处理函数</h3><h4 id="添加事件处理函数步骤:"><a href="#添加事件处理函数步骤:" class="headerlink" title="添加事件处理函数步骤:"></a>添加事件处理函数步骤:</h4><ol>
<li>首先定义一个函数组件</li>
<li>在函数组件中定义事件处理程序的函数,然后<strong>将其作为prop传入</strong>合适的JSX标签。</li>
</ol>
<h4 id="事件函数特点:"><a href="#事件函数特点:" class="headerlink" title="事件函数特点:"></a>事件函数特点:</h4><ul>
<li><p>通常是在<strong>组件内部</strong>定义</p>
</li>
<li><p>名称<code>handle</code>开头,后面跟事件名称</p>
<p>事件处理函数可以在JSX中有俩种定义方式:</p>
<ul>
<li>内联事件处理函数(<em><strong>函数体比较短使用较为方便</strong></em>)</li>
<li>简洁函数</li>
</ul>
</li>
</ul>
<p>这时候看个例子:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">export default function Button() {</span><br><span class="line"> function handleClick() {</span><br><span class="line"> alert('你点击了我!');</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <button onClick={handleClick}></span><br><span class="line"> 点我</span><br><span class="line"> </button></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 onClick={() => {</span><br><span class="line"> alert('你点击了我!');</span><br><span class="line">}}></span><br><span class="line"> </span><br><span class="line">// 内联写法</span><br><span class="line"><button onClick={function handleClick() {</span><br><span class="line"> alert('你点击了我!');</span><br><span class="line">}}></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h4 id="事件处理函数传递的陷阱"><a href="#事件处理函数传递的陷阱" class="headerlink" title="事件处理函数传递的陷阱"></a>事件处理函数传递的陷阱</h4><p>在Vue中会出现事件绑定时,直接触发事件处理程序。在这里存在陷阱。</p>
<p>传递给事件处理函数的函数应直接传递,而非调用。看下面的例子:</p>
<table>
<thead>
<tr>
<th align="center"><strong>传递一个函数(正确)</strong></th>
<th align="center"><strong>调用一个函数(错误)</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><button onclick="{handleClick}"></button></td>
<td align="center"><button onclick="{handleClick()}"></button></td>
</tr>
</tbody></table>
<p>这样看来其实,区别很微妙。</p>
<p>左侧示例中<code>handleClick</code>函数作为<code>onClick</code>的事件处理函数传递。这个是告诉React这个事件是当用户点击按钮时才会触发函数。</p>
<p>右侧示例中<code>handleClick()</code>中最后的<code>()</code>会在<strong>渲染过程中立即触发函数</strong>,即使没有任何点击。这是因为<strong>JSX<code>{</code>和<code>}</code>之间的Javascript会立即执行</strong>。</p>
<p>当传入内联函数时,会出现不同的陷阱。</p>
<table>
<thead>
<tr>
<th><strong>传递一个函数(正确)</strong></th>
<th><strong>调用一个函数(错误)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><button onClick={() => alert(‘…’)}></td>
<td><button onClick={alert(‘…’)}></td>
</tr>
</tbody></table>
<p>右侧的写法,将会导致组件渲染时,每次都触发。</p>
<p>左侧就是创建了一个稍后调用的函数,而不是在每次渲染时执行其内部的代码。</p>
<p><strong>综上,就是想要定义内联函数事件处理函数,要将其包装在匿名函数中。</strong></p>
<h4 id="在事件处理函数中读取props"><a href="#在事件处理函数中读取props" class="headerlink" title="在事件处理函数中读取props"></a>在事件处理函数中读取props</h4><p>事件函数声明于组件内部,因此他们可以直接访问组件的props。</p>
<p>例如:事件处理函数就可以接收到message</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">function AlertButton({ message, children }) {</span><br><span class="line"> return (</span><br><span class="line"> <button onClick={() => alert(message)}></span><br><span class="line"> {children}</span><br><span class="line"> </button></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default function Toolbar() {</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <AlertButton message="正在播放!"></span><br><span class="line"> 播放电影</span><br><span class="line"> </AlertButton></span><br><span class="line"> <AlertButton message="正在上传!"></span><br><span class="line"> 上传图片</span><br><span class="line"> </AlertButton></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></tbody></table></figure>
<h4 id="将事件处理函数作为props传递"><a href="#将事件处理函数作为props传递" class="headerlink" title="将事件处理函数作为props传递"></a>将事件处理函数作为props传递</h4><p>通常,我们会在父组件中定义子组件的事件处理函数。为此将组件从父组件接收的prop作为事件处理函数传递。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// 定义一个接收事件处理函数的子组件 </span><br><span class="line">function Button({ onClick, children }) {</span><br><span class="line"> return (</span><br><span class="line"> <button onClick={onClick}></span><br><span class="line"> {children}</span><br><span class="line"> </button></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">// 作为props传递给button组件</span><br><span class="line">function PlayButton({ movieName }) {</span><br><span class="line"> function handlePlayClick() {</span><br><span class="line"> alert(`正在播放 ${movieName}!`);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <Button onClick={handlePlayClick}></span><br><span class="line"> 播放 "{movieName}"</span><br><span class="line"> </Button></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function UploadButton() {</span><br><span class="line"> return (</span><br><span class="line"> <Button onClick={() => alert('正在上传!')}></span><br><span class="line"> 上传图片</span><br><span class="line"> </Button></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default function Toolbar() {</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <PlayButton movieName="魔女宅急便" /></span><br><span class="line"> <UploadButton /></span><br><span class="line"> </div></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h4 id="命名事件处理函数prop"><a href="#命名事件处理函数prop" class="headerlink" title="命名事件处理函数prop"></a>命名事件处理函数prop</h4><p>对于浏览器内置组件(<code><button></code> 和 <code><div></code>),仅支持浏览器事件名称,例如,onclick。<strong>但是当我们构建自己的组件时,可以任意命名事件处理函数的prop。</strong></p>
<p>当组件支持多种交互时,可以根据不同的应用程序命名事件处理函数props。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// 这里onClick接收的还是浏览器内置的<button>(小写)</span><br><span class="line">// 仍然需要使用 onClick prop,而自定义的 Button 组件接收到的 prop 名称还是可以定义。</span><br><span class="line">function Button({ onSmash, children }) {</span><br><span class="line"> return (</span><br><span class="line"> <button onClick={onSmash}></span><br><span class="line"> {children}</span><br><span class="line"> </button></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default function App() {</span><br><span class="line"> return (</span><br><span class="line"> <div></span><br><span class="line"> <Button onSmash={() => alert('正在播放!')}></span><br><span class="line"> 播放电影</span><br><span class="line"> </Button></span><br><span class="line"> <Button onSmash={() => alert('正在上传!')}></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><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<h4 id="事件传播"><a href="#事件传播" class="headerlink" title="事件传播"></a>事件传播</h4><p><strong>事件处理函数还将捕获来自任何子组件的事件。</strong>通常,我们会说事件沿着树向上“冒泡”或者“传播”:他从事件发生的地方开始,然后沿着树向上传播。</p>
<p><strong>在React中所有的事件都会传播,除了onScroll,它仅适用于附加到的JSX标签中。</strong></p>
<p>例如:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// 当你点击button时,先触发他自身的onClick</span><br><span class="line">// 在执行父级div的onClick</span><br><span class="line">// 但是如果只点击了父级的那么只会触发父级本身的onClick</span><br><span class="line">export default function Toolbar() {</span><br><span class="line"> return (</span><br><span class="line"> <div className="Toolbar" onClick={() => {</span><br><span class="line"> alert('你点击了 toolbar !');</span><br><span class="line"> }}></span><br><span class="line"> <button onClick={() => alert('正在播放!')}></span><br><span class="line"> 播放电影</span><br><span class="line"> </button></span><br><span class="line"> <button onClick={() => alert('正在上传!')}></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></tbody></table></figure>
<h4 id="阻止传播"><a href="#阻止传播" class="headerlink" title="阻止传播"></a>阻止传播</h4><p>事件处理函数接收一个**<u>事件对象</u><strong>作为唯一的参数。一般通常被称为<code>e</code>,代表`event(事件)。</strong><u><em>这个可以使用此对象读取事件的有关信息。</em></u>**</p>
<p>这个事件对象还允许阻止传播。例如:</p>
<p>当你点击按钮时:</p>
<ol>
<li>React 调用了传递给 <code><button></code> 的 <code>onClick</code> 处理函数。</li>
<li>定义在<button>中的处理函数执行了如下操作:<ul>
<li>调用 <code>e.stopPropagation()</code>,阻止事件进一步冒泡。</li>
<li>调用 <code>onClick</code> 函数,它是从 <code>Toolbar</code> 组件传递过来的 prop。</li>
</ul>
</button></li>
<li>在 <code>Toolbar</code> 组件中定义的函数,显示按钮对应的 alert。</li>
<li>由于传播被阻止,父级 <code><div></code> 的 <code>onClick</code> 处理函数不会执行。</li>
</ol>
<p>由于调用了 <code>e.stopPropagation()</code>,点击按钮现在将只显示一个 alert(来自 <code><button></code>),而并非两个(分别来自 <code><button></code> 和父级 toolbar <code><div></code>)。点击按钮与点击周围的 toolbar 不同,因此阻止传播对这个 UI 是有意义的。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">function Button({ onClick, children }) {</span><br><span class="line"> return (</span><br><span class="line"> <button onClick={e => {</span><br><span class="line"> e.stopPropagation();</span><br><span class="line"> onClick();</span><br><span class="line"> }}></span><br><span class="line"> {children}</span><br><span class="line"> </button></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">export default function Toolbar() {</span><br><span class="line"> return (</span><br><span class="line"> <div className="Toolbar" onClick={() => {</span><br><span class="line"> alert('你点击了 toolbar !');</span><br><span class="line"> }}></span><br><span class="line"> <Button onClick={() => alert('正在播放!')}></span><br><span class="line"> 播放电影</span><br><span class="line"> </Button></span><br><span class="line"> <Button onClick={() => alert('正在上传!')}></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></tbody></table></figure>
<p>拓展:</p>
<p>少数情况下,你可能需要捕获子元素上的所有事件,<strong>即便它们阻止了传播</strong>。例如,你可能想对每次点击进行埋点记录,传播逻辑暂且不论。那么你可以通过在事件名称末尾添加 <code>Capture</code> 来实现这一点:</p>
<h5 id="onClickCapture捕获所有事件"><a href="#onClickCapture捕获所有事件" class="headerlink" title="onClickCapture捕获所有事件"></a><strong>onClickCapture</strong>捕获所有事件</h5><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><div onClickCapture={() => { /* 这会首先执行 */ }}></span><br><span class="line"> <button onClick={e => e.stopPropagation()} /></span><br><span class="line"> <button onClick={e => e.stopPropagation()} /></span><br><span class="line"></div></span><br></pre></td></tr></tbody></table></figure>
<p>每个事件分三个阶段传播:</p>
<ol>
<li>它向下传播,调用所有的 <code>onClickCapture</code> 处理函数。</li>
<li>它执行被点击元素的 <code>onClick</code> 处理函数。</li>
<li>它向上传播,调用所有的 <code>onClick</code> 处理函数。</li>
</ol>
<p>捕获事件对于路由或数据分析之类的代码很有用,但你可能不会在应用程序代码中使用它们。</p>
<h4 id="传递处理函数作为事件传播的代替方案"><a href="#传递处理函数作为事件传播的代替方案" class="headerlink" title="传递处理函数作为事件传播的代替方案"></a>传递处理函数作为事件传播的代替方案</h4><p>看这一段代码</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">function Button({ onClick, children }) {</span><br><span class="line"> return (</span><br><span class="line"> <button onClick={e => {</span><br><span class="line"> e.stopPropagation();</span><br><span class="line"> onClick();</span><br><span class="line"> }}></span><br><span class="line"> {children}</span><br><span class="line"> </button></span><br><span class="line"> );</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>此处的点击事件处理函数先执行了一段代码,<strong>然后</strong>调用了父组件传递的 <code>onClick</code> prop。</p>
<p>也可以在调用父元素<code>onClick</code>函数之前,添加其他代码。此模式是事件传播的另一种 <strong>替代方案</strong> 。它让子组件处理事件,同时也让父组件指定一些额外的行为。与事件传播不同,它并非自动。但使用这种模式的好处是你可以清楚地追踪因某个事件的触发而执行的整条代码链。</p>
<p>如果你依赖于事件传播,而且很难追踪哪些处理程序在执行,及其执行的原因,可以尝试这种方法。</p>
<h4 id="阻止默认行为"><a href="#阻止默认行为" class="headerlink" title="阻止默认行为"></a>阻止默认行为</h4><p>某些浏览器事件具有与事件相关联的默认行为。例如,点击 <code><form></code> 表单内部的按钮会触发表单提交事件,默认情况下将重新加载整个页面。</p>
<p>可以调用事件对象中的 <code>e.preventDefault()</code> 来阻止这种情况发生:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">export default function Signup() {</span><br><span class="line"> return (</span><br><span class="line"> <form onSubmit={e => {</span><br><span class="line"> e.preventDefault();</span><br><span class="line"> alert('提交表单!');</span><br><span class="line"> }}></span><br><span class="line"> <input /></span><br><span class="line"> <button>发送</button></span><br><span class="line"> </form></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>不要混淆 <code>e.stopPropagation()</code> 和 <code>e.preventDefault()</code>。它们都很有用,但二者并不相关:</p>
<ul>
<li><a href="https://developer.mozilla.org/docs/Web/API/Event/stopPropagation"><code>e.stopPropagation()</code></a> 阻止触发绑定在外层标签上的事件处理函数。</li>
<li><a href="https://developer.mozilla.org/docs/Web/API/Event/preventDefault"><code>e.preventDefault()</code></a> 阻止少数事件的默认浏览器行为。</li>
</ul>
<h4 id="事件函数可以包含副作用吗"><a href="#事件函数可以包含副作用吗" class="headerlink" title="事件函数可以包含副作用吗"></a>事件函数可以包含副作用吗</h4><p>当然可以!事件处理函数是执行副作用的最佳位置。</p>
<p>与渲染函数不同,事件处理函数不需要是 <a href="https://react.docschina.org/learn/keeping-components-pure">纯函数</a>,因此它是用来 <em>更改</em> 某些值的绝佳位置。例如,更改输入框的值以响应键入,或者更改列表以响应按钮的触发。但是,为了更改某些信息,你首先需要某种方式存储它。在 React 中,这是通过 <a href="https://react.docschina.org/learn/state-a-components-memory">state(组件的记忆)</a> 来完成的。</p>
<h2 id="state:组件的记忆"><a href="#state:组件的记忆" class="headerlink" title="state:组件的记忆"></a>state:组件的记忆</h2><p>组件通常需要根据交互更改屏幕上显示的内容。在我们表单输入的时候应该更新字段、单机轮播图上的点击下一个应该更改的图片。组件需要“记住”这些东西:当前输入值、当前轮播图。在React中,这种组件持有的记忆被称为<code>state</code>。</p>
<p>当使用普通变量时,事件处理函数会更新局部的变量,但是没有达到预期的效果。</p>
<p>原因有二:</p>
<ul>
<li><strong>局部变量无法在多次渲染中持久化保存。</strong>当React在此渲染这个组件时,他会使用事件处理函数中最初的值重新开始渲染,<strong>他不会考虑之前局部变量的任何修改。</strong></li>
<li><strong>更新局部变量不会触发渲染。</strong>React没有意识它需要去使用新数据渲染数组。</li>
</ul>
<h3 id="如何使用新数据更新组件"><a href="#如何使用新数据更新组件" class="headerlink" title="如何使用新数据更新组件?"></a>如何使用新数据更新组件?</h3><p>需要做俩件事:</p>
<ol>
<li><strong>保留</strong>渲染之间的数据</li>
<li><strong>触发</strong>React使用新数据渲染组件(重新渲染)</li>
</ol>
<h3 id="这时候就引出了,主人公:useStateHook。"><a href="#这时候就引出了,主人公:useStateHook。" class="headerlink" title="这时候就引出了,主人公:useStateHook。"></a>这时候就引出了,主人公:<code>useState</code>Hook。</h3><p><code>useState</code>Hook提供了俩个功能:</p>
<ol>
<li><strong>State变量</strong>用于保存渲染间的数据。</li>
<li><strong>State setter函数</strong>更新变量并触发React再次渲染组件。</li>
</ol>
<h4 id="使用姿势(如何添加一个state变量)"><a href="#使用姿势(如何添加一个state变量)" class="headerlink" title="使用姿势(如何添加一个state变量)"></a>使用姿势(如何添加一个state变量)</h4><ol>
<li><p>现在顶部文件React导入<code>useState</code></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br></pre></td></tr></tbody></table></figure>
</li>
<li><p>定义state变量</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const [index, setIndex] = useState(0);</span><br></pre></td></tr></tbody></table></figure>
<p><strong>其中index为<code>State</code>变量,<code>setIndex</code>是对应的<code>setter</code>函数。</strong></p>
<p><strong>这里的 <code>[</code> 和 <code>]</code> 语法称为<a href="https://react.docschina.org/learn/a-javascript-refresher#array-destructuring">数组解构</a>,它允许你从数组中读取值。 <code>useState</code> 返回的数组总是正好有两项。</strong></p>
</li>
</ol>
<p>在React中,<code>useState</code>以及其他以<code>use</code>开头的函数都被称为<strong>Hook</strong></p>
<p>Hook是特殊的函数,只在React<a href="https://react.docschina.org/learn/render-and-commit#step-1-trigger-a-render">渲染</a>是有效。</p>
<p><strong><u><em>注意:</em></u></strong></p>
<p>Hooks,以<code>use</code>开头的函数,只能在组件或者**<a href="https://react.docschina.org/learn/reusing-logic-with-custom-hooks">自定义 Hook</a>** 的最顶层调用。**<u><em>不能在条件语句、循环语句或其他嵌套函数内调用 Hook。</em></u>**Hook是函数。</p>
<h3 id="深度剖析useState"><a href="#深度剖析useState" class="headerlink" title="深度剖析useState"></a>深度剖析<code>useState</code></h3><p>当调用<code>useState</code>时,是在告诉React你想让组件记住一些东西。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const [index, setIndex] = useState(0);</span><br></pre></td></tr></tbody></table></figure>
<p>在这段代码中,我们希望React记住index。</p>
<p><code>useState</code>的唯一参数是state变量的<strong>初始值</strong>。这个例子中<code>index</code><strong>初始值</strong>被<code>useState(0)</code>设置为0。</p>
<p>每当我们组件渲染时,<code>useState</code>都会返回一个包含俩个值的数组:</p>
<ol>
<li><strong>state变量</strong>(index)会保存上次渲染的值。</li>
<li><strong>state setter函数</strong>(setIndex)可以更新state变量并触发React重新渲染组件。</li>
</ol>
<p>以下是具体的执行顺序:</p>
<ol>
<li><strong>组件进行第一次渲染</strong>。会将<code>index</code>初始值0传递给<code>useState</code>,他就会返回<code>[0,setIndex]</code>。这时候React会记住<code>0</code>是最新的值。</li>
<li><strong>更新了state。</strong>当用户点击按钮的时候,他会调用<code>setIndex(index+1)</code>。index初始值<code>0</code>,所以就会变成<code>setIndex(1)</code>。这将告诉React记住<code>index</code>是<code>1</code>触发下一次渲染。</li>
<li><strong>组件进行二次渲染。</strong>React仍然看到<code>useState(0)</code>,但是这时候React记住了<code>index</code>设置为<code>1</code>,他将返回<code>[1,setIndex]</code>。</li>
<li>后续渲染过程如此反复。</li>
</ol>
<p>我们可以为一个组件赋予多个state变量。并且这些组件可以拥有任意的多种类型的state变量。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line">import { sculptureList } from './data.js';</span><br><span class="line"></span><br><span class="line">export default function Gallery() {</span><br><span class="line"> const [index, setIndex] = useState(0);</span><br><span class="line"> const [showMore, setShowMore] = useState(false);</span><br><span class="line"></span><br><span class="line"> function handleNextClick() {</span><br><span class="line"> setIndex(index + 1);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> function handleMoreClick() {</span><br><span class="line"> setShowMore(!showMore);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> let sculpture = sculptureList[index];</span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <button onClick={handleNextClick}></span><br><span class="line"> Next</span><br><span class="line"> </button></span><br><span class="line"> <h2></span><br><span class="line"> <i>{sculpture.name} </i> </span><br><span class="line"> by {sculpture.artist}</span><br><span class="line"> </h2></span><br><span class="line"> <h3> </span><br><span class="line"> ({index + 1} of {sculptureList.length})</span><br><span class="line"> </h3></span><br><span class="line"> <button onClick={handleMoreClick}></span><br><span class="line"> {showMore ? 'Hide' : 'Show'} details</span><br><span class="line"> </button></span><br><span class="line"> {showMore && <p>{sculpture.description}</p>}</span><br><span class="line"> <img </span><br><span class="line"> src={sculpture.url} </span><br><span class="line"> alt={sculpture.alt}</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></tbody></table></figure>
<p>如果它们不相关,那么存在多个 state 变量是一个好主意,例如本例中的 <code>index</code> 和 <code>showMore</code>。但是,如果你发现经常同时更改两个 state 变量,那么最好将它们合并为一个。例如,如果你有一个包含多个字段的表单,那么有一个值为对象的 state 变量比每个字段对应一个 state 变量更方便。 <a href="https://react.docschina.org/learn/choosing-the-state-structure">选择 state 结构</a>在这方面有更多提示。</p>
<h4 id="拓展:为什么React如何知道要返回哪个state??"><a href="#拓展:为什么React如何知道要返回哪个state??" class="headerlink" title="拓展:为什么React如何知道要返回哪个state??"></a>拓展:为什么React如何知道要返回哪个state??</h4><p> <a href="https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e">React Hooks: not magic, just arrays</a></p>
<h4 id="State是隔离且私有的"><a href="#State是隔离且私有的" class="headerlink" title="State是隔离且私有的"></a>State是隔离且私有的</h4><p>State是屏幕上组件实例内部的状态。<strong>也就是说每次渲染都会产生完全隔离的state副本!</strong>一个改变会会影响另一个。</p>
<p>这是因为<code>state</code>与生命在模块顶部的普通变量不同的原因。State不依赖于特定的函数调用或在代码中的位置。<strong>他的作用域“只限于”屏幕上模块特定的区域。重复渲染组件,他们的<code>state</code>是分开存储的。</strong></p>
<p><strong>state完全私有于声明他的组件。父组件无法更改它。</strong></p>
<p><strong><u><em>State 变量仅用于在组件重渲染时保存信息。在单个事件处理函数中,普通变量就足够了。当普通变量运行良好时,不要引入 state 变量。</em></u></strong></p>
<h2 id="渲染和提交"><a href="#渲染和提交" class="headerlink" title="渲染和提交"></a>渲染和提交</h2><p>设想我们是一个厨师,把食材做成美味的菜肴。在这个场景下,React就是一个服务员。这种请求和提供UI的过程分为三步:</p>
<ol>
<li><strong>触发</strong>一次渲染(把客人的点单派发厨房)</li>
<li><strong>渲染</strong>组件(厨房准备订单)</li>
<li><strong>提交</strong>到DOM(将菜品放到桌子上)</li>
</ol>
<h3 id="步骤1:触发一次渲染"><a href="#步骤1:触发一次渲染" class="headerlink" title="步骤1:触发一次渲染"></a>步骤1:触发一次渲染</h3><p>触发渲染的原因有二:</p>
<ol>
<li>组件的<strong>初次渲染</strong>。</li>
<li>组件(或者其祖先之一)的<strong>状态发生了改变</strong></li>
</ol>
<h4 id="初次渲染"><a href="#初次渲染" class="headerlink" title="初次渲染"></a>初次渲染</h4><p>当应用启动的时候,会触发初次渲染,框架和沙箱有时候会隐藏这段代码,但是它通过调用目标DOM节点的 <a href="https://react.docschina.org/reference/react-dom/client/createRoot"><code>createRoot</code></a>,然后组件调用<code>render</code>函数完成的。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import Image from './Image.js';</span><br><span class="line">import { createRoot } from 'react-dom/client';</span><br><span class="line"></span><br><span class="line">const root = createRoot(document.getElementById('root'))</span><br><span class="line">root.render(<Image />);</span><br></pre></td></tr></tbody></table></figure>
<h4 id="状态更新时重现渲染"><a href="#状态更新时重现渲染" class="headerlink" title="状态更新时重现渲染"></a>状态更新时重现渲染</h4><p>一旦组件被初次渲染,我们就可以通过<a href="https://react.docschina.org/reference/react/useState#setstate"><code>set</code> 函数</a>更新其状态来触发之后的渲染。更新组件的状态会自动将一次渲染送入队列。</p>
<h3 id="步骤2-React渲染你的组件"><a href="#步骤2-React渲染你的组件" class="headerlink" title="步骤2:React渲染你的组件"></a>步骤2:React渲染你的组件</h3><p>在触发初次渲染之后,React会调用组件的来确定屏幕上渲染显示的内容。<strong>“渲染中”</strong>即React在调用你的组件。</p>
<ul>
<li><strong>在初次渲染时</strong>,React会调用根组件。</li>
<li><strong>对于后续的渲染</strong>,React会调用内部状态更新触发了渲染的函数组件。</li>
</ul>
<p>这个过程是递归的:如果更新的组件会返回某个另外组件,那么React接下来就会渲染<em>那个</em>组件,如果哪个组件又返回了某个组件,那么React接下来会渲染<em>那个</em>组件。以此类推,这个过程会持续下去,知道没有更多的嵌套组件并且React确定知道哪些东西应该显示到屏幕上为止。</p>
<p>我们看个例子,React将会调用<code>Gallery()</code>和<code>Image()</code>若干次。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">// Gallery组件</span><br><span class="line">export default function Gallery() {</span><br><span class="line"> return (</span><br><span class="line"> <section></span><br><span class="line"> <h1>鼓舞人心的雕塑</h1></span><br><span class="line"> <Image /></span><br><span class="line"> <Image /></span><br><span class="line"> <Image /></span><br><span class="line"> </section></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function Image() {</span><br><span class="line"> return (</span><br><span class="line"> <img</span><br><span class="line"> src="https://i.imgur.com/ZF6s192.jpg"</span><br><span class="line"> alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"</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">// app</span><br><span class="line">import Gallery from './Gallery.js';</span><br><span class="line">import { createRoot } from 'react-dom/client';</span><br><span class="line"></span><br><span class="line">const root = createRoot(document.getElementById('root'))</span><br><span class="line">root.render(<Gallery />);</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<ul>
<li><strong>初次渲染中</strong>,React将会为<code><section></code>、<code><h1></code> 和三个 <code><img></code> 标签<a href="https://developer.mozilla.org/docs/Web/API/Document/createElement">创建 DOM 节点</a></li>
<li><strong>在一次重渲染过程中,</strong>React将计算它们的那些属性(如果有的话)自上次渲染以来已经更改。再下一步(提交阶段)之前,他不会对这些信息执行任何操作。</li>
</ul>
<p><em><strong><u>注意:</u></strong></em></p>
<p>渲染必须是一次 <a href="https://react.docschina.org/learn/keeping-components-pure">纯计算</a>:</p>
<ul>
<li><strong>输入相同,输出相同。</strong>给定相同的输入,组件应该始终返回相同的JSX。就好比,食客点了西红柿沙拉,不应该收到洋葱沙拉!</li>
<li><strong>只做它们自己的事情。</strong>他不应该更改更改任何存在于渲染之前的对象或者变量。就好比一个订单不应该更改其他任何人的订单。</li>
</ul>
<p>否则,随着代码库复杂性的增加,可能会遇到令人困惑的错误和不可预测的行为。在“严格模式‘下开发时,React会调用每个组件函数俩次,这可以检测不纯函数引起的错误。</p>
<h5 id="性能优化"><a href="#性能优化" class="headerlink" title="性能优化"></a>性能优化</h5><p>如果更新的组件在树中的位置非常高,渲染更新后的组件内部所有嵌套组件的默认行为将不会获得最佳性能。如果你遇到了性能问题,<a href="https://reactjs.org/docs/optimizing-performance.html">性能</a> 章节描述了几种可选的解决方案 。<strong>不要过早进行优化!</strong></p>
<h3 id="步骤3-React把更改提交到DOM上"><a href="#步骤3-React把更改提交到DOM上" class="headerlink" title="步骤3:React把更改提交到DOM上"></a>步骤3:React把更改提交到DOM上</h3><p>在渲染(调用)你的组件之后,React将会修改DOM</p>
<ul>
<li><strong>对于初次渲染,</strong>React会使用<a href="https://developer.mozilla.org/docs/Web/API/Node/appendChild"><code>appendChild()</code></a>DOM API将其创建所有的DOM节点放在屏幕上。</li>
<li><strong>对于重渲染,</strong>React将应用最少的必要操作(在渲染时计算!),以使得DOM与最新的渲染输出互相匹配。</li>
</ul>
<p><strong>React仅在渲染之间存在差异时才会更新DOM节点。</strong></p>
<p>有一个组件,它每秒使用从父组件传递下来的不同属性重新渲染一次。注意,你可以添加一些文本到 <code><input></code> 标签,更新它的 <code>value</code>,但是文本不会在组件重渲染时消失:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">export default function Clock({ time }) {</span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h1>{time}</h1></span><br><span class="line"> <input /></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></tbody></table></figure>
<p>这个例子之所以会正常运行,是因为在最后一步中,React 只会使用最新的 <code>time</code> 更新 <code><h1></code> 标签的内容。它看到 <code><input></code> 标签出现在 JSX 中与上次相同的位置,因此 React 不会修改 <code><input></code> 标签或它的 <code>value</code>!</p>
<h3 id="浏览器绘制"><a href="#浏览器绘制" class="headerlink" title="浏览器绘制"></a>浏览器绘制</h3><p>在渲染完成并且React更新DOM之后,浏览器就会重新绘制屏幕。尽管这个过程称为“浏览器渲染”(“browser rendering”),这里还是称为“绘制”(“painting”),以避免在这些文档的其余部分中出现混淆。</p>
<h2 id="state如同一张快照"><a href="#state如同一张快照" class="headerlink" title="state如同一张快照"></a>state如同一张快照</h2><p>也许state变量看起来就和一般的可读写的JavaScript变量类似。但state在其表现出的特性上更像是一张快照。设置他不会更爱你已有的state变量,但会触发重新渲染。</p>
<h3 id="设置state会触发渲染"><a href="#设置state会触发渲染" class="headerlink" title="设置state会触发渲染"></a>设置state会触发渲染</h3><p>你可能会认为你的用户界面会直接对点击之类的用户输入做出相应并发生变化。在React中,他的工作方式与这种思维模型略有不同。上一章节我们知道通过设置<code>state</code>请求重新渲染。这就意味着要使界面对输入做出反应,需要使用设置state。</p>
<p>看个例子:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function Form() {</span><br><span class="line"> const [isSent, setIsSent] = useState(false);</span><br><span class="line"> const [message, setMessage] = useState('Hi!');</span><br><span class="line"> if (isSent) {</span><br><span class="line"> return <h1>Your message is on its way!</h1></span><br><span class="line"> }</span><br><span class="line"> return (</span><br><span class="line"> <form onSubmit={(e) => {</span><br><span class="line"> e.preventDefault();</span><br><span class="line"> setIsSent(true);</span><br><span class="line"> sendMessage(message);</span><br><span class="line"> }}></span><br><span class="line"> <textarea</span><br><span class="line"> placeholder="Message"</span><br><span class="line"> value={message}</span><br><span class="line"> onChange={e => setMessage(e.target.value)}</span><br><span class="line"> /></span><br><span class="line"> <button type="submit">Send</button></span><br><span class="line"> </form></span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">function sendMessage(message) {</span><br><span class="line"> // ...</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>当单击按钮时会发生以下情况:</p>
<ol>
<li>执行 <code>onSubmit</code> 事件处理函数。</li>
<li><code>setIsSent(true)</code> 将 <code>isSent</code> 设置为 <code>true</code> 并排列一个新的渲染。</li>
<li>React 根据新的 <code>isSent</code> 值重新渲染组件。</li>
</ol>
<h3 id="渲染会及时生出一张快照"><a href="#渲染会及时生出一张快照" class="headerlink" title="渲染会及时生出一张快照"></a>渲染会及时生出一张快照</h3><p><a href="https://react.docschina.org/learn/render-and-commit#step-2-react-renders-your-components">“正在渲染”</a>就意味着React正在调用组件—- 一个函数。你从该函数返回的JSX就像是UI的一张及时的快照。它的props、事件处理函数和内部变量都是<strong>根据当前渲染时的state被计算出来的。</strong></p>
<p>相较于照片或电影画面不同,你返回的UI“快照”是可交互的。他其中包含着类似事件处理函数的逻辑,这些逻辑对于指定如何输入作出响应。React随后会更新屏幕来匹配这张快照,并绑定事件处理函数。因此,按下按钮即会触发你的JSX的点击事件处理函数。</p>
<p>当React重新渲染一组件时:</p>
<ol>
<li>React会再次调用你的函数</li>
<li>函数会返回新的JSX快照</li>
<li>React会更新界面以匹配返回的快照</li>
</ol>
<p>作为一个组件的记忆,<code>state</code>不同于在你的函数返回之后就会消失的普通变量。state实际是“活”在React本身– 就像摆在一个架子上!– 位于你的函数之外。当React调用组件时,他会为特定的那一次渲染提供一张state快照。你的组件会在会在JSX中返回一张包含一整套新的props和事件处理函数的UI快照,其中所有的值都是<strong>根据一次渲染中state的值</strong>被计算出来的!</p>
<p>下面看个例子:</p>
<p>试想下结果!</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function Counter() {</span><br><span class="line"> const [number, setNumber] = useState(0);</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h1>{number}</h1></span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line"> }}>+3</button></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></tbody></table></figure>
<p><em>点击按钮后其实发现,每次点击<code>number</code>递增一次!!!</em></p>
<p><strong>设置state只会为下一次渲染变更state的值。</strong>在第一次渲染期间,<code>number</code>为<code>0</code>。这也解释了为什么在<strong>那次渲染中的</strong> <code>onClick</code> 处理函数中,即便在调用了 <code>setNumber(number + 1)</code> 之后,<code>number</code> 的值也仍然是 <code>0</code>:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">button onClick={() => {</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line">}}>+3</button></span><br></pre></td></tr></tbody></table></figure>
<p>分析下这个按钮点击事件处理函数通知React要做的事情:</p>
<ol>
<li><code>setNumber(number + 1)</code>:<code>number</code> 是 <code>0</code> 所以 <code>setNumber(0 + 1)</code>。<ul>
<li>React 准备在下一次渲染时将 <code>number</code> 更改为 <code>1</code>。</li>
</ul>
</li>
<li><code>setNumber(number + 1)</code>:<code>number</code> 是<code>0</code> 所以 <code>setNumber(0 + 1)</code>。<ul>
<li>React 准备在下一次渲染时将 <code>number</code> 更改为 <code>1</code>。</li>
</ul>
</li>
<li><code>setNumber(number + 1)</code>:<code>number</code> 是<code>0</code> 所以 <code>setNumber(0 + 1)</code>。<ul>
<li>React 准备在下一次渲染时将 <code>number</code> 更改为 <code>1</code>。</li>
</ul>
</li>
</ol>
<p>尽管调用了三次 <code>setNumber(number + 1)</code>,但是在<strong>这次渲染的</strong>的事件处理函数中<code>number</code>会一直是<code>0</code>,所以你会三次将state设置为<code>1</code>。这就是为什么你在事件处理函数执行完后,React重新渲染的组件中的<code>number</code>等于<code>1</code>而不是<code>3</code></p>
<p>其实就是可以把state变量放入这次渲染中。由于<strong>这次渲染</strong>中的state变量就是<code>0</code>,其实事件处理函数就是以下这种:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><button onClick={() => {</span><br><span class="line"> setNumber(0 + 1);</span><br><span class="line"> setNumber(0 + 1);</span><br><span class="line"> setNumber(0 + 1);</span><br><span class="line">}}>+3</button></span><br></pre></td></tr></tbody></table></figure>
<p>所以对于下一次渲染来说,<code>number</code>是<code>1</code>,因此<strong>那次渲染中的</strong>点击事件处理函数就是这样:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><button onClick={() => {</span><br><span class="line"> setNumber(1 + 1);</span><br><span class="line"> setNumber(1 + 1);</span><br><span class="line"> setNumber(1 + 1);</span><br><span class="line">}}>+3</button></span><br></pre></td></tr></tbody></table></figure>
<p>以上这就是为什么每次都是递增<code>1</code>。</p>
<h3 id="随着时间变化的state"><a href="#随着时间变化的state" class="headerlink" title="随着时间变化的state"></a>随着时间变化的state</h3><p>来看来段代码:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function Counter() {</span><br><span class="line"> const [number, setNumber] = useState(0);</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h1>{number}</h1></span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> setNumber(number + 5);</span><br><span class="line"> alert(number);</span><br><span class="line"> }}>+5</button></span><br><span class="line"> </></span><br><span class="line"> )</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>以上代码会出现 <code>alert</code>先显示<code>0</code>,页面显示累加后的。也就是<code>alert</code>会先显示上一次的数值,页面在显示。</p>
<p>在<code>alert</code>加上记时器,使得在组件重新渲染<strong>之后</strong>才触发。又会怎么样呢?</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function Counter() {</span><br><span class="line"> const [number, setNumber] = useState(0);</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h1>{number}</h1></span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> setNumber(number + 5);</span><br><span class="line"> setTimeout(() => {</span><br><span class="line"> alert(number);</span><br><span class="line"> }, 3000);</span><br><span class="line"> }}>+5</button></span><br><span class="line"> </></span><br><span class="line"> )</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>这时候就会发现页面先渲染出累加后的数值,<code>alert</code>才会输出累计后的数值。</p>
<p>到提示框运行时,React中存储的state可能已经发生了改变,但他是使用用户与之交互时的快照进行调度的!</p>
<p><strong>一个state变量的值永远不会在一次渲染的内部发生变化</strong>,即使事件处理函数的代码是异步的。在<strong>那个渲染的</strong><code>onClick</code>内部,<code>number</code>的值即使在调用的<code>setNumber(number + 5)</code>之后也是<code>0</code>。它的值是在React通过调用你的组件“获取UI的快照”时就被“固定”了。</p>
<p><strong>React 会使 state 的值始终”固定“在一次渲染的各个事件处理函数内部。</strong> 你无需担心代码运行时 state 是否发生了变化。</p>
<h2 id="把一系列state更新加入队列"><a href="#把一系列state更新加入队列" class="headerlink" title="把一系列state更新加入队列"></a>把一系列state更新加入队列</h2><h3 id="React会对state更新进行批处理"><a href="#React会对state更新进行批处理" class="headerlink" title="React会对state更新进行批处理"></a>React会对state更新进行批处理</h3><p>在上节这个示例中,我们发现当按钮点击后,组件页面渲染数值一直是每次累加一次的。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function Counter() {</span><br><span class="line"> const [number, setNumber] = useState(0);</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h1>{number}</h1></span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line"> setNumber(number + 1);</span><br><span class="line"> }}>+3</button></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></tbody></table></figure>
<p><a href="https://react.docschina.org/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time">每一次渲染的 state 值都是固定的</a>,在第一次渲染的事件处理函数内部的<code>number</code>值总是<code>0</code>。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">setNumber(0 + 1);</span><br><span class="line">setNumber(0 + 1);</span><br><span class="line">setNumber(0 + 1);</span><br></pre></td></tr></tbody></table></figure>
<p>在Reac机制里,<strong>React会等到事件处理函数中的所有代码都运行完毕在处理你的state更新。</strong>这也就是为什么重新渲染只会发生在所有这些<code>setNumber()</code>调用<strong>之后的原因</strong>。</p>
<p>就好比,点餐时。服务员不会在你说第一道菜的时候,就去厨房下单,而是等你,把菜点完、如有修改修改完后,再一次性去下单。</p>
<p>这样就可以更新多个state变量–甚至来自多个组件的state变量–而不会触发太多的 <a href="https://react.docschina.org/learn/render-and-commit#re-renders-when-state-updates">重新渲染</a>。这样也意味着只有在我们的事件处理函数以及其中任何代码执行完成<strong>之后</strong>,UI才会更新。这种特性也就是<strong>批处理</strong>,他会使React应用运行得更快。这样也可以帮助我们避免处理只更新了一部分state变量的令人困惑的“半成品”渲染。</p>
<p><strong>React不会垮<em>多个</em>需要刻意触发的事件(如点击)进行批处理</strong>–每次点击都是单独处理的。React只会在一般来说安全的情况下才进行批处理。例如,如果第一次点击按钮会禁用表单,那么第二次点击就不会再次提交它。</p>
<h3 id="在下次渲染前读次更新同一个state"><a href="#在下次渲染前读次更新同一个state" class="headerlink" title="在下次渲染前读次更新同一个state"></a>在下次渲染前读次更新同一个state</h3><p>如果想在下次渲染之前多次更新同一个state,我们可以使用<code>setNumber(n => n + 1)</code>这样传入一个根据队列中的前一个state计算下一个state函数,而不是像<code>setNumber(number + 1)</code>这样传入<strong>下一个state的值</strong>。这是告诉React“用state值做某事”而不是仅仅替换它的方法。</p>
<p>现在尝试递增计数器:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function Counter() {</span><br><span class="line"> const [number, setNumber] = useState(0);</span><br><span class="line"></span><br><span class="line"> return (</span><br><span class="line"> <></span><br><span class="line"> <h1>{number}</h1></span><br><span class="line"> <button onClick={() => {</span><br><span class="line"> setNumber(n => n + 1);</span><br><span class="line"> setNumber(n => n + 1);</span><br><span class="line"> setNumber(n => n + 1);</span><br><span class="line"> }}>+3</button></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></tbody></table></figure>
<p>在这里,<code>n => n + 1</code>被称为<strong>更新函数</strong>。当我们给他传递一个state设置函数时:</p>
<ol>
<li>React会将此函数加入队列,以便在事件处理函数中的所有其他代码运行后进行处理。</li>
<li>在下一次渲染期间,React会遍历队列并更新之后的最终state。</li>
</ol>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">setNumber(n => n + 1);</span><br><span class="line">setNumber(n => n + 1);</span><br><span class="line">setNumber(n => n + 1);</span><br></pre></td></tr></tbody></table></figure>
<p>以下是React在执行事件处理函数时处理这几行代码的过程:</p>
<ol>
<li><code>setNumber(n => n + 1)</code>:<code>n => n + 1</code> 是一个函数。React 将它加入队列。</li>
<li><code>setNumber(n => n + 1)</code>:<code>n => n + 1</code> 是一个函数。React 将它加入队列。</li>
<li><code>setNumber(n => n + 1)</code>:<code>n => n + 1</code> 是一个函数。React 将它加入队列。</li>
</ol>
<p>在下次渲染期间调用<code>useState</code>时,React会遍历队列。之前的<code>number</code>state的值是<code>0</code>,所以这就是React作为参数<code>n</code>传递给第一个更新函数的值。然后React会获取上一个更新函数的返回值,并将其作为<code>n</code>传递给下一个更新函数,以此类推:</p>
<table>
<thead>
<tr>
<th align="center"><strong>更新队列</strong></th>
<th align="center"><strong><code>n</code></strong></th>
<th align="center"><strong>返回值</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>n => n + 1</code></td>
<td align="center"><code>0</code></td>
<td align="center"><code>0 + 1 = 1</code></td>
</tr>
<tr>
<td align="center"><code>n => n + 1</code></td>
<td align="center"><code>1</code></td>
<td align="center"><code>1 + 1 = 2</code></td>
</tr>
<tr>
<td align="center"><code>n => n + 1</code></td>
<td align="center"><code>2</code></td>
<td align="center"><code>2 + 1 = 3</code></td>
</tr>
</tbody></table>
<h3 id="如果在替换state后更新state会发生什么"><a href="#如果在替换state后更新state会发生什么" class="headerlink" title="如果在替换state后更新state会发生什么"></a>如果在替换state后更新state会发生什么</h3><p>看看下面这个例子,思考下下一次<code>number</code>渲染的值是什么?</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><button onClick={() => {</span><br><span class="line"> setNumber(number + 5);</span><br><span class="line"> setNumber(n => n + 1);</span><br><span class="line">}}></span><br></pre></td></tr></tbody></table></figure>
<p><strong>实际结果是:每次递增6</strong></p>
<p>这个事件处理函数告诉React要做的事情:</p>
<ol>
<li><code>setNumber(number + 5)</code>:<code>number</code> 为 <code>0</code>,所以 <code>setNumber(0 + 5)</code>。React 将 <em>“替换为 <code>5</code>”</em> 添加到其队列中。</li>
<li><code>setNumber(n => n + 1)</code>:<code>n => n + 1</code> 是一个更新函数。 React 将 <strong>该函数</strong> 添加到其队列中。</li>
</ol>
<p>在下一次渲染期间,React会遍历state队列:</p>
<table>
<thead>
<tr>
<th>更新队列</th>
<th><code>n</code></th>
<th>返回值</th>
</tr>
</thead>
<tbody><tr>
<td>“替换为 <code>5</code>”</td>
<td><code>0</code>(未使用)</td>
<td><code>5</code></td>
</tr>
<tr>
<td><code>n => n + 1</code></td>
<td><code>5</code></td>
<td><code>5 + 1 = 6</code></td>
</tr>
</tbody></table>
<p>React会保存<code>6</code>为最终结果并从<code>useState</code>中返回。</p>
<p>注意:其实这时候就可以发现,<code>setState(x)</code>实际上会像<code>setState(n => x)</code>一样运行,只不过没有使用<code>n</code>!</p>
<h3 id="如果在更新state后替换state会发生什么"><a href="#如果在更新state后替换state会发生什么" class="headerlink" title="如果在更新state后替换state会发生什么"></a>如果在更新state后替换state会发生什么</h3><p>看看这例子,你认为<code>number</code>在下一次渲染中的值是什么</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line"><button onClick={() => {</span><br><span class="line"> setNumber(number + 5);</span><br><span class="line"> setNumber(n => n + 1);</span><br><span class="line"> setNumber(42);</span><br><span class="line">}}></span><br></pre></td></tr></tbody></table></figure>
<p><strong>实际结果:第一次变更为<code>42</code>,后续一直为<code>42</code>。</strong></p>
<p>以下是 React 在执行事件处理函数时处理这几行代码的过程:</p>
<ol>
<li><code>setNumber(number + 5)</code>:<code>number</code> 为 <code>0</code>,所以 <code>setNumber(0 + 5)</code>。React 将 <em>“替换为 <code>5</code>”</em> 添加到其队列中。</li>
<li><code>setNumber(n => n + 1)</code>:<code>n => n + 1</code> 是一个更新函数。React 将该函数添加到其队列中。</li>
<li><code>setNumber(42)</code>:React 将 <em>“替换为 <code>42</code>”</em> 添加到其队列中。</li>
</ol>
<p>在下一次渲染期间,React 会遍历 state 队列:</p>
<table>
<thead>
<tr>
<th>更新队列</th>
<th><code>n</code></th>
<th>返回值</th>
</tr>
</thead>
<tbody><tr>
<td>“替换为 <code>5</code>”</td>
<td><code>0</code>(未使用)</td>
<td><code>5</code></td>
</tr>
<tr>
<td><code>n => n + 1</code></td>
<td><code>5</code></td>
<td><code>5 + 1 = 6</code></td>
</tr>
<tr>
<td>“替换为 <code>42</code>”</td>
<td><code>6</code>(未使用)</td>
<td><code>42</code></td>
</tr>
</tbody></table>
<p>然后 React 会保存 <code>42</code> 为最终结果并从 <code>useState</code> 中返回。</p>
<p>总而言之,以下是我们可以考虑传递给<code>setNumber</code>state设置函数的内容:</p>
<ul>
<li><strong>一个更新函数</strong>(例如:<code>n => n + 1</code>)会被添加到队列中。</li>
<li><strong>任何其他的值</strong>(例如:数字<code>5</code>)会导致“替换为<code>5</code>”被添加到队列中,已经在队列中的内容会被忽略。</li>
</ul>
<p>事件处理函数执行完成之后,React将重新触发渲染。再重新渲染期间,React将处理队列。更新函数会在渲染期间执行,因此<strong>更新函数必须是</strong> <strong><a href="https://react.docschina.org/learn/keeping-components-pure">纯函数</a></strong> 并且只返回结果。不要尝试从他内部设置state或者执行其他副作用。在严格模式下,React会更新每个更新函数俩次(但是丢弃第二个结果),以便帮助发现错误。</p>
<h3 id="命名惯例"><a href="#命名惯例" class="headerlink" title="命名惯例"></a>命名惯例</h3><p>通常使用相应的state变量的第一个字母来命名更新函数的的参数:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">setEnabled(e => !e);</span><br><span class="line">setLastName(ln => ln.reverse());</span><br><span class="line">setFriendCount(fc => fc * 2);</span><br></pre></td></tr></tbody></table></figure>
<p>另一个常见的惯例是重复使用完整的 state 变量名称,如 <code>setEnabled(enabled => !enabled)</code>,或使用前缀,如 <code>setEnabled(prevEnabled => !prevEnabled)</code>。</p>
<h2 id="更新state中的对象"><a href="#更新state中的对象" class="headerlink" title="更新state中的对象"></a>更新state中的对象</h2><p>state中可以保存任意类型的<strong>JavaScript</strong>值,包括对象。但是,在修改对象的时候不应该直接修改存放在<strong>React state</strong>中的对象。当我们想更新一个对象时,需要创建一个新的对象(或者将其拷贝一份),然后将state更次为此对象。</p>
<h3 id="什么是mutation"><a href="#什么是mutation" class="headerlink" title="什么是mutation"></a>什么是mutation</h3><p>我们可以在state中存放任意类型的JavaScript值。</p>
<p>我们在state中存放的数字、字符串和布尔值,这些类型的值在JavaScript中是不可变(immutable)的,这意味着它们不能被改变或是只读的。这些值可以通过替换它们的值来触发下一次重新渲染。</p>
<h4 id="state存放数字"><a href="#state存放数字" class="headerlink" title="state存放数字"></a>state存放数字</h4><p>state<code>x</code>从<code>0</code>到<code>5</code>,数字<code>0</code>本身没有发生改变。在JavaScript中,无法对内置的原始值,如数字、字符串和布尔值,进行任何更改。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const [x,setX] = useState(0)</span><br></pre></td></tr></tbody></table></figure>
<h4 id="state存放对象"><a href="#state存放对象" class="headerlink" title="state存放对象"></a>state存放对象</h4><p><strong>当我们改变对象本身的内容时,就制造了一个mutation</strong></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const [position, setPosition] = useState({ x: 0, y: 0 });</span><br></pre></td></tr></tbody></table></figure>
<p>严格来说,React state中存放的对象是可变的,但是应该像处理数字、布尔值、字符串一样视为不可变。当要改变的时候,应该考虑去替换它们的值,而不是对他们进行修改。</p>
<h3 id="将state视为只读的"><a href="#将state视为只读的" class="headerlink" title="将state视为只读的"></a>将state视为只读的</h3><p>在React中我们应该<strong>将所有放在state中的JavaScript对象都视为只读的。</strong></p>
<p>我们来看这个例子,我们使用存放在state中的对象来表示指针当前的位置。当我们在预览区域触发或移动光标时,红色移动。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line">export default function MovingDot() {</span><br><span class="line"> const [position, setPosition] = useState({</span><br><span class="line"> x: 0,</span><br><span class="line"> y: 0</span><br><span class="line"> });</span><br><span class="line"> return (</span><br><span class="line"> <div</span><br><span class="line"> onPointerMove={e => {</span><br><span class="line"> setPosition({</span><br><span class="line"> x: e.clientX,</span><br><span class="line"> y: e.clientY </span><br><span class="line"> })</span><br><span class="line"> }}</span><br><span class="line"> style={{</span><br><span class="line"> position: 'relative',</span><br><span class="line"> width: '100vw',</span><br><span class="line"> height: '100vh',</span><br><span class="line"> }}></span><br><span class="line"> <div style={{</span><br><span class="line"> position: 'absolute',</span><br><span class="line"> backgroundColor: 'red',</span><br><span class="line"> borderRadius: '50%',</span><br><span class="line"> transform: `translate(${position.x}px, ${position.y}px)`,</span><br><span class="line"> left: -10,</span><br><span class="line"> top: -10,</span><br><span class="line"> width: 20,</span><br><span class="line"> height: 20,</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></tbody></table></figure>
<p>为了真正 <a href="https://react.docschina.org/learn/state-as-a-snapshot#setting-state-triggers-renders">触发一次重新渲染</a>,<strong>我们需要创建一个新的对象并把它传递给state的设置函数</strong>:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">onPointerMove={e => {</span><br><span class="line"> setPosition({</span><br><span class="line"> x: e.clientX,</span><br><span class="line"> y: e.clientY</span><br><span class="line"> });</span><br><span class="line">}}</span><br></pre></td></tr></tbody></table></figure>
<p>通过使用<code>setPosition</code>,你在告诉React:</p>
<ul>
<li>使用这个新的对象替换<code>positon</code>的值</li>
<li>然后再次渲染这个组件</li>
</ul>
<h4 id="局部的mutation是可以接受的"><a href="#局部的mutation是可以接受的" class="headerlink" title="局部的mutation是可以接受的"></a>局部的mutation是可以接受的</h4><p>像这样的代码是有问题的,因为它改变了state中现有的对象:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">position.x = e.clientX;</span><br><span class="line">position.y = e.clientY;</span><br></pre></td></tr></tbody></table></figure>
<p>但是像这样的代码就<strong>没有任何问题</strong>,因为改变的是刚刚创建的一个循对象,并将这个对象传递给了state:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">const nextPosition = {};</span><br><span class="line">nextPosition.x = e.clientX;</span><br><span class="line">nextPosition.y = e.clientY;</span><br><span class="line">setPosition(nextPosition);</span><br></pre></td></tr></tbody></table></figure>
<p>这种写法完全等于这这写法:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">setPosition({</span><br><span class="line"> x: e.clientX,</span><br><span class="line"> y: e.clientY</span><br><span class="line">});</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>只有改变处于state中的<strong>现有</strong>对象时,mutation才会成为问题。而修改一个刚刚创建的对象就不会出现任何问题,因为<strong>还没有其他代码引用它。</strong>改变它并不会意外的影响到其他依赖它的东西。这叫做“局部mutation”。我们也可以在 <a href="https://react.docschina.org/learn/keeping-components-pure#local-mutation-your-components-little-secret">在渲染的过程中</a> 进行“局部mutation”的操作。这种操作既便捷又没有任何问题!</p>
<h3 id="使用展开语法复制对象"><a href="#使用展开语法复制对象" class="headerlink" title="使用展开语法复制对象"></a>使用展开语法复制对象</h3><p>在之前的例子,都会根据指针的位置创建出一个新的<code>position</code>对象。当我们只需要改变一个属性值的时候,也或者是将<strong>现有</strong>数据作为新对象的一部分。</p>
<p>看下面的例子,输入框并不会直接正常运行,因为<code>onChange</code>直接修改了state:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">import { useState } from 'react';</span><br><span class="line"></span><br><span class="line">export default function Form() {</span><br><span class="line"> const [person, setPerson] = useState({</span><br><span class="line"> firstName: 'Barbara',</span><br><span class="line"> lastName: 'Hepworth',</span><br><span class="line"> email: 'bhepworth@sculpture.com'</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> function handleFirstNameChange(e) {</span><br><span class="line"> person.firstName = e.target.value;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> function handleLastNameChange(e) {</span><br><span class="line"> person.lastName = e.target.value;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> function handleEmailChange(e) {</span><br><span class="line"> person.email = e.target.value;</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"> <label></span><br><span class="line"> First name:</span><br><span class="line"> <input</span><br><span class="line"> value={person.firstName}</span><br><span class="line"> onChange={handleFirstNameChange}</span><br><span class="line"> /></span><br><span class="line"> </label></span><br><span class="line"> <label></span><br><span class="line"> Last name:</span><br><span class="line"> <input</span><br><span class="line"> value={person.lastName}</span><br><span class="line"> onChange={handleLastNameChange}</span><br><span class="line"> /></span><br><span class="line"> </label></span><br><span class="line"> <label></span><br><span class="line"> Email:</span><br><span class="line"> <input</span><br><span class="line"> value={person.email}</span><br><span class="line"> onChange={handleEmailChange}</span><br><span class="line"> /></span><br><span class="line"> </label></span><br><span class="line"> <p></span><br><span class="line"> {person.firstName}{' '}</span><br><span class="line"> {person.lastName}{' '}</span><br><span class="line"> ({person.email})</span><br><span class="line"> </p></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></tbody></table></figure>
<p>以上的,这段代码直接修改了上一次渲染中的state:</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">person.firstName = e.target.value;</span><br></pre></td></tr></tbody></table></figure>
<p>如果我们想在获取<code>firstName</code>的需求,最可靠的办法就是创建一个新的对象将它传递给<code>stePerson</code>。但是在这里,我们还需要将<strong>当前的数据复制到新对象中</strong>,因为我们只改了一个字段。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">setPerson({</span><br><span class="line"> firstName: e.target.value, // 从 input 中获取新的 first name</span><br><span class="line"> lastName: person.lastName,</span><br><span class="line"> email: person.email</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>我们也可以使用<code>...</code><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals">对象展开</a> 语法,这样就不需要单独复制某个属性。这里注意:<strong>新的属性值应该放在最后。</strong></p>