You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/typescript.md
+303-3Lines changed: 303 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -2,13 +2,313 @@
2
2
3
3
These guidelines specifically apply to TypeScript.
4
4
5
-
## Provide explicit return types for functions/methods
5
+
## Types
6
+
7
+
### Type Inference
8
+
9
+
TypeScript is very good at inferring types. A well-maintained codebase can provide strict type safety with explicit type annotations being the exception rather than the rule.
10
+
11
+
When writing TypeScript, type inferences should generally be preferred over type declarations and assertions. There are several reasons for this:
12
+
13
+
#### Advantages
14
+
15
+
- Explicit type annotations (`:`) and type assertions (`as`, `!`) prevent inference-based narrowing of the user-supplied types.
16
+
- The compiler errs on the side of trusting user input, which prevents it from providing additional, and sometimes crucial, type information that it could otherwise infer.
17
+
<!-- TODO: Expand into entry. Add example. -->
18
+
- In TypeScript v4.9+ the `satisfies` operator can be used to assign type constraints that are narrowable through type inference.
19
+
- Type inferences are responsive to changes in code without requiring user input, while annotations and assertions rely on hard-coding, making them brittle against code drift.
20
+
- The `as const` operator can be used to further narrow an inferred abstract type into a specific literal type.
There is a clear exception to the above: if an explicit type annotation or assertion can narrow an inferred type further, thereby improving its accuracy, it should be applied.
47
+
48
+
##### Avoid widening a type with a type annotation
49
+
50
+
> **Warning**<br />
51
+
> Double-check that a declared type is narrower than the inferred type.<br />
52
+
> Enforcing an even wider type defeats the purpose of adding an explicit type annotation, as it _loses_ type information instead of adding it.
53
+
54
+
🚫
55
+
56
+
```ts
57
+
const chainId:string=this.messagingSystem(
58
+
'NetworkController:getProviderConfig',
59
+
).chainId; // Type 'string'
60
+
```
61
+
62
+
✅
63
+
64
+
```ts
65
+
const chainId =this.messagingSystem(
66
+
'NetworkController:getProviderConfig',
67
+
).chainId; // Type '`0x${string}`'
68
+
```
69
+
70
+
##### When instantiating an empty container type, provide a type annotation
71
+
72
+
This is one case where type inference is unable to reach a useful conclusion without user-provided information. Since the compiler cannot arbitrarily restrict the range of types that could be inserted into the container, it has to assume the widest type, which is often `any`. It's up to the user to narrow that into the intended type with an explicit type annotation.
73
+
74
+
🚫
75
+
76
+
```ts
77
+
const tokens = []; // Type 'any[]'
78
+
const tokensMap =newMap(); // Type 'Map<any, any>'
79
+
```
80
+
81
+
✅
82
+
83
+
```ts
84
+
const tokens:string[] = []; // Type 'string[]'
85
+
const tokensMap =newMap<string, Token>(); // Type 'Map<string, Token>'
86
+
```
87
+
88
+
##### Type guards and null checks can be used to improve type inference
89
+
90
+
<!-- TODO: Add explanation and examples -->
91
+
92
+
```ts
93
+
function isSomeInterface(x:unknown):xisSomeInterface {
94
+
return (
95
+
'name'inx&&
96
+
typeofx.name==='string'&&
97
+
'length'inx&&
98
+
typeofx.length==='number'
99
+
);
100
+
}
101
+
102
+
function f(x:SomeInterface|SomeOtherInterface) {
103
+
if (isSomeInterface(x)) {
104
+
// Type of x: 'SomeInterface | SomeOtherInterface'
105
+
console.log(x.name); // Type of x: 'SomeInterface'. Type of x.name: 'string'.
Typeassertionsmakethecodebrittleagainstchanges. WhileTypeScriptwillthrowtypeerrors against some unsafe or structurally unsound type assertions, it will generally accept the user-supplied type without type-checking. This can cause silent failures where errors are suppressed, even though the type's relationship to the rest of the code, or the type itself, has been altered so that the type assertion is no longer valid.
`any`isthemostdangerousformofexplicittypedeclaration, and should be completely avoided if possible.
153
+
154
+
-`any`doesn't represent the widest type, or indeed any type at all. `any` is a compiler directive for _disabling_ static type checking for the value or type to which it'sassigned.
-`any`subsumesallothertypesitcomesintocontactwith. Anytypethat is in a union, intersection, is a property of, or has any other relationship with an `any` type or value is erased and becomes an `any` type itself.
-However, whentypingtheassigned, `unknown`can't be used to replace `any`, as `unknown` is only assignable to `unknown`. In this case, try `never`, which is assignable to all types.
177
+
<!--TODO: Addexample-->
178
+
179
+
##### Don't allow generic types to use `any` as a default argument
// Argument of type '(origin: any, type: any) => void' is not assignable to parameter of type '(networkClientId: string) => NetworkConfiguration | undefined'.
214
+
// Target signature provides too few arguments. Expected 2 or more, but got 1.ts(2345)
Inmosttypeerrors involving property access or runtime property assignment, `any` usage can be avoided by substituting with `as unknown as`.
222
+
223
+
🚫
224
+
225
+
```ts
226
+
for (const key of getKnownPropertyNames(this.internalConfig)) {
227
+
(this as any)[key] = this.internalConfig[key];
228
+
}
229
+
230
+
delete addressBook[chainId as any];
231
+
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ [chainId: `0x${string}`]: { [address: string]: AddressBookEntry; }; }'.
232
+
// No index signature with a parameter of type 'string' was found on type '{ [chainId: `0x${string}`]: { [address: string]: AddressBookEntry; }; }'.ts(7053)
233
+
```
234
+
235
+
✅
236
+
237
+
```ts
238
+
for (const key of getKnownPropertyNames(this.config)) {
239
+
(this as unknown as typeof this.config)[key] = this.config[key];
240
+
}
241
+
242
+
delete addressBook[chainId as unknown as `0x${string}`];
243
+
```
244
+
245
+
However, whenassigningtoagenerictype, using`as any`istheonlysolution.
246
+
247
+
🚫
248
+
249
+
```ts
250
+
(state as RateLimitState<RateLimitedApis>).requests[api][origin] = previous + 1;
251
+
// is generic and can only be indexed for reading.ts(2862)
252
+
```
253
+
254
+
✅
255
+
256
+
```ts
257
+
(state as any).requests[api][origin] = previous + 1;
258
+
```
259
+
260
+
Eveninthiscase, however, `any`usagemightbeavoidablebyusing`Object.assign`orspreadoperatorsyntaxinsteadofassignment.
-Recommended: Use`unknown`withtypeguards like `isJsonRpcError`.
297
+
- Avoid typing an error object with `any` if it is passed on to be used elsewhere instead of just being thrown, as the `any` type will infect the downstream code.
0 commit comments