"Enhancing Your React with Advanced TypeScript", Titian Cernicova-Dragomir

fwdays 260 views 25 slides Oct 19, 2024
Slide 1
Slide 1 of 25
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25

About This Presentation

This talk will introduce you to some advanced TypeScript techniques that can make your React components more expressive and predictable. We’ll cover union types, discriminated unions, and conditional types, providing practical examples that demonstrate how these tools can help model complex compon...


Slide Content

Enhancing Your React with Advanced TypeScript React + fwdays ’ 24 October 19, 2024 Titian Cernicova-Dragomir Software Engineer, TypeScript/JavaScript Infrastructure TypeScript compiler contributor

What we will talk about today Should you use advanced types? Discriminated unions Mapped types Final thoughts

Should you use advanced types at all?

Don’t trust influencers… With simple views Things are always more complicated and nuanced Experts usually answer with: “It depends.” Unfortunately, that is not usually favored by the almighty algorithm

So, should I use complicated types? Depends what you mean by complicated types When you start out, everything is complicated Make sure you have enough experience with types Make sure the pattern you are using: Is well-documented

Discriminated unions Unions are a staple of TypeScript programming What makes a union discriminated? It has a field that uniquely selects a union constituent The field should usually be of a literal type

Demo: Discriminated unions as parameters

Discriminated unions Unions are a staple of TypeScript programming What makes a union discriminated ? It has a field that uniquely selects a union constituent The field should usually be of a literal type Both objects and tuples can be discriminated

Demo: Tuple discriminated union

So, should I use complicated types? Depends what you mean by complicated types When you start out, everything is complicated Make sure the pattern you are using: Is well-documented Is worth the extra complexity Usually, a case where we can improve the call site DX The code is going to be reused a lot

Generics – Introduction A function can have type parameters We can use the type parameter as a type anywhere in the function Type parameters can capture information from the call site We can use type parameters to tie together: Some parameters types to other parameter types Some parameters types to the return type function getValue < T >() { } function getValue < T >( p : T ) { } getValue ( 10 ) // T = number function getValue < T >( p : T | undefined , defaultValue : T ) { } let value : number | undefined ; getValue ( value , 10 ) // p: number | undefined,defaultValue : T getValue ( value , "1" ) // Error function getValue < T >( p : T | undefined , defaultValue : T ): T let value : number | undefined ; getValue ( value , 10 ) // Returns number

Demo: Generic Dialog Hook

So, should I use complicated types? Depends what you mean by complicated types When you start out, everything is complicated Make sure you have enough experience with types Make sure the pattern you are using: Is well-documented Is worth the extra complexity Can be understood by others

Make sure the pattern you are using can be understood by others Complicated conditional and mapped types are: Often hard to understand Even for the person that wrote them

Make sure the pattern you are using can be understood by others type PrimitiveSchemaToType < T extends PrimitiveSchemaDefinition > = PrimitiveTypeNameToType [ T [ 'type' ]]; type SchemaDefinition = PrimitiveSchemaDefinition | ObjectSchemaDefinition  | EnumSchemaDefinition < any > | ArraySchemaDefinition < any > | UnionSchemaDefinition < any > type SchemaToType < T extends SchemaDefinition > =     T extends PrimitiveSchemaDefinition ? PrimitiveSchemaToType < T > :     T extends ObjectSchemaDefinition ? ObjectSchemaToType < T > :     T extends EnumSchemaDefinition < infer U > ? U :     T extends ArraySchemaDefinition < infer U > ? SchemaToType < U >[] :     T extends UnionSchemaDefinition < infer U >? SchemaToType < U >:     never ; type ObjectSchemaToTypeOptionalPart < T extends ObjectSchemaDefinition > = {     - readonly [ P in keyof T [ 'fields' ] as T [ 'fields' ][ P ] extends { required : false }? P : never ]?: SchemaToType < T [ 'fields' ][ P ]> } type ObjectSchemaToTypeRequiredPart < T extends ObjectSchemaDefinition > = {     - readonly [ P in keyof T [ 'fields' ] as T [ 'fields' ][ P ] extends { required : false }? never : P ]: SchemaToType < T [ 'fields' ][ P ]> } 😃

Make sure the pattern you are using can be understood by others type PrimitiveSchemaToType < T extends PrimitiveSchemaDefinition > = PrimitiveTypeNameToType [ T [ 'type' ]]; type SchemaDefinition = PrimitiveSchemaDefinition | ObjectSchemaDefinition  | EnumSchemaDefinition < any > | ArraySchemaDefinition < any > | UnionSchemaDefinition < any > type SchemaToType < T extends SchemaDefinition > =     T extends PrimitiveSchemaDefinition ? PrimitiveSchemaToType < T > :     T extends ObjectSchemaDefinition ? ObjectSchemaToType < T > :     T extends EnumSchemaDefinition < infer U > ? U :     T extends ArraySchemaDefinition < infer U > ? SchemaToType < U >[] :     T extends UnionSchemaDefinition < infer U >? SchemaToType < U >:     never ; type ObjectSchemaToTypeOptionalPart < T extends ObjectSchemaDefinition > = {     - readonly [ P in keyof T [ 'fields' ] as T [ 'fields' ][ P ] extends { required : false }? P : never ]?: SchemaToType < T [ 'fields' ][ P ]> } type ObjectSchemaToTypeRequiredPart < T extends ObjectSchemaDefinition > = {     - readonly [ P in keyof T [ ' fds ' ] as T [ 'fields' ][ P ] extends { required : false }? never : P ]: SchemaToType < T [ 'fields' ][ P ]> } 😕 😕 😕 😕 😕 😕

Make sure the pattern you are using can be understood by others Complicated nested conditional and mapped types are: Often hard to understand Hard to debug No built-in debugger Split and hover debugging only 😞

Make sure the pattern you are using can be understood by others Complicated nested conditional and mapped types are: Often hard to understand Hard to debug Prefer built-in mapped and conditional types whenever possible Change modifiers: Partial<T>, Readonly <T>, Required<T> Manipulate properties: Pick<T>, Omit<T>, Record<T, K> Extract types: Parameters<T>, ReturnType <T>, InstanceType <T>

Anatomy of a mapped type Iterate over a union of keys Often paired with keyof – To get the keys of an existing object type We can change the type of each property We often index into the original type to the type of the original property

Demo: Form with validation

Demo – Form with validation Was that example overly complicated? Not if you know the syntax The problems usually come when we layer: Mapped types on top of other complex types Conditional types on top of other complex types What kind of problems? Readability and maintainability Compiler performance IDE performance

You probably shouldn’t Obsess about generic function implementation type safety Complex types generally require some type assertions You are optimizing for caller DX Use complex types for single use code Not worth the cost

You probably shouldn’t – Xtreme edition Create your own class system ES already has classes If you already have it as a legacy You might benefit from some advanced types to help you migrate Work on migrating Parsing other languages in the type system Will probably make your complier slow Will probably generate hard to read errors Anything that takes advantage of the fact that the TypeScript type system is Turing-complete

You probably should Get familiar with more advanced TypeScript techniques Even if you don’t write them, you will still benefit from understanding them Use conditional/mapped types in library code To improve DX for your users. Make sure your team also can understand and maintain your advanced TypeScript types

Thank you for listening! Questions? @TitianCernicova