render as prop - use React.ElementType
interface IExampleProps {
as: React.ElementType;
}
const Example = ({ as: Element = "button" }) => {
return <Element></Element>;
};
Dynamic as
prop
/**
* Type for `as` prop that accepts any HTML element as string or any React Function Component
*
* Accepts optional interface for specifying which props the Function Component must accept
*/
export type AnyTag<Interface = any> =
| keyof JSX.IntrinsicElements
| React.FunctionComponent<Interface>
| React.ForwardRefExoticComponent<Interface>
| (new (props: Interface) => React.Component);
/**
* Type for component that accepts dynamic `as` prop to unpack the props accepted by the injected component
*/
export type PropsOf<Tag> = Tag extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[Tag]
: Tag extends React.ComponentType<infer Props>
? Props & JSX.IntrinsicAttributes
: Tag extends React.ForwardRefExoticComponent<infer Props>
? Props & JSX.IntrinsicAttributes
: never;
/**
* Type used in components with forwardRef and `as` dynamic prop to use the HTML element of `as` prop
* @todo add support for extracting HTML element of React component that use forwardRef as well
*/
export type ElementOf<Tag> = Tag extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[Tag]
: Tag extends React.ForwardRefExoticComponent<any>
? HTMLElement
: never;
/**
* Type for component props that accept it's props, spacing props, ref and `as` prop to inject other component in render
*
* **NOTE**: The props accepted by the dynamic `as` component can override internal props of UI component
* @see {@link https://stackoverflow.com/questions/54049871/how-do-i-type-this-as-jsx-attribute-in-typescript}
*/
export type UIComponentInjectableProps<
Props,
Tag extends AnyTag
> = Readonly<Props> & SpacingPropsType & PropsOf<Tag>;
export type UIComponentInjectable<Props, Tag extends AnyTag> = React.FC<
UIComponentInjectableProps<Props, Tag>
>;
Component that can be used only in specific parent component as a child.
For example: <List>
(can have) => <ListItem>
- Make a component that doesn't render anything and throws an error instead. Set a property on the function
ListItem.parent = AllowedParentComponent
so that we can check it later. - Make another component
Private*
that has the same props type and renders the content - Inside the parent component when iterating over children (by using toArray) check for
child.type
andchild.type.parent
type ListItemProps = {
children: ReactText;
} & JSX.IntrinsicElements["li"];
type ChildWithParent = ReactElement & {
type: { parent: ReactElement };
props: ListItemProps;
};
const ListItem = (_props: ListItemProps) => {
throw new Error("ListItem musi być dzieckiem List");
};
ListItem.parent = List;
export const List = ({ children }: ListProps) => {
const newChildren = flattenChildren(children).map((child: unknown) => {
const child = child as ChildWithParent;
if (isValidElement(child)) {
if (child.type === ListItem && child.type.parent === List) {
return (
<PrivateListItem
key={child.key}
{...(child.props as ListItemProps)}
/>
);
} else {
throw new Error("Tylko ListItem może być dzieckiem List");
}
}
});
return <ul>{newChildren}</ul>;
};
const ListItemWrapper = (props: ListItemProps) => <ListItem {...props} />;
ListItemWrapper.parent = List;
Note: flattenChildren
is provided by react-keyed-flatten-children
Code from this blog
install plugin @typescript-eslint
npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser
set parser to @typescript-eslint/parser
some eslint rules can be problematic:
no-undef
- disable altogether as TypeScript has its own chek for thatno-unused-vars
- use@typescript-eslint/no-unused-vars
insteadno-use-before-define
- use@typescript-eslint/no-use-before-define
instead
zbiór stałych wartości, domyślnie numerowany od 0
żeby używać jako typ union wszystkich wartości:
interface SomeInterface {
type: keyof typeof MyTypes;
}
const foo = <T extends unknown>(x: T) => x;
// or
const foo = <T>(x: T) => x;
aka. forwardRef with generics
const SelectWithRef = forwardRef(
<Option extends string>(
props: Props<Option>,
ref?: Ref<HTMLSelectElement>
) => <Select<Option> {...props} forwardedRef={ref} />
);
<A extends B>
means:
A
is a superset of B
A
has all of B
properties (and maybe some more)
A
is possibly more specific version of B
A musi zawierać te same propertisy co B żeby przeszedł typecheck.
A extends { meow(): void } ? A : never
przejdzie tylko jak object który przekażemy będzie miał funkcję meow
, z pasującym typem - jak nie to never
, czyli nie może przejść
można pomyśleś że conditional type działa jak funkcja JSowa:
type isNumber<T> = T extends number ? "number" : "other";
const isNumber = (x) => {
typeof x === "number" ? "number" : "other";
};
infer pozwala na użycie generycznego typu w conditional types i wyciągnięcie go
type PropsOf<T> = T extends React.ComponentType<infer Props> ? Props : never;
infer używa nazwy podanego typu np:
type Unpack<A> = A extends Array<infer E> ? E : A;
type Test = Unpack<Apple[]>;
// => Apple
type Test = Unpack<Apple>;
// => Apple
tutaj zwróciło Apple nawet jak podaliśmy array Apple
- Using
any
is like saying "I have no idea what this value looks like. So, TypeScript, please assume I'm using it correctly, and don't complain if anything I do seems dangerous". - Using
unknown
is like saying "I have no idea what this value looks like. So, TypeScript, please make sure I check what it is capable of at run time." never
is the bottom type (it literally means it can never happen)
Creates new interface from union of keys or another interface with keyof
(keyof turns interafece into union)
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Typescript posiada gotowe mapped types do częstych przypadków
Partial<Type>
- wszystkie parametry stają się opcjonalneRequired<Type>
- odwrotnie do Partial, wszystko jest wymaganeReadonly<Type>
- dodaje do każdego parametru readonlyRecord<Keys, Type>
- tworzy typ obiektu mappując keys na możliwe wartości podane w `Types
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const nav: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" },
};
Pick<Type, Keys>
- wybiera wybrane keys z podanego typu
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Omit<Type, Keys>
- działa odwrotnie do Pick, wybiera wszystkie keys z typu poza podanymi
Funfact: Typescript pod spodem używa tak naprawdę conditional types + infer np.
// Obtain the parameters of a function type in a tuple
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
Use an intersection - &
type SomeType = {
[P in keyof T]?: T[P];
} & { key: value };
Source Sometimes TypeScript DOM types are broken (either spec is not up to date or other stuff)
To fix types:
- Add global declaration in your file or
- Add declaration for whole project, into folder that is defined in
typeRoots
declare global {
// opening up the namespace
var ResizeObserver: {
// mergin ResizeObserver to it
prototype: ResizeObserver;
new (callback: ResizeObserverCallback): ResizeObserver;
};
}
if adding for whole project make shure that your type definitions file is included inside typeRoots
:
{
"compilerOptions": {
//...
"typeRoots": ["@types", "./node_modules/@types"]
//...
},
"include": ["src", "@types"]
}
Create an object that will have predefined set of keys (for type checking) that can have one type (string) and specified value.
Example:
const filterFunctions = {
date: (value, index) => true,
string: (value, index) => false,
};
Solution:
type FilterFn = (data: SomeDataType, index: number) => boolean;
function filterFunctionsWrapper<T extends { [nkeyame: string]: FilterFn }>(
functions: T
) {
return functions;
}