React
React.FC
import React, { HTMLAttributes, PropsWithChildren } from "react";
interface IHelloProps extends HTMLAttributes<HTMLDivElement> {
name: string;
}
const Hello: React.FC<PropsWithChildren<IHelloProps>> = ({
name,
children,
...rest
}) => {
return (
<div>
<div {...rest}>{`Hello, ${name}!`}</div>
{children}
</div>
);
};
- Use
PropsWithChildren
forIHelloProps
injectchildren
type Use
React.FC
to declare the component and pass in the component through the generic parameterProps
type- Note:
react@16
type definitionReact.FC
comes withchildren
type, no additional processing is required (step 1 can be omitted)
- Note:
- 若
html
属性,如className
、style
等,可以直接extends HTMLAttributes<HTMLDivElement>
,其中HTMLDivElement
可Replace with the desired type, such asHTMLInputElement
React.FC is not recommended?
Remove React.FC from Typescript template #8177
In this PR, React.FC of the CRA default template has been removed, mainly for the following reasons:
- children are implicitly defined
- Cannot support generic components
- Mounting static properties is more complicated, such as
<Select.Option>
- Problem with defaultProps
There is only one benefit:
- Return value constraints are provided
So you can choose whether or not to use React.FC. Generic components and mounting static properties belong to low-frequency scenarios. If you meet React.FC, you can do it~
As for defaultProps, it is basically no longer used in business code (replaced with default values), and the latest React.FC has removed the built-in children
If there are clear type requirements for the return value and typescript rules are configured, then React.FC can be used, and in other cases, the Props interface can be defined directly, as follows:
import React, { HTMLAttributes, PropsWithChildren } from "react";
interface IHelloProps extends HTMLAttributes<HTMLDivElement> {
name: string;
}
const Hello = ({ name, children, ...rest }: PropsWithChildren<IHelloProps>) => {
return (
<div>
<div {...rest}>{`Hello, ${name}!`}</div>
{children}
</div>
);
};
React.forwardRef
React provides forwardRef
function for forwarding Ref, which can also pass in generic parameters, as follows:
import { forwardRef, PropsWithChildren } from "react";
interface IFancyButtonProps {
type: "submit" | "button";
}
export const FancyButton = forwardRef<
HTMLButtonElement,
PropsWithChildren<IFancyButtonProps>
>((props, ref) => (
<button ref={ref} className="MyClassName" type={props.type}>
{props.children}
</button>
));
React.ComponentProps
Tool generics for getting component props, similar to:
- React.ComponentPropsWithRef
- React.ComponentPropsWithoutRef
import { DatePicker } from "@douyinfe/semi-ui";
type SemiDatePikerProps = React.ComponentProps<typeof DatePicker>;
export const DisabledDatePicker: React.FC = () => {
const disabledDate: SemiDatePikerProps["disabledDate"] = (date) => {
// ...
};
return <DatePicker disabledDate={disabledDate} />;
};
When using third-party library components, do not use specific paths to reference types (if the third-party components are subsequently upgraded to modify the internal file reference path, an error will occur).
import { InputProps } from "@douyinfe/semi-ui/input"; // ×
import { InputProps } from "@douyinfe/semi-ui"; // √
If the entry file does not expose the relevant type declaration of the corresponding component, use React.ComponentProps
import { Input } from "@douyinfe/semi-ui";
type InputProps = React.ComponentProps<typeof Input>;
Another example:
Type narrows
In some scenarios, the parameter passed in is a union type, and its type needs to be narrowed (Narrowing) based on some means.
function printAll(strs: string | string[] | null) {
if (strs && typeof strs === "object") {
// strs 为 string[]
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
// strs 为 string
console.log(strs);
}
}
Use type predicates: is
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
Think about Lodash's isBoolean
/ isString
/ isArray
and other functions, and then think about what is wrong with isEmpty
.
interface LoDashStatic {
isBoolean(value?: any): value is boolean;
isString(value?: any): value is string;
isArray(value?: any): value is any[];
isEmpty(value?: any): boolean; // 这里的定义会使得业务中时使用出现什么问题?
}
Type-safe redux actions
The author does not use redux, here is only a demonstration
TS Playground - An online editor for exploring TypeScript and JavaScript
interface ActionA {
type: "a";
a: string;
}
interface ActionB {
type: "b";
b: string;
}
type Action = ActionA | ActionB;
function reducer(action: Action) {
switch (action.type) {
case "a":
return console.info("action a: ", action.a);
case "b":
return console.info("action b: ", action.b);
}
}
reducer({ type: "a", a: "1" }); // √
reducer({ type: "b", b: "1" }); // √
reducer({ type: "a", b: "1" }); // ×
reducer({ type: "b", a: "1" }); // ×
- How to type Redux actions and Redux reducers in TypeScript?
- For more narrowing methods, please refer to the official documentation - TypeScript Documentation - Narrowing
Multi-parameter type constraints
Take the very familiar window.addEventListener
as an example:
// e 为 MouseEvent
window.addEventListener("click", (e) => {
// ...
});
// e 为 DragEvent
window.addEventListener("drag", (e) => {
// ...
});
It can be found that the input parameter type (event) of the callback function of addEventListener
will vary with the listening event. The function signature of addEventListener
is as follows:
addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
type
is the generic type K, which is constrained to the key range of WindowEventMap
, and then deduce the ev event type from WindowEventMap
based on K.
Of course you can also choose to use union types, like redux actions do.
Common tool generics
After understanding the basic content of TypeScript (keyof/in/extends/infer), you can try to implement the built-in tool generics by yourself to achieve a deeper understanding.
interface Person {
name: string;
age: number;
address?: string;
}
- Partial<T>. make all fields optional
type PartialPerson = Partial<Person>;
// ↓
type PartialPerson = {
name?: string | undefined;
age?: number | undefined;
address?: string | undefined;
};
- Required<T>. Make all fields required
type RequiredPerson = Required<Person>;
// ↓
type RequiredPerson = {
name: string;
age: number;
address: string;
};
- Pick<T, K extends keyof T>. Take part of the attribute K from T
type PersonWithoutAddress = Pick<Person, "name" | "age">;
// ↓
type PersonWithoutAddress = {
name: string;
age: number;
};
- Omit<T, K extends keyof T>. remove part of attribute K from T
type PersonWithOnlyAddress = Omit<Person, "name" | "age">;
// ↓
type PersonWithOnlyAddress = {
address?: string | undefined;
};
- Exclude<T, U>. exclude from T those types assignable to U
The generic implementation needs to master Distributive Conditional Types
type T = Exclude<1 | 2, 1 | 3>; // -> 2
- Extract<T, U>. extract those types from T that are assignable to U
The generic implementation needs to master Distributive Conditional Types
type T = Extract<1 | 2, 1 | 3>; // -> 1
- Parameters. Get function parameter type
declare function f1(arg: { a: number; b: string }): void;
type T = Parameters<typeof f1>;
// ↓
type T = [
arg: {
a: number;
b: string;
}
];
- ReturnType. Get function return value type
declare function f1(): { a: number; b: string };
type T = ReturnType<typeof f1>;
// ↓
type T = {
a: number;
b: string;
};
- Record<K, T>. Convert the values of all attributes in K to type T
Understand each tool generic as a function, and the type can be used as the input parameter and return value. You can also read the specific implementation by cmd + left-click on the specific tool generic.
- For the specific implementation of tool generics, please read: TS Use of some tool generics and their implementation
- For more built-in utility generics, please refer to: TypeScript Documentation - Utility Types
Recommended reading
- The use and implementation of some tool generics in TS
- 22 Examples Deep Dive into Ts' Most Obscure Advanced Type Tool
- The minimum TypeScript you need for React
- tsconfig common configuration analysis
- TypeScript Type Tricks - Multiple Argument Type Constraints
- Typescript Tips: Dynamic overloading implements a cheap version of dependent type
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。