├── docs ├── faqs │ ├── comments.md │ ├── decorators.md │ ├── glossary-and-terms.md │ ├── enums.md │ ├── common-feature-request.md │ ├── type-guards.md │ ├── function.md │ ├── jsx-and-react.md │ ├── modules.md │ ├── thing-that-dont-work.md │ ├── tsconfig-behavior.md │ ├── commandline-behavior.md │ ├── common-bug-not-bugs.md │ ├── generics.md │ └── class.md ├── .vuepress │ ├── theme │ │ ├── styles │ │ │ ├── toc.styl │ │ │ ├── wrapper.styl │ │ │ ├── arrow.styl │ │ │ ├── mobile.styl │ │ │ ├── custom-blocks.styl │ │ │ ├── code.styl │ │ │ └── theme.styl │ │ ├── layouts │ │ │ ├── 404.vue │ │ │ └── Layout.vue │ │ ├── components │ │ │ ├── DropdownTransition.vue │ │ │ ├── Ads.vue │ │ │ ├── SidebarButton.vue │ │ │ ├── NavLink.vue │ │ │ ├── Sidebar.vue │ │ │ ├── SidebarLinks.vue │ │ │ ├── SidebarLink.vue │ │ │ ├── SidebarGroup.vue │ │ │ ├── Navbar.vue │ │ │ ├── Home.vue │ │ │ ├── NavLinks.vue │ │ │ ├── DropdownLink.vue │ │ │ └── AlgoliaSearchBox.vue │ │ ├── index.js │ │ └── global-components │ │ │ └── Badge.vue │ ├── public │ │ ├── ide.png │ │ ├── logo.png │ │ ├── contact.png │ │ ├── qrcode.jpg │ │ ├── wechat.jpg │ │ ├── zhifubao.jpg │ │ ├── typescript-downloads.jpg │ │ ├── icons │ │ │ ├── android-chrome-36x36.png │ │ │ ├── android-chrome-48x48.png │ │ │ ├── android-chrome-72x72.png │ │ │ ├── android-chrome-96x96.png │ │ │ ├── android-chrome-144x144.png │ │ │ └── android-chrome-192x192.png │ │ └── manifest.json │ └── config.js ├── new │ └── typescript-3.9.md ├── tips │ ├── stringBasedEmuns.md │ ├── staticConstructors.md │ ├── curry.md │ ├── createArrays.md │ ├── functionParameters.md │ ├── limitPropertySetters.md │ ├── statefulFunctions.md │ ├── typeInstantiation.md │ ├── singletonPatern.md │ ├── classAreUseful.md │ ├── buildToggles.md │ ├── truthy.md │ ├── lazyObjectLiteralInitialization.md │ ├── avoidExportDefault.md │ ├── barrel.md │ ├── bind.md │ ├── typesafeEventEmitter.md │ ├── outFileCaution.md │ ├── covarianceAndContravariance.md │ ├── nominalTyping.md │ ├── metadata.md │ └── infer.md ├── compiler │ ├── program.md │ ├── checker.md │ ├── overview.md │ ├── scanner.md │ ├── parser.md │ └── ast.md ├── jsx │ ├── support.md │ └── nonReactJSX.md ├── typings │ ├── types.md │ ├── neverType.md │ ├── callable.md │ ├── ambient.md │ ├── movingTypes.md │ ├── interfaces.md │ ├── mixins.md │ ├── thisType.md │ ├── typeAssertion.md │ ├── literals.md │ ├── freshness.md │ ├── migrating.md │ ├── typeGuard.md │ ├── exceptionsHanding.md │ ├── functions.md │ ├── typeInference.md │ ├── readonly.md │ ├── discrominatedUnion.md │ └── generices.md ├── project │ ├── declarationspaces.md │ ├── namespaces.md │ ├── dynamicImportExpressions.md │ └── compilationContext.md ├── error │ ├── common.md │ └── interpreting.md └── README.md ├── .gitattributes ├── .huskyrc.js ├── .gitignore ├── .prettierrc ├── commitlint.config.js ├── .github └── workflows │ └── gh-pages.yml ├── LICENSE └── package.json /docs/faqs/comments.md: -------------------------------------------------------------------------------- 1 | # 评论 2 | -------------------------------------------------------------------------------- /docs/faqs/decorators.md: -------------------------------------------------------------------------------- 1 | # Decorators 2 | -------------------------------------------------------------------------------- /docs/faqs/glossary-and-terms.md: -------------------------------------------------------------------------------- 1 | # 术语表 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=TypeScript -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/toc.styl: -------------------------------------------------------------------------------- 1 | .table-of-contents 2 | .badge 3 | vertical-align middle 4 | -------------------------------------------------------------------------------- /docs/.vuepress/public/ide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/ide.png -------------------------------------------------------------------------------- /docs/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/logo.png -------------------------------------------------------------------------------- /docs/.vuepress/public/contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/contact.png -------------------------------------------------------------------------------- /docs/.vuepress/public/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/qrcode.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/wechat.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/zhifubao.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/zhifubao.jpg -------------------------------------------------------------------------------- /docs/new/typescript-3.9.md: -------------------------------------------------------------------------------- 1 | # TypeScript 3.9 2 | 3 | [TypeScript3.9 —— 前端之颠](https://wemp.app/posts/7b99df08-8245-4dbe-8514-c10919779e6a) 4 | -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS' 4 | // 'pre-commit': 'lint-staged' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /docs/.vuepress/public/typescript-downloads.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/typescript-downloads.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/icons/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/icons/android-chrome-36x36.png -------------------------------------------------------------------------------- /docs/.vuepress/public/icons/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/icons/android-chrome-48x48.png -------------------------------------------------------------------------------- /docs/.vuepress/public/icons/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/icons/android-chrome-72x72.png -------------------------------------------------------------------------------- /docs/.vuepress/public/icons/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/icons/android-chrome-96x96.png -------------------------------------------------------------------------------- /docs/.vuepress/public/icons/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/icons/android-chrome-144x144.png -------------------------------------------------------------------------------- /docs/.vuepress/public/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkchao/typescript-book-chinese/HEAD/docs/.vuepress/public/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/tips/stringBasedEmuns.md: -------------------------------------------------------------------------------- 1 | # 基于字符串的枚举 2 | 3 | 有时你需要在公共的键下收集一些字符串的集合。在 TypeScript 2.4 以前,它仅支持基于数字类型的枚举,如果你在使用 TypeScript 2.4 以上的版本,你通过可以使用[字符串字面量类型与联合类型组合使用创建基于字符串枚举类型的方式](../typings/literals.md#使用用例)。 4 | -------------------------------------------------------------------------------- /docs/faqs/enums.md: -------------------------------------------------------------------------------- 1 | # 枚举 2 | 3 | ## `enum` 和 `const enum` 之间的区别是什么? 4 | 5 | TODO:`enum` / `const enum` 有如下区别,请看:[https://www.typescriptlang.org/docs/handbook/enums.html#enums](https://www.typescriptlang.org/docs/handbook/enums.html#enums) -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/wrapper.styl: -------------------------------------------------------------------------------- 1 | $wrapper 2 | max-width $contentWidth 3 | // margin 0 2rem 4 | padding 2rem 2.5rem 5 | @media (max-width: $MQNarrow) 6 | padding 2rem 7 | @media (max-width: $MQMobileNarrow) 8 | padding 1.5rem 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | .vs 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | 17 | # Others 18 | .history 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "none", 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "avoid", 9 | "requirePragma": false, 10 | "proseWrap": "preserve" 11 | } -------------------------------------------------------------------------------- /docs/tips/staticConstructors.md: -------------------------------------------------------------------------------- 1 | # TypeScript 中的静态构造函数 2 | 3 | TypeScript 中的 `class` (JavaScript 中的 `class`)没有静态构造函数的功能,但是你可以通过调用它自己来获取相同的效果: 4 | 5 | ```ts 6 | class MyClass { 7 | static initalize() { 8 | // 9 | } 10 | } 11 | 12 | MyClass.initalize(); 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/tips/curry.md: -------------------------------------------------------------------------------- 1 | # 柯里化 2 | 3 | 仅仅需要使用一系列箭头函数: 4 | 5 | ```ts 6 | // 一个柯里化函数 7 | let add = (x: number) => (y: number) => x + y; 8 | 9 | // 简单使用 10 | add(123)(456); 11 | 12 | // 部分应用 13 | let add123 = add(123); 14 | 15 | // fully apply the function 16 | add123(456); 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/tips/createArrays.md: -------------------------------------------------------------------------------- 1 | # 创建数组 2 | 3 | 创建数组十分简单: 4 | 5 | ```ts 6 | const foo: string[] = []; 7 | ``` 8 | 9 | 你也可以在创建数组时使用 ES6 的 `Array.prototype.fill` 方法为数组填充数据: 10 | 11 | ```ts 12 | const foo: string[] = new Array(3).fill(''); 13 | console.log(foo); // 会输出 ['','',''] 14 | ``` 15 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const types = ['build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test', 'chore', 'update']; 2 | 3 | module.exports = { 4 | // https://github.com/conventional-changelog/commitlint/blob/master/%40commitlint/config-angular/README.md 5 | extends: ['@commitlint/config-angular'], 6 | 7 | rules: { 8 | 'type-enum': [2, 'always', types] 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /docs/tips/functionParameters.md: -------------------------------------------------------------------------------- 1 | # 函数参数 2 | 3 | 如果你有一个含有很多参数或者相同类型参数的函数,那么你可能需要考虑将函数改为接收对象的形式: 4 | 5 | 如下一个函数: 6 | 7 | ```ts 8 | function foo(flagA: boolean, flagB: boolean) { 9 | // 函数主体 10 | } 11 | ``` 12 | 13 | 像这样的函数,你可能会很容易错误的调用它,如 `foo(flagB, flagA)`,并且你并不会从编译器得到想要的帮助。 14 | 15 | 你可以将函数变为接收对象的形式: 16 | 17 | ```ts 18 | function foo(config: { flagA: boolean; flagB: boolean }) { 19 | const { flagA, flagB } = config; 20 | } 21 | ``` 22 | 23 | 现在,函数将会被 `foo({ flagA, flagB })` 的形式调用,这样有利于发现错误及代码审查。 24 | 25 | ::: tip 26 | 如果你的函数足够简单,并且你不希望增加代码,忽略这个建议。 27 | ::: 28 | -------------------------------------------------------------------------------- /docs/faqs/common-feature-request.md: -------------------------------------------------------------------------------- 1 | # 一些常见的 Feature 需求 2 | 3 | 这有一些常见的 Feature 列表以及相应的 ISSUES,在提新的 ISSUES 之前,如果有相同的 Feature,请在相应的评论区留下评论 4 | 5 | - 安全的导航操作符 [#16](https://github.com/Microsoft/TypeScript/issues/16) 6 | - 最小化 [#8](https://github.com/Microsoft/TypeScript/issues/8) 7 | - 局部的 classes [#563](https://github.com/Microsoft/TypeScript/issues/563) 8 | - 与 `this` 相关 [#531](https://github.com/Microsoft/TypeScript/issues/531) 9 | - 函数成员 `call/bind/apply` 的强类型 [#212](https://github.com/Microsoft/TypeScript/issues/531) 10 | - 运行时的函数重载 [#3422](https://github.com/Microsoft/TypeScript/issues/531) 11 | -------------------------------------------------------------------------------- /docs/compiler/program.md: -------------------------------------------------------------------------------- 1 | # 程序 2 | 3 | 程序定义在 `program.ts` 中。[编译上下文](../project/compilationContext.md)在 TypeScript 编译器中被视为一个 `Program`,它包含 `SourceFile` 和编译选项 4 | 5 | ## `CompilerHost` 的使用 6 | 7 | CompilerHost 是与操作环境(OE, Operating Enviornment)进行交互的机制: 8 | 9 | `Program` _-使用->_ `CompilerHost` _-使用->_ `System` 10 | 11 | 用 `CompilerHost` 作中间层的原因是可以让接口对 `Program` 的需求进行细粒度的调整,而无需考虑操作环境的需求。(例如:`Program` 无需关心 `System` 的 `fileExists` 函数) 12 | 13 | 对`System`而言还有其他的使用者(比如测试) 14 | 15 | ## SourceFile 16 | 17 | 程序有个 API,用于获取 SourceFile:`getSourceFiles(): SourceFile[];`。得到的每个元素均是一棵抽象语法树的根节点(称做 `SourceFile`) 18 | -------------------------------------------------------------------------------- /docs/tips/limitPropertySetters.md: -------------------------------------------------------------------------------- 1 | # 减少 setter 属性的使用 2 | 3 | 倾向于使用更精确的 `set/get` 函数(如 `setBar`, `getBar`),减少使用 `setter/getter`; 4 | 5 | 考虑以下代码: 6 | 7 | ```ts 8 | foo.bar = { 9 | a: 123, 10 | b: 456 11 | }; 12 | ``` 13 | 14 | 存在 `setter/getter` 时: 15 | 16 | ```ts 17 | class Foo { 18 | a: number; 19 | b: number; 20 | set bar(value: { a: number; b: number }) { 21 | this.a = value.a; 22 | this.b = value.b; 23 | } 24 | } 25 | 26 | let foo = new Foo(); 27 | ``` 28 | 29 | 这并不是 `setter` 的一个好的使用场景,当开发人员阅读第一段代码时,不知道将要更改的所有内容的上下文。然而,当开发者使用 `foo.setBar(value)`,他可能会意识到在 `foo` 里可能会引起一些改变。 30 | -------------------------------------------------------------------------------- /docs/faqs/type-guards.md: -------------------------------------------------------------------------------- 1 | # 类型守卫 2 | 3 | ## 为什么 `x instanceof Foo` 不能将 `x` 的类型缩小至 `Foo`? 4 | 5 | 这取决于 `x` 是什么?如果 `x` 的类型不与 `Foo` 兼容,那么缩小 `x` 的类型就毫无意义,所以我们不会这么做。 6 | 7 | 当你发现 `x` 具有任何类型时,我们对此推荐的做法是: 8 | 9 | ```ts 10 | function doIt(x) { 11 | if (x instanceof Object) { 12 | // Assume 'x' is a well-known object which 13 | // we know how to handle specifically 14 | } 15 | 16 | // Treat 'x' as a primitive 17 | } 18 | ``` 19 | 20 | 你将在 TypeScript 中看到这些代码(它们可能早于联合类型被发现),或者是一些从 JavaScript 移植到 TypeScript 的代码,如果我们把 `x` 缩小至 `Object`,那么你将只能做更少的事情。使用任何不在 `Object` 中的属性都将导致错误。这不仅适用于 `Object`,对于具有已定义属性集的任何其他类型都是如此。 21 | -------------------------------------------------------------------------------- /docs/tips/statefulFunctions.md: -------------------------------------------------------------------------------- 1 | # 状态函数 2 | 3 | 其他编程语言有一个共同特征,它们使用 `static` 关键字来增加函数变量的生命周期(不是范围),使其超出函数的调用范围,如 C 语言中的实现: 4 | 5 | ```c 6 | void called () { 7 | static count = 0; 8 | count++; 9 | printf("Called : %d", count); 10 | } 11 | 12 | int main () { 13 | called(); // Called : 1 14 | called(); // Called : 2 15 | return 0; 16 | } 17 | ``` 18 | 19 | 由于 JavaScript(TypeScript)并没有静态函数的功能,你可以使用一个包裹着本地变量的抽象变量,如使用 `class`: 20 | 21 | ```ts 22 | const { called } = new class { 23 | count = 0; 24 | called = () => { 25 | this.count++; 26 | console.log(`Called : ${this.count}`); 27 | }; 28 | }(); 29 | 30 | called(); // Called : 1 31 | called(); // Called : 2 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/arrow.styl: -------------------------------------------------------------------------------- 1 | @require './config' 2 | 3 | .arrow 4 | display inline-block 5 | width 0 6 | height 0 7 | &.up 8 | border-left 4px solid transparent 9 | border-right 4px solid transparent 10 | border-bottom 6px solid $arrowBgColor 11 | &.down 12 | border-left 4px solid transparent 13 | border-right 4px solid transparent 14 | border-top 6px solid $arrowBgColor 15 | &.right 16 | border-top 4px solid transparent 17 | border-bottom 4px solid transparent 18 | border-left 6px solid $arrowBgColor 19 | &.left 20 | border-top 4px solid transparent 21 | border-bottom 4px solid transparent 22 | border-right 6px solid $arrowBgColor 23 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/404.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 5 | {{ getMsg() }} 6 | Take me home. 7 | 8 | 9 | 10 | 11 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | branches: 7 | - master 8 | 9 | push: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build-deploy: 15 | runs-on: ubuntu-18.04 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@master 19 | with: 20 | ref: master 21 | 22 | - name: Install Dependencies 23 | run: yarn 24 | 25 | - name: Build 26 | run: yarn build 27 | 28 | - name: Deploy 29 | uses: peaceiris/actions-gh-pages@v2 30 | env: 31 | ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} 32 | PUBLISH_BRANCH: gh-pages 33 | PUBLISH_DIR: ./docs/.vuepress/dist 34 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/DropdownTransition.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /docs/faqs/function.md: -------------------------------------------------------------------------------- 1 | # 函数 2 | 3 | ## 为什么我不能在解构函数 `function f({ x: number }) { /* ... */ }` 中使用 `x`? 4 | 5 | > 我写下这单代码,但是得到了一个错误 6 | 7 | ```ts 8 | function f({ x: number }) { 9 | // Error, x is not defined? 10 | console.log(x); 11 | } 12 | ``` 13 | 14 | 对于那些习惯于查看 TypeScript 类型字面量的人来说,解构语法是有悖常理的。语法 `f({ x: number })` 声明了属性名从 `x` 转换为 `number` 名的解构。 15 | 16 | 让我们从发出的代码来收到启发: 17 | 18 | ```ts 19 | function f(_a) { 20 | // Not really what we were going for 21 | var number = _a.x; 22 | } 23 | ``` 24 | 25 | 为了能让这段代码正确运行,你需要写下: 26 | 27 | ```ts 28 | function f({ x }: { x: number }) { 29 | // OK 30 | console.log(x); 31 | } 32 | ``` 33 | 34 | 如果你想为所有属性提供一个初始变量,最合适的写法是: 35 | 36 | ```ts 37 | function f({ x = 0 }) { 38 | // x: number 39 | console.log(x); 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/faqs/jsx-and-react.md: -------------------------------------------------------------------------------- 1 | # JSX 和 React 2 | 3 | ## 我写了声明 `declare var MyComponent: React.Component`,为什么我不能写 `` 4 | 5 | > 我写下了如下代码,为什么会抛出错误? 6 | 7 | ```ts 8 | class Display extends React.Component { 9 | render() { ... } 10 | } 11 | 12 | let SomeThing: Display = /* ... */; 13 | // Error here, isn't this OK? 14 | let jsx = ; 15 | ``` 16 | 17 | 这可能是把类的实例与静态类混淆了。当 React 实例化一个组件时,它在调用构造函数。因此当 TypeScript 看到一个 JSX 标签 `` 时,它在验证构造函数 `TagName` 的结果是否可以产生有效组件。 18 | 19 | 但是这个声明 `let someThing: Display` 只是表明了 `someThing` 是类的实例,并不是类的构造函数。实际上,他会在运行时抛出错误: 20 | 21 | ```ts 22 | let SomeThing = new Display(); 23 | let jsx = ; // Not gonna work 24 | ``` 25 | 26 | 最简单的修复方式是使用 `typeof` 操作符: 27 | 28 | ```ts 29 | let SomeThing: typeof Display = /* ... */; 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/jsx/support.md: -------------------------------------------------------------------------------- 1 | # 支持 JSX 2 | 3 | TypeScript 支持 JSX 转换和代码分析,如果你还不了解 JSX,[官网](https://facebook.github.io/jsx/)上有关于它的摘要: 4 | 5 | > JSX is an XML-like syntax extension to ECMAScript without any defined semantics. It's NOT intended to be implemented by engines or browsers. It's NOT a proposal to incorporate JSX into the ECMAScript spec itself. It's intended to be used by various preprocessors (transpilers) to transform these tokens into standard ECMAScript. 6 | 7 | JSX 背后的动机是允许用户在 JavaScript 中书写类似于 HTML 的视图,因此你可以: 8 | 9 | - 使用相同代码,既能检查你的 JavaScript,同时能检查你的 HTML 视图层部分。 10 | - 让视图层了解运行时的上下文(加强传统 MVC 中的控制器与视图连接)。 11 | - 复用 JavaScript 设计模式维护 HTML 部分,例如:用 `Array.prototype.map.`、`?:`、`switch` 等,代替创建新的可替代品。 12 | 13 | 这能够减少错误的可能性,并且能增加用户界面的可维护性。目前 JSX 的主要消费者来自 [facebook 推出的 ReactJS](http://facebook.github.io/react/),接下来我们结合它来讨论 JSX 用法。 -------------------------------------------------------------------------------- /docs/.vuepress/theme/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // Theme API. 4 | module.exports = (options, ctx) => ({ 5 | alias() { 6 | const { themeConfig, siteConfig } = ctx; 7 | // resolve algolia 8 | const isAlgoliaSearch = 9 | themeConfig.algolia || 10 | Object.keys((siteConfig.locales && themeConfig.locales) || {}).some(base => themeConfig.locales[base].algolia); 11 | return { 12 | '@AlgoliaSearchBox': isAlgoliaSearch 13 | ? path.resolve(__dirname, 'components/AlgoliaSearchBox.vue') 14 | : path.resolve(__dirname, 'noopModule.js') 15 | }; 16 | }, 17 | 18 | plugins: [ 19 | '@vuepress/active-header-links', 20 | '@vuepress/search', 21 | '@vuepress/plugin-nprogress', 22 | ['@vuepress/container', { type: 'tip' }], 23 | ['@vuepress/container', { type: 'warning' }], 24 | ['@vuepress/container', { type: 'danger' }] 25 | ] 26 | }); 27 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/mobile.styl: -------------------------------------------------------------------------------- 1 | @require './config' 2 | 3 | $mobileSidebarWidth = $sidebarWidth * 0.82 4 | 5 | // narrow desktop / iPad 6 | @media (max-width: $MQNarrow) 7 | .sidebar 8 | font-size 15px 9 | width $mobileSidebarWidth 10 | .page 11 | padding-left $mobileSidebarWidth 12 | 13 | // wide mobile 14 | @media (max-width: $MQMobile) 15 | .sidebar 16 | top 0 17 | padding-top $navbarHeight 18 | transform translateX(-100%) 19 | transition transform .2s ease 20 | .page 21 | padding-left 0 22 | .theme-container 23 | &.sidebar-open 24 | .sidebar 25 | transform translateX(0) 26 | &.no-navbar 27 | .sidebar 28 | padding-top: 0 29 | 30 | // narrow mobile 31 | @media (max-width: $MQMobileNarrow) 32 | h1 33 | font-size 1.9rem 34 | .content 35 | div[class*="language-"] 36 | margin 0.85rem -1.5rem 37 | border-radius 0 38 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/Ads.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | {{ads.title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 49 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/global-components/Badge.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 45 | -------------------------------------------------------------------------------- /docs/tips/typeInstantiation.md: -------------------------------------------------------------------------------- 1 | # 泛型的实例化类型 2 | 3 | 假如你有一个具有泛型参数的类型,如一个类 `Foo`: 4 | 5 | ```ts 6 | class Foo { 7 | foo: T; 8 | } 9 | ``` 10 | 11 | 你想为一个特定的类型创建单独的版本,可以通过将它拷贝到一个新变量里,并且用具体类型代替泛型的类型注解的方式来实现。例如,如果你想有一个类:`Foo`: 12 | 13 | ```ts 14 | class Foo { 15 | foo: T; 16 | } 17 | 18 | const FooNumber = Foo as { new (): Foo }; // ref 1 19 | ``` 20 | 21 | 在 `ref 1` 中,你说 `FooNumber` 与 `Foo` 相同,但是,只是将其看作使用 `new` 运算符调用时的一个 `Foo` 实例。 22 | 23 | ## 继承 24 | 25 | 类型断言模式是不安全的,因为编译器相信你在做正确的事情。在其他语言中用于类的常见模式是使用继承: 26 | 27 | ```ts 28 | class FooNumber extends Foo {} 29 | ``` 30 | 31 | ::: warning 32 | 这里需要注意的一点,如果你在基类上使用修饰器,继承类可能没有与基类相同的行为(它不再被修饰器包裹)。 33 | ::: 34 | 35 | 当然,如果你不需要一个单独的类,你仍然写出一个有效的强制/断言模式,因此在开始时,我们便展示出了普通的断言模式: 36 | 37 | ```ts 38 | function id(x: T) { 39 | return x; 40 | } 41 | 42 | const idNum = id as { (x: number): number }; 43 | ``` 44 | 45 | > 灵感来源于:[stackoverflow question](https://stackoverflow.com/questions/34859911/instantiated-polymorphic-function-as-argument-in-typescript/34864705#34864705) 46 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/SidebarButton.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 28 | -------------------------------------------------------------------------------- /docs/typings/types.md: -------------------------------------------------------------------------------- 1 | # @types 2 | 3 | 毫无疑问,[DefinitelyTyped](https://github.com/borisyankov/DefinitelyTyped) 是 TypeScript 最大的优势之一,社区已经记录了 90% 的顶级 JavaScript 库。 4 | 5 | 这意味着,你可以非常高效地使用这些库,而无须在单独的窗口打开相应文档以确保输入的正确性。 6 | 7 | ## 使用 `@types` 8 | 9 | 你可以通过 `npm` 来安装使用 `@types`,例如为 `jquery` 添加声明文件: 10 | 11 | ```shell 12 | npm install @types/jquery --save-dev 13 | ``` 14 | 15 | `@types` 支持全局和模块类型定义。 16 | 17 | ### 全局 `@types` 18 | 19 | 默认情况下,TypeScript 会自动包含支持全局使用的任何声明定义。例如,对于 jquery,你应该能够在项目中开始全局使用 `$`。 20 | 21 | ### 模块 `@types` 22 | 23 | 安装完之后,不需要特别的配置,你就可以像使用模块一样使用它: 24 | 25 | ```ts 26 | import * as $ from 'jquery'; 27 | 28 | // 现在你可以此模块中任意使用$了 :) 29 | ``` 30 | 31 | ## 控制全局 32 | 33 | 可以看出,对于某些团队而言,拥有允许全局使用的定义是一个问题。因此,你可以通过配置 `tsconfig.json` 的 `compilerOptions.types` 选项,引入有意义的类型: 34 | 35 | ```ts 36 | { 37 | "compilerOptions": { 38 | "types" : [ 39 | "jquery" 40 | ] 41 | } 42 | } 43 | ``` 44 | 45 | 如上例所示,通过配置 `compilerOptions.types: [ "jquery" ]` 后,只允许使用 `jquery` 的 `@types` 包,即使这个人安装了另一个声明文件,比如 `npm install @types/node`,它的全局变量(例如 `process`)也不会泄漏到你的代码中,直到你将它们添加到 tsconfig.json 类型选项。 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 三毛 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/NavLink.vue: -------------------------------------------------------------------------------- 1 | 2 | {{ item.text }} 8 | 15 | {{ item.text }} 16 | 17 | 18 | 19 | 20 | 50 | -------------------------------------------------------------------------------- /docs/faqs/modules.md: -------------------------------------------------------------------------------- 1 | # 模块 2 | 3 | ## 为什么我导入的模块在编译后被删除了? 4 | 5 | > 我写了一些这样的代码 6 | 7 | ```typescript 8 | import someModule = require('./myMod'); 9 | 10 | let x: someModule.SomeType = /* something */; 11 | ``` 12 | 13 | > 有这样的输出 14 | 15 | ```typescript 16 | // Expected to see "var someModule = require('./myMod');" here! 17 | 18 | var x = /* something */; 19 | ``` 20 | 21 | `TypeScript` 假定导入的模块没有副作用,所以它移除了不用于任何表达式的模块导入。 22 | 23 | 使用 `import "mod"` 语法来强制加载模块 24 | 25 | ```typescript 26 | import './myMod'; // For side effects 27 | ``` 28 | 29 | 你也可以简单调用模块,这是最常见的解决办法。 30 | 31 | ```typescript 32 | import someModule = require('./myMod'); 33 | someModule; // Used for side effects 34 | ``` 35 | 36 | ## 为什么不跨模块文件合并命名空间? 37 | 38 | TODO:本小节内容请查看:[https://stackoverflow.com/questions/30357634/how-do-i-use-namespaces-with-typescript-external-modules](https://stackoverflow.com/questions/30357634/how-do-i-use-namespaces-with-typescript-external-modules) 或者 [https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-5.html#namespace-keyword](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-5.html#namespace-keyword) 39 | -------------------------------------------------------------------------------- /docs/project/declarationspaces.md: -------------------------------------------------------------------------------- 1 | # 声明空间 2 | 3 | 在 TypeScript 里存在两种声明空间:类型声明空间与变量声明空间。下文将分别讨论这两个概念。 4 | 5 | ## 类型声明空间 6 | 7 | 类型声明空间包含用来当做类型注解的内容,例如下面的类型声明: 8 | 9 | ```ts 10 | class Foo {} 11 | interface Bar {} 12 | type Bas = {}; 13 | ``` 14 | 15 | 你可以将 `Foo`, `Bar`, `Bas` 作为类型注解使用,示例如下: 16 | 17 | ```ts 18 | let foo: Foo; 19 | let bar: Bar; 20 | let bas: Bas; 21 | ``` 22 | 23 | 注意,尽管你定义了 `interface Bar`,却并不能够把它作为一个变量来使用,因为它没有定义在变量声明空间中。 24 | 25 | ```ts 26 | interface Bar {} 27 | const bar = Bar; // Error: "cannot find name 'Bar'" 28 | ``` 29 | 30 | 出现错误提示: `cannot find name 'Bar'` 的原因是名称 `Bar` 并未定义在变量声明空间。这将带领我们进入下一个主题 -- 变量声明空间。 31 | 32 | ## 变量声明空间 33 | 34 | 变量声明空间包含可用作变量的内容,在上文中 `Class Foo` 提供了一个类型 `Foo` 到类型声明空间,此外它同样提供了一个变量 `Foo` 到变量声明空间,如下所示: 35 | 36 | ```ts 37 | class Foo {} 38 | const someVar = Foo; 39 | const someOtherVar = 123; 40 | ``` 41 | 42 | 这很棒,尤其是当你想把一个类来当做变量传递时。 43 | 44 | ::: warning 45 | 我们并不能把一些如 `interface` 定义的内容当作变量使用。 46 | ::: 47 | 48 | 与此相似,一些用 `var` 声明的变量,也只能在变量声明空间使用,不能用作类型注解。 49 | 50 | ```js 51 | const foo = 123; 52 | let bar: foo; // ERROR: "cannot find name 'foo'" 53 | ``` 54 | 55 | 提示 `ERROR: "cannot find name 'foo'"` 原因是,名称 foo 没有定义在类型声明空间里。 56 | -------------------------------------------------------------------------------- /docs/tips/singletonPatern.md: -------------------------------------------------------------------------------- 1 | # 单例模式 2 | 3 | 传统的单例模式可以用来解决所有代码必须写到 `class` 中的问题: 4 | 5 | ```ts 6 | class Singleton { 7 | private static instance: Singleton; 8 | private constructor() { 9 | // .. 10 | } 11 | 12 | public static getInstance() { 13 | if (!Singleton.instance) { 14 | Singleton.instance = new Singleton(); 15 | } 16 | 17 | return Singleton.instance; 18 | } 19 | 20 | someMethod() {} 21 | } 22 | 23 | let someThing = new Singleton(); // Error: constructor of 'singleton' is private 24 | 25 | let instacne = Singleton.getInstance(); // do some thing with the instance 26 | ``` 27 | 28 | 然而,如果你不想延迟初始化,你可以使用 `namespace` 替代: 29 | 30 | ```ts 31 | namespace Singleton { 32 | // .. 其他初始化的代码 33 | 34 | export function someMethod() {} 35 | } 36 | 37 | // 使用 38 | Singleton.someMethod(); 39 | ``` 40 | 41 | ::: warning 42 | 单例只是[全局](https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons/142450#142450)的一个别称。 43 | ::: 44 | 45 | 对大部分使用者来说,`namespace` 可以用模块来替代。 46 | 47 | ```ts 48 | // someFile.ts 49 | // ... any one time initialization goes here ... 50 | export function someMethod() {} 51 | 52 | // Usage 53 | import { someMethod } from './someFile'; 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/tips/classAreUseful.md: -------------------------------------------------------------------------------- 1 | # 类是有用的 2 | 3 | 以下结构在应用中很常见: 4 | 5 | ```ts 6 | function foo() { 7 | let someProperty; 8 | 9 | // 一些其他的初始化代码 10 | 11 | function someMethod() { 12 | // 用 someProperty 做一些事情 13 | // 可能有其他属性 14 | } 15 | 16 | // 可能有其他的方法 17 | return { 18 | someMethod 19 | // 可能有其他方法 20 | }; 21 | } 22 | ``` 23 | 24 | 它被称为模块模式(利用 JavaScript 的闭包)。 25 | 26 | 如果你使用[文件模块](../project/modules.md#文件模块)(你确实应该将全局变量视为错误),文件中的代码与示例一样,都不是全局变量。 27 | 28 | 然而,开发者有时会写以下类似代码: 29 | 30 | ```ts 31 | let someProperty; 32 | 33 | function foo() { 34 | // 一些初始化代码 35 | } 36 | 37 | foo(); 38 | someProperty = 123; // 其他初始化代码 39 | 40 | // 一些其它未导出 41 | 42 | // later 43 | export function someMethod() {} 44 | ``` 45 | 46 | 尽管我并不是一个特别喜欢使用**继承**的人,但是我确实发现让开发者使用类,可以在一定程度上更好的组织他们的代码,例如: 47 | 48 | ```ts 49 | class Foo { 50 | public someProperty; 51 | 52 | constructor() { 53 | // 一些初始化内容 54 | } 55 | 56 | public someMethod() { 57 | // ..code 58 | } 59 | 60 | public someUtility() { 61 | // .. code 62 | } 63 | } 64 | 65 | export = new Foo(); 66 | ``` 67 | 68 | 这并不仅仅有利于开发者,在创建基于类的更出色可视化工具中,它更常见。并且,这有利于项目的理解和维护。 69 | 70 | ::: tip 71 | 在浅层次的结构中,如果它们能够提供明显的重复使用和减少模版的好处,那么在这个观点里,我并没有错误。 72 | ::: 73 | -------------------------------------------------------------------------------- /docs/.vuepress/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TypeScriptBook", 3 | "short_name": "TypeScriptBook", 4 | "icons": [ 5 | { 6 | "src": "/icons/android-chrome-36x36.png", 7 | "sizes": "36x36", 8 | "type": "image/png", 9 | "density": 0.75 10 | }, 11 | { 12 | "src": "/icons/android-chrome-48x48.png", 13 | "sizes": "48x48", 14 | "type": "image/png", 15 | "density": 1 16 | }, 17 | { 18 | "src": "/icons/android-chrome-72x72.png", 19 | "sizes": "72x72", 20 | "type": "image/png", 21 | "density": 1.5 22 | }, 23 | { 24 | "src": "/icons/android-chrome-96x96.png", 25 | "sizes": "96x96", 26 | "type": "image/png", 27 | "density": 2 28 | }, 29 | { 30 | "src": "/icons/android-chrome-144x144.png", 31 | "sizes": "144x144", 32 | "type": "image/png", 33 | "density": 3 34 | }, 35 | { 36 | "src": "/icons/android-chrome-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png", 39 | "density": 4 40 | } 41 | ], 42 | "start_url": "/typescript-book-chinese/", 43 | "display": "standalone", 44 | "background_color": "#fff", 45 | "theme_color": "#3eaf7c" 46 | } 47 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/custom-blocks.styl: -------------------------------------------------------------------------------- 1 | .custom-block 2 | .custom-block-title 3 | font-weight 600 4 | margin-bottom -0.4rem 5 | &.tip, &.warning, &.danger 6 | padding .1rem 1.5rem 7 | border-left-width .5rem 8 | border-left-style solid 9 | margin 1rem 0 10 | &.tip 11 | background-color #f3f5f7 12 | border-color #42b983 13 | &.warning 14 | background-color rgba(255,229,100,.3) 15 | border-color darken(#ffe564, 35%) 16 | color darken(#ffe564, 70%) 17 | .custom-block-title 18 | color darken(#ffe564, 50%) 19 | a 20 | color $textColor 21 | &.danger 22 | background-color #ffe6e6 23 | border-color darken(red, 20%) 24 | color darken(red, 70%) 25 | .custom-block-title 26 | color darken(red, 40%) 27 | a 28 | color $textColor 29 | 30 | pre.vue-container 31 | border-left-width: .5rem; 32 | border-left-style: solid; 33 | border-color: #42b983; 34 | border-radius: 0px; 35 | & > code 36 | font-size: 14px !important; 37 | & > p 38 | margin: -5px 0 -20px 0; 39 | code 40 | background-color: #42b983 !important; 41 | padding: 3px 5px; 42 | border-radius: 3px; 43 | color #000 44 | em 45 | color #808080 46 | font-weight light 47 | 48 | -------------------------------------------------------------------------------- /docs/faqs/thing-that-dont-work.md: -------------------------------------------------------------------------------- 1 | # 一些不能按预期工作的代码 2 | 3 | ## 你应该像这样发出一些类,以便于他们拥有真正的私有成员 4 | 5 | > 如果我写下一些以下代码 6 | 7 | ```ts 8 | class Foo { 9 | private x = 0; 10 | increment(): number { 11 | this.x++; 12 | return x; 13 | } 14 | } 15 | ``` 16 | 17 | > 你应该发出这样的代码,以便 `x` 是真正的私有成员: 18 | 19 | ```ts 20 | var Foo = (function() { 21 | var x = 0; 22 | 23 | function Foo() {} 24 | Foo.prototype.increment = function() { 25 | x++; 26 | return x; 27 | }; 28 | return Foo; 29 | })(); 30 | ``` 31 | 32 | 这些代码不会工作,它创建了一个所有类共享的单个私有字段: 33 | 34 | ```ts 35 | var a = new Foo(); 36 | a.increment(); // Prints 1 37 | a.increment(); // Prints 2 38 | var b = new Foo(); // Should not affect a 39 | a.increment(); // Prints 1 40 | ``` 41 | 42 | ## 你应该发出这样的类,这样它们就不会在回调函数中丢失 `this` 43 | 44 | > 如果我写下这样的代码 45 | 46 | ```ts 47 | class MyClass { 48 | method() {} 49 | } 50 | ``` 51 | 52 | > 你应该发出这样的代码,以便我不会在回调函数中丢失 `this` 53 | 54 | ```ts 55 | var MyClass = (function() { 56 | function MyClass() { 57 | this.method = function() {}; 58 | } 59 | return MyClass; 60 | })(); 61 | ``` 62 | 63 | 这里有两个问题: 64 | 65 | 首先,建议改变的行为与 ECMAScript 规范不一致。在这方面没有任何异议 -- TypeScript 必须与 JavaScript 具有相同的运行时行为。 66 | 67 | 其次,这个运行时类的特点非常令人惊讶。它为每个实例的每个方法创建一个闭包,而不是为每个方法创建一个闭包,这在初始化时,内存、以及垃圾回收上的性能都非常糟糕。 68 | -------------------------------------------------------------------------------- /docs/tips/buildToggles.md: -------------------------------------------------------------------------------- 1 | # 构建切换 2 | 3 | 根据 JavaScript 项目的运行环境进行切换环境变量是很常见的,通过 webpack 可以很轻松地做到这一点,因为它支持基于环境变量的死代码排除。 4 | 5 | 在你的 `package.json script` 里,添加不同的编译目标: 6 | 7 | ```json 8 | "build:test": "webpack -p --config ./src/webpack.config.js", 9 | "build:prod": "webpack -p --define process.env.NODE_ENV='\"production\"' --config ./src/webpack.config.js" 10 | ``` 11 | 12 | 当然,假设你已经安装了 webpack `npm install webpack`,现在,你可以运行 `npm run build:test` 了。 13 | 14 | 使用环境变量也超级简单: 15 | 16 | ```ts 17 | /** 18 | * This interface makes sure we don't miss adding a property to both `prod` and `test` 19 | */ 20 | interface Config { 21 | someItem: string; 22 | } 23 | 24 | /** 25 | * We only export a single thing. The config. 26 | */ 27 | export let config: Config; 28 | 29 | /** 30 | * `process.env.NODE_ENV` definition is driven from webpack 31 | * 32 | * The whole `else` block will be removed in the emitted JavaScript 33 | * for a production build 34 | */ 35 | if (process.env.NODE_ENV === 'production') { 36 | config = { 37 | someItem: 'prod' 38 | }; 39 | console.log('Running in prod'); 40 | } else { 41 | config = { 42 | someItem: 'test' 43 | }; 44 | console.log('Running in test'); 45 | } 46 | ``` 47 | 48 | ::: tip 49 | 我们使用 `process.env.NODE_ENV` 仅仅是因为绝大多数 JavaScript 库中都使用此变量,例如:`React`。 50 | ::: 51 | -------------------------------------------------------------------------------- /docs/project/namespaces.md: -------------------------------------------------------------------------------- 1 | # 命名空间 2 | 3 | 在 JavaScript 使用命名空间时, 这有一个常用的、方便的语法: 4 | 5 | ```js 6 | (function(something) { 7 | something.foo = 123; 8 | })(something || (something = {})); 9 | ``` 10 | 11 | `something || (something = {})` 允许匿名函数 `function (something) {}` 向现有对象添加内容,或者创建一个新对象,然后向该对象添加内容。这意味着你可以拥有两个由某些边界拆成的块。 12 | 13 | ```js 14 | (function(something) { 15 | something.foo = 123; 16 | })(something || (something = {})); 17 | 18 | console.log(something); 19 | // { foo: 123 } 20 | 21 | (function(something) { 22 | something.bar = 456; 23 | })(something || (something = {})); 24 | 25 | console.log(something); // { foo: 123, bar: 456 } 26 | ``` 27 | 28 | 在确保创建的变量不会泄漏至全局命名空间时,这种方式在 JavaScript 中很常见。当基于文件模块使用时,你无须担心这点,但是该模式仍然适用于一组函数的逻辑分组。因此 TypeScript 提供了 `namespace` 关键字来描述这种分组,如下所示。 29 | 30 | ```ts 31 | namespace Utility { 32 | export function log(msg) { 33 | console.log(msg); 34 | } 35 | export function error(msg) { 36 | console.log(msg); 37 | } 38 | } 39 | 40 | // usage 41 | Utility.log('Call me'); 42 | Utility.error('maybe'); 43 | ``` 44 | 45 | `namespace` 关键字编译后的 JavaScript 代码,与我们早些时候看到的 JavaScript 代码一样。 46 | 47 | ```js 48 | (function (Utility) { 49 | // 添加属性至 Utility 50 | })(Utility || Utility = {}); 51 | ``` 52 | 53 | 值得注意的一点是,命名空间是支持嵌套的。因此,你可以做一些类似于在 `Utility` 命名空间下嵌套一个命名空间 `Messaging` 的事情。 54 | 55 | 对于大多数项目,我们建议使用外部模块和命名空间,来快速演示和移植旧的 JavaScript 代码。 56 | -------------------------------------------------------------------------------- /docs/tips/truthy.md: -------------------------------------------------------------------------------- 1 | # Truthy 2 | 3 | JavaScript 有一个 `truthy` 概念,即在某些场景下会被推断为 `true`,例如除 `0` 以外的任何数字: 4 | 5 | ```ts 6 | if (123) { 7 | // 将会被推断出 `true` 8 | console.log('Any number other than 0 is truthy'); 9 | } 10 | ``` 11 | 12 | 你可以用下表来做参考: 13 | 14 | | **Variable Type** | **When it is falsy** | **When it is truthy** | 15 | | ------------------------------------------------ | -------------------- | --------------------- | 16 | | boolean | false | true | 17 | | string | ' ' (empty string) | any other string | 18 | | number | 0 NaN | any other number | 19 | | null | always | never | 20 | | Any other Object including empty ones like {},[] | never | always | 21 | 22 | ## 明确的 23 | 24 | 通过操作符 `!!`,你可以很容易的将某些值转化为布尔类型的值,例如:`!!foo`,它使用了两次 `!`,第一个 `!` 用来将其(在这里是 `foo`)转换为布尔值,但是这一操作取得的是其取反后的值,第二个取反时,能得到真正的布尔值。 25 | 26 | 这在很多地方都可以看到: 27 | 28 | ```ts 29 | // Direct variables 30 | const hasName = !!name; 31 | 32 | // As members of objects 33 | const someObj = { 34 | hasName: !!name 35 | }; 36 | 37 | // ReactJS 38 | { 39 | !!someName && {someName}; 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/error/common.md: -------------------------------------------------------------------------------- 1 | # 常见的 Error 2 | 3 | 在此章节中,我们学习在实际应用中将会遇到的常见错误代码。 4 | 5 | ## TS2304 6 | 7 | 例子: 8 | 9 | > `Cannot find name ga`, `Cannot find name $`, `Cannot find module jquery` 10 | 11 | 你可能在使用第三方的库(如:google analytics),但是你并没有 `declare` 的声明。在没有声明它们之前,TypeScript 试图避免错误和使用变量。因此在使用一些额外的库时,你需要明确的声明使用的任何变量([如何修复它](../typings/ambient.md))。 12 | 13 | ## TS2307 14 | 15 | 例子: 16 | 17 | > `Cannot find module 'underscore'` 18 | 19 | 你可能把第三方的库作为模块([移步模块](../project/modules.md))来使用,并且没有一个与之对应的环境声明文件([更多声明文件信息](../typings/ambient.md))。 20 | 21 | ## TS1148 22 | 23 | 例子: 24 | 25 | > `Cannot compile modules unless the '--module' flag provided` 26 | 27 | 请查看[模块](../project/modules.md)章节 28 | 29 | ## 捕获不能有类型注解的简短变量 30 | 31 | 例子: 32 | 33 | ```ts 34 | try { 35 | something(); 36 | } catch (e) { 37 | // 捕获不能有类型注解的简短变量 38 | // ... 39 | } 40 | ``` 41 | 42 | TypeScript 正在保护你免受 JavaScript 代码的侵害,取而代之,使用类型保护: 43 | 44 | ```ts 45 | try { 46 | something(); 47 | } catch (e) { 48 | // 捕获不能有类型注解的简短变量 49 | if (e instanceof Error) { 50 | // do... 51 | } 52 | } 53 | ``` 54 | 55 | ## 接口 `ElementClass` 不能同时扩展类型别名 `Component` 和 `Component` 56 | 57 | 当在编译上下文中同时含有两个 `react.d.ts`(`@types/react/index.d.ts`)会发生这种情况。 58 | 59 | 修复: 60 | 61 | - 删除 `node_modules` 和任何 `package-lock`(或者 `yarn lock`),然后再一次 `npm install`; 62 | - 如果这不能工作,查找无效的模块(你所使用的所用用到了 `react.d.ts` 模块应该作为 `peerDependency` 而不是作为 `dependency` 使用)并且把这个报告给相关模块。 63 | -------------------------------------------------------------------------------- /docs/faqs/tsconfig-behavior.md: -------------------------------------------------------------------------------- 1 | # `tsconfig.json` 的行为 2 | 3 | ## 为什么把一个文件放入「exclude」选项中,它仍然会被编译器选中? 4 | 5 | `tsconfig.json` 将会把一个文件夹转换为「项目」,如果不指定任何 `exclude` 或者 `files`,则包含在 `tsconfig.json` 中的所有文件夹中的所有文件都会被包含在编译中。 6 | 7 | 如果你想忽略一些文件,使用 `exclude`。如果希望指定所有文件,而不是让编译器查找它们,请使用 `files`。 8 | 9 | 这些行为,`tsconfig.json` 将会自动确认。但是这有一个不同的问题,即是解析模块。模块解析:编译器将尝试去理解 `ns` 在模块语法中表示什么,即 `import * as ns from 'mod'`。为了理解它,编译器需要定义一个模块,它可能是包含你自己代码的 .ts 文件,或者是导入的一个 .d.ts 文件。如果一个文件被找到,则无论它是否在 `excludes` 中,它都将会被编译。 10 | 11 | 因此,如果你想从编译中排除一个文件,你需要排除所有具有 `import` 或者 `` 指令的文件。 12 | 13 | 使用 `tsc --listFiles` 来列出在编译时包含了哪些文件,`tsc --traceResolution` 来看看它们为什么会被包含在编译中。 14 | 15 | ## 我怎么指定一个 `include`? 16 | 17 | 现在无法在 `tsconfig.json` 的 `include` 选项外指定所需要包含的文件。你可以通过以下任意一种方式获得相同的结果:1 使用 `files` 列表,2 在目录中添加 `///` 指令。 18 | 19 | ## 当我使用 JavaScript 文件时,为什么我会得到 `error TS5055: Cannot write file 'xxx.js' because it would overwrite input file` 错误? 20 | 21 | 对于 TypeScript 文件来说,在默认情况下,编译器将在同一目录中生成与 JavaScript 相同文件名的文件。因为 TypeScript 文件与编译后的文件总是拥有不同的后缀,这么做是安全的。然而,如果你设置 `allowJs` 编译选项为 `true` 和没有设置任何的编译输出属性(`outFile` 和 `outDir`),编译器将会尝试使用相同的规则来编译文件,这将导致发出的 JavaScript 文件与源文件具有相同的文件名。为了避免意外覆盖源文件,编译器将会发出此警告,并跳过编写输出文件。 22 | 23 | 有多种方法可以解决此问题,但所有这些方法都涉及配置编译器选项,因此建议你在项目根目录中的 tsconfig.json 文件来启用此功能。如果你不想编译 JavaScript 文件,你只需要将 `allowJs` 选项设置为 `false`;如果你确实想要包含和编译这些 JavaScript 文件,你应该设置 `outDir` 或者 `outFile` 选项,定向到其他位置,这样他们就不会与源文件冲突。如果你仅仅是想包含这些 JavaScript 文件,但是不需要编译,设置 `noEmit` 选项为 `true` 可以跳过编译检查。 24 | -------------------------------------------------------------------------------- /docs/jsx/nonReactJSX.md: -------------------------------------------------------------------------------- 1 | # 非 React JSX 2 | 3 | TypeScript 让你能够以类型安全的方式,在 React 中使用 JSX 之外的其他东西。下面列出了一些可自定义的点,但请注意,这只适用于高级 UI 框架的作者。 4 | 5 | - 你可以使用 `"jsx":"preserve"` 选项来禁用 React 的样式触发。这意味着,JSX 将按原样被触发,然后你可以使用自定义转化器来转化 JSX 部分。 6 | - 使用 `JSX` 全局模块: 7 | - 你可以通过定制 `JSX.IntrinsicElements` 的接口成员来控制哪些 HTML 标签是可用的,以及如何对其进行类型检查; 8 | - 当你在组件中使用时: 9 | - 你可以通过自定义默认的 `interface ElementClass extends React.Component { }` 声明文件来控制哪个 `class` 必须由组件继承; 10 | - 你可以通过自定义 `declare module JSX { interface ElementAttributesProperty { props: {} } }` 声明文件来控制使用的哪个属性(property)来检查特性(attribute)(默认是 `props`)。 11 | 12 | ## jsxFactory 13 | 14 | 通过 `--jsxFactory ` 与 `--jsx react`,能让你不同于默认 `React` 的方式使用 JSX 工厂函数。 15 | 16 | 这个新的工厂函数名字习惯被称之为 `createElement` 函数。 17 | 18 | ### 例子 19 | 20 | ```jsx 21 | import { jsxFactory } from 'jsxFactory'; 22 | 23 | const div = Hello JSX!; 24 | ``` 25 | 26 | 使用编译: 27 | 28 | ```ts 29 | tsc --jsx react --reactNamespace jsxFactory --m commonJS 30 | ``` 31 | 32 | 编译结果: 33 | 34 | ```js 35 | 'use strict'; 36 | var jsxFactory_1 = require('jsxFactory'); 37 | var div = jsxFactory_1.jsxFactory.createElement('div', null, 'Hello JSX!'); 38 | ``` 39 | 40 | ### jsx 编译提示 41 | 42 | 你甚至可以使用`jsxPragma` 为每个文件指定不同的 `jsxFactory`: 43 | 44 | ```tsx 45 | /** @jsx jsxFactory */ 46 | import { jsxFactory } from 'jsxFactory'; 47 | 48 | var div = Hello JSX!; 49 | ``` 50 | 51 | 在 jsx 编译提示中,配合 `--jsx react` 命令,这个文件将会被触发使用工厂函数: 52 | 53 | ```js 54 | 'use strict'; 55 | var jsxFactory_1 = require('jsxFactory'); 56 | var div = jsxFactory_1.jsxFactory.createElement('div', null, 'Hello JSX!'); 57 | ``` 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-book-chinese", 3 | "version": "1.0.0", 4 | "description": "typescript-book-chinese", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "dev": "vuepress dev docs", 11 | "build": "vuepress build docs", 12 | "precommit": "lint-staged", 13 | "contributor:add": "all-contributors add", 14 | "contributor:generate": "all-contributors generate" 15 | }, 16 | "lint-staged": { 17 | "linters": { 18 | "*.{js,json,css,md}": [ 19 | "prettier --write", 20 | "git add" 21 | ] 22 | }, 23 | "ignore": [] 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/jkchao/typescript-book-chinese.git" 28 | }, 29 | "keywords": [ 30 | "typescript", 31 | "vue" 32 | ], 33 | "author": "jkchao", 34 | "license": "ISC", 35 | "bugs": { 36 | "url": "https://github.com/jkchao/typescript-book-chinese/issues" 37 | }, 38 | "homepage": "https://github.com/jkchao/typescript-book-chinese#readme", 39 | "devDependencies": { 40 | "@commitlint/cli": "^7.5.2", 41 | "@commitlint/config-angular": "^7.5.0", 42 | "@vuepress/plugin-back-to-top": "^1.0.0-alpha.44", 43 | "@vuepress/plugin-container": "^1.0.0-alpha.44", 44 | "@vuepress/plugin-google-analytics": "^1.0.0-alpha.44", 45 | "@vuepress/plugin-pwa": "^1.0.0-alpha.44", 46 | "all-contributors-cli": "^5.4.0", 47 | "eslint": "^5.6.1", 48 | "husky": "^1.3.1", 49 | "lint-staged": "^8.1.5", 50 | "prettier": "1.14.2", 51 | "vuepress": "^1.0.0-alpha.44" 52 | }, 53 | "dependencies": {} 54 | } 55 | -------------------------------------------------------------------------------- /docs/faqs/commandline-behavior.md: -------------------------------------------------------------------------------- 1 | # 命令行的行为 2 | 3 | ## 如何控制输出文件中的排序(-- out)? 4 | 5 | 输出文件的排序遵循预处理后输入文件的顺序。 6 | 7 | 编译器执行预处理,主要是为了解决所有的三斜线指令和模块导入。在这个过程中,额外的文件将会被将入到编译过程中。 8 | 9 | 这个过程开始于一个给定的根文件,这些是在命令行或者是 `tsconfig.json` 文件中 files 指定文件名,这些根文件按照指定的顺序进行预处理。在一个文件添加到这个列表之前,将处理所有的三斜线引用和模块导入语法,并包括它们的目标。三斜线引用和导入语法按照它们在文件中出现的顺序,以深度优先的方式解析。 10 | 11 | 请参考有关[三斜线指令](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)的更多信息,和[模块](https://www.typescriptlang.org/docs/handbook/module-resolution.html)导入语法的信息。 12 | 13 | ## `Exported variable [name] has or is using private name [name]` 是什么错误? 14 | 15 | 当你使用 `--declarartion` 编译选项的时候,可能会出现这个错误,因为编译器试图生成与你定义模块完全匹配的声明文件: 16 | 17 | 假设你有这样一段代码: 18 | 19 | ```ts 20 | /// MyFile.ts 21 | class Test { 22 | // ... other members .... 23 | constructor(public parent: Test) {} 24 | } 25 | 26 | export let t = new Test('some thing'); 27 | ``` 28 | 29 | 为了生成声明文件,编译器必须为 `t` 写一个类型: 30 | 31 | ```ts 32 | /// MyFile.d.ts, auto-generated 33 | export let t: ___fill in the blank___; 34 | ``` 35 | 36 | 成员 `t` 有类型 `Test`,但是类型 `Test` 并不是可见的,因为它没有导出,因此我们不能写 `t: Test`。 37 | 38 | 在这个非常简单的例子里,我们可以用一个对象字面量重写 `Test's` 的形状。但是对于绝大多数情况,这并不能正常工作。如代码里所写,Test 的形状是自引用的,不能重写为匿名函数。如果 `Test` 有任何私有或受保护的成员,这同样也不能正常工作。因此,与其让你通过编写一个真实的类来获得 65% 的成功而后开始抛出错误,我们仅仅是在一开始的时候就抛出错误(你以后会发现)并为你省去不必要的麻烦。 39 | 40 | 为了避免这些错误: 41 | 42 | - 导出相关类型中使用的声明 43 | - 当编写声明的时候,显示的为编译器指定类型注解 44 | 45 | ## 为什么添加 `--outDir` 属性后,当在添加一个新文件时,会把所有的输出删除 46 | 47 | `--outDir` 指定输出的「根」目录。编译器需要此属性,用来将资源映射输出到根目录。如果 `--rootDir` 没有被指定,编辑器将会自己计算出一个。它根据常见的路径计算,它是所有输入文件的最长公共前缀。显然,当在较短路径前缀中添加新文件时,`--rootDir` 将会被修改。 48 | 49 | 为了确保添加一个新文件时,输出不会被修改,你应该在命令行中或 `tsconfig.json` 指定一个 `--rootDir`。 50 | -------------------------------------------------------------------------------- /docs/tips/lazyObjectLiteralInitialization.md: -------------------------------------------------------------------------------- 1 | # 对象字面量的惰性初始化 2 | 3 | 在 JavaScript 中,像这样用字面量初始化对象的写法十分常见: 4 | 5 | ```ts 6 | let foo = {}; 7 | foo.bar = 123; 8 | foo.bas = 'Hello World'; 9 | ``` 10 | 11 | 但在 TypeScript 中,同样的写法就会报错: 12 | 13 | ```ts 14 | let foo = {}; 15 | foo.bar = 123; // Error: Property 'bar' does not exist on type '{}' 16 | foo.bas = 'Hello World'; // Error: Property 'bas' does not exist on type '{}' 17 | ``` 18 | 19 | 这是因为 TypeScript 在解析 `let foo = {}` 这段赋值语句时,会进行“类型推断”:它会认为等号左边 `foo` 的类型即为等号右边 `{}` 的类型。由于 `{}` 本没有任何属性,因此,像上面那样给 `foo` 添加属性时就会报错。 20 | 21 | ## 最好的解决方案 22 | 23 | 最*好*的解决方案就是在为变量赋值的同时,添加属性及其对应的值: 24 | 25 | ```ts 26 | let foo = { 27 | bar: 123, 28 | bas: 'Hello World' 29 | }; 30 | ``` 31 | 32 | 这种写法也比较容易通过其他人或工具的代码审核,对后期维护也是有利的。 33 | 34 | > 以下的快速解决方案采用*惰性*的思路,本质上是*在初始化变量时忘了添加属性*的做法。 35 | 36 | ## 快速解决方案 37 | 38 | 如果你的 JavaScript 项目很大,那么在迁移到 TypeScript 的时候,上面的做法可能会比较麻烦。此时,你可以利用 TypeScript 的“类型断言”机制让代码顺利通过编译: 39 | 40 | ```ts 41 | let foo = {} as any; 42 | foo.bar = 123; 43 | foo.bas = 'Hello World'; 44 | ``` 45 | 46 | ## 折中的解决方案 47 | 48 | 当然,总是用 `any` 肯定是不好的,因为这样做其实是在想办法绕开 TypeScript 的类型检查。那么,折中的方案就是创建 `interface`,这样的好处在于: 49 | 50 | - 方便撰写类型文档 51 | - TypeScript 会参与类型检查,确保类型安全 52 | 53 | 请看以下的示例: 54 | 55 | ```ts 56 | interface Foo { 57 | bar: number; 58 | bas: string; 59 | } 60 | 61 | let foo = {} as Foo; 62 | foo.bar = 123; 63 | foo.bas = 'Hello World'; 64 | ``` 65 | 66 | 使用 `interface` 可以确保类型安全,比如这种情况: 67 | 68 | ```ts 69 | interface Foo { 70 | bar: number; 71 | bas: string; 72 | } 73 | 74 | let foo = {} as Foo; 75 | foo.bar = 123; 76 | foo.bas = 'Hello World'; 77 | 78 | // 然后我们尝试这样做: 79 | foo.bar = 'Hello Stranger'; // 错误:你可能把 `bas` 写成了 `bar`,不能为数字类型的属性赋值字符串 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/tips/avoidExportDefault.md: -------------------------------------------------------------------------------- 1 | # `export default` 被认为是有害的 2 | 3 | 假如你有一个包含以下内容的 `foo.ts` 文件: 4 | 5 | ```ts 6 | class Foo {} 7 | 8 | export default Foo; 9 | ``` 10 | 11 | 你可能会使用 ES6 语法导入它(在 `bar.ts` 里): 12 | 13 | ```ts 14 | import Foo from './foo'; 15 | ``` 16 | 17 | 这存在一些可维护性的问题: 18 | 19 | - 如果你在 `foo.ts` 里重构 `Foo`,在 `bar.ts` 文件中,它将不会被重新命名; 20 | - 如果你最终需要从 `foo.ts` 文件中导出更多有用的信息(在你的很多文件中都存在这种情景),那么你必须兼顾导入语法。 21 | 22 | 由于这些原因,我推荐在导入时使用简单的 `export` 与解构的形式,如 `foo.ts`: 23 | 24 | ```ts 25 | export class Foo {} 26 | ``` 27 | 28 | 接着: 29 | 30 | ```ts 31 | import { Foo } from './Foo'; 32 | ``` 33 | 34 | 下面,我将会介绍更多的原因。 35 | 36 | ## 可发现性差 37 | 38 | 默认导出的可发现性非常差,你不能智能的辨别一个模块它是否有默认导出。 39 | 40 | 在使用默认导出时,你什么也没有得到(可能它有默认导出,可能它没有)。 41 | 42 | ```ts 43 | import /* here */ from 'something'; 44 | ``` 45 | 46 | 没有默认导出,你可以用以下方式获取智能提示: 47 | 48 | ```ts 49 | import /* here */ 'something'; 50 | ``` 51 | 52 | ## 自动完成 53 | 54 | 不管你是否了解导出,你都可以在 `import { /* here */ } from './foo'` 的 `here` 位置,来了解导出模块的信息。 55 | 56 | ## CommonJS 互用 57 | 58 | 对于必须使用 `const { default } = require('module/foo')` 而不是 `const { Foo } = require('module/foo')` 的 CommonJS 的用户来说,这会是一个糟糕的体验。当你导入一个模块时,你很可能想重命名 `default` 作为导入的名字。 59 | 60 | ## 防止拼写错误 61 | 62 | 当你在开发时使用 `import Foo from './foo'` 时,并不会得到有关于拼写的任何错误,其他人可能会这么写 `import foo from './foo'`; 63 | 64 | ## 再次导出 65 | 66 | 再次导出是没必要的,但是在 `npm` 包的根文件 `index` 却是很常见。如:`import Foo from './foo';export { Foo }`(默认导出)VS `export * from './foo'` (命名导出)。 67 | 68 | ## 动态导入 69 | 70 | 在动态的 `import` 中,默认导出会以 `default` 的名字暴露自己,如: 71 | 72 | ```ts 73 | const HighChart = await import('https://code.highcharts.com/js/es-modules/masters/highcharts.src.js'); 74 | HighChart.default.chart('container', { ... }); // Notice `.default` 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/tips/barrel.md: -------------------------------------------------------------------------------- 1 | # Barrel 2 | 3 | Barrel 就像是一个容器,它的作用是把分散在多个模块的导出合并到一个模块里导出。一般来说,barrel 本身就是一个包含模块的文件,这个模块做的就是重新导出其他(多个)模块导出的东西。 4 | 5 | 想象一下,在一个库中,具有如下结构的类。 6 | 7 | ```ts 8 | // demo/foo.ts 9 | export class Foo {} 10 | 11 | // demo/bar.ts 12 | export class Bar {} 13 | 14 | // demo/baz.ts 15 | export class Baz {} 16 | ``` 17 | 18 | 如果不用 barrel,那么用户在引入时就需要三条 `import` 语句: 19 | 20 | ```ts 21 | import { Foo } from '../demo/foo'; 22 | import { Bar } from '../demo/bar'; 23 | import { Baz } from '../demo/baz'; 24 | ``` 25 | 26 | 但如果我们在同级添加 barrel 文件 `demo/index.ts`,然后这样定义它: 27 | 28 | ```ts 29 | // demo/index.ts 30 | export * from './foo'; // 重新导出 foo 导出的东西 31 | export * from './bar'; // 重新导出 bar 导出的东西 32 | export * from './baz'; // 重新导出 baz 导出的东西 33 | ``` 34 | 35 | 现在,用户就可以直接用一条 `import` 语句从 barrel file 导入所有东西: 36 | 37 | ```ts 38 | import { Foo, Bar, Baz } from '../demo'; // ../demo,会自动解析成 ../demo/index.ts 39 | ``` 40 | 41 | ## 命名导出 42 | 43 | 除了使用通配符 `*` 导出模块中的所有东西,我们也可以选择要导出什么以及如何导出。试想一个存在多个函数的 `baz.ts`: 44 | 45 | ```ts 46 | // demo/foo.ts 47 | export class Foo {} 48 | 49 | // demo/bar.ts 50 | export class Bar {} 51 | 52 | // demo/baz.ts 53 | export function getBaz() {} 54 | export function setBaz() {} 55 | ``` 56 | 57 | 如果不想在 `demo` 模块上直接提供 `getBaz` 和 `setBaz` 接口,你可以把它们挂载到一个变量下。你需要做的只是在 barrel file 里导入全部并命名,然后导出命名后的名称即可。 58 | 59 | ```ts 60 | // demo/index.ts 61 | export * from './foo'; // 重新导出 foo 导出的东西 62 | export * from './bar'; // 重新导出 bar 导出的东西 63 | 64 | import * as baz from './baz'; // 导入 baz 中所有的东西,并命名为 baz 65 | export { baz }; // 导出命名后的名称 66 | ``` 67 | 68 | 现在,用户需要这样调用: 69 | 70 | ```ts 71 | import { Foo, Bar, baz } from '../demo'; // ../demo,会自动解析成 ../demo/index.ts 72 | 73 | // 使用 74 | baz.getBaz(); 75 | baz.setBaz(); 76 | // …… 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 24 | 25 | 74 | -------------------------------------------------------------------------------- /docs/tips/bind.md: -------------------------------------------------------------------------------- 1 | # Bind 是有害的 2 | 3 | ::: tip 4 | 译者注:在这个 [PR](https://github.com/Microsoft/TypeScript/pull/27028?from=timeline&isappinstalled=0) 下,已经解决 `bind`、`call`、`apply` 类型正确推导的问题,预计在 3.2 版本中发布。 5 | ::: 6 | 7 | 这是在 `lib.d.ts` 中 `bind` 的定义: 8 | 9 | ```ts 10 | bind(thisArg: any, ...argArray: any[]): any 11 | ``` 12 | 13 | 你可以看到他的返回值是 `any`,这意味着在函数上调用 `bind` 会导致你在原始函数调用签名上将会完全失去类型的安全检查。 14 | 15 | 如下所示: 16 | 17 | ```ts 18 | function twoParams(a: number, b: number) { 19 | return a + b; 20 | } 21 | 22 | let curryOne = twoParams.bind(null, 123); 23 | curryOne(456); // ok 24 | curryOne('456'); // ok 25 | ``` 26 | 27 | 一个更好的方式的是使用类型注解的箭头函数: 28 | 29 | ```ts 30 | function twoParams(a: number, b: number) { 31 | return a + b; 32 | } 33 | 34 | let curryOne = (x: number) => twoParams(123, x); 35 | curryOne(456); // ok 36 | curryOne('456'); // Error 37 | ``` 38 | 39 | 如果你想用一个柯里化的函数,你可以看看[此章节](./curry.md): 40 | 41 | ## 类成员 42 | 43 | 另一个常见用途是在传递类函数时使用 `bind` 来确保 `this` 的正确值,不要这么做。 44 | 45 | 在接下来的示例中,如果你使用了 `bind`,你将会失去函数参数的类型安全: 46 | 47 | ```ts 48 | class Adder { 49 | constructor(public a: string) {} 50 | 51 | add(b: string): string { 52 | return this.a + b; 53 | } 54 | } 55 | 56 | function useAdd(add: (x: number) => number) { 57 | return add(456); 58 | } 59 | 60 | let adder = new Adder('mary had a little 🐑'); 61 | useAdd(adder.add.bind(adder)); // 没有编译的错误 62 | useAdd(x => adder.add(x)); // Error: number 不能分配给 string 63 | ``` 64 | 65 | 如果你想传递一个类成员的函数,使用箭头函数。例如: 66 | 67 | ```ts 68 | class Adder { 69 | constructor(public a: string) {} 70 | 71 | // 此时,这个函数可以安全传递 72 | add = (b: string): string => { 73 | return this.a + b; 74 | }; 75 | } 76 | ``` 77 | 78 | 另一种方法是手动指定要绑定的变量的类型: 79 | 80 | ```ts 81 | const add: typeof adder.add = adder.add.bind(adder); 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/typings/neverType.md: -------------------------------------------------------------------------------- 1 | # Never 2 | 3 | ::: tip 4 | [一个关于 never 的介绍视频](https://egghead.io/lessons/typescript-use-the-never-type-to-avoid-code-with-dead-ends-using-typescript) 5 | ::: 6 | 7 | 程序语言的设计确实应该存在一个底部类型的概念,当你在分析代码流的时候,这会是一个理所当然存在的类型。TypeScript 就是这样一种分析代码流的语言(:sunglasses:),因此它需要一个可靠的,代表永远不会发生的类型。 8 | 9 | `never` 类型是 TypeScript 中的底层类型。它自然被分配的一些例子: 10 | 11 | - 一个从来不会有返回值的函数(如:如果函数内含有 `while(true) {}`); 12 | - 一个总是会抛出错误的函数(如:`function foo() { throw new Error('Not Implemented') }`,`foo` 的返回类型是 `never`); 13 | 14 | 你也可以将它用做类型注解: 15 | 16 | ```ts 17 | let foo: never; // ok 18 | ``` 19 | 20 | 但是,`never` 类型仅能被赋值给另外一个 `never`: 21 | 22 | ```ts 23 | let foo: never = 123; // Error: number 类型不能赋值给 never 类型 24 | 25 | // ok, 作为函数返回类型的 never 26 | let bar: never = (() => { 27 | throw new Error('Throw my hands in the air like I just dont care'); 28 | })(); 29 | ``` 30 | 31 | 很棒,现在让我们看看它的关键用例。 32 | 33 | ## 用例:详细的检查 34 | 35 | ```ts 36 | function foo(x: string | number): boolean { 37 | if (typeof x === 'string') { 38 | return true; 39 | } else if (typeof x === 'number') { 40 | return false; 41 | } 42 | 43 | // 如果不是一个 never 类型,这会报错: 44 | // - 不是所有条件都有返回值 (严格模式下) 45 | // - 或者检查到无法访问的代码 46 | // 但是由于 TypeScript 理解 `fail` 函数返回为 `never` 类型 47 | // 它可以让你调用它,因为你可能会在运行时用它来做安全或者详细的检查。 48 | return fail('Unexhaustive'); 49 | } 50 | 51 | function fail(message: string): never { 52 | throw new Error(message); 53 | } 54 | ``` 55 | 56 | `never` 仅能被赋值给另外一个 `never` 类型,因此你可以用它来进行编译时的全面的检查,我们将会在[辨析联合类型](./discrominatedUnion.md)中讲解它。 57 | 58 | ## 与 `void` 的差异 59 | 60 | 一旦有人告诉你,`never` 表示一个从来不会优雅的返回的函数时,你可能马上就会想到与此类似的 `void`,然而实际上,`void` 表示没有任何类型,`never` 表示永远不存在的值的类型。 61 | 62 | 当一个函数返回空值时,它的返回值为 void 类型,但是,当一个函数永不返回时(或者总是抛出错误),它的返回值为 never 类型。void 类型可以被赋值(在 strictNullChecking 为 false 时),但是除了 never 本身以外,其他任何类型不能赋值给 never。 63 | -------------------------------------------------------------------------------- /docs/typings/callable.md: -------------------------------------------------------------------------------- 1 | # 可调用的 2 | 3 | 你可以使用类型别名或者接口来表示一个可被调用的类型注解: 4 | 5 | ```ts 6 | interface ReturnString { 7 | (): string; 8 | } 9 | ``` 10 | 11 | 它可以表示一个返回值为 `string` 的函数: 12 | 13 | ```ts 14 | declare const foo: ReturnString; 15 | 16 | const bar = foo(); // bar 被推断为一个字符串。 17 | ``` 18 | 19 | ## 一个实际的例子 20 | 21 | 当然,像这样一个可被调用的类型注解,你也可以根据实际来传递任何参数、可选参数以及 rest 参数,这有一个稍微复杂的例子: 22 | 23 | ```ts 24 | interface Complex { 25 | (foo: string, bar?: number, ...others: boolean[]): number; 26 | } 27 | ``` 28 | 29 | 一个接口可提供多种调用签名,用以特殊的函数重载: 30 | 31 | ```ts 32 | interface Overloaded { 33 | (foo: string): string; 34 | (foo: number): number; 35 | } 36 | 37 | // 实现接口的一个例子: 38 | function stringOrNumber(foo: number): number; 39 | function stringOrNumber(foo: string): string; 40 | function stringOrNumber(foo: any): any { 41 | if (typeof foo === 'number') { 42 | return foo * foo; 43 | } else if (typeof foo === 'string') { 44 | return `hello ${foo}`; 45 | } 46 | } 47 | 48 | const overloaded: Overloaded = stringOrNumber; 49 | 50 | // 使用 51 | const str = overloaded(''); // str 被推断为 'string' 52 | const num = overloaded(123); // num 被推断为 'number' 53 | ``` 54 | 55 | 这也可以用于内联注解中: 56 | 57 | ```ts 58 | let overloaded: { 59 | (foo: string): string; 60 | (foo: number): number; 61 | }; 62 | ``` 63 | 64 | ## 箭头函数 65 | 66 | 为了使指定可调用的类型签名更容易,TypeScript 也允许你使用简单的箭头函数类型注解。例如,在一个以 number 类型为参数,以 string 类型为返回值的函数中,你可以这么写: 67 | 68 | ```ts 69 | const simple: (foo: number) => string = foo => foo.toString(); 70 | ``` 71 | 72 | ::: tip 73 | 它仅仅只能作为简单的箭头函数,你无法使用重载。如果想使用重载,你必须使用完整的 `{ (someArgs): someReturn }` 的语法 74 | ::: 75 | 76 | ## 可实例化 77 | 78 | 可实例化仅仅是可调用的一种特殊情况,它使用 `new` 作为前缀。它意味着你需要使用 `new` 关键字去调用它: 79 | 80 | ```ts 81 | interface CallMeWithNewToGetString { 82 | new (): string; 83 | } 84 | 85 | // 使用 86 | declare const Foo: CallMeWithNewToGetString; 87 | const bar = new Foo(); // bar 被推断为 string 类型 88 | ``` 89 | -------------------------------------------------------------------------------- /docs/faqs/common-bug-not-bugs.md: -------------------------------------------------------------------------------- 1 | # 一些常见的「bug」并不是 bug 2 | 3 | > 注:此章节的所有文章都来自 [TypeScript FAQs](https://github.com/Microsoft/TypeScript/wiki/FAQ) 4 | 5 | 这有一些看起来像 Bug 的行为,但实际上,它们并不是。 6 | 7 | - 两个空的类,可以彼此代替 8 | 9 | - 查看相关的 [FAQ](./class.html#为什么这些空类的行为很奇怪?) 10 | 11 | - 我可以在一个返回值为 void 的函数中使用一个返回值不为 `void` 的函数 12 | 13 | - 查看相关的 [FAQ](./type-system-behavior.html#为什么一个返回值不是-void-的函数,可以赋值给一个返回值为-void-的函数?) 14 | - 查看此 [ISSUES](https://github.com/Microsoft/TypeScript/issues/4544) 15 | 16 | - 我可以使用一个更短的参数列表,而不是一个期望的长参数列表 17 | 18 | - 查看相关 [FAQ](./type-system-behavior.html#为什么有更少参数的函数能够赋值给更多参数的函数?) 19 | - 相关 ISSUES:[#370](https://github.com/Microsoft/TypeScript/issues/370)、[#9300](https://github.com/Microsoft/TypeScript/issues/9300)、[#9765](https://github.com/Microsoft/TypeScript/issues/9765)、[#9825](https://github.com/Microsoft/TypeScript/issues/9825)、[#13043](https://github.com/Microsoft/TypeScript/issues/13043)、[#16871](https://github.com/Microsoft/TypeScript/issues/16871)、[#13529](https://github.com/Microsoft/TypeScript/issues/13529)、[#13977](https://github.com/Microsoft/TypeScript/issues/13977)、[#17868](https://github.com/Microsoft/TypeScript/issues/17868)、[#20274](https://github.com/Microsoft/TypeScript/issues/20274)、[#20541](https://github.com/Microsoft/TypeScript/issues/20541)、[#21868](https://github.com/Microsoft/TypeScript/issues/21868)。 20 | 21 | - 类的 `private` 成员,在运行时实际上是可见的 22 | 23 | - 查看相关 FAQ,以及一些修复的建议 24 | - 相关 ISSUES:[#564](https://github.com/Microsoft/TypeScript/issues/564)、[#1537](https://github.com/Microsoft/TypeScript/issues/1537)、[#2967](https://github.com/Microsoft/TypeScript/issues/2967)、[#3151](https://github.com/Microsoft/TypeScript/issues/3151)、[#6748](https://github.com/Microsoft/TypeScript/issues/6748)、[#8847](https://github.com/Microsoft/TypeScript/issues/8847)、[#9733](https://github.com/Microsoft/TypeScript/issues/9733)、[#11033](https://github.com/Microsoft/TypeScript/issues/11033)。 25 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 深入理解 TypeScript 2 | 3 | 此书是 [《TypeScript Deep Dive》](https://github.com/basarat/typescript-book/) 的中文翻译版,感谢作者 [Basarat](https://github.com/basarat) 的付出。 4 | 5 | 如果你喜欢纸质书籍,可以通过[京东](https://item.jd.com/12755624.html)或者[当当](http://product.m.dangdang.com/28487648.html?t=1574581821),来购买此书。 6 | 7 | 你可以通过订阅该[公众号](https://cdn.jkchao.cn/nuxt/img/14958af.jpg),来获取更多有趣的内容。 8 | 9 | ## Why 10 | 11 | 12 | 13 | > 数据来源:[npm 包下载量](https://npm-stat.com/charts.html?package=typescript&from=2016-01-01&to=2018-07-31) 14 | 15 | 如你所见,TypeScript 发展至今,已经成为大型项目的标配,其提供的静态类型系统,大大增强了代码的可读性以及可维护性;同时,它提供最新和不断发展的 JavaScript 特性,能让我们建立更健壮的组件。 16 | 17 | [《TypeScript Deep Dive》](https://github.com/basarat/typescript-book/) 是一本很好的开源书,从基础到深入,很全面的阐述了 TypeScript 的各种魔法,不管你是新手,还是老鸟,它都将适应你。此外,它不同于 TypeScript 官方给出的文档(当然 TypeScript 给出的文档是很好的),在此书中,结合实际应用下的场景用例,你将能更深入的理解 TypeScript。 18 | 19 | 如今社区已经存在部分翻译,但都似乎已经停止更新。 20 | 21 | 于是在某天的某个冲动之下,这个 RP 就诞生了。 22 | 23 | ## 翻译内容 24 | 25 | 《TypeScript Deep Dive》 书中包含一部分 JavaScript Future 和一些其他的内容,在这里,我们并不打算翻译它,如果你有兴趣,可以查看原书中 [JavaScript Future](https://basarat.gitbooks.io/typescript/content/docs/future-javascript.html) 的有关章节。 26 | 27 | 由于 TypeScript 更新频繁,在此书中,我也将加入一些原书中并没有涉及到的知识点,希望和大家相互学习,一起进步。 28 | 29 | 此外,在不违背原作者本意前提下,为了更直观的表达,部分内容将采用意译,而非直译。 30 | 31 | ## How to contribute 32 | 33 | 你可以: 34 | 35 | - 通过 PR 修改错别字,或者错误的格式; 36 | - 发 issue 讨论文章中出现的一些不合理地方; 37 | - 翻译 TODO 文件夹下的文章,并顺手 Email 我。 38 | 39 | 希望你在翻译或者 PR 之前,阅读[中文文章排版指北](https://github.com/mzlogin/chinese-copywriting-guidelines)。 40 | 41 | ## 最后 42 | 43 | 如果你和我一样对 TypeScript 充满兴趣,可以订阅(star)本项目,及时收到有关于此项目的更新。 44 | 45 | 如果你对文章有任何疑问,欢迎提交 [issues](https://github.com/jkchao/typescript-book-chinese/issues) 和我交流。 46 | 47 | 如果你认为有些地方翻译不够准确,或者你想补充一些文中没提到但是非常有意思的知识点,欢迎 [PR](https://github.com/jkchao/typescript-book-chinese/pulls)。 48 | 49 | 公众号: 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/typings/ambient.md: -------------------------------------------------------------------------------- 1 | # 环境声明 2 | 3 | 正如我们在为什么使用 [TypeScript](https://basarat.gitbooks.io/typescript/content/docs/why-typescript.html) 中所说: 4 | 5 | > TypeScript 的设计目标之一是让你在 TypeScript 中安全、轻松地使用现有的 JavaScript 库,TypeScript 通过声明文件来做到这一点 6 | 7 | 环境声明允许你安全地使用现有的 JavaScript 库,并且能让你的 JavaScript、CoffeeScript 或者其他需要编译成 JavaScript 的语言逐步迁移至 TypeScript。 8 | 9 | 学习为第三方 JavaScript 库编写环境声明,是一种为 TypeScript 写注解比较好的实践方式。 10 | 11 | ## 声明文件 12 | 13 | 你可以通过 `declare` 关键字来告诉 TypeScript,你正在试图表述一个其他地方已经存在的代码,如:写在 JavaScript、CoffeeScript 或者是像浏览器和 Node.js 运行环境里的代码: 14 | 15 | ```ts 16 | foo = 123; // Error: 'foo' is not defined 17 | ``` 18 | 19 | 和: 20 | 21 | ```ts 22 | declare var foo: any; 23 | foo = 123; // allow 24 | ``` 25 | 26 | 你可以选择把这些声明放入 `.ts` 或者 `.d.ts` 里。在你实际的项目里,我们强烈建议你应该把声明放入独立的 `.d.ts` 里(可以从一个命名为 `global.d.ts` 或者 `vendor.d.ts` 文件开始)。 27 | 28 | 如果一个文件有扩展名 `.d.ts`,这意味着每个根级别的声明都必须以 `declare` 关键字作为前缀。这有利于让开发者清楚的知道,在这里 TypeScript 将不会把它编译成任何代码,同时开发者需要确保这些在编译时存在。 29 | 30 | ::: tip 31 | 32 | - 环境声明就好像你与编译器之间的一个约定,如果在编译时它们不存在,但是你却使用了它们,程序将会在没有警告的情况下中断。 33 | - 环境声明就好像是一个文档。如果源文件更新了,你应该同步更新。所以,当你在运行时有新的行为时,如果没有去更新环境声明,编译器将会报错。 34 | 35 | ::: 36 | 37 | ## 变量 38 | 39 | 当你想告诉 TypeScript 编辑器关于 `process` 变量时,你可以这么做: 40 | 41 | ```ts 42 | declare let process: any; 43 | ``` 44 | 45 | ::: tip 46 | 你并不需要为 `process` 做这些,因为这已经存在于社区维护的 [`node.d.ts`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/index.d.ts) 47 | ::: 48 | 49 | 这允许你使用 `process`,并能成功通过 TypeScript 的编译: 50 | 51 | ```ts 52 | process.exit(); 53 | ``` 54 | 55 | 我们推荐尽可能的使用接口,例如: 56 | 57 | ```ts 58 | interface Process { 59 | exit(code?: number): void; 60 | } 61 | 62 | declare let process: Process; 63 | ``` 64 | 65 | 因为这允许其他人扩充这些全局变量,并且会告诉 TypeScript 有关于这些声明的修改。例如:考虑到以下情况,我们添加一个 `exitWithLogging` 函数至 `process`: 66 | 67 | ```ts 68 | interface Process { 69 | exitWithLogging(code?: number): void; 70 | } 71 | 72 | process.exitWithLogging = function() { 73 | console.log('exiting'); 74 | process.exit.apply(process, arguments); 75 | }; 76 | ``` 77 | 78 | 接下来,让我们更详细的了解接口。 79 | -------------------------------------------------------------------------------- /docs/compiler/checker.md: -------------------------------------------------------------------------------- 1 | # 检查器 2 | 3 | 如前所述,*检查器*使得 TypeScript 更独特,比*其它 JavaScript 转译器*更强大。检查器位于 `checker.ts` 中,当前有 23k 行以上的代码(编译器中最大的部分) 4 | 5 | ### 程序对检查器的使用 6 | 7 | 检查器是由程序初始化,下面是调用栈示意(绑定器一节也展示过): 8 | 9 | ``` 10 | program.getTypeChecker -> 11 | ts.createTypeChecker(检查器中)-> 12 | initializeTypeChecker(检查器中) -> 13 | for each SourceFile `ts.bindSourceFile`(绑定器中) 14 | // 接着 15 | for each SourceFile `ts.mergeSymbolTable`(检查器中) 16 | ``` 17 | 18 | ### 与发射器的联系 19 | 20 | 真正的类型检查会在调用 `getDiagnostics` 时才发生。该函数被调用时(比如由 `Program.emit` 请求),检查器返回一个 `EmitResolver`(由程序调用检查器的 `getEmitResolver` 函数得到),`EmitResolver` 是 `createTypeChecker` 的一个本地函数的集合。介绍发射器时还会再次提到。 21 | 22 | 下面是该过程直到 `checkSourceFile` 的调用栈(`checkSourceFile` 是 `createTypeChecker` 的一个本地函数): 23 | 24 | ``` 25 | program.emit -> 26 | emitWorker (program local) -> 27 | createTypeChecker.getEmitResolver -> 28 | // 第一次调用下面的几个 createTypeChecker 的本地函数 29 | call getDiagnostics -> 30 | getDiagnosticsWorker -> 31 | checkSourceFile 32 | 33 | // 接着 34 | return resolver 35 | // 通过对本地函数 createResolver() 的调用,resolver 已在 createTypeChecker 中初始化。 36 | ``` 37 | 38 | ## 全局命名空间合并 39 | 40 | `initializeTypeChecker` 中存在以下代码: 41 | 42 | ```ts 43 | // 初始化全局符号表(SymbolTable)。 44 | forEach(host.getSourceFiles(), file => { 45 | if (!isExternalModule(file)) { 46 | mergeSymbolTable(globals, file.locals); 47 | } 48 | }); 49 | ``` 50 | 51 | 基本上是将所有的 `global` 符号合并到 `let globals: SymbolTable = {}` 符号表中(位于 `createTypeChecker` 中)。 52 | `mergeSymbolTable` 主要调用 `mergeSymbol` 函数。 53 | 54 | ## 检查器错误报告 55 | 56 | 检查器使用本地的 `error` 函数报告错误,如下所示: 57 | 58 | ```ts 59 | function error(location: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): void { 60 | let diagnostic = location 61 | ? createDiagnosticForNode(location, message, arg0, arg1, arg2) 62 | : createCompilerDiagnostic(message, arg0, arg1, arg2); 63 | diagnostics.add(diagnostic); 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/error/interpreting.md: -------------------------------------------------------------------------------- 1 | # 解读 Errors 2 | 3 | TypeScript 是一种专注于帮助开发人员的编程语言,当错误出现时,它会提供尽可能提供非常有用的错误信息。这对于那些信任使用者的编译器来说,可能会导致轻微的信息量过载,而不会那么实用。 4 | 5 | 让我们来看一个在 IDE 中的例子: 6 | 7 | ```ts 8 | type SomethingComplex = { 9 | foo: number; 10 | bar: string; 11 | }; 12 | 13 | function takeSomethingComplex(arg: SomethingComplex) {} 14 | 15 | function getBar(): string { 16 | return 'some bar'; 17 | } 18 | 19 | // 一个可能会出现的错误使用 20 | const fail = { 21 | foo: 123, 22 | bar: getBar 23 | }; 24 | 25 | takeSomethingComplex(fail); // 在这里 TypeScript 会报错 26 | ``` 27 | 28 | 这个简单的例子,演示了一个常见的程序设计错误,它调用函数失败(`bar: getBar` 应该是 `bar: getBar()`)。幸运的是,一旦不符合类型要求,TypeScript 将会捕捉到这个错误。 29 | 30 | ## 错误分类 31 | 32 | TypeScript 错误信息分为两类:简洁和详细。 33 | 34 | ### 简洁 35 | 36 | 简洁的错误信息是为了提供一个编译器描述的错误号以及一些相关的信息,一个简洁的错误信息类似于如下所示: 37 | 38 | ```ts 39 | TS2345: Argument of type '{ foo: number; bar: () => string; }' is not assignable to parameter of type 'SomethingComplex'. 40 | ``` 41 | 42 | 然而,它没有提供更深层次的信息,如为什么这个错误会发生。这就是详细错误所需要的原因。 43 | 44 | ## 详细 45 | 46 | 详细的错误信息类似于如下所示: 47 | 48 | ```ts 49 | [ts] 50 | Argument of type '{ foo: number; bar: () => string; }' is not assignable to parameter of type 'SomethingComplex'. 51 | Types of property 'bar' are incompatible. 52 | Type '() => string' is not assignable to type 'string'. 53 | ``` 54 | 55 | 详细的错误信息是为了指导使用者知道为什么一些错误(在这个例子里是类型不兼容)会发生。第一行与简洁的错误信息相同,后跟一些详细的信息。你应该阅读这些详细信息,因为对于开发者的一些疑问,它都给出了问答: 56 | 57 | ```ts 58 | ERROR: Argument of type '{ foo: number; bar: () => string; }' is not assignable to parameter of type 'SomethingComplex'. 59 | 60 | WHY? 61 | CAUSE ERROR: Types of property 'bar' are incompatible. 62 | 63 | WHY? 64 | CAUSE ERROR: Type '() => string' is not assignable to type 'string'. 65 | ``` 66 | 67 | 所以,最根本的原因是: 68 | 69 | - 在属性 `bar` 70 | - 函数 `() => string` 它应该是一个字符串。 71 | 72 | 这能够帮助开发者修复 bar 属性的 bug(它们忘记了调用这个函数)。 73 | 74 | ## 在 IDE 中怎么提示 75 | 76 | IDE 通常会在详细的错误提示之后显示简洁版本,如下所示: 77 | 78 | 79 | 80 | - 你通常可能只会阅读「为什么」的详细信息; 81 | - 当你想寻找相同的错误时(使用 `TSXXX` 错误编号,或者部分错误信息),使用简洁的版本。 82 | -------------------------------------------------------------------------------- /docs/tips/typesafeEventEmitter.md: -------------------------------------------------------------------------------- 1 | # 类型安全的 Event Emitter 2 | 3 | 通常来说,在 Node.js 与传统的 JavaScript 里,你有一个单一的 Event Emitter,你可以用它来为不同的事件添加监听器。 4 | 5 | ```ts 6 | const emitter = new EventEmitter(); 7 | 8 | // Emit 9 | emitter.emit('foo', foo); 10 | emitter.emit('bar', bar); 11 | 12 | // Listen 13 | emitter.on('foo', foo => console.log(foo)); 14 | emitter.on('bar', bar => console.log(bar)); 15 | ``` 16 | 17 | 实际上,在 `EventEmitter` 内部以映射数组的形式存储数据: 18 | 19 | ```ts 20 | { foo: [fooListeners], bar: [barListeners] } 21 | ``` 22 | 23 | 为了事件的类型安全,你可以为每个事件类型创建一个 emitter: 24 | 25 | ```ts 26 | const onFoo = new TypedEvent(); 27 | const onBar = new TypedEvent(); 28 | 29 | // Emit: 30 | onFoo.emit(foo); 31 | onBar.emit(bar); 32 | 33 | // Listen: 34 | onFoo.on(foo => console.log(foo)); 35 | onBar.on(bar => console.log(bar)); 36 | ``` 37 | 38 | 它一些优点: 39 | 40 | - 事件的类型,能以变量的形式被发现。 41 | - Event Emitter 非常容易被重构。 42 | - 事件数据结构是类型安全的。 43 | 44 | ## 参考 TypedEvent 45 | 46 | ```ts 47 | export interface Listener { 48 | (event: T): any; 49 | } 50 | 51 | export interface Disposable { 52 | dispose(): any; 53 | } 54 | 55 | export class TypedEvent { 56 | private listeners: Listener[] = []; 57 | private listenersOncer: Listener[] = []; 58 | 59 | public on = (listener: Listener): Disposable => { 60 | this.listeners.push(listener); 61 | 62 | return { 63 | dispose: () => this.off(listener) 64 | }; 65 | }; 66 | 67 | public once = (listener: Listener): void => { 68 | this.listenersOncer.push(listener); 69 | }; 70 | 71 | public off = (listener: Listener) => { 72 | const callbackIndex = this.listeners.indexOf(listener); 73 | if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1); 74 | }; 75 | 76 | public emit = (event: T) => { 77 | this.listeners.forEach(listener => listener(event)); 78 | 79 | this.listenersOncer.forEach(listener => listener(event)); 80 | 81 | this.listenersOncer = []; 82 | }; 83 | 84 | public pipe = (te: TypedEvent): Disposable => { 85 | return this.on(e => te.emit(e)); 86 | }; 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/SidebarLinks.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 20 | 21 | 22 | 23 | 24 | 87 | -------------------------------------------------------------------------------- /docs/typings/movingTypes.md: -------------------------------------------------------------------------------- 1 | # 流动的类型 2 | 3 | TypeScript 类型系统非常强大,它支持其他任何单一语言无法实现的类型流动和类型片段。 4 | 5 | 这是因为 TypeScript 的设计目的之一是让你无缝与像 JavaScript 这类高动态的语言一起工作。在这里,我们介绍一些在 TypeScript 中使用移动类型的技巧。 6 | 7 | 关键的动机:当你改变了其中一个时,其他相关的会自动更新,并且当有事情变糟糕时,你会得到一个友好的提示,就好像一个被精心设计过的约束系统。 8 | 9 | ## 复制类型和值 10 | 11 | 如果你想移动一个类,你可能会想要做以下事情: 12 | 13 | ```ts 14 | class Foo {} 15 | 16 | const Bar = Foo; 17 | 18 | let bar: Bar; // Error: 不能找到名称 'Bar' 19 | ``` 20 | 21 | 这会得到一个错误,因为 `const` 仅仅是复制了 `Foo` 到一个变量声明空间,因此你无法把 `Bar` 当作一个类型声明使用。正确的方式是使用 `import` 关键字,请注意,如果你在使用 `namespace` 或者 `modules`,使用 `import` 是你唯一能用的方式: 22 | 23 | ```ts 24 | namespace importing { 25 | export class Foo {} 26 | } 27 | 28 | import Bar = importing.Foo; 29 | let bar: Bar; // ok 30 | ``` 31 | 32 | 这个 `import` 技巧,仅适合于类型和变量。 33 | 34 | ## 捕获变量的类型 35 | 36 | 你可以通过 `typeof` 操作符在类型注解中使用变量。这允许你告诉编译器,一个变量的类型与其他类型相同,如下所示: 37 | 38 | ```ts 39 | let foo = 123; 40 | let bar: typeof foo; // 'bar' 类型与 'foo' 类型相同(在这里是: 'number') 41 | 42 | bar = 456; // ok 43 | bar = '789'; // Error: 'string' 不能分配给 'number' 类型 44 | ``` 45 | 46 | ## 捕获类成员的类型 47 | 48 | 与捕获变量的类型相似,你仅仅是需要声明一个变量用来捕获到的类型: 49 | 50 | ```ts 51 | class Foo { 52 | foo: number; // 我们想要捕获的类型 53 | } 54 | 55 | declare let _foo: Foo; 56 | 57 | // 与之前做法相同 58 | let bar: typeof _foo.foo; 59 | ``` 60 | 61 | ## 捕获字符串类型 62 | 63 | 许多 JavaScript 库和框架都使用原始的 JavaScript 字符串,你可以使用 `const` 定义一个变量捕获它的类型: 64 | 65 | ```ts 66 | // 捕获字符串的类型与值 67 | const foo = 'Hello World'; 68 | 69 | // 使用一个捕获的类型 70 | let bar: typeof foo; 71 | 72 | // bar 仅能被赋值 'Hello World' 73 | bar = 'Hello World'; // ok 74 | bar = 'anything else'; // Error 75 | ``` 76 | 77 | 在这个例子里,`bar` 有字面量类型 `Hello World`,我们在[字面量类型](./literals.md)章节已经深入讨论。 78 | 79 | ## 捕获键的名称 80 | 81 | `keyof` 操作符能让你捕获一个类型的键。例如,你可以使用它来捕获变量的键名称,在通过使用 `typeof` 来获取类型之后: 82 | 83 | ```ts 84 | const colors = { 85 | red: 'red', 86 | blue: 'blue' 87 | }; 88 | 89 | type Colors = keyof typeof colors; 90 | 91 | let color: Colors; // color 的类型是 'red' | 'blue' 92 | color = 'red'; // ok 93 | color = 'blue'; // ok 94 | color = 'anythingElse'; // Error 95 | ``` 96 | 97 | 这允许你很容易地拥有像字符串枚举+常量这样的类型,如上例所示。 98 | -------------------------------------------------------------------------------- /docs/compiler/overview.md: -------------------------------------------------------------------------------- 1 | # 概览 2 | 3 | TypeScript 编译器源文件位于 [`src/compiler`](https://github.com/Microsoft/TypeScript/tree/master/src/compiler) 目录下 4 | 5 | > 译注:Typescript Deep Dive 使用的源码应为 2016 年以前的源码。学习时请对照现有的源码 6 | 7 | 它分为以下几个关键部分: 8 | 9 | - Scanner 扫描器(`scanner.ts`) 10 | - Parser 解析器(`parser.ts`) 11 | - Binder 绑定器(`binder.ts`) 12 | - Checker 检查器(`checker.ts`) 13 | - Emitter 发射器(`emitter.ts`) 14 | 15 | 每个部分在源文件中均有独立文件,本章稍后会对这些部分做解释。 16 | 17 | ### BYOTS 18 | 19 | 我们有个名为 [Bring Your Own TypeScript (BYOTS)](https://github.com/basarat/byots) 的项目,通过暴露内部接口让编译器 API 使用起来更简单。你可以在全局范围上暴露你 TypeScript 应用的本地变量。 20 | 21 | ### 语法和语义 22 | 23 | *语法*正确并不意味着*语义*上也正确。下面的 TypeScript 代码,语法合法,但是语义却不正确 24 | 25 | ```ts 26 | var foo: number = 'not a number'; 27 | ``` 28 | 29 | `语义` 从自然语言角度意味着有意义,理解这个概念对你很有用。 30 | 31 | ### 处理概览 32 | 33 | 以下演示简单说明 TypeScript 编译器如何将上述几个关键部分组合在一起: 34 | 35 | ```code 36 | SourceCode(源码) ~~ 扫描器 ~~> Token 流 37 | ``` 38 | 39 | ```code 40 | Token 流 ~~ 解析器 ~~> AST(抽象语法树) 41 | ``` 42 | 43 | ```code 44 | AST ~~ 绑定器 ~~> Symbols(符号) 45 | ``` 46 | 47 | 符号(`Symbol`)是 TypeScript *语义*系统的主要构造块。如上所示,符号是绑定的结果。符号将 AST 中的声明节点与相同实体的其他声明相连。 48 | 49 | 符号和 AST 是检查器用来验证源代码*语义*的 50 | 51 | ```code 52 | AST + 符号 ~~ 检查器 ~~> 类型验证 53 | ``` 54 | 55 | 最后,需要输出 JavaScript 时: 56 | 57 | ```code 58 | AST + 检查器 ~~ 发射器 ~~> JavaScript 代码 59 | ``` 60 | 61 | TypeScript 编译器中还有一些其他文件,为我们接下来介绍的很多关键部分提供实用工具。 62 | 63 | ## 文件:Utilities 64 | 65 | `core.ts` :TypeScript 编译器使用的核心工具集,重要的有: 66 | 67 | - `let objectAllocator: ObjectAllocator` 是一个定义为全局单例的变量。提供以下定义: 68 | - `getNodeConstructor`(节点会在解析器 / AST 中介绍) 69 | - `getSymbolConstructor`(符号会在绑定器中介绍) 70 | - `getTypeConstructor`(类型会在检查器中介绍) 71 | - `getSignatureConstructor`(签名是索引,调用和构造签名) 72 | 73 | ## 文件:关键数据结构 74 | 75 | `types.ts` 包含整个编译器中使用的关键数据结构和接口,这里列出一些关键部分: 76 | 77 | - `SyntaxKind` 78 | AST 节点类型通过 `SyntaxKind` 枚举进行识别 79 | - `TypeChecker` 80 | 类型检查器提供此接口 81 | - `CompilerHost` 82 | 用于程序(`Program`)和系统之间的交互 83 | - `Node` 84 | AST 节点 85 | 86 | ## 文件:系统 87 | 88 | `system.ts`,TypeScript 编译器与操作系统的所有交互均通过 `System` 接口进行。接口及其实现(`WScript` 和 `Node`) 均定义在 `system.ts` 中。你可以将其视为*操作环境(OE, Operating Environment)*。 89 | 90 | 现在对主要文件有一个整体了解了,我们继续介绍程序([`Program`](./program.md))的概念 91 | -------------------------------------------------------------------------------- /docs/typings/interfaces.md: -------------------------------------------------------------------------------- 1 | # 接口 2 | 3 | 接口运行时的影响为 0。在 TypeScript 接口中有很多方式来声明变量的结构。 4 | 5 | 下面两个是等效的声明, 示例 A 使用内联注解,示例 B 使用接口形式: 6 | 7 | ```ts 8 | // 示例 A 9 | declare const myPoint: { x: number; y: number }; 10 | 11 | // 示例 B 12 | interface Point { 13 | x: number; 14 | y: number; 15 | } 16 | declare const myPoint: Point; 17 | ``` 18 | 19 | 示例 B 的好处在于,如果有人创建了一个基于 `myPoint` 的库来添加新成员, 那么他可以轻松将此成员添加到 `myPoint` 的现有声明中: 20 | 21 | ```ts 22 | // Lib a.d.ts 23 | interface Point { 24 | x: number, 25 | y: number 26 | } 27 | declare const myPoint: Point 28 | 29 | // Lib b.d.ts 30 | interface Point { 31 | z: number 32 | } 33 | 34 | // Your code 35 | myPoint.z // Allowed! 36 | ``` 37 | 38 | TypeScript 接口是开放式的,这是 TypeScript 的一个重要原则,它允许你使用接口来模仿 JavaScript 的可扩展性。 39 | 40 | ## 类可以实现接口 41 | 42 | 如果你希望在类中使用必须要被遵循的接口(类)或别人定义的对象结构,可以使用 `implements` 关键字来确保其兼容性: 43 | 44 | ```ts 45 | interface Point { 46 | x: number; 47 | y: number; 48 | } 49 | 50 | class MyPoint implements Point { 51 | x: number; 52 | y: number; // Same as Point 53 | } 54 | ``` 55 | 56 | 基本上,在 `implements(实现)` 存在的情况下,该外部 `Point` 接口的任何更改都将导致代码库中的编译错误,因此可以轻松地使其保持同步: 57 | 58 | ```ts 59 | interface Point { 60 | x: number; 61 | y: number; 62 | z: number; // New member 63 | } 64 | 65 | class MyPoint implements Point { 66 | // ERROR : missing member `z` 67 | x: number; 68 | y: number; 69 | } 70 | ``` 71 | 72 | 注意,`implements` 限制了类实例的结构,如下所示: 73 | 74 | ```ts 75 | let foo: Point = new MyPoint(); 76 | ``` 77 | 78 | 但像 `foo: Point = MyPoint` 这样的代码,与其并不是一回事。 79 | 80 | ## 注意 81 | 82 | ### 并非每个接口都是很容易实现的 83 | 84 | 接口旨在声明 JavaScript 中可能存在的任意结构。 85 | 86 | 思考以下例子,可以使用 `new` 调用某些内容: 87 | 88 | ```ts 89 | interface Crazy { 90 | new (): { 91 | hello: number; 92 | }; 93 | } 94 | ``` 95 | 96 | 你可能会有下面这样的代码: 97 | 98 | ```ts 99 | class CrazyClass implements Crazy { 100 | constructor() { 101 | return { hello: 123 }; 102 | } 103 | } 104 | 105 | // Because 106 | const crazy = new CrazyClass(); // crazy would be { hello:123 } 107 | ``` 108 | 109 | 你可以使用接口声明所有“疯狂的”的 JavaScript 代码,甚至可以安全地在 TypeScript 中使用它们。但这并不意味着你可以使用 TypeScript 类来实现它们。 110 | -------------------------------------------------------------------------------- /docs/compiler/scanner.md: -------------------------------------------------------------------------------- 1 | # 扫描器 2 | 3 | TypeScript 扫描器的源码均位于 `scanner.ts`。在内部,由解析器*控制*扫描器将源码转化为抽象语法树(AST)。期望结果如下: 4 | 5 | ``` 6 | SourceCode ~~ 扫描器 ~~> Token 流 ~~ 解析器 ~~> AST 7 | ``` 8 | 9 | ## 解析器对扫描器的使用 10 | 11 | 为避免重复创建扫描器造成的开销,`parser.ts` 中创建了一个扫描器的*单例*。解析器根据需要使用 `initializeState` 函数*准备*该扫描器。 12 | 13 | 下面是解析器中的实际代码的简化版,你可以运行它演示以上概念 14 | 15 | `code/compiler/scanner/runScanner.ts` 16 | 17 | ```ts 18 | import * as ts from 'ntypescript'; 19 | 20 | // 单例扫描器 21 | const scanner = ts.createScanner(ts.ScriptTarget.Latest, /* 忽略杂项 */ true); 22 | 23 | // 此函数与初始化使用的 `initializeState` 函数相似 24 | function initializeState(text: string) { 25 | scanner.setText(text); 26 | scanner.setOnError((message: ts.DiagnosticMessage, length: number) => { 27 | console.error(message); 28 | }); 29 | scanner.setScriptTarget(ts.ScriptTarget.ES5); 30 | scanner.setLanguageVariant(ts.LanguageVariant.Standard); 31 | } 32 | 33 | // 使用示例 34 | initializeState( 35 | ` 36 | var foo = 123; 37 | `.trim() 38 | ); 39 | 40 | // 开始扫描 41 | var token = scanner.scan(); 42 | while (token != ts.SyntaxKind.EndOfFileToken) { 43 | console.log(ts.formatSyntaxKind(token)); 44 | token = scanner.scan(); 45 | } 46 | ``` 47 | 48 | 该段代码输出以下内容: 49 | 50 | ``` 51 | VarKeyword 52 | Identifier 53 | FirstAssignment 54 | FirstLiteralToken 55 | SemicolonToken 56 | ``` 57 | 58 | ## 扫描器状态 59 | 60 | 调用 `scan` 后,扫描器更新其局部状态(扫描位置,当前 token 详情等)。扫描器提供了一组工具函数获取当前扫描器状态。下例中,我们创建一个扫描器并用它识别 token 以及 token 在代码中的位置。 61 | 62 | `code/compiler/scanner/runScannerWithPosition.ts` 63 | 64 | ```ts 65 | // 使用示例 66 | initializeState( 67 | ` 68 | var foo = 123; 69 | `.trim() 70 | ); 71 | 72 | // 开始扫描 73 | var token = scanner.scan(); 74 | while (token != ts.SyntaxKind.EndOfFileToken) { 75 | let currentToken = ts.formatSyntaxKind(token); 76 | let tokenStart = scanner.getStartPos(); 77 | token = scanner.scan(); 78 | let tokenEnd = scanner.getStartPos(); 79 | console.log(currentToken, tokenStart, tokenEnd); 80 | } 81 | ``` 82 | 83 | 该代码输出以下内容: 84 | 85 | ``` 86 | VarKeyword 0 3 87 | Identifier 3 7 88 | FirstAssignment 7 9 89 | FirstLiteralToken 9 13 90 | SemicolonToken 13 14 91 | ``` 92 | 93 | ## 独立扫描器 94 | 95 | 即便 TypeScript 解析器有单例扫描器,你仍可以使用 `createScanner` 创建独立的扫描器,然后可以用 `setText`/`setTextPos` 随意扫描文件的不同位置。 96 | -------------------------------------------------------------------------------- /docs/tips/outFileCaution.md: -------------------------------------------------------------------------------- 1 | # 谨慎使用 `--outFile` 2 | 3 | 由于以下几点原因,你应该谨慎使用 `--outFile` 选项: 4 | 5 | - 运行时的错误; 6 | - 快速编译; 7 | - 全局作用域; 8 | - 难以分析; 9 | - 难以扩展; 10 | - `_references`; 11 | - 代码重用; 12 | - 多目标; 13 | - 单独编译; 14 | 15 | ## 运行时的错误 16 | 17 | 如果你的代码依赖于上文中的 JavaScript,你可能会在运行时得到错误: 18 | 19 | - 类的继承在运行时中断。 20 | 21 | 有如下 `foo.ts`: 22 | 23 | ```ts 24 | class Foo {} 25 | ``` 26 | 27 | 以及 `bar.ts`: 28 | 29 | ```ts 30 | class Bar extends Foo {} 31 | ``` 32 | 33 | 如果你没有按正确的顺序编译它(例如:`tsc bar.ts foo.ts`),虽然它能够被编译成功,但是会在运行时抛出 `ReferenceError` 的错误。 34 | 35 | - 模块拆分在运行时会失败。 36 | 37 | `foo.ts`: 38 | 39 | ```ts 40 | namespace App { 41 | export const foo = 123; 42 | } 43 | ``` 44 | 45 | `bar.ts`: 46 | 47 | ```ts 48 | namespace App { 49 | export const bar = foo + 456; 50 | } 51 | ``` 52 | 53 | 与上文中一致,当你没有用正确的顺序编译它,它会在运行时将 `NaN` 赋值给 `bar`; 54 | 55 | ## 快速编译 56 | 57 | 如果你使用 `--out` 编译选项,而没有使用一些 `hacks` 时,单独的 `.ts` 文件是不会被编译成单独的 `.js` 文件。 `--out` 选项实际上使用了较慢的构建方式。 58 | 59 | 此外,由于 source map 基于长度编码,且对位置信息敏感,因此,大部分 source map 都会在编译时重新构建(如果你使用 source map)。 60 | 61 | ## 全局作用域 62 | 63 | 当然,你可以使用命名空间,但是它仍然在 `window` 上(如果你在浏览器中打开),命名空间仅仅是一个临时的解决方式。`/// = new (...args: any[]) => T; 29 | 30 | ///////////// 31 | // mixins 例子 32 | //////////// 33 | 34 | // 添加属性的混合例子 35 | function Timestamped(Base: TBase) { 36 | return class extends Base { 37 | timestamp = Date.now(); 38 | }; 39 | } 40 | 41 | // 添加属性和方法的混合例子 42 | function Activatable(Base: TBase) { 43 | return class extends Base { 44 | isActivated = false; 45 | 46 | activate() { 47 | this.isActivated = true; 48 | } 49 | 50 | deactivate() { 51 | this.isActivated = false; 52 | } 53 | }; 54 | } 55 | 56 | /////////// 57 | // 组合类 58 | /////////// 59 | 60 | // 简单的类 61 | class User { 62 | name = ''; 63 | } 64 | 65 | // 添加 Timestamped 的 User 66 | const TimestampedUser = Timestamped(User); 67 | 68 | // Tina Timestamped 和 Activatable 的类 69 | const TimestampedActivatableUser = Timestamped(Activatable(User)); 70 | 71 | ////////// 72 | // 使用组合类 73 | ////////// 74 | 75 | const timestampedUserExample = new TimestampedUser(); 76 | console.log(timestampedUserExample.timestamp); 77 | 78 | const timestampedActivatableUserExample = new TimestampedActivatableUser(); 79 | console.log(timestampedActivatableUserExample.timestamp); 80 | console.log(timestampedActivatableUserExample.isActivated); 81 | ``` 82 | 83 | 让我们分解这个例子。 84 | 85 | ## 创建一个构造函数 86 | 87 | 混合接受一个类,并且使用新功能扩展它。因此,我们需要定义构造函数的类型: 88 | 89 | ```ts 90 | type Constructor = new (...args: any[]) => T; 91 | ``` 92 | 93 | ## 扩展一个类并且返回它 94 | 95 | ```ts 96 | // 添加属性的混合例子 97 | function Timestamped(Base: TBase) { 98 | return class extends Base { 99 | timestamp = Date.now(); 100 | }; 101 | } 102 | ``` 103 | -------------------------------------------------------------------------------- /docs/project/dynamicImportExpressions.md: -------------------------------------------------------------------------------- 1 | # 动态导入表达式 2 | 3 | 动态导入表达式是 ECMAScript 的一个新功能,它允许你在程序的任意位置异步加载一个模块,TC39 JavaScript 委员会有一个提案,目前处于第四阶段,它被称为 [import() proposal for JavaScript](https://github.com/tc39/proposal-dynamic-import)。 4 | 5 | 此外,**webpack** bundler 有一个 [`Code Splitting`](https://webpack.js.org/guides/code-splitting/) 功能,它能允许你将代码拆分为许多块,这些块在将来可被异步下载。因此,你可以在程序中首先提供一个最小的程序启动包,并在将来异步加载其他模块。 6 | 7 | 这很自然就会让人想到(如果我们工作在 webpack dev 的工作流程中)[TypeScript 2.4 dynamic import expressions](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#dynamic-import-expressions) 将会把你最终生成的 JavaScript 代码自动分割成很多块。但是这似乎并不容易实现,因为它依赖于我们正在使用的 `tsconfig.json` 配置文件。 8 | 9 | webpack 实现代码分割的方式有两种:使用 `import()` (首选,ECMAScript 的提案)和 `require.ensure()` (最后考虑,webpack 具体实现)。因此,我们期望 TypeScript 的输出是保留 `import()` 语句,而不是将其转化为其他任何代码。 10 | 11 | 让我们来看一个例子,在这个例子中,我们演示了如何配置 webpack 和 TypeScript 2.4 +。 12 | 13 | 在下面的代码中,我希望懒加载 `moment` 库,同时我也希望使用代码分割的功能,这意味 `moment` 会被分割到一个单独的 JavaScript 文件,当它被使用时,会被异步加载。 14 | 15 | ```ts 16 | import(/* webpackChunkName: "momentjs" */ 'moment') 17 | .then(moment => { 18 | // 懒加载的模块拥有所有的类型,并且能够按期工作 19 | // 类型检查会工作,代码引用也会工作 :100: 20 | const time = moment().format(); 21 | console.log('TypeScript >= 2.4.0 Dynamic Import Expression:'); 22 | console.log(time); 23 | }) 24 | .catch(err => { 25 | console.log('Failed to load moment', err); 26 | }); 27 | ``` 28 | 29 | 这是 `tsconfig.json` 的配置文件: 30 | 31 | ```js 32 | { 33 | "compilerOptions": { 34 | "target": "es5", 35 | "module": "esnext", 36 | "lib": [ 37 | "dom", 38 | "es5", 39 | "scripthost", 40 | "es2015.promise" 41 | ], 42 | "jsx": "react", 43 | "declaration": false, 44 | "sourceMap": true, 45 | "outDir": "./dist/js", 46 | "strict": true, 47 | "moduleResolution": "node", 48 | "typeRoots": [ 49 | "./node_modules/@types" 50 | ], 51 | "types": [ 52 | "node", 53 | "react", 54 | "react-dom" 55 | ] 56 | } 57 | } 58 | ``` 59 | 60 | :::danger 重要的提示 61 | 62 | - 使用 `"module": "esnext"` 选项:TypeScript 保留 `import()` 语句,该语句用于 Webpack Code Splitting。 63 | - 进一步了解有关信息,推荐阅读这篇文章:[Dynamic Import Expressions and webpack 2 Code Splitting integration with TypeScript 2.4.](https://blog.josequinto.com/2017/06/29/dynamic-import-expressions-and-webpack-code-splitting-integration-with-typescript-2-4/) 64 | 65 | ::: 66 | -------------------------------------------------------------------------------- /docs/faqs/generics.md: -------------------------------------------------------------------------------- 1 | # 泛型 2 | 3 | ## 通过接口 `A`,为什么 `A` 可赋值给 `A`? 4 | 5 | > 我写下这段代码,让它抛出一个错误。 6 | 7 | ```typescript 8 | interface Something { 9 | name: string; 10 | } 11 | let x: Something; 12 | let y: Something; 13 | // Expected error: Can't convert Something to Something! 14 | x = y; 15 | ``` 16 | 17 | `TypeScript` 使用了一种结构类型的系统。当判断 `Something` 和 `Something` 兼容性的时候,我们会检查每一个成员的每一个属性,如果类型的每个成员都是兼容的,那么这个类型也是兼容的。因为 `Something` 没有在任何成员中使用 `T`,所以 `T` 是什么类型并不重要。 18 | 19 | 通常,你绝不应该有未使用类型的参数。该类型会有无法预料的兼容性(如上所示),同时在函数调用中也无法获取正确的泛型类型接口。 20 | 21 | ## 为什么类型接口不能在这个接口上运行: `interface Foo { }`? 22 | 23 | > 我写了一些这样的代码 24 | 25 | ```typescript 26 | interface Named { 27 | name: string; 28 | } 29 | class MyNamed implements Named { 30 | name: 'mine'; 31 | } 32 | function findByName(x: Named): T { 33 | // TODO: Implement 34 | return undefined; 35 | } 36 | 37 | var x: MyNamed; 38 | var y = findByName(x); // expected y: string, got y: {} 39 | ``` 40 | 41 | `TypeScript` 使用了一种结构类型的系统。这种结构性也适用于泛型类型接口。当在函数调用中推断 `T` 的类型时,我们试图在 `x` 参数上找到 `T` 类型的成员,从而判断 `T` 应该是什么。因为没有使用 `T` 的成员,所以没有什么可推断的,于是我们返回 `{}`。 42 | 43 | 请注意,如果你使用 `T`,你就会得到正确的结果: 44 | 45 | ```typescript 46 | interface Named { 47 | name: string; 48 | value: T; // <-- added 49 | } 50 | class MyNamed implements Named { 51 | name: 'mine'; 52 | value: T; // <-- added 53 | } 54 | function findByName(x: Named): T { 55 | // TODO: Implement 56 | return undefined; 57 | } 58 | 59 | var x: MyNamed; 60 | var y = findByName(x); // got y: string; 61 | ``` 62 | 63 | 记住:你绝不应该有未使用类型的参数!请看前一个问题,了解为什么这样不好。 64 | 65 | ## 为什么不要在泛型函数中写 `typeof T`、`new T`, 或者 `instanceof T`? 66 | 67 | > 我写了一些这样的代码 68 | 69 | ```typescript 70 | function doSomething(x: T) { 71 | // Can't find name T? 72 | let xType = typeof T; 73 | let y = new xType(); 74 | // Same here? 75 | if(someVar instanceof typeof T) { 76 | 77 | } 78 | // How do I instantiate? 79 | let z = new T(); 80 | } 81 | ``` 82 | 83 | 泛型在编译期间被删除,这意味着在 `doSomething` 运行时没有值为 `T` 。这里人们试图表达的正常模式是将类的构造函数用于工厂或运行时类型检查。。在这两种情况下,使用构造签名并将其作为参数提供是正确的: 84 | ```typescript 85 | function create(ctor: { new(): T }) { 86 | return new ctor(); 87 | } 88 | var c = create(MyClass); // c: MyClass 89 | 90 | function isReallyInstanceOf(ctor: { new(...args: any[]): T }, obj: T) { 91 | return obj instanceof ctor; 92 | } 93 | ``` -------------------------------------------------------------------------------- /docs/tips/covarianceAndContravariance.md: -------------------------------------------------------------------------------- 1 | # 协变与逆变 2 | 3 | > [原文链接: what are covariance and contravariance](https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance) 4 | 5 | [子类型](https://en.wikipedia.org/wiki/Subtyping) 在编程理论上是一个复杂的话题,而他的复杂之处来自于一对经常会被混淆的现象,我们称之为*协变*与*逆变*。这篇文章将会解释上述两个概念。 6 | 7 | 开始文章之前我们先约定如下的标记: 8 | 9 | - `A ≼ B` 意味着 `A` 是 `B` 的子类型。 10 | - `A → B` 指的是以 `A` 为参数类型,以 `B` 为返回值类型的函数类型。 11 | - `x : A` 意味着 `x` 的类型为 `A`。 12 | 13 | ## 一个有趣的问题 14 | 15 | 假设我有如下三种类型: 16 | 17 | > `Greyhound ≼ Dog ≼ Animal` 18 | 19 | `Greyhound` (灰狗)是 `Dog` (狗)的子类型,而 `Dog` 则是 `Animal` (动物)的子类型。由于子类型通常是可传递的,因此我们也称 `Greyhound` 是 `Animal` 的子类型。 20 | 21 | **问题**:以下哪种类型是 `Dog → Dog` 的子类型呢? 22 | 23 | 1. `Greyhound → Greyhound` 24 | 2. `Greyhound → Animal` 25 | 3. `Animal → Animal` 26 | 4. `Animal → Greyhound` 27 | 28 | 让我们来思考一下如何解答这个问题。首先我们假设 `f` 是一个以 `Dog → Dog` 为参数的函数。它的返回值并不重要,为了具体描述问题,我们假设函数结构体是这样的: `f : (Dog → Dog) → String`。 29 | 30 | 现在我想给函数 `f` 传入某个函数 `g` 来调用。我们来瞧瞧当 `g` 为以上四种类型时,会发生什么情况。 31 | 32 | **1. 我们假设 `g : Greyhound → Greyhound`, `f(g)` 的类型是否安全?** 33 | 34 | 不安全,因为在f内调用它的参数`(g)`函数时,使用的参数可能是一个不同于灰狗但又是狗的子类型,例如 `GermanShepherd` (牧羊犬)。 35 | 36 | **2. 我们假设 `g : Greyhound → Animal`, `f(g)` 的类型是否安全?** 37 | 38 | 不安全。理由同(1)。 39 | 40 | **3. 我们假设 `g : Animal → Animal`, `f(g)` 的类型是否安全?** 41 | 42 | 不安全。因为 `f` 有可能在调用完参数之后,让返回值,也就是 `Animal` (动物)狗叫。并非所有动物都会狗叫。 43 | 44 | **4. 我们假设 `g : Animal → Greyhound`, `f(g)` 的类型是否安全?** 45 | 46 | 是的,它的类型是安全的。首先,`f` 可能会以任何狗的品种来作为参数调用,而所有的狗都是动物。其次,它可能会假设结果是一条狗,而所有的灰狗都是狗。 47 | 48 | ## 展开讲讲? 49 | 50 | 如上所述,我们得出结论: 51 | 52 | > `(Animal → Greyhound) ≼ (Dog → Dog)` 53 | 54 | 返回值类型很容易理解:灰狗是狗的子类型。但参数类型则是相反的:动物是狗的*父类*! 55 | 56 | 用合适的术语来描述这个奇怪的表现,可以说我们允许一个函数类型中,返回值类型是*协变*的,而参数类型是*逆变*的。返回值类型是协变的,意思是 `A ≼ B` 就意味着 `(T → A) ≼ (T → B)` 。参数类型是逆变的,意思是 `A ≼ B` 就意味着 `(B → T) ≼ (A → T)` ( `A` 和 `B` 的位置颠倒过来了)。 57 | 58 | **一个有趣的现象**:在 `TypeScript` 中, [参数类型是双向协变的](https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-function-parameters-bivariant) 59 | ,也就是说既是协变又是逆变的,而这并不安全。但是现在你可以在 [`TypeScript 2.6`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html) 版本中通过 `--strictFunctionTypes` 或 `--strict` 标记来修复这个问题。 60 | 61 | ## 那其他类型呢? 62 | 63 | **问题**:`List` 能否为 `List` 的子类型? 64 | 65 | 答案有点微妙。如果列表是不可变的(immutable),那么答案是肯定的,因为类型很安全。但是假如列表是可变的,那么答案绝对是否定的! 66 | 67 | 原因是,假设我需要一串 `List` 而你传给我一串 `List`。由于我认为我拥有的是一串 `List` ,我可能会尝试往列表插入一只 `Cat`。那么你的 `List` 里面就会有一只猫!类型系统不应该允许这种情况发生。 68 | 69 | 总结一下,我们可以允许不变的列表(immutable)在它的参数类型上是协变的,但是对于可变的列表(mutable),其参数类型则必须是不变的(invariant),既不是协变也不是逆变。 70 | 71 | **一个有趣的现象**:在 `Java` 中,数组[既是可变的,又是协变的](https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29#Covariant_arrays_in_Java_and_C.23)。当然,这并不安全。 72 | -------------------------------------------------------------------------------- /docs/typings/thisType.md: -------------------------------------------------------------------------------- 1 | # ThisType 2 | 3 | 通过 `ThisType` 我们可以在对象字面量中键入 `this`,并提供通过上下文类型控制 `this` 类型的便捷方式。它只有在 `--noImplicitThis` 的选项下才有效。 4 | 5 | 现在,在对象字面量方法中的 `this` 类型,将由以下决定: 6 | 7 | - 如果这个方法显式指定了 `this` 参数,那么 `this` 具有该参数的类型。(下例子中 `bar`) 8 | - 否则,如果方法由带 `this` 参数的签名进行上下文键入,那么 `this` 具有该参数的类型。(下例子中 `foo`) 9 | - 否则,如果 `--noImplicitThis` 选项已经启用,并且对象字面量中包含由 `ThisType` 键入的上下文类型,那么 `this` 的类型为 `T`。 10 | - 否则,如果 `--noImplicitThis` 选项已经启用,并且对象字面量中不包含由 `ThisType` 键入的上下文类型,那么 `this` 的类型为该上下文类型。 11 | - 否则,如果 `--noImplicitThis` 选项已经启用,`this` 具有该对象字面量的类型。 12 | - 否则,`this` 的类型为 `any`。 13 | 14 | 一些例子: 15 | 16 | ```ts 17 | // Compile with --noImplicitThis 18 | 19 | type Point = { 20 | x: number; 21 | y: number; 22 | moveBy(dx: number, dy: number): void; 23 | }; 24 | 25 | let p: Point = { 26 | x: 10, 27 | y: 20, 28 | moveBy(dx, dy) { 29 | this.x += dx; // this has type Point 30 | this.y += dy; // this has type Point 31 | } 32 | }; 33 | 34 | let foo = { 35 | x: 'hello', 36 | f(n: number) { 37 | this; // { x: string, f(n: number): void } 38 | } 39 | }; 40 | 41 | let bar = { 42 | x: 'hello', 43 | f(this: { message: string }) { 44 | this; // { message: string } 45 | } 46 | }; 47 | ``` 48 | 49 | 类似的方式,当使用 `--noImplicitThis` 时,函数表达式赋值给 `obj.xxx` 或者 `obj[xxx]` 的目标时,在函数中 `this` 的类型将会是 `obj`: 50 | 51 | ```ts 52 | // Compile with --noImplicitThis 53 | 54 | obj.f = function(n) { 55 | return this.x - n; // 'this' has same type as 'obj' 56 | }; 57 | 58 | obj['f'] = function(n) { 59 | return this.x - n; // 'this' has same type as 'obj' 60 | }; 61 | ``` 62 | 63 | 通过 API 转换参数的形式来生成 `this` 的值的情景下,可以通过创建一个新的 `ThisType` 标记接口,可用于在上下文中表明转换后的类型。尤其是当字面量中的上下文类型为 `ThisType` 或者是包含 `ThisType` 的交集时,显得尤为有效,对象字面量方法中 `this` 的类型即为 `T`。 64 | 65 | ```ts 66 | // Compile with --noImplicitThis 67 | 68 | type ObjectDescriptor = { 69 | data?: D; 70 | methods?: M & ThisType; // Type of 'this' in methods is D & M 71 | }; 72 | 73 | function makeObject(desc: ObjectDescriptor): D & M { 74 | let data: object = desc.data || {}; 75 | let methods: object = desc.methods || {}; 76 | return { ...data, ...methods } as D & M; 77 | } 78 | 79 | let obj = makeObject({ 80 | data: { x: 0, y: 0 }, 81 | methods: { 82 | moveBy(dx: number, dy: number) { 83 | this.x += dx; // Strongly typed this 84 | this.y += dy; // Strongly typed this 85 | } 86 | } 87 | }); 88 | 89 | obj.x = 10; 90 | obj.y = 20; 91 | obj.moveBy(5, 5); 92 | ``` 93 | 94 | 在上面的例子中,`makeObject` 参数中的对象属性 `methods` 具有包含 `ThisType` 的上下文类型,因此对象中 `methods` 属性下的方法的 `this` 类型为 `{ x: number, y: number } & { moveBy(dx: number, dy: number): number }`。 95 | 96 | `ThisType` 的接口,在 `lib.d.ts` 只是被声明为空的接口,除了可以在对象字面量上下文中可以被识别以外,该接口的作用等同于任意空接口。 97 | -------------------------------------------------------------------------------- /docs/typings/typeAssertion.md: -------------------------------------------------------------------------------- 1 | # 类型断言 2 | 3 | TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。 4 | 5 | 类型断言的一个常见用例是当你从 JavaScript 迁移到 TypeScript 时: 6 | 7 | ```ts 8 | const foo = {}; 9 | foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’ 10 | foo.bas = 'hello'; // Error: 'bas' 属性不存在于 '{}' 11 | ``` 12 | 13 | 这里的代码发出了错误警告,因为 `foo` 的类型推断为 `{}`,即没有属性的对象。因此,你不能在它的属性上添加 `bar` 或 `bas`,你可以通过类型断言来避免此问题: 14 | 15 | ```ts 16 | interface Foo { 17 | bar: number; 18 | bas: string; 19 | } 20 | 21 | const foo = {} as Foo; 22 | foo.bar = 123; 23 | foo.bas = 'hello'; 24 | ``` 25 | 26 | ## `as foo` 与 `` 27 | 28 | 最初的断言语法如下所示: 29 | 30 | ```ts 31 | let foo: any; 32 | let bar = foo; // 现在 bar 的类型是 'string' 33 | ``` 34 | 35 | 然而,当你在 JSX 中使用 `` 的断言语法时,这会与 JSX 的语法存在歧义: 36 | 37 | ```ts 38 | let foo = bar;; 39 | ``` 40 | 41 | 因此,为了一致性,我们建议你使用 `as foo` 的语法来为类型断言。 42 | 43 | ## 类型断言与类型转换 44 | 45 | 它之所以不被称为「类型转换」,是因为转换通常意味着某种运行时的支持。但是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法。 46 | 47 | ## 类型断言被认为是有害的 48 | 49 | 在很多情景下,断言能让你更容易的从遗留项目中迁移(甚至将其他代码粘贴复制到你的项目中),然而,你应该小心谨慎的使用断言。让我们用最初的代码作为示例,如果你没有按约定添加属性,TypeScript 编译器并不会对此发出错误警告: 50 | 51 | ```ts 52 | interface Foo { 53 | bar: number; 54 | bas: string; 55 | } 56 | 57 | const foo = {} as Foo; 58 | 59 | // ahhh, 忘记了什么? 60 | ``` 61 | 62 | 另外一个常见的想法是使用类型断言来提供代码的提示: 63 | 64 | ```ts 65 | interface Foo { 66 | bar: number; 67 | bas: string; 68 | } 69 | 70 | const foo = { 71 | // 编译器将会提供关于 Foo 属性的代码提示 72 | // 但是开发人员也很容易忘记添加所有的属性 73 | // 同样,如果 Foo 被重构,这段代码也可能被破坏(例如,一个新的属性被添加)。 74 | }; 75 | ``` 76 | 77 | 这也会存在一个同样的问题,如果你忘记了某个属性,编译器同样也不会发出错误警告。使用一种更好的方式: 78 | 79 | ```ts 80 | interface Foo { 81 | bar: number; 82 | bas: string; 83 | } 84 | 85 | const foo: Foo = { 86 | // 编译器将会提供 Foo 属性的代码提示 87 | }; 88 | ``` 89 | 90 | 在某些情景下,你可能需要创建一个临时的变量,但至少,你不会使用一个承诺(可能是假的),而是依靠类型推断来检查你的代码。 91 | 92 | ## 双重断言 93 | 94 | 类型断言,尽管我们已经证明了它并不是那么安全,但它也还是有用武之地。如下一个非常实用的例子所示,当使用者了解传入参数更具体的类型时,类型断言能按预期工作: 95 | 96 | ```ts 97 | function handler(event: Event) { 98 | const mouseEvent = event as MouseEvent; 99 | } 100 | ``` 101 | 102 | 然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言: 103 | 104 | ```ts 105 | function handler(event: Event) { 106 | const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个 107 | } 108 | ``` 109 | 110 | 如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的 `any`,编译器将不会报错: 111 | 112 | ```ts 113 | function handler(event: Event) { 114 | const element = (event as any) as HTMLElement; // ok 115 | } 116 | ``` 117 | 118 | ### TypeScript 是怎么确定单个断言是否足够 119 | 120 | 当 `S` 类型是 `T` 类型的子集,或者 `T` 类型是 `S` 类型的子集时,`S` 能被成功断言成 `T`。这是为了在进行类型断言时提供额外的安全性,完全毫无根据的断言是危险的,如果你想这么做,你可以使用 `any`。 121 | -------------------------------------------------------------------------------- /docs/typings/literals.md: -------------------------------------------------------------------------------- 1 | # 字面量类型 2 | 3 | 字面量是 JavaScript 本身提供的一个准确变量。 4 | 5 | ## 字符串字面量 6 | 7 | 你可以使用一个字符串字面量作为一个类型: 8 | 9 | ```ts 10 | let foo: 'Hello'; 11 | ``` 12 | 13 | 在这里,我们创建了一个被称为 `foo` 变量,它仅接收一个字面量值为 `Hello` 的变量: 14 | 15 | ```ts 16 | let foo: 'Hello'; 17 | foo = 'Bar'; // Error: 'bar' 不能赋值给类型 'Hello' 18 | ``` 19 | 20 | 它们本身并不是很实用,但是可以在一个联合类型中组合创建一个强大的(实用的)抽象: 21 | 22 | ```ts 23 | type CardinalDirection = 'North' | 'East' | 'South' | 'West'; 24 | 25 | function move(distance: number, direction: CardinalDirection) { 26 | // ... 27 | } 28 | 29 | move(1, 'North'); // ok 30 | move(1, 'Nurth'); // Error 31 | ``` 32 | 33 | ## 其他字面量类型 34 | 35 | TypeScript 同样也提供 `boolean` 和 `number` 的字面量类型: 36 | 37 | ```ts 38 | type OneToFive = 1 | 2 | 3 | 4 | 5; 39 | type Bools = true | false; 40 | ``` 41 | 42 | ## 推断 43 | 44 | 通常,你会得到一个类似于 `Type string is not assignable to type 'foo'` 的错误,如下: 45 | 46 | ```ts 47 | function iTakeFoo(foo: 'foo') {} 48 | const test = { 49 | someProp: 'foo' 50 | }; 51 | 52 | iTakeFoo(test.someProp); // Error: Argument of type string is not assignable to parameter of type 'foo' 53 | ``` 54 | 55 | 这是由于 `test` 被推断为 `{ someProp: string }`,我们可以采用一个简单的类型断言来告诉 TypeScript 你想推断的字面量: 56 | 57 | ```ts 58 | function iTakeFoo(foo: 'foo') {} 59 | 60 | const test = { 61 | someProp: 'foo' as 'foo' 62 | }; 63 | 64 | iTakeFoo(test.someProp); // ok 65 | ``` 66 | 67 | 或者使用类型注解的方式,来帮助 TypeScript 推断正确的类型: 68 | 69 | ```ts 70 | function iTakeFoo(foo: 'foo') {} 71 | 72 | type Test = { 73 | someProp: 'foo'; 74 | }; 75 | 76 | const test: Test = { 77 | // 推断 `someProp` 永远是 'foo' 78 | someProp: 'foo' 79 | }; 80 | 81 | iTakeFoo(test.someProp); // ok 82 | ``` 83 | 84 | ## 使用用例 85 | 86 | TypeScript 枚举类型是基于数字的,你可以使用带字符串字面量的联合类型,来模拟一个基于字符串的枚举类型,就好像上文中提出的 `CardinalDirection`。你甚至可以使用下面的函数来生成 `key: value` 的结构: 87 | 88 | ```ts 89 | // 用于创建字符串列表映射至 `K: V` 的函数 90 | function strEnum(o: Array): { [K in T]: K } { 91 | return o.reduce((res, key) => { 92 | res[key] = key; 93 | return res; 94 | }, Object.create(null)); 95 | } 96 | ``` 97 | 98 | 然后,你就可以使用 `keyof`、`typeof` 来生成字符串的联合类型。下面是一个完全的例子: 99 | 100 | ```ts 101 | // 用于创建字符串列表映射至 `K: V` 的函数 102 | function strEnum(o: Array): { [K in T]: K } { 103 | return o.reduce((res, key) => { 104 | res[key] = key; 105 | return res; 106 | }, Object.create(null)); 107 | } 108 | 109 | // 创建 K: V 110 | const Direction = strEnum(['North', 'South', 'East', 'West']); 111 | 112 | // 创建一个类型 113 | type Direction = keyof typeof Direction; 114 | 115 | // 简单的使用 116 | let sample: Direction; 117 | 118 | sample = Direction.North; // Okay 119 | sample = 'North'; // Okay 120 | sample = 'AnythingElse'; // ERROR! 121 | ``` 122 | 123 | ## 辨析联合类型 124 | 125 | 我们将会在此书的稍后章节讲解它。 126 | -------------------------------------------------------------------------------- /docs/typings/freshness.md: -------------------------------------------------------------------------------- 1 | # Freshness 2 | 3 | 为了能让检查对象字面量类型更容易,TypeScript 提供 「[Freshness](https://github.com/Microsoft/TypeScript/pull/3823)」 的概念(它也被称为更严格的对象字面量检查)用来确保对象字面量在结构上类型兼容。 4 | 5 | 结构类型非常方便。考虑如下例子代码,它可以让你非常便利的从 JavaScript 迁移至 TypeScript,并且会提供类型安全: 6 | 7 | ```js 8 | function logName(something: { name: string }) { 9 | console.log(something.name); 10 | } 11 | 12 | const person = { name: 'matt', job: 'being awesome' }; 13 | const animal = { name: 'cow', diet: 'vegan, but has milk of own specie' }; 14 | const randow = { note: `I don't have a name property` }; 15 | 16 | logName(person); // ok 17 | logName(animal); // ok 18 | logName(randow); // Error: 没有 `name` 属性 19 | ``` 20 | 21 | 但是,结构类型有一个缺点,它能误导你认为某些东西接收的数据比它实际的多。如下例,TypeScript 发出错误警告: 22 | 23 | ```ts 24 | function logName(something: { name: string }) { 25 | console.log(something.name); 26 | } 27 | 28 | logName({ name: 'matt' }); // ok 29 | logName({ name: 'matt', job: 'being awesome' }); // Error: 对象字面量只能指定已知属性,`job` 属性在这里并不存在。 30 | ``` 31 | 32 | ::: warning 33 | 请注意,这种错误提示,只会发生在对象字面量上。 34 | ::: 35 | 36 | 如果没有这种错误提示,我们可能会去寻找函数的调用 `logName({ name: 'matt', job: 'being awesome' })`,继而会认为 `logName` 可能会使用 `job` 属性做一些事情,然而实际上 `logName` 并没有使用它。 37 | 38 | 另外一个使用比较多的场景是与具有可选成员的接口一起使用,如果没有这样的对象字面量检查,当你输入错误单词的时候,并不会发出错误警告: 39 | 40 | ```ts 41 | function logIfHasName(something: { name?: string }) { 42 | if (something.name) { 43 | console.log(something.name); 44 | } 45 | } 46 | 47 | const person = { name: 'matt', job: 'being awesome' }; 48 | const animal = { name: 'cow', diet: 'vegan, but has milk of own species' }; 49 | 50 | logIfHasName(person); // okay 51 | logIfHasName(animal); // okay 52 | 53 | logIfHasName({ neme: 'I just misspelled name to neme' }); // Error: 对象字面量只能指定已知属性,`neme` 属性不存在。 54 | ``` 55 | 56 | 之所以只对对象字面量进行类型检查,因为在这种情况下,那些实际上并没有被使用到的属性有可能会拼写错误或者会被误用。 57 | 58 | ## 允许额外的属性 59 | 60 | 一个类型能够包含索引签名,以明确表明可以使用额外的属性: 61 | 62 | ```ts 63 | let x: { foo: number, [x: string]: any }; 64 | 65 | x = { foo: 1, baz: 2 }; // ok, 'baz' 属性匹配于索引签名 66 | ``` 67 | 68 | ## 用例:React State 69 | 70 | Facebook ReactJS 为对象的 Freshness 提供了一个很好的用例,通常在组件中,你只使用少量属性,而不是传入所有,来调用 `setState`: 71 | 72 | ```ts 73 | // 假设 74 | interface State { 75 | foo: string; 76 | bar: string; 77 | } 78 | 79 | // 你可能想做: 80 | this.setState({ foo: 'Hello' }); // Error: 没有属性 'bar' 81 | 82 | // 因为 state 包含 'foo' 与 'bar',TypeScript 会强制你这么做: 83 | this.setState({ foo: 'Hello', bar: this.state.bar }); 84 | ``` 85 | 86 | 如果你想使用 Freshness,你可能需要将所有成员标记为可选,这仍然会捕捉到拼写错误: 87 | 88 | ```ts 89 | // 假设 90 | interface State { 91 | foo?: string; 92 | bar?: string; 93 | } 94 | 95 | // 你可能想做 96 | this.setState({ foo: 'Hello' }); // Yay works fine! 97 | 98 | // 由于 Freshness,你也可以防止错别字 99 | this.setState({ foos: 'Hello' }}; // Error: 对象只能指定已知属性 100 | 101 | // 仍然会有类型检查 102 | this.setState({ foo: 123 }); // Error: 无法将 number 类型赋值给 string 类型 103 | ``` 104 | -------------------------------------------------------------------------------- /docs/tips/nominalTyping.md: -------------------------------------------------------------------------------- 1 | # 名义化类型 2 | 3 | TypeScript 的类型系统是结构化的,[这也是其主要的优点之一](https://basarat.gitbooks.io/typescript/content/docs/why-typescript.html)。然而,在实际的特定用例中,有时尽管变量具有相同的结构,你也想将他们视为不同类型。一个非常常见的用例是身份类型结构(它们可能只是在 C# 或者 Java 中表示一个它们语义化名字的字符串)。 4 | 5 | 这有一些社区使用的方式,我按照个人喜好降序排列: 6 | 7 | ## 使用字面量类型 8 | 9 | 这种模式使用泛型和字面量类型: 10 | 11 | ```ts 12 | // 泛型 Id 类型 13 | type Id = { 14 | type: T; 15 | value: string; 16 | }; 17 | 18 | // 特殊的 Id 类型 19 | type FooId = Id<'foo'>; 20 | type BarId = Id<'bar'>; 21 | 22 | // 可选:构造函数 23 | const createFoo = (value: string): FooId => ({ type: 'foo', value }); 24 | const createBar = (value: string): BarId => ({ type: 'bar', value }); 25 | 26 | let foo = createFoo('sample'); 27 | let bar = createBar('sample'); 28 | 29 | foo = bar; // Error 30 | foo = foo; // Okey 31 | ``` 32 | 33 | - 优点 34 | - 不需要类型断言。 35 | - 缺点 36 | - 如上结构 `{type,value}` 可能不那么尽如人意,而且需要服务器序列化支持。 37 | 38 | ## 使用枚举 39 | 40 | TypeScript 中[枚举](../typings/enums.md) 提供一定程度的名义化类型。如果两个枚举的命名不相同,则它们类型不相等。我们可以利用这个事实来为结构上兼容的类型,提供名义化类型。 41 | 42 | 解决办法包括: 43 | 44 | - 创建一个只有名字的枚举; 45 | - 利用这个枚举与实际结构体创建一个交叉类型(`&`)。 46 | 47 | 如下所示,当实际结构体仅仅是一个字符串时: 48 | 49 | ```ts 50 | // FOO 51 | enum FooIdBrand { 52 | _ = '' 53 | } 54 | type FooId = FooIdBrand & string; 55 | 56 | // BAR 57 | enum BarIdBrand { 58 | _ = '' 59 | } 60 | type BarId = BarIdBrand & string; 61 | 62 | // user 63 | 64 | let fooId: FooId; 65 | let barId: BarId; 66 | 67 | // 类型安全 68 | fooId = barId; // error 69 | barId = fooId; // error 70 | 71 | // 创建一个新的 72 | fooId = 'foo' as FooId; 73 | barId = 'bar' as BarId; 74 | 75 | // 两种类型都与基础兼容 76 | let str: string; 77 | str = fooId; 78 | str = barId; 79 | ``` 80 | 81 | 请注意上文中的 `FooIdBrand` 与 `BarIdBrand`,它们都有一个 `_` 映射到空字符串的成员,即 `{ _ = '' }`。这可以强制 TypeScript 推断出这是一个基于字符串的枚举,而不是一个数字类型的枚举。这是很重要的,因为 TypeScript 会把一个空的枚举类型(`{}`)推断为一个数字类型的枚举,在 TypeScript 3.6.2 版本及其以上时,数字类型的枚举与 `string` 的交叉类型是 `never`。 82 | 83 | ## 使用接口 84 | 85 | 因为 `number` 类型与 `enum` 类型在类型上是兼容的,因此我们不能使用上述提到的方法来处理它们。取而代之,我们可以使用接口打破这种类型的兼容性。TypeScript 编译团队仍然在使用这种方法,因此它值得一提。使用 `_` 前缀和 `Brand` 后缀是一种我强烈推荐的惯例方法([TypeScript 也这么推荐](https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types.ts#L693-L698))。 86 | 87 | 解决办法包括: 88 | 89 | - 在类型上添加一个不用的属性,用来打破类型兼容性; 90 | - 在新建或向下转换类型的时候使用断言。 91 | 92 | 如下所示: 93 | 94 | ```ts 95 | // FOO 96 | interface FooId extends String { 97 | _fooIdBrand: string; // 防止类型错误 98 | } 99 | 100 | // BAR 101 | interface BarId extends String { 102 | _barIdBrand: string; // 防止类型错误 103 | } 104 | 105 | // 使用 106 | let fooId: FooId; 107 | let barId: BarId; 108 | 109 | // 类型安全 110 | fooId = barId; // error 111 | barId = fooId; // error 112 | fooId = barId; // error 113 | barId = fooId; // error 114 | 115 | // 创建新的 116 | fooId = 'foo' as any; 117 | barId = 'bar' as any; 118 | 119 | // 如果你需要以字符串作为基础 120 | var str: string; 121 | str = fooId as any; 122 | str = barId as any; 123 | ``` 124 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/SidebarLink.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 110 | -------------------------------------------------------------------------------- /docs/typings/migrating.md: -------------------------------------------------------------------------------- 1 | # 从 JavaScript 迁移 2 | 3 | 首先,假设如下: 4 | 5 | - 你了解 JavaScript; 6 | - 你了解在项目中常用的方式和构建工具(如:webpack)。 7 | 8 | 有了以上假设,一般来说,将 JavaScript 代码迁移至 TypeScript 包括以下步骤: 9 | 10 | - 添加一个 `tsconfig.json` 文件; 11 | - 把文件扩展名从 `.js` 改成 `.ts`,开始使用 `any` 来减少错误; 12 | - 开始在 TypeScript 中写代码,尽可能的减少 `any` 的使用; 13 | - 回到旧代码,开始添加类型注解,并修复已识别的错误; 14 | - 为第三方 JavaScript 代码定义环境声明。 15 | 16 | 让我们进一步讨论其中的几个关键点。 17 | 18 | 记住:所有的 JavaScript 代码都是有效的 TypeScript 代码。这意味着,如果让 TypeScript 编译器编译 TypeScript 里的 JavaScript 代码,编译后的结果将会与原始的 JavaScript 代码一模一样。也就是说,把文件扩展名从 `.js` 改成 `.ts` 将不会造成任何负面的影响。 19 | 20 | ## 减少错误 21 | 22 | 代码被迁移至 TypeScript 后,TypeScript 将会立即对你的代码进行类型检查,你的 JavaScript 代码可能并不像想象中那样整齐了,因此你可能会收到一些报错信息。这时,可以使用 `any` 来解决大部分的报错问题: 23 | 24 | ```typescript 25 | let foo = 123; 26 | let bar = 'hey'; 27 | 28 | bar = foo; // Error: 不能把 number 类型赋值给 string 类型 29 | ``` 30 | 31 | 虽然这些错误是有效的,并且在大多数情况下,根据这些错误所推断出的信息比代码库的不同部分的原始作者想象的更好,但是你的重点是在逐步更新旧代码库的同时,用 TypeScript 编写新代码。在这里,你可以使用类型断言来减少此错误: 32 | 33 | ```typescript 34 | let foo = 123; 35 | let bar = 'hey'; 36 | 37 | bar = foo as any; // ok 38 | ``` 39 | 40 | 从另一方面来说,你可能想用 `any` 用作类型注解: 41 | 42 | ```typescript 43 | function foo() { 44 | return 1; 45 | } 46 | 47 | let bar = 'hey'; 48 | bar = foo(); // Error: 不能把一个 number 类型赋值给 string 类型 49 | ``` 50 | 51 | 减少这种错误: 52 | 53 | ```typescript 54 | function foo(): any { 55 | // 添加 'any' 56 | return 1; 57 | } 58 | 59 | let bar = 'hey'; 60 | bar = foo(); 61 | ``` 62 | 63 | ::: warning NOTICE 64 | 使用此种方式来减少错误是危险的,但是它允许你将注意力转移到你的新 TypeScript 代码错误上。当你进行下一步前,最好要留下 `// TODO` 的注释。 65 | ::: 66 | 67 | ## 第三方代码 68 | 69 | 你可以将你的 JavaScript 代码改成 TypeScript 代码,但是你不能让整个世界都使用 TypeScript。这正是 TypeScript 环境声明支持的地方。我建议你以创建一个 `vendor.d.ts` 文件作为开始(`.d.ts` 文件扩展名指定这个文件是一个声明文件),然后我向文件里添加东西。或者,你也可以创建一个针对于特定库的声明文件,如为 jquery 创建 `jquery.d.ts` 文件。 70 | 71 | ::: tip NOTICE 72 | 几乎排名前 90% 的 JavaScript 库的声明文件存在于 [DefinitelyTyped](https://github.com/borisyankov/DefinitelyTyped) 仓库里,在创建自己定义的声明文件之前,我们建议你先去仓库中寻找是否有对应的声明文件。尽管如此,创建一个声明文件这种快速但不好的方式是减小使用 TypeScript 初始阻力的重要步骤 73 | ::: 74 | 75 | 根据 `jquery` 的使用,你可以非常简单快速的为它创建一个定义: 76 | 77 | ```typescript 78 | declare var $: any; 79 | ``` 80 | 81 | 有时,你可能想在某些内容(如 `jQuery`)上添加显式的注解,并且你会在类型声明空间中使用它。你可以通过 `type` 关键字快速的实现它: 82 | 83 | ```typescript 84 | declare type JQuery = any; 85 | declare var $: JQuery; 86 | ``` 87 | 88 | 这提供给你一个更清晰的使用模式。 89 | 90 | 一个高质量的 `jquery.d.ts` 已经在 [DefinitelyTyped](https://github.com/borisyankov/DefinitelyTyped) 中存在。现在你已经知道如何在使用第三方 JavaScript 模块时,快速克服从 JavaScript 至 TypeScript 的阻力了。在接下去的章节,我们将会讨论环境声明。 91 | 92 | ## 第三方的 NPM 模块 93 | 94 | 与全局变量声明相似,你可以快速的定义一个全局模块,如:对于 `jquery`,如果你想把它作为一个模块来使用([NPM](https://www.npmjs.com/package/jquery)),可以自己通过以下方式实现: 95 | 96 | ```typescript 97 | declare module 'jquery'; 98 | ``` 99 | 100 | 然后你就可以在必要时导入它: 101 | 102 | ```typescript 103 | import * as $ from 'jquery'; 104 | ``` 105 | 106 | ::: tip 107 | 再一次说明,一个高质量的 `jquery.d.ts` 已经在 [DefinitelyTyped](https://github.com/borisyankov/DefinitelyTyped) 中存在,但是可能在你的包里没有,那么,你现在有一个简单快速的方式来继续迁移。 108 | ::: 109 | 110 | ## 额外的非 JavaScript 资源 111 | 112 | 在 TypeScript 中,甚至可以允许你导入任何文件,例如 `.css` 文件(如果你使用的是 webpack 样式加载器或 css 模块),你只要添加如下代码(放在 `global.d.ts`): 113 | 114 | ```typescript 115 | declare module '*.css'; 116 | ``` 117 | 118 | 现在你可以使用 `import * as foo from './some/file.css'`。 119 | 120 | 与此相似,如果你想使用 html 模版(例如:angular),你可以: 121 | 122 | ```typescript 123 | declare module '*.html'; 124 | ``` 125 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/code.styl: -------------------------------------------------------------------------------- 1 | .content 2 | code 3 | color lighten($textColor, 20%) 4 | padding 0.25rem 0.5rem 5 | margin 0 6 | font-size 0.85em 7 | background-color rgba(27,31,35,0.05) 8 | border-radius 3px 9 | .token 10 | &.deleted 11 | color #EC5975 12 | &.inserted 13 | color $accentColor 14 | 15 | .content 16 | pre, pre[class*="language-"] 17 | line-height 1.4 18 | padding 1.25rem 1.5rem 19 | margin 0.85rem 0 20 | background-color $codeBgColor 21 | border-radius 6px 22 | overflow auto 23 | code 24 | color #fff 25 | padding 0 26 | background-color transparent 27 | border-radius 0 28 | 29 | div[class*="language-"] 30 | position relative 31 | background-color $codeBgColor 32 | border-radius 6px 33 | .highlight-lines 34 | user-select none 35 | padding-top 1.3rem 36 | position absolute 37 | top 0 38 | left 0 39 | width 100% 40 | line-height 1.4 41 | .highlighted 42 | background-color rgba(0, 0, 0, 66%) 43 | pre, pre[class*="language-"] 44 | background transparent 45 | position relative 46 | z-index 1 47 | &::before 48 | position absolute 49 | z-index 3 50 | top 0.8em 51 | right 1em 52 | font-size 0.75rem 53 | color rgba(255, 255, 255, 0.4) 54 | &:not(.line-numbers-mode) 55 | .line-numbers-wrapper 56 | display none 57 | &.line-numbers-mode 58 | .highlight-lines .highlighted 59 | position relative 60 | &:before 61 | content ' ' 62 | position absolute 63 | z-index 3 64 | left 0 65 | top 0 66 | display block 67 | width $lineNumbersWrapperWidth 68 | height 100% 69 | background-color rgba(0, 0, 0, 66%) 70 | pre 71 | padding-left $lineNumbersWrapperWidth + 1 rem 72 | vertical-align middle 73 | .line-numbers-wrapper 74 | position absolute 75 | top 0 76 | width $lineNumbersWrapperWidth 77 | text-align center 78 | color rgba(255, 255, 255, 0.3) 79 | padding 1.25rem 0 80 | line-height 1.4 81 | br 82 | user-select none 83 | .line-number 84 | position relative 85 | z-index 4 86 | user-select none 87 | font-size 0.85em 88 | &::after 89 | content '' 90 | position absolute 91 | z-index 2 92 | top 0 93 | left 0 94 | width $lineNumbersWrapperWidth 95 | height 100% 96 | border-radius 6px 0 0 6px 97 | border-right 1px solid rgba(0, 0, 0, 66%) 98 | background-color $codeBgColor 99 | 100 | 101 | for lang in $codeLang 102 | div{'[class~="language-' + lang + '"]'} 103 | &:before 104 | content ('' + lang) 105 | 106 | div[class~="language-javascript"] 107 | &:before 108 | content "js" 109 | 110 | div[class~="language-typescript"] 111 | &:before 112 | content "ts" 113 | 114 | div[class~="language-markup"] 115 | &:before 116 | content "html" 117 | 118 | div[class~="language-markdown"] 119 | &:before 120 | content "md" 121 | 122 | div[class~="language-json"]:before 123 | content "json" 124 | 125 | div[class~="language-ruby"]:before 126 | content "rb" 127 | 128 | div[class~="language-python"]:before 129 | content "py" 130 | 131 | div[class~="language-bash"]:before 132 | content "sh" 133 | 134 | div[class~="language-php"]:before 135 | content "php" 136 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/SidebarGroup.vue: -------------------------------------------------------------------------------- 1 | 2 | 12 | 22 | {{ item.title }} 23 | 27 | 28 | 29 | 30 | 36 | {{ item.title }} 37 | 41 | 42 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | 56 | 71 | 72 | 130 | -------------------------------------------------------------------------------- /docs/compiler/parser.md: -------------------------------------------------------------------------------- 1 | # 解析器 2 | 3 | TypeScript 解析器代码均位于 `parser.ts` 中。在内部,由解析器控制扫描器将源码转化为 AST。其期望结果如下: 4 | 5 | ``` 6 | 源码 ~~ 扫描器 ~~> Token 流 ~~ 解析器 ~~> AST 7 | ``` 8 | 9 | 解析器实现原理是单例模式(其原因类似扫描器,如果能重新初始化就不重新构建)。实际实现成 `namespace Parser`,包含解析器的各种*状态*变量和单例扫描器(`const scanner`)。该扫描器由解析器函数管理。 10 | 11 | ### 程序对解析器的使用 12 | 13 | 解析器由程序间接驱动(通过之前提到过的 `CompilerHost`)。基本上,简化的调用栈如下所示: 14 | 15 | ``` 16 | 程序 -> 17 | CompilerHost.getSourceFile -> 18 | (全局函数 parser.ts).createSourceFile -> 19 | Parser.parseSourceFile 20 | ``` 21 | 22 | `parseSourceFile` 不仅准备好解析器的状态,还调用 `initializeState` 准备好扫描器的状态。然后使用 `parseSourceFileWorker` 继续解析源代码。 23 | 24 | ### 使用示例 25 | 26 | 深入解析器的内部之前,这里有个使用 TypeScript 解析器的示例,(使用 `ts.createSourceFile`)获取一个源文件的 AST 并打印它。 27 | 28 | `code/compiler/parser/runParser.ts` 29 | 30 | ```ts 31 | import * as ts from 'ntypescript'; 32 | 33 | function printAllChildren(node: ts.Node, depth = 0) { 34 | console.log(new Array(depth + 1).join('----'), ts.formatSyntaxKind(node.kind), node.pos, node.end); 35 | depth++; 36 | node.getChildren().forEach(c => printAllChildren(c, depth)); 37 | } 38 | 39 | var sourceCode = ` 40 | var foo = 123; 41 | `.trim(); 42 | 43 | var sourceFile = ts.createSourceFile('foo.ts', sourceCode, ts.ScriptTarget.ES5, true); 44 | printAllChildren(sourceFile); 45 | ``` 46 | 47 | 该段代码会打印以下内容: 48 | 49 | ```ts 50 | SourceFile 0 14 51 | ---- SyntaxList 0 14 52 | -------- VariableStatement 0 14 53 | ------------ VariableDeclarationList 0 13 54 | ---------------- VarKeyword 0 3 55 | ---------------- SyntaxList 3 13 56 | -------------------- VariableDeclaration 3 13 57 | ------------------------ Identifier 3 7 58 | ------------------------ FirstAssignment 7 9 59 | ------------------------ FirstLiteralToken 9 13 60 | ------------ SemicolonToken 13 14 61 | ---- EndOfFileToken 14 14 62 | ``` 63 | 64 | 如果把头向左倾,这个看起来像棵(右侧)树 65 | 66 | ## 解析器函数 67 | 68 | 如前所述,`parseSourceFile` 设置初始状态并将工作交给 `parseSourceFileWorker` 函数。 69 | 70 | ### `parseSourceFileWorker` 71 | 72 | 该函数先创建一个 `SourceFile` AST 节点,然后从 `parseStatements` 函数开始解析源代码。一旦返回结果,就用额外信息(例如 `nodeCount`, `identifierCount`等) 完善 `SourceFile` 节点。 73 | 74 | ### `parseStatements` 75 | 76 | 是最重要的 `parseXXX` 系函数之一(概念接下来介绍)。它根据扫描器返回的当前 token 来切换(调用相应的 `parseXXX` 函数),例如:如果当前 token 是一个 `SemicolonToken`(分号标记),就会调用 `paserEmptyStatement` 为空语句创建一个 AST 节点。 77 | 78 | ### 节点创建 79 | 80 | 解析器有一系列 `parseXXX` 函数用来创建相应类型为`XXX`的节点,通常在相应类型的节点出现时被(其他解析器函数)调用。该过程的典型示例是解析空语句(例如 `;;;;;;`)时要用的 `parseEmptyStatement()` 函数。下面是其全部代码: 81 | 82 | ```ts 83 | function parseEmptyStatement(): Statement { 84 | let node = createNode(SyntaxKind.EmptyStatement); 85 | parseExpected(SyntaxKind.SemicolonToken); 86 | return finishNode(node); 87 | } 88 | ``` 89 | 90 | 它展示了 3 个关键函数 `createNode`, `parseExpected` 和 `finishNode`. 91 | 92 | #### `createNode` 93 | 94 | 解析器函数 `function createNode(kind: SyntaxKind, pos?: number): Node` 负责创建节点,设置传入的 `SyntaxKind`(语法类别),和初始位置(默认使用当前扫描器状态提供的位置信息)。 95 | 96 | #### `parseExpected` 97 | 98 | 解析器的 `parseExpected` 函数 `function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage): boolean` 会检查解析器状态中的当前 token 是否与指定的 `SyntaxKind` 匹配。如果不匹配,则会向传入的 `diagnosticMessage`(诊断消息)报告,未传入则创建某种通用形式 `xxx expected`。该函数内部用 `parseErrorAtPosition` 函数(使用扫描位置)提供良好的错误报告。 99 | 100 | #### `finishNode` 101 | 102 | 解析器的 `finishNode` 函数 `function finishNode(node: T, end?: number): T` 设置节点的 `end` 位置,并添加一些有用的信息,例如上下文标志(`parserContextFlags`)以及解析该节点前出现的错误(如果有错的话,就不能在增量解析中重用此 AST 节点)。 103 | -------------------------------------------------------------------------------- /docs/typings/typeGuard.md: -------------------------------------------------------------------------------- 1 | # 类型保护 2 | 3 | 类型保护允许你使用更小范围下的对象类型。 4 | 5 | ## typeof 6 | 7 | TypeScript 熟知 JavaScript 中 `instanceof` 和 `typeof` 运算符的用法。如果你在一个条件块中使用这些,TypeScript 将会推导出在条件块中的的变量类型。如下例所示,TypeScript 将会辨别 `string` 上是否存在特定的函数,以及是否发生了拼写错误: 8 | 9 | ```ts 10 | function doSome(x: number | string) { 11 | if (typeof x === 'string') { 12 | // 在这个块中,TypeScript 知道 `x` 的类型必须是 `string` 13 | console.log(x.subtr(1)); // Error: 'subtr' 方法并没有存在于 `string` 上 14 | console.log(x.substr(1)); // ok 15 | } 16 | 17 | x.substr(1); // Error: 无法保证 `x` 是 `string` 类型 18 | } 19 | ``` 20 | 21 | ## instanceof 22 | 23 | 这有一个关于 `class` 和 `instanceof` 的例子: 24 | 25 | ```ts 26 | class Foo { 27 | foo = 123; 28 | common = '123'; 29 | } 30 | 31 | class Bar { 32 | bar = 123; 33 | common = '123'; 34 | } 35 | 36 | function doStuff(arg: Foo | Bar) { 37 | if (arg instanceof Foo) { 38 | console.log(arg.foo); // ok 39 | console.log(arg.bar); // Error 40 | } 41 | if (arg instanceof Bar) { 42 | console.log(arg.foo); // Error 43 | console.log(arg.bar); // ok 44 | } 45 | } 46 | 47 | doStuff(new Foo()); 48 | doStuff(new Bar()); 49 | ``` 50 | 51 | TypeScript 甚至能够理解 `else`。当你使用 `if` 来缩小类型时,TypeScript 知道在其他块中的类型并不是 `if` 中的类型: 52 | 53 | ```ts 54 | class Foo { 55 | foo = 123; 56 | } 57 | 58 | class Bar { 59 | bar = 123; 60 | } 61 | 62 | function doStuff(arg: Foo | Bar) { 63 | if (arg instanceof Foo) { 64 | console.log(arg.foo); // ok 65 | console.log(arg.bar); // Error 66 | } else { 67 | // 这个块中,一定是 'Bar' 68 | console.log(arg.foo); // Error 69 | console.log(arg.bar); // ok 70 | } 71 | } 72 | 73 | doStuff(new Foo()); 74 | doStuff(new Bar()); 75 | ``` 76 | 77 | ## in 78 | 79 | `in` 操作符可以安全的检查一个对象上是否存在一个属性,它通常也被作为类型保护使用: 80 | 81 | ```ts 82 | interface A { 83 | x: number; 84 | } 85 | 86 | interface B { 87 | y: string; 88 | } 89 | 90 | function doStuff(q: A | B) { 91 | if ('x' in q) { 92 | // q: A 93 | } else { 94 | // q: B 95 | } 96 | } 97 | ``` 98 | 99 | ## 字面量类型保护 100 | 101 | 当你在联合类型里使用字面量类型时,你可以检查它们是否有区别: 102 | 103 | ```ts 104 | type Foo = { 105 | kind: 'foo'; // 字面量类型 106 | foo: number; 107 | }; 108 | 109 | type Bar = { 110 | kind: 'bar'; // 字面量类型 111 | bar: number; 112 | }; 113 | 114 | function doStuff(arg: Foo | Bar) { 115 | if (arg.kind === 'foo') { 116 | console.log(arg.foo); // ok 117 | console.log(arg.bar); // Error 118 | } else { 119 | // 一定是 Bar 120 | console.log(arg.foo); // Error 121 | console.log(arg.bar); // ok 122 | } 123 | } 124 | ``` 125 | 126 | ## 使用定义的类型保护 127 | 128 | JavaScript 并没有内置非常丰富的、运行时的自我检查机制。当你在使用普通的 JavaScript 对象时(使用结构类型,更有益处),你甚至无法访问 `instanceof` 和 `typeof`。在这种情景下,你可以创建*用户自定义的类型保护函数*,这仅仅是一个返回值为类似于`someArgumentName is SomeType` 的函数,如下: 129 | 130 | ```ts 131 | // 仅仅是一个 interface 132 | interface Foo { 133 | foo: number; 134 | common: string; 135 | } 136 | 137 | interface Bar { 138 | bar: number; 139 | common: string; 140 | } 141 | 142 | // 用户自己定义的类型保护! 143 | function isFoo(arg: Foo | Bar): arg is Foo { 144 | return (arg as Foo).foo !== undefined; 145 | } 146 | 147 | // 用户自己定义的类型保护使用用例: 148 | function doStuff(arg: Foo | Bar) { 149 | if (isFoo(arg)) { 150 | console.log(arg.foo); // ok 151 | console.log(arg.bar); // Error 152 | } else { 153 | console.log(arg.foo); // Error 154 | console.log(arg.bar); // ok 155 | } 156 | } 157 | 158 | doStuff({ foo: 123, common: '123' }); 159 | doStuff({ bar: 123, common: '123' }); 160 | ``` 161 | -------------------------------------------------------------------------------- /docs/typings/exceptionsHanding.md: -------------------------------------------------------------------------------- 1 | # 异常处理 2 | 3 | JavaScript 有一个 `Error` 类,用于处理异常。你可以通过 `throw` 关键字来抛出一个错误。然后通过 `try/catch` 块来捕获此错误: 4 | 5 | ```ts 6 | try { 7 | throw new Error('Something bad happened'); 8 | } catch (e) { 9 | console.log(e); 10 | } 11 | ``` 12 | 13 | ## 错误子类型 14 | 15 | 除内置的 `Error` 类外,还有一些额外的内置错误,它们继承自 `Error` 类: 16 | 17 | ### RangeError 18 | 19 | 当数字类型变量或者参数超出其有效范围时,出现 `RangeError` 的错误提示: 20 | 21 | ```ts 22 | // 使用过多参数调用 console 23 | console.log.apply(console, new Array(1000000000)); // RangeError: 数组长度无效 24 | ``` 25 | 26 | ### ReferenceError 27 | 28 | 当引用无效时,会出现 `ReferenceError` 的错误提示: 29 | 30 | ```ts 31 | 'use strict'; 32 | console.log(notValidVar); // ReferenceError: notValidVar 未定义 33 | ``` 34 | 35 | ### SyntaxError 36 | 37 | 当解析无效 JavaScript 代码时,会出现 `SyntaxError` 的错误提示: 38 | 39 | ```ts 40 | 1 *** 3 // SyntaxError: 无效的标记 * 41 | ``` 42 | 43 | ### TypeError 44 | 45 | 变量或者参数不是有效类型时,会出现 `TypeError` 的错误提示: 46 | 47 | ```ts 48 | '1.2'.toPrecision(1); // TypeError: '1.2'.toPrecision 不是函数。 49 | ``` 50 | 51 | ### URIError 52 | 53 | 当传入无效参数至 `encodeURI()` 和 `decodeURI()` 时,会出现 `URIError` 的错误提示: 54 | 55 | ```ts 56 | decodeURI('%'); // URIError: URL 异常 57 | ``` 58 | 59 | ## 使用 `Error` 60 | 61 | JavaScript 初学者可能有时候仅仅是抛出一个原始字符串: 62 | 63 | ```ts 64 | try { 65 | throw 'Something bad happened'; 66 | } catch (e) { 67 | console.log(e); 68 | } 69 | ``` 70 | 71 | **不要这么做**,使用 `Error` 对象的基本好处是,它能自动跟踪堆栈的属性构建以及生成位置。 72 | 73 | 原始字符串会导致极差的调试体验,并且在分析日志时,将会变得错综复杂。 74 | 75 | ## 你并不需要 `throw` 抛出一个错误 76 | 77 | 传递一个 `Error` 对象是没问题的,这种方式在 `Node.js` 回调函数中非常常见,它用第一个参数作为错误对象进行回调处理。 78 | 79 | ```ts 80 | function myFunction (callback: (e: Error)) { 81 | doSomethingAsync(function () { 82 | if (somethingWrong) { 83 | callback(new Error('This is my error')); 84 | } else { 85 | callback(); 86 | } 87 | }) 88 | } 89 | ``` 90 | 91 | ## 优秀的用例 92 | 93 | 「Exceptions should be exceptional」是计算机科学中常用用语。这里有一些原因说明在 JavaScript(TypeScript) 中也是如此。 94 | 95 | ### 不清楚从哪里抛出错误 96 | 97 | 考虑如下代码块: 98 | 99 | ```ts 100 | try { 101 | const foo = runTask1(); 102 | const bar = runTask2(); 103 | } catch (e) { 104 | console.log('Error:', e); 105 | } 106 | ``` 107 | 108 | 下一个开发者可能并不清楚哪个函数可能会抛出错误。在没有阅读 `task1/task2` 代码以及他们可能会调用的函数时,对代码 `review` 的人员可能也不会知道错误会从哪里抛出。 109 | 110 | ### 优雅的捕获错误 111 | 112 | 你可以通过为每个可能抛出错误的代码显式捕获,来使其优雅: 113 | 114 | ```ts 115 | try { 116 | const foo = runTask1(); 117 | } catch (e) { 118 | console.log('Error:', e); 119 | } 120 | 121 | try { 122 | const bar = runTask2(); 123 | } catch (e) { 124 | console.log('Error:', e); 125 | } 126 | ``` 127 | 128 | 但是现在,如果你想从第一个任务中传递变量到第二个任务中,代码会变的混乱(注意:foo 变量需要用 let 显式注解它,因为它不能从 `runTask1` 中返回出来): 129 | 130 | ```ts 131 | let foo: number; // Notice 使用 let 并且显式注明类型注解 132 | 133 | try { 134 | foo = runTask1(); 135 | } catch (e) { 136 | console.log('Error:', e); 137 | } 138 | 139 | try { 140 | const bar = runTask2(foo); 141 | } catch (e) { 142 | console.log('Error:', e); 143 | } 144 | ``` 145 | 146 | ### 没有在类型系统中很好的表示 147 | 148 | 考虑如下函数: 149 | 150 | ```ts 151 | function validate(value: number) { 152 | if (value < 0 || value > 100) { 153 | throw new Error('Invalid value'); 154 | } 155 | } 156 | ``` 157 | 158 | 在这种情境下使用 `Error` 不是一个好的主意。因为没有用来验证函数的类型定义(如:`(value: number) => void`),取而代之一个更好的方式是创建一个验证方法: 159 | 160 | ```ts 161 | function validate( 162 | value: number 163 | ): { 164 | error?: string; 165 | } { 166 | if (value < 0 || value > 100) { 167 | return { error: 'Invalid value' }; 168 | } 169 | } 170 | ``` 171 | 172 | 现在它具有类型定义了。 173 | 174 | ::: tip 175 | 除非你想用以非常通用(try/catch)的方式处理错误,否则不要抛出错误。 176 | ::: 177 | -------------------------------------------------------------------------------- /docs/typings/functions.md: -------------------------------------------------------------------------------- 1 | # 函数 2 | 3 | 函数类型在 TypeScript 类型系统中扮演着非常重要的角色,它们是可组合系统的核心构建块。 4 | 5 | ## 参数注解 6 | 7 | 你可以注解函数参数,就像你可以注解其他变量一样: 8 | 9 | ```ts 10 | // variable annotation 11 | let sampleVariable: { bar: number }; 12 | 13 | // function parameter annotation 14 | function foo(sampleParameter: { bar: number }) {} 15 | ``` 16 | 17 | 这里我们使用了内联类型注解,除此之外,你还可以使用接口等其他方式。 18 | 19 | ### 返回类型注解 20 | 21 | 你可以在函数参数列表之后使用与变量相同的样式来注解返回类型,如例子中 `:Foo`: 22 | 23 | ```ts 24 | interface Foo { 25 | foo: string; 26 | } 27 | 28 | // Return type annotated as `: Foo` 29 | function foo(sample: Foo): Foo { 30 | return sample; 31 | } 32 | ``` 33 | 34 | 我们在这里使用了一个 `interface`,但你可以自由地使用其他注解方式,例如内联注解。 35 | 36 | 通常,你不*需要*注解函数的返回类型,因为它可以由编译器推断: 37 | 38 | ```ts 39 | interface Foo { 40 | foo: string; 41 | } 42 | 43 | function foo(sample: Foo) { 44 | return sample; // inferred return type 'Foo' 45 | } 46 | ``` 47 | 48 | 但是,添加这些注解以帮助解决错误提示通常是一个好主意,例如: 49 | 50 | ```ts 51 | function foo() { 52 | return { fou: 'John Doe' }; // You might not find this misspelling of `foo` till it's too late 53 | } 54 | 55 | sendAsJSON(foo()); 56 | ``` 57 | 58 | 如果你不打算从函数返回任何内容,则可以将其标注为:`void` 。你通常可以删除 `void`, TypeScript 能推导出来: 59 | 60 | ### 可选参数 61 | 62 | 你可以将参数标记为可选: 63 | 64 | ```ts 65 | function foo(bar: number, bas?: string): void { 66 | // .. 67 | } 68 | 69 | foo(123); 70 | foo(123, 'hello'); 71 | ``` 72 | 73 | 或者,当调用者没有提供该参数时,你可以提供一个默认值(在参数声明后使用 `= someValue` ): 74 | 75 | ```ts 76 | function foo(bar: number, bas: string = 'hello') { 77 | console.log(bar, bas); 78 | } 79 | 80 | foo(123); // 123, hello 81 | foo(123, 'world'); // 123, world 82 | ``` 83 | 84 | ### 重载 85 | 86 | TypeScript 允许你声明函数重载。这对于文档 + 类型安全来说很实用。请思考以下代码: 87 | 88 | ```ts 89 | function padding(a: number, b?: number, c?: number, d?: any) { 90 | if (b === undefined && c === undefined && d === undefined) { 91 | b = c = d = a; 92 | } else if (c === undefined && d === undefined) { 93 | c = a; 94 | d = b; 95 | } 96 | return { 97 | top: a, 98 | right: b, 99 | bottom: c, 100 | left: d 101 | }; 102 | } 103 | ``` 104 | 105 | 如果仔细查看代码,就会发现 a,b,c,d 的值会根据传入的参数数量而变化。此函数也只需要 1 个,2 个或 4 个参数。可以使用函数重载来*强制*和*记录*这些约束。你只需多次声明函数头。最后一个函数头是在函数体内实际处于活动状态但不可用于外部。 106 | 107 | 如下所示: 108 | 109 | ```ts 110 | // 重载 111 | function padding(all: number); 112 | function padding(topAndBottom: number, leftAndRight: number); 113 | function padding(top: number, right: number, bottom: number, left: number); 114 | // Actual implementation that is a true representation of all the cases the function body needs to handle 115 | function padding(a: number, b?: number, c?: number, d?: number) { 116 | if (b === undefined && c === undefined && d === undefined) { 117 | b = c = d = a; 118 | } else if (c === undefined && d === undefined) { 119 | c = a; 120 | d = b; 121 | } 122 | return { 123 | top: a, 124 | right: b, 125 | bottom: c, 126 | left: d 127 | }; 128 | } 129 | ``` 130 | 131 | 这里前三个函数头可有效调用 `padding`: 132 | 133 | ```ts 134 | padding(1); // Okay: all 135 | padding(1, 1); // Okay: topAndBottom, leftAndRight 136 | padding(1, 1, 1, 1); // Okay: top, right, bottom, left 137 | 138 | padding(1, 1, 1); // Error: Not a part of the available overloads 139 | ``` 140 | 141 | 当然,最终声明(从函数内部看到的真正声明)与所有重载兼容是很重要的。这是因为这是函数体需要考虑的函数调用的真实性质。 142 | 143 | ::: tip 144 | TypeScript 中的函数重载没有任何运行时开销。它只允许你记录希望调用函数的方式,并且编译器会检查其余代码。 145 | ::: 146 | 147 | ### 函数声明 148 | 149 | > 快速开始:类型注解是你描述现有实现类型的一种方式 150 | 151 | 在没有提供函数实现的情况下,有两种声明函数类型的方式: 152 | 153 | ```ts 154 | type LongHand = { 155 | (a: number): number; 156 | }; 157 | 158 | type ShortHand = (a: number) => number; 159 | ``` 160 | 161 | 上面代码中的两个例子完全相同。但是,当你想使用函数重载时,只能用第一种方式: 162 | 163 | ```ts 164 | type LongHandAllowsOverloadDeclarations = { 165 | (a: number): number; 166 | (a: string): string; 167 | }; 168 | ``` 169 | -------------------------------------------------------------------------------- /docs/typings/typeInference.md: -------------------------------------------------------------------------------- 1 | # 类型推断 2 | 3 | TypeScript 能根据一些简单的规则推断(检查)变量的类型,你可以通过实践,很快的了解它们。 4 | 5 | ## 定义变量 6 | 7 | 变量的类型,由定义推断: 8 | 9 | ```ts 10 | let foo = 123; // foo 是 'number' 11 | let bar = 'hello'; // bar 是 'string' 12 | 13 | foo = bar; // Error: 不能将 'string' 赋值给 `number` 14 | ``` 15 | 16 | 这是一个从右向左流动类型的示例。 17 | 18 | ## 函数返回类型 19 | 20 | 返回类型能被 `return` 语句推断,如下所示,推断函数返回为一个数字: 21 | 22 | ```ts 23 | function add(a: number, b: number) { 24 | return a + b; 25 | } 26 | ``` 27 | 28 | 这是一个从底部流出类型的例子。 29 | 30 | ## 赋值 31 | 32 | 函数参数类型/返回值也能通过赋值来推断。如下所示,`foo` 的类型是 `Adder`,他能让 `foo` 的参数 `a`、`b` 是 `number` 类型。 33 | 34 | ```ts 35 | type Adder = (a: number, b: number) => number; 36 | let foo: Adder = (a, b) => a + b; 37 | ``` 38 | 39 | 这个事实可以用下面的代码来证明,TypeScript 会发出正如你期望发出的错误警告: 40 | 41 | ```ts 42 | type Adder = (a: number, b: number) => number; 43 | let foo: Adder = (a, b) => { 44 | a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型 45 | return a + b; 46 | }; 47 | ``` 48 | 49 | 这是一个从左向右流动类型的示例。 50 | 51 | 如果你创建一个函数,并且函数参数为一个回调函数,相同的赋值规则也适用于它。从 `argument` 至 `parameter` 只是变量赋值的另一种形式。 52 | 53 | ```ts 54 | type Adder = (a: number, b: number) => number; 55 | function iTakeAnAdder(adder: Adder) { 56 | return adder(1, 2); 57 | } 58 | 59 | iTakeAnAdder((a, b) => { 60 | a = 'hello'; // Error: 不能把 'string' 类型赋值给 'number' 类型 61 | return a + b; 62 | }); 63 | ``` 64 | 65 | ## 结构化 66 | 67 | 这些简单的规则也适用于结构化的存在(对象字面量),例如在下面这种情况下 `foo` 的类型被推断为 `{ a: number, b: number }`: 68 | 69 | ```ts 70 | const foo = { 71 | a: 123, 72 | b: 456 73 | }; 74 | 75 | foo.a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型 76 | ``` 77 | 78 | 数组也一样: 79 | 80 | ```ts 81 | const bar = [1, 2, 3]; 82 | bar[0] = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型 83 | ``` 84 | 85 | ## 解构 86 | 87 | 这些也适用于解构中: 88 | 89 | ```ts 90 | const foo = { 91 | a: 123, 92 | b: 456 93 | }; 94 | let { a } = foo; 95 | 96 | a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型 97 | ``` 98 | 99 | 数组中: 100 | 101 | ```ts 102 | const bar = [1, 2]; 103 | let [a, b] = bar; 104 | 105 | a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型 106 | ``` 107 | 108 | 如果函数参数能够被推断出来,那么解构亦是如此。在如下例子中,函数参数能够被解构为 `a/b` 成员: 109 | 110 | ```ts 111 | type Adder = (number: { a: number; b: number }) => number; 112 | function iTakeAnAdder(adder: Adder) { 113 | return adder({ a: 1, b: 2 }); 114 | } 115 | 116 | iTakeAnAdder(({ a, b }) => { 117 | // a, b 的类型能被推断出来 118 | a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型 119 | return a + b; 120 | }); 121 | ``` 122 | 123 | ## 类型保护 124 | 125 | 在前面章节[类型保护](./typeGuard.md)中,我们已经知道它如何帮助我们改变和缩小类型范围(特别是在联合类型下)。类型保护只是一个块中变量另一种推断形式。 126 | 127 | ## 警告 128 | 129 | ### 小心使用参数 130 | 131 | 如果类型不能被赋值推断出来,类型也将不会流入函数参数中。例如如下的一个例子,编译器并不知道 `foo` 的类型,所它也就不能推断出 `a` 或者 `b` 的类型。 132 | 133 | ```ts 134 | const foo = (a, b) => { 135 | /* do something */ 136 | }; 137 | ``` 138 | 139 | 然而,如果 `foo` 添加了类型注解,函数参数也就能被推断(`a`,`b` 都能被推断为 `number` 类型): 140 | 141 | ```ts 142 | type TwoNumberFunction = (a: number, b: number) => void; 143 | const foo: TwoNumberFunction = (a, b) => { 144 | /* do something */ 145 | }; 146 | ``` 147 | 148 | ### 小心使用返回值 149 | 150 | 尽管 TypeScript 一般情况下能推断函数的返回值,但是它可能并不是你想要的。例如如下的 `foo` 函数,它的返回值为 `any`: 151 | 152 | ```ts 153 | function foo(a: number, b: number) { 154 | return a + addOne(b); 155 | } 156 | 157 | // 一些使用 JavaScript 库的特殊函数 158 | function addOne(a) { 159 | return a + 1; 160 | } 161 | ``` 162 | 163 | 这是因为返回值的类型被一个缺少类型定义的 `addOne` 函数所影响(`a` 是 `any`,所以 `addOne` 返回值为 `any`,`foo` 的返回值是也是 `any`)。 164 | 165 | ::: tip 166 | 我发现最简单的方式是明确的写上函数返回值,毕竟这些注解是一个定理,而函数是注解的一个证据。 167 | ::: 168 | 169 | 这里还有一些其他可以想象的情景,但是有一个好消息是有编译器选项 `noImplicitAny` 可以捕获这些 bug。 170 | 171 | ### `noImplicitAny` 172 | 173 | 选项 `noImplicitAny` 用来告诉编译器,当无法推断一个变量时发出一个错误(或者只能推断为一个隐式的 `any` 类型),你可以: 174 | 175 | - 通过显式添加 `:any` 的类型注解,来让它成为一个 `any` 类型; 176 | - 通过一些更正确的类型注解来帮助 TypeScript 推断类型。 177 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 15 | {{ $siteTitle }} 21 | 22 | 23 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 87 | 88 | 129 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 17 | 18 | 22 | 26 | 30 | 31 | 32 | 33 | 34 | 38 | 42 | 46 | 47 | 48 | 49 | 50 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/styles/theme.styl: -------------------------------------------------------------------------------- 1 | @require './code' 2 | @require './custom-blocks' 3 | @require './arrow' 4 | @require './wrapper' 5 | @require './toc' 6 | 7 | html, body 8 | padding 0 9 | margin 0 10 | background-color #fff 11 | 12 | body 13 | font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif 14 | -webkit-font-smoothing antialiased 15 | -moz-osx-font-smoothing grayscale 16 | font-size 16px 17 | color $textColor 18 | 19 | .page 20 | padding-left $sidebarWidth 21 | 22 | .navbar 23 | position fixed 24 | z-index 20 25 | top 0 26 | left 0 27 | right 0 28 | height $navbarHeight 29 | background-color #fff 30 | box-sizing border-box 31 | border-bottom 1px solid $borderColor 32 | 33 | .sidebar-mask 34 | position fixed 35 | z-index 9 36 | top 0 37 | left 0 38 | width 100vw 39 | height 100vh 40 | display none 41 | 42 | .sidebar 43 | font-size 16px 44 | background-color #fff 45 | width $sidebarWidth 46 | position fixed 47 | z-index 10 48 | margin 0 49 | top $navbarHeight 50 | left 0 51 | bottom 0 52 | box-sizing border-box 53 | border-right 1px solid $borderColor 54 | overflow-y auto 55 | 56 | .content:not(.custom) 57 | @extend $wrapper 58 | > *:first-child 59 | margin-top $navbarHeight 60 | a:hover 61 | text-decoration underline 62 | p.demo 63 | padding 1rem 1.5rem 64 | border 1px solid #ddd 65 | border-radius 4px 66 | img 67 | max-width 100% 68 | 69 | .content.custom 70 | padding 0 71 | margin 0 72 | img 73 | max-width 100% 74 | 75 | a 76 | font-weight 500 77 | color $accentColor 78 | text-decoration none 79 | 80 | p a code 81 | font-weight 400 82 | color $accentColor 83 | 84 | kbd 85 | background #eee 86 | border solid 0.15rem #ddd 87 | border-bottom solid 0.25rem #ddd 88 | border-radius 0.15rem 89 | padding 0 0.15em 90 | 91 | blockquote 92 | font-size .9rem 93 | color #999 94 | border-left .5rem solid #dfe2e5 95 | margin 0.5rem 0 96 | padding .25rem 0 .25rem 1rem 97 | & > p 98 | margin 0 99 | 100 | ul, ol 101 | padding-left 1.2em 102 | 103 | strong 104 | font-weight 600 105 | 106 | h1, h2, h3, h4, h5, h6 107 | font-weight 600 108 | line-height 1.25 109 | .content:not(.custom) > & 110 | margin-top (0.5rem - $navbarHeight) 111 | padding-top ($navbarHeight + 1rem) 112 | margin-bottom 0 113 | &:first-child 114 | margin-top -1.5rem 115 | margin-bottom 1rem 116 | + p, + pre, + .custom-block 117 | margin-top 2rem 118 | &:hover .header-anchor 119 | opacity: 1 120 | 121 | h1 122 | font-size 2.2rem 123 | 124 | h2 125 | font-size 1.65rem 126 | padding-bottom .3rem 127 | border-bottom 1px solid $borderColor 128 | 129 | h3 130 | font-size 1.35rem 131 | 132 | a.header-anchor 133 | font-size 0.85em 134 | float left 135 | margin-left -0.87em 136 | padding-right 0.23em 137 | margin-top 0.125em 138 | opacity 0 139 | &:hover 140 | text-decoration none 141 | 142 | code, kbd, .line-number 143 | font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace 144 | 145 | p, ul, ol 146 | line-height 1.7 147 | 148 | hr 149 | border 0 150 | border-top 1px solid $borderColor 151 | 152 | table 153 | border-collapse collapse 154 | margin 1rem 0 155 | display: block 156 | overflow-x: auto 157 | 158 | tr 159 | border-top 1px solid #dfe2e5 160 | &:nth-child(2n) 161 | background-color #f6f8fa 162 | 163 | th, td 164 | border 1px solid #dfe2e5 165 | padding .6em 1em 166 | 167 | .theme-container 168 | &.sidebar-open 169 | .sidebar-mask 170 | display: block 171 | &.no-navbar 172 | .content:not(.custom) > h1, h2, h3, h4, h5, h6 173 | margin-top 1.5rem 174 | padding-top 0 175 | .sidebar 176 | top 0 177 | 178 | 179 | @media (min-width: ($MQMobile + 1px)) 180 | .theme-container.no-sidebar 181 | .sidebar 182 | display none 183 | .page 184 | padding-left 0 185 | 186 | @require 'mobile.styl' 187 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/Home.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | {{ data.heroText || $title || 'Hello' }} 11 | 12 | 13 | {{ data.tagline || $description || 'Welcome to your VuePress site' }} 14 | 15 | 16 | 20 | 24 | 25 | 26 | 27 | 31 | 36 | {{ feature.title }} 37 | {{ feature.details }} 38 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 72 | 73 | 163 | -------------------------------------------------------------------------------- /docs/typings/readonly.md: -------------------------------------------------------------------------------- 1 | # readonly 2 | 3 | TypeScript 类型系统允许你在一个接口里使用 `readonly` 来标记属性。它能让你以一种更安全的方式工作(不可预期的改变是很糟糕的): 4 | 5 | ```ts 6 | function foo(config: { readonly bar: number, readonly bas: number }) { 7 | // .. 8 | } 9 | 10 | const config = { bar: 123, bas: 123 }; 11 | foo(config); 12 | 13 | // 现在你能够确保 'config' 不能够被改变了 14 | ``` 15 | 16 | 当然,你也可以在 `interface` 和 `type` 里使用 `readonly`: 17 | 18 | ```ts 19 | type Foo = { 20 | readonly bar: number; 21 | readonly bas: number; 22 | }; 23 | 24 | // 初始化 25 | const foo: Foo = { bar: 123, bas: 456 }; 26 | 27 | // 不能被改变 28 | foo.bar = 456; // Error: foo.bar 为仅读属性 29 | ``` 30 | 31 | 你也能指定一个类的属性为只读,然后在声明时或者构造函数中初始化它们,如下所示: 32 | 33 | ```ts 34 | class Foo { 35 | readonly bar = 1; // OK 36 | readonly baz: string; 37 | constructor() { 38 | this.baz = 'hello'; // OK 39 | } 40 | } 41 | ``` 42 | 43 | ## Readonly 44 | 45 | 这有一个 `Readonly` 的映射类型,它接收一个泛型 `T`,用来把它的所有属性标记为只读类型: 46 | 47 | ```ts 48 | type Foo = { 49 | bar: number; 50 | bas: number; 51 | }; 52 | 53 | type FooReadonly = Readonly; 54 | 55 | const foo: Foo = { bar: 123, bas: 456 }; 56 | const fooReadonly: FooReadonly = { bar: 123, bas: 456 }; 57 | 58 | foo.bar = 456; // ok 59 | fooReadonly.bar = 456; // Error: bar 属性只读 60 | ``` 61 | 62 | ## 其他的使用用例 63 | 64 | ### ReactJS 65 | 66 | `ReactJS` 是一个喜欢用不变数据的库,你可以标记你的 `Props` 和 `State` 为不可变数据: 67 | 68 | ```ts 69 | interface Props { 70 | readonly foo: number; 71 | } 72 | 73 | interface State { 74 | readonly bar: number; 75 | } 76 | 77 | export class Something extends React.Component { 78 | someMethod() { 79 | // 你可以放心,没有人会像下面这么做 80 | this.props.foo = 123; // Error: props 是不可变的 81 | this.state.baz = 456; // Error: 你应该使用 this.setState() 82 | } 83 | } 84 | ``` 85 | 86 | 然而,你并没有必要这么做,`React` 的声明文件已经标记这些为 `readonly`(通过传入泛型参数至一个内部包装,来把每个属性标记为 `readonly`,如上例子所示), 87 | 88 | ```ts 89 | export class Something extends React.Component<{ foo: number }, { baz: number }> { 90 | someMethod() { 91 | this.props.foo = 123; // Error: props 是不可变的 92 | this.state.baz = 456; // Error: 你应该使用 this.setState() 93 | } 94 | } 95 | ``` 96 | 97 | ### 绝对的不可变 98 | 99 | 你甚至可以把索引签名标记为只读: 100 | 101 | ```ts 102 | interface Foo { 103 | readonly [x: number]: number; 104 | } 105 | 106 | // 使用 107 | 108 | const foo: Foo = { 0: 123, 2: 345 }; 109 | console.log(foo[0]); // ok(读取) 110 | foo[0] = 456; // Error: 属性只读 111 | ``` 112 | 113 | 如果你想以不变的方式使用原生 JavaScript 数组,可以使用 TypeScript 提供的 `ReadonlyArray` 接口: 114 | 115 | ```ts 116 | let foo: ReadonlyArray = [1, 2, 3]; 117 | console.log(foo[0]); // ok 118 | foo.push(4); // Error: ReadonlyArray 上不存在 `push`,因为他会改变数组 119 | foo = foo.concat(4); // ok, 创建了一个复制 120 | ``` 121 | 122 | ### 自动推断 123 | 124 | 在一些情况下,编译器能把一些特定的属性推断为 `readonly`,例如在一个 `class` 中,如果你有一个只含有 `getter` 但是没有 `setter` 的属性,他能被推断为只读: 125 | 126 | ```ts 127 | class Person { 128 | firstName: string = 'John'; 129 | lastName: string = 'Doe'; 130 | 131 | get fullName() { 132 | return this.firstName + this.lastName; 133 | } 134 | } 135 | 136 | const person = new Person(); 137 | 138 | console.log(person.fullName); // John Doe 139 | person.fullName = 'Dear Reader'; // Error, fullName 只读 140 | ``` 141 | 142 | ## 与 `const` 的不同 143 | 144 | `const` 145 | 146 | - 用于变量; 147 | - 变量不能重新赋值给其他任何事物。 148 | 149 | `readonly` 150 | 151 | - 用于属性; 152 | - 用于别名,可以修改属性; 153 | 154 | 简单的例子 1: 155 | 156 | ```ts 157 | const foo = 123; // 变量 158 | let bar: { 159 | readonly bar: number; // 属性 160 | }; 161 | ``` 162 | 163 | 简单的例子 2: 164 | 165 | ```ts 166 | const foo: { 167 | readonly bar: number; 168 | } = { 169 | bar: 123 170 | }; 171 | 172 | function iMutateFoo(foo: { bar: number }) { 173 | foo.bar = 456; 174 | } 175 | 176 | iMutateFoo(foo); 177 | console.log(foo.bar); // 456 178 | ``` 179 | 180 | `readonly` 能确保“我”不能修改属性,但是当你把这个属性交给其他并没有这种保证的使用者(允许出于类型兼容性的原因),他们能改变它。当然,如果 `iMutateFoo` 明确的表示,他们的参数不可修改,那么编译器会发出错误警告: 181 | 182 | ```ts 183 | interface Foo { 184 | readonly bar: number; 185 | } 186 | 187 | let foo: Foo = { 188 | bar: 123 189 | }; 190 | 191 | function iTakeFoo(foo: Foo) { 192 | foo.bar = 456; // Error: bar 属性只读 193 | } 194 | 195 | iTakeFoo(foo); 196 | ``` 197 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/NavLinks.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 16 | 20 | 21 | 22 | 23 | 30 | {{ repoLabel }} 31 | 32 | 33 | 34 | 35 | 36 | 117 | 118 | 150 | -------------------------------------------------------------------------------- /docs/compiler/ast.md: -------------------------------------------------------------------------------- 1 | # 抽象语法树 2 | 3 | ### Node 节点 4 | 5 | 节点是抽象语法树(AST) 的基本构造块。语法上,通常 `Node` 表示非末端(non-terminals)节点。但是,有些末端节点,如:标识符和字面量也会保留在树中。 6 | 7 | AST 节点文档由两个关键部分构成。一是节点的 `SyntaxKind` 枚举,用于标识 AST 中的类型。二是其接口,即实例化 AST 时节点提供的 API。 8 | 9 | 这里是 `interface Node` 的一些关键成员: 10 | 11 | - `TextRange` 标识该节点在源文件中的起止位置。 12 | - `parent?: Node` 当前节点(在 AST 中)的父节点 13 | 14 | `Node` 还有一些其他的成员,标志(flags)和修饰符(modifiers)等。你可以在源码中搜索 `interface Node` 来查看,而上面提到对节点的遍历是非常重要的。 15 | 16 | ### SourceFile 17 | 18 | - `SyntaxKind.SourceFile` 19 | - `interface SourceFile`. 20 | 21 | 每个 `SourceFile` 都是一棵 AST 的顶级节点,它们包含在 `Program` 中。 22 | 23 | ## AST 技巧:访问子节点 24 | 25 | 有个工具函数 `ts.forEachChild`,可以用来访问 AST 任一节点的所有子节点。 26 | 27 | 下面是简化的代码片段,用于演示如何工作: 28 | 29 | ```ts 30 | export function forEachChild(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T { 31 | if (!node) { 32 | return; 33 | } 34 | switch (node.kind) { 35 | case SyntaxKind.BinaryExpression: 36 | return visitNode(cbNode, (node).left) || 37 | visitNode(cbNode, (node).operatorToken) || 38 | visitNode(cbNode, (node).right); 39 | case SyntaxKind.IfStatement: 40 | return visitNode(cbNode, (node).expression) || 41 | visitNode(cbNode, (node).thenStatement) || 42 | visitNode(cbNode, (node).elseStatement); 43 | 44 | // .... 更多 45 | ``` 46 | 47 | 该函数主要检查 `node.kind` 并据此判断 node 的接口,然后在其子节点上调用 `cbNode`。但是,要注意该函数不会为*所有*子节点调用 `visitNode`(例如:SyntaxKind.SemicolonToken)。想获得某 AST 节点的*所有*子节点,只要调用该节点的成员函数 `.getChildren`。 48 | 49 | 如下函数会打印 AST 节点详细信息: 50 | 51 | ```ts 52 | function printAllChildren(node: ts.Node, depth = 0) { 53 | console.log(new Array(depth + 1).join('----'), ts.syntaxKindToName(node.kind), node.pos, node.end); 54 | depth++; 55 | node.getChildren().forEach(c => printAllChildren(c, depth)); 56 | } 57 | ``` 58 | 59 | 我们进一步讨论解析器时会看到该函数的使用示例。 60 | 61 | ## AST 技巧:SyntaxKind 枚举 62 | 63 | `SyntaxKind` 被定义为一个常量枚举,如下所示: 64 | 65 | ```ts 66 | export const enum SyntaxKind { 67 | Unknown, 68 | EndOfFileToken, 69 | SingleLineCommentTrivia, 70 | // ... 更多 71 | ``` 72 | 73 | 这是个[常量枚举](../typings/enums.md#常量枚举),方便*内联*(例如:`ts.SyntaxKind.EndOfFileToken` 会变为 `1`),这样在使用 AST 时就不会有处理引用的额外开销。但编译时需要使用 --preserveConstEnums 编译标志,以便枚举*在运行时仍可用*。JavaScript 中你也可以根据需要使用 `ts.SyntaxKind.EndOfFileToken`。另外,可以用以下函数,将枚举成员转化为可读的字符串: 74 | 75 | ```ts 76 | export function syntaxKindToName(kind: ts.SyntaxKind) { 77 | return (ts).SyntaxKind[kind]; 78 | } 79 | ``` 80 | 81 | ## AST 杂项 82 | 83 | 杂项(Trivia)是指源文本中对正常理解代码不太重要的部分,例如:空白,注释,冲突标记。(为了保持轻量)杂项*不会存储*在 AST 中。但是可以*视需要*使用一些 `ts.*` API 来获取。 84 | 85 | 展示这些 API 前,你需要理解以下内容: 86 | 87 | ### 杂项的所有权 88 | 89 | 通常: 90 | 91 | - token 拥有它后面 _同一行_ 到下一个 token 之前的所有杂项 92 | - 该行之后的注释都与下个的 token 相关 93 | 94 | 对于文件中的前导(leading)和结束(ending)注释: 95 | 96 | - 源文件中的第一个 token 拥有所有开始的杂项 97 | - 而文件最后的一些列杂项则附加到文件结束符上,该 token 长度为 0 98 | 99 | ### 杂项 API 100 | 101 | 注释在多数基本使用中,都是让人关注的杂项。节点的注释可以通过以下函数获取: 102 | 103 | | 函数 | 描述 | 104 | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- | 105 | | `ts.getLeadingCommentRanges` | 给定源文本及其位置,返回给定位置后第一个换行符到 token 本身之间的注释范围(可能需要结合 `ts.Node.getFullStart` 使用)。 | 106 | | `ts.getTrailingCommentRanges` | 给定源文本及其位置,返回给定位置后第一个换行符之前的注释范围(可能需要结合 `ts.Node.getEnd` 使用)。 | 107 | 108 | 假设下面是某个源文件的一部分: 109 | 110 | ```ts 111 | debugger;/*hello*/ 112 | //bye 113 | /*hi*/ function 114 | ``` 115 | 116 | 对 `function` 而言,`getLeadingCommentRanges` 仅返回最后的两个注释 `//bye` 和 `/*hi*/`。 117 | 另外,而在 `debugger` 语句结束位置调用 `getTrailingCommentRanges` 会得到注释 `/*hello*/`。 118 | 119 | ### Token Start 和 Full Start 位置 120 | 121 | 节点有所谓的 "token start" 和 "full start" 位置。 122 | 123 | - Token Start:比较自然的版本,即文件中一个 token 的文本开始的位置。 124 | - Full Start:是指扫描器从上一个重要 token 开始扫描的位置。 125 | 126 | AST 节点有 `getStart` 和 `getFullStart` API 用于获取以上两种位置,还是这个例子: 127 | 128 | ```ts 129 | debugger;/*hello*/ 130 | //bye 131 | /*hi*/ function 132 | ``` 133 | 134 | 对 `function` 而言,token start 即 `function` 的位置,而 _full_ start 是 `/*hello*/` 的位置。要注意,full start 甚至会包含前一节点拥有的杂项。 135 | -------------------------------------------------------------------------------- /docs/project/compilationContext.md: -------------------------------------------------------------------------------- 1 | # 编译上下文 2 | 3 | 编译上下文算是一个比较花哨的术语,可以用它来给文件分组,告诉 TypeScript 哪些文件是有效的,哪些是无效的。除了有效文件所携带信息外,编译上下文还包含有正在被使用的编译选项的信息。定义这种逻辑分组,一个比较好的方式是使用 `tsconfig.json` 文件。 4 | 5 | ## tsconfig.json 6 | 7 | ### 基础 8 | 9 | 开始使用 `tsconfig.json` 是一件比较容易的事,你仅仅需要写下: 10 | 11 | ```json 12 | {} 13 | ``` 14 | 15 | 例如,在项目的根目录下创建一个空 JSON 文件。通过这种方式,TypeScript 将 会把此目录和子目录下的所有 .ts 文件作为编译上下文的一部分,它还会包含一部分默认的编译选项。 16 | 17 | ### 编译选项 18 | 19 | 你可以通过 `compilerOptions` 来定制你的编译选项: 20 | 21 | ```js 22 | { 23 | "compilerOptions": { 24 | 25 | /* 基本选项 */ 26 | "target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT' 27 | "module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015' 28 | "lib": [], // 指定要包含在编译中的库文件 29 | "allowJs": true, // 允许编译 javascript 文件 30 | "checkJs": true, // 报告 javascript 文件中的错误 31 | "jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react' 32 | "declaration": true, // 生成相应的 '.d.ts' 文件 33 | "sourceMap": true, // 生成相应的 '.map' 文件 34 | "outFile": "./", // 将输出文件合并为一个文件 35 | "outDir": "./", // 指定输出目录 36 | "rootDir": "./", // 用来控制输出目录结构 --outDir. 37 | "removeComments": true, // 删除编译后的所有的注释 38 | "noEmit": true, // 不生成输出文件 39 | "importHelpers": true, // 从 tslib 导入辅助工具函数 40 | "isolatedModules": true, // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似). 41 | 42 | /* 严格的类型检查选项 */ 43 | "strict": true, // 启用所有严格类型检查选项 44 | "noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错 45 | "strictNullChecks": true, // 启用严格的 null 检查 46 | "noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误 47 | "alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict' 48 | 49 | /* 额外的检查 */ 50 | "noUnusedLocals": true, // 有未使用的变量时,抛出错误 51 | "noUnusedParameters": true, // 有未使用的参数时,抛出错误 52 | "noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误 53 | "noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿) 54 | 55 | /* 模块解析选项 */ 56 | "moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6) 57 | "baseUrl": "./", // 用于解析非相对模块名称的基目录 58 | "paths": {}, // 模块名到基于 baseUrl 的路径映射的列表 59 | "rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容 60 | "typeRoots": [], // 包含类型声明的文件列表 61 | "types": [], // 需要包含的类型声明文件名列表 62 | "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。 63 | 64 | /* Source Map Options */ 65 | "sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置 66 | "mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置 67 | "inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件 68 | "inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性 69 | 70 | /* 其他选项 */ 71 | "experimentalDecorators": true, // 启用装饰器 72 | "emitDecoratorMetadata": true // 为装饰器提供元数据的支持 73 | } 74 | } 75 | ``` 76 | 77 | 关于这些(或者更多)编译选项,稍后将会讨论。 78 | 79 | ### TypeScript 编译 80 | 81 | 好的 IDE 支持对 TypeScript 的即时编译。但是,如果你想在使用 `tsconfig.json` 时从命令行手动运行 TypeScript 编译器,你可以通过以下方式: 82 | 83 | - 运行 tsc,它会在当前目录或者是父级目录寻找 `tsconfig.json` 文件。 84 | - 运行 `tsc -p ./path-to-project-directory` 。当然,这个路径可以是绝对路径,也可以是相对于当前目录的相对路径。 85 | 86 | 你甚至可以使用 `tsc -w` 来启用 TypeScript 编译器的观测模式,在检测到文件改动之后,它将重新编译。 87 | 88 | ## 指定文件 89 | 90 | 你也可以显式指定需要编译的文件: 91 | 92 | ```js 93 | { 94 | "files": [ 95 | "./some/file.ts" 96 | ] 97 | } 98 | ``` 99 | 100 | 你还可以使用 `include` 和 `exclude` 选项来指定需要包含的文件和排除的文件: 101 | 102 | ```js 103 | { 104 | "include": [ 105 | "./folder" 106 | ], 107 | "exclude": [ 108 | "./folder/**/*.spec.ts", 109 | "./folder/someSubFolder" 110 | ] 111 | } 112 | ``` 113 | 114 | :::tip 注意 115 | 使用 `globs`:`**/*` (一个示例用法:`some/folder/**/*`)意味着匹配所有的文件夹和所有文件(扩展名为 `.ts/.tsx`,当开启了 `allowJs: true` 选项时,扩展名可以是 `.js/.jsx`)。 116 | ::: 117 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/DropdownLink.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {{ item.text }} 11 | 15 | 16 | 17 | 18 | 22 | 27 | {{ subItem.text }} 28 | 29 | 33 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 78 | 79 | 180 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/components/AlgoliaSearchBox.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | 13 | 14 | 61 | 62 | 156 | -------------------------------------------------------------------------------- /docs/typings/discrominatedUnion.md: -------------------------------------------------------------------------------- 1 | # 辨析联合类型 2 | 3 | 当类中含有[字面量成员](./literals.md)时,我们可以用该类的属性来辨析联合类型。 4 | 5 | 作为一个例子,考虑 `Square` 和 `Rectangle` 的联合类型 `Shape`。`Square` 和 `Rectangle`有共同成员 `kind`,因此 `kind` 存在于 `Shape` 中。 6 | 7 | ```ts 8 | interface Square { 9 | kind: 'square'; 10 | size: number; 11 | } 12 | 13 | interface Rectangle { 14 | kind: 'rectangle'; 15 | width: number; 16 | height: number; 17 | } 18 | 19 | type Shape = Square | Rectangle; 20 | ``` 21 | 22 | 如果你使用类型保护风格的检查(`==`、`===`、`!=`、`!==`)或者使用具有判断性的属性(在这里是 `kind`),TypeScript 将会认为你会使用的对象类型一定是拥有特殊字面量的,并且它会为你自动把类型范围变小: 23 | 24 | ```ts 25 | function area(s: Shape) { 26 | if (s.kind === 'square') { 27 | // 现在 TypeScript 知道 s 的类型是 Square 28 | // 所以你现在能安全使用它 29 | return s.size * s.size; 30 | } else { 31 | // 不是一个 square ?因此 TypeScript 将会推算出 s 一定是 Rectangle 32 | return s.width * s.height; 33 | } 34 | } 35 | ``` 36 | 37 | ## 详细的检查 38 | 39 | 通常,联合类型的成员有一些自己的行为(代码): 40 | 41 | ```ts 42 | interface Square { 43 | kind: 'square'; 44 | size: number; 45 | } 46 | 47 | interface Rectangle { 48 | kind: 'rectangle'; 49 | width: number; 50 | height: number; 51 | } 52 | 53 | // 有人仅仅是添加了 `Circle` 类型 54 | // 我们可能希望 TypeScript 能在任何被需要的地方抛出错误 55 | interface Circle { 56 | kind: 'circle'; 57 | radius: number; 58 | } 59 | 60 | type Shape = Square | Rectangle | Circle; 61 | ``` 62 | 63 | 一个可能会让你的代码变差的例子: 64 | 65 | ```ts 66 | function area(s: Shape) { 67 | if (s.kind === 'square') { 68 | return s.size * s.size; 69 | } else if (s.kind === 'rectangle') { 70 | return s.width * s.height; 71 | } 72 | 73 | // 如果你能让 TypeScript 给你一个错误,这是不是很棒? 74 | } 75 | ``` 76 | 77 | 你可以通过一个简单的向下思想,来确保块中的类型被推断为与 `never` 类型兼容的类型。例如,你可以添加一个更详细的检查来捕获错误: 78 | 79 | ```ts 80 | function area(s: Shape) { 81 | if (s.kind === 'square') { 82 | return s.size * s.size; 83 | } else if (s.kind === 'rectangle') { 84 | return s.width * s.height; 85 | } else { 86 | // Error: 'Circle' 不能被赋值给 'never' 87 | const _exhaustiveCheck: never = s; 88 | } 89 | } 90 | ``` 91 | 92 | 它将强制你添加一种新的条件: 93 | 94 | ```ts 95 | function area(s: Shape) { 96 | if (s.kind === 'square') { 97 | return s.size * s.size; 98 | } else if (s.kind === 'rectangle') { 99 | return s.width * s.height; 100 | } else if (s.kind === 'circle') { 101 | return Math.PI * s.radius ** 2; 102 | } else { 103 | // ok 104 | const _exhaustiveCheck: never = s; 105 | } 106 | } 107 | ``` 108 | 109 | ## Switch 110 | 111 | ::: tip 112 | 你可以通过 `switch` 来实现以上例子。 113 | ::: 114 | 115 | ```ts 116 | function area(s: Shape) { 117 | switch (s.kind) { 118 | case 'square': 119 | return s.size * s.size; 120 | case 'rectangle': 121 | return s.width * s.height; 122 | case 'circle': 123 | return Math.PI * s.radius ** 2; 124 | default: 125 | const _exhaustiveCheck: never = s; 126 | } 127 | } 128 | ``` 129 | 130 | ## strictNullChecks 131 | 132 | 如果你使用 `strictNullChecks` 选项来做详细的检查,你应该返回 `_exhaustiveCheck` 变量(类型是 `never`),否则 TypeScript 可能会推断返回值为 `undefined`: 133 | 134 | ```ts 135 | function area(s: Shape) { 136 | switch (s.kind) { 137 | case 'square': 138 | return s.size * s.size; 139 | case 'rectangle': 140 | return s.width * s.height; 141 | case 'circle': 142 | return Math.PI * s.radius ** 2; 143 | default: 144 | const _exhaustiveCheck: never = s; 145 | return _exhaustiveCheck; 146 | } 147 | } 148 | ``` 149 | 150 | ## Redux 151 | 152 | Redux 库正是使用的上述例子。 153 | 154 | 以下是添加了 TypeScript 类型注解的[redux 要点](https://github.com/reduxjs/redux#the-gist)。 155 | 156 | ```ts 157 | import { createStore } from 'redux'; 158 | 159 | type Action = 160 | | { 161 | type: 'INCREMENT'; 162 | } 163 | | { 164 | type: 'DECREMENT'; 165 | }; 166 | 167 | /** 168 | * This is a reducer, a pure function with (state, action) => state signature. 169 | * It describes how an action transforms the state into the next state. 170 | * 171 | * The shape of the state is up to you: it can be a primitive, an array, an object, 172 | * or even an Immutable.js data structure. The only important part is that you should 173 | * not mutate the state object, but return a new object if the state changes. 174 | * 175 | * In this example, we use a `switch` statement and strings, but you can use a helper that 176 | * follows a different convention (such as function maps) if it makes sense for your 177 | * project. 178 | */ 179 | function counter(state = 0, action: Action) { 180 | switch (action.type) { 181 | case 'INCREMENT': 182 | return state + 1; 183 | case 'DECREMENT': 184 | return state - 1; 185 | default: 186 | return state; 187 | } 188 | } 189 | 190 | // Create a Redux store holding the state of your app. 191 | // Its API is { subscribe, dispatch, getState }. 192 | let store = createStore(counter); 193 | 194 | // You can use subscribe() to update the UI in response to state changes. 195 | // Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly. 196 | // However it can also be handy to persist the current state in the localStorage. 197 | 198 | store.subscribe(() => console.log(store.getState())); 199 | 200 | // The only way to mutate the internal state is to dispatch an action. 201 | // The actions can be serialized, logged or stored and later replayed. 202 | store.dispatch({ type: 'INCREMENT' }); 203 | // 1 204 | store.dispatch({ type: 'INCREMENT' }); 205 | // 2 206 | store.dispatch({ type: 'DECREMENT' }); 207 | // 1 208 | ``` 209 | 210 | 与 TypeScript 一起使用可以有效的防止拼写错误,并且能提高重构和书写文档化代码的能力。 211 | -------------------------------------------------------------------------------- /docs/typings/generices.md: -------------------------------------------------------------------------------- 1 | # 泛型 2 | 3 | 设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是: 4 | 5 | - 类的实例成员 6 | - 类的方法 7 | - 函数参数 8 | - 函数返回值 9 | 10 | ## 动机和示例 11 | 12 | 下面是对一个先进先出的数据结构——队列,在 `TypeScript` 和 `JavaScript` 中的简单实现。 13 | 14 | ```ts 15 | class Queue { 16 | private data = []; 17 | push = item => this.data.push(item); 18 | pop = () => this.data.shift(); 19 | } 20 | ``` 21 | 22 | 在上述代码中存在一个问题,它允许你向队列中添加任何类型的数据,当然,当数据被弹出队列时,也可以是任意类型。在下面的示例中,看起来人们可以向队列中添加`string` 类型的数据,但是实际上,该用法假定的是只有 `number` 类型会被添加到队列里。 23 | 24 | ```ts 25 | class Queue { 26 | private data = []; 27 | push = item => this.data.push(item); 28 | pop = () => this.data.shift(); 29 | } 30 | 31 | const queue = new Queue(); 32 | 33 | queue.push(0); 34 | queue.push('1'); // Oops,一个错误 35 | 36 | // 一个使用者,走入了误区 37 | console.log(queue.pop().toPrecision(1)); 38 | console.log(queue.pop().toPrecision(1)); // RUNTIME ERROR 39 | ``` 40 | 41 | 一个解决的办法(事实上,这也是不支持泛型类型的唯一解决办法)是为这些约束创建特殊类,如快速创建数字类型的队列: 42 | 43 | ```ts 44 | class QueueNumber { 45 | private data = []; 46 | push = (item: number) => this.data.push(item); 47 | pop = (): number => this.data.shift(); 48 | } 49 | 50 | const queue = new QueueNumber(); 51 | 52 | queue.push(0); 53 | queue.push('1'); // Error: 不能推入一个 `string` 类型,只能是 `number` 类型 54 | 55 | // 如果该错误得到修复,其他将不会出现问题 56 | ``` 57 | 58 | 当然,快速也意味着痛苦。例如当你想创建一个字符串的队列时,你将不得不再次修改相当大的代码。我们真正想要的一种方式是无论什么类型被推入队列,被推出的类型都与推入类型一样。当你使用泛型时,这会很容易: 59 | 60 | ```ts 61 | // 创建一个泛型类 62 | class Queue { 63 | private data: T[] = []; 64 | push = (item: T) => this.data.push(item); 65 | pop = (): T | undefined => this.data.shift(); 66 | } 67 | 68 | // 简单的使用 69 | const queue = new Queue(); 70 | queue.push(0); 71 | queue.push('1'); // Error:不能推入一个 `string`,只有 number 类型被允许 72 | ``` 73 | 74 | 另外一个我们见过的例子:一个 `reverse` 函数,现在在这个函数里提供了函数参数与函数返回值的约束: 75 | 76 | ```ts 77 | function reverse(items: T[]): T[] { 78 | const toreturn = []; 79 | for (let i = items.length - 1; i >= 0; i--) { 80 | toreturn.push(items[i]); 81 | } 82 | return toreturn; 83 | } 84 | 85 | const sample = [1, 2, 3]; 86 | let reversed = reverse(sample); 87 | 88 | reversed[0] = '1'; // Error 89 | reversed = ['1', '2']; // Error 90 | 91 | reversed[0] = 1; // ok 92 | reversed = [1, 2]; // ok 93 | ``` 94 | 95 | 在此章节中,你已经了解在*类*和*函数*上使用泛型的例子。一个值得补充一点的是,你可以为创建的成员函数添加泛型: 96 | 97 | ```ts 98 | class Utility { 99 | reverse(items: T[]): T[] { 100 | const toreturn = []; 101 | for (let i = items.length; i >= 0; i--) { 102 | toreturn.push(items[i]); 103 | } 104 | return toreturn; 105 | } 106 | } 107 | ``` 108 | 109 | ::: tip 110 | 你可以随意调用泛型参数,当你使用简单的泛型时,泛型常用 `T`、`U`、`V` 表示。如果在你的参数里,不止拥有一个泛型,你应该使用一个更语义化名称,如 `TKey` 和 `TValue` (通常情况下,以 `T` 作为泛型的前缀,在其他语言如 C++ 里,也被称为模板) 111 | ::: 112 | 113 | ## 误用的泛型 114 | 115 | 我见过开发者使用泛型仅仅是为了它的 hack。当你使用它时,你应该问问自己:你想用它来提供什么样的约束。如果你不能很好的回答它,你可能会误用泛型,如: 116 | 117 | ```ts 118 | declare function foo(arg: T): void; 119 | ``` 120 | 121 | 在这里,泛型完全没有必要使用,因为它仅用于单个参数的位置,使用如下方式可能更好: 122 | 123 | ```ts 124 | declare function foo(arg: any): void; 125 | ``` 126 | 127 | ## 设计模式:方便通用 128 | 129 | 考虑如下函数: 130 | 131 | ```ts 132 | declare function parse(name: string): T; 133 | ``` 134 | 135 | 在这种情况下,泛型 `T` 只在一个地方被使用了,它并没有在成员之间提供约束 `T`。这相当于一个如下的类型断言: 136 | 137 | ```ts 138 | declare function parse(name: string): any; 139 | 140 | const something = parse('something') as TypeOfSomething; 141 | ``` 142 | 143 | 仅使用一次的泛型并不比一个类型断言来的安全。它们都给你使用 API 提供了便利。 144 | 145 | 另一个明显的例子是,一个用于加载 json 返回值函数,它返回你任何传入类型的 `Promise`: 146 | 147 | ```ts 148 | const getJSON = (config: { url: string; headers?: { [key: string]: string } }): Promise => { 149 | const fetchConfig = { 150 | method: 'GET', 151 | Accept: 'application/json', 152 | 'Content-Type': 'application/json', 153 | ...(config.headers || {}) 154 | }; 155 | return fetch(config.url, fetchConfig).then(response => response.json()); 156 | }; 157 | ``` 158 | 159 | 请注意,你仍然需要明显的注解任何你需要的类型,但是 `getJSON` 的签名 `config => Promise` 能够减少你一些关键的步骤(你不需要注解 `loadUsers` 的返回类型,因为它能够被推出来): 160 | 161 | ```ts 162 | type LoadUserResponse = { 163 | user: { 164 | name: string; 165 | email: string; 166 | }[]; 167 | }; 168 | 169 | function loaderUser() { 170 | return getJSON({ url: 'https://example.com/users' }); 171 | } 172 | ``` 173 | 174 | 与此类似:使用 `Promise` 作为一个函数的返回值比一些如:`Promise` 的备选方案要好很多。 175 | 176 | ### 配合 axios 使用 177 | 178 | 通常情况下,我们会把后端返回数据格式单独放入一个 interface 里: 179 | 180 | ```ts 181 | // 请求接口数据 182 | export interface ResponseData { 183 | /** 184 | * 状态码 185 | * @type { number } 186 | */ 187 | code: number; 188 | 189 | /** 190 | * 数据 191 | * @type { T } 192 | */ 193 | result: T; 194 | 195 | /** 196 | * 消息 197 | * @type { string } 198 | */ 199 | message: string; 200 | } 201 | ``` 202 | 203 | 当我们把 API 单独抽离成单个模块时: 204 | 205 | ```ts 206 | // 在 axios.ts 文件中对 axios 进行了处理,例如添加通用配置、拦截器等 207 | import Ax from './axios'; 208 | 209 | import { ResponseData } from './interface.ts'; 210 | 211 | export function getUser() { 212 | return Ax.get>('/somepath') 213 | .then(res => res.data) 214 | .catch(err => console.error(err)); 215 | } 216 | ``` 217 | 218 | 接着我们写入返回的数据类型 `User`,这可以让 TypeScript 顺利推断出我们想要的类型: 219 | 220 | ```ts 221 | interface User { 222 | name: string; 223 | age: number; 224 | } 225 | 226 | async function test() { 227 | // user 被推断出为 228 | // { 229 | // code: number, 230 | // result: { name: string, age: number }, 231 | // message: string 232 | // } 233 | const user = await getUser(); 234 | } 235 | ``` 236 | -------------------------------------------------------------------------------- /docs/tips/metadata.md: -------------------------------------------------------------------------------- 1 | # Reflect Metadata 2 | 3 | ## 基础 4 | 5 | Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+ 的版本已经支持它,你只需要: 6 | 7 | - `npm i reflect-metadata --save`。 8 | - 在 `tsconfig.json` 里配置 `emitDecoratorMetadata` 选项。 9 | 10 | Reflect Metadata 的 API 可以用于类或者类的属性上,如: 11 | 12 | ```ts 13 | function metadata( 14 | metadataKey: any, 15 | metadataValue: any 16 | ): { 17 | (target: Function): void; 18 | (target: Object, propertyKey: string | symbol): void; 19 | }; 20 | ``` 21 | 22 | `Reflect.metadata` 当作 `Decorator` 使用,当修饰类时,在类上添加元数据,当修饰类属性时,在类原型的属性上添加元数据,如: 23 | 24 | ```ts 25 | @Reflect.metadata('inClass', 'A') 26 | class Test { 27 | @Reflect.metadata('inMethod', 'B') 28 | public hello(): string { 29 | return 'hello world'; 30 | } 31 | } 32 | 33 | console.log(Reflect.getMetadata('inClass', Test)); // 'A' 34 | console.log(Reflect.getMetadata('inMethod', new Test(), 'hello')); // 'B' 35 | ``` 36 | 37 | 它具有诸多使用场景。 38 | 39 | ## 获取类型信息 40 | 41 | 譬如在 [`vue-property-decorator`](https://github.com/kaorun343/vue-property-decorator) 6.1 及其以下版本中,通过使用 `Reflect.getMetadata` API,`Prop` Decorator 能获取属性类型传至 Vue,简要代码如下: 42 | 43 | ```ts 44 | function Prop(): PropertyDecorator { 45 | return (target, key: string) => { 46 | const type = Reflect.getMetadata('design:type', target, key); 47 | console.log(`${key} type: ${type.name}`); 48 | // other... 49 | }; 50 | } 51 | 52 | class SomeClass { 53 | @Prop() 54 | public Aprop!: string; 55 | } 56 | ``` 57 | 58 | 运行代码可在控制台看到 `Aprop type: string`。除能获取属性类型外,通过 `Reflect.getMetadata("design:paramtypes", target, key)` 和 `Reflect.getMetadata("design:returntype", target, key)` 可以分别获取函数参数类型和返回值类型。 59 | 60 | ## 自定义 `metadataKey` 61 | 62 | 除能获取类型信息外,常用于自定义 `metadataKey`,并在合适的时机获取它的值,示例如下: 63 | 64 | ```ts 65 | function classDecorator(): ClassDecorator { 66 | return target => { 67 | // 在类上定义元数据,key 为 `classMetaData`,value 为 `a` 68 | Reflect.defineMetadata('classMetaData', 'a', target); 69 | }; 70 | } 71 | 72 | function methodDecorator(): MethodDecorator { 73 | return (target, key, descriptor) => { 74 | // 在类的原型属性 'someMethod' 上定义元数据,key 为 `methodMetaData`,value 为 `b` 75 | Reflect.defineMetadata('methodMetaData', 'b', target, key); 76 | }; 77 | } 78 | 79 | @classDecorator() 80 | class SomeClass { 81 | @methodDecorator() 82 | someMethod() {} 83 | } 84 | 85 | Reflect.getMetadata('classMetaData', SomeClass); // 'a' 86 | Reflect.getMetadata('methodMetaData', new SomeClass(), 'someMethod'); // 'b' 87 | ``` 88 | 89 | ## 例子 90 | 91 | ### 控制反转和依赖注入 92 | 93 | 在 Angular 2+ 的版本中,[控制反转与依赖注入](https://segmentfault.com/a/1190000008626680)便是基于此实现,现在,我们来实现一个简单版: 94 | 95 | ```ts 96 | type Constructor = new (...args: any[]) => T; 97 | 98 | const Injectable = (): ClassDecorator => target => {}; 99 | 100 | class OtherService { 101 | a = 1; 102 | } 103 | 104 | @Injectable() 105 | class TestService { 106 | constructor(public readonly otherService: OtherService) {} 107 | 108 | testMethod() { 109 | console.log(this.otherService.a); 110 | } 111 | } 112 | 113 | const Factory = (target: Constructor): T => { 114 | // 获取所有注入的服务 115 | const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService] 116 | const args = providers.map((provider: Constructor) => new provider()); 117 | return new target(...args); 118 | }; 119 | 120 | Factory(TestService).testMethod(); // 1 121 | ``` 122 | 123 | ### Controller 与 Get 的实现 124 | 125 | 如果你在使用 TypeScript 开发 Node 应用,相信你对 `Controller`、`Get`、`POST` 这些 Decorator,并不陌生: 126 | 127 | ```ts 128 | @Controller('/test') 129 | class SomeClass { 130 | @Get('/a') 131 | someGetMethod() { 132 | return 'hello world'; 133 | } 134 | 135 | @Post('/b') 136 | somePostMethod() {} 137 | } 138 | ``` 139 | 140 | 这些 Decorator 也是基于 `Reflect Metadata` 实现,这次,我们将 `metadataKey` 定义在 `descriptor` 的 `value` 上: 141 | 142 | ```ts 143 | const METHOD_METADATA = 'method'; 144 | const PATH_METADATA = 'path'; 145 | 146 | const Controller = (path: string): ClassDecorator => { 147 | return target => { 148 | Reflect.defineMetadata(PATH_METADATA, path, target); 149 | } 150 | } 151 | 152 | const createMappingDecorator = (method: string) => (path: string): MethodDecorator => { 153 | return (target, key, descriptor) => { 154 | Reflect.defineMetadata(PATH_METADATA, path, descriptor.value); 155 | Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value); 156 | } 157 | } 158 | 159 | const Get = createMappingDecorator('GET'); 160 | const Post = createMappingDecorator('POST'); 161 | ``` 162 | 163 | 接着,创建一个函数,映射出 `route`: 164 | 165 | ```ts 166 | function mapRoute(instance: Object) { 167 | const prototype = Object.getPrototypeOf(instance); 168 | 169 | // 筛选出类的 methodName 170 | const methodsNames = Object.getOwnPropertyNames(prototype) 171 | .filter(item => !isConstructor(item) && isFunction(prototype[item])); 172 | return methodsNames.map(methodName => { 173 | const fn = prototype[methodName]; 174 | 175 | // 取出定义的 metadata 176 | const route = Reflect.getMetadata(PATH_METADATA, fn); 177 | const method = Reflect.getMetadata(METHOD_METADATA, fn); 178 | return { 179 | route, 180 | method, 181 | fn, 182 | methodName 183 | } 184 | }) 185 | }; 186 | ``` 187 | 188 | 因此,我们可以得到一些有用的信息: 189 | 190 | ```ts 191 | Reflect.getMetadata(PATH_METADATA, SomeClass); // '/test' 192 | 193 | mapRoute(new SomeClass()); 194 | 195 | /** 196 | * [{ 197 | * route: '/a', 198 | * method: 'GET', 199 | * fn: someGetMethod() { ... }, 200 | * methodName: 'someGetMethod' 201 | * },{ 202 | * route: '/b', 203 | * method: 'POST', 204 | * fn: somePostMethod() { ... }, 205 | * methodName: 'somePostMethod' 206 | * }] 207 | * 208 | */ 209 | ``` 210 | 211 | 最后,只需把 `route` 相关信息绑在 `express` 或者 `koa` 上就 ok 了。 212 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | base: '/typescript-book-chinese/', 3 | title: '深入理解 TypeScript', 4 | description: 'TypeScript Deep Dive 中文版', 5 | head: [ 6 | ['link', { rel: 'icon', href: `/logo.png` }], 7 | ['link', { rel: 'manifest', href: '/manifest.json' }], 8 | ['meta', { name: 'theme-color', content: '#3eaf7c' }], 9 | ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }], 10 | ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }], 11 | ['meta', { name: 'msapplication-TileColor', content: '#000000' }] 12 | ], 13 | plugins: [ 14 | // '@vuepress/pwa', 15 | '@vuepress/back-to-top', 16 | [ 17 | '@vuepress/google-analytics', 18 | { 19 | ga: 'UA-106861408-1' // UA-00000000-0 20 | } 21 | ] 22 | ], 23 | // theme: [], 24 | themeConfig: { 25 | repo: 'jkchao/typescript-book-chinese', 26 | docsDir: 'docs', 27 | editLinks: true, 28 | editLinkText: '编辑此页', 29 | activeHeaderLinks: false, 30 | sidebarDepth: 3, 31 | lastUpdated: '上次更新', 32 | // algolia: { 33 | // apiKey: 'fd0efd57c48824ceb1bcfa9690dba5b0', 34 | // indexName: 'jkchao_typescript' 35 | // }, 36 | adsConfig: [ 37 | { title: '关注公众号', src: '/typescript-book-chinese/qrcode.jpg' }, 38 | { title: '与我交流', src: '/typescript-book-chinese/contact.png' }, 39 | { 40 | title: '购买此书', 41 | src: 42 | 'https://static.jkchao.cn/2019-11-22/WechatIMG719.png?imageMogr2/auto-orient/thumbnail/80x/blur/1x0/quality/75|imageslim', 43 | url: 'https://item.jd.com/12755624.html' 44 | } 45 | ], 46 | nav: [ 47 | { text: '原书链接', link: 'https://basarat.gitbooks.io/typescript/content/docs/getting-started.html' }, 48 | { text: 'Blog', link: 'https://jkchao.cn' } 49 | ], 50 | sidebar: [ 51 | { 52 | title: '写在前面', 53 | collapsable: false, 54 | children: ['/'] 55 | }, 56 | { 57 | title: 'TypeScript 项目', 58 | collapsable: false, 59 | children: [ 60 | '/project/compilationContext', 61 | '/project/declarationspaces', 62 | '/project/modules', 63 | '/project/namespaces', 64 | '/project/dynamicImportExpressions' 65 | ] 66 | }, 67 | { 68 | title: 'TypeScript 类型系统', 69 | collapsable: false, 70 | children: [ 71 | '/typings/overview', 72 | '/typings/migrating', 73 | '/typings/types', 74 | '/typings/ambient', 75 | '/typings/interfaces', 76 | '/typings/enums', 77 | '/typings/lib', 78 | '/typings/functions', 79 | '/typings/callable', 80 | '/typings/typeAssertion', 81 | '/typings/freshness', 82 | '/typings/typeGuard', 83 | '/typings/literals', 84 | '/typings/readonly', 85 | '/typings/generices', 86 | '/typings/typeInference', 87 | '/typings/typeCompatibility', 88 | '/typings/neverType', 89 | '/typings/discrominatedUnion', 90 | '/typings/indexSignatures', 91 | '/typings/movingTypes', 92 | '/typings/exceptionsHanding', 93 | '/typings/mixins', 94 | '/typings/thisType' 95 | ] 96 | }, 97 | { 98 | title: 'JSX', 99 | collapsable: false, 100 | children: ['/jsx/support', '/jsx/reactJSX', '/jsx/nonReactJSX'] 101 | }, 102 | { 103 | title: 'TypeScript 错误提示', 104 | collapsable: false, 105 | children: ['/error/interpreting', '/error/common'] 106 | }, 107 | { 108 | title: 'TIPs', 109 | collapsable: false, 110 | children: [ 111 | '/tips/stringBasedEmuns', 112 | '/tips/nominalTyping', 113 | '/tips/statefulFunctions', 114 | '/tips/bind', 115 | '/tips/curry', 116 | '/tips/typeInstantiation', 117 | '/tips/lazyObjectLiteralInitialization', 118 | '/tips/classAreUseful', 119 | '/tips/avoidExportDefault', 120 | '/tips/limitPropertySetters', 121 | '/tips/createArrays', 122 | '/tips/outFileCaution', 123 | '/tips/staticConstructors', 124 | '/tips/singletonPatern', 125 | '/tips/functionParameters', 126 | '/tips/truthy', 127 | '/tips/buildToggles', 128 | '/tips/typesafeEventEmitter', 129 | '/tips/metadata', 130 | '/tips/covarianceAndContravariance', 131 | '/tips/infer' 132 | ] 133 | }, 134 | { 135 | title: 'TypeScript 编译原理', 136 | collapsable: false, 137 | children: [ 138 | '/compiler/overview', 139 | '/compiler/program', 140 | '/compiler/ast', 141 | '/compiler/scanner', 142 | '/compiler/parser', 143 | '/compiler/binder', 144 | '/compiler/checker', 145 | '/compiler/emitter' 146 | ] 147 | }, 148 | { 149 | title: 'TypeScript FAQs', 150 | collapsable: false, 151 | children: [ 152 | './faqs/common-bug-not-bugs', 153 | './faqs/common-feature-request', 154 | './faqs/type-system-behavior', 155 | './faqs/function', 156 | './faqs/class', 157 | './faqs/generics', 158 | './faqs/modules', 159 | './faqs/enums', 160 | './faqs/type-guards', 161 | './faqs/jsx-and-react', 162 | './faqs/thing-that-dont-work', 163 | './faqs/commandline-behavior', 164 | './faqs/tsconfig-behavior' 165 | ] 166 | }, 167 | { 168 | title: 'TypeScript 更新', 169 | collapsable: false, 170 | children: ['/new/typescript-3.9', '/new/typescript-3.8', '/new/typescript-3.7'] 171 | } 172 | // { 173 | // title: 'TypeScript 更新', 174 | // collapsable: false, 175 | // children: ['/new/typescript-3.7'] 176 | // } 177 | ] 178 | } 179 | }; 180 | -------------------------------------------------------------------------------- /docs/faqs/class.md: -------------------------------------------------------------------------------- 1 | # 类 2 | 3 | ## 为什么这些空类的行为很奇怪? 4 | 5 | > 我写下这段代码,并期望它抛出错误 6 | 7 | ```ts 8 | class Empty { 9 | /* empty */ 10 | } 11 | 12 | var e2: Empty = window; 13 | ``` 14 | 15 | 请参阅此问题「[为什么所有的内容都能赋值给空的接口](./type-system-behavior.html#为什么所有的类型,都能赋值给一个空的接口?)」。值得重新思考一下这个答案的建议:一般来说,你永远不应该声明一个没有属性的类。即使对于子类也是如此: 16 | 17 | ```ts 18 | class Base { 19 | important: number; 20 | properties: number; 21 | } 22 | class Alpha extends Base {} 23 | class Bravo extends Base {} 24 | ``` 25 | 26 | `Alpha` 和 `Bravo` 的结构相同,都是继承自 `Base`,这会产生许多令人惊讶的效果,所以别这么做。如果你想让 `Alpha` 与 `Bravo` 不相同,为它们各自提供一个属性。 27 | 28 | ## 什么是名义上的类 29 | 30 | 这两段代码该如何解释: 31 | 32 | ```ts 33 | class Alpha { 34 | x: number; 35 | } 36 | class Bravo { 37 | x: number; 38 | } 39 | class Charlie { 40 | private x: number; 41 | } 42 | class Delta { 43 | private x: number; 44 | } 45 | 46 | let a = new Alpha(), 47 | b = new Bravo(), 48 | c = new Charlie(), 49 | d = new Delta(); 50 | 51 | a = b; // OK 52 | c = d; // Error 53 | ``` 54 | 55 | 在 TypeScript 中,类进行结构上的比较,有一个例外是对于 `private` 与 `protected` 的成员。当一个成员是 `private` 或者 `protected` 时,它们必须来自同一个声明,才能被视为与另一个 `private` 或者 `protected` 的成员相同。 56 | 57 | ## 为什么在我的实例方法中,`this` 成了一个「孤儿」? 58 | 59 | > 我写下如下代码 60 | 61 | ```ts 62 | class MyClass { 63 | x = 10; 64 | someCallback() { 65 | console.log(this.x); // Prints 'undefined', not 10 66 | this.someMethod(); // Throws error "this.method is not a function" 67 | } 68 | someMethod() {} 69 | } 70 | 71 | let obj = new MyClass(); 72 | window.setTimeout(obj.someCallback, 10); 73 | ``` 74 | 75 | 可能会提出一些相似的问题: 76 | 77 | - 为什么在我的回调函数中类的属性没有定义? 78 | - 为什么在我的回调函数中,`this` 指向 `window`? 79 | - 为什么在我的回调函数中,`this` 指向 `undefined`? 80 | - 为什么我会得到 `this.someMethod is not a function` 的错误? 81 | - 为什么我会得到 `Cannot read property 'someMethod' of undefined` 的错误? 82 | 83 | 在 JavaScript 中,`this` 值由以下确定: 84 | 85 | 1. 该函数是调用 `.bind` 的结果吗?如果是这样,`this` 由传递给 `bind` 的第一个参数确定 86 | 87 | 2. 该函数是通过属性表达式 `expr.method() ?` 直接调用吗?如果是这样,`this` 指向 `expr` 88 | 89 | 3. 否则,`this` 是 `undefined`(在严格模式中),或者是 `window` (非严格模式中)。 90 | 91 | 在上一个例子中,影响结果的是这行代码: 92 | 93 | ```ts 94 | window.setTimeout(obj.someCallback, 10); 95 | ``` 96 | 97 | 在这里,我们提供了 `obj.someCallback` 到 `setTimeout` 的函数引用,然后该函数并不是作为 `bind` 的结果调用,也不是直接作为一个方法调用。因此在 `someCallback` 里的 `this` 指向 `window`(或者在严格模式下的 `undefied`)。 98 | 99 | 这里概述了一些解决办法:http://stackoverflow.com/a/20627988/1704166 100 | 101 | ## 当 `Bar` 是一个 `class` 时,`Bar` 和 `typeof Bar` 有什么区别? 102 | 103 | > 我写下这段代码,但是我不理解我为什么会得到错误: 104 | 105 | ```ts 106 | class MyClass { 107 | someMethod() {} 108 | } 109 | var x: MyClass; 110 | // Cannot assign 'typeof MyClass' to MyClass? Huh? 111 | x = MyClass; 112 | ``` 113 | 114 | 在 JavaScript 中,类仅仅是个函数,这点很重要。我们将类对象本身 -- `MyClass` 的值,作为是构造函数。当一个构造函数被 `new` 调用时,我们得到一个对象,它是该类的实例。 115 | 116 | 因此,当我们定义一个类时,实际上,我们定义了两个不同的类型。 117 | 118 | 第一个是由类的名字推导而来,在这个例子中是 `MyClass`。这个是类实例的类型,它定义了类的实例具有的属性和方法,它是一个通过调用类的构造函数来返回的类型。 119 | 120 | 第二个类型是一个匿名的类型,它是构造函数具有的类型。它包含一个返回类实例的构造函数签名(可以使用 `new` 调用),同时,它也包含类中可能含有的 `static` 属性和方法。它也通常被称为「静态方面」,因为它包含那些静态成员(以及作为类的构造函数)。我们可以用 `typeof` 来引用此类型。 121 | 122 | 当在类型位置使用 `typeof` 操作符时,描述了表达式的类型。因此 `typeof MyClass` 是指 `MyClass` 的类型。 123 | 124 | ## 为什么我的子类属性初始值设定会覆盖基类构造函数中设置的值? 125 | 126 | 有关此问题,和其他初始化顺序问题,请参阅 [#1617](https://github.com/Microsoft/TypeScript/issues/1617)。 127 | 128 | ## 声明类和接口有什么区别? 129 | 130 | 参阅: http://stackoverflow.com/a/14348084/1704166 131 | 132 | ## 接口继承类,意味着什么? 133 | 134 | > 这段代码是什么意思? 135 | 136 | ```ts 137 | class Foo { 138 | /* ... */ 139 | } 140 | interface Bar extends Foo {} 141 | ``` 142 | 143 | 这创建了一个名叫 `Bar` 的类型,它与 `Foo` 的实例具有相同的成员。当 `Foo` 具有私有成员时,`Bar` 内的相同属性,必须由一个继承自 `Foo` 的类实现。总的来说,这种模式是应当避免的,尤其是在 `Foo` 有私有成员时。 144 | 145 | ## 为什么我会得到错误:`TypeError: [base class name] is not defined in __extends`? 146 | 147 | > 我写下一段代码, 148 | 149 | ```ts 150 | /** file1.ts **/ 151 | class Alpha { 152 | /* ... */ 153 | } 154 | 155 | /** file2.ts **/ 156 | class Bravo extends Alpha { 157 | /* ... */ 158 | } 159 | ``` 160 | 161 | 在运行时,有如下错误发生在 `_extends` 中: 162 | 163 | ```ts 164 | Uncaught TypeError: Alpha is not defined 165 | ``` 166 | 167 | 最常见的原因是在你的 HTML 中包含有 file2.ts 的 `script`,但是并没有包含 `file1.ts` 的 `script`。因此你需要在引用 `file2.ts` 之前引用 `file1.ts`。 168 | 169 | ## 为什么我会得到 `TypeError: Cannot read property 'prototype' of undefined" in __extends` 的错误? 170 | 171 | > 我写下如下代码: 172 | 173 | ```ts 174 | /** file1.ts **/ 175 | class Alpha { 176 | /* ... */ 177 | } 178 | 179 | /** file2.ts **/ 180 | class Bravo extends Alpha { 181 | /* ... */ 182 | } 183 | ``` 184 | 185 | 在运行时,有如下错误发生在 `_extends` 中: 186 | 187 | ```ts 188 | Uncaught TypeError: Cannot read property 'prototype' of undefined 189 | ``` 190 | 191 | 出现这种情况,原因可能有一些。 192 | 193 | 首先,在单个文件中,你在基类之前定义了派生类,那么你应该重新排序文件,以便在派生类之前声明基类。 194 | 195 | 如果你使用了 `--out` 的编译选项,编译器可能会对你希望文件的顺序感到困惑。请参阅常见问题简答中「如果控制文件排序」部分 196 | 197 | 如果您没有使用 `--out`,HTML 文件中的 `script` 引用文件的顺序可能出现错误。重新排序 `script` 对文件的引用,以便在定义派生类的文件之前包含定义基类的文件。 198 | 199 | 最后,如果你使用某种类型的第三方包,该包可能会错误地排序了文件。请参阅该工具的文档以了解如何在结果输出中正确排序输入文件。 200 | 201 | ## 为什么不扩展 `Error`、`Array`、`Map` 内置函数? 202 | 203 | 在 ES2015 中,返回一个对象的构造函数将 `this` 的值隐式替换为 `super(...)` 的任何调用者。这对于构造函数代码捕获 `super(...)` 的任何潜在返回值并将其替换为 `this` 是必要的。 204 | 205 | 这样导致的结果是:`Error`、`Array` 等子类将不再按预期工作。这是由于 `Error`、`Array` 等的构造函数使用 ECMAScript6 中的 `new.target` 来调整原型链。但是,在 ECMAScript 5 中调用构造函数时,无法确保 `new.target` 的值。在其他一些低水平的编译器通常都有相同的限制。 206 | 207 | ### 例如: 208 | 209 | 如下作为一个子类: 210 | 211 | ```ts 212 | class FooError extends Error { 213 | constructor(m: string) { 214 | super(m); 215 | } 216 | sayHello() { 217 | return 'hello ' + this.message; 218 | } 219 | } 220 | ``` 221 | 222 | 你可能会发现: 223 | 224 | - 通过这些子类的构造函数返回的对象中,方法可能是 `undefined`。因此,当调用 `sayHello` 时,会抛出一个错误。 225 | - `instanceof` 将会在子类的实例和自身实例中被中断。因此 `new FooError() instanceof FooError` 将返回 `false`。 226 | 227 | ### 推荐 228 | 229 | 作为一个推荐方式,你可以在 `super(...)` 被调用之后手动调整原型。 230 | 231 | ```ts 232 | class FooError extends Error { 233 | constructor(m: string) { 234 | super(m); 235 | 236 | // Set the prototype explicitly. 237 | Object.setPrototypeOf(this, FooError.prototype); 238 | } 239 | 240 | sayHello() { 241 | return 'hello ' + this.message; 242 | } 243 | } 244 | ``` 245 | 246 | 然而,任何 `FooError` 的子类将不得不手动设置原型。在运行时,对于那些不支持 `Object.setPrototypeOf` 属性的,你可能用要 `__proto__` 来替代他。 247 | 248 | 不幸的是,[IE 10 及其一下不兼容这些方法](https://docs.microsoft.com/zh-cn/microsoft-edge/dev-guide/whats-new/javascript-version-information)。你可以手动将原型中的方法复制到实例本身,(例如:`FooError.prototype` 复制到 `this` 上),但是对于原型链本身是无法修复的。 249 | -------------------------------------------------------------------------------- /docs/tips/infer.md: -------------------------------------------------------------------------------- 1 | # infer 2 | 3 | ## 介绍 4 | 5 | `infer` 最早出现在此 [PR](https://github.com/Microsoft/TypeScript/pull/21496) 中,表示在 `extends` 条件语句中待推断的类型变量。 6 | 7 | 简单示例如下: 8 | 9 | ```ts 10 | type ParamType = T extends (arg: infer P) => any ? P : T; 11 | ``` 12 | 13 | 在这个条件语句 `T extends (arg: infer P) => any ? P : T` 中,`infer P` 表示待推断的函数参数。 14 | 15 | 整句表示为:如果 `T` 能赋值给 `(arg: infer P) => any`,则结果是 `(arg: infer P) => any` 类型中的参数 `P`,否则返回为 `T`。 16 | 17 | ```ts 18 | interface User { 19 | name: string; 20 | age: number; 21 | } 22 | 23 | type Func = (user: User) => void; 24 | 25 | type Param = ParamType; // Param = User 26 | type AA = ParamType; // string 27 | ``` 28 | 29 | ## 内置类型 30 | 31 | 在 2.8 版本中,TypeScript 内置了一些与 `infer` 有关的映射类型: 32 | 33 | - 用于提取函数类型的返回值类型: 34 | 35 | ```ts 36 | type ReturnType = T extends (...args: any[]) => infer P ? P : any; 37 | ``` 38 | 39 | 相比于文章开始给出的示例,`ReturnType` 只是将 `infer P` 从参数位置移动到返回值位置,因此此时 `P` 即是表示待推断的返回值类型。 40 | 41 | ```ts 42 | type Func = () => User; 43 | type Test = ReturnType; // Test = User 44 | ``` 45 | 46 | - 用于提取构造函数中参数(实例)类型: 47 | 48 | 一个构造函数可以使用 `new` 来实例化,因此它的类型通常表示如下: 49 | 50 | ```ts 51 | type Constructor = new (...args: any[]) => any; 52 | ``` 53 | 54 | 当 `infer` 用于构造函数类型中,可用于参数位置 `new (...args: infer P) => any;` 和返回值位置 `new (...args: any[]) => infer P;`。 55 | 56 | 因此就内置如下两个映射类型: 57 | 58 | ```ts 59 | // 获取参数类型 60 | type ConstructorParameters any> = T extends new (...args: infer P) => any 61 | ? P 62 | : never; 63 | 64 | // 获取实例类型 65 | type InstanceType any> = T extends new (...args: any[]) => infer R ? R : any; 66 | 67 | class TestClass { 68 | constructor(public name: string, public age: number) {} 69 | } 70 | 71 | type Params = ConstructorParameters; // [string, number] 72 | 73 | type Instance = InstanceType; // TestClass 74 | ``` 75 | 76 | ## 一些用例 77 | 78 | 至此,相信你已经对 `infer` 已有基本了解,我们来看看一些使用它的「骚操作」: 79 | 80 | - **tuple** 转 **union** ,如:`[string, number]` -> `string | number` 81 | 82 | 解答之前,我们需要了解 tuple 类型在一定条件下,是可以赋值给数组类型: 83 | 84 | ```ts 85 | type TTuple = [string, number]; 86 | type TArray = Array; 87 | 88 | type Res = TTuple extends TArray ? true : false; // true 89 | type ResO = TArray extends TTuple ? true : false; // false 90 | ``` 91 | 92 | 因此,在配合 `infer` 时,这很容易做到: 93 | 94 | ```ts 95 | type ElementOf = T extends Array ? E : never; 96 | 97 | type TTuple = [string, number]; 98 | 99 | type ToUnion = ElementOf; // string | number 100 | ``` 101 | 102 | 在 [stackoverflow](https://stackoverflow.com/questions/44480644/typescript-string-union-to-string-array/45486495#45486495) 上看到另一种解法,比较简(牛)单(逼): 103 | 104 | ```ts 105 | type TTuple = [string, number]; 106 | type Res = TTuple[number]; // string | number 107 | ``` 108 | 109 | - **union** 转 **intersection**,如:`T1 | T2` -> `T1 & T2` 110 | 111 | 这个可能要稍微麻烦一点,需要 `infer` 配合「 [Distributive conditional types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types) 」使用。 112 | 113 | 在[相关链接](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types)中,我们可以了解到「Distributive conditional types」是由「naked type parameter」构成的条件类型。而「naked type parameter」表示没有被 `Wrapped` 的类型(如:`Array`、`[T]`、`Promise` 等都是不是「naked type parameter」)。「Distributive conditional types」主要用于拆分 `extends` 左边部分的联合类型,举个例子:在条件类型 `T extends U ? X : Y` 中,当 `T` 是 `A | B` 时,会拆分成 `A extends U ? X : Y | B extends U ? X : Y`; 114 | 115 | 有了这个前提,再利用在逆变位置上,[同一类型变量的多个候选类型将会被推断为交叉类型](https://github.com/Microsoft/TypeScript/pull/21496)的特性,即 116 | 117 | ```ts 118 | type T1 = { name: string }; 119 | type T2 = { age: number }; 120 | 121 | type Bar = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never; 122 | type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // string 123 | type T21 = Bar<{ a: (x: T1) => void; b: (x: T2) => void }>; // T1 & T2 124 | ``` 125 | 126 | 因此,综合以上几点,我们可以得到在 [stackoverflow](https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type) 上的一个答案: 127 | 128 | ```ts 129 | type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; 130 | 131 | type Result = UnionToIntersection; // T1 & T2 132 | ``` 133 | 134 | 当传入 `T1 | T2` 时: 135 | 136 | - 第一步:`(U extends any ? (k: U) => void : never)` 会把 union 拆分成 `(T1 extends any ? (k: T1) => void : never) | (T2 extends any ? (k: T2)=> void : never)`,即是得到 `(k: T1) => void | (k: T2) => void`; 137 | 138 | - 第二步:`(k: T1) => void | (k: T2) => void extends ((k: infer I) => void) ? I : never`,根据上文,可以推断出 `I` 为 `T1 & T2`。 139 | 140 | 当然,你可以玩出更多花样,比如 [**union** 转 **tuple**](https://zhuanlan.zhihu.com/p/58704376)。 141 | 142 | ## LeetCode 的一道 TypeScript 面试题 143 | 144 | 前段时间,在 [GitHub](https://github.com/LeetCode-OpenSource/hire/blob/master/typescript_zh.md) 上,发现一道来自 LeetCode TypeScript 的面试题,比较有意思,题目的大致意思是: 145 | 146 | 假设有一个这样的类型(原题中给出的是类,这里简化为 interface): 147 | 148 | ```ts 149 | interface Module { 150 | count: number; 151 | message: string; 152 | asyncMethod(input: Promise): Promise>; 153 | syncMethod(action: Action): Action; 154 | } 155 | ``` 156 | 157 | 在经过 `Connect` 函数之后,返回值类型为 158 | 159 | ```ts 160 | type Result = { 161 | asyncMethod(input: T): Action; 162 | syncMethod(action: T): Action; 163 | } 164 | ``` 165 | 166 | 其中 `Action` 的定义为: 167 | 168 | ```ts 169 | interface Action { 170 | payload?: T; 171 | type: string; 172 | } 173 | ``` 174 | 175 | 这里主要考察两点 176 | 177 | - 挑选出函数 178 | - 此篇文章所提及的 `infer` 179 | 180 | 挑选函数的方法,已经在 [handbook](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html) 中已经给出,只需判断 value 能赋值给 Function 就行了: 181 | 182 | ```ts 183 | type FuncName = { [P in keyof T]: T[P] extends Function ? P : never }[keyof T]; 184 | 185 | type Connect = (module: Module) => { [T in FuncName]: Module[T] }; 186 | /* 187 | * type Connect = (module: Module) => { 188 | * asyncMethod: (input: Promise) => Promise>; 189 | * syncMethod: (action: Action) => Action; 190 | * } 191 | */ 192 | ``` 193 | 194 | 接下来就比较简单了,主要是利用条件类型 + `infer`,如果函数可以赋值给 `asyncMethod(input: Promise): Promise>`,则取值为 `asyncMethod(input: T): Action`。具体答案就不给出了,感兴趣的小伙伴可以尝试一下。 195 | --------------------------------------------------------------------------------
{{ getMsg() }}
36 | {{ item.title }} 37 | 41 | 42 |
13 | {{ data.tagline || $description || 'Welcome to your VuePress site' }} 14 |
20 | 24 |
{{ feature.details }}