This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the javascript category.
Last Updated: 2024-11-23
The trick is to use typeof
and then index it with the number
type:
const sizes = ['small', 'medium', 'large'] as const;
// 👇️ type SizesUnion = "small" | "medium" | "large"
type SizesUnion = typeof sizes[number];
type T0 = "a" | "b" | "c"
type T1 = Exclude<T0, "a">; // Omit won't work here
// type T1 = "b" | "c"
type T0 = string | number | (() => void)
// `Function` is just a union of one here
type T1 = Extract<T1, Function>;
// type T1 = () => void
Use never
for a property on an interface and then the union operator for two mutually exclusively possible prop sets
interface PropsBase {
headline?: string,
className?: string,
}
interface PropsWithText extends PropsBase {
text: string
children?: never
}
interface PropsWithTextReplacementChildren extends PropsBase {
text?: never,
children: React.ReactNode,
}
type Props = PropsWithText | PropsWithTextReplacementChildren;
type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>
// type ShoutyGreeting = "HELLO, WORLD"
type ASCIICacheKey<Str extends string> = `ID-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">
type MainID = "ID-my-app"
// Any time isFish is called with some variable, TypeScript will narrow that variable to that specific type if the original type is compatible.
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
See if they include types by looking for index.d.ts
files or typings
entry in package.json
. If available, opening up that file will reveal what many of they types are
Otherwise use something like "Definitely Typed: to get types externally via open source contributions(e.g. yarn add --dev @types/react
). If these types get out of date due to you updating the main package, simply uninstall the @types/x
package and then reinstall, letting yarn
or npm
figure out the right version.
If neither is available, go DIY by declaring a declarations.d.ts
with code for your libraries
You can give TypeScript types for code that exists elsewhere (e.g. written in JavaScript) using the declare keyword.
e.g. if, in your main ts code, you'd like to do
import foo from "foo-module";
foo();
but the foo
library has no types, then you can define a "module" with those types
// declarations.d.ts
declare module "foo-module" {
function foo(): void;
export = foo;
}
Note that this does not define the actual functionality - just types for it.
declare var foo: any;
// In actual code
foo = 123; // allowed
If you want to use a namespace from a package as a type, import the library (eg. import * as firebaseNamespace from firebase
) and then call typeof firebaseNamespace
for typing.
If you know more about a type than typescript does, you can force it to interpret a type a certain way - e.g. when it complained that timerId
might be a boolean, you could do clearInterval(timerId as unknown as number);
The bit with the as unknown
in the center is necessary in cases where the types have insufficient overlap.
useState
with nullThe following will wrongly infer the type of timerId to be null
const [timerId, setTimerId] = useState(null)
How to make it realize it could be number too?
const [timerId, setTimerId] = useState<number | null>(null)
Due to JS weirdness, don't use object
as a type since it just means "any non-nullish value". Instead use Record<string, unknown>
or a description of the object itself:
type PersonObj = {
name: string,
age: number,
subscribed?: boolean
}
type CatName = "miffy" | "boris" | "mordred";
interface CatInfo {
age: number;
breed: string;
}
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
};
const person = {age: 10, name: "jack"}
type PersonKey = keyof typeof person
// type PersonKey = "age" | "name|
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
Partial
)interface Props {
a?: number;
b?: string;
}
const obj2: Required<Props> = { a: 5 };
// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
interface Todo {
title: string;
description: string;
completed: boolean;
}
// Just expect the "title" and "completed" properties
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
// Everything except `description`
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
createdAt: 1615544252770,
};
Use typescript-eslint
to prevent stupidness.
If multiple functions take the same types, you can define a type for that function
type TakePhotoCallback = ({exif, uri}: {exif?: Record<string,unknown>, uri: string}) => unknown;
// Notice that this type definition is _used_ after the initial variable name (not after the
// function arguments)
const saveData: TakePhotoCallback = async ({exif, uri})=> {
}
Sometimes typescript has the wrong interface built in and you have to override it. This involves creating a global.d.ts
file, including the overrides
declare global {
interface FormDataValue {
uri: string;
name: string;
type: string;
}
interface FormData {
append(name: string, value: FormDataValue, fileName?: string): void;
set(name: string, value: FormDataValue, fileName?: string): void;
}
}
// I don't know why, but this last line was necessary
export {};
Then import this file in the main entry point for your code
import "./global.d.ts"
If you have then()
chain, your type will be Promise<number>
- where number
is replaced with whatever then
returns.
To get the awaited item, wrap in Awaited
, e.g. Awaited<Promise<number>>
will be number
.
You can give default types to generic type definitions using T = DefaultType
type Classification = {category: string, probability: number};
type PostResponse<T = Classification> = {data: T, status: number};
function centerMap(lng: number, lat: number) { }
type CenterMapParams = Parameters<typeof centerMap>
// type CenterMapParams = [lng: number, lat: number]
export type UsePushNotificationsReturnValue = ReturnType<typeof usePushNotifications>
Say you have this function
useLocation() {
return {
x: 20,
getGeolocation,
}
}
type UseLocationProps = ReturnType<typeof useLocation>
// type UseLocationProps = {x: number, getGeolocation: () => void]
You need to access with ['X']
notation, not .X
notation
ReturnType<typeof useLocation>['getGeolocation]`
null
and undefined
from a typetype T0 = NonNullable<string | number | undefined>;
// type T0 = string | number