我们使用TypeScript是因为它使开发更安全,更快捷。
但是在我看来,TypeScript包含了很多开箱即用的功能。当切换到TS时,它们为JavaScript开发人员节省了一些时间,但从长远来看却会浪费大量时间。
我整理了一组设置和指南,以更严格地使用TypeScript。您需要习惯一下它们-将来它们将节省大量时间。
任何
从长远来看,可以为我的团队带来很多利润的最简单的规则是:
不要使用任何东西。
实际上,在任何情况下都无法描述类型而不使用任何类型。如果我发现自己不得不在一个长期项目中编写任何东西,通常会发现体系结构或其遗留代码存在问题。
使用通用类型,未知或重载,您可以忽略意外的数据错误。这些问题有时很难发现,调试起来也很昂贵。
, any, — № 3.
strict
TypeScript strict-, , , . TypeScript. , .
strict- undefined is not a function cannot read property X of null. .
-?
, strict .
strict- , . , . , , , .
, — . strict- . . . , . strict-!
, . , @ts-ignore TODO. .
// tsconfig.json file
{
// ...,
"compilerOptions": {
// a set of cool rules
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
// a shortcut enabling 6 rules above
"strict": true,
// ...
}
}
readonly
— readonly.
, , — . , Angular- : , .
? readonly .
?
, , , readonly-.
readonly :
// before
export interface Thing {
data: string;
}
// after
export interface Thing {
readonly data: string;
}
readonly :
// Before
export type UnsafeType = { prop: number };
// After
export type SafeType = Readonly<{ prop: number }>;
readonly , :
// Before
class UnsafeComponent {
loaderShow$ = new BehaviorSubject<boolean>(true);
}
// After
class SafeComponent {
readonly loaderShow$ = new BehaviorSubject<boolean>(true);
}
readonly-:
// Before
const unsafeArray: Array<number> = [1, 2, 3];
const unsafeArrayOtherWay: number[] = [1, 2, 3];
// After
const safeArray: ReadonlyArray<number> = [1, 2, 3];
const safeArrayOtherWay: readonly number[] = [1, 2, 3];
// three levels
const unsafeArray: number[] = [1, 2, 3]; // bad
const safeArray: readonly number[] = [1, 2, 3]; // good
const verySafeTuple: [number, number, number] = [1, 2, 3]; // super
const verySafeTuple: readonly [number, number, number] = [1, 2, 3]; // awesome (after v3.4)
// Map:
// Before
const unsafeMap: Map<string, number> = new Map<string, number>();
// After
const safeMap: ReadonlyMap<string, number> = new Map<string, number>();
// Set:
// Before
const unsafeSet: Set<number> = new Set<number>();
// After
const safeSet: ReadonlySet<number> = new Set<number>();
as const
TypeScript v3.4 const-assertions. , readonly-, . : .
, as const IDE .
Utility Types
TypeScript , .
TypeScript . , .
. , :
( repl.it)
import {Subject} from 'rxjs';
import {filter} from 'rxjs/operators';
interface Data {
readonly greeting: string;
}
const data$$ = new Subject<Data | null>();
/**
* source$ "Observable<Data | null>"
* "null" filter
*
* , TS , .
* "value => !!value" boolean,
*/
const source$ = data$$.pipe(
filter(value => !!value)
)
/**
*
*
* , value Data.
* , "wellTypedSource$"
*/
const wellTypedSource$ = data$$.pipe(
filter((value): value is Data => !!value)
)
// , :)
// source$.subscribe(x => console.log(x.greeting));
wellTypedSource$.subscribe(x => console.log(x.greeting));
data$$.next({ greeting: 'Hi!' });
:
typeof — JavaScript .
instanceof — JavaScript .
is T — TypeScript, . , TS’ .
:
( repl.it)
// typeof narrowing
function getCheckboxState(value: boolean | null): string {
if (typeof value === 'boolean') {
// value has "boolean" only type
return value ? 'checked' : 'not checked';
}
/**
* value “null”
*/
return 'indeterminate';
}
// instanceof narrowing
abstract class AbstractButton {
click(): void { }
}
class Button extends AbstractButton {
click(): void { }
}
class IconButton extends AbstractButton {
icon = 'src/icon';
click(): void { }
}
function getButtonIcon(button: AbstractButton): string | null {
/**
* "instanceof" TS , "icon"
*/
return button instanceof IconButton ? button.icon : null;
}
// is T narrowing
interface User {
readonly id: string;
readonly name: string;
}
function isUser(candidate: unknown): candidate is User {
return (
typeof candidate === "object" &&
typeof candidate.id === "string" &&
typeof candidate.name === "string"
);
}
const someData = { id: '42', name: 'John' };
if (isUser(someData)) {
/**
* TS , someData User
*/
console.log(someData.id, someData.name)
}
, , , . , TypeScript, , , , . .