Skip to content

Instantly share code, notes, and snippets.

@3ZsForInsomnia
Last active July 28, 2020 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 3ZsForInsomnia/60b22f19507bb7cfcb183acbdf1394e1 to your computer and use it in GitHub Desktop.
Save 3ZsForInsomnia/60b22f19507bb7cfcb183acbdf1394e1 to your computer and use it in GitHub Desktop.
Typescript expressive types workshop examples
/**
* `kind` is what is called a "discriminator", as it allows us to determine (at runtime)
* what type the returned data is, _without_ relying on introspection of the values in the data.
*
* This let's us do "if (claim.kind === 'prescription') ...", which is much less fragile or "hacky" than
* "if (Object.keys(claim).includes('surgeryType') ..."
*/
type surgicalProcedure = { kind: 'surgicalProcedure'; surgeryType: string };
type prescription = { kind: 'prescription'; refillsAllowed: number; medicationName: string };
type claimsType = surgicalProcedure | prescription;
/**
* Basic Member type
*/
interface Member {
name: string;
id: number;
}
/**
* Admins are users with a `permissions` property
*/
interface Admin extends Member {
permissions: number;
}
/**
* An ImpersonatedMember is an Admin with their ID moved to the `adminID` property, so that the `id`
* property from Member can be used normally, pulled from the impersonated member
*/
interface ImpersonatedMember extends Admin {
adminID: number;
}
/**
* Another example
*/
interface FormControlInputs {
label: string;
onChange(): any;
}
type isTextValidator = (input: any) => boolean;
interface TextInput extends FormControlInputs {
placeholder: string;
validator: isTextValidator;
onChange(): string; // this is valid because `string` is a subset of `any`
}
/**
* We know that an API list returns a `results` array, but we don't know the type of that array ahead of time.
* To solve this, we make the list a generic type. In usage, this would look like `const response: ApiList<Claim> = ....`
*/
interface ApiList<TListType> {
count: number;
next: number;
previous: number;
results: TListType[];
}
/**
* In this example, assume ButtonConfig and DescriptionSectionConfig are config classes
* for reusable components. A page's config is really the config of it and what it contains
* so in this case, the types of multiple parts of a config can be merged together to create
* a single type
*/
class ButtonConfig {
label: string;
spacing: 'small' | 'medium' | 'large';
}
class DescriptionSectionConfig {
header: string;
contents: string;
}
class MyPageConfig {
someOtherProps: object;
}
type BestConfig = ButtonConfig & DescriptionSectionConfig & MyPageConfig;
const a: BestConfig = {
label: 'abc',
spacing: 'medium',
header: 'ermagerd',
contents: 'this is some text describing ermagerd',
someOtherProps: {},
};
/**
* In this situation all props are optional - this is virtually _never_ actually the case. As a result, this
* type does not convey any information about when `a`, `b` or `c` is actually present. What might be _actually_
* the case is that `a` and `b` must be present together, otherwise `c` is present, in which case we have implicit
* rules about how this type works that must exist in _code_ rather than the actual definition of the data itself
*/
interface NonExpressiveType {
a?: string;
b?: string;
c?: string;
}
/**
* A benefitValue can come back as a number, or pre-formatted as a string
*
* This is useful when the type is discernible at runtime (unlike object or array types
* which can only have their type determined at build time). If you need to discern
* between types of objects, it is preferable to use a `discriminated union` instead.
*/
type benefitValue = number | string;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment