按 TypeScript 版本分类的实用模式
TypeScript 版本通常会引入新的方法来完成任务;本节帮助 React + TypeScript 的当前用户升级 TypeScript 版本,并探索 TypeScript + React 应用和库中常用的模式。这可能与其他部分重复;如果您发现任何差异,请提交问题!
2.9 之前的 TypeScript 版本指南尚未编写,欢迎发送 PR! 除了官方 TS 团队的沟通外,我们还推荐Marius Schulz 的博客获取版本说明。有关更多 TypeScript 历史,请参阅TypeScript 类型简史和DefinitelyTyped 简史。您可能还希望探索 React 不太为人所知的替代类型,例如prop-types、om、reason-react和typed-react。
TypeScript 2.9
- 带标签的模板字符串的类型参数(例如
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")};
`;
- 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} />;
TypeScript 3.0
- 键入剩余参数以编写可变长度的参数
// `rest` accepts any number of strings - even none!
function foo(...rest: string[]) {
// ...
}
foo("hello"); // works
foo("hello", "world"); // also works
- 使用
LibraryManagedAttributes
支持 JSX 中的propTypes
和static 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 />;
- 新的
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
更好。
- 项目引用
项目引用允许 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 --build
或 tsc -b
。
要保存 tsconfig 模板,您可以使用 extends
选项
{
"extends": "../tsconfig.base"
// more stuff here
}
TypeScript 3.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
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
内置
<Omit>
类型!!来自泛型构造函数的高阶类型推断
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);
TypeScript 3.6
没有什么特别针对 React 的,但是游乐场进行了升级,并且环境类和函数可以合并
TypeScript 3.7
- 可选链
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;
}
- 空值合并运算符
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...
}
- 断言函数
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'?
}
ts-nocheck
您现在可以在 TypeScript 文件顶部添加 // @ts-nocheck
!对于迁移很有用。
TypeScript 3.8
- 仅类型导入和导出
import type { SomeThing } from "./some-module.js";
export type { SomeThing };
- ECMAScript 私有字段
不是特定于 React 的,但 Bloomberg 很好
export * as ns
语法
这是 ES2020 语法。而不是
import * as utilities from "./utilities.js";
export { utilities };
您可以执行
export * as utilities from "./utilities.js";
- 顶级
await
不是特定于 React 的,但 gj Myles
- JSDoc 属性修饰符
对 JSDoc 用户很有用 - @public, @private, @protected, @readonly
- Linux 上更好的目录监视和 watchOptions
- “快速且宽松”的增量检查
assumeChangesOnlyAffectDirectDependencies
减少了超大型代码库的构建时间。
TypeScript 3.9
- (次要功能)新的
ts-expect-error
指令。
在编写您期望出错的测试时使用它。
// @ts-expect-error
console.log(47 * "octopus");
如果满足以下条件,请选择 ts-expect-error
- 您正在编写测试代码,您实际上希望类型系统在操作上出错
- 您预计修复很快就会到来,您只需要一个快速解决方法
- 您在一个规模合理的项目中,并且有一个积极主动的团队,希望在受影响的代码再次有效后删除抑制注释
如果满足以下条件,请选择 ts-ignore
- 您有一个较大的项目,并且在没有明确所有者的代码中出现了新的错误
- 您正在 TypeScript 的两个不同版本之间进行升级,并且一行代码在一个版本中出错,但在另一个版本中没有出错。
- 您确实没有时间来决定哪种选择更好。
}
和>
现在是无效的 JSX 文本字符
它们一直都是无效的,但现在 TypeScript 和 Babel 正在强制执行它
Unexpected token. Did you mean `{'>'}` or `>`?
Unexpected token. Did you mean `{'}'}` or `}`?
如果需要,您可以批量转换这些字符。
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 - 从其他字符串字面量类型的排列生成字符串字面量类型
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>;
为了使字符串操作更容易,有新的泛型:Uppercase
、Lowercase
、Capitalize
和 Uncapitalize
。
您可以将其与 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!
此功能非常灵活,请在此处查看其他用例想法
- https://hasura.io/blog/how-typescript-template-literal-types-helped-us-with-multiple-database-support/
- https://github.com/ghoullier/awesome-template-literal-types
这是一个新的编译器选项,用于在一般情况下提供与 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"
}
}
其他
TypeScript 4.2
与 React 无关
TypeScript 4.3
与 React 无关
TypeScript 4.4
与 React 无关
TypeScript 4.5
- (轻微的 VS Code 改进)JSX 属性的代码片段补全
TypeScript 4.6
- (极其微小)删除了
react-jsx
编译输出中不必要的参数
TypeScript 路线图和规范
https://github.com/Microsoft/TypeScript/wiki/Roadmap
您是否知道您也可以在线阅读 TypeScript 规范? https://github.com/microsoft/TypeScript/blob/master/doc/spec-ARCHIVED.md