"Enhancing Your React with Advanced TypeScript", Titian Cernicova-Dragomir
fwdays
260 views
25 slides
Oct 19, 2024
Slide 1 of 25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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...
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 component behaviors. You'll gain valuable insights into how TypeScript can improve your React components, providing a foundation to explore and apply these powerful features in your projects.
Size: 6.19 MB
Language: en
Added: Oct 19, 2024
Slides: 25 pages
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