1. 赋值断言
类属性使用赋值断言
官方示例:class C { foo!: number; // ^ // Notice this exclamation point! // This is the "definite assignment assertion" modifier. constructor() { this.initialize(); } initialize() { this.foo = 0; } }
如果去掉foo后面的感叹号会怎样?
报错提示我们foo没有初始化,也没有在构造函数中定义;意思就是在构造函数中初始化就不会报错了?
在构造函数中初始化的确不报错,反向证明报错信息很准确。
- 变量声明使用赋值断言
官方示例
let x!: number[];
initialize();
x.push(4);
function initialize() {
x = [0, 1, 2, 3];
}
去掉感叹号
报错信息告诉我们变量x在被赋值之前使用了。
提示:
上面的报错信息都是建立在tsconfig.json设置strict:true的前提下,
在不使用赋值断言的前提下:
strictPropertyInitialization | strict | 变量 | 类属性 |
---|---|---|---|
false | true | 报错 | 不报错 |
false | false | 不报错 | 不报错 |
true | false | 不报错 | 报错 |
true | true | 报错 | 报错 |
strictPropertyInitialization的粒度更细,变量没有初始化导致报错没有更细粒度的参数去配置。
小结:对于延迟初始化,typescript没法推断出来,要通过赋值断言显示指定后面会为其初始化。
2. readonly vs as const
- as const 的作用是将被修饰的所有属性都设置为只读状态(注意修饰的是值,而不是变量)
- readonly 用于修饰interface type class声明的属性
type
type Foo = {
readonly bar: number;
readonly bas: number;
};
class
class Foo {
readonly bar = 1; // OK
readonly baz: string;
constructor() {
this.baz = 'hello'; // OK
}
}
对于只读数组可以用ReadonlyArray<T>
let foo: ReadonlyArray<number> = [1, 2, 3];
对于函数式编程的要求之一就是函数是纯函数,其中一个对象或者数组传递到纯函数中,该参数不应该被改变。有没有类型去修饰函数参数是不可变的?
Can a typescript parameter be annotated as const?做了说明,还没有类型能直接修饰参数是不可变的,但是用readonly可以每一层去指定该参数是不可变的。比如
function foo(arr: readonly string[]) {}
经过这么修饰arr就不能再调用push方法。一般后端返回的数据不会有太多层,我们可以在数据的每一层去用readonly修饰,比如:
type Foo = {
readonly bar: number;
readonly bas: number;
readonly children: {
readonly biu: number;
};
};
function foo(config: Foo) {
config.children.biu = 99;
}
const config = { bar: 123, bas: 123, children: { biu: 77 } };
foo(config);
这样赋值的时候就会报错:
readonly做了详细的解释
as const 也不能修饰变量,只能修饰确定的值而不是变量,比如
3. 覆盖、扩展node_modules下的类型定义
我想在覆盖umi@3.x的useSelector达到使用的时候不传递state类型的目的,在一个d.ts中定义:
import 'react-redux'
export interface GlobalState {
loading: Loading
user: UserState
}
declare module 'react-redux' {
export function useSelector<TState = GlobalState, TSelected = unknown>(
selector: (state: TState) => TSelected,
equalityFn?: (left: TSelected, right: TSelected) => boolean
): TSelected
}
这样我们在页面中就可以直接使用,不传递任何类型,而获得良好的代码提示
const age = useSelector((state) => state.user.age)
使用umi@4.0.90后上面的方式没法用了,换个方式,
根目录下的 typings.d.ts
import { FileManagerModelState } from '@/pages/file-manager/model';
declare module 'lodash';
function useSelector<TState = RootState, TSelected = unknown>(
selector: (state: TState) => TSelected,
equalityFn?: (left: TSelected, right: TSelected) => boolean,
): TSelected;
export interface RootState {
// loading: Loading;
fileManager: FileManagerModelState;
}
declare global {
type Nullable<T> = T | null;
}
declare module 'umi' {
export { useSelector }; // 这里很关键
}
参考自:Force Override Declarations Types #36146
4. 从一个文件导入类型 再将其导出 不加type关键字 会有warning
utils.ts
import { ColumnGroupType, ColumnType } from 'antd/es/table/index'
export type IColumnsType<RecordType = unknown> = Array<
| ColumnGroupType<RecordType>
| ColumnType<RecordType>
| { maxWidth?: number; minWidth: number }
>
export interface X {
title: string
}
index.tsx
import {
IColumnsType,
X
} from './utils'
interface IProps
extends Omit<TableProps<any>, 'onChange' | 'columns' | 'dataSource'> {
columns: IColumnsType
}
export type { IColumnsType, X }
如果直接 export { IColumnsType }
不加上 type
关键字 会有warning ,对于interface同样适用。
TypeScript export imported interface 提到 when '--isolatedModules' flag is provided it requires using 'export type'. 我的tsconfig.json并没有配置isolatedModules,我的ts@4.3.2
5. 'string' is assignable to the constraint of type 'I', but 'I' could be instantiated with a different subtype of constraint 'string | number'
查看 TypeScript 类型编程: 从基础到编译器实战 基于泛型创建新类型 - 函数调用,对函数泛型的写法感到新奇,写了个例子:
type Fn2<T> = <I extends string | number>(a: I) => T;
const f2: Fn2<string> = (a) => {
a = "scscd";
return "xxx";
};
上述的例子并不好理解,这篇问答的例子好理解点,How to fix TS2322: "could be instantiated with a different subtype of constraint 'object'"?有详细的解释。
用到的类型 数据
type Foo = { readonly 0: '0'}
type SubType = { readonly 0: '0', readonly a: 'a'}
type DiffSubType = { readonly 0: '0', readonly b: 'b'}
const foo: Foo = { 0: '0'}
const foo_SubType: SubType = { 0: '0', a: 'a' }
const foo_DiffSubType: DiffSubType = { 0: '0', b: 'b' }
定义泛型的函数:
const func = <A extends Foo>(a: A = foo_SubType) => `hello!`; //error!
出现跟上述一样的错误,翻译过来就是SubType可以指给类型A,但是A可能会被Foo的不同子类实例化。所以foo_SubType不能赋值给A。extends限定了A是Foo类型或者其子类型,但是在函数定义的时候A并不能确定是哪个子类,所以函数定义的时候在函数体内无法给a赋值具体的值。如果我想在函数体内对a进行赋值咋办?上面的函数定义方式没法对a在函数体内进行赋值。好,我们变换种形式,
type Fn3 = <A extends Foo>(a: A) => string;
const func3: Fn3 = (a: Foo = foo_SubType) => {
a = foo_SubType;
return `hello!`;
};
将函数的类型提取出来,然后再定义类型为Fn3的func3函数,然后给a指定Foo类型,这样就可以给a赋默认值或者在函数体内对a赋值。这么操作相当于把泛型变成了固定类型。如果不给a指定Foo类型,直接赋值,依然会报一样的错误。最开始的f2跟func3一个道理,如果想在函数体内给a赋值,则需要把泛型固定下来。换句话说,不固定泛型类型的函数参数是没法直接赋值的。
type Fn2<T> = <I extends string>(a: I) => T;
const f2: Fn2<string> = (a: string | boolean) => {
a = "xxx";
return "xxx";
};
上述两种函数参数泛型的写法在调用的时候没有任何区别,只是在函数体内对泛型参数的赋值有区别。
func<Foo>(foo);
func<SubType>(foo_SubType);
func<DiffSubType>(foo_DiffSubType);
func(foo);
func(foo_SubType);
func(foo_DiffSubType);
func3<Foo>(foo);
func3<SubType>(foo_SubType);
func3<DiffSubType>(foo_DiffSubType);
func3(foo);
func3(foo_SubType);
func3(foo_DiffSubType);
总结:
泛型函数参数类型不固定,所以在函数体内无法直接赋值。
6. 给指定的类型赋空对象,报错
报错信息
解决办法currentDialog: <IDialog>{},
或者 currentDialog: {} as IDialog,
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。