We use TypeScript because it makes development safer and faster.
But in my opinion, TypeScript contains too many indulgences out of the box. They help save a little time for JavaScript developers when switching to TS, but eat up a lot of time in the long run.
I have put together a set of settings and guidelines for a more rigorous use of TypeScript. You need to get used to them once - and they will save a lot of time in the future.
any
The simplest rule that gives my team a lot of profit in the long run:
Don't use any.
There are practically no situations when you cannot describe a type instead of using any. If I find myself in a situation where I have to write any in a long-term project, I usually find problems in the architecture or itβs legacy code.
Use generic types , unknown or overloads and you can forget about unexpected errors when working with data. Such problems are sometimes difficult to catch and quite expensive to debug.
, 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, , , , . .