跳至主要内容

按 TypeScript 版本分类的实用模式

TypeScript 版本通常会引入新的方法来完成任务;本节帮助 React + TypeScript 的当前用户升级 TypeScript 版本,并探索 TypeScript + React 应用和库中常用的模式。这可能与其他部分重复;如果您发现任何差异,请提交问题

2.9 之前的 TypeScript 版本指南尚未编写,欢迎发送 PR! 除了官方 TS 团队的沟通外,我们还推荐Marius Schulz 的博客获取版本说明。有关更多 TypeScript 历史,请参阅TypeScript 类型简史DefinitelyTyped 简史。您可能还希望探索 React 不太为人所知的替代类型,例如prop-typesomreason-reacttyped-react

TypeScript 2.9

[发行说明 | 博文]

  1. 带标签的模板字符串的类型参数(例如 styled-components
export interface InputFormProps {
foo: string; // this is understood inside the template string below
}

export const InputForm = styledInput<InputFormProps>`
color:
${({ themeName }) => (themeName === "dark" ? "black" : "white")};
border-color: ${({ foo }) => (foo ? "red" : "black")};
`;
  1. JSX 泛型

https://github.com/Microsoft/TypeScript/pull/22415

有助于键入/使用泛型组件

// instead of
<Formik
render={(props: FormikProps<Values>) => {
/* your code here ... */
}}
/>;

// usage
<Formik<Values>
render={(props) => {
/* your code here ... */
}}
/>;
<MyComponent<number> data={12} />;

更多信息:https://github.com/basarat/typescript-book/blob/master/docs/jsx/react.md#react-jsx-tip-generic-components

TypeScript 3.0

[发行说明 | 博文]

  1. 键入剩余参数以编写可变长度的参数
// `rest` accepts any number of strings - even none!
function foo(...rest: string[]) {
// ...
}

foo("hello"); // works
foo("hello", "world"); // also works
  1. 使用 LibraryManagedAttributes 支持 JSX 中的 propTypesstatic defaultProps
export interface Props {
name: string;
}

export class Greet extends React.Component<Props> {
render() {
const { name } = this.props;
return <div>Hello ${name.toUpperCase()}!</div>;
}
static defaultProps = { name: "world" };
}

// Type-checks! No type assertions needed!
let el = <Greet />;
  1. 新的 Unknown 类型

用于键入 API 以强制执行类型检查 - 不是特定于 React 的,但对于处理 API 响应非常方便

interface IComment {
date: Date;
message: string;
}

interface IDataService1 {
getData(): any;
}

let service1: IDataService1;
const response = service1.getData();
response.a.b.c.d; // RUNTIME ERROR

// ----- compare with -------

interface IDataService2 {
getData(): unknown; // ooo
}

let service2: IDataService2;
const response2 = service2.getData();
// response2.a.b.c.d; // COMPILE TIME ERROR if you do this

if (typeof response === "string") {
console.log(response.toUpperCase()); // `response` now has type 'string'
}

待办事项:指责此更改。不知道这应该做什么

您还可以断言类型,或对 unknown 类型使用类型保护。这比诉诸 any 更好。

  1. 项目引用

项目引用允许 TypeScript 项目依赖于其他 TypeScript 项目 - 特别是允许 tsconfig.json 文件引用其他 tsconfig.json 文件。这使大型代码库能够扩展,而无需每次都重新编译代码库的每个部分,方法是将其分解成多个项目。

在每个文件夹中,创建一个包含至少以下内容的 tsconfig.json

{
"compilerOptions": {
"composite": true, // tells TSC it is a subproject of a larger project
"declaration": true, // emit .d.ts declaration files since project references dont have access to source ts files. important for project references to work!
"declarationMap": true, // sourcemaps for .d.ts
"rootDir": "." // specify compile it relative to root project at .
},
"include": ["./**/*.ts"],
"references": [
// (optional) array of subprojects your subproject depends on
{
"path": "../myreferencedproject", // must have tsconfig.json
"prepend": true // concatenate js and sourcemaps generated by this subproject, if and only if using outFile
}
]
}

以及引用顶级子项目的根 tsconfig.json

{
"files": [],
"references": [{ "path": "./proj1" }, { "path": "./proj2" }]
}

并且您必须运行 tsc --buildtsc -b

要保存 tsconfig 模板,您可以使用 extends 选项

{
"extends": "../tsconfig.base"
// more stuff here
}

TypeScript 3.1

[发行说明 | 博文]

  1. 函数上的属性声明

像这样将属性附加到函数现在“可以正常工作”了

export const FooComponent = ({ name }) => <div>Hello! I am {name}</div>;

FooComponent.defaultProps = {
name: "swyx",
};

TypeScript 3.2

[发行说明 | 博文]

没有特定于 React 的内容。

TypeScript 3.3

[发行说明 | 博文]

没有特定于 React 的内容。

TypeScript 3.4

[发行说明 | 博文]

  1. const 断言
function useLoading() {
const [isLoading, setState] = useState(false);

const load = (aPromise: Promise<any>) => {
setState(true);
return aPromise.finally(() => setState(false));
};

return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[]
}

有关可以在哪些地方使用const 断言的更多信息。

TypeScript 3.5

[发行说明 | 博文]

  1. 内置 <Omit> 类型!!

  2. 来自泛型构造函数的高阶类型推断

type ComponentClass<P> = new (props: P) => Component<P>;
declare class Component<P> {
props: P;
constructor(props: P);
}

declare function myHoc<P>(C: ComponentClass<P>): ComponentClass<P>;

type NestedProps<T> = { foo: number; stuff: T };

declare class GenericComponent<T> extends Component<NestedProps<T>> {}

// type is 'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>'
const GenericComponent2 = myHoc(GenericComponent);

另请参阅Google 升级到 3.5 的说明

TypeScript 3.6

[发行说明 | 博文]

没有什么特别针对 React 的,但是游乐场进行了升级,并且环境类和函数可以合并

TypeScript 3.7

[发行说明 | 博文]

  1. 可选链
let x = foo?.bar.baz();

// is equivalent to

let x = foo === null || foo === undefined ? undefined : foo.bar.baz();

// Optional Element access
function tryGetFirstElement<T>(arr?: T[]) {
return arr?.[0];
}

// Optional Call
async function makeRequest(url: string, log?: (msg: string) => void) {
log?.(`Request started at ${new Date().toISOString()}`);
const result = (await fetch(url)).json();
log?.(`Request finished at at ${new Date().toISOString()}`);
return result;
}
  1. 空值合并运算符
let x = foo ?? bar();

// equivalent to

let x = foo !== null && foo !== undefined ? foo : bar();

除非您确实意味着虚假性,否则您通常应在通常使用 || 的任何地方使用 ??

function ShowNumber({ value }: { value: number }) {
let _value = value || 0.5; // will replace 0 with 0.5 even if user really means 0
// etc...
}
  1. 断言函数
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new AssertionError(msg);
}
}
function yell(str) {
assert(typeof str === "string");

return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}

您也可以在没有自定义函数的情况下进行断言

function assertIsString(val: any): asserts val is string {
if (typeof val !== "string") {
throw new AssertionError("Not a string!");
}
}
function yell(str: any) {
assertIsString(str);

// Now TypeScript knows that 'str' is a 'string'.

return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
  1. ts-nocheck

您现在可以在 TypeScript 文件顶部添加 // @ts-nocheck!对于迁移很有用。

TypeScript 3.8

[发行说明 | 博文]

  1. 仅类型导入和导出
import type { SomeThing } from "./some-module.js";

export type { SomeThing };
  1. ECMAScript 私有字段

不是特定于 React 的,但 Bloomberg 很好

  1. export * as ns 语法

这是 ES2020 语法。而不是

import * as utilities from "./utilities.js";
export { utilities };

您可以执行

export * as utilities from "./utilities.js";
  1. 顶级 await

不是特定于 React 的,但 gj Myles

  1. JSDoc 属性修饰符

对 JSDoc 用户很有用 - @public, @private, @protected, @readonly

  1. Linux 上更好的目录监视和 watchOptions
  2. “快速且宽松”的增量检查

assumeChangesOnlyAffectDirectDependencies 减少了超大型代码库的构建时间。

TypeScript 3.9

[发行说明 | 博文]

  1. (次要功能)新的 ts-expect-error 指令。

在编写您期望出错的测试时使用它。

// @ts-expect-error
console.log(47 * "octopus");

如果满足以下条件,请选择 ts-expect-error

  • 您正在编写测试代码,您实际上希望类型系统在操作上出错
  • 您预计修复很快就会到来,您只需要一个快速解决方法
  • 您在一个规模合理的项目中,并且有一个积极主动的团队,希望在受影响的代码再次有效后删除抑制注释

如果满足以下条件,请选择 ts-ignore

  • 您有一个较大的项目,并且在没有明确所有者的代码中出现了新的错误
  • 您正在 TypeScript 的两个不同版本之间进行升级,并且一行代码在一个版本中出错,但在另一个版本中没有出错。
  • 您确实没有时间来决定哪种选择更好。
  1. }> 现在是无效的 JSX 文本字符

它们一直都是无效的,但现在 TypeScript 和 Babel 正在强制执行它

Unexpected token. Did you mean `{'>'}` or `&gt;`?
Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

如果需要,您可以批量转换这些字符

TypeScript 4.0

[发行说明 | 博文]

它用于带有 Preact 的自定义 pragma

// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */

import { h, Fragment } from "preact";

let stuff = (
<>
<div>Hello</div>
</>
);
// transformed to
let stuff = h(Fragment, null, h("div", null, "Hello"));

TypeScript 4.1

[发行说明 | 博文]

  1. 模板字面量类型

这是一个巨大的功能。

用例 1 - 从其他字符串字面量类型的排列生成字符串字面量类型

type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";

// Takes
// | "top-left" | "top-center" | "top-right"
// | "middle-left" | "middle-center" | "middle-right"
// | "bottom-left" | "bottom-center" | "bottom-right"
declare function setAlignment(
value: `${VerticalAlignment}-${HorizontalAlignment}`
): void;

setAlignment("top-left"); // works!
setAlignment("top-middel"); // error!
setAlignment("top-pot"); // error! but good doughnuts if you're ever in Seattle

用例 2 - 建模动态字符串字面量类型

type PropEventSource<T> = {
on(eventName: `${string & keyof T}Changed`, callback: () => void): void;
};

/// Create a "watched object" with an 'on' method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;

为了使字符串操作更容易,有新的泛型:UppercaseLowercaseCapitalizeUncapitalize

您可以将其与 infer 关键字结合使用,例如这样

type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}` ? P : never;

type Params = ParseRouteParams<"/api/user/:userID">; // Params is "userID"

type NoParams = ParseRouteParams<"/api/user">; // NoParams is never --> no params!

此功能非常灵活,请在此处查看其他用例想法

  1. React 17 jsx 工厂

这是一个新的编译器选项,用于在一般情况下提供与 React 17 支持一致的输出。

// ./src/tsconfig.json - for production
{
"compilerOptions": {
"module": "esnext",
"target": "es2015",
"jsx": "react-jsx",
"strict": true
},
"include": [
"./**/*"
]
}

// ./src/tsconfig.dev.json - for development - extending the production config
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react-jsxdev"
}
}

其他

  1. 映射类型中的键重映射
  2. 递归条件类型
  3. 已检查的索引访问

TypeScript 4.2

[发行说明 | 博客文章]

与 React 无关

TypeScript 4.3

[发行说明 | 博客文章]

与 React 无关

TypeScript 4.4

[发行说明 | 博客文章]

与 React 无关

TypeScript 4.5

[发行说明 | 博客文章]

  1. (轻微的 VS Code 改进)JSX 属性的代码片段补全

TypeScript 4.6

[发行说明 | 博客文章]

  1. (极其微小)删除了 react-jsx 编译输出中不必要的参数

TypeScript 路线图和规范

https://github.com/Microsoft/TypeScript/wiki/Roadmap

您是否知道您也可以在线阅读 TypeScript 规范? https://github.com/microsoft/TypeScript/blob/master/doc/spec-ARCHIVED.md