OneForm icon indicating copy to clipboard operation
OneForm copied to clipboard

Add TypeScript support

Open souldreamer opened this issue 2 years ago • 6 comments

Gave you access to the repository containing the current Typescript work, if you want to take a look. Started from the main OneForm component and trying to get everything to have types from there, typing error by typing error.

If you look over it and have any questions / want to discuss things, ask away. 😄

As for your questions in the other issue:

Generics

In typed languages, you sometimes (often actually) want to indicate that there is an entire family of functions with the same name, but different parameter types. useState and useRef are good examples. In TS they're actually generic functions that depend on the type of the data you pass into them. So for example ref.current will be the same type as the initial data you pass in to useRef. Generally TS figures that out itself when you give useRef a starting data point, but you might want to clarify at times or when you don't give it a starting value, so you'll call it as useRef<DataType>(initial). (useRef is actually a special case as it's overloaded to provide a version for when you don't give it an initial value, but in that case it assumes that ref.current is DataType | undefined). It's pretty well explained on the TS docs site here

Record

It's pretty much just a short-hand way to say that it's an object dictionary. It has two generic arguments, key type and value type, where key type is a combination of string, number, and symbol (as those are the only key types an object takes in JS). You can also write it long-form as Record<string, string> = { [key: string]: string }, but Record is usually more readable. (The long form is definitely useful for more specific cases, like defining extra specific keys, or using template string types for the key, like: { [key: `data-${string}`]: string } being a good type for the HTML data-* elements.

Pick

Typescript has some utility types that ( docs here ) that help with defining things based on existing types. Pick does just as its name suggests and creates a sub-type from a given type that only contains some of its keys. So given an interface Foo { a: number, b: string, c: any }, Pick<Foo, 'a'> will create a type for {a: number} and Pick<Foo, 'a' | 'c'> will create a type for { a: number, c: any }. This is very useful when you have certain connected types. In the example I gave ( ({ groupValidation, identifierGroup }: Pick<IdentifierGroup, 'groupValidation' | 'identifierGroup'>) ), I could have written down the actual type of { groupValidation, identifierGroup }, but given that they'll always be a subset of an IdentifierGroup, if I feel the need to change what their types are in IdentifierGroup later (and as my understanding of the actual code grows, it's likely to happen), using Pick will actually update them here too and I can see if the new type works out with the existing code. While I could easily have typed them as IdentifierGroup directly, that type has extra required properties that you don't always pass around in your code.

Related utility types to Pick are Omit, Exclude, and Extract. You can think of Pick as an allow-list for keys, Extract as an allow-list for types that match, Omit as a block-list for keys, and Exclude as a block-list for types that match.

souldreamer avatar Sep 08 '21 00:09 souldreamer