-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathreact.html
More file actions
637 lines (562 loc) · 57.9 KB
/
react.html
File metadata and controls
637 lines (562 loc) · 57.9 KB
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
<!DOCTYPE HTML>
<html lang="zh-Hans" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>React - TypeScript 使用指南手册</title>
<!-- Custom HTML head -->
<meta name="description" content="TypeScript Handbook 中文翻译。">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../../favicon.svg">
<link rel="shortcut icon" href="../../favicon.png">
<link rel="stylesheet" href="../../css/variables.css">
<link rel="stylesheet" href="../../css/general.css">
<link rel="stylesheet" href="../../css/chrome.css">
<link rel="stylesheet" href="../../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../../highlight.css">
<link rel="stylesheet" href="../../tomorrow-night.css">
<link rel="stylesheet" href="../../ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><a href="../../PREFACE.html">前言</a></li><li class="chapter-item expanded affix "><li class="part-title">快速上手</li><li class="chapter-item expanded "><a href="../../zh/tutorials/index.html"><strong aria-hidden="true">1.</strong> 快速上手</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/tutorials/typescript-in-5-minutes.html"><strong aria-hidden="true">1.1.</strong> 5 分钟了解 TypeScript</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/asp.net-core.html"><strong aria-hidden="true">1.2.</strong> ASP.NET Core</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/asp.net-4.html"><strong aria-hidden="true">1.3.</strong> ASP.NET 4</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/gulp.html"><strong aria-hidden="true">1.4.</strong> Gulp</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/knockout.html"><strong aria-hidden="true">1.5.</strong> Knockout.js</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/react-and-webpack.html"><strong aria-hidden="true">1.6.</strong> React 与 webpack</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/react.html" class="active"><strong aria-hidden="true">1.7.</strong> React</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/angular-2.html"><strong aria-hidden="true">1.8.</strong> Angular 2</a></li><li class="chapter-item expanded "><a href="../../zh/tutorials/migrating-from-javascript.html"><strong aria-hidden="true">1.9.</strong> 从 JavaScript 迁移到 TypeScript</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">手册</li><li class="chapter-item expanded "><a href="../../zh/handbook/index.html"><strong aria-hidden="true">2.</strong> 手册</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/handbook/basic-types.html"><strong aria-hidden="true">2.1.</strong> 基础类型</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/interfaces.html"><strong aria-hidden="true">2.2.</strong> 接口</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/functions.html"><strong aria-hidden="true">2.3.</strong> 函数</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/literal-types.html"><strong aria-hidden="true">2.4.</strong> 字面量类型</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/unions-and-intersections.html"><strong aria-hidden="true">2.5.</strong> 联合类型和交叉类型</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/classes.html"><strong aria-hidden="true">2.6.</strong> 类</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/enums.html"><strong aria-hidden="true">2.7.</strong> 枚举</a></li><li class="chapter-item expanded "><a href="../../zh/handbook/generics.html"><strong aria-hidden="true">2.8.</strong> 泛型</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">手册(进阶)</li><li class="chapter-item expanded "><a href="../../zh/reference/index.html"><strong aria-hidden="true">3.</strong> 手册(进阶)</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/reference/advanced-types.html"><strong aria-hidden="true">3.1.</strong> 高级类型</a></li><li class="chapter-item expanded "><a href="../../zh/reference/utility-types.html"><strong aria-hidden="true">3.2.</strong> 实用工具类型</a></li><li class="chapter-item expanded "><a href="../../zh/reference/decorators.html"><strong aria-hidden="true">3.3.</strong> Decorators</a></li><li class="chapter-item expanded "><a href="../../zh/reference/declaration-merging.html"><strong aria-hidden="true">3.4.</strong> 声明合并</a></li><li class="chapter-item expanded "><a href="../../zh/reference/iterators-and-generators.html"><strong aria-hidden="true">3.5.</strong> Iterators 和 Generators</a></li><li class="chapter-item expanded "><a href="../../zh/reference/jsx.html"><strong aria-hidden="true">3.6.</strong> JSX</a></li><li class="chapter-item expanded "><a href="../../zh/reference/mixins.html"><strong aria-hidden="true">3.7.</strong> 混入</a></li><li class="chapter-item expanded "><a href="../../zh/reference/modules.html"><strong aria-hidden="true">3.8.</strong> 模块</a></li><li class="chapter-item expanded "><a href="../../zh/reference/module-resolution.html"><strong aria-hidden="true">3.9.</strong> 模块解析</a></li><li class="chapter-item expanded "><a href="../../zh/reference/namespaces.html"><strong aria-hidden="true">3.10.</strong> 命名空间</a></li><li class="chapter-item expanded "><a href="../../zh/reference/namespaces-and-modules.html"><strong aria-hidden="true">3.11.</strong> 命名空间和模块</a></li><li class="chapter-item expanded "><a href="../../zh/reference/symbols.html"><strong aria-hidden="true">3.12.</strong> Symbols</a></li><li class="chapter-item expanded "><a href="../../zh/reference/triple-slash-directives.html"><strong aria-hidden="true">3.13.</strong> 三斜线指令</a></li><li class="chapter-item expanded "><a href="../../zh/reference/type-compatibility.html"><strong aria-hidden="true">3.14.</strong> 类型兼容性</a></li><li class="chapter-item expanded "><a href="../../zh/reference/type-inference.html"><strong aria-hidden="true">3.15.</strong> 类型推论</a></li><li class="chapter-item expanded "><a href="../../zh/reference/variable-declarations.html"><strong aria-hidden="true">3.16.</strong> 变量声明</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">手册(v2)</li><li class="chapter-item expanded "><a href="../../zh/handbook-v2/index.html"><strong aria-hidden="true">4.</strong> 手册(v2)</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/handbook-v2/type-manipulation/template-literal-types.html"><strong aria-hidden="true">4.1.</strong> 模版字面量类型</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">TypeScript 声明文件(.d.ts)</li><li class="chapter-item expanded "><a href="../../zh/declaration-files/index.html"><strong aria-hidden="true">5.</strong> 如何书写声明文件</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/declaration-files/introduction.html"><strong aria-hidden="true">5.1.</strong> 介绍</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/by-example.html"><strong aria-hidden="true">5.2.</strong> 举例</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/library-structures.html"><strong aria-hidden="true">5.3.</strong> 库结构</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/templates.html"><strong aria-hidden="true">5.4.</strong> 模板</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/do-s-and-don-ts.html"><strong aria-hidden="true">5.5.</strong> 最佳实践</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/deep-dive.html"><strong aria-hidden="true">5.6.</strong> 深入</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/publishing.html"><strong aria-hidden="true">5.7.</strong> 发布</a></li><li class="chapter-item expanded "><a href="../../zh/declaration-files/consumption.html"><strong aria-hidden="true">5.8.</strong> 使用</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">TypeScript for JavaScript</li><li class="chapter-item expanded "><a href="../../zh/javascript/type-checking-javascript-files.html"><strong aria-hidden="true">6.</strong> JavaScript 文件里的类型检查</a></li><li class="chapter-item expanded affix "><li class="part-title">工程配置</li><li class="chapter-item expanded "><a href="../../zh/project-config/index.html"><strong aria-hidden="true">7.</strong> 工程配置</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/project-config/tsconfig.json.html"><strong aria-hidden="true">7.1.</strong> tsconfig.json</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/project-references.html"><strong aria-hidden="true">7.2.</strong> 工程引用</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/typings-for-npm-packages.html"><strong aria-hidden="true">7.3.</strong> NPM 包的类型</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/compiler-options.html"><strong aria-hidden="true">7.4.</strong> 编译选项</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/configuring-watch.html"><strong aria-hidden="true">7.5.</strong> 配置 Watch</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/compiler-options-in-msbuild.html"><strong aria-hidden="true">7.6.</strong> 在 MSBuild 里使用编译选项</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/integrating-with-build-tools.html"><strong aria-hidden="true">7.7.</strong> 与其它构建工具整合</a></li><li class="chapter-item expanded "><a href="../../zh/project-config/nightly-builds.html"><strong aria-hidden="true">7.8.</strong> 使用 TypeScript 的每日构建版本</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">版本发布说明(Release Notes)</li><li class="chapter-item expanded "><a href="../../zh/release-notes/index.html"><strong aria-hidden="true">8.</strong> 新增功能</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.4.html"><strong aria-hidden="true">8.1.</strong> TypeScript 5.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.3.html"><strong aria-hidden="true">8.2.</strong> TypeScript 5.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.2.html"><strong aria-hidden="true">8.3.</strong> TypeScript 5.2</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.1.html"><strong aria-hidden="true">8.4.</strong> TypeScript 5.1</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-5.0.html"><strong aria-hidden="true">8.5.</strong> TypeScript 5.0</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.9.html"><strong aria-hidden="true">8.6.</strong> TypeScript 4.9</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.8.html"><strong aria-hidden="true">8.7.</strong> TypeScript 4.8</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.7.html"><strong aria-hidden="true">8.8.</strong> TypeScript 4.7</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.6.html"><strong aria-hidden="true">8.9.</strong> TypeScript 4.6</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.5.html"><strong aria-hidden="true">8.10.</strong> TypeScript 4.5</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.4.html"><strong aria-hidden="true">8.11.</strong> TypeScript 4.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.3.html"><strong aria-hidden="true">8.12.</strong> TypeScript 4.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.2.html"><strong aria-hidden="true">8.13.</strong> TypeScript 4.2</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.1.html"><strong aria-hidden="true">8.14.</strong> TypeScript 4.1</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-4.0.html"><strong aria-hidden="true">8.15.</strong> TypeScript 4.0</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.9.html"><strong aria-hidden="true">8.16.</strong> TypeScript 3.9</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.8.html"><strong aria-hidden="true">8.17.</strong> TypeScript 3.8</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.7.html"><strong aria-hidden="true">8.18.</strong> TypeScript 3.7</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.6.html"><strong aria-hidden="true">8.19.</strong> TypeScript 3.6</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.5.html"><strong aria-hidden="true">8.20.</strong> TypeScript 3.5</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.4.html"><strong aria-hidden="true">8.21.</strong> TypeScript 3.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.3.html"><strong aria-hidden="true">8.22.</strong> TypeScript 3.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.2.html"><strong aria-hidden="true">8.23.</strong> TypeScript 3.2</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.1.html"><strong aria-hidden="true">8.24.</strong> TypeScript 3.1</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-3.0.html"><strong aria-hidden="true">8.25.</strong> TypeScript 3.0</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.9.html"><strong aria-hidden="true">8.26.</strong> TypeScript 2.9</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.8.html"><strong aria-hidden="true">8.27.</strong> TypeScript 2.8</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.7.html"><strong aria-hidden="true">8.28.</strong> TypeScript 2.7</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.6.html"><strong aria-hidden="true">8.29.</strong> TypeScript 2.6</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.5.html"><strong aria-hidden="true">8.30.</strong> TypeScript 2.5</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.4.html"><strong aria-hidden="true">8.31.</strong> TypeScript 2.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.3.html"><strong aria-hidden="true">8.32.</strong> TypeScript 2.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.2.html"><strong aria-hidden="true">8.33.</strong> TypeScript 2.2</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.1.html"><strong aria-hidden="true">8.34.</strong> TypeScript 2.1</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-2.0.html"><strong aria-hidden="true">8.35.</strong> TypeScript 2.0</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.8.html"><strong aria-hidden="true">8.36.</strong> TypeScript 1.8</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.7.html"><strong aria-hidden="true">8.37.</strong> TypeScript 1.7</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.6.html"><strong aria-hidden="true">8.38.</strong> TypeScript 1.6</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.5.html"><strong aria-hidden="true">8.39.</strong> TypeScript 1.5</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.4.html"><strong aria-hidden="true">8.40.</strong> TypeScript 1.4</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.3.html"><strong aria-hidden="true">8.41.</strong> TypeScript 1.3</a></li><li class="chapter-item expanded "><a href="../../zh/release-notes/typescript-1.1.html"><strong aria-hidden="true">8.42.</strong> TypeScript 1.1</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">破坏性改动(Breaking Changes)</li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/index.html"><strong aria-hidden="true">9.</strong> Breaking Changes</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.6.html"><strong aria-hidden="true">9.1.</strong> TypeScript 3.6</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.5.html"><strong aria-hidden="true">9.2.</strong> TypeScript 3.5</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.4.html"><strong aria-hidden="true">9.3.</strong> TypeScript 3.4</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.2.html"><strong aria-hidden="true">9.4.</strong> TypeScript 3.2</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.1.html"><strong aria-hidden="true">9.5.</strong> TypeScript 3.1</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-3.0.html"><strong aria-hidden="true">9.6.</strong> TypeScript 3.0</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.9.html"><strong aria-hidden="true">9.7.</strong> TypeScript 2.9</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.8.html"><strong aria-hidden="true">9.8.</strong> TypeScript 2.8</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.7.html"><strong aria-hidden="true">9.9.</strong> TypeScript 2.7</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.6.html"><strong aria-hidden="true">9.10.</strong> TypeScript 2.6</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.4.html"><strong aria-hidden="true">9.11.</strong> TypeScript 2.4</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.3.html"><strong aria-hidden="true">9.12.</strong> TypeScript 2.3</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.2.html"><strong aria-hidden="true">9.13.</strong> TypeScript 2.2</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.1.html"><strong aria-hidden="true">9.14.</strong> TypeScript 2.1</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-2.0.html"><strong aria-hidden="true">9.15.</strong> TypeScript 2.0</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.8.html"><strong aria-hidden="true">9.16.</strong> TypeScript 1.8</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.7.html"><strong aria-hidden="true">9.17.</strong> TypeScript 1.7</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.6.html"><strong aria-hidden="true">9.18.</strong> TypeScript 1.6</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.5.html"><strong aria-hidden="true">9.19.</strong> TypeScript 1.5</a></li><li class="chapter-item expanded "><a href="../../zh/breaking-changes/typescript-1.4.html"><strong aria-hidden="true">9.20.</strong> TypeScript 1.4</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">TypeScript 使用指南手册</h1>
<div class="right-buttons">
<a href="../../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="react"><a class="header" href="#react">React</a></h1>
<p>这篇快速上手指南会教你如何将TypeScript与<a href="https://reactjs.org/">React</a>结合起来使用。 在最后,你将学到:</p>
<ul>
<li>使用TypeScript和React创建工程</li>
<li>使用<a href="https://github.com/palantir/tslint">TSLint</a>进行代码检查</li>
<li>使用<a href="https://facebook.github.io/jest/">Jest</a>和<a href="http://airbnb.io/enzyme/">Enzyme</a>进行测试,以及</li>
<li>使用<a href="https://github.com/reactjs/react-redux">Redux</a>管理状态</li>
</ul>
<p>我们会使用<a href="https://github.com/facebookincubator/create-react-app">create-react-app</a>工具快速搭建工程环境。</p>
<p>这里假设你已经在使用<a href="https://nodejs.org/">Node.js</a>和<a href="https://www.npmjs.com/">npm</a>。 并且已经了解了<a href="https://reactjs.org/docs/hello-world.html">React的基础知识</a>。</p>
<h2 id="创建新工程"><a class="header" href="#创建新工程">创建新工程</a></h2>
<p>让我们首先创建一个叫做<code>my-app</code>的新工程:</p>
<pre><code class="language-text">npx create-react-app my-app --template typescript
</code></pre>
<p><a href="https://www.npmjs.com/package/react-scripts-ts">react-scripts-ts</a>是一系列适配器,它利用标准的create-react-app工程管道并把TypeScript混入进来。</p>
<p>此时的工程结构应如下所示:</p>
<pre><code class="language-text">my-app/
├─ .gitignore
├─ node_modules/
├─ public/
├─ src/
│ └─ ...
├─ package.json
├─ tsconfig.json
└─ tslint.json
</code></pre>
<p>注意:</p>
<ul>
<li><code>tsconfig.json</code>包含了工程里TypeScript特定的选项。</li>
<li><code>tslint.json</code>保存了要使用的代码检查器的设置,<a href="https://github.com/palantir/tslint">TSLint</a>。</li>
<li><code>package.json</code>包含了依赖,还有一些命令的快捷方式,如测试命令,预览命令和发布应用的命令。</li>
<li><code>public</code>包含了静态资源如HTML页面或图片。除了<code>index.html</code>文件外,其它的文件都可以删除。</li>
<li><code>src</code>包含了TypeScript和CSS源码。<code>index.tsx</code>是强制使用的入口文件。</li>
</ul>
<h2 id="运行工程"><a class="header" href="#运行工程">运行工程</a></h2>
<p>通过下面的方式即可轻松地运行这个工程。</p>
<pre><code class="language-bash">npm run start
</code></pre>
<p>它会执行<code>package.json</code>里面指定的<code>start</code>命令,并且会启动一个服务器,当我们保存文件时还会自动刷新页面。 通常这个服务器的地址是<code>http://localhost:3000</code>,页面应用会被自动地打开。</p>
<p>它会保持监听以方便我们快速地预览改动。</p>
<h2 id="测试工程"><a class="header" href="#测试工程">测试工程</a></h2>
<p>测试也仅仅是一行命令的事儿:</p>
<pre><code class="language-bash">npm run test
</code></pre>
<p>这个命令会运行Jest,一个非常好用的测试工具,它会运行所有扩展名是<code>.test.ts</code>或<code>.spec.ts</code>的文件。 好比是<code>npm run start</code>命令,当检测到有改动的时候Jest会自动地运行。 如果喜欢的话,你还可以同时运行<code>npm run start</code>和<code>npm run test</code>,这样你就可以在预览的同时进行测试。</p>
<h2 id="生成生产环境的构建版本"><a class="header" href="#生成生产环境的构建版本">生成生产环境的构建版本</a></h2>
<p>在使用<code>npm run start</code>运行工程的时候,我们并没有生成一个优化过的版本。 通常我们想给用户一个运行的尽可能快并在体积上尽可能小的代码。 像压缩这样的优化方法可以做到这一点,但是总是要耗费更多的时间。 我们把这样的构建版本称做“生产环境”版本(与开发版本相对)。</p>
<p>要执行生产环境的构建,可以运行如下命令:</p>
<pre><code class="language-bash">npm run build
</code></pre>
<p>这会相应地创建优化过的JS和CSS文件,<code>./build/static/js</code>和<code>./build/static/css</code>。</p>
<p>大多数情况下你不需要生成生产环境的构建版本, 但它可以帮助你衡量应用最终版本的体积大小。</p>
<h2 id="创建一个组件"><a class="header" href="#创建一个组件">创建一个组件</a></h2>
<p>下面我们将要创建一个<code>Hello</code>组件。 这个组件接收任意一个我们想对之打招呼的名字(我们把它叫做<code>name</code>),并且有一个可选数量的感叹号做为结尾(通过<code>enthusiasmLevel</code>)。</p>
<p>若我们这样写<code><Hello name="Daniel" enthusiasmLevel={3} /></code>,这个组件大至会渲染成<code><div>Hello Daniel!!!</div></code>。 如果没指定<code>enthusiasmLevel</code>,组件将默认显示一个感叹号。 若<code>enthusiasmLevel</code>为<code>0</code>或负值将抛出一个错误。</p>
<p>下面来写一下<code>Hello.tsx</code>:</p>
<pre><code class="language-typescript">// src/components/Hello.tsx
import * as React from 'react';
export interface Props {
name: string;
enthusiasmLevel?: number;
}
function Hello({ name, enthusiasmLevel = 1 }: Props) {
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
</div>
);
}
export default Hello;
// helpers
function getExclamationMarks(numChars: number) {
return Array(numChars + 1).join('!');
}
</code></pre>
<p>注意我们定义了一个类型<code>Props</code>,它指定了我们组件要用到的属性。 <code>name</code>是必需的且为<code>string</code>类型,同时<code>enthusiasmLevel</code>是可选的且为<code>number</code>类型(你可以通过名字后面加<code>?</code>为指定可选参数)。</p>
<p>我们创建了一个函数组件<code>Hello</code>。 具体来讲,<code>Hello</code>是一个函数,接收一个<code>Props</code>对象并拆解它。 如果<code>Props</code>对象里没有设置<code>enthusiasmLevel</code>,默认值为<code>1</code>。</p>
<p>使用函数是React中定义组件的<a href="https://reactjs.org/docs/components-and-props.html#functional-and-class-components">两种方式</a>之一。 如果你喜欢的话,也_可以_通过类的方式定义:</p>
<pre><code class="language-typescript">class Hello extends React.Component<Props, object> {
render() {
const { name, enthusiasmLevel = 1 } = this.props;
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
</div>
);
}
}
</code></pre>
<p>当我们的<a href="https://reactjs.org/docs/state-and-lifecycle.html">组件具有某些状态</a>的时候,使用类的方式是很有用处的。 但在这个例子里我们不需要考虑状态 - 事实上,在<code>React.Component<Props, object></code>我们把状态指定为了<code>object</code>,因此使用函数组件更简洁。 当在创建可重用的通用UI组件的时候,在表现层使用组件局部状态比较适合。 针对我们应用的生命周期,我们会审视应用是如何通过Redux轻松地管理普通状态的。</p>
<p>现在我们已经写好了组件,让我们仔细看看<code>index.tsx</code>,把<code><App /></code>替换成<code><Hello ... /></code>。</p>
<p>首先我们在文件头部导入它:</p>
<pre><code class="language-typescript">import Hello from './components/Hello';
</code></pre>
<p>然后修改<code>render</code>调用:</p>
<pre><code class="language-typescript">ReactDOM.render(
<Hello name="TypeScript" enthusiasmLevel={10} />,
document.getElementById('root') as HTMLElement
);
</code></pre>
<h3 id="类型断言"><a class="header" href="#类型断言">类型断言</a></h3>
<p>这里还有一点要指出,就是最后一行<code>document.getElementById('root') as HTMLElement</code>。 这个语法叫做_类型断言_,有时也叫做_转换_。 当你比类型检查器更清楚一个表达式的类型的时候,你可以通过这种方式通知TypeScript。</p>
<p>这里,我们之所以这么做是因为<code>getElementById</code>的返回值类型是<code>HTMLElement | null</code>。 简单地说,<code>getElementById</code>返回<code>null</code>是当无法找对对应<code>id</code>元素的时候。 我们假设<code>getElementById</code>总是成功的,因此我们要使用<code>as</code>语法告诉TypeScript这点。</p>
<p>TypeScript还有一种感叹号(<code>!</code>)结尾的语法,它会从前面的表达式里移除<code>null</code>和<code>undefined</code>。 所以我们也_可以_写成<code>document.getElementById('root')!</code>,但在这里我们想写的更清楚些。</p>
<h2 id="sunglasses添加样式"><a class="header" href="#sunglasses添加样式">:sunglasses:添加样式</a></h2>
<p>通过我们的设置为一个组件添加样式很容易。 若要设置<code>Hello</code>组件的样式,我们可以创建这样一个CSS文件<code>src/components/Hello.css</code>。</p>
<pre><code class="language-css">.hello {
text-align: center;
margin: 20px;
font-size: 48px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif
}
.hello button {
margin-left: 25px;
margin-right: 25px;
font-size: 40px;
min-width: 50px;
}
</code></pre>
<p><code>create-react-app</code>包含的工具(Webpack和一些加载器)允许我们导入样式表文件。 当我们构建应用的时候,所有导入的<code>.css</code>文件会被拼接成一个输出文件。 因此在<code>src/components/Hello.tsx</code>,我们需要添加如下导入语句。</p>
<pre><code class="language-typescript">import './Hello.css';
</code></pre>
<h2 id="使用jest编写测试"><a class="header" href="#使用jest编写测试">使用Jest编写测试</a></h2>
<p>如果你没使用过Jest,你可能先要把它安装为开发依赖项。</p>
<pre><code class="language-bash">npm install -D jest jest-cli jest-config
</code></pre>
<p>我们对<code>Hello</code>组件有一些假设。 让我们在此重申一下:</p>
<blockquote>
<ul>
<li>当这样写<code><Hello name="Daniel" enthusiasmLevel={3} /></code>时,组件应被渲染成<code><div>Hello Daniel!!!</div></code>。</li>
<li>若未指定<code>enthusiasmLevel</code>,组件应默认显示一个感叹号。</li>
<li>若<code>enthusiasmLevel</code>为<code>0</code>或负值,它应抛出一个错误。</li>
</ul>
</blockquote>
<p>我们将针对这些需求为组件写一些注释。</p>
<p>但首先,我们要安装Enzyme。 <a href="http://airbnb.io/enzyme/">Enzyme</a>是React生态系统里一个通用工具,它方便了针对组件的行为编写测试。 默认地,我们的应用包含了一个叫做jsdom的库,它允许我们模拟DOM以及在非浏览器的环境下测试运行时的行为。 Enzyme与此类似,但是是基于jsdom的,并且方便我们查询组件。</p>
<p>让我们把它安装为开发依赖项。</p>
<pre><code class="language-bash">npm install -D enzyme @types/enzyme enzyme-adapter-react-16 @types/enzyme-adapter-react-16
</code></pre>
<p>如果你的react版本低于15.5.0,还需安装如下</p>
<pre><code class="language-bash">npm install -D react-addons-test-utils
</code></pre>
<p>注意我们同时安装了<code>enzyme</code>和<code>@types/enzyme</code>。 <code>enzyme</code>包指的是包含了实际运行的JavaScript代码包,而<code>@types/enzyme</code>则包含了声明文件(<code>.d.ts</code>文件)的包,以便TypeScript能够了解该如何使用Enzyme。 你可以在<a href="https://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html">这里</a>了解更多关于<code>@types</code>包的信息。</p>
<p>我们还需要安装<code>enzyme-adapter</code>和<code>react-addons-test-utils</code>。 它们是使用<code>enzyme</code>所需要安装的包,前者作为配置适配器是必须的,而后者若采用的React版本在15.5.0之上则毋需安装。</p>
<p>现在我们已经设置好了Enzyme,下面开始编写测试! 先创建一个文件<code>src/components/Hello.test.tsx</code>,与先前的<code>Hello.tsx</code>文件放在一起。</p>
<pre><code class="language-typescript">// src/components/Hello.test.tsx
import * as React from 'react';
import * as enzyme from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import Hello from './Hello';
enzyme.configure({ adapter: new Adapter() });
it('renders the correct text when no enthusiasm level is given', () => {
const hello = enzyme.shallow(<Hello name='Daniel' />);
expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
});
it('renders the correct text with an explicit enthusiasm of 1', () => {
const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={1}/>);
expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
});
it('renders the correct text with an explicit enthusiasm level of 5', () => {
const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={5} />);
expect(hello.find(".greeting").text()).toEqual('Hello Daniel!!!!!');
});
it('throws when the enthusiasm level is 0', () => {
expect(() => {
enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={0} />);
}).toThrow();
});
it('throws when the enthusiasm level is negative', () => {
expect(() => {
enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={-1} />);
}).toThrow();
});
</code></pre>
<p>这些测试都十分基础,但你可以从中得到启发。</p>
<h2 id="添加state管理"><a class="header" href="#添加state管理">添加state管理</a></h2>
<p>到此为止,如果你使用React的目的是只获取一次数据并显示,那么你已经完成了。 但是如果你想开发一个可以交互的应用,那么你需要添加state管理。</p>
<h3 id="state管理概述"><a class="header" href="#state管理概述">state管理概述</a></h3>
<p>React本身就是一个适合于创建可组合型视图的库。 但是,React并没有任何在应用间同步数据的功能。 就React组件而言,数据是通过每个元素上指定的props向子元素传递。</p>
<p>因为React本身并没有提供内置的state管理功能,React社区选择了Redux和MobX库。</p>
<p><a href="http://redux.js.org">Redux</a>依靠一个统一且不可变的数据存储来同步数据,并且更新那里的数据时会触发应用的更新渲染。 state的更新是以一种不可变的方式进行,它会发布一条明确的action消息,这个消息必须被reducer函数处理。 由于使用了这样明确的方式,很容易弄清楚一个action是如何影响程序的state。</p>
<p><a href="https://mobx.js.org/">MobX</a>借助于函数式响应型模式,state被包装在了可观察对象里,并通过props传递。 通过将state标记为可观察的,即可在所有观察者之间保持state的同步性。 另一个好处是,这个库已经使用TypeScript实现了。</p>
<p>这两者各有优缺点。 但Redux使用得更广泛,因此在这篇教程里,我们主要看如何使用Redux; 但是也鼓励大家两者都去了解一下。</p>
<p>后面的小节学习曲线比较陡。 因此强烈建议大家先去<a href="http://redux.js.org/">熟悉一下Redux</a>。</p>
<h3 id="设置actions"><a class="header" href="#设置actions">设置actions</a></h3>
<p>只有当应用里的state会改变的时候,我们才需要去添加Redux。 我们需要一个action的来源,它将触发改变。 它可以是一个定时器或者UI上的一个按钮。</p>
<p>为此,我们将增加两个按钮来控制<code>Hello</code>组件的感叹级别。</p>
<h3 id="安装redux"><a class="header" href="#安装redux">安装Redux</a></h3>
<p>安装<code>redux</code>和<code>react-redux</code>以及它们的类型文件做为依赖。</p>
<pre><code class="language-bash">npm install -S redux react-redux @types/react-redux
</code></pre>
<p>这里我们不需要安装<code>@types/redux</code>,因为Redux已经自带了声明文件(<code>.d.ts</code>文件)。</p>
<h3 id="定义应用的状态"><a class="header" href="#定义应用的状态">定义应用的状态</a></h3>
<p>我们需要定义Redux保存的state的结构。 创建<code>src/types/index.tsx</code>文件,它保存了类型的定义,我们在整个程序里都可能用到。</p>
<pre><code class="language-typescript">// src/types/index.tsx
export interface StoreState {
languageName: string;
enthusiasmLevel: number;
}
</code></pre>
<p>这里我们想让<code>languageName</code>表示应用使用的编程语言(例如,TypeScript或者JavaScript),<code>enthusiasmLevel</code>是可变的。 在写我们的第一个容器的时候,就会明白为什么要令state与props稍有不同。</p>
<h3 id="添加actions"><a class="header" href="#添加actions">添加actions</a></h3>
<p>下面我们创建这个应用将要响应的消息类型,<code>src/constants/index.tsx</code>。</p>
<pre><code class="language-typescript">// src/constants/index.tsx
export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';
export type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM;
export const DECREMENT_ENTHUSIASM = 'DECREMENT_ENTHUSIASM';
export type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM;
</code></pre>
<p>这里的<code>const</code>/<code>type</code>模式允许我们以容易访问和重构的方式使用TypeScript的字符串字面量类型。</p>
<p>接下来,我们创建一些actions以及创建这些actions的函数,<code>src/actions/index.tsx</code>。</p>
<pre><code class="language-typescript">import * as constants from '../constants'
export interface IncrementEnthusiasm {
type: constants.INCREMENT_ENTHUSIASM;
}
export interface DecrementEnthusiasm {
type: constants.DECREMENT_ENTHUSIASM;
}
export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;
export function incrementEnthusiasm(): IncrementEnthusiasm {
return {
type: constants.INCREMENT_ENTHUSIASM
}
}
export function decrementEnthusiasm(): DecrementEnthusiasm {
return {
type: constants.DECREMENT_ENTHUSIASM
}
}
</code></pre>
<p>我们创建了两个类型,它们负责增加操作和减少操作的行为。 我们还定义了一个类型(<code>EnthusiasmAction</code>),它描述了哪些action是可以增加或减少的。 最后,我们定义了两个函数用来创建实际的actions。</p>
<p>这里有一些清晰的模版,你可以参考类似<a href="https://www.npmjs.com/package/redux-actions">redux-actions</a>的库。</p>
<h3 id="添加reducer"><a class="header" href="#添加reducer">添加reducer</a></h3>
<p>现在我们可以开始写第一个reducer了! Reducers是函数,它们负责生成应用state的拷贝使之产生变化,但它并没有_副作用_。 它们是一种<a href="https://en.wikipedia.org/wiki/Pure_function"><em>纯函数</em></a>。</p>
<p>我们的reducer将放在<code>src/reducers/index.tsx</code>文件里。 它的功能是保证增加操作会让感叹级别加1,减少操作则要将感叹级别减1,但是这个级别永远不能小于1。</p>
<pre><code class="language-typescript">// src/reducers/index.tsx
import { EnthusiasmAction } from '../actions';
import { StoreState } from '../types/index';
import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';
export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
switch (action.type) {
case INCREMENT_ENTHUSIASM:
return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
case DECREMENT_ENTHUSIASM:
return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
}
return state;
}
</code></pre>
<p>注意我们使用了_对象展开_(<code>...state</code>),当替换<code>enthusiasmLevel</code>时,它可以对状态进行浅拷贝。 将<code>enthusiasmLevel</code>属性放在末尾是十分关键的,否则它将被旧的状态覆盖。</p>
<p>你可能想要对reducer写一些测试。 因为reducers是纯函数,它们可以传入任意的数据。 针对每个输入,可以测试reducers生成的新的状态。 可以考虑使用Jest的<a href="https://facebook.github.io/jest/docs/en/expect.html#toequalvalue">toEqual</a>方法。</p>
<h3 id="创建容器"><a class="header" href="#创建容器">创建容器</a></h3>
<p>在使用Redux时,我们常常要创建组件和容器。 组件是数据无关的,且工作在表现层。 _容器_通常包裹组件及其使用的数据,用以显示和修改状态。 你可以在这里阅读更多关于这个概念的细节:<a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0">Dan Abramov写的_表现层的容器组件_</a>。</p>
<p>现在我们修改<code>src/components/Hello.tsx</code>,让它可以修改状态。 我们将添加两个可选的回调属性到<code>Props</code>,它们分别是<code>onIncrement</code>和<code>onDecrement</code>:</p>
<pre><code class="language-typescript">export interface Props {
name: string;
enthusiasmLevel?: number;
onIncrement?: () => void;
onDecrement?: () => void;
}
</code></pre>
<p>然后将这两个回调绑定到两个新按钮上,将按钮添加到我们的组件里。</p>
<pre><code class="language-typescript">function Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
<div>
<button onClick={onDecrement}>-</button>
<button onClick={onIncrement}>+</button>
</div>
</div>
);
}
</code></pre>
<p>通常情况下,我们应该给<code>onIncrement</code>和<code>onDecrement</code>写一些测试,它们是在各自的按钮被点击时调用。 试一试以便掌握编写测试的窍门。</p>
<p>现在我们的组件更新好了,可以把它放在一个容器里了。 让我们来创建一个文件<code>src/containers/Hello.tsx</code>,在开始的地方使用下列导入语句。</p>
<pre><code class="language-typescript">import Hello from '../components/Hello';
import * as actions from '../actions/';
import { StoreState } from '../types/index';
import { connect, Dispatch } from 'react-redux';
</code></pre>
<p>两个关键点是初始的<code>Hello</code>组件和react-redux的<code>connect</code>函数。 <code>connect</code>可以将我们的<code>Hello</code>组件转换成一个容器,通过以下两个函数:</p>
<ul>
<li><code>mapStateToProps</code>将当前store里的数据以我们的组件需要的形式传递到组件。</li>
<li><code>mapDispatchToProps</code>利用<code>dispatch</code>函数,创建回调props将actions送到store。</li>
</ul>
<p>回想一下,我们的应用包含两个属性:<code>languageName</code>和<code>enthusiasmLevel</code>。 我们的<code>Hello</code>组件,希望得到一个<code>name</code>和一个<code>enthusiasmLevel</code>。 <code>mapStateToProps</code>会从store得到相应的数据,如果需要的话将针对组件的props调整它。 下面让我们继续往下写。</p>
<pre><code class="language-typescript">export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {
return {
enthusiasmLevel,
name: languageName,
}
}
</code></pre>
<p>注意<code>mapStateToProps</code>仅创建了<code>Hello</code>组件需要的四个属性中的两个。 我们还想要传入<code>onIncrement</code>和<code>onDecrement</code>回调函数。 <code>mapDispatchToProps</code>是一个函数,它需要传入一个调度函数。 这个调度函数可以将actions传入store来触发更新,因此我们可以创建一对回调函数,它们会在需要的时候调用调度函数。</p>
<pre><code class="language-typescript">export function mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
return {
onIncrement: () => dispatch(actions.incrementEnthusiasm()),
onDecrement: () => dispatch(actions.decrementEnthusiasm()),
}
}
</code></pre>
<p>最后,我们可以调用<code>connect</code>了。 <code>connect</code>首先会接收<code>mapStateToProps</code>和<code>mapDispatchToProps</code>,然后返回另一个函数,我们用它来包裹我们的组件。 最终的容器是通过下面的代码定义的:</p>
<pre><code class="language-typescript">export default connect(mapStateToProps, mapDispatchToProps)(Hello);
</code></pre>
<p>现在,我们的文件应该是下面这个样子:</p>
<pre><code class="language-typescript">// src/containers/Hello.tsx
import Hello from '../components/Hello';
import * as actions from '../actions/';
import { StoreState } from '../types/index';
import { connect, Dispatch } from 'react-redux';
export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {
return {
enthusiasmLevel,
name: languageName,
}
}
export function mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
return {
onIncrement: () => dispatch(actions.incrementEnthusiasm()),
onDecrement: () => dispatch(actions.decrementEnthusiasm()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Hello);
</code></pre>
<h3 id="创建store"><a class="header" href="#创建store">创建store</a></h3>
<p>让我们回到<code>src/index.tsx</code>。 要把所有的东西合到一起,我们需要创建一个带初始状态的store,并用我们所有的reducers来设置它。</p>
<pre><code class="language-typescript">import { createStore } from 'redux';
import { enthusiasm } from './reducers/index';
import { StoreState } from './types/index';
const store = createStore<StoreState>(enthusiasm, {
enthusiasmLevel: 1,
languageName: 'TypeScript',
});
</code></pre>
<p><code>store</code>可能正如你想的那样,它是我们应用全局状态的核心store。</p>
<p>接下来,我们将要用<code>./src/containers/Hello</code>来包裹<code>./src/components/Hello</code>,然后使用react-redux的<code>Provider</code>将props与容器连通起来。 我们将导入它们:</p>
<pre><code class="language-typescript">import Hello from './containers/Hello';
import { Provider } from 'react-redux';
</code></pre>
<p>将<code>store</code>以<code>Provider</code>的属性形式传入:</p>
<pre><code class="language-typescript">ReactDOM.render(
<Provider store={store}>
<Hello />
</Provider>,
document.getElementById('root') as HTMLElement
);
</code></pre>
<p>注意,<code>Hello</code>不再需要props了,因为我们使用了<code>connect</code>函数为包裹起来的<code>Hello</code>组件的props适配了应用的状态。</p>
<h2 id="退出"><a class="header" href="#退出">退出</a></h2>
<p>如果你发现create-react-app使一些自定义设置变得困难,那么你就可以选择不使用它,使用你需要配置。 比如,你要添加一个Webpack插件,你就可以利用create-react-app提供的“eject”功能。</p>
<p>运行:</p>
<pre><code class="language-bash">npm run eject
</code></pre>
<p>这样就可以了!</p>
<p>你要注意,在运行eject前最好保存你的代码。 你不能撤销eject命令,因此退出操作是永久性的除非你从一个运行eject前的提交来恢复工程。</p>
<h2 id="下一步"><a class="header" href="#下一步">下一步</a></h2>
<p>create-react-app带有很多很棒的功能。 它们的大多数都在我们工程生成的<code>README.md</code>里面有记录,所以可以简单阅读一下。</p>
<p>如果你想学习更多关于Redux的知识,你可以前往<a href="http://redux.js.org/">官方站点</a>查看文档。 同样的,<a href="https://mobx.js.org/">MobX</a>官方站点。</p>
<p>如果你想要在某个时间点eject,你需要了解再多关于Webpack的知识。 你可以查看<a href="react-and-webpack.html">React & Webpack教程</a>。</p>
<p>有时候你需要路由功能。 已经有一些解决方案了,但是对于Redux工程来讲<a href="https://github.com/ReactTraining/react-router">react-router</a>是最流行的,并经常与<a href="https://github.com/reactjs/react-router-redux">react-router-redux</a>联合使用。</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../zh/tutorials/react-and-webpack.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../zh/tutorials/angular-2.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../zh/tutorials/react-and-webpack.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../zh/tutorials/angular-2.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../../elasticlunr.min.js"></script>
<script src="../../mark.min.js"></script>
<script src="../../searcher.js"></script>
<script src="../../clipboard.min.js"></script>
<script src="../../highlight.js"></script>
<script src="../../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>