├── .eslintignore ├── .npmrc ├── .gitignore ├── examples ├── declaration-files │ ├── 01-jquery │ │ ├── tsconfig.json │ │ └── src │ │ │ └── index.ts │ ├── 02-declare-var │ │ ├── tsconfig.json │ │ └── src │ │ │ └── index.ts │ ├── 08-declare-enum │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── index.ts │ │ │ └── Directions.d.ts │ ├── 12-interface │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ ├── 06-declare-function │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ ├── 07-declare-class │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── index.ts │ │ │ └── Animal.d.ts │ ├── 09-declare-namespace │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ ├── 04-declare-const-jquery │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── jQuery.d.ts │ │ │ └── index.ts │ ├── 05-declare-jquery-value │ │ ├── tsconfig.json │ │ └── src │ │ │ └── jQuery.d.ts │ ├── 11-declare-namespace-dot │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── jQuery.d.ts │ │ │ └── index.ts │ ├── 13-avoid-name-conflict │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ ├── 14-declaration-merging │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ ├── 23-merge-global-interface │ │ ├── tsconfig.json │ │ └── src │ │ │ └── index.ts │ ├── 10-declare-namespace-nesting │ │ ├── tsconfig.json │ │ └── src │ │ │ ├── jQuery.d.ts │ │ │ └── index.ts │ ├── 03-jquery-d-ts │ │ ├── src │ │ │ ├── index.ts │ │ │ └── jQuery.d.ts │ │ └── tsconfig.json │ ├── 25-declare-global │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── foo │ │ │ │ └── index.d.ts │ │ └── tsconfig.json │ ├── 28-triple-slash-directives │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── jquery-plugin │ │ │ │ └── index.d.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── package-lock.json │ ├── 30-auto-d-ts │ │ ├── src │ │ │ ├── bar │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── lib │ │ │ ├── bar │ │ │ │ ├── index.d.ts │ │ │ │ └── index.js │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── package-lock.json │ ├── 18-export-default │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── foo │ │ │ │ └── index.d.ts │ │ └── tsconfig.json │ ├── 22-export-as-namespace │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types │ │ │ └── foo │ │ │ └── index.d.ts │ ├── 24-merge-global-namespace │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ ├── types │ │ │ └── jquery-plugin │ │ │ │ └── index.d.ts │ │ ├── package.json │ │ └── package-lock.json │ ├── 21-export-equal │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── foo │ │ │ │ └── index.d.ts │ │ └── tsconfig.json │ ├── 20-export-default-enum │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── foo │ │ │ │ └── index.d.ts │ │ └── tsconfig.json │ ├── 19-export-default-enum-error │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── foo │ │ │ │ └── index.d.ts │ │ └── tsconfig.json │ ├── 17-export-namespace │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── foo │ │ │ │ └── index.d.ts │ │ └── tsconfig.json │ ├── 29-triple-slash-directives-global │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── node-plugin │ │ │ │ └── index.d.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── package-lock.json │ ├── 26-declare-module │ │ ├── src │ │ │ └── index.ts │ │ ├── types │ │ │ └── moment-plugin │ │ │ │ └── index.d.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── package-lock.json │ ├── 27-multiple-declare-module │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── types │ │ │ └── foo-bar.d.ts │ ├── 15-export │ │ ├── tsconfig.json │ │ ├── types │ │ │ └── foo │ │ │ │ └── index.d.ts │ │ └── src │ │ │ └── index.ts │ └── 16-declare-and-export │ │ ├── tsconfig.json │ │ ├── src │ │ └── index.ts │ │ └── types │ │ └── foo │ │ └── index.d.ts └── compiler-options │ ├── 01-allowJs │ ├── false │ │ ├── src │ │ │ ├── foo.js │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ ├── lib │ │ │ └── index.js │ │ └── package.json │ └── true │ │ ├── src │ │ ├── foo.js │ │ └── index.ts │ │ ├── lib │ │ ├── foo.js │ │ └── index.js │ │ ├── tsconfig.json │ │ └── package.json │ └── 02-allowSyntheticDefaultImports │ ├── false │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── package.json │ └── package-lock.json │ └── true │ ├── src │ └── index.ts │ ├── tsconfig.json │ ├── package.json │ └── package-lock.json ├── favicon.png ├── pandoc-cover.jpg ├── assets ├── alipay.jpg ├── join-qq.jpg ├── wechat.jpg ├── join-wechat.jpg ├── why-typescript-tip.png ├── vscode-output-eslint.png ├── why-typescript-bugs.png ├── what-is-typescript-vue.png ├── why-typescript-airbnb.png ├── what-is-typescript-react.png └── what-is-typescript-vscode.png ├── TypeScript 入门教程.epub ├── TypeScript 入门教程 2020.epub ├── .lintmdrc ├── pandoc-metadata.txt ├── .prettierignore ├── introduction ├── README.md ├── get-typescript.md ├── why-typescript.md ├── hello-typescript.md └── what-is-typescript.md ├── engineering ├── README.md ├── compiler-options.md └── lint.md ├── advanced ├── README.md ├── type-aliases.md ├── string-literal-types.md ├── tuple.md ├── declaration-merging.md ├── further-reading.md ├── class-and-interfaces.md ├── generics.md ├── enum.md ├── decorator.md └── class.md ├── basics ├── README.md ├── type-inference.md ├── any.md ├── union-types.md ├── built-in-objects.md ├── type-of-array.md ├── primitive-data-types.md ├── type-of-object-interfaces.md ├── type-of-function.md ├── type-assertion.md └── declaration-files.md ├── .editorconfig ├── .vscode └── settings.json ├── .github ├── FUNDING.yml └── workflows │ └── gh-pages.yml ├── .eslintrc.js ├── pandoc-list.txt ├── thanks └── README.md ├── .prettierrc.js ├── package.json ├── README.md └── pagic.config.tsx /.eslintignore: -------------------------------------------------------------------------------- 1 | *.d.ts 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /examples/declaration-files/01-jquery/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/02-declare-var/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/08-declare-enum/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/12-interface/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/06-declare-function/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/07-declare-class/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/09-declare-namespace/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/04-declare-const-jquery/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/05-declare-jquery-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/11-declare-namespace-dot/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/13-avoid-name-conflict/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/14-declaration-merging/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/23-merge-global-interface/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /examples/declaration-files/10-declare-namespace-nesting/tsconfig.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/favicon.png -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/false/src/foo.js: -------------------------------------------------------------------------------- 1 | const foo = 1; 2 | export default foo; 3 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/true/src/foo.js: -------------------------------------------------------------------------------- 1 | const foo = 1; 2 | export default foo; 3 | -------------------------------------------------------------------------------- /examples/declaration-files/03-jquery-d-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | jQuery('#foo'); 4 | -------------------------------------------------------------------------------- /pandoc-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/pandoc-cover.jpg -------------------------------------------------------------------------------- /assets/alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/alipay.jpg -------------------------------------------------------------------------------- /assets/join-qq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/join-qq.jpg -------------------------------------------------------------------------------- /assets/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/wechat.jpg -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/false/src/index.ts: -------------------------------------------------------------------------------- 1 | import foo from './foo'; 2 | console.log(foo); 3 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/true/src/index.ts: -------------------------------------------------------------------------------- 1 | import foo from './foo'; 2 | console.log(foo); 3 | -------------------------------------------------------------------------------- /examples/compiler-options/02-allowSyntheticDefaultImports/false/src/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | -------------------------------------------------------------------------------- /examples/compiler-options/02-allowSyntheticDefaultImports/true/src/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | -------------------------------------------------------------------------------- /examples/declaration-files/25-declare-global/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | 'foo'.prependHello(); 4 | -------------------------------------------------------------------------------- /examples/declaration-files/28-triple-slash-directives/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | foo({}); 4 | -------------------------------------------------------------------------------- /TypeScript 入门教程.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/TypeScript 入门教程.epub -------------------------------------------------------------------------------- /examples/declaration-files/01-jquery/src/index.ts: -------------------------------------------------------------------------------- 1 | jQuery('#foo'); 2 | // ERROR: Cannot find name 'jQuery'. 3 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/src/bar/index.ts: -------------------------------------------------------------------------------- 1 | export function bar() { 2 | return 'bar'; 3 | } 4 | -------------------------------------------------------------------------------- /assets/join-wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/join-wechat.jpg -------------------------------------------------------------------------------- /examples/declaration-files/07-declare-class/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | let cat = new Animal('Tom'); 4 | -------------------------------------------------------------------------------- /TypeScript 入门教程 2020.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/TypeScript 入门教程 2020.epub -------------------------------------------------------------------------------- /examples/declaration-files/03-jquery-d-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": ["src/index.ts", "src/jQuery.d.ts"] 3 | } 4 | -------------------------------------------------------------------------------- /assets/why-typescript-tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/why-typescript-tip.png -------------------------------------------------------------------------------- /examples/declaration-files/18-export-default/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import foo from 'foo'; 4 | 5 | foo(); 6 | -------------------------------------------------------------------------------- /examples/declaration-files/22-export-as-namespace/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | foo(); 4 | console.log(foo.bar); 5 | -------------------------------------------------------------------------------- /assets/vscode-output-eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/vscode-output-eslint.png -------------------------------------------------------------------------------- /assets/why-typescript-bugs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/why-typescript-bugs.png -------------------------------------------------------------------------------- /examples/declaration-files/24-merge-global-namespace/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | jQuery.foo({ 4 | bar: '' 5 | }); 6 | -------------------------------------------------------------------------------- /.lintmdrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": [], 3 | "rules": { 4 | "no-trailing-punctuation": 0, 5 | "no-long-code": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/what-is-typescript-vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/what-is-typescript-vue.png -------------------------------------------------------------------------------- /assets/why-typescript-airbnb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/why-typescript-airbnb.png -------------------------------------------------------------------------------- /examples/declaration-files/03-jquery-d-ts/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | declare var jQuery: (selector: string) => any; 4 | -------------------------------------------------------------------------------- /examples/declaration-files/21-export-equal/src/index.ts: -------------------------------------------------------------------------------- 1 | // 整体导入 2 | import foo = require('foo'); 3 | // 单个导入 4 | import bar = foo.bar; 5 | -------------------------------------------------------------------------------- /assets/what-is-typescript-react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/what-is-typescript-react.png -------------------------------------------------------------------------------- /assets/what-is-typescript-vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeMaster0099/typesecripts_sample/HEAD/assets/what-is-typescript-vscode.png -------------------------------------------------------------------------------- /examples/declaration-files/18-export-default/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | export default function foo(): string; 4 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/lib/bar/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare function bar(): string; 2 | //# sourceMappingURL=index.d.ts.map 3 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/true/lib/foo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var foo = 1; 4 | exports["default"] = foo; 5 | -------------------------------------------------------------------------------- /examples/declaration-files/04-declare-const-jquery/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | declare const jQuery: (selector: string) => any; 4 | -------------------------------------------------------------------------------- /examples/declaration-files/14-declaration-merging/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | jQuery('#foo'); 4 | jQuery.ajax('/api/get_something'); 5 | -------------------------------------------------------------------------------- /examples/declaration-files/20-export-default-enum/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import foo from 'foo'; 4 | 5 | console.log(foo.Down); 6 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bar'; 2 | 3 | export default function foo() { 4 | return 'foo'; 5 | } 6 | -------------------------------------------------------------------------------- /examples/declaration-files/19-export-default-enum-error/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import foo from 'foo'; 4 | 5 | console.log(foo.Down); 6 | -------------------------------------------------------------------------------- /examples/declaration-files/23-merge-global-interface/src/index.ts: -------------------------------------------------------------------------------- 1 | interface String { 2 | prependHello(): string; 3 | } 4 | 5 | 'foo'.prependHello(); 6 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/true/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "outDir": "lib" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/false/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "outDir": "lib" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/declaration-files/06-declare-function/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | jQuery('#foo'); 4 | jQuery(function() { 5 | alert('Dom Ready!'); 6 | }); 7 | -------------------------------------------------------------------------------- /examples/declaration-files/17-export-namespace/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import { foo } from 'foo'; 4 | 5 | console.log(foo.name); 6 | foo.bar.baz(); 7 | -------------------------------------------------------------------------------- /examples/declaration-files/29-triple-slash-directives-global/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import { foo } from 'node-plugin'; 4 | 5 | foo(global.process); 6 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './bar'; 2 | export default function foo(): string; 3 | //# sourceMappingURL=index.d.ts.map 4 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/false/lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var foo_1 = require("./foo"); 4 | console.log(foo_1["default"]); 5 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/true/lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var foo_1 = require("./foo"); 4 | console.log(foo_1["default"]); 5 | -------------------------------------------------------------------------------- /examples/declaration-files/02-declare-var/src/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-var 2 | declare var jQuery: (selector: string) => any; 3 | 4 | jQuery('#foo'); 5 | -------------------------------------------------------------------------------- /examples/declaration-files/08-declare-enum/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; 4 | -------------------------------------------------------------------------------- /examples/declaration-files/26-declare-module/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import * as moment from 'moment'; 4 | import 'moment-plugin'; 5 | 6 | moment.foo(); 7 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/lib/bar/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | function bar() { 4 | return 'bar'; 5 | } 6 | exports.bar = bar; 7 | -------------------------------------------------------------------------------- /examples/declaration-files/11-declare-namespace-dot/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | declare namespace jQuery.fn { 4 | function extend(object: any): void; 5 | } 6 | -------------------------------------------------------------------------------- /examples/declaration-files/08-declare-enum/src/Directions.d.ts: -------------------------------------------------------------------------------- 1 | // src/Directions.d.ts 2 | 3 | declare enum Directions { 4 | Up, 5 | Down, 6 | Left, 7 | Right 8 | } 9 | -------------------------------------------------------------------------------- /examples/declaration-files/27-multiple-declare-module/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import { Foo } from 'foo'; 4 | import * as bar from 'bar'; 5 | 6 | let f: Foo; 7 | bar.bar(); 8 | -------------------------------------------------------------------------------- /pandoc-metadata.txt: -------------------------------------------------------------------------------- 1 | --- 2 | title: TypeScript 入门教程 3 | author: xcatliu 4 | description: 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript 5 | language: zh-CN 6 | cover-image: pandoc-cover.jpg 7 | ... 8 | -------------------------------------------------------------------------------- /examples/declaration-files/07-declare-class/src/Animal.d.ts: -------------------------------------------------------------------------------- 1 | // src/Animal.d.ts 2 | 3 | declare class Animal { 4 | name: string; 5 | constructor(name: string); 6 | sayHi(): string; 7 | } 8 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "lib", 5 | "declaration": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/compiler-options/02-allowSyntheticDefaultImports/false/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": false, 4 | "outDir": "lib" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/compiler-options/02-allowSyntheticDefaultImports/true/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "outDir": "lib" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/declaration-files/06-declare-function/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | declare function jQuery(selector: string): any; 4 | declare function jQuery(domReadyCallback: () => any): any; 5 | -------------------------------------------------------------------------------- /examples/declaration-files/21-export-equal/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | export = foo; 4 | 5 | declare function foo(): string; 6 | declare namespace foo { 7 | const bar: number; 8 | } 9 | -------------------------------------------------------------------------------- /examples/declaration-files/29-triple-slash-directives-global/types/node-plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/node-plugin/index.d.ts 2 | 3 | /// 4 | 5 | export function foo(p: NodeJS.Process): string; 6 | -------------------------------------------------------------------------------- /examples/declaration-files/25-declare-global/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | declare global { 4 | interface String { 5 | prependHello(): string; 6 | } 7 | } 8 | 9 | export {}; 10 | -------------------------------------------------------------------------------- /examples/declaration-files/09-declare-namespace/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | jQuery.ajax('/api/get_something'); 4 | console.log(jQuery.version); 5 | const e = new jQuery.Event(); 6 | e.blur(jQuery.EventType.CustomClick); 7 | -------------------------------------------------------------------------------- /examples/declaration-files/17-export-namespace/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | export namespace foo { 4 | const name: string; 5 | namespace bar { 6 | function baz(): string; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/declaration-files/28-triple-slash-directives/types/jquery-plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/jquery-plugin/index.d.ts 2 | 3 | /// 4 | 5 | declare function foo(options: JQuery.AjaxSettings): string; 6 | -------------------------------------------------------------------------------- /examples/declaration-files/05-declare-jquery-value/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | declare const jQuery = function(selector) { 2 | return document.querySelector(selector); 3 | }; 4 | // ERROR: An implementation cannot be declared in ambient contexts. 5 | -------------------------------------------------------------------------------- /examples/declaration-files/14-declaration-merging/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | declare function jQuery(selector: string): any; 4 | declare namespace jQuery { 5 | function ajax(url: string, settings?: any): void; 6 | } 7 | -------------------------------------------------------------------------------- /examples/declaration-files/20-export-default-enum/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | export default Directions; 4 | 5 | declare enum Directions { 6 | Up, 7 | Down, 8 | Left, 9 | Right 10 | } 11 | -------------------------------------------------------------------------------- /examples/declaration-files/15-export/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/26-declare-module/types/moment-plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/moment-plugin/index.d.ts 2 | 3 | import * as moment from 'moment'; 4 | 5 | declare module 'moment' { 6 | export function foo(): moment.CalendarKey; 7 | } 8 | -------------------------------------------------------------------------------- /examples/declaration-files/12-interface/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | let settings: AjaxSettings = { 4 | method: 'POST', 5 | data: { 6 | name: 'foo' 7 | } 8 | }; 9 | jQuery.ajax('/api/post_something', settings); 10 | -------------------------------------------------------------------------------- /examples/declaration-files/18-export-default/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/19-export-default-enum-error/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | export default enum Directions { 4 | // ERROR: Expression expected. 5 | Up, 6 | Down, 7 | Left, 8 | Right 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/21-export-equal/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/25-declare-global/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/26-declare-module/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/16-declare-and-export/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/17-export-namespace/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/20-export-default-enum/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/22-export-as-namespace/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/11-declare-namespace-dot/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | jQuery.fn.extend({ 4 | check: function() { 5 | return this.each(function() { 6 | this.checked = true; 7 | }); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /examples/declaration-files/19-export-default-enum-error/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/22-export-as-namespace/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | export as namespace foo; 4 | export = foo; 5 | 6 | declare function foo(): string; 7 | declare namespace foo { 8 | const bar: number; 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/24-merge-global-namespace/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/27-multiple-declare-module/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/28-triple-slash-directives/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/29-triple-slash-directives-global/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "baseUrl": "./", 5 | "paths": { 6 | "*": ["types/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | *.png 3 | *.epub 4 | 5 | node_modules 6 | 7 | assets 8 | 9 | .DS_Store 10 | .editorconfig 11 | .eslintignore 12 | .gitignore 13 | .lintmdrc 14 | .prettierignore 15 | .npmrc 16 | 17 | pnpm-lock.yaml 18 | 19 | examples 20 | dist 21 | -------------------------------------------------------------------------------- /examples/declaration-files/13-avoid-name-conflict/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | let settings: jQuery.AjaxSettings = { 4 | method: 'POST', 5 | data: { 6 | name: 'foo' 7 | } 8 | }; 9 | jQuery.ajax('/api/post_something', settings); 10 | -------------------------------------------------------------------------------- /introduction/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | prev: README.md 3 | --- 4 | 5 | # 简介 6 | 7 | 本部分介绍了在学习 TypeScript 之前需要了解的知识,具体内容包括: 8 | 9 | - [什么是 TypeScript](what-is-typescript.md) 10 | - [安装 TypeScript](get-typescript.md) 11 | - [Hello TypeScript](hello-typescript.md) 12 | -------------------------------------------------------------------------------- /examples/declaration-files/10-declare-namespace-nesting/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | declare namespace jQuery { 4 | function ajax(url: string, settings?: any): void; 5 | namespace fn { 6 | function extend(object: any): void; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /engineering/README.md: -------------------------------------------------------------------------------- 1 | # 工程 2 | 3 | 掌握了 TypeScript 的语法就像学会了砌墙的工艺。 4 | 5 | 我们学习 TypeScript 的目的不是为了造一间小茅屋,而是为了造高楼大厦,这也正是 TypeScript 的类型系统带来的优势。 6 | 7 | 那么一项大工程应该如何开展呢?本部分的内容就会介绍 TypeScript 工程化的最佳实践,具体内容包括: 8 | 9 | - [代码检查](lint.md) 10 | - [编译选项](compiler-options.md) 11 | -------------------------------------------------------------------------------- /examples/declaration-files/12-interface/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | interface AjaxSettings { 4 | method?: 'GET' | 'POST'; 5 | data?: any; 6 | } 7 | declare namespace jQuery { 8 | function ajax(url: string, settings?: AjaxSettings): void; 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/27-multiple-declare-module/types/foo-bar.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo-bar.d.ts 2 | 3 | declare module 'foo' { 4 | export interface Foo { 5 | foo: string; 6 | } 7 | } 8 | 9 | declare module 'bar' { 10 | export function bar(): string; 11 | } 12 | -------------------------------------------------------------------------------- /examples/declaration-files/10-declare-namespace-nesting/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | jQuery.ajax('/api/get_something'); 4 | jQuery.fn.extend({ 5 | check: function() { 6 | return this.each(function() { 7 | this.checked = true; 8 | }); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /examples/declaration-files/13-avoid-name-conflict/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | declare namespace jQuery { 4 | interface AjaxSettings { 5 | method?: 'GET' | 'POST'; 6 | data?: any; 7 | } 8 | function ajax(url: string, settings?: AjaxSettings): void; 9 | } 10 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | exports.__esModule = true; 6 | __export(require("./bar")); 7 | function foo() { 8 | return 'foo'; 9 | } 10 | exports["default"] = foo; 11 | -------------------------------------------------------------------------------- /examples/declaration-files/04-declare-const-jquery/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | jQuery('#foo'); 4 | // 使用 declare const 定义的 jQuery 类型,禁止修改这个全局变量 5 | jQuery = function(selector) { 6 | return document.querySelector(selector); 7 | }; 8 | // ERROR: Cannot assign to 'jQuery' because it is a constant or a read-only property. 9 | -------------------------------------------------------------------------------- /examples/declaration-files/24-merge-global-namespace/types/jquery-plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/jquery-plugin/index.d.ts 2 | 3 | declare namespace JQuery { 4 | interface CustomOptions { 5 | bar: string; 6 | } 7 | } 8 | 9 | interface JQueryStatic { 10 | foo(options: JQuery.CustomOptions): string; 11 | } 12 | -------------------------------------------------------------------------------- /advanced/README.md: -------------------------------------------------------------------------------- 1 | # 进阶 2 | 3 | 本部分介绍一些高级的类型与技术,具体内容包括: 4 | 5 | - [类型别名](type-aliases.md) 6 | - [字符串字面量类型](string-literal-types.md) 7 | - [元组](tuple.md) 8 | - [枚举](enum.md) 9 | - [类](class.md) 10 | - [类与接口](class-and-interfaces.md) 11 | - [泛型](generics.md) 12 | - [声明合并](declaration-merging.md) 13 | - [扩展阅读](further-reading.md) 14 | -------------------------------------------------------------------------------- /examples/declaration-files/09-declare-namespace/src/jQuery.d.ts: -------------------------------------------------------------------------------- 1 | // src/jQuery.d.ts 2 | 3 | declare namespace jQuery { 4 | function ajax(url: string, settings?: any): void; 5 | const version: number; 6 | class Event { 7 | blur(eventType: EventType): void; 8 | } 9 | enum EventType { 10 | CustomClick 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/declaration-files/15-export/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | export const name: string; 4 | export function getName(): string; 5 | export class Animal { 6 | constructor(name: string); 7 | sayHi(): string; 8 | } 9 | export enum Directions { 10 | Up, 11 | Down, 12 | Left, 13 | Right 14 | } 15 | export interface Options { 16 | data: any; 17 | } 18 | -------------------------------------------------------------------------------- /examples/declaration-files/15-export/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import { name, getName, Animal, Directions, Options } from 'foo'; 4 | 5 | console.log(name); 6 | let myName = getName(); 7 | let cat = new Animal('Tom'); 8 | let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; 9 | let options: Options = { 10 | data: { 11 | name: 'foo' 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /examples/declaration-files/16-declare-and-export/src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import { name, getName, Animal, Directions, Options } from 'foo'; 4 | 5 | console.log(name); 6 | let myName = getName(); 7 | let cat = new Animal('Tom'); 8 | let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; 9 | let options: Options = { 10 | data: { 11 | name: 'foo' 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /examples/declaration-files/26-declare-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "26-declare-module", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "moment": "^2.24.0" 13 | }, 14 | "devDependencies": {} 15 | } 16 | -------------------------------------------------------------------------------- /basics/README.md: -------------------------------------------------------------------------------- 1 | # 基础 2 | 3 | 本部分介绍了 TypeScript 中的常用类型和一些基本概念,旨在让大家对 TypeScript 有个初步的理解。具体内容包括: 4 | 5 | - [原始数据类型](primitive-data-types.md) 6 | - [任意值](any.md) 7 | - [类型推论](type-inference.md) 8 | - [联合类型](union-types.md) 9 | - [对象的类型——接口](type-of-object-interfaces.md) 10 | - [数组的类型](type-of-array.md) 11 | - [函数的类型](type-of-function.md) 12 | - [类型断言](type-assertion.md) 13 | - [声明文件](declaration-files.md) 14 | - [内置对象](built-in-objects.md) 15 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/false/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-allow-js-false", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "tsc -w", 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": {}, 14 | "devDependencies": {} 15 | } 16 | -------------------------------------------------------------------------------- /examples/compiler-options/01-allowJs/true/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-allow-js-true", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "tsc -w", 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": {}, 14 | "devDependencies": {} 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*] 14 | charset = utf-8 15 | 16 | # 4 space indentation 17 | # [*] 18 | # indent_style = space 19 | # indent_size = 4 20 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "30-auto-d-ts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "start": "tsc -w", 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "typescript": "^3.4.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/declaration-files/29-triple-slash-directives-global/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "29-triple-slash-directives-global", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@types/node": "^12.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/declaration-files/16-declare-and-export/types/foo/index.d.ts: -------------------------------------------------------------------------------- 1 | // types/foo/index.d.ts 2 | 3 | declare const name: string; 4 | declare function getName(): string; 5 | declare class Animal { 6 | constructor(name: string); 7 | sayHi(): string; 8 | } 9 | declare enum Directions { 10 | Up, 11 | Down, 12 | Left, 13 | Right 14 | } 15 | interface Options { 16 | data: any; 17 | } 18 | 19 | export { name, getName, Animal, Directions, Options }; 20 | -------------------------------------------------------------------------------- /examples/declaration-files/24-merge-global-namespace/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "24-merge-global-namespace", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "jquery": "^3.5.0" 13 | }, 14 | "devDependencies": { 15 | "@types/jquery": "^3.3.29" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/declaration-files/28-triple-slash-directives/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "28-triple-slash-directives", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "jquery": "^3.5.0" 13 | }, 14 | "devDependencies": { 15 | "@types/jquery": "^3.3.29" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/declaration-files/30-auto-d-ts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "30-auto-d-ts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "typescript": { 8 | "version": "3.4.5", 9 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", 10 | "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/declaration-files/26-declare-module/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "26-declare-module", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "moment": { 8 | "version": "2.24.0", 9 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", 10 | "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/declaration-files/29-triple-slash-directives-global/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "29-triple-slash-directives-global", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "12.0.1", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.1.tgz", 10 | "integrity": "sha512-7sy7DKVJrCTbaAERJZq/CU12bzdmpjRr321/Ne9QmzhB3iZ//L16Cizcni5hHNbANxDbxwMb9EFoWkM8KPkp0A==", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/compiler-options/02-allowSyntheticDefaultImports/false/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02-allow-synthetic-default-imports-false", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "tsc -w", 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "react": "^16.12.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^16.9.14" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/compiler-options/02-allowSyntheticDefaultImports/true/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02-allow-synthetic-default-imports-false", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "tsc -w", 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "react": "^16.12.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^16.9.14" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n", 3 | "editor.tabSize": 2, 4 | "editor.formatOnSave": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": "explicit" 9 | }, 10 | "typescript.tsdk": "node_modules/typescript/lib", 11 | "[json]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[jsonc]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: xcatliu 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://github.com/xcatliu/buy-me-a-coffee 13 | -------------------------------------------------------------------------------- /advanced/type-aliases.md: -------------------------------------------------------------------------------- 1 | # 类型别名 2 | 3 | 类型别名用来给一个类型起个新名字。 4 | 5 | ## 简单的例子 6 | 7 | ```ts 8 | type Name = string; 9 | type NameResolver = () => string; 10 | type NameOrResolver = Name | NameResolver; 11 | function getName(n: NameOrResolver): Name { 12 | if (typeof n === 'string') { 13 | return n; 14 | } else { 15 | return n(); 16 | } 17 | } 18 | ``` 19 | 20 | 上例中,我们使用 `type` 创建类型别名。 21 | 22 | 类型别名常用于联合类型。 23 | 24 | ## 参考 25 | 26 | - [Advanced Types # Type Aliases](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#类型别名)) 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['alloy', 'alloy/react', 'alloy/typescript'], 3 | env: { 4 | // Your environments (which contains several predefined global variables) 5 | // 6 | // browser: true, 7 | // mocha: true, 8 | // jquery: true 9 | }, 10 | globals: { 11 | // Your global variables (setting to false means it's not allowed to be reassigned) 12 | // 13 | // myGlobal: false 14 | }, 15 | rules: { 16 | // Customize your rules 17 | 'no-undef': 'off', 18 | 'prefer-arrow-callback': 'off', 19 | '@typescript-eslint/no-invalid-this': 'off', 20 | '@typescript-eslint/no-require-imports': 'off', 21 | '@typescript-eslint/method-signature-style': 'off', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /pandoc-list.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | introduction/README.md 3 | introduction/what-is-typescript.md 4 | introduction/get-typescript.md 5 | introduction/hello-typescript.md 6 | basics/README.md 7 | basics/primitive-data-types.md 8 | basics/any.md 9 | basics/type-inference.md 10 | basics/union-types.md 11 | basics/type-of-object-interfaces.md 12 | basics/type-of-array.md 13 | basics/type-of-function.md 14 | basics/type-assertion.md 15 | basics/declaration-files.md 16 | basics/built-in-objects.md 17 | advanced/README.md 18 | advanced/type-aliases.md 19 | advanced/string-literal-types.md 20 | advanced/tuple.md 21 | advanced/enum.md 22 | advanced/class.md 23 | advanced/class-and-interfaces.md 24 | advanced/generics.md 25 | advanced/declaration-merging.md 26 | advanced/further-reading.md 27 | engineering/README.md 28 | engineering/lint.md 29 | engineering/compiler-options.md 30 | thanks/README.md 31 | -------------------------------------------------------------------------------- /advanced/string-literal-types.md: -------------------------------------------------------------------------------- 1 | # 字符串字面量类型 2 | 3 | 字符串字面量类型用来约束取值只能是某几个字符串中的一个。 4 | 5 | ## 简单的例子 6 | 7 | ```ts 8 | type EventNames = 'click' | 'scroll' | 'mousemove'; 9 | function handleEvent(ele: Element, event: EventNames) { 10 | // do something 11 | } 12 | 13 | handleEvent(document.getElementById('hello'), 'scroll'); // 没问题 14 | handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick' 15 | 16 | // index.ts(7,47): error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'. 17 | ``` 18 | 19 | 上例中,我们使用 `type` 定了一个字符串字面量类型 `EventNames`,它只能取三种字符串中的一种。 20 | 21 | 注意,**类型别名与字符串字面量类型都是使用 `type` 进行定义。** 22 | 23 | ## 参考 24 | 25 | - [Advanced Types # Type Aliases](http://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#字符串字面量类型)) 26 | -------------------------------------------------------------------------------- /thanks/README.md: -------------------------------------------------------------------------------- 1 | # 感谢 2 | 3 | - 感谢[创造和维护 TypeScript 的人们](https://github.com/Microsoft/TypeScript/graphs/contributors),给我们带来了如此优秀的工具 4 | - 感谢 [@zhongsp](https://github.com/zhongsp/) 对[官方手册的翻译](https://zhongsp.gitbooks.io/typescript-handbook/content/index.html),本书参考了大量他的翻译,能一直坚持跟进非常不容易 5 | - 感谢 [@阮一峰](http://www.ruanyifeng.com/home.html) 老师的 [ECMAScript 6 入门](http://es6.ruanyifeng.com/),本书引用了多处 ES6 的知识 6 | 7 | 最后,感谢你阅读完本书,希望你会有所收获。 8 | 9 | ## 下一步 10 | 11 | - 在 [GitHub](https://github.com/xcatliu/typescript-tutorial) 上关注本书 12 | - 阅读[官方手册](http://www.typescriptlang.org/docs/handbook/basic-types.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/))巩固知识 13 | - 阅读 [Project Configuration](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html)) 学习如何配置 TypeScript 工程 14 | - 查看[官方示例](http://www.typescriptlang.org/samples/index.html),学习真实项目 15 | -------------------------------------------------------------------------------- /basics/type-inference.md: -------------------------------------------------------------------------------- 1 | # 类型推论 2 | 3 | 如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。 4 | 5 | ## 什么是类型推论 6 | 7 | 以下代码虽然没有指定类型,但是会在编译的时候报错: 8 | 9 | ```ts 10 | let myFavoriteNumber = 'seven'; 11 | myFavoriteNumber = 7; 12 | 13 | // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'. 14 | ``` 15 | 16 | 事实上,它等价于: 17 | 18 | ```ts 19 | let myFavoriteNumber: string = 'seven'; 20 | myFavoriteNumber = 7; 21 | 22 | // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'. 23 | ``` 24 | 25 | TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。 26 | 27 | **如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 `any` 类型而完全不被类型检查**: 28 | 29 | ```ts 30 | let myFavoriteNumber; 31 | myFavoriteNumber = 'seven'; 32 | myFavoriteNumber = 7; 33 | ``` 34 | 35 | ## 参考 36 | 37 | - [Type Inference](http://www.typescriptlang.org/docs/handbook/type-inference.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Inference.html)) 38 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: gh-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Setup deno 17 | uses: denolib/setup-deno@v2 18 | with: 19 | deno-version: v1.34.1 20 | 21 | - name: Build gh-pages 22 | run: | 23 | curl -fsSL https://deno.land/x/install/install.sh | sh 24 | export DENO_INSTALL="/home/runner/.deno" 25 | export PATH="$DENO_INSTALL/bin:$PATH" 26 | deno --version 27 | deno install --unstable --allow-read --allow-write --allow-net --allow-run -n pagic https://deno.land/x/pagic@v1.6.3/mod.ts 28 | pagic build 29 | 30 | - name: Deploy gh-pages 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: ./dist 35 | cname: ts.xcatliu.com 36 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // .prettierrc.js 2 | module.exports = { 3 | // 一行最多 120 字符 4 | printWidth: 120, 5 | // 使用 2 个空格缩进 6 | tabWidth: 2, 7 | // 不使用缩进符,而使用空格 8 | useTabs: false, 9 | // 行尾需要有分号 10 | semi: true, 11 | // 使用单引号 12 | singleQuote: true, 13 | // 对象的 key 仅在必要时用引号 14 | quoteProps: 'as-needed', 15 | // jsx 不使用单引号,而使用双引号 16 | jsxSingleQuote: false, 17 | // 末尾需要有逗号 18 | trailingComma: 'all', 19 | // 大括号内的首尾需要空格 20 | bracketSpacing: true, 21 | // jsx 标签的反尖括号需要换行 22 | bracketSameLine: false, 23 | // 箭头函数,只有一个参数的时候,也需要括号 24 | arrowParens: 'always', 25 | // 每个文件格式化的范围是文件的全部内容 26 | rangeStart: 0, 27 | rangeEnd: Infinity, 28 | // 不需要写文件开头的 @prettier 29 | requirePragma: false, 30 | // 不需要自动在文件开头插入 @prettier 31 | insertPragma: false, 32 | // 使用默认的折行标准 33 | proseWrap: 'preserve', 34 | // 根据显示样式决定 html 要不要折行 35 | htmlWhitespaceSensitivity: 'css', 36 | // vue 文件中的 script 和 style 内不用缩进 37 | vueIndentScriptAndStyle: false, 38 | // 换行符使用 lf 39 | endOfLine: 'lf', 40 | // 格式化嵌入的内容 41 | embeddedLanguageFormatting: 'auto', 42 | // html, vue, jsx 中每个属性占一行 43 | singleAttributePerLine: false, 44 | }; 45 | -------------------------------------------------------------------------------- /examples/declaration-files/24-merge-global-namespace/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "24-merge-global-namespace", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/jquery": { 8 | "version": "3.3.29", 9 | "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz", 10 | "integrity": "sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ==", 11 | "dev": true, 12 | "requires": { 13 | "@types/sizzle": "*" 14 | } 15 | }, 16 | "@types/sizzle": { 17 | "version": "2.3.2", 18 | "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", 19 | "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", 20 | "dev": true 21 | }, 22 | "jquery": { 23 | "version": "3.5.0", 24 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", 25 | "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/declaration-files/28-triple-slash-directives/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "28-triple-slash-directives", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/jquery": { 8 | "version": "3.3.29", 9 | "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz", 10 | "integrity": "sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ==", 11 | "dev": true, 12 | "requires": { 13 | "@types/sizzle": "*" 14 | } 15 | }, 16 | "@types/sizzle": { 17 | "version": "2.3.2", 18 | "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", 19 | "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", 20 | "dev": true 21 | }, 22 | "jquery": { 23 | "version": "3.5.0", 24 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", 25 | "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /advanced/tuple.md: -------------------------------------------------------------------------------- 1 | # 元组 2 | 3 | 数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。 4 | 5 | 元组起源于函数编程语言(如 F#),这些语言中会频繁使用元组。 6 | 7 | ## 简单的例子 8 | 9 | 定义一对值分别为 `string` 和 `number` 的元组: 10 | 11 | ```ts 12 | let tom: [string, number] = ['Tom', 25]; 13 | ``` 14 | 15 | 当赋值或访问一个已知索引的元素时,会得到正确的类型: 16 | 17 | ```ts 18 | let tom: [string, number]; 19 | tom[0] = 'Tom'; 20 | tom[1] = 25; 21 | 22 | tom[0].slice(1); 23 | tom[1].toFixed(2); 24 | ``` 25 | 26 | 也可以只赋值其中一项: 27 | 28 | ```ts 29 | let tom: [string, number]; 30 | tom[0] = 'Tom'; 31 | ``` 32 | 33 | 但是当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。 34 | 35 | ```ts 36 | let tom: [string, number]; 37 | tom = ['Tom', 25]; 38 | ``` 39 | 40 | ```ts 41 | let tom: [string, number]; 42 | tom = ['Tom']; 43 | 44 | // Property '1' is missing in type '[string]' but required in type '[string, number]'. 45 | ``` 46 | 47 | ## 越界的元素 48 | 49 | 当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型: 50 | 51 | ```ts 52 | let tom: [string, number]; 53 | tom = ['Tom', 25]; 54 | tom.push('male'); 55 | tom.push(true); 56 | 57 | // Argument of type 'true' is not assignable to parameter of type 'string | number'. 58 | ``` 59 | 60 | ## 参考 61 | 62 | - [Basic Types # Tuple](http://www.typescriptlang.org/docs/handbook/basic-types.html#tuple)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#元组-tuple)) 63 | -------------------------------------------------------------------------------- /basics/any.md: -------------------------------------------------------------------------------- 1 | # 任意值 2 | 3 | 任意值(Any)用来表示允许赋值为任意类型。 4 | 5 | ## 什么是任意值类型 6 | 7 | 如果是一个普通类型,在赋值过程中改变类型是不被允许的: 8 | 9 | ```ts 10 | let myFavoriteNumber: string = 'seven'; 11 | myFavoriteNumber = 7; 12 | 13 | // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'. 14 | ``` 15 | 16 | 但如果是 `any` 类型,则允许被赋值为任意类型。 17 | 18 | ```ts 19 | let myFavoriteNumber: any = 'seven'; 20 | myFavoriteNumber = 7; 21 | ``` 22 | 23 | ## 任意值的属性和方法 24 | 25 | 在任意值上访问任何属性都是允许的: 26 | 27 | ```ts 28 | let anyThing: any = 'hello'; 29 | console.log(anyThing.myName); 30 | console.log(anyThing.myName.firstName); 31 | ``` 32 | 33 | 也允许调用任何方法: 34 | 35 | ```ts 36 | let anyThing: any = 'Tom'; 37 | anyThing.setName('Jerry'); 38 | anyThing.setName('Jerry').sayHello(); 39 | anyThing.myName.setFirstName('Cat'); 40 | ``` 41 | 42 | 可以认为,**声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值**。 43 | 44 | ## 未声明类型的变量 45 | 46 | **变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型**: 47 | 48 | ```ts 49 | let something; 50 | something = 'seven'; 51 | something = 7; 52 | 53 | something.setName('Tom'); 54 | ``` 55 | 56 | 等价于 57 | 58 | ```ts 59 | let something: any; 60 | something = 'seven'; 61 | something = 7; 62 | 63 | something.setName('Tom'); 64 | ``` 65 | 66 | ## 参考 67 | 68 | - [Basic Types # Any](http://www.typescriptlang.org/docs/handbook/basic-types.html#any)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#任意值)) 69 | -------------------------------------------------------------------------------- /introduction/get-typescript.md: -------------------------------------------------------------------------------- 1 | # 安装 TypeScript 2 | 3 | TypeScript 的命令行工具安装方法如下: 4 | 5 | ```bash 6 | npm install -g typescript 7 | ``` 8 | 9 | 以上命令会在全局环境下安装 `tsc` 命令,安装完成之后,我们就可以在任何地方执行 `tsc` 命令了。 10 | 11 | 编译一个 TypeScript 文件很简单: 12 | 13 | ```bash 14 | tsc hello.ts 15 | ``` 16 | 17 | 我们约定使用 TypeScript 编写的文件以 `.ts` 为后缀,用 TypeScript 编写 React 时,以 `.tsx` 为后缀。 18 | 19 | ## 编辑器 20 | 21 | TypeScript 最大的优势之一便是增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等。 22 | 23 | 主流的编辑器都支持 TypeScript,这里我推荐使用 [Visual Studio Code](https://code.visualstudio.com/)。 24 | 25 | 它是一款开源,跨终端的轻量级编辑器,内置了对 TypeScript 的支持。 26 | 27 | 另外它本身也是[用 TypeScript 编写的](https://github.com/Microsoft/vscode/)。 28 | 29 | 下载安装:https://code.visualstudio.com/ 30 | 31 | 获取其他编辑器或 IDE 对 TypeScript 的支持: 32 | 33 | - [Sublime Text](https://github.com/Microsoft/TypeScript-Sublime-Plugin) 34 | - [WebStorm](https://www.jetbrains.com/webstorm/) 35 | - [Vim](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support#vim) 36 | - [Emacs](https://github.com/ananthakumaran/tide) 37 | - [Eclipse](https://github.com/palantir/eclipse-typescript) 38 | - [Atom](https://atom.io/packages/atom-typescript) 39 | - [Visual Studio 2019](https://marketplace.visualstudio.com/search?term=TypeScriptTeam&target=VS&category=All%20categories&vsVersion=vs2019&sortBy=UpdatedDate) 40 | - [Visual Studio 2017](https://marketplace.visualstudio.com/search?term=TypeScriptTeam&target=VS&category=All%20categories&vsVersion=vs15&sortBy=UpdatedDate) 41 | -------------------------------------------------------------------------------- /introduction/why-typescript.md: -------------------------------------------------------------------------------- 1 | 2 | 你可能或多或少听说过这样的言论: 3 | 4 | - TypeScript 只适用于大公司的大型项目 5 | - TypeScript 适合多人协作开发,一个人维护的项目就没必要使用了 6 | - TypeScript 学习成本高,开发成本高 7 | - 旧项目是 JavaScript,所以没必要改造成 TypeScript 了 8 | 9 | 我想说,都 2021 年了,赶紧上车吧,绝大部分项目都应该使用 TypeScript! 10 | 11 | 大型项目不必多说,TypeScript 的类型系统能够集成到 IDE(或编辑器)中,通过代码补全、代码提示、跳转到定义等功能,极大的提高代码的可维护性,提高开发效率,降低 bug 率。 12 | 13 | 14 | 15 | > 动态类型一时爽,代码重构火葬场。 16 | 17 | 18 | 如果你没有学过 TypeScript,那么可能连这样的接口提示都看不懂了: 19 | 20 | ![what-is-typescript-tip](../assets/what-is-typescript-tip.png) 21 | 22 | 23 | 24 | ## 为什么选择 TypeScript 25 | 26 | [TypeScript 官网][TypeScript]列举了一些优势,不过我更愿意自己总结一下: 27 | 28 | ### TypeScript 增加了代码的可读性和可维护性 29 | 30 | - 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了 31 | - 可以在编译阶段就发现大部分错误,这总比在运行时候出错好 32 | - 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、代码重构等 33 | 34 | ### TypeScript 非常包容 35 | 36 | - TypeScript 是 JavaScript 的超集,`.js` 文件可以直接重命名为 `.ts` 即可 37 | - 即使不显式的定义类型,也能够自动做出[类型推论](../basics/type-inference.md) 38 | - TypeScript 的类型系统是图灵完备的,可以定义从简单到复杂的几乎一切类型 39 | - 即使 TypeScript 编译报错,也可以生成 JavaScript 文件 40 | - 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取 41 | 42 | ### TypeScript 拥有活跃的社区 43 | 44 | - 大部分第三方库都有提供给 TypeScript 的类型定义文件 45 | - Angular、Vue、VS Code、Ant Design 等等耳熟能详的项目都是使用 TypeScript 编写的 46 | - TypeScript 拥抱了 ES6 规范,支持 ESNext 草案中处于第三阶状态(Stage 3)的特性 47 | 48 | ### TypeScript 的缺点 49 | 50 | 任何事物都是有两面性的,我认为 TypeScript 的弊端在于: 51 | 52 | - 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念 53 | - 短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本 54 | - 集成到构建流程需要一些工作量 55 | - 可能和一些库结合的不是很完美 56 | 57 | 大家可以根据自己团队和项目的情况判断是否需要使用 TypeScript。 58 | 59 | StackOverflow 2020 开发者调查报告,TypeScript 击败 Python 60 | -------------------------------------------------------------------------------- /basics/union-types.md: -------------------------------------------------------------------------------- 1 | # 联合类型 2 | 3 | 联合类型(Union Types)表示取值可以为多种类型中的一种。 4 | 5 | ## 简单的例子 6 | 7 | ```ts 8 | let myFavoriteNumber: string | number; 9 | myFavoriteNumber = 'seven'; 10 | myFavoriteNumber = 7; 11 | ``` 12 | 13 | ```ts 14 | let myFavoriteNumber: string | number; 15 | myFavoriteNumber = true; 16 | 17 | // index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'. 18 | // Type 'boolean' is not assignable to type 'number'. 19 | ``` 20 | 21 | 联合类型使用 `|` 分隔每个类型。 22 | 23 | 这里的 `let myFavoriteNumber: string | number` 的含义是,允许 `myFavoriteNumber` 的类型是 `string` 或者 `number`,但是不能是其他类型。 24 | 25 | ## 访问联合类型的属性或方法 26 | 27 | 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们**只能访问此联合类型的所有类型里共有的属性或方法**: 28 | 29 | ```ts 30 | function getLength(something: string | number): number { 31 | return something.length; 32 | } 33 | 34 | // index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'. 35 | // Property 'length' does not exist on type 'number'. 36 | ``` 37 | 38 | 上例中,`length` 不是 `string` 和 `number` 的共有属性,所以会报错。 39 | 40 | 访问 `string` 和 `number` 的共有属性是没问题的: 41 | 42 | ```ts 43 | function getString(something: string | number): string { 44 | return something.toString(); 45 | } 46 | ``` 47 | 48 | 联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型: 49 | 50 | ```ts 51 | let myFavoriteNumber: string | number; 52 | myFavoriteNumber = 'seven'; 53 | console.log(myFavoriteNumber.length); // 5 54 | myFavoriteNumber = 7; 55 | console.log(myFavoriteNumber.length); // 编译时报错 56 | 57 | // index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'. 58 | ``` 59 | 60 | 上例中,第二行的 `myFavoriteNumber` 被推断成了 `string`,访问它的 `length` 属性不会报错。 61 | 62 | 而第四行的 `myFavoriteNumber` 被推断成了 `number`,访问它的 `length` 属性时就报错了。 63 | 64 | ## 参考 65 | 66 | - [Advanced Types # Union Types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#联合类型)) 67 | -------------------------------------------------------------------------------- /introduction/hello-typescript.md: -------------------------------------------------------------------------------- 1 | # Hello TypeScript 2 | 3 | 我们从一个简单的例子开始。 4 | 5 | 将以下代码复制到 `hello.ts` 中: 6 | 7 | ```ts 8 | function sayHello(person: string) { 9 | return 'Hello, ' + person; 10 | } 11 | 12 | let user = 'Tom'; 13 | console.log(sayHello(user)); 14 | ``` 15 | 16 | 然后执行 17 | 18 | ```bash 19 | tsc hello.ts 20 | ``` 21 | 22 | 这时候会生成一个编译好的文件 `hello.js`: 23 | 24 | ```js 25 | function sayHello(person) { 26 | return 'Hello, ' + person; 27 | } 28 | var user = 'Tom'; 29 | console.log(sayHello(user)); 30 | ``` 31 | 32 | 在 TypeScript 中,我们使用 `:` 指定变量的类型,`:` 的前后有没有空格都可以。 33 | 34 | 上述例子中,我们用 `:` 指定 `person` 参数类型为 `string`。但是编译为 js 之后,并没有什么检查的代码被插入进来。 35 | 36 | 这是因为 **TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错**。而在运行时,与普通的 JavaScript 文件一样,不会对类型进行检查。 37 | 38 | 如果我们需要保证运行时的参数类型,还是得手动对类型进行判断: 39 | 40 | ```ts 41 | function sayHello(person: string) { 42 | if (typeof person === 'string') { 43 | return 'Hello, ' + person; 44 | } else { 45 | throw new Error('person is not a string'); 46 | } 47 | } 48 | 49 | let user = 'Tom'; 50 | console.log(sayHello(user)); 51 | ``` 52 | 53 | > `let` 是 ES6 中的关键字,和 `var` 类似,用于定义一个局部变量,可以参阅 [let 和 const 命令](http://es6.ruanyifeng.com/#docs/let)。 54 | 55 | 下面尝试把这段代码编译一下: 56 | 57 | ```ts 58 | function sayHello(person: string) { 59 | return 'Hello, ' + person; 60 | } 61 | 62 | let user = [0, 1, 2]; 63 | console.log(sayHello(user)); 64 | ``` 65 | 66 | 编辑器中会提示错误,编译的时候也会出错: 67 | 68 | ```bash 69 | hello.ts:6:22 - error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'. 70 | ``` 71 | 72 | 但是还是生成了 js 文件: 73 | 74 | ```js 75 | function sayHello(person) { 76 | return 'Hello, ' + person; 77 | } 78 | var user = [0, 1, 2]; 79 | console.log(sayHello(user)); 80 | ``` 81 | 82 | 这是因为 **TypeScript 编译的时候即使报错了,还是会生成编译结果**,我们仍然可以使用这个编译之后的文件。 83 | 84 | 如果要在报错的时候终止 js 文件的生成,可以在 `tsconfig.json` 中配置 `noEmitOnError` 即可。关于 `tsconfig.json`,请参阅[官方手册](http://www.typescriptlang.org/docs/handbook/tsconfig-json.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html))。 85 | -------------------------------------------------------------------------------- /advanced/declaration-merging.md: -------------------------------------------------------------------------------- 1 | # 声明合并 2 | 3 | 如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型: 4 | 5 | ## 函数的合并 6 | 7 | [之前学习过](../basics/type-of-function.md#重载),我们可以使用重载定义多个函数类型: 8 | 9 | ```ts 10 | function reverse(x: number): number; 11 | function reverse(x: string): string; 12 | function reverse(x: number | string): number | string { 13 | if (typeof x === 'number') { 14 | return Number(x.toString().split('').reverse().join('')); 15 | } else if (typeof x === 'string') { 16 | return x.split('').reverse().join(''); 17 | } 18 | } 19 | ``` 20 | 21 | ## 接口的合并 22 | 23 | 接口中的属性在合并时会简单的合并到一个接口中: 24 | 25 | ```ts 26 | interface Alarm { 27 | price: number; 28 | } 29 | interface Alarm { 30 | weight: number; 31 | } 32 | ``` 33 | 34 | 相当于: 35 | 36 | ```ts 37 | interface Alarm { 38 | price: number; 39 | weight: number; 40 | } 41 | ``` 42 | 43 | 注意,**合并的属性的类型必须是唯一的**: 44 | 45 | ```ts 46 | interface Alarm { 47 | price: number; 48 | } 49 | interface Alarm { 50 | price: number; // 虽然重复了,但是类型都是 `number`,所以不会报错 51 | weight: number; 52 | } 53 | ``` 54 | 55 | ```ts 56 | interface Alarm { 57 | price: number; 58 | } 59 | interface Alarm { 60 | price: string; // 类型不一致,会报错 61 | weight: number; 62 | } 63 | 64 | // index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type. Variable 'price' must be of type 'number', but here has type 'string'. 65 | ``` 66 | 67 | 接口中方法的合并,与函数的合并一样: 68 | 69 | ```ts 70 | interface Alarm { 71 | price: number; 72 | alert(s: string): string; 73 | } 74 | interface Alarm { 75 | weight: number; 76 | alert(s: string, n: number): string; 77 | } 78 | ``` 79 | 80 | 相当于: 81 | 82 | ```ts 83 | interface Alarm { 84 | price: number; 85 | weight: number; 86 | alert(s: string): string; 87 | alert(s: string, n: number): string; 88 | } 89 | ``` 90 | 91 | ## 类的合并 92 | 93 | 类的合并与接口的合并规则一致。 94 | 95 | ## 参考 96 | 97 | - [Declaration Merging](http://www.typescriptlang.org/docs/handbook/declaration-merging.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Declaration%20Merging.html)) 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-tutorial", 3 | "version": "0.1.0", 4 | "description": "从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript", 5 | "main": "README.md", 6 | "scripts": { 7 | "start": "deno run --unstable --allow-read --allow-write --allow-net --allow-run ../pagic/mod.ts build --serve --watch", 8 | "build": "deno run --unstable --allow-read --allow-write --allow-net --allow-run ../pagic/mod.ts build", 9 | "test": "npm run lint", 10 | "pandoc": "pandoc -o TypeScript\\ 入门教程.epub --resource-path assets pandoc-metadata.txt $(cat pandoc-list.txt)", 11 | "lint": "run-s eclint prettier lint-md eslint", 12 | "lint:fix": "run-s eclint:fix prettier:fix lint-md:fix", 13 | "eclint": "bash -c 'eclint check $(git ls-files -- . \":!:*.epub\")'", 14 | "eclint:fix": "bash -c 'eclint fix $(git ls-files -- . \":!:*.epub\")'", 15 | "prettier": "prettier -l \"./**/*\"", 16 | "prettier:fix": "prettier --write -l \"./**/*\"", 17 | "lint-md": "lint-md .", 18 | "lint-md:fix": "lint-md --fix .", 19 | "eslint": "eslint --ext .ts examples", 20 | "eslint:fix": "eslint --ext .ts --fix examples" 21 | }, 22 | "husky": { 23 | "hooks": { 24 | "pre-commit": "npm test", 25 | "pre-push": "npm test" 26 | } 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/xcatliu/typescript-tutorial.git" 31 | }, 32 | "keywords": [ 33 | "typescript", 34 | "tutorial", 35 | "javascript" 36 | ], 37 | "author": "xcatliu ", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/xcatliu/typescript-tutorial/issues" 41 | }, 42 | "homepage": "https://github.com/xcatliu/typescript-tutorial#readme", 43 | "devDependencies": { 44 | "@typescript-eslint/eslint-plugin": "^5.59.9", 45 | "@typescript-eslint/parser": "^5.59.9", 46 | "eclint": "^2.8.1", 47 | "eslint": "^8.42.0", 48 | "eslint-config-alloy": "^5.0.0", 49 | "eslint-plugin-react": "^7.32.2", 50 | "husky": "^8.0.3", 51 | "lint-md-cli": "^0.1.2", 52 | "npm-run-all": "^4.1.5", 53 | "prettier": "^2.8.8", 54 | "typescript": "^5.1.3" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /engineering/compiler-options.md: -------------------------------------------------------------------------------- 1 | # 编译选项 2 | 3 | TypeScript 提供了非常多的编译选项,但是官方文档对每一项的解释很抽象,这一章会详细介绍每一个选项的作用,并给出对应的示例。 4 | 5 | 索引(点击选项跳转到详细介绍): 6 | 7 | 选项 | 类型 | 默认值 | 描述 8 | --- | --- | --- | --- 9 | [`allowJs`](#allowjs) | `boolean` | `false` | 允许编译 js 文件 10 | [`allowSyntheticDefaultImports`](#allowsyntheticdefaultimports) | `boolean` | `false` | 允许对不包含默认导出的模块使用默认导入。这个选项不会影响生成的代码,只会影响类型检查。 11 | 12 | ## allowJs 13 | 14 | > 允许编译 js 文件。 15 | 16 | 设置为 `true` 时,js 文件会被 tsc 编译,否则不会。一般在项目中 js, ts 混合开发时需要设置。 17 | 18 | [查看示例](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/compiler-options/01-allowJs) 19 | 20 | ```bash 21 | # 设置为 true 时,编译后的文件包含 foo.js 22 | ├── lib 23 | │ ├── foo.js 24 | │ └── index.js 25 | ├── src 26 | │ ├── foo.js 27 | │ └── index.ts 28 | ├── package.json 29 | └── tsconfig.json 30 | ``` 31 | 32 | ```bash 33 | # 设置为 false 时,编译后的文件不包含 foo.js 34 | ├── lib 35 | │ └── index.js 36 | ├── src 37 | │ ├── foo.js 38 | │ └── index.ts 39 | ├── package.json 40 | └── tsconfig.json 41 | ``` 42 | 43 | ## allowSyntheticDefaultImports 44 | 45 | > 允许对不包含默认导出的模块使用默认导入。这个选项不会影响生成的代码,只会影响类型检查。 46 | 47 | `export = foo` 是 ts 为了兼容 commonjs 创造的语法,它对应于 commonjs 中的 `module.exports = foo`。 48 | 49 | 在 ts 中,如果要引入一个通过 `export = foo` 导出的模块,标准的语法是 `import foo = require('foo')`,或者 `import * as foo from 'foo'`。 50 | 51 | 但由于历史原因,我们已经习惯了使用 `import foo from 'foo'`。 52 | 53 | 这个选项就是为了解决这个问题。当它设置为 `true` 时,允许使用 `import foo from 'foo'` 来导入一个通过 `export = foo` 导出的模块。当它设置为 `false` 时,则不允许,会报错。 54 | 55 | 当然,我们一般不会在 ts 文件中使用 `export = foo` 来导出模块,而是在[写(符合 commonjs 规范的)第三方库的声明文件](../basics/declaration-files#export-1)时,才会用到 `export = foo` 来导出类型。 56 | 57 | 比如 React 的声明文件中,就是通过 `export = React` 来导出类型: 58 | 59 | ```ts 60 | export = React; 61 | export as namespace React; 62 | 63 | declare namespace React { 64 | // 声明 React 的类型 65 | } 66 | ``` 67 | 68 | 此时若我们通过 `import React from 'react'` 来导入 react 则会报错,[查看示例](https://github.com/xcatliu/typescript-tutorial/tree/master/examples/compiler-options/02-allowSyntheticDefaultImports) 69 | : 70 | 71 | ```ts 72 | import React from 'react'; 73 | // Module '"typescript-tutorial/examples/compiler-options/02-allowSyntheticDefaultImports/false/node_modules/@types/react/index"' can only be default-imported using the 'esModuleInterop' flagts(1259) 74 | ``` 75 | 76 | 解决办法就是将 `allowSyntheticDefaultImports` 设置为 `true`。 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | next: introduction/README.md 3 | --- 4 | 5 | # TypeScript 入门教程 6 | 7 | [![Actions Status](https://github.com/xcatliu/typescript-tutorial/workflows/gh-pages/badge.svg)](https://github.com/xcatliu/typescript-tutorial/actions) 8 | 9 | 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 10 | 11 | ## 关于本书 12 | 13 | - [在线阅读](https://ts.xcatliu.com/) 14 | - [GitHub 地址][GitHub] 15 | - 作者:[xcatliu](https://github.com/xcatliu/) 16 | - 本网站使用 [Pagic](https://github.com/xcatliu/pagic) 构建 17 | 18 | 本书是作者在学习 [TypeScript] 后整理的学习笔记。 19 | 20 | 随着对 TypeScript 理解的加深和 TypeScript 社区的发展,本书也会做出相应的更新,欢迎大家 [Star 收藏][GitHub]。 21 | 22 | - 发现文章内容有问题,可以直接在页面下方评论 23 | - 对项目的建议,可以[提交 issue](https://github.com/xcatliu/typescript-tutorial/issues/new) 向作者反馈 24 | - 欢迎直接提交 pull-request 参与贡献 25 | 26 | ## 为什么要写本书 27 | 28 | TypeScript 虽然有[官方手册][Handbook]及其[非官方中文版][中文手册],但是它每一章都希望能详尽的描述一个概念,导致前面的章节就会包含很多后面才会学习到的内容,而有些本该一开始就了解的基础知识却在后面才会涉及。如果是初学者,可能需要阅读多次才能理解。所以它更适合用来查阅,而不是学习。 29 | 30 | 与官方手册不同,本书着重于从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript,希望能给大家一些帮助和启示。 31 | 32 | 由于一些知识点与官方手册重合度很高,本书会在相应章节推荐直接阅读中文手册。 33 | 34 | ## 关于 TypeScript 35 | 36 | [TypeScript] 是 JavaScript 的一个超集,主要提供了**类型系统**和**对 ES6 的支持**,它由 Microsoft 开发,代码[开源于 GitHub](https://github.com/Microsoft/TypeScript) 上。 37 | 38 | 它的第一个版本发布于 2012 年 10 月,经历了多次更新后,现在已成为前端社区中不可忽视的力量,不仅在 Microsoft 内部得到广泛运用,而且 Google 开发的 [Angular](https://angular.io/) 从 2.0 开始就使用了 TypeScript 作为开发语言,[Vue](https://vuejs.org/) 3.0 也使用 TypeScript 进行了重构。 39 | 40 | ## 适合人群 41 | 42 | 本书适合以下人群 43 | 44 | - 熟悉 JavaScript,至少阅读过一遍[《JavaScript 高级程序设计》](https://book.douban.com/subject/10546125/) 45 | - 了解 ES6,推荐阅读 [ECMAScript 6 入门] 46 | - 了解 Node.js,会用 npm 安装及使用一些工具 47 | - 想了解 TypeScript 或者想对 TypeScript 有更深的理解 48 | 49 | 本书**不适合**以下人群 50 | 51 | - 没有系统学习过 JavaScript 52 | - 已经能够很熟练的运用 TypeScript 53 | 54 | ## 评价 55 | 56 | > 《TypeScript 入门教程》全面介绍了 TypeScript 强大的类型系统,完整而简洁,示例丰富,比官方文档更易读,非常适合作为初学者学习 TypeScript 的第一本书。 57 | > 58 | > —— [阮一峰](https://github.com/ruanyf) 59 | 60 | ## 版权许可 61 | 62 | 本书采用「保持署名—非商用」创意共享 4.0 许可证。 63 | 64 | 只要保持原作者署名和非商用,您可以自由地阅读、分享、修改本书。 65 | 66 | 详细的法律条文请参见[创意共享](http://creativecommons.org/licenses/by-nc/4.0/)网站。 67 | 68 | ## 相关资料 69 | 70 | - [TypeScript 官网][TypeScript] 71 | - [Handbook]([中文版][中文手册]) 72 | - [ECMAScript 6 入门] 73 | 74 | [GitHub]: https://github.com/xcatliu/typescript-tutorial 75 | [TypeScript]: http://www.typescriptlang.org/ 76 | [Handbook]: http://www.typescriptlang.org/docs/handbook/basic-types.html 77 | [中文手册]: https://zhongsp.gitbook.io/typescript-handbook/ 78 | [ECMAScript 6 入门]: http://es6.ruanyifeng.com/ 79 | -------------------------------------------------------------------------------- /basics/built-in-objects.md: -------------------------------------------------------------------------------- 1 | # 内置对象 2 | 3 | JavaScript 中有很多[内置对象][],它们可以直接在 TypeScript 中当做定义好了的类型。 4 | 5 | 内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。 6 | 7 | ## ECMAScript 的内置对象 8 | 9 | ECMAScript 标准提供的内置对象有: 10 | 11 | `Boolean`、`Error`、`Date`、`RegExp` 等。 12 | 13 | 我们可以在 TypeScript 中将变量定义为这些类型: 14 | 15 | ```ts 16 | let b: Boolean = new Boolean(1); 17 | let e: Error = new Error('Error occurred'); 18 | let d: Date = new Date(); 19 | let r: RegExp = /[a-z]/; 20 | ``` 21 | 22 | 更多的内置对象,可以查看 [MDN 的文档][内置对象]。 23 | 24 | 而他们的定义文件,则在 [TypeScript 核心库的定义文件][]中。 25 | 26 | ## DOM 和 BOM 的内置对象 27 | 28 | DOM 和 BOM 提供的内置对象有: 29 | 30 | `Document`、`HTMLElement`、`Event`、`NodeList` 等。 31 | 32 | TypeScript 中会经常用到这些类型: 33 | 34 | ```ts 35 | let body: HTMLElement = document.body; 36 | let allDiv: NodeList = document.querySelectorAll('div'); 37 | document.addEventListener('click', function(e: MouseEvent) { 38 | // Do something 39 | }); 40 | ``` 41 | 42 | 它们的定义文件同样在 [TypeScript 核心库的定义文件][]中。 43 | 44 | ## TypeScript 核心库的定义文件 45 | 46 | [TypeScript 核心库的定义文件][]中定义了所有浏览器环境需要用到的类型,并且是预置在 TypeScript 中的。 47 | 48 | 当你在使用一些常用的方法的时候,TypeScript 实际上已经帮你做了很多类型判断的工作了,比如: 49 | 50 | ```ts 51 | Math.pow(10, '2'); 52 | 53 | // index.ts(1,14): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. 54 | ``` 55 | 56 | 上面的例子中,`Math.pow` 必须接受两个 `number` 类型的参数。事实上 `Math.pow` 的类型定义如下: 57 | 58 | ```ts 59 | interface Math { 60 | /** 61 | * Returns the value of a base expression taken to a specified power. 62 | * @param x The base value of the expression. 63 | * @param y The exponent value of the expression. 64 | */ 65 | pow(x: number, y: number): number; 66 | } 67 | ``` 68 | 69 | 再举一个 DOM 中的例子: 70 | 71 | ```ts 72 | document.addEventListener('click', function(e) { 73 | console.log(e.targetCurrent); 74 | }); 75 | 76 | // index.ts(2,17): error TS2339: Property 'targetCurrent' does not exist on type 'MouseEvent'. 77 | ``` 78 | 79 | 上面的例子中,`addEventListener` 方法是在 TypeScript 核心库中定义的: 80 | 81 | ```ts 82 | interface Document extends Node, GlobalEventHandlers, NodeSelector, DocumentEvent { 83 | addEventListener(type: string, listener: (ev: MouseEvent) => any, useCapture?: boolean): void; 84 | } 85 | ``` 86 | 87 | 所以 `e` 被推断成了 `MouseEvent`,而 `MouseEvent` 是没有 `targetCurrent` 属性的,所以报错了。 88 | 89 | 注意,TypeScript 核心库的定义中不包含 Node.js 部分。 90 | 91 | ## 用 TypeScript 写 Node.js 92 | 93 | Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件: 94 | 95 | ```bash 96 | npm install @types/node --save-dev 97 | ``` 98 | 99 | ## 参考 100 | 101 | - [内置对象][] 102 | - [TypeScript 核心库的定义文件][] 103 | 104 | [内置对象]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects 105 | [TypeScript 核心库的定义文件]: https://github.com/Microsoft/TypeScript/tree/master/src/lib 106 | -------------------------------------------------------------------------------- /basics/type-of-array.md: -------------------------------------------------------------------------------- 1 | # 数组的类型 2 | 3 | 在 TypeScript 中,数组类型有多种定义方式,比较灵活。 4 | 5 | ## 「类型 + 方括号」表示法 6 | 7 | 最简单的方法是使用「类型 + 方括号」来表示数组: 8 | 9 | ```ts 10 | let fibonacci: number[] = [1, 1, 2, 3, 5]; 11 | ``` 12 | 13 | 数组的项中**不允许**出现其他的类型: 14 | 15 | ```ts 16 | let fibonacci: number[] = [1, '1', 2, 3, 5]; 17 | 18 | // Type 'string' is not assignable to type 'number'. 19 | ``` 20 | 21 | 数组的一些方法的参数也会根据数组在定义时约定的类型进行限制: 22 | 23 | ```ts 24 | let fibonacci: number[] = [1, 1, 2, 3, 5]; 25 | fibonacci.push('8'); 26 | 27 | // Argument of type '"8"' is not assignable to parameter of type 'number'. 28 | ``` 29 | 30 | 上例中,`push` 方法只允许传入 `number` 类型的参数,但是却传了一个 `"8"` 类型的参数,所以报错了。这里 `"8"` 是一个字符串字面量类型,会在后续章节中详细介绍。 31 | 32 | ## 数组泛型 33 | 34 | 我们也可以使用数组泛型(Array Generic) `Array` 来表示数组: 35 | 36 | ```ts 37 | let fibonacci: Array = [1, 1, 2, 3, 5]; 38 | ``` 39 | 40 | 关于泛型,可以参考[泛型](../advanced/generics.md)一章。 41 | 42 | ## 用接口表示数组 43 | 44 | 接口也可以用来描述数组: 45 | 46 | ```ts 47 | interface NumberArray { 48 | [index: number]: number; 49 | } 50 | let fibonacci: NumberArray = [1, 1, 2, 3, 5]; 51 | ``` 52 | 53 | `NumberArray` 表示:只要索引的类型是数字时,那么值的类型必须是数字。 54 | 55 | 虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了。 56 | 57 | 不过有一种情况例外,那就是它常用来表示类数组。 58 | 59 | ## 类数组 60 | 61 | 类数组(Array-like Object)不是数组类型,比如 `arguments`: 62 | 63 | ```ts 64 | function sum() { 65 | let args: number[] = arguments; 66 | } 67 | 68 | // Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more. 69 | ``` 70 | 71 | 上例中,`arguments` 实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口: 72 | 73 | ```ts 74 | function sum() { 75 | let args: { 76 | [index: number]: number; 77 | length: number; 78 | callee: Function; 79 | } = arguments; 80 | } 81 | ``` 82 | 83 | 在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 `length` 和 `callee` 两个属性。 84 | 85 | 事实上常用的类数组都有自己的接口定义,如 `IArguments`, `NodeList`, `HTMLCollection` 等: 86 | 87 | ```ts 88 | function sum() { 89 | let args: IArguments = arguments; 90 | } 91 | ``` 92 | 93 | 其中 `IArguments` 是 TypeScript 中定义好了的类型,它实际上就是: 94 | 95 | ```ts 96 | interface IArguments { 97 | [index: number]: any; 98 | length: number; 99 | callee: Function; 100 | } 101 | ``` 102 | 103 | 关于内置对象,可以参考[内置对象](./built-in-objects.md)一章。 104 | 105 | ## any 在数组中的应用 106 | 107 | 一个比较常见的做法是,用 `any` 表示数组中允许出现任意类型: 108 | 109 | ```ts 110 | let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }]; 111 | ``` 112 | 113 | ## 参考 114 | 115 | - [Basic Types # Array](http://www.typescriptlang.org/docs/handbook/basic-types.html#array)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#数组)) 116 | - [Interfaces # Indexable Types](http://www.typescriptlang.org/docs/handbook/interfaces.html#indexable-types)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html#数组类型)) 117 | -------------------------------------------------------------------------------- /examples/compiler-options/02-allowSyntheticDefaultImports/false/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02-allow-synthetic-default-imports-false", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/prop-types": { 8 | "version": "15.7.3", 9 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", 10 | "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", 11 | "dev": true 12 | }, 13 | "@types/react": { 14 | "version": "16.9.14", 15 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.14.tgz", 16 | "integrity": "sha512-Q4tW4RGmR+u/CgzR8VqZcsUWjP4Pz/LcHfs9AzSG+aBnwq8As3Bid3vG1eGGsXg/xuR2k2tqNlI8pzyV8kxe0g==", 17 | "dev": true, 18 | "requires": { 19 | "@types/prop-types": "*", 20 | "csstype": "^2.2.0" 21 | } 22 | }, 23 | "csstype": { 24 | "version": "2.6.7", 25 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", 26 | "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==", 27 | "dev": true 28 | }, 29 | "js-tokens": { 30 | "version": "4.0.0", 31 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 32 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 33 | }, 34 | "loose-envify": { 35 | "version": "1.4.0", 36 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 37 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 38 | "requires": { 39 | "js-tokens": "^3.0.0 || ^4.0.0" 40 | } 41 | }, 42 | "object-assign": { 43 | "version": "4.1.1", 44 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 45 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 46 | }, 47 | "prop-types": { 48 | "version": "15.7.2", 49 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 50 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 51 | "requires": { 52 | "loose-envify": "^1.4.0", 53 | "object-assign": "^4.1.1", 54 | "react-is": "^16.8.1" 55 | } 56 | }, 57 | "react": { 58 | "version": "16.12.0", 59 | "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", 60 | "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", 61 | "requires": { 62 | "loose-envify": "^1.1.0", 63 | "object-assign": "^4.1.1", 64 | "prop-types": "^15.6.2" 65 | } 66 | }, 67 | "react-is": { 68 | "version": "16.12.0", 69 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", 70 | "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/compiler-options/02-allowSyntheticDefaultImports/true/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02-allow-synthetic-default-imports-false", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/prop-types": { 8 | "version": "15.7.3", 9 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", 10 | "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", 11 | "dev": true 12 | }, 13 | "@types/react": { 14 | "version": "16.9.14", 15 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.14.tgz", 16 | "integrity": "sha512-Q4tW4RGmR+u/CgzR8VqZcsUWjP4Pz/LcHfs9AzSG+aBnwq8As3Bid3vG1eGGsXg/xuR2k2tqNlI8pzyV8kxe0g==", 17 | "dev": true, 18 | "requires": { 19 | "@types/prop-types": "*", 20 | "csstype": "^2.2.0" 21 | } 22 | }, 23 | "csstype": { 24 | "version": "2.6.7", 25 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", 26 | "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==", 27 | "dev": true 28 | }, 29 | "js-tokens": { 30 | "version": "4.0.0", 31 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 32 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 33 | }, 34 | "loose-envify": { 35 | "version": "1.4.0", 36 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 37 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 38 | "requires": { 39 | "js-tokens": "^3.0.0 || ^4.0.0" 40 | } 41 | }, 42 | "object-assign": { 43 | "version": "4.1.1", 44 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 45 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 46 | }, 47 | "prop-types": { 48 | "version": "15.7.2", 49 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 50 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 51 | "requires": { 52 | "loose-envify": "^1.4.0", 53 | "object-assign": "^4.1.1", 54 | "react-is": "^16.8.1" 55 | } 56 | }, 57 | "react": { 58 | "version": "16.12.0", 59 | "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", 60 | "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", 61 | "requires": { 62 | "loose-envify": "^1.1.0", 63 | "object-assign": "^4.1.1", 64 | "prop-types": "^15.6.2" 65 | } 66 | }, 67 | "react-is": { 68 | "version": "16.12.0", 69 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", 70 | "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /advanced/further-reading.md: -------------------------------------------------------------------------------- 1 | # 扩展阅读 2 | 3 | 此处记录了[官方手册](http://www.typescriptlang.org/docs/handbook/basic-types.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/))中包含,但是本书未涉及的概念。 4 | 5 | 我认为它们是一些不重要或者不属于 TypeScript 的概念,所以这里只给出一个简单的释义,详细内容可以点击链接深入理解。 6 | 7 | - [Never](http://www.typescriptlang.org/docs/handbook/basic-types.html#never)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html#never)):永远不存在值的类型,一般用于错误处理函数 8 | - [Variable Declarations](http://www.typescriptlang.org/docs/handbook/variable-declarations.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Variable%20Declarations.html)):使用 `let` 和 `const` 替代 `var`,这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/let) 9 | - [`this`](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Functions.html#this):箭头函数的运用,这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/function) 10 | - [Using Class Types in Generics](http://www.typescriptlang.org/docs/handbook/generics.html#using-class-types-in-generics)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Generics.html#在泛型里使用类类型)):创建工厂函数时,需要引用构造函数的类类型 11 | - [Best common type](http://www.typescriptlang.org/docs/handbook/type-inference.html#best-common-type)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Inference.html#最佳通用类型)):数组的类型推论 12 | - [Contextual Type](http://www.typescriptlang.org/docs/handbook/type-inference.html#contextual-type)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Inference.html#上下文类型)):函数输入的类型推论 13 | - [Type Compatibility](http://www.typescriptlang.org/docs/handbook/type-compatibility.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Type%20Compatibility.html)):允许不严格符合类型,只需要在一定规则下兼容即可 14 | - [Advanced Types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#交叉类型(intersection-types))):使用 `&` 将多种类型的共有部分叠加成一种类型 15 | - [Type Guards and Differentiating Types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#类型保护与区分类型(type-guards-and-differentiating-types))):联合类型在一些情况下被识别为特定的类型 16 | - [Discriminated Unions](http://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#可辨识联合(discriminated-unions))):使用 `|` 联合多个接口的时候,通过一个共有的属性形成可辨识联合 17 | - [Polymorphic `this` types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#polymorphic-this-types)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#多态的this类型)):父类的某个方法返回 `this`,当子类继承父类后,子类的实例调用此方法,返回的 `this` 能够被 TypeScript 正确的识别为子类的实例。 18 | - [Symbols](http://www.typescriptlang.org/docs/handbook/symbols.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Symbols.html)):新原生类型,这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/symbol) 19 | - [Iterators and Generators](http://www.typescriptlang.org/docs/handbook/iterators-and-generators.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Iterators%20and%20Generators.html)):迭代器,这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/iterator) 20 | - [Namespaces](http://www.typescriptlang.org/docs/handbook/namespaces.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Namespaces.html)):避免全局污染,现在已被 [ES6 Module](http://es6.ruanyifeng.com/#docs/module) 替代 21 | - [Mixins](http://www.typescriptlang.org/docs/handbook/mixins.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Mixins.html)):一种编程模式,与 TypeScript 没有直接关系,可以参考 [ES6 中 Mixin 模式的实现](http://es6.ruanyifeng.com/#docs/class#Mixin模式的实现) 22 | -------------------------------------------------------------------------------- /basics/primitive-data-types.md: -------------------------------------------------------------------------------- 1 | # 原始数据类型 2 | 3 | JavaScript 的类型分为两种:原始数据类型([Primitive data types][])和对象类型(Object types)。 4 | 5 | 原始数据类型包括:布尔值、数值、字符串、`null`、`undefined` 以及 ES6 中的新类型 [`Symbol`][] 和 ES10 中的新类型 [`BigInt`][]。 6 | 7 | 本节主要介绍**前五种**原始数据类型在 TypeScript 中的应用。 8 | 9 | ## 布尔值 10 | 11 | 布尔值是最基础的数据类型,在 TypeScript 中,使用 `boolean` 定义布尔值类型: 12 | 13 | ```ts 14 | let isDone: boolean = false; 15 | 16 | // 编译通过 17 | // 后面约定,未强调编译错误的代码片段,默认为编译通过 18 | ``` 19 | 20 | 注意,使用构造函数 `Boolean` 创造的对象**不是**布尔值: 21 | 22 | ```ts 23 | let createdByNewBoolean: boolean = new Boolean(1); 24 | 25 | // Type 'Boolean' is not assignable to type 'boolean'. 26 | // 'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible. 27 | ``` 28 | 29 | 事实上 `new Boolean()` 返回的是一个 `Boolean` 对象: 30 | 31 | ```ts 32 | let createdByNewBoolean: Boolean = new Boolean(1); 33 | ``` 34 | 35 | 直接调用 `Boolean` 也可以返回一个 `boolean` 类型: 36 | 37 | ```ts 38 | let createdByBoolean: boolean = Boolean(1); 39 | ``` 40 | 41 | 在 TypeScript 中,`boolean` 是 JavaScript 中的基本类型,而 `Boolean` 是 JavaScript 中的构造函数。其他基本类型(除了 `null` 和 `undefined`)一样,不再赘述。 42 | 43 | ## 数值 44 | 45 | 使用 `number` 定义数值类型: 46 | 47 | ```ts 48 | let decLiteral: number = 6; 49 | let hexLiteral: number = 0xf00d; 50 | // ES6 中的二进制表示法 51 | let binaryLiteral: number = 0b1010; 52 | // ES6 中的八进制表示法 53 | let octalLiteral: number = 0o744; 54 | let notANumber: number = NaN; 55 | let infinityNumber: number = Infinity; 56 | ``` 57 | 58 | 编译结果: 59 | 60 | ```js 61 | var decLiteral = 6; 62 | var hexLiteral = 0xf00d; 63 | // ES6 中的二进制表示法 64 | var binaryLiteral = 10; 65 | // ES6 中的八进制表示法 66 | var octalLiteral = 484; 67 | var notANumber = NaN; 68 | var infinityNumber = Infinity; 69 | ``` 70 | 71 | 其中 `0b1010` 和 `0o744` 是 [ES6 中的二进制和八进制表示法][],它们会被编译为十进制数字。 72 | 73 | ## 字符串 74 | 75 | 使用 `string` 定义字符串类型: 76 | 77 | ```ts 78 | let myName: string = 'Tom'; 79 | let myAge: number = 25; 80 | 81 | // 模板字符串 82 | let sentence: string = `Hello, my name is ${myName}. 83 | I'll be ${myAge + 1} years old next month.`; 84 | ``` 85 | 86 | 编译结果: 87 | 88 | ```js 89 | var myName = 'Tom'; 90 | var myAge = 25; 91 | // 模板字符串 92 | var sentence = "Hello, my name is " + myName + ".\nI'll be " + (myAge + 1) + " years old next month."; 93 | ``` 94 | 95 | 其中 ` 用来定义 [ES6 中的模板字符串][],`${expr}` 用来在模板字符串中嵌入表达式。 96 | 97 | ## 空值 98 | 99 | JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 `void` 表示没有任何返回值的函数: 100 | 101 | ```ts 102 | function alertName(): void { 103 | alert('My name is Tom'); 104 | } 105 | ``` 106 | 107 | 声明一个 `void` 类型的变量没有什么用,因为你只能将它赋值为 `undefined` 和 `null`(只在 --strictNullChecks 未指定时): 108 | 109 | ```ts 110 | let unusable: void = undefined; 111 | ``` 112 | 113 | ## Null 和 Undefined 114 | 115 | 在 TypeScript 中,可以使用 `null` 和 `undefined` 来定义这两个原始数据类型: 116 | 117 | ```ts 118 | let u: undefined = undefined; 119 | let n: null = null; 120 | ``` 121 | 122 | 与 `void` 的区别是,`undefined` 和 `null` 是所有类型的子类型。也就是说 `undefined` 类型的变量,可以赋值给 `number` 类型的变量: 123 | 124 | ```ts 125 | // 这样不会报错 126 | let num: number = undefined; 127 | ``` 128 | 129 | ```ts 130 | // 这样也不会报错 131 | let u: undefined; 132 | let num: number = u; 133 | ``` 134 | 135 | 而 `void` 类型的变量不能赋值给 `number` 类型的变量: 136 | 137 | ```ts 138 | let u: void; 139 | let num: number = u; 140 | 141 | // Type 'void' is not assignable to type 'number'. 142 | ``` 143 | 144 | ## 参考 145 | 146 | - [Basic Types](http://www.typescriptlang.org/docs/handbook/basic-types.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Basic%20Types.html)) 147 | - [Primitive data types][] 148 | - [ES6 中的新类型 `Symbol`][] 149 | - [ES6 中的二进制和八进制表示法][] 150 | - [ES6 中的模板字符串][] 151 | 152 | [Primitive data types]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive 153 | [`Symbol`]: http://es6.ruanyifeng.com/#docs/symbol 154 | [`BigInt`]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt 155 | [ES6 中的二进制和八进制表示法]: http://es6.ruanyifeng.com/#docs/number#二进制和八进制表示法 156 | [ES6 中的模板字符串]: http://es6.ruanyifeng.com/#docs/string#模板字符串 157 | -------------------------------------------------------------------------------- /pagic.config.tsx: -------------------------------------------------------------------------------- 1 | import { React } from 'https://deno.land/x/pagic@v1.6.3/mod.ts'; 2 | //import {useEffect, useState} 3 | 4 | export default { 5 | srcDir: '.', 6 | exclude: ['examples'], 7 | theme: 'docs', 8 | plugins: ['sidebar', 'prev_next', 'gitalk', 'ga'], 9 | title: 'TypeScript 入门教程', 10 | description: '从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript', 11 | github: 'https://github.com/xcatliu/typescript-tutorial', 12 | head: , 13 | nav: [ 14 | { 15 | text: '赞助作者', 16 | link: 'https://github.com/xcatliu/buy-me-a-coffee', 17 | target: '_blank', 18 | popover: ( 19 | <> 20 | 21 | 22 | 23 | ), 24 | }, 25 | { 26 | text: '加入微信群', 27 | popover: ( 28 | <> 29 |

一群已满,请扫码加二群

30 | 31 | 32 | ), 33 | }, 34 | { 35 | text: '加入 QQ 群', 36 | link: 'https://jq.qq.com/?_wv=1027&k=5nkkFCl', 37 | target: '_blank', 38 | popover: ( 39 | <> 40 |

41 | 一群(767142358)已满,请扫码加二群(706191218) 42 |

43 | 44 | 45 | ), 46 | }, 47 | { 48 | text: '本网站使用 Pagic 构建', 49 | link: 'https://github.com/xcatliu/pagic', 50 | target: '_blank', 51 | }, 52 | ], 53 | sidebar: { 54 | '/': [ 55 | { 56 | link: 'introduction/README.md', 57 | children: [ 58 | 'introduction/what-is-typescript.md', 59 | 'introduction/get-typescript.md', 60 | 'introduction/hello-typescript.md', 61 | ], 62 | }, 63 | { 64 | link: 'basics/README.md', 65 | children: [ 66 | 'basics/primitive-data-types.md', 67 | 'basics/any.md', 68 | 'basics/type-inference.md', 69 | 'basics/union-types.md', 70 | 'basics/type-of-object-interfaces.md', 71 | 'basics/type-of-array.md', 72 | 'basics/type-of-function.md', 73 | 'basics/type-assertion.md', 74 | 'basics/declaration-files.md', 75 | 'basics/built-in-objects.md', 76 | ], 77 | }, 78 | { 79 | link: 'advanced/README.md', 80 | children: [ 81 | 'advanced/type-aliases.md', 82 | 'advanced/string-literal-types.md', 83 | 'advanced/tuple.md', 84 | 'advanced/enum.md', 85 | 'advanced/class.md', 86 | 'advanced/class-and-interfaces.md', 87 | 'advanced/generics.md', 88 | 'advanced/declaration-merging.md', 89 | 'advanced/decorator.md', 90 | 'advanced/further-reading.md', 91 | ], 92 | }, 93 | { 94 | link: 'engineering/README.md', 95 | children: ['engineering/lint.md', 'engineering/compiler-options.md'], 96 | }, 97 | 'thanks/README.md', 98 | ], 99 | }, 100 | tools: { 101 | editOnGitHub: true, 102 | backToTop: true, 103 | }, 104 | 105 | // 106 | 107 | // tocAd: ( 108 | //
112 | // 113 | // 119 | // ` 123 | // }} 124 | // /> 125 | // ), 126 | 127 | // 128 | gitalk: { 129 | clientID: '29aa4941759fc887ed4f', 130 | clientSecret: '33e355efdf3a1959624506a5d88311145208471b', 131 | repo: 'typescript-tutorial', 132 | owner: 'xcatliu', 133 | admin: ['xcatliu'], 134 | pagerDirection: 'first', 135 | }, 136 | ga: { 137 | id: 'UA-45256157-14', 138 | }, 139 | port: 8001, 140 | }; 141 | -------------------------------------------------------------------------------- /advanced/class-and-interfaces.md: -------------------------------------------------------------------------------- 1 | # 类与接口 2 | 3 | [之前学习过](../basics/type-of-object-interfaces.md),接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述。 4 | 5 | 这一章主要介绍接口的另一个用途,对类的一部分行为进行抽象。 6 | 7 | ## 类实现接口 8 | 9 | 实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 `implements` 关键字来实现。这个特性大大提高了面向对象的灵活性。 10 | 11 | 举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它: 12 | 13 | ```ts 14 | interface Alarm { 15 | alert(): void; 16 | } 17 | 18 | class Door { 19 | } 20 | 21 | class SecurityDoor extends Door implements Alarm { 22 | alert() { 23 | console.log('SecurityDoor alert'); 24 | } 25 | } 26 | 27 | class Car implements Alarm { 28 | alert() { 29 | console.log('Car alert'); 30 | } 31 | } 32 | ``` 33 | 34 | 一个类可以实现多个接口: 35 | 36 | ```ts 37 | interface Alarm { 38 | alert(): void; 39 | } 40 | 41 | interface Light { 42 | lightOn(): void; 43 | lightOff(): void; 44 | } 45 | 46 | class Car implements Alarm, Light { 47 | alert() { 48 | console.log('Car alert'); 49 | } 50 | lightOn() { 51 | console.log('Car light on'); 52 | } 53 | lightOff() { 54 | console.log('Car light off'); 55 | } 56 | } 57 | ``` 58 | 59 | 上例中,`Car` 实现了 `Alarm` 和 `Light` 接口,既能报警,也能开关车灯。 60 | 61 | ## 接口继承接口 62 | 63 | 接口与接口之间可以是继承关系: 64 | 65 | ```ts 66 | interface Alarm { 67 | alert(): void; 68 | } 69 | 70 | interface LightableAlarm extends Alarm { 71 | lightOn(): void; 72 | lightOff(): void; 73 | } 74 | ``` 75 | 76 | 这很好理解,`LightableAlarm` 继承了 `Alarm`,除了拥有 `alert` 方法之外,还拥有两个新方法 `lightOn` 和 `lightOff`。 77 | 78 | ## 接口继承类 79 | 80 | 常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可以的: 81 | 82 | ```ts 83 | class Point { 84 | x: number; 85 | y: number; 86 | constructor(x: number, y: number) { 87 | this.x = x; 88 | this.y = y; 89 | } 90 | } 91 | 92 | interface Point3d extends Point { 93 | z: number; 94 | } 95 | 96 | let point3d: Point3d = {x: 1, y: 2, z: 3}; 97 | ``` 98 | 99 | 为什么 TypeScript 会支持接口继承类呢? 100 | 101 | 实际上,当我们在声明 `class Point` 时,除了会创建一个名为 `Point` 的类之外,同时也创建了一个名为 `Point` 的类型(实例的类型)。 102 | 103 | 所以我们既可以将 `Point` 当做一个类来用(使用 `new Point` 创建它的实例): 104 | 105 | ```ts 106 | class Point { 107 | x: number; 108 | y: number; 109 | constructor(x: number, y: number) { 110 | this.x = x; 111 | this.y = y; 112 | } 113 | } 114 | 115 | const p = new Point(1, 2); 116 | ``` 117 | 118 | 也可以将 `Point` 当做一个类型来用(使用 `: Point` 表示参数的类型): 119 | 120 | ```ts 121 | class Point { 122 | x: number; 123 | y: number; 124 | constructor(x: number, y: number) { 125 | this.x = x; 126 | this.y = y; 127 | } 128 | } 129 | 130 | function printPoint(p: Point) { 131 | console.log(p.x, p.y); 132 | } 133 | 134 | printPoint(new Point(1, 2)); 135 | ``` 136 | 137 | 这个例子实际上可以等价于: 138 | 139 | ```ts 140 | class Point { 141 | x: number; 142 | y: number; 143 | constructor(x: number, y: number) { 144 | this.x = x; 145 | this.y = y; 146 | } 147 | } 148 | 149 | interface PointInstanceType { 150 | x: number; 151 | y: number; 152 | } 153 | 154 | function printPoint(p: PointInstanceType) { 155 | console.log(p.x, p.y); 156 | } 157 | 158 | printPoint(new Point(1, 2)); 159 | ``` 160 | 161 | 上例中我们新声明的 `PointInstanceType` 类型,与声明 `class Point` 时创建的 `Point` 类型是等价的。 162 | 163 | 所以回到 `Point3d` 的例子中,我们就能很容易的理解为什么 TypeScript 会支持接口继承类了: 164 | 165 | ```ts 166 | class Point { 167 | x: number; 168 | y: number; 169 | constructor(x: number, y: number) { 170 | this.x = x; 171 | this.y = y; 172 | } 173 | } 174 | 175 | interface PointInstanceType { 176 | x: number; 177 | y: number; 178 | } 179 | 180 | // 等价于 interface Point3d extends PointInstanceType 181 | interface Point3d extends Point { 182 | z: number; 183 | } 184 | 185 | let point3d: Point3d = {x: 1, y: 2, z: 3}; 186 | ``` 187 | 188 | 当我们声明 `interface Point3d extends Point` 时,`Point3d` 继承的实际上是类 `Point` 的实例的类型。 189 | 190 | 换句话说,可以理解为定义了一个接口 `Point3d` 继承另一个接口 `PointInstanceType`。 191 | 192 | 所以「接口继承类」和「接口继承接口」没有什么本质的区别。 193 | 194 | 值得注意的是,`PointInstanceType` 相比于 `Point`,缺少了 `constructor` 方法,这是因为声明 `Point` 类时创建的 `Point` 类型是不包含构造函数的。另外,除了构造函数是不包含的,静态属性或静态方法也是不包含的(实例的类型当然不应该包括构造函数、静态属性或静态方法)。 195 | 196 | 换句话说,声明 `Point` 类时创建的 `Point` 类型只包含其中的实例属性和实例方法: 197 | 198 | ```ts 199 | class Point { 200 | /** 静态属性,坐标系原点 */ 201 | static origin = new Point(0, 0); 202 | /** 静态方法,计算与原点距离 */ 203 | static distanceToOrigin(p: Point) { 204 | return Math.sqrt(p.x * p.x + p.y * p.y); 205 | } 206 | /** 实例属性,x 轴的值 */ 207 | x: number; 208 | /** 实例属性,y 轴的值 */ 209 | y: number; 210 | /** 构造函数 */ 211 | constructor(x: number, y: number) { 212 | this.x = x; 213 | this.y = y; 214 | } 215 | /** 实例方法,打印此点 */ 216 | printPoint() { 217 | console.log(this.x, this.y); 218 | } 219 | } 220 | 221 | interface PointInstanceType { 222 | x: number; 223 | y: number; 224 | printPoint(): void; 225 | } 226 | 227 | let p1: Point; 228 | let p2: PointInstanceType; 229 | ``` 230 | 231 | 上例中最后的类型 `Point` 和类型 `PointInstanceType` 是等价的。 232 | 233 | 同样的,在接口继承类的时候,也只会继承它的实例属性和实例方法。 234 | 235 | ## 参考 236 | 237 | - [Interfaces](http://www.typescriptlang.org/docs/handbook/interfaces.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html)) 238 | -------------------------------------------------------------------------------- /advanced/generics.md: -------------------------------------------------------------------------------- 1 | # 泛型 2 | 3 | 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。 4 | 5 | ## 简单的例子 6 | 7 | 首先,我们来实现一个函数 `createArray`,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值: 8 | 9 | ```ts 10 | function createArray(length: number, value: any): Array { 11 | let result = []; 12 | for (let i = 0; i < length; i++) { 13 | result[i] = value; 14 | } 15 | return result; 16 | } 17 | 18 | createArray(3, 'x'); // ['x', 'x', 'x'] 19 | ``` 20 | 21 | 上例中,我们使用了[之前提到过的数组泛型](../basics/type-of-array.md#数组泛型)来定义返回值的类型。 22 | 23 | 这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型: 24 | 25 | `Array` 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 `value` 的类型。 26 | 27 | 这时候,泛型就派上用场了: 28 | 29 | ```ts 30 | function createArray(length: number, value: T): Array { 31 |   let result: T[] = []; 32 | for (let i = 0; i < length; i++) { 33 | result[i] = value; 34 | } 35 | return result; 36 | } 37 | 38 | createArray(3, 'x'); // ['x', 'x', 'x'] 39 | ``` 40 | 41 | 上例中,我们在函数名后添加了 ``,其中 `T` 用来指代任意输入的类型,在后面的输入 `value: T` 和输出 `Array` 中即可使用了。 42 | 43 | 接着在调用的时候,可以指定它具体的类型为 `string`。当然,也可以不手动指定,而让类型推论自动推算出来: 44 | 45 | ```ts 46 | function createArray(length: number, value: T): Array { 47 | let result: T[] = []; 48 | for (let i = 0; i < length; i++) { 49 | result[i] = value; 50 | } 51 | return result; 52 | } 53 | 54 | createArray(3, 'x'); // ['x', 'x', 'x'] 55 | ``` 56 | 57 | ## 多个类型参数 58 | 59 | 定义泛型的时候,可以一次定义多个类型参数: 60 | 61 | ```ts 62 | function swap(tuple: [T, U]): [U, T] { 63 | return [tuple[1], tuple[0]]; 64 | } 65 | 66 | swap([7, 'seven']); // ['seven', 7] 67 | ``` 68 | 69 | 上例中,我们定义了一个 `swap` 函数,用来交换输入的元组。 70 | 71 | ## 泛型约束 72 | 73 | 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法: 74 | 75 | ```ts 76 | function loggingIdentity(arg: T): T { 77 | console.log(arg.length); 78 | return arg; 79 | } 80 | 81 | // index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'. 82 | ``` 83 | 84 | 上例中,泛型 `T` 不一定包含属性 `length`,所以编译的时候报错了。 85 | 86 | 这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 `length` 属性的变量。这就是泛型约束: 87 | 88 | ```ts 89 | interface Lengthwise { 90 | length: number; 91 | } 92 | 93 | function loggingIdentity(arg: T): T { 94 | console.log(arg.length); 95 | return arg; 96 | } 97 | ``` 98 | 99 | 上例中,我们使用了 `extends` 约束了泛型 `T` 必须符合接口 `Lengthwise` 的形状,也就是必须包含 `length` 属性。 100 | 101 | 此时如果调用 `loggingIdentity` 的时候,传入的 `arg` 不包含 `length`,那么在编译阶段就会报错了: 102 | 103 | ```ts 104 | interface Lengthwise { 105 | length: number; 106 | } 107 | 108 | function loggingIdentity(arg: T): T { 109 | console.log(arg.length); 110 | return arg; 111 | } 112 | 113 | loggingIdentity(7); 114 | 115 | // index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'. 116 | ``` 117 | 118 | 多个类型参数之间也可以互相约束: 119 | 120 | ```ts 121 | function copyFields(target: T, source: U): T { 122 | for (let id in source) { 123 | target[id] = (source)[id]; 124 | } 125 | return target; 126 | } 127 | 128 | let x = { a: 1, b: 2, c: 3, d: 4 }; 129 | 130 | copyFields(x, { b: 10, d: 20 }); 131 | ``` 132 | 133 | 上例中,我们使用了两个类型参数,其中要求 `T` 继承 `U`,这样就保证了 `U` 上不会出现 `T` 中不存在的字段。 134 | 135 | ## 泛型接口 136 | 137 | [之前学习过](../basics/type-of-function.md#接口中函数的定义),可以使用接口的方式来定义一个函数需要符合的形状: 138 | 139 | ```ts 140 | interface SearchFunc { 141 | (source: string, subString: string): boolean; 142 | } 143 | 144 | let mySearch: SearchFunc; 145 | mySearch = function(source: string, subString: string) { 146 | return source.search(subString) !== -1; 147 | } 148 | ``` 149 | 150 | 当然也可以使用含有泛型的接口来定义函数的形状: 151 | 152 | ```ts 153 | interface CreateArrayFunc { 154 | (length: number, value: T): Array; 155 | } 156 | 157 | let createArray: CreateArrayFunc; 158 | createArray = function(length: number, value: T): Array { 159 | let result: T[] = []; 160 | for (let i = 0; i < length; i++) { 161 | result[i] = value; 162 | } 163 | return result; 164 | } 165 | 166 | createArray(3, 'x'); // ['x', 'x', 'x'] 167 | ``` 168 | 169 | 进一步,我们可以把泛型参数提前到接口名上: 170 | 171 | ```ts 172 | interface CreateArrayFunc { 173 | (length: number, value: T): Array; 174 | } 175 | 176 | let createArray: CreateArrayFunc; 177 | createArray = function(length: number, value: T): Array { 178 | let result: T[] = []; 179 | for (let i = 0; i < length; i++) { 180 | result[i] = value; 181 | } 182 | return result; 183 | } 184 | 185 | createArray(3, 'x'); // ['x', 'x', 'x'] 186 | ``` 187 | 188 | 注意,此时在使用泛型接口的时候,需要定义泛型的类型。 189 | 190 | ## 泛型类 191 | 192 | 与泛型接口类似,泛型也可以用于类的类型定义中: 193 | 194 | ```ts 195 | class GenericNumber { 196 | zeroValue: T; 197 | add: (x: T, y: T) => T; 198 | } 199 | 200 | let myGenericNumber = new GenericNumber(); 201 | myGenericNumber.zeroValue = 0; 202 | myGenericNumber.add = function(x, y) { return x + y; }; 203 | ``` 204 | 205 | ## 泛型参数的默认类型 206 | 207 | 在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。 208 | 209 | ```ts 210 | function createArray(length: number, value: T): Array { 211 | let result: T[] = []; 212 | for (let i = 0; i < length; i++) { 213 | result[i] = value; 214 | } 215 | return result; 216 | } 217 | ``` 218 | 219 | ## 参考 220 | 221 | - [Generics](http://www.typescriptlang.org/docs/handbook/generics.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/generics.html)) 222 | - [Generic parameter defaults](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#generic-parameter-defaults) 223 | -------------------------------------------------------------------------------- /basics/type-of-object-interfaces.md: -------------------------------------------------------------------------------- 1 | # 对象的类型——接口 2 | 3 | 在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。 4 | 5 | ## 什么是接口 6 | 7 | 在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。 8 | 9 | TypeScript 中的接口是一个非常灵活的概念,除了可用于[对类的一部分行为进行抽象](../advanced/class-and-interfaces.md#类实现接口)以外,也常用于对「对象的形状(Shape)」进行描述。 10 | 11 | ## 简单的例子 12 | 13 | ```ts 14 | interface Person { 15 | name: string; 16 | age: number; 17 | } 18 | 19 | let tom: Person = { 20 | name: 'Tom', 21 | age: 25 22 | }; 23 | ``` 24 | 25 | 上面的例子中,我们定义了一个接口 `Person`,接着定义了一个变量 `tom`,它的类型是 `Person`。这样,我们就约束了 `tom` 的形状必须和接口 `Person` 一致。 26 | 27 | 接口一般首字母大写。[有的编程语言中会建议接口的名称加上 `I` 前缀](https://msdn.microsoft.com/en-us/library/8bc1fexb%28v=vs.71%29.aspx)。 28 | 29 | 定义的变量比接口少了一些属性是不允许的: 30 | 31 | ```ts 32 | interface Person { 33 | name: string; 34 | age: number; 35 | } 36 | 37 | let tom: Person = { 38 | name: 'Tom' 39 | }; 40 | 41 | // index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'. 42 | // Property 'age' is missing in type '{ name: string; }'. 43 | ``` 44 | 45 | 多一些属性也是不允许的: 46 | 47 | ```ts 48 | interface Person { 49 | name: string; 50 | age: number; 51 | } 52 | 53 | let tom: Person = { 54 | name: 'Tom', 55 | age: 25, 56 | gender: 'male' 57 | }; 58 | 59 | // index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. 60 | // Object literal may only specify known properties, and 'gender' does not exist in type 'Person'. 61 | ``` 62 | 63 | 可见,**赋值的时候,变量的形状必须和接口的形状保持一致**。 64 | 65 | ## 可选属性 66 | 67 | 有时我们希望不要完全匹配一个形状,那么可以用可选属性: 68 | 69 | ```ts 70 | interface Person { 71 | name: string; 72 | age?: number; 73 | } 74 | 75 | let tom: Person = { 76 | name: 'Tom' 77 | }; 78 | ``` 79 | 80 | ```ts 81 | interface Person { 82 | name: string; 83 | age?: number; 84 | } 85 | 86 | let tom: Person = { 87 | name: 'Tom', 88 | age: 25 89 | }; 90 | ``` 91 | 92 | 可选属性的含义是该属性可以不存在。 93 | 94 | 这时**仍然不允许添加未定义的属性**: 95 | 96 | ```ts 97 | interface Person { 98 | name: string; 99 | age?: number; 100 | } 101 | 102 | let tom: Person = { 103 | name: 'Tom', 104 | age: 25, 105 | gender: 'male' 106 | }; 107 | 108 | // examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. 109 | // Object literal may only specify known properties, and 'gender' does not exist in type 'Person'. 110 | ``` 111 | 112 | ## 任意属性 113 | 114 | 有时候我们希望一个接口允许有任意的属性,可以使用如下方式: 115 | 116 | ```ts 117 | interface Person { 118 | name: string; 119 | age?: number; 120 | [propName: string]: any; 121 | } 122 | 123 | let tom: Person = { 124 | name: 'Tom', 125 | gender: 'male' 126 | }; 127 | ``` 128 | 129 | 使用 `[propName: string]` 定义了任意属性取 `string` 类型的值。 130 | 131 | 需要注意的是,**一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集**: 132 | 133 | ```ts 134 | interface Person { 135 | name: string; 136 | age?: number; 137 | [propName: string]: string; 138 | } 139 | 140 | let tom: Person = { 141 | name: 'Tom', 142 | age: 25, 143 | gender: 'male' 144 | }; 145 | 146 | // index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'. 147 | // index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'. 148 | // Index signatures are incompatible. 149 | // Type 'string | number' is not assignable to type 'string'. 150 | // Type 'number' is not assignable to type 'string'. 151 | ``` 152 | 153 | 上例中,任意属性的值允许是 `string`,但是可选属性 `age` 的值却是 `number`,`number` 不是 `string` 的子属性,所以报错了。 154 | 155 | 另外,在报错信息中可以看出,此时 `{ name: 'Tom', age: 25, gender: 'male' }` 的类型被推断成了 `{ [x: string]: string | number; name: string; age: number; gender: string; }`,这是联合类型和接口的结合。 156 | 157 | 一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型: 158 | ```ts 159 | interface Person { 160 | name: string; 161 | age?: number; 162 | [propName: string]: string | number; 163 | } 164 | 165 | let tom: Person = { 166 | name: 'Tom', 167 | age: 25, 168 | gender: 'male' 169 | }; 170 | ``` 171 | 172 | ## 只读属性 173 | 174 | 有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 `readonly` 定义只读属性: 175 | 176 | ```ts 177 | interface Person { 178 | readonly id: number; 179 | name: string; 180 | age?: number; 181 | [propName: string]: any; 182 | } 183 | 184 | let tom: Person = { 185 | id: 89757, 186 | name: 'Tom', 187 | gender: 'male' 188 | }; 189 | 190 | tom.id = 9527; 191 | 192 | // index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property. 193 | ``` 194 | 195 | 上例中,使用 `readonly` 定义的属性 `id` 初始化后,又被赋值了,所以报错了。 196 | 197 | **注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候**: 198 | 199 | ```ts 200 | interface Person { 201 | readonly id: number; 202 | name: string; 203 | age?: number; 204 | [propName: string]: any; 205 | } 206 | 207 | let tom: Person = { 208 | name: 'Tom', 209 | gender: 'male' 210 | }; 211 | 212 | tom.id = 89757; 213 | 214 | // index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'. 215 | // Property 'id' is missing in type '{ name: string; gender: string; }'. 216 | // index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property. 217 | ``` 218 | 219 | 上例中,报错信息有两处,第一处是在对 `tom` 进行赋值的时候,没有给 `id` 赋值。 220 | 221 | 第二处是在给 `tom.id` 赋值的时候,由于它是只读属性,所以报错了。 222 | 223 | ## 参考 224 | 225 | - [Interfaces](http://www.typescriptlang.org/docs/handbook/interfaces.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html)) 226 | -------------------------------------------------------------------------------- /advanced/enum.md: -------------------------------------------------------------------------------- 1 | # 枚举 2 | 3 | 枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。 4 | 5 | ## 简单的例子 6 | 7 | 枚举使用 `enum` 关键字来定义: 8 | 9 | ```ts 10 | enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; 11 | ``` 12 | 13 | 枚举成员会被赋值为从 `0` 开始递增的数字,同时也会对枚举值到枚举名进行反向映射: 14 | 15 | ```ts 16 | enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; 17 | 18 | console.log(Days["Sun"] === 0); // true 19 | console.log(Days["Mon"] === 1); // true 20 | console.log(Days["Tue"] === 2); // true 21 | console.log(Days["Sat"] === 6); // true 22 | 23 | console.log(Days[0] === "Sun"); // true 24 | console.log(Days[1] === "Mon"); // true 25 | console.log(Days[2] === "Tue"); // true 26 | console.log(Days[6] === "Sat"); // true 27 | ``` 28 | 29 | 事实上,上面的例子会被编译为: 30 | 31 | ```js 32 | var Days; 33 | (function (Days) { 34 | Days[Days["Sun"] = 0] = "Sun"; 35 | Days[Days["Mon"] = 1] = "Mon"; 36 | Days[Days["Tue"] = 2] = "Tue"; 37 | Days[Days["Wed"] = 3] = "Wed"; 38 | Days[Days["Thu"] = 4] = "Thu"; 39 | Days[Days["Fri"] = 5] = "Fri"; 40 | Days[Days["Sat"] = 6] = "Sat"; 41 | })(Days || (Days = {})); 42 | ``` 43 | 44 | ## 手动赋值 45 | 46 | 我们也可以给枚举项手动赋值: 47 | 48 | ```ts 49 | enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}; 50 | 51 | console.log(Days["Sun"] === 7); // true 52 | console.log(Days["Mon"] === 1); // true 53 | console.log(Days["Tue"] === 2); // true 54 | console.log(Days["Sat"] === 6); // true 55 | ``` 56 | 57 | 上面的例子中,未手动赋值的枚举项会接着上一个枚举项递增。 58 | 59 | 如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的: 60 | 61 | ```ts 62 | enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat}; 63 | 64 | console.log(Days["Sun"] === 3); // true 65 | console.log(Days["Wed"] === 3); // true 66 | console.log(Days[3] === "Sun"); // false 67 | console.log(Days[3] === "Wed"); // true 68 | ``` 69 | 70 | 上面的例子中,递增到 `3` 的时候与前面的 `Sun` 的取值重复了,但是 TypeScript 并没有报错,导致 `Days[3]` 的值先是 `"Sun"`,而后又被 `"Wed"` 覆盖了。编译的结果是: 71 | 72 | ```js 73 | var Days; 74 | (function (Days) { 75 | Days[Days["Sun"] = 3] = "Sun"; 76 | Days[Days["Mon"] = 1] = "Mon"; 77 | Days[Days["Tue"] = 2] = "Tue"; 78 | Days[Days["Wed"] = 3] = "Wed"; 79 | Days[Days["Thu"] = 4] = "Thu"; 80 | Days[Days["Fri"] = 5] = "Fri"; 81 | Days[Days["Sat"] = 6] = "Sat"; 82 | })(Days || (Days = {})); 83 | ``` 84 | 85 | 所以使用的时候需要注意,最好不要出现这种覆盖的情况。 86 | 87 | 手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的): 88 | 89 | ```ts 90 | enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = "S"}; 91 | ``` 92 | 93 | ```js 94 | var Days; 95 | (function (Days) { 96 | Days[Days["Sun"] = 7] = "Sun"; 97 | Days[Days["Mon"] = 8] = "Mon"; 98 | Days[Days["Tue"] = 9] = "Tue"; 99 | Days[Days["Wed"] = 10] = "Wed"; 100 | Days[Days["Thu"] = 11] = "Thu"; 101 | Days[Days["Fri"] = 12] = "Fri"; 102 | Days[Days["Sat"] = "S"] = "Sat"; 103 | })(Days || (Days = {})); 104 | ``` 105 | 106 | 当然,手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 `1`: 107 | 108 | ```ts 109 | enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat}; 110 | 111 | console.log(Days["Sun"] === 7); // true 112 | console.log(Days["Mon"] === 1.5); // true 113 | console.log(Days["Tue"] === 2.5); // true 114 | console.log(Days["Sat"] === 6.5); // true 115 | ``` 116 | 117 | ## 常数项和计算所得项 118 | 119 | 枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。 120 | 121 | 前面我们所举的例子都是常数项,一个典型的计算所得项的例子: 122 | 123 | ```ts 124 | enum Color {Red, Green, Blue = "blue".length}; 125 | ``` 126 | 127 | 上面的例子中,`"blue".length` 就是一个计算所得项。 128 | 129 | 上面的例子不会报错,但是**如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错**: 130 | 131 | ```ts 132 | enum Color {Red = "red".length, Green, Blue}; 133 | 134 | // index.ts(1,33): error TS1061: Enum member must have initializer. 135 | // index.ts(1,40): error TS1061: Enum member must have initializer. 136 | ``` 137 | 138 | 下面是常数项和计算所得项的完整定义,部分引用自[中文手册 - 枚举]: 139 | 140 | 当满足以下条件时,枚举成员被当作是常数: 141 | 142 | - 不具有初始化函数并且之前的枚举成员是常数。在这种情况下,当前枚举成员的值为上一个枚举成员的值加 `1`。但第一个枚举元素是个例外。如果它没有初始化方法,那么它的初始值为 `0`。 143 | - 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常数枚举表达式: 144 | - 数字字面量 145 | - 引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的)如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用 146 | - 带括号的常数枚举表达式 147 | - `+`, `-`, `~` 一元运算符应用于常数枚举表达式 148 | - `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `>>>`, `&`, `|`, `^` 二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错 149 | 150 | 所有其它情况的枚举成员被当作是需要计算得出的值。 151 | 152 | ## 常数枚举 153 | 154 | 常数枚举是使用 `const enum` 定义的枚举类型: 155 | 156 | ```ts 157 | const enum Directions { 158 | Up, 159 | Down, 160 | Left, 161 | Right 162 | } 163 | 164 | let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; 165 | ``` 166 | 167 | 常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。 168 | 169 | 上例的编译结果是: 170 | 171 | ```js 172 | var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]; 173 | ``` 174 | 175 | 假如包含了计算成员,则会在编译阶段报错: 176 | 177 | ```ts 178 | const enum Color {Red, Green, Blue = "blue".length}; 179 | 180 | // index.ts(1,38): error TS2474: In 'const' enum declarations member initializer must be constant expression. 181 | ``` 182 | 183 | ## 外部枚举 184 | 185 | 外部枚举(Ambient Enums)是使用 `declare enum` 定义的枚举类型: 186 | 187 | ```ts 188 | declare enum Directions { 189 | Up, 190 | Down, 191 | Left, 192 | Right 193 | } 194 | 195 | let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; 196 | ``` 197 | 198 | 之前提到过,`declare` 定义的类型只会用于编译时的检查,编译结果中会被删除。 199 | 200 | 上例的编译结果是: 201 | 202 | ```js 203 | var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; 204 | ``` 205 | 206 | 外部枚举与声明语句一样,常出现在声明文件中。 207 | 208 | 同时使用 `declare` 和 `const` 也是可以的: 209 | 210 | ```ts 211 | declare const enum Directions { 212 | Up, 213 | Down, 214 | Left, 215 | Right 216 | } 217 | 218 | let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; 219 | ``` 220 | 221 | 编译结果: 222 | 223 | ```js 224 | var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]; 225 | ``` 226 | 227 | > TypeScript 的枚举类型的概念[来源于 C#][C# Enum]。 228 | 229 | ## 参考 230 | 231 | - [Enums](http://www.typescriptlang.org/docs/handbook/enums.html)([中文版][中文手册 - 枚举]) 232 | - [C# Enum][] 233 | 234 | [中文手册 - 枚举]: https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Enums.html 235 | [C# Enum]: https://msdn.microsoft.com/zh-cn/library/sbbt4032.aspx 236 | -------------------------------------------------------------------------------- /basics/type-of-function.md: -------------------------------------------------------------------------------- 1 | # 函数的类型 2 | 3 | > [函数是 JavaScript 中的一等公民](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch2.html) 4 | 5 | ## 函数声明 6 | 7 | 在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression): 8 | 9 | ```js 10 | // 函数声明(Function Declaration) 11 | function sum(x, y) { 12 | return x + y; 13 | } 14 | 15 | // 函数表达式(Function Expression) 16 | let mySum = function (x, y) { 17 | return x + y; 18 | }; 19 | ``` 20 | 21 | 一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单: 22 | 23 | ```ts 24 | function sum(x: number, y: number): number { 25 | return x + y; 26 | } 27 | ``` 28 | 29 | 注意,**输入多余的(或者少于要求的)参数,是不被允许的**: 30 | 31 | ```ts 32 | function sum(x: number, y: number): number { 33 | return x + y; 34 | } 35 | sum(1, 2, 3); 36 | 37 | // index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target. 38 | ``` 39 | 40 | ```ts 41 | function sum(x: number, y: number): number { 42 | return x + y; 43 | } 44 | sum(1); 45 | 46 | // index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target. 47 | ``` 48 | 49 | ## 函数表达式 50 | 51 | 如果要我们现在写一个函数表达式(Function Expression)的定义,可能会写成这样: 52 | 53 | ```ts 54 | let mySum = function (x: number, y: number): number { 55 | return x + y; 56 | }; 57 | ``` 58 | 59 | 这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 `mySum`,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 `mySum` 添加类型,则应该是这样: 60 | 61 | ```ts 62 | let mySum: (x: number, y: number) => number = function (x: number, y: number): number { 63 | return x + y; 64 | }; 65 | ``` 66 | 67 | 注意不要混淆了 TypeScript 中的 `=>` 和 ES6 中的 `=>`。 68 | 69 | 在 TypeScript 的类型定义中,`=>` 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。 70 | 71 | 在 ES6 中,`=>` 叫做箭头函数,应用十分广泛,可以参考 [ES6 中的箭头函数][]。 72 | 73 | ## 用接口定义函数的形状 74 | 75 | 我们也可以使用接口的方式来定义一个函数需要符合的形状: 76 | 77 | ```ts 78 | interface SearchFunc { 79 | (source: string, subString: string): boolean; 80 | } 81 | 82 | let mySearch: SearchFunc; 83 | mySearch = function(source: string, subString: string) { 84 | return source.search(subString) !== -1; 85 | } 86 | ``` 87 | 88 | 采用函数表达式|接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。 89 | 90 | ## 可选参数 91 | 92 | 前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢? 93 | 94 | 与接口中的可选属性类似,我们用 `?` 表示可选的参数: 95 | 96 | ```ts 97 | function buildName(firstName: string, lastName?: string) { 98 | if (lastName) { 99 | return firstName + ' ' + lastName; 100 | } else { 101 | return firstName; 102 | } 103 | } 104 | let tomcat = buildName('Tom', 'Cat'); 105 | let tom = buildName('Tom'); 106 | ``` 107 | 108 | 需要注意的是,可选参数必须接在必需参数后面。换句话说,**可选参数后面不允许再出现必需参数了**: 109 | 110 | ```ts 111 | function buildName(firstName?: string, lastName: string) { 112 | if (firstName) { 113 | return firstName + ' ' + lastName; 114 | } else { 115 | return lastName; 116 | } 117 | } 118 | let tomcat = buildName('Tom', 'Cat'); 119 | let tom = buildName(undefined, 'Tom'); 120 | 121 | // index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter. 122 | ``` 123 | 124 | ## 参数默认值 125 | 126 | 在 ES6 中,我们允许给函数的参数添加默认值,**TypeScript 会将添加了默认值的参数识别为可选参数**: 127 | 128 | ```ts 129 | function buildName(firstName: string, lastName: string = 'Cat') { 130 | return firstName + ' ' + lastName; 131 | } 132 | let tomcat = buildName('Tom', 'Cat'); 133 | let tom = buildName('Tom'); 134 | ``` 135 | 136 | 此时就不受「可选参数必须接在必需参数后面」的限制了: 137 | 138 | ```ts 139 | function buildName(firstName: string = 'Tom', lastName: string) { 140 | return firstName + ' ' + lastName; 141 | } 142 | let tomcat = buildName('Tom', 'Cat'); 143 | let cat = buildName(undefined, 'Cat'); 144 | ``` 145 | 146 | > 关于默认参数,可以参考 [ES6 中函数参数的默认值][]。 147 | 148 | ## 剩余参数 149 | 150 | ES6 中,可以使用 `...rest` 的方式获取函数中的剩余参数(rest 参数): 151 | 152 | ```js 153 | function push(array, ...items) { 154 | items.forEach(function(item) { 155 | array.push(item); 156 | }); 157 | } 158 | 159 | let a: any[] = []; 160 | push(a, 1, 2, 3); 161 | ``` 162 | 163 | 事实上,`items` 是一个数组。所以我们可以用数组的类型来定义它: 164 | 165 | ```ts 166 | function push(array: any[], ...items: any[]) { 167 | items.forEach(function(item) { 168 | array.push(item); 169 | }); 170 | } 171 | 172 | let a = []; 173 | push(a, 1, 2, 3); 174 | ``` 175 | 176 | 注意,rest 参数只能是最后一个参数,关于 rest 参数,可以参考 [ES6 中的 rest 参数][]。 177 | 178 | ## 重载 179 | 180 | 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。 181 | 182 | 比如,我们需要实现一个函数 `reverse`,输入数字 `123` 的时候,输出反转的数字 `321`,输入字符串 `'hello'` 的时候,输出反转的字符串 `'olleh'`。 183 | 184 | 利用联合类型,我们可以这么实现: 185 | 186 | ```ts 187 | function reverse(x: number | string): number | string | void { 188 | if (typeof x === 'number') { 189 | return Number(x.toString().split('').reverse().join('')); 190 | } else if (typeof x === 'string') { 191 | return x.split('').reverse().join(''); 192 | } 193 | } 194 | ``` 195 | 196 | **然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。** 197 | 198 | 这时,我们可以使用重载定义多个 `reverse` 的函数类型: 199 | 200 | ```ts 201 | function reverse(x: number): number; 202 | function reverse(x: string): string; 203 | function reverse(x: number | string): number | string | void { 204 | if (typeof x === 'number') { 205 | return Number(x.toString().split('').reverse().join('')); 206 | } else if (typeof x === 'string') { 207 | return x.split('').reverse().join(''); 208 | } 209 | } 210 | ``` 211 | 212 | 上例中,我们重复定义了多次函数 `reverse`,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。 213 | 214 | 注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。 215 | 216 | ## 参考 217 | 218 | - [Functions](http://www.typescriptlang.org/docs/handbook/functions.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Functions.html)) 219 | - [Functions # Function Types](http://www.typescriptlang.org/docs/handbook/interfaces.html#function-types)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html#函数类型)) 220 | - [JS 函数式编程指南](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/) 221 | - [ES6 中的箭头函数] 222 | - [ES6 中函数参数的默认值] 223 | - [ES6 中的 rest 参数] 224 | 225 | [ES6 中的箭头函数]: http://es6.ruanyifeng.com/#docs/function#箭头函数 226 | [ES6 中函数参数的默认值]: http://es6.ruanyifeng.com/#docs/function#函数参数的默认值 227 | [ES6 中的 rest 参数]: http://es6.ruanyifeng.com/#docs/function#rest参数 228 | -------------------------------------------------------------------------------- /introduction/what-is-typescript.md: -------------------------------------------------------------------------------- 1 | # 什么是 TypeScript 2 | 3 | > Typed JavaScript at Any Scale. 4 | > 添加了类型系统的 JavaScript,适用于任何规模的项目。 5 | 6 | 以上描述是官网[[1]](#link-1)对于 TypeScript 的定义。 7 | 8 | 它强调了 TypeScript 的两个最重要的特性——类型系统、适用于任何规模。 9 | 10 | ## TypeScript 的特性 11 | 12 | ### 类型系统 13 | 14 | 从 TypeScript 的名字就可以看出来,「类型」是其最核心的特性。 15 | 16 | 我们知道,JavaScript 是一门非常灵活的编程语言: 17 | 18 | - 它没有类型约束,一个变量可能初始化时是字符串,过一会儿又被赋值为数字。 19 | - 由于隐式类型转换的存在,有的变量的类型很难在运行前就确定。 20 | - 基于原型的面向对象编程,使得原型上的属性或方法可以在运行时被修改。 21 | - 函数是 JavaScript 中的一等公民[[2]](#link-2),可以赋值给变量,也可以当作参数或返回值。 22 | 23 | 这种灵活性就像一把双刃剑,一方面使得 JavaScript 蓬勃发展,无所不能,从 2013 年开始就一直蝉联最普遍使用的编程语言排行榜冠军[[3]](#link-3);另一方面也使得它的代码质量参差不齐,维护成本高,运行时错误多。 24 | 25 | 而 TypeScript 的类型系统,在很大程度上弥补了 JavaScript 的缺点。 26 | 27 | #### TypeScript 是静态类型 28 | 29 | 类型系统按照「类型检查的时机」来分类,可以分为动态类型和静态类型。 30 | 31 | 动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。JavaScript 是一门解释型语言[[4]](#link-4),没有编译阶段,所以它是动态类型,以下这段代码在运行时才会报错: 32 | 33 | ```js 34 | let foo = 1; 35 | foo.split(' '); 36 | // Uncaught TypeError: foo.split is not a function 37 | // 运行时会报错(foo.split 不是一个函数),造成线上 bug 38 | ``` 39 | 40 | 静态类型是指编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 **TypeScript 是静态类型**,这段 TypeScript 代码在编译阶段就会报错了: 41 | 42 | ```ts 43 | let foo = 1; 44 | foo.split(' '); 45 | // Property 'split' does not exist on type 'number'. 46 | // 编译时会报错(数字没有 split 方法),无法通过编译 47 | ``` 48 | 49 | 你可能会奇怪,这段 TypeScript 代码看上去和 JavaScript 没有什么区别呀。 50 | 51 | 没错!大部分 JavaScript 代码都只需要经过少量的修改(或者完全不用修改)就变成 TypeScript 代码,这得益于 TypeScript 强大的[类型推论][],即使不去手动声明变量 `foo` 的类型,也能在变量初始化时自动推论出它是一个 `number` 类型。 52 | 53 | 完整的 TypeScript 代码是这样的: 54 | 55 | ```ts 56 | let foo: number = 1; 57 | foo.split(' '); 58 | // Property 'split' does not exist on type 'number'. 59 | // 编译时会报错(数字没有 split 方法),无法通过编译 60 | ``` 61 | 62 | #### TypeScript 是弱类型 63 | 64 | 类型系统按照「是否允许隐式类型转换」来分类,可以分为强类型和弱类型。 65 | 66 | 以下这段代码不管是在 JavaScript 中还是在 TypeScript 中都是可以正常运行的,运行时数字 `1` 会被隐式类型转换为字符串 `'1'`,加号 `+` 被识别为字符串拼接,所以打印出结果是字符串 `'11'`。 67 | 68 | ```js 69 | console.log(1 + '1'); 70 | // 打印出字符串 '11' 71 | ``` 72 | 73 | TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性,所以**它们都是弱类型**。 74 | 75 | 作为对比,Python 是强类型,以下代码会在运行时报错: 76 | 77 | ```py 78 | print(1 + '1') 79 | # TypeError: unsupported operand type(s) for +: 'int' and 'str' 80 | ``` 81 | 82 | 若要修复该错误,需要进行强制类型转换: 83 | 84 | ```py 85 | print(str(1) + '1') 86 | # 打印出字符串 '11' 87 | ``` 88 | 89 | > 强/弱是相对的,Python 在处理整型和浮点型相加时,会将整型隐式转换为浮点型,但是这并不影响 Python 是强类型的结论,因为大部分情况下 Python 并不会进行隐式类型转换。相比而言,JavaScript 和 TypeScript 中不管加号两侧是什么类型,都可以通过隐式类型转换计算出一个结果——而不是报错——所以 JavaScript 和 TypeScript 都是弱类型。 90 | 91 | > 虽然 TypeScript 不限制加号两侧的类型,但是我们可以借助 TypeScript 提供的类型系统,以及 ESLint 提供的代码检查功能,来限制加号两侧必须同为数字或同为字符串[[5]](#link-5)。这在一定程度上使得 TypeScript 向「强类型」更近一步了——当然,这种限制是可选的。 92 | 93 | 这样的类型系统体现了 TypeScript 的核心设计理念[[6]](#link-6):在完整保留 JavaScript 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug。 94 | 95 | ### 适用于任何规模 96 | 97 | TypeScript 非常适用于大型项目——这是显而易见的,类型系统可以为大型项目带来更高的可维护性,以及更少的 bug。 98 | 99 | 在中小型项目中推行 TypeScript 的最大障碍就是认为使用 TypeScript 需要写额外的代码,降低开发效率。但事实上,由于有[类型推论][],大部分类型都不需要手动声明了。相反,TypeScript 增强了编辑器(IDE)的功能,包括代码补全、接口提示、跳转到定义、代码重构等,这在很大程度上提高了开发效率。而且 TypeScript 有近百个[编译选项][],如果你认为类型检查过于严格,那么可以通过修改编译选项来降低类型检查的标准。 100 | 101 | TypeScript 还可以和 JavaScript 共存。这意味着如果你有一个使用 JavaScript 开发的旧项目,又想使用 TypeScript 的特性,那么你不需要急着把整个项目都迁移到 TypeScript,你可以使用 TypeScript 编写新文件,然后在后续更迭中逐步迁移旧文件。如果一些 JavaScript 文件的迁移成本太高,TypeScript 也提供了一个方案,可以让你在不修改 JavaScript 文件的前提下,编写一个[类型声明文件][],实现旧项目的渐进式迁移。 102 | 103 | 事实上,就算你从来没学习过 TypeScript,你也可能已经在不知不觉中使用到了 TypeScript——在 VSCode 编辑器中编写 JavaScript 时,代码补全和接口提示等功能就是通过 TypeScript Language Service 实现的[[7]](#link-7): 104 | 105 | ![what-is-typescript-vscode](../assets/what-is-typescript-vscode.png) 106 | 107 | 一些第三方库原生支持了 TypeScript,在使用时就能获得代码补全了,比如 Vue 3.0[[8]](#link-8): 108 | 109 | ![what-is-typescript-vue](../assets/what-is-typescript-vue.png) 110 | 111 | 有一些第三方库原生不支持 TypeScript,但是可以通过安装社区维护的类型声明库[[9]](#link-9)(比如通过运行 `npm install --save-dev @types/react` 来安装 React 的类型声明库)来获得代码补全能力——不管是在 JavaScript 项目中还是在 TypeScript 中项目中都是支持的: 112 | 113 | ![what-is-typescript-react](../assets/what-is-typescript-react.png) 114 | 115 | 由此可见,TypeScript 的发展已经深入到前端社区的方方面面了,任何规模的项目都或多或少得到了 TypeScript 的支持。 116 | 117 | ### 与标准同步发展 118 | 119 | TypeScript 的另一个重要的特性就是坚持与 ECMAScript 标准[[10]](#link-10)同步发展。 120 | 121 | ECMAScript 是 JavaScript 核心语法的标准,自 2015 年起,每年都会发布一个新版本,包含一些新的语法。 122 | 123 | 一个新的语法从提案到变成正式标准,需要经历以下几个阶段: 124 | 125 | - Stage 0:展示阶段,仅仅是提出了讨论、想法,尚未正式提案。 126 | - Stage 1:征求意见阶段,提供抽象的 API 描述,讨论可行性,关键算法等。 127 | - Stage 2:草案阶段,使用正式的规范语言精确描述其语法和语义。 128 | - Stage 3:候选人阶段,语法的设计工作已完成,需要浏览器、Node.js 等环境支持,搜集用户的反馈。 129 | - Stage 4:定案阶段,已准备好将其添加到正式的 ECMAScript 标准中。 130 | 131 | 一个语法进入到 Stage 3 阶段后,TypeScript 就会实现它。一方面,让我们可以尽早的使用到最新的语法,帮助它进入到下一个阶段;另一方面,处于 Stage 3 阶段的语法已经比较稳定了,基本不会有语法的变更,这使得我们能够放心的使用它。 132 | 133 | 除了实现 ECMAScript 标准之外,TypeScript 团队也推进了诸多语法提案,比如可选链操作符(`?.`)[[11]](#link-11)、空值合并操作符(`??`)[[12]](#link-12)、Throw 表达式[[13]](#link-13)、正则匹配索引[[14]](#link-14)等。 134 | 135 | ## 总结 136 | 137 | 什么是 TypeScript? 138 | 139 | - TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。 140 | - TypeScript 是一门静态类型、弱类型的语言。 141 | - TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。 142 | - TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。 143 | - TypeScript 拥有很多编译选项,类型检查的严格程度由你决定。 144 | - TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。 145 | - TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。 146 | - TypeScript 拥有活跃的社区,大多数常用的第三方库都提供了类型声明。 147 | - TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。 148 | 149 | ## 附:TypeScript 的发展历史 150 | 151 | - 2012-10:微软发布了 TypeScript 第一个版本(0.8),此前已经在微软内部开发了两年。 152 | - 2014-04:TypeScript 发布了 1.0 版本。 153 | - 2014-10:Angular 发布了 2.0 版本,它是一个基于 TypeScript 开发的前端框架。 154 | - 2015-01:ts-loader 发布,webpack 可以编译 TypeScript 文件了。 155 | - 2015-04:微软发布了 Visual Studio Code,它内置了对 TypeScript 语言的支持,它自身也是用 TypeScript 开发的。 156 | - 2016-05:`@types/react` 发布,TypeScript 可以开发 React 应用了。 157 | - 2016-05:`@types/node` 发布,TypeScript 可以开发 Node.js 应用了。 158 | - 2016-09:TypeScript 发布了 2.0 版本。 159 | - 2018-06:TypeScript 发布了 3.0 版本。 160 | - 2019-02:TypeScript 宣布由官方团队来维护 typescript-eslint,以支持在 TypeScript 文件中运行 ESLint 检查。 161 | - 2020-05:Deno 发布了 1.0 版本,它是一个 JavaScript 和 TypeScript 运行时。 162 | - 2020-08:TypeScript 发布了 4.0 版本。 163 | - 2020-09:Vue 发布了 3.0 版本,官方支持 TypeScript。 164 | 165 | ## 参考资料 166 | 167 | 1. [TypeScript 官网](https://www.typescriptlang.org/) 168 | 2. [第 2 章: 一等公民的函数](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch2.html) · 函数式编程指北 169 | 3. [StackOverflow 2020 开发者调查报告](https://insights.stackoverflow.com/survey/2020) 170 | 4. [斯坦福 JavaScript 第一课](https://web.stanford.edu/class/cs98si/slides/overview.html) 171 | 5. [TypeScript ESLint 规则 `restrict-plus-operands`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-plus-operands.md) 172 | 6. [TypeScript 设计理念](https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals) 173 | 7. [Visual Studio Code 中集成了 TypeScript](https://code.visualstudio.com/docs/languages/typescript) 174 | 8. [Vue 3.0 支持 TypeScript](https://v3.vuejs.org/guide/typescript-support.html) 175 | 9. [Definitely Typed](https://github.com/DefinitelyTyped/DefinitelyTyped)——TypeScript 团队帮助维护的类型定义仓库 176 | 10. [ECMAScript 标准](https://tc39.es/process-document/) 177 | 11. [可选链操作符(`?.`)](https://github.com/tc39/proposal-optional-chaining) 178 | 12. [空值合并操作符(`??`)](https://github.com/tc39/proposal-nullish-coalescing) 179 | 13. [Throw 表达式](https://github.com/tc39/proposal-throw-expressions) 180 | 14. [正则匹配索引](https://github.com/tc39/proposal-regexp-match-indices) 181 | -------------------------------------------------------------------------------- /advanced/decorator.md: -------------------------------------------------------------------------------- 1 | # 装饰器 2 | 3 | 写在前面:本章只介绍 TypeScript 5.0+ 的装饰器用法,对于 5.0 以下的版本,请参考 [TypeScript 官方文档](https://www.typescriptlang.org/docs/handbook/decorators.html) 4 | 5 | ## 什么是装饰器 6 | 7 | 首先,什么是装饰器呢?[维基百科](https://en.wikipedia.org/wiki/Decorator_pattern)是这么说的: 8 | 9 | > In [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), the **decorator pattern** is a [design pattern](https://en.wikipedia.org/wiki/Design_pattern_(computer_science)) that allows behavior to be added to an individual [object](https://en.wikipedia.org/wiki/Object_(computer_science)), dynamically, without affecting the behavior of other instances of the same [class](https://en.wikipedia.org/wiki/Class_(computer_science)). 10 | 11 | 本人的蹩足翻译:在 OOP (面向对象编程)中,装饰器模式是一种允许动态地往一个对象上添加自定义行为,而又不影响该对象所属的类的其他实例的一种设计模式。 12 | 13 | > 什么是 OOP 和类?[前面的章节](https://ts.xcatliu.com/advanced/class.html)做过介绍。 14 | 15 | 这句话未免过于拗口了,我们不妨换个角度去切入。 16 | 17 | ## 装饰器的使用场景 18 | 19 | 要知道,一切设计模式的诞生,都是为了解决某个问题。在 JavaScript 的世界中,装饰器通常出现于以下场景: 20 | 21 | 1. 提供一种易读且容易实现的方式,修改类或者类的方法,避免出现大量重复的代码。 22 | 23 | 下面以修改类的方法为例。 24 | 25 | 首先,假设我们有一个 `Animal` 类: 26 | 27 | ```ts 28 | class Animal { 29 | type: string 30 | constructor(type: string) { 31 | this.type = type 32 | } 33 | 34 | greet() { 35 | console.log(`Hello, I'm a(n) ${this.type}!`) 36 | } 37 | } 38 | 39 | const xcat = new Animal('cat') 40 | xcat.greet() // Hello, I'm a(n) cat! 41 | ``` 42 | 43 | 该类有一个 greet 方法,和调用方打招呼。 44 | 45 | 假如说,我还希望根据不同的 `type`,往 console 打印不同动物的叫声呢? 46 | 47 | 聪明的你或许想到了,这不就是**类的继承**吗!在子类的 `greet()` 方法中,实现不同的逻辑,再调用 `super.greet()` 即可。 48 | 49 | ```ts 50 | class Xcat extends Animal { 51 | constructor() { 52 | super('cat') 53 | } 54 | 55 | greet() { 56 | console.log('meow~ meow~') 57 | super.greet() 58 | } 59 | } 60 | 61 | const xcat = new Xcat() 62 | xcat.greet() // meow~ meow~ 63 | // Hello, I'm a(n) cat! 64 | ``` 65 | 66 | 用装饰器实现,也不妨为一种思路,比如在 `Animal` 类中,为 `greet()` 方法添加「打印不同动物叫声的」行为: 67 | 68 | ```ts 69 | class Animal { 70 | type: string 71 | constructor(type: string) { 72 | this.type = type 73 | } 74 | 75 | @yelling 76 | greet() { 77 | console.log(`Hello, I'm a(n) ${this.type}!`) 78 | } 79 | } 80 | 81 | const typeToYellingMap = { 82 | cat: 'meow~ meow~' 83 | } 84 | 85 | function yelling(originalMethod: any, context: ClassMethodDecoratorContext) { 86 | return function(...args: any[]) { 87 | console.log(typeToYellingMap[this.type]) 88 | originalMethod.call(this, ...args) 89 | } 90 | } 91 | 92 | const xcat = new Animal('cat') 93 | xcat.greet() // meow~ meow~ 94 | // Hello, I'm a(n) cat! 95 | ``` 96 | 97 | 在 `Animal.greet()` 方法上出现的 `@yelling` ,就是 TypeScript 中装饰器的写法,即 @ + 函数名的组合。 98 | 99 | 上述示例对装饰器的应用属于**方法装饰器**,此类装饰器本身接收两个参数,一是被装饰的方法,二是方法装饰器的上下文。方法装饰器应返回一个函数,此函数在运行时真正被执行。在上述例子中,我们在装饰器返回的函数中做了两件事情: 100 | 101 | 1. 打印相应类别的动物的叫声。 102 | 2. 调用 `originalMethod.call(this, …args)` ,确保原方法(即装饰器所装饰的方法)能够正确地被执行。 103 | 2. 结合「**依赖注入**」这一设计模式,优化模块与 class 的依赖关系。 104 | 105 | 什么是依赖注入呢?引用同事 [zio](https://github.com/ziofat) 的原话: 106 | 107 | > **依赖注入其实是将一个模块所依赖的部分作为参数传入,而不是由模块自己去构造。** 108 | 109 | 可见,依赖注入解决了实际工程项目中,类、模块间依赖关系层级复杂的问题,将构造单例的行为交由实现依赖注入的框架去处理。 110 | 111 | 举个例子: 112 | 113 | ```ts 114 | @injectable 115 | class Dog implements IAnimal { 116 | sayHi() { 117 | console.log('woof woof woof') 118 | } 119 | } 120 | 121 | @injectable 122 | class Cat implements IAnimal { 123 | sayHi() { 124 | console.log('meow meow meow') 125 | } 126 | } 127 | 128 | class AnimalService { 129 | constructor( 130 | @inject dog: Dog 131 | @inject cat: Cat 132 | ) { 133 | this._dog = dog 134 | this._cat = cat 135 | } 136 | 137 | sayHiByDog() { 138 | this._dog.sayHi() 139 | } 140 | 141 | sayHiByCat() { 142 | this._cat.sayHi() 143 | } 144 | } 145 | ``` 146 | 147 | 在上述代码中,`@injectable` 将一个类标记为「可被注入的」,在面向业务的类(即 `AnimalService`)中,使用 `@inject` 注入此类的单例,实现了「依赖倒置」。注意到这里的 `implements IAnimal` 用法,也是实战中依赖注入运用的精妙之处 —— 关心接口,而非具体实现。 148 | 149 | 3. 实现「AOP」,即 Aspect-oriented programming,面向切面编程。 150 | 151 | 所谓的「切面」,可以理解成,在复杂的各个业务维度中,只关注一个维度的事务。 152 | 153 | 例如,使用装饰器,实现对类的某个方法的执行时间记录: 154 | 155 | ```ts 156 | class MyService { 157 | @recordExecution 158 | myFn() { 159 | // do something... 160 | } 161 | } 162 | 163 | function recordExecution(originalMethod: any, context: ClassMethodDecoratorContext) { 164 | return function(...args: any[]) { 165 | console.time('mark execution') 166 | originalMethod.call(this, ...args) 167 | console.timeEnd('mark execution') 168 | } 169 | } 170 | ``` 171 | 172 | 173 | ## 装饰器的类别 174 | 175 | 通过以上例子,相信读者已经对装饰器有一定了解,且认识到了装饰器在一些场景的强大之处。在此引用[阮一峰 es6 教程](https://es6.ruanyifeng.com/#docs/decorator#%E7%AE%80%E4%BB%8B%EF%BC%88%E6%96%B0%E8%AF%AD%E6%B3%95%EF%BC%89)稍做总结: 176 | 177 | > 装饰器是一种函数,写成`@ + 函数名`,可以用来装饰四种类型的值。 178 | > 179 | > - 类 180 | > - 类的属性 181 | > - 类的方法 182 | > - 属性存取器(accessor, getter, setter) 183 | 184 | > 装饰器的执行步骤如下。 185 | > 186 | > 1. 计算各个装饰器的值,按照从左到右,从上到下的顺序。 187 | > 2. 调用方法装饰器。 188 | > 3. 调用类装饰器。 189 | 190 | 不管是哪种类型的装饰器,它们的函数签名都可以认为是一致的,即均接收 `value`, `context` 两个参数,前者指被装饰的对象,后者指一个存储了上下文信息的对象。 191 | 192 | ## context 与 metadata 二三讲 193 | 194 | 四种装饰器的 context,均包含以下信息: 195 | 196 | - kind 197 | 198 | 描述被装饰的 value 的类型,可取 `class`, `method`, `field`, `getter`, `setter`, `accessor` 这些值。 199 | 200 | - name 201 | 202 | 描述被装饰的 value 的名字。 203 | 204 | - addInitializer 205 | 206 | 一个方法,接收一个回调函数,使得开发者可以侵入 value 的初始化过程作修改。 207 | 208 | 对 `class` 来说,这个回调函数会在类定义最终确认后调用,即相当于在初始化过程的最后一步。 209 | 210 | 对其他的 value 来说,如果是被 `static` 所修饰的,则会在类定义期间被调用,且早于其他静态属性的赋值过程;否则,会在类初始化期间被调用,且早于 value 自身的初始化。 211 | 212 | 以下是 `@bound` 类方法装饰器的例子,该装饰器自动为方法绑定 `this`: 213 | 214 | ```ts 215 | const bound = (value, context: ClassMemberDecoratorContext) { 216 | if (context.private) throw new TypeError("Not supported on private methods."); 217 | context.addInitializer(function () { 218 | this[context.name] = this[context.name].bind(this); 219 | }); 220 | } 221 | ``` 222 | 223 | - metadata 224 | 225 | 和装饰器类似,[metadata](https://github.com/tc39/proposal-decorator-metadata) 也是处于 stage 3 阶段的一个提案。装饰器只能访问到类原型链、类实例的相关数据,而 metadata 给了开发者更大的自由,让程序于运行时访问到编译时决定的元数据。 226 | 227 | 举个例子: 228 | 229 | ```ts 230 | function meta(key, value) { 231 | return (_, context) => { 232 | context.metadata[key] = value; 233 | }; 234 | } 235 | 236 | @meta('a', 'x') 237 | class C { 238 | @meta('b', 'y') 239 | m() {} 240 | } 241 | 242 | C[Symbol.metadata].a; // 'x' 243 | C[Symbol.metadata].b; // 'y' 244 | ``` 245 | 246 | 在上述程序中,我们通过访问类的 `Symbol.metadata` ,读取到了 meta 装饰器所写入的元数据。对元数据的访问,有且仅有这一种形式。 247 | 248 | 注意一点,metadata 是作用在类上的,即使它的位置在类方法上。想实现细粒度的元数据存储,可以考虑手动维护若干 `WeakMap`。 249 | 250 | 251 | 除了类装饰器以外,其他3种装饰器的 context 还拥有以下 3 个字段: 252 | 253 | - static 254 | 255 | 布尔值,描述 value 是否为 static 所修饰。 256 | 257 | - private 258 | 259 | 布尔值,描述 value 是否为 private 所修饰。 260 | 261 | - access 262 | 263 | 一个对象,可在运行时访问 value 相关数据。 264 | 265 | 以类方法装饰器为例,用 `access.get` 可在运行时读取方法值,`access.has` 可在运行时查询对象上是否有某方法,举个例子: 266 | 267 | ```ts 268 | const typeToYellingMap = { 269 | cat: 'meow~ meow~', 270 | } 271 | 272 | let yellingMethodContext: ClassMethodDecoratorContext 273 | 274 | class Animal { 275 | type: string 276 | constructor(type: string) { 277 | this.type = type 278 | } 279 | 280 | @yelling 281 | greet() { 282 | console.log(`Hello, I'm a(n) ${this.type}!`) 283 | } 284 | 285 | accessor y = 1 286 | } 287 | 288 | function yelling(originalMethod: any, context: ClassMethodDecoratorContext) { 289 | yellingMethodContext = context 290 | return function (this: any, ...args: any[]) { 291 | console.log(typeToYellingMap[this.type as keyof typeof typeToYellingMap]) 292 | originalMethod.call(this, ...args) 293 | } 294 | } 295 | 296 | const xcat = new Animal('cat') 297 | xcat.greet() // meow~ meow~ 298 | // Hello, I'm a(n) cat! 299 | yellingMethodContext.access.get(xcat).call(xcat) // meow~ meow~ 300 | // Hello, I'm a(n) cat! 301 | console.log(yellingMethodContext.access.has(xcat)) // true 302 | ``` 303 | 304 | `getter` 类别的装饰器,其 `context.access` 同样拥有 `has`, `get` 两个方法。 305 | 306 | 对于 `setter` 类别的装饰器,则是 `has` 与 `set` 方法。 307 | 308 | `filed` 与 `accessor` 类别的装饰器,拥有 `has`, `get`, `set` 全部三个方法。 309 | -------------------------------------------------------------------------------- /advanced/class.md: -------------------------------------------------------------------------------- 1 | # 类 2 | 3 | 传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 `class`。 4 | 5 | TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。 6 | 7 | 这一节主要介绍类的用法,下一节再介绍如何定义类的类型。 8 | 9 | ## 类的概念 10 | 11 | 虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。 12 | 13 | - 类(Class):定义了一件事物的抽象特点,包含它的属性和方法 14 | - 对象(Object):类的实例,通过 `new` 生成 15 | - 面向对象(OOP)的三大特性:封装、继承、多态 16 | - 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据 17 | - 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性 18 | - 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 `Cat` 和 `Dog` 都继承自 `Animal`,但是分别实现了自己的 `eat` 方法。此时针对某一个实例,我们无需了解它是 `Cat` 还是 `Dog`,就可以直接调用 `eat` 方法,程序会自动判断出来应该如何执行 `eat` 19 | - 存取器(getter & setter):用以改变属性的读取和赋值行为 20 | - 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 `public` 表示公有属性或方法 21 | - 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现 22 | - 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口 23 | 24 | ## ES6 中类的用法 25 | 26 | 下面我们先回顾一下 ES6 中类的用法,更详细的介绍可以参考 [ECMAScript 6 入门 - Class]。 27 | 28 | ### 属性和方法 29 | 30 | 使用 `class` 定义类,使用 `constructor` 定义构造函数。 31 | 32 | 通过 `new` 生成新实例的时候,会自动调用构造函数。 33 | 34 | ```js 35 | class Animal { 36 | name; 37 | constructor(name) { 38 | this.name = name; 39 | } 40 | sayHi() { 41 | return `My name is ${this.name}`; 42 | } 43 | } 44 | 45 | let a = new Animal('Jack'); 46 | console.log(a.sayHi()); // My name is Jack 47 | ``` 48 | 49 | ### 类的继承 50 | 51 | 使用 `extends` 关键字实现继承,子类中使用 `super` 关键字来调用父类的构造函数和方法。 52 | 53 | ```js 54 | class Cat extends Animal { 55 | constructor(name) { 56 | super(name); // 调用父类的 constructor(name) 57 | console.log(this.name); 58 | } 59 | sayHi() { 60 | return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi() 61 | } 62 | } 63 | 64 | let c = new Cat('Tom'); // Tom 65 | console.log(c.sayHi()); // Meow, My name is Tom 66 | ``` 67 | 68 | ### 存取器 69 | 70 | 使用 getter 和 setter 可以改变属性的赋值和读取行为: 71 | 72 | ```js 73 | class Animal { 74 | constructor(name) { 75 | this.name = name; 76 | } 77 | get name() { 78 | return 'Jack'; 79 | } 80 | set name(value) { 81 | console.log('setter: ' + value); 82 | } 83 | } 84 | 85 | let a = new Animal('Kitty'); // setter: Kitty 86 | a.name = 'Tom'; // setter: Tom 87 | console.log(a.name); // Jack 88 | ``` 89 | 90 | ### 静态方法 91 | 92 | 使用 `static` 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用: 93 | 94 | ```js 95 | class Animal { 96 | static isAnimal(a) { 97 | return a instanceof Animal; 98 | } 99 | } 100 | 101 | let a = new Animal('Jack'); 102 | Animal.isAnimal(a); // true 103 | a.isAnimal(a); // TypeError: a.isAnimal is not a function 104 | ``` 105 | 106 | ## ES7 中类的用法 107 | 108 | ES7 中有一些关于类的提案,TypeScript 也实现了它们,这里做一个简单的介绍。 109 | 110 | ### 实例属性 111 | 112 | ES6 中实例的属性只能通过构造函数中的 `this.xxx` 来定义,ES7 提案中可以直接在类里面定义: 113 | 114 | ```js 115 | class Animal { 116 | name = 'Jack'; 117 | 118 | constructor() { 119 | // ... 120 | } 121 | } 122 | 123 | let a = new Animal(); 124 | console.log(a.name); // Jack 125 | ``` 126 | 127 | ### 静态属性 128 | 129 | ES7 提案中,可以使用 `static` 定义一个静态属性: 130 | 131 | ```js 132 | class Animal { 133 | static num = 42; 134 | 135 | constructor() { 136 | // ... 137 | } 138 | } 139 | 140 | console.log(Animal.num); // 42 141 | ``` 142 | 143 | ## TypeScript 中类的用法 144 | 145 | ### public private 和 protected 146 | 147 | TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 `public`、`private` 和 `protected`。 148 | 149 | - `public` 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 `public` 的 150 | - `private` 修饰的属性或方法是私有的,不能在声明它的类的外部访问 151 | - `protected` 修饰的属性或方法是受保护的,它和 `private` 类似,区别是它在子类中也是允许被访问的 152 | 153 | 下面举一些例子: 154 | 155 | ```ts 156 | class Animal { 157 | public name; 158 | public constructor(name) { 159 | this.name = name; 160 | } 161 | } 162 | 163 | let a = new Animal('Jack'); 164 | console.log(a.name); // Jack 165 | a.name = 'Tom'; 166 | console.log(a.name); // Tom 167 | ``` 168 | 169 | 上面的例子中,`name` 被设置为了 `public`,所以直接访问实例的 `name` 属性是允许的。 170 | 171 | 很多时候,我们希望有的属性是无法直接存取的,这时候就可以用 `private` 了: 172 | 173 | ```ts 174 | class Animal { 175 | private name; 176 | public constructor(name) { 177 | this.name = name; 178 | } 179 | } 180 | 181 | let a = new Animal('Jack'); 182 | console.log(a.name); 183 | a.name = 'Tom'; 184 | 185 | // index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'. 186 | // index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'. 187 | ``` 188 | 189 | 需要注意的是,TypeScript 编译之后的代码中,并没有限制 `private` 属性在外部的可访问性。 190 | 191 | 上面的例子编译后的代码是: 192 | 193 | ```js 194 | var Animal = (function () { 195 | function Animal(name) { 196 | this.name = name; 197 | } 198 | return Animal; 199 | })(); 200 | var a = new Animal('Jack'); 201 | console.log(a.name); 202 | a.name = 'Tom'; 203 | ``` 204 | 205 | 使用 `private` 修饰的属性或方法,在子类中也是不允许访问的: 206 | 207 | ```ts 208 | class Animal { 209 | private name; 210 | public constructor(name) { 211 | this.name = name; 212 | } 213 | } 214 | 215 | class Cat extends Animal { 216 | constructor(name) { 217 | super(name); 218 | console.log(this.name); 219 | } 220 | } 221 | 222 | // index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'. 223 | ``` 224 | 225 | 而如果是用 `protected` 修饰,则允许在子类中访问: 226 | 227 | ```ts 228 | class Animal { 229 | protected name; 230 | public constructor(name) { 231 | this.name = name; 232 | } 233 | } 234 | 235 | class Cat extends Animal { 236 | constructor(name) { 237 | super(name); 238 | console.log(this.name); 239 | } 240 | } 241 | ``` 242 | 243 | 当构造函数修饰为 `private` 时,该类不允许被继承或者实例化: 244 | 245 | ```ts 246 | class Animal { 247 | public name; 248 | private constructor(name) { 249 | this.name = name; 250 | } 251 | } 252 | class Cat extends Animal { 253 | constructor(name) { 254 | super(name); 255 | } 256 | } 257 | 258 | let a = new Animal('Jack'); 259 | 260 | // index.ts(7,19): TS2675: Cannot extend a class 'Animal'. Class constructor is marked as private. 261 | // index.ts(13,9): TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration. 262 | ``` 263 | 264 | 当构造函数修饰为 `protected` 时,该类只允许被继承: 265 | 266 | ```ts 267 | class Animal { 268 | public name; 269 | protected constructor(name) { 270 | this.name = name; 271 | } 272 | } 273 | class Cat extends Animal { 274 | constructor(name) { 275 | super(name); 276 | } 277 | } 278 | 279 | let a = new Animal('Jack'); 280 | 281 | // index.ts(13,9): TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration. 282 | ``` 283 | 284 | ### 参数属性 285 | 286 | 修饰符和`readonly`还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。 287 | 288 | ```ts 289 | class Animal { 290 | // public name: string; 291 | public constructor(public name) { 292 | // this.name = name; 293 | } 294 | } 295 | ``` 296 | 297 | ### readonly 298 | 299 | 只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。 300 | 301 | ```ts 302 | class Animal { 303 | readonly name; 304 | public constructor(name) { 305 | this.name = name; 306 | } 307 | } 308 | 309 | let a = new Animal('Jack'); 310 | console.log(a.name); // Jack 311 | a.name = 'Tom'; 312 | 313 | // index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property. 314 | ``` 315 | 316 | 注意如果 `readonly` 和其他访问修饰符同时存在的话,需要写在其后面。 317 | 318 | ```ts 319 | class Animal { 320 | // public readonly name; 321 | public constructor(public readonly name) { 322 | // this.name = name; 323 | } 324 | } 325 | ``` 326 | 327 | ### 抽象类 328 | 329 | `abstract` 用于定义抽象类和其中的抽象方法。 330 | 331 | 什么是抽象类? 332 | 333 | 首先,抽象类是不允许被实例化的: 334 | 335 | ```ts 336 | abstract class Animal { 337 | public name; 338 | public constructor(name) { 339 | this.name = name; 340 | } 341 | public abstract sayHi(); 342 | } 343 | 344 | let a = new Animal('Jack'); 345 | 346 | // index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'. 347 | ``` 348 | 349 | 上面的例子中,我们定义了一个抽象类 `Animal`,并且定义了一个抽象方法 `sayHi`。在实例化抽象类的时候报错了。 350 | 351 | 其次,抽象类中的抽象方法必须被子类实现: 352 | 353 | ```ts 354 | abstract class Animal { 355 | public name; 356 | public constructor(name) { 357 | this.name = name; 358 | } 359 | public abstract sayHi(); 360 | } 361 | 362 | class Cat extends Animal { 363 | public eat() { 364 | console.log(`${this.name} is eating.`); 365 | } 366 | } 367 | 368 | let cat = new Cat('Tom'); 369 | 370 | // index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'. 371 | ``` 372 | 373 | 上面的例子中,我们定义了一个类 `Cat` 继承了抽象类 `Animal`,但是没有实现抽象方法 `sayHi`,所以编译报错了。 374 | 375 | 下面是一个正确使用抽象类的例子: 376 | 377 | ```ts 378 | abstract class Animal { 379 | public name; 380 | public constructor(name) { 381 | this.name = name; 382 | } 383 | public abstract sayHi(); 384 | } 385 | 386 | class Cat extends Animal { 387 | public sayHi() { 388 | console.log(`Meow, My name is ${this.name}`); 389 | } 390 | } 391 | 392 | let cat = new Cat('Tom'); 393 | ``` 394 | 395 | 上面的例子中,我们实现了抽象方法 `sayHi`,编译通过了。 396 | 397 | 需要注意的是,即使是抽象方法,TypeScript 的编译结果中,仍然会存在这个类,上面的代码的编译结果是: 398 | 399 | ```js 400 | var __extends = 401 | (this && this.__extends) || 402 | function (d, b) { 403 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 404 | function __() { 405 | this.constructor = d; 406 | } 407 | d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __()); 408 | }; 409 | var Animal = (function () { 410 | function Animal(name) { 411 | this.name = name; 412 | } 413 | return Animal; 414 | })(); 415 | var Cat = (function (_super) { 416 | __extends(Cat, _super); 417 | function Cat() { 418 | _super.apply(this, arguments); 419 | } 420 | Cat.prototype.sayHi = function () { 421 | console.log('Meow, My name is ' + this.name); 422 | }; 423 | return Cat; 424 | })(Animal); 425 | var cat = new Cat('Tom'); 426 | ``` 427 | 428 | ## 类的类型 429 | 430 | 给类加上 TypeScript 的类型很简单,与接口类似: 431 | 432 | ```ts 433 | class Animal { 434 | name: string; 435 | constructor(name: string) { 436 | this.name = name; 437 | } 438 | sayHi(): string { 439 | return `My name is ${this.name}`; 440 | } 441 | } 442 | 443 | let a: Animal = new Animal('Jack'); 444 | console.log(a.sayHi()); // My name is Jack 445 | ``` 446 | 447 | ## 参考 448 | 449 | - [Classes](http://www.typescriptlang.org/docs/handbook/classes.html)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Classes.html)) 450 | - [ECMAScript 6 入门 - Class] 451 | 452 | [ecmascript 6 入门 - class]: http://es6.ruanyifeng.com/#docs/class 453 | -------------------------------------------------------------------------------- /engineering/lint.md: -------------------------------------------------------------------------------- 1 | # 代码检查 2 | 3 | 2019 年 1 月,[TypeScirpt 官方决定全面采用 ESLint](https://www.oschina.net/news/103818/future-typescript-eslint) 作为代码检查的工具,并创建了一个新项目 [typescript-eslint][],提供了 TypeScript 文件的解析器 [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser) 和相关的配置选项 [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin) 等。而之前的两个 lint 解决方案都将弃用: 4 | 5 | - [typescript-eslint-parser](https://github.com/eslint/typescript-eslint-parser) 已停止维护 6 | - [TSLint](https://palantir.github.io/tslint/) 将提供迁移工具,并在 typescript-eslint 的功能足够完整后停止维护 TSLint(Once we consider ESLint feature-complete w.r.t. TSLint, we will deprecate TSLint and help users migrate to ESLint[1](https://medium.com/palantir/tslint-in-2019-1a144c2317a9)) 7 | 8 | 综上所述,目前以及将来的 TypeScript 的代码检查方案就是 [typescript-eslint][]。 9 | 10 | ## 什么是代码检查 11 | 12 | 代码检查主要是用来发现代码错误、统一代码风格。 13 | 14 | 在 JavaScript 项目中,我们一般使用 [ESLint][] 来进行代码检查,它通过插件化的特性极大的丰富了适用范围,搭配 [typescript-eslint][] 之后,甚至可以用来检查 TypeScript 代码。 15 | 16 | ## 为什么需要代码检查 17 | 18 | 有人会觉得,JavaScript 非常灵活,所以需要代码检查。而 TypeScript 已经能够在编译阶段检查出很多问题了,为什么还需要代码检查呢? 19 | 20 | 因为 TypeScript 关注的重心是类型的检查,而不是代码风格。当团队的人员越来越多时,同样的逻辑不同的人写出来可能会有很大的区别: 21 | 22 | - 缩进应该是四个空格还是两个空格? 23 | - 是否应该禁用 `var`? 24 | - 接口名是否应该以 `I` 开头? 25 | - 是否应该强制使用 `===` 而不是 `==`? 26 | 27 | 这些问题 TypeScript 不会关注,但是却影响到多人协作开发时的效率、代码的可理解性以及可维护性。 28 | 29 | 下面来看一个具体的例子: 30 | 31 | ```ts 32 | var myName = 'Tom'; 33 | 34 | console.log(`My name is ${myNane}`); 35 | console.log(`My name is ${myName.toStrng()}`); 36 | ``` 37 | 38 | 以上代码你能看出有什么错误吗? 39 | 40 | 分别用 tsc 编译和 eslint 检查后,报错信息如下: 41 | 42 | ```ts 43 | var myName = 'Tom'; 44 | // eslint 报错信息: 45 | // Unexpected var, use let or const instead.eslint(no-var) 46 | 47 | console.log(`My name is ${myNane}`); 48 | // tsc 报错信息: 49 | // Cannot find name 'myNane'. Did you mean 'myName'? 50 | // eslint 报错信息: 51 | // 'myNane' is not defined.eslint(no-undef) 52 | console.log(`My name is ${myName.toStrng()}`); 53 | // tsc 报错信息: 54 | // Property 'toStrng' does not exist on type 'string'. Did you mean 'toString'? 55 | ``` 56 | 57 | | 存在的问题 | `tsc` 是否报错 | `eslint` 是否报错 | 58 | | --- | --- | --- | 59 | | 应该使用 `let` 或 `const` 而不是 `var` | ❌ | ✅ | 60 | | `myName` 被误写成了 `myNane` | ✅ | ✅ | 61 | | `toString` 被误写成了 `toStrng` | ✅️ | ❌ | 62 | 63 | 上例中,我们使用了 `var` 来定义一个变量,但其实 ES6 中有更先进的语法 `let` 和 `const`,此时就可以通过 `eslint` 检查出来,提示我们应该使用 `let` 或 `const` 而不是 `var`。 64 | 65 | 对于未定义的变量 `myNane`,`tsc` 和 `eslint` 都可以检查出来。 66 | 67 | 由于 `eslint` 无法识别 `myName` 存在哪些方法,所以对于拼写错误的 `toString` 没有检查出来。 68 | 69 | 由此可见,`eslint` 能够发现出一些 `tsc` 不会关心的错误,检查出一些潜在的问题,所以代码检查还是非常重要的。 70 | 71 | ## 在 TypeScript 中使用 ESLint 72 | 73 | ### 安装 ESLint 74 | 75 | ESLint 可以安装在当前项目中或全局环境下,因为代码检查是项目的重要组成部分,所以我们一般会将它安装在当前项目中。可以运行下面的脚本来安装: 76 | 77 | ```bash 78 | npm install --save-dev eslint 79 | ``` 80 | 81 | 由于 ESLint 默认使用 [Espree](https://github.com/eslint/espree) 进行语法解析,无法识别 TypeScript 的一些语法,故我们需要安装 [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser),替代掉默认的解析器,别忘了同时安装 `typescript`: 82 | 83 | ```bash 84 | npm install --save-dev typescript @typescript-eslint/parser 85 | ``` 86 | 87 | 接下来需要安装对应的插件 [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin) 它作为 eslint 默认规则的补充,提供了一些额外的适用于 ts 语法的规则。 88 | 89 | ```bash 90 | npm install --save-dev @typescript-eslint/eslint-plugin 91 | ``` 92 | 93 | ### 创建配置文件 94 | 95 | ESLint 需要一个配置文件来决定对哪些规则进行检查,配置文件的名称一般是 `.eslintrc.js` 或 `.eslintrc.json`。 96 | 97 | 当运行 ESLint 的时候检查一个文件的时候,它会首先尝试读取该文件的目录下的配置文件,然后再一级一级往上查找,将所找到的配置合并起来,作为当前被检查文件的配置。 98 | 99 | 我们在项目的根目录下创建一个 `.eslintrc.js`,内容如下: 100 | 101 | ```js 102 | module.exports = { 103 | parser: '@typescript-eslint/parser', 104 | plugins: ['@typescript-eslint'], 105 | rules: { 106 | // 禁止使用 var 107 | 'no-var': "error", 108 | // 优先使用 interface 而不是 type 109 | '@typescript-eslint/consistent-type-definitions': [ 110 | "error", 111 | "interface" 112 | ] 113 | } 114 | } 115 | ``` 116 | 117 | 以上配置中,我们指定了两个规则,其中 `no-var` 是 ESLint 原生的规则,`@typescript-eslint/consistent-type-definitions` 是 `@typescript-eslint/eslint-plugin` 新增的规则。 118 | 119 | 规则的取值一般是一个数组(上例中的 `@typescript-eslint/consistent-type-definitions`),其中第一项是 `off`、`warn` 或 `error` 中的一个,表示关闭、警告和报错。后面的项都是该规则的其他配置。 120 | 121 | 如果没有其他配置的话,则可以将规则的取值简写为数组中的第一项(上例中的 `no-var`)。 122 | 123 | 关闭、警告和报错的含义如下: 124 | 125 | - 关闭:禁用此规则 126 | - 警告:代码检查时输出错误信息,但是不会影响到 exit code 127 | - 报错:发现错误时,不仅会输出错误信息,而且 exit code 将被设为 1(一般 exit code 不为 0 则表示执行出现错误) 128 | 129 | ### 检查一个 ts 文件 130 | 131 | 创建了配置文件之后,我们来创建一个 ts 文件看看是否能用 ESLint 去检查它。 132 | 133 | 创建一个新文件 `index.ts`,将以下内容复制进去: 134 | 135 | ```ts 136 | var myName = 'Tom'; 137 | 138 | type Foo = {}; 139 | ``` 140 | 141 | 然后执行以下命令: 142 | 143 | ```bash 144 | ./node_modules/.bin/eslint index.ts 145 | ``` 146 | 147 | 则会得到如下报错信息: 148 | 149 | ```bash 150 | /path/to/index.ts 151 | 1:1 error Unexpected var, use let or const instead no-var 152 | 3:6 error Use an `interface` instead of a `type` @typescript-eslint/consistent-type-definitions 153 | 154 | ✖ 2 problems (2 errors, 0 warnings) 155 | 2 errors and 0 warnings potentially fixable with the `--fix` option. 156 | ``` 157 | 158 | 上面的结果显示,刚刚配置的两个规则都生效了:禁止使用 `var`;优先使用 `interface` 而不是 `type`。 159 | 160 | 需要注意的是,我们使用的是 `./node_modules/.bin/eslint`,而不是全局的 `eslint` 脚本,这是因为代码检查是项目的重要组成部分,所以我们一般会将它安装在当前项目中。 161 | 162 | 可是每次执行这么长一段脚本颇有不便,我们可以通过在 `package.json` 中添加一个 `script` 来创建一个 npm script 来简化这个步骤: 163 | 164 | ```json 165 | { 166 | "scripts": { 167 | "eslint": "eslint index.ts" 168 | } 169 | } 170 | ``` 171 | 172 | 这时只需执行 `npm run eslint` 即可。 173 | 174 | ### 检查整个项目的 ts 文件 175 | 176 | 我们的项目源文件一般是放在 `src` 目录下,所以需要将 `package.json` 中的 `eslint` 脚本改为对一个目录进行检查。由于 `eslint` 默认不会检查 `.ts` 后缀的文件,所以需要加上参数 `--ext .ts`: 177 | 178 | ```json 179 | { 180 | "scripts": { 181 | "eslint": "eslint src --ext .ts" 182 | } 183 | } 184 | ``` 185 | 186 | 此时执行 `npm run eslint` 即会检查 `src` 目录下的所有 `.ts` 后缀的文件。 187 | 188 | ### 在 VSCode 中集成 ESLint 检查 189 | 190 | 在编辑器中集成 ESLint 检查,可以在开发过程中就发现错误,甚至可以在保存时自动修复错误,极大的增加了开发效率。 191 | 192 | 要在 VSCode 中集成 ESLint 检查,我们需要先安装 ESLint 插件,点击「扩展」按钮,搜索 ESLint,然后安装即可。 193 | 194 | 通过配置 VSCode,可以开启保存时自动修复的功能: 195 | 196 | ```json 197 | { 198 | "eslint.autoFixOnSave": true, 199 | "eslint.validate": [ 200 | "javascript", 201 | "javascriptreact", 202 | { 203 | "language": "typescript", 204 | "autoFix": true 205 | }, 206 | ], 207 | "typescript.tsdk": "node_modules/typescript/lib" 208 | } 209 | ``` 210 | 211 | 就可以在保存文件后,自动修复为: 212 | 213 | ```ts 214 | let myName = 'Tom'; 215 | 216 | interface Foo {} 217 | ``` 218 | 219 | ### 使用 Prettier 修复格式错误 220 | 221 | ESLint 包含了一些代码格式的检查,比如空格、分号等。但前端社区中有一个更先进的工具可以用来格式化代码,那就是 [Prettier](https://prettier.io/)。 222 | 223 | Prettier 聚焦于代码的格式化,通过语法分析,重新整理代码的格式,让所有人的代码都保持同样的风格。 224 | 225 | 首先需要安装 Prettier: 226 | 227 | ```bash 228 | npm install --save-dev prettier 229 | ``` 230 | 231 | 然后创建一个 `prettier.config.js` 文件,里面包含 Prettier 的配置项。Prettier 的配置项很少,这里我推荐大家一个配置规则,作为参考: 232 | 233 | ```js 234 | // prettier.config.js or .prettierrc.js 235 | module.exports = { 236 | // 一行最多 100 字符 237 | printWidth: 100, 238 | // 使用 4 个空格缩进 239 | tabWidth: 4, 240 | // 不使用缩进符,而使用空格 241 | useTabs: false, 242 | // 行尾需要有分号 243 | semi: true, 244 | // 使用单引号 245 | singleQuote: true, 246 | // 对象的 key 仅在必要时用引号 247 | quoteProps: 'as-needed', 248 | // jsx 不使用单引号,而使用双引号 249 | jsxSingleQuote: false, 250 | // 末尾不需要逗号 251 | trailingComma: 'none', 252 | // 大括号内的首尾需要空格 253 | bracketSpacing: true, 254 | // jsx 标签的反尖括号需要换行 255 | jsxBracketSameLine: false, 256 | // 箭头函数,只有一个参数的时候,也需要括号 257 | arrowParens: 'always', 258 | // 每个文件格式化的范围是文件的全部内容 259 | rangeStart: 0, 260 | rangeEnd: Infinity, 261 | // 不需要写文件开头的 @prettier 262 | requirePragma: false, 263 | // 不需要自动在文件开头插入 @prettier 264 | insertPragma: false, 265 | // 使用默认的折行标准 266 | proseWrap: 'preserve', 267 | // 根据显示样式决定 html 要不要折行 268 | htmlWhitespaceSensitivity: 'css', 269 | // 换行符使用 lf 270 | endOfLine: 'lf' 271 | }; 272 | ``` 273 | 274 | 接下来安装 VSCode 中的 Prettier 插件,然后修改 `.vscode/settings.json`: 275 | 276 | ```json 277 | { 278 | "files.eol": "\n", 279 | "editor.tabSize": 4, 280 | "editor.formatOnSave": true, 281 | "editor.defaultFormatter": "esbenp.prettier-vscode", 282 | "eslint.autoFixOnSave": true, 283 | "eslint.validate": [ 284 | "javascript", 285 | "javascriptreact", 286 | { 287 | "language": "typescript", 288 | "autoFix": true 289 | } 290 | ], 291 | "typescript.tsdk": "node_modules/typescript/lib" 292 | } 293 | ``` 294 | 295 | 这样就实现了保存文件时自动格式化并且自动修复 ESLint 错误。 296 | 297 | 需要注意的是,由于 ESLint 也可以检查一些代码格式的问题,所以在和 Prettier 配合使用时,我们一般会把 ESLint 中的代码格式相关的规则禁用掉,否则就会有冲突了。 298 | 299 | ### 使用 AlloyTeam 的 ESLint 配置 300 | 301 | ESLint 原生的规则和 `@typescript-eslint/eslint-plugin` 的规则太多了,而且原生的规则有一些在 TypeScript 中支持的不好,需要禁用掉。 302 | 303 | 这里我推荐使用 [AlloyTeam ESLint 规则中的 TypeScript 版本](https://github.com/AlloyTeam/eslint-config-alloy#typescript),它已经为我们提供了一套完善的配置规则,并且与 Prettier 是完全兼容的(eslint-config-alloy 不包含任何代码格式的规则,代码格式的问题交给更专业的 Prettier 去处理)。 304 | 305 | 安装: 306 | 307 | ```bash 308 | npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy 309 | ``` 310 | 311 | 在你的项目根目录下创建 `.eslintrc.js`,并将以下内容复制到文件中即可: 312 | 313 | ```js 314 | module.exports = { 315 | extends: [ 316 | 'alloy', 317 | 'alloy/typescript', 318 | ], 319 | env: { 320 | // 您的环境变量(包含多个预定义的全局变量) 321 | // Your environments (which contains several predefined global variables) 322 | // 323 | // browser: true, 324 | // node: true, 325 | // mocha: true, 326 | // jest: true, 327 | // jquery: true 328 | }, 329 | globals: { 330 | // 您的全局变量(设置为 false 表示它不允许被重新赋值) 331 | // Your global variables (setting to false means it's not allowed to be reassigned) 332 | // 333 | // myGlobal: false 334 | }, 335 | rules: { 336 | // 自定义您的规则 337 | // Customize your rules 338 | } 339 | }; 340 | ``` 341 | 342 | 更多的使用方法,请参考 [AlloyTeam ESLint 规则](https://github.com/AlloyTeam/eslint-config-alloy) 343 | 344 | ### 使用 ESLint 检查 tsx 文件 345 | 346 | 如果需要同时支持对 tsx 文件的检查,则需要对以上步骤做一些调整: 347 | 348 | #### 安装 `eslint-plugin-react` 349 | 350 | ```bash 351 | npm install --save-dev eslint-plugin-react 352 | ``` 353 | 354 | #### package.json 中的 scripts.eslint 添加 `.tsx` 后缀 355 | 356 | ```json 357 | { 358 | "scripts": { 359 | "eslint": "eslint src --ext .ts,.tsx" 360 | } 361 | } 362 | ``` 363 | 364 | #### VSCode 的配置中新增 typescriptreact 检查 365 | 366 | ```json 367 | { 368 | "files.eol": "\\n", 369 | "editor.tabSize": 4, 370 | "editor.formatOnSave": true, 371 | "editor.defaultFormatter": "esbenp.prettier-vscode", 372 | "eslint.autoFixOnSave": true, 373 | "eslint.validate": [ 374 | "javascript", 375 | "javascriptreact", 376 | { 377 | "language": "typescript", 378 | "autoFix": true 379 | }, 380 | { 381 | "language": "typescriptreact", 382 | "autoFix": true 383 | } 384 | ], 385 | "typescript.tsdk": "node_modules/typescript/lib" 386 | } 387 | ``` 388 | 389 | #### 使用 AlloyTeam ESLint 规则中的 TypeScript React 版本 390 | 391 | [AlloyTeam ESLint 规则中的 TypeScript React 版本](https://github.com/AlloyTeam/eslint-config-alloy#typescript-react) 392 | 393 | ## Troubleshootings 394 | 395 | ### Cannot find module '@typescript-eslint/parser' 396 | 397 | 你运行的是全局的 eslint,需要改为运行 `./node_modules/.bin/eslint`。 398 | 399 | ### VSCode 没有显示出 ESLint 的报错 400 | 401 | 1. 检查「文件 => 首选项 => 设置」中有没有配置正确 402 | 2. 检查必要的 npm 包有没有安装 403 | 3. 检查 `.eslintrc.js` 有没有配置 404 | 4. 检查文件是不是在 `.eslintignore` 中 405 | 406 | 如果以上步骤都不奏效,则可以在「文件 => 首选项 => 设置」中配置 `"eslint.trace.server": "messages"`,按 `Ctrl`+`Shift`+`U` 打开输出面板,然后选择 ESLint 输出,查看具体错误。 407 | 408 | ![VSCode 的 ESLint 输出](../assets/vscode-output-eslint.png) 409 | 410 | ### 为什么有些定义了的变量(比如使用 `enum` 定义的变量)未使用,ESLint 却没有报错? 411 | 412 | 因为无法支持这种变量定义的检查。建议在 `tsconfig.json` 中添加以下配置,使 `tsc` 编译过程能够检查出定义了未使用的变量: 413 | 414 | ```json 415 | { 416 | "compilerOptions": { 417 | "noUnusedLocals": true, 418 | "noUnusedParameters": true 419 | } 420 | } 421 | ``` 422 | 423 | ### 启用了 noUnusedParameters 之后,只使用了第二个参数,但是又必须传入第一个参数,这就会报错了 424 | 425 | 第一个参数以下划线开头即可,参考 https://github.com/Microsoft/TypeScript/issues/9458 426 | 427 | [ESLint]: https://eslint.org/ 428 | [typescript-eslint]: https://github.com/typescript-eslint/typescript-eslint 429 | -------------------------------------------------------------------------------- /basics/type-assertion.md: -------------------------------------------------------------------------------- 1 | # 类型断言 2 | 3 | 类型断言(Type Assertion)可以用来手动指定一个值的类型。 4 | 5 | ## 语法 6 | 7 | ```ts 8 | 值 as 类型 9 | ``` 10 | 11 | 或 12 | 13 | ```ts 14 | <类型>值 15 | ``` 16 | 17 | 在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 `值 as 类型`。 18 | 19 | 形如 `` 的语法在 tsx 中表示的是一个 `ReactNode`,在 ts 中除了表示类型断言之外,也可能是表示一个[泛型][]。 20 | 21 | 故建议大家在使用类型断言时,统一使用 `值 as 类型` 这样的语法,本书中也会贯彻这一思想。 22 | 23 | ## 类型断言的用途 24 | 25 | 类型断言的常见用途有以下几种: 26 | 27 | ### 将一个联合类型断言为其中一个类型 28 | 29 | [之前提到过](union-types.md#访问联合类型的属性或方法),当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们**只能访问此联合类型的所有类型中共有的属性或方法**: 30 | 31 | ```ts 32 | interface Cat { 33 | name: string; 34 | run(): void; 35 | } 36 | interface Fish { 37 | name: string; 38 | swim(): void; 39 | } 40 | 41 | function getName(animal: Cat | Fish) { 42 | return animal.name; 43 | } 44 | ``` 45 | 46 | 而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,比如: 47 | 48 | ```ts 49 | interface Cat { 50 | name: string; 51 | run(): void; 52 | } 53 | interface Fish { 54 | name: string; 55 | swim(): void; 56 | } 57 | 58 | function isFish(animal: Cat | Fish) { 59 | if (typeof animal.swim === 'function') { 60 | return true; 61 | } 62 | return false; 63 | } 64 | 65 | // index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'. 66 | // Property 'swim' does not exist on type 'Cat'. 67 | ``` 68 | 69 | 上面的例子中,获取 `animal.swim` 的时候会报错。 70 | 71 | 此时可以使用类型断言,将 `animal` 断言成 `Fish`: 72 | 73 | ```ts 74 | interface Cat { 75 | name: string; 76 | run(): void; 77 | } 78 | interface Fish { 79 | name: string; 80 | swim(): void; 81 | } 82 | 83 | function isFish(animal: Cat | Fish) { 84 | if (typeof (animal as Fish).swim === 'function') { 85 | return true; 86 | } 87 | return false; 88 | } 89 | ``` 90 | 91 | 这样就可以解决访问 `animal.swim` 时报错的问题了。 92 | 93 | 需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误: 94 | 95 | ```ts 96 | interface Cat { 97 | name: string; 98 | run(): void; 99 | } 100 | interface Fish { 101 | name: string; 102 | swim(): void; 103 | } 104 | 105 | function swim(animal: Cat | Fish) { 106 | (animal as Fish).swim(); 107 | } 108 | 109 | const tom: Cat = { 110 | name: 'Tom', 111 | run() { console.log('run') } 112 | }; 113 | swim(tom); 114 | // Uncaught TypeError: animal.swim is not a function` 115 | ``` 116 | 117 | 上面的例子编译时不会报错,但在运行时会报错: 118 | 119 | ```text 120 | Uncaught TypeError: animal.swim is not a function` 121 | ``` 122 | 123 | 原因是 `(animal as Fish).swim()` 这段代码隐藏了 `animal` 可能为 `Cat` 的情况,将 `animal` 直接断言为 `Fish` 了,而 TypeScript 编译器信任了我们的断言,故在调用 `swim()` 时没有编译错误。 124 | 125 | 可是 `swim` 函数接受的参数是 `Cat | Fish`,一旦传入的参数是 `Cat` 类型的变量,由于 `Cat` 上没有 `swim` 方法,就会导致运行时错误了。 126 | 127 | 总之,使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误。 128 | 129 | ### 将一个父类断言为更加具体的子类 130 | 131 | 当类之间有继承关系时,类型断言也是很常见的: 132 | 133 | ```ts 134 | class ApiError extends Error { 135 | code: number = 0; 136 | } 137 | class HttpError extends Error { 138 | statusCode: number = 200; 139 | } 140 | 141 | function isApiError(error: Error) { 142 | if (typeof (error as ApiError).code === 'number') { 143 | return true; 144 | } 145 | return false; 146 | } 147 | ``` 148 | 149 | 上面的例子中,我们声明了函数 `isApiError`,它用来判断传入的参数是不是 `ApiError` 类型,为了实现这样一个函数,它的参数的类型肯定得是比较抽象的父类 `Error`,这样的话这个函数就能接受 `Error` 或它的子类作为参数了。 150 | 151 | 但是由于父类 `Error` 中没有 `code` 属性,故直接获取 `error.code` 会报错,需要使用类型断言获取 `(error as ApiError).code`。 152 | 153 | 大家可能会注意到,在这个例子中有一个更合适的方式来判断是不是 `ApiError`,那就是使用 `instanceof`: 154 | 155 | ```ts 156 | class ApiError extends Error { 157 | code: number = 0; 158 | } 159 | class HttpError extends Error { 160 | statusCode: number = 200; 161 | } 162 | 163 | function isApiError(error: Error) { 164 | if (error instanceof ApiError) { 165 | return true; 166 | } 167 | return false; 168 | } 169 | ``` 170 | 171 | 上面的例子中,确实使用 `instanceof` 更加合适,因为 `ApiError` 是一个 JavaScript 的类,能够通过 `instanceof` 来判断 `error` 是否是它的实例。 172 | 173 | 但是有的情况下 `ApiError` 和 `HttpError` 不是一个真正的类,而只是一个 TypeScript 的接口(`interface`),接口是一个类型,不是一个真正的值,它在编译结果中会被删除,当然就无法使用 `instanceof` 来做运行时判断了: 174 | 175 | ```ts 176 | interface ApiError extends Error { 177 | code: number; 178 | } 179 | interface HttpError extends Error { 180 | statusCode: number; 181 | } 182 | 183 | function isApiError(error: Error) { 184 | if (error instanceof ApiError) { 185 | return true; 186 | } 187 | return false; 188 | } 189 | 190 | // index.ts:9:26 - error TS2693: 'ApiError' only refers to a type, but is being used as a value here. 191 | ``` 192 | 193 | 此时就只能用类型断言,通过判断是否存在 `code` 属性,来判断传入的参数是不是 `ApiError` 了: 194 | 195 | ```ts 196 | interface ApiError extends Error { 197 | code: number; 198 | } 199 | interface HttpError extends Error { 200 | statusCode: number; 201 | } 202 | 203 | function isApiError(error: Error) { 204 | if (typeof (error as ApiError).code === 'number') { 205 | return true; 206 | } 207 | return false; 208 | } 209 | ``` 210 | 211 | ### 将任何一个类型断言为 `any` 212 | 213 | 理想情况下,TypeScript 的类型系统运转良好,每个值的类型都具体而精确。 214 | 215 | 当我们引用一个在此类型上不存在的属性或方法时,就会报错: 216 | 217 | ```ts 218 | const foo: number = 1; 219 | foo.length = 1; 220 | 221 | // index.ts:2:5 - error TS2339: Property 'length' does not exist on type 'number'. 222 | ``` 223 | 224 | 上面的例子中,数字类型的变量 `foo` 上是没有 `length` 属性的,故 TypeScript 给出了相应的错误提示。 225 | 226 | 这种错误提示显然是非常有用的。 227 | 228 | 但有的时候,我们非常确定这段代码不会出错,比如下面这个例子: 229 | 230 | ```ts 231 | window.foo = 1; 232 | 233 | // index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'. 234 | ``` 235 | 236 | 上面的例子中,我们需要将 `window` 上添加一个属性 `foo`,但 TypeScript 编译时会报错,提示我们 `window` 上不存在 `foo` 属性。 237 | 238 | 此时我们可以使用 `as any` 临时将 `window` 断言为 `any` 类型: 239 | 240 | ```ts 241 | (window as any).foo = 1; 242 | ``` 243 | 244 | 在 `any` 类型的变量上,访问任何属性都是允许的。 245 | 246 | 需要注意的是,将一个变量断言为 `any` 可以说是解决 TypeScript 中类型问题的最后一个手段。 247 | 248 | **它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 `as any`。** 249 | 250 | 上面的例子中,我们也可以通过[扩展 window 的类型(TODO)][]解决这个错误,不过如果只是临时的增加 `foo` 属性,`as any` 会更加方便。 251 | 252 | 总之,**一方面不能滥用 `as any`,另一方面也不要完全否定它的作用,我们需要在类型的严格性和开发的便利性之间掌握平衡**(这也是 [TypeScript 的设计理念][]之一),才能发挥出 TypeScript 最大的价值。 253 | 254 | ### 将 `any` 断言为一个具体的类型 255 | 256 | 在日常的开发中,我们不可避免的需要处理 `any` 类型的变量,它们可能是由于第三方库未能定义好自己的类型,也有可能是历史遗留的或其他人编写的烂代码,还可能是受到 TypeScript 类型系统的限制而无法精确定义类型的场景。 257 | 258 | 遇到 `any` 类型的变量时,我们可以选择无视它,任由它滋生更多的 `any`。 259 | 260 | 我们也可以选择改进它,通过类型断言及时的把 `any` 断言为精确的类型,亡羊补牢,使我们的代码向着高可维护性的目标发展。 261 | 262 | 举例来说,历史遗留的代码中有个 `getCacheData`,它的返回值是 `any`: 263 | 264 | ```ts 265 | function getCacheData(key: string): any { 266 | return (window as any).cache[key]; 267 | } 268 | ``` 269 | 270 | 那么我们在使用它时,最好能够将调用了它之后的返回值断言成一个精确的类型,这样就方便了后续的操作: 271 | 272 | ```ts 273 | function getCacheData(key: string): any { 274 | return (window as any).cache[key]; 275 | } 276 | 277 | interface Cat { 278 | name: string; 279 | run(): void; 280 | } 281 | 282 | const tom = getCacheData('tom') as Cat; 283 | tom.run(); 284 | ``` 285 | 286 | 上面的例子中,我们调用完 `getCacheData` 之后,立即将它断言为 `Cat` 类型。这样的话明确了 `tom` 的类型,后续对 `tom` 的访问时就有了代码补全,提高了代码的可维护性。 287 | 288 | ## 类型断言的限制 289 | 290 | > 本小节的前置知识点:[结构类型系统(TODO)][]、[类型兼容性(TODO)][] 291 | 292 | 从上面的例子中,我们可以总结出: 293 | 294 | - 联合类型可以被断言为其中一个类型 295 | - 父类可以被断言为子类 296 | - 任何类型都可以被断言为 any 297 | - any 可以被断言为任何类型 298 | 299 | 那么类型断言有没有什么限制呢?是不是任何一个类型都可以被断言为任何另一个类型呢? 300 | 301 | 答案是否定的——并不是任何一个类型都可以被断言为任何另一个类型。 302 | 303 | 具体来说,若 `A` 兼容 `B`,那么 `A` 能够被断言为 `B`,`B` 也能被断言为 `A`。 304 | 305 | 下面我们通过一个简化的例子,来理解类型断言的限制: 306 | 307 | ```ts 308 | interface Animal { 309 | name: string; 310 | } 311 | interface Cat { 312 | name: string; 313 | run(): void; 314 | } 315 | 316 | let tom: Cat = { 317 | name: 'Tom', 318 | run: () => { console.log('run') } 319 | }; 320 | let animal: Animal = tom; 321 | ``` 322 | 323 | 我们知道,TypeScript 是结构类型系统,类型之间的对比只会比较它们最终的结构,而会忽略它们定义时的关系。 324 | 325 | 在上面的例子中,`Cat` 包含了 `Animal` 中的所有属性,除此之外,它还有一个额外的方法 `run`。TypeScript 并不关心 `Cat` 和 `Animal` 之间定义时是什么关系,而只会看它们最终的结构有什么关系——所以它与 `Cat extends Animal` 是等价的: 326 | 327 | ```ts 328 | interface Animal { 329 | name: string; 330 | } 331 | interface Cat extends Animal { 332 | run(): void; 333 | } 334 | ``` 335 | 336 | 那么也不难理解为什么 `Cat` 类型的 `tom` 可以赋值给 `Animal` 类型的 `animal` 了——就像面向对象编程中我们可以将子类的实例赋值给类型为父类的变量。 337 | 338 | 我们把它换成 TypeScript 中更专业的说法,即:`Animal` 兼容 `Cat`。 339 | 340 | 当 `Animal` 兼容 `Cat` 时,它们就可以互相进行类型断言了: 341 | 342 | ```ts 343 | interface Animal { 344 | name: string; 345 | } 346 | interface Cat { 347 | name: string; 348 | run(): void; 349 | } 350 | 351 | function testAnimal(animal: Animal) { 352 | return (animal as Cat); 353 | } 354 | function testCat(cat: Cat) { 355 | return (cat as Animal); 356 | } 357 | ``` 358 | 359 | 这样的设计其实也很容易就能理解: 360 | 361 | - 允许 `animal as Cat` 是因为「父类可以被断言为子类」,这个前面已经学习过了 362 | - 允许 `cat as Animal` 是因为既然子类拥有父类的属性和方法,那么被断言为父类,获取父类的属性、调用父类的方法,就不会有任何问题,故「子类可以被断言为父类」 363 | 364 | 需要注意的是,这里我们使用了简化的父类子类的关系来表达类型的兼容性,而实际上 TypeScript 在判断类型的兼容性时,比这种情况复杂很多,详细请参考[类型的兼容性(TODO)][]章节。 365 | 366 | 总之,若 `A` 兼容 `B`,那么 `A` 能够被断言为 `B`,`B` 也能被断言为 `A`。 367 | 368 | 同理,若 `B` 兼容 `A`,那么 `A` 能够被断言为 `B`,`B` 也能被断言为 `A`。 369 | 370 | 所以这也可以换一种说法: 371 | 372 | 要使得 `A` 能够被断言为 `B`,只需要 `A` 兼容 `B` 或 `B` 兼容 `A` 即可,这也是为了在类型断言时的安全考虑,毕竟毫无根据的断言是非常危险的。 373 | 374 | 综上所述: 375 | 376 | - 联合类型可以被断言为其中一个类型 377 | - 父类可以被断言为子类 378 | - 任何类型都可以被断言为 any 379 | - any 可以被断言为任何类型 380 | - 要使得 `A` 能够被断言为 `B`,只需要 `A` 兼容 `B` 或 `B` 兼容 `A` 即可 381 | 382 | 其实前四种情况都是最后一个的特例。 383 | 384 | ## 双重断言 385 | 386 | 既然: 387 | 388 | - 任何类型都可以被断言为 any 389 | - any 可以被断言为任何类型 390 | 391 | 那么我们是不是可以使用双重断言 `as any as Foo` 来将任何一个类型断言为任何另一个类型呢? 392 | 393 | ```ts 394 | interface Cat { 395 | run(): void; 396 | } 397 | interface Fish { 398 | swim(): void; 399 | } 400 | 401 | function testCat(cat: Cat) { 402 | return (cat as any as Fish); 403 | } 404 | ``` 405 | 406 | 在上面的例子中,若直接使用 `cat as Fish` 肯定会报错,因为 `Cat` 和 `Fish` 互相都不兼容。 407 | 408 | 但是若使用双重断言,则可以打破「要使得 `A` 能够被断言为 `B`,只需要 `A` 兼容 `B` 或 `B` 兼容 `A` 即可」的限制,将任何一个类型断言为任何另一个类型。 409 | 410 | 若你使用了这种双重断言,那么十有八九是非常错误的,它很可能会导致运行时错误。 411 | 412 | **除非迫不得已,千万别用双重断言。** 413 | 414 | ## 类型断言 vs 类型转换 415 | 416 | 类型断言只会影响 TypeScript 编译时的类型,类型断言语句在编译结果中会被删除: 417 | 418 | ```ts 419 | function toBoolean(something: any): boolean { 420 | return something as boolean; 421 | } 422 | 423 | toBoolean(1); 424 | // 返回值为 1 425 | ``` 426 | 427 | 在上面的例子中,将 `something` 断言为 `boolean` 虽然可以通过编译,但是并没有什么用,代码在编译后会变成: 428 | 429 | ```js 430 | function toBoolean(something) { 431 | return something; 432 | } 433 | 434 | toBoolean(1); 435 | // 返回值为 1 436 | ``` 437 | 438 | 所以类型断言不是类型转换,它不会真的影响到变量的类型。 439 | 440 | 若要进行类型转换,需要直接调用类型转换的方法: 441 | 442 | ```ts 443 | function toBoolean(something: any): boolean { 444 | return Boolean(something); 445 | } 446 | 447 | toBoolean(1); 448 | // 返回值为 true 449 | ``` 450 | 451 | ## 类型断言 vs 类型声明 452 | 453 | 在这个例子中: 454 | 455 | ```ts 456 | function getCacheData(key: string): any { 457 | return (window as any).cache[key]; 458 | } 459 | 460 | interface Cat { 461 | name: string; 462 | run(): void; 463 | } 464 | 465 | const tom = getCacheData('tom') as Cat; 466 | tom.run(); 467 | ``` 468 | 469 | 我们使用 `as Cat` 将 `any` 类型断言为了 `Cat` 类型。 470 | 471 | 但实际上还有其他方式可以解决这个问题: 472 | 473 | ```ts 474 | function getCacheData(key: string): any { 475 | return (window as any).cache[key]; 476 | } 477 | 478 | interface Cat { 479 | name: string; 480 | run(): void; 481 | } 482 | 483 | const tom: Cat = getCacheData('tom'); 484 | tom.run(); 485 | ``` 486 | 487 | 上面的例子中,我们通过类型声明的方式,将 `tom` 声明为 `Cat`,然后再将 `any` 类型的 `getCacheData('tom')` 赋值给 `Cat` 类型的 `tom`。 488 | 489 | 这和类型断言是非常相似的,而且产生的结果也几乎是一样的——`tom` 在接下来的代码中都变成了 `Cat` 类型。 490 | 491 | 它们的区别,可以通过这个例子来理解: 492 | 493 | ```ts 494 | interface Animal { 495 | name: string; 496 | } 497 | interface Cat { 498 | name: string; 499 | run(): void; 500 | } 501 | 502 | const animal: Animal = { 503 | name: 'tom' 504 | }; 505 | let tom = animal as Cat; 506 | ``` 507 | 508 | 在上面的例子中,由于 `Animal` 兼容 `Cat`,故可以将 `animal` 断言为 `Cat` 赋值给 `tom`。 509 | 510 | 但是若直接声明 `tom` 为 `Cat` 类型: 511 | 512 | ```ts 513 | interface Animal { 514 | name: string; 515 | } 516 | interface Cat { 517 | name: string; 518 | run(): void; 519 | } 520 | 521 | const animal: Animal = { 522 | name: 'tom' 523 | }; 524 | let tom: Cat = animal; 525 | 526 | // index.ts:12:5 - error TS2741: Property 'run' is missing in type 'Animal' but required in type 'Cat'. 527 | ``` 528 | 529 | 则会报错,不允许将 `animal` 赋值为 `Cat` 类型的 `tom`。 530 | 531 | 这很容易理解,`Animal` 可以看作是 `Cat` 的父类,当然不能将父类的实例赋值给类型为子类的变量。 532 | 533 | 深入的讲,它们的核心区别就在于: 534 | 535 | - `animal` 断言为 `Cat`,只需要满足 `Animal` 兼容 `Cat` 或 `Cat` 兼容 `Animal` 即可 536 | - `animal` 赋值给 `tom`,需要满足 `Cat` 兼容 `Animal` 才行 537 | 538 | 但是 `Cat` 并不兼容 `Animal`。 539 | 540 | 而在前一个例子中,由于 `getCacheData('tom')` 是 `any` 类型,`any` 兼容 `Cat`,`Cat` 也兼容 `any`,故 541 | 542 | ```ts 543 | const tom = getCacheData('tom') as Cat; 544 | ``` 545 | 546 | 等价于 547 | 548 | ```ts 549 | const tom: Cat = getCacheData('tom'); 550 | ``` 551 | 552 | 知道了它们的核心区别,就知道了类型声明是比类型断言更加严格的。 553 | 554 | 所以为了增加代码的质量,我们最好优先使用类型声明,这也比类型断言的 `as` 语法更加优雅。 555 | 556 | ## 类型断言 vs 泛型 557 | 558 | > 本小节的前置知识点:[泛型][] 559 | 560 | 还是这个例子: 561 | 562 | ```ts 563 | function getCacheData(key: string): any { 564 | return (window as any).cache[key]; 565 | } 566 | 567 | interface Cat { 568 | name: string; 569 | run(): void; 570 | } 571 | 572 | const tom = getCacheData('tom') as Cat; 573 | tom.run(); 574 | ``` 575 | 576 | 我们还有第三种方式可以解决这个问题,那就是泛型: 577 | 578 | ```ts 579 | function getCacheData(key: string): T { 580 | return (window as any).cache[key]; 581 | } 582 | 583 | interface Cat { 584 | name: string; 585 | run(): void; 586 | } 587 | 588 | const tom = getCacheData('tom'); 589 | tom.run(); 590 | ``` 591 | 592 | 通过给 `getCacheData` 函数添加了一个泛型 ``,我们可以更加规范的实现对 `getCacheData` 返回值的约束,这也同时去除掉了代码中的 `any`,是最优的一个解决方案。 593 | 594 | ## 参考 595 | 596 | - [TypeScript Deep Dive / Type Assertion](https://basarat.gitbooks.io/typescript/content/docs/types/type-assertion.html) 597 | - [Advanced Types # Type Guards and Differentiating Types](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types)([中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Advanced%20Types.html#类型保护与区分类型(type-guards-and-differentiating-types))) 598 | - [TypeScript 的设计理念][] 599 | 600 | [TypeScript 的设计理念]: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals 601 | [泛型]: ../advanced/generics.md 602 | -------------------------------------------------------------------------------- /basics/declaration-files.md: -------------------------------------------------------------------------------- 1 | # 声明文件 2 | 3 | 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。 4 | 5 | ## 新语法索引 6 | 7 | 由于本章涉及大量新语法,故在本章开头列出新语法的索引,方便大家在使用这些新语法时能快速查找到对应的讲解: 8 | 9 | - [`declare var`](#declare-var) 声明全局变量 10 | - [`declare function`](#declare-function) 声明全局方法 11 | - [`declare class`](#declare-class) 声明全局类 12 | - [`declare enum`](#declare-enum) 声明全局枚举类型 13 | - [`declare namespace`](#declare-namespace) 声明(含有子属性的)全局对象 14 | - [`interface` 和 `type`](#interface-和-type) 声明全局类型 15 | - [`export`](#export) 导出变量 16 | - [`export namespace`](#export-namespace) 导出(含有子属性的)对象 17 | - [`export default`](#export-default) ES6 默认导出 18 | - [`export =`](#export-1) commonjs 导出模块 19 | - [`export as namespace`](#export-as-namespace) UMD 库声明全局变量 20 | - [`declare global`](#declare-global) 扩展全局变量 21 | - [`declare module`](#declare-module) 扩展模块 22 | - [`/// `](#san-xie-xian-zhi-ling) 三斜线指令 23 | 24 | ## 什么是声明语句 25 | 26 | 假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 `