export declare interface AppProps {
children: React.ReactNode; // best, accepts everything (see edge case below)
style?: React.CSSProperties; // to pass through style props
onChange?: React.FormEventHandler; // form events! the generic parameter is the type of event.target
props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
props2: Props & React.ComponentPropsWithRef; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
Types or Interfaces
优先使用 interface。Use Interface until You Need Type。
在工具库或者三方库中使用 interface 定义类型,因为 interface 易于扩展。
TS 官网:Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.
Function Components
为什么不建议使用 React.FC?React.FC 和 React.VFC 有何区别?
现在的共识是 React.FunctionComponent(React.FC) 是不推荐使用的。
FC 的具有明确的返回值类型,然而普通函数的返回值不明确的。
FC 为一些静态属性如 displayName、propTypes 和 defaultProps 提供类型检查和自动补全。已知在 FC 中使用 defaultProps 有一些已知的问题。
FC 为 children 提供了明确的定义,但是这样会有一些已知的问题。
使用 React.VFC 来代替
如果你想显示定义 children 的类型,可以使用 React.VoidFunctionComponent(React.VFC)。这是在 FC 默认接受没有 children 之前的一个临时方案。
Hooks
如果 state 即将被初始化并且总有一个返回值,可以在 setState 中使用类型声明。这将暂时欺骗 ts 编译器 {} 是 IUser 类型。你应该随后更新这个状态,否则后面代码的执行依赖于 user 是 IUser 的类型可能会引起运行时错误。
const [user, setUser] = React.useState<IUser>({} as IUser);
setUser(newUser);
在 ts 中,React.Component 是一个泛型(generic type)(React.Component<PropType, StateType>)。初始化 state 时二次声明 state 的类型,有利于类型推断。这是因为 state 的第二次泛型参数可以使 this.setState() 正常工作,因为 setState 来自于基类(React.Component),但是我们在初始化 state 时重写了基类的实现,所以二次声明 state 类型是为了告诉 ts 编译器类型没变。
Changing the value of <input>, <select> and <textarea> element.
ClipboardEvent
Using copy, paste and cut events. 使用复制、粘贴和剪切事件
CompositionEvent
Events that occur due to the user indirectly entering text (e.g. depending on Browser and PC setup, a popup window may appear with additional characters if you e.g. want to type Japanese on a US Keyboard) 由于用户间接输入文本而发生的事件(例如,根据浏览器和 PC 设置,如果你想在美国键盘上输入日文,弹出窗口可能会显示附加字符)
DragEvent
Drag and drop interaction with a pointer device (e.g. mouse). 使用指针设备(如鼠标)进行拖放交互
FocusEvent
Event that occurs when elements gets or loses focus. 当元素获得或失去焦点时发生的事件
FormEvent
Event that occurs whenever a form or form element gets/loses focus, a form element value is changed or the form is submitted. 当窗体或窗体元素获得/失去焦点、窗体元素值发生更改或窗体提交时发生的事件
InvalidEvent
Fired when validity restrictions of an input fails (e.g <input type="number" max="10"> and someone would insert number 20). 当输入的有效性限制失败时触发
KeyboardEvent
User interaction with the keyboard. Each event describes a single key interaction. 用户与键盘的交互。每个事件描述一个单键交互
MouseEvent
Events that occur due to the user interacting with a pointing device (e.g. mouse) 由于用户与指向设备(例如鼠标)交互而发生的事件
PointerEvent
Events that occur due to user interaction with a variety pointing of devices such as mouse, pen/stylus, a touchscreen and which also supports multi-touch. Unless you develop for older browsers (IE10 or Safari 12), pointer events are recommended. Extends UIEvent. 由于用户与各种设备(如鼠标、笔/触笔、触摸屏)进行交互而发生的事件,该设备还支持多点触摸。除非您是为较旧的浏览器(IE10或 Safari 12)开发的,建议使用指针事件。继承自 UIEvent。
TouchEvent
Events that occur due to the user interacting with a touch device. Extends UIEvent. 由于用户与触摸设备交互而发生的事件
import * as React from "react";
* A helper to create a Context and Provider with no upfront default value, and
* without having to check for undefined all the time.
function createCtx<A extends {} | null>() {
const ctx = React.createContext<A | undefined>(undefined);
function useCtx() {
const c = React.useContext(ctx);
if (c === undefined)
throw new Error("useCtx must be inside a Provider with a value");
return c;
return [useCtx, ctx.Provider] as const; // 'as const' makes TypeScript infer a tuple
// Usage:
// We still have to specify a type, but no default!
export const [useCurrentUserName, CurrentUserProvider] = createCtx<string>();
function EnthusasticGreeting() {
const currentUser = useCurrentUserName();
return <div>HELLO {currentUser.toUpperCase()}!</div>;
function App() {
return (
<CurrentUserProvider value="Anders">
<EnthusasticGreeting />
</CurrentUserProvider>
const modalRoot = document.getElementById("modal-root") as HTMLElement;
// assuming in your html file has a div with id 'modal-root';
export class Modal extends React.Component {
el: HTMLElement = document.createElement("div");
componentDidMount() {
modalRoot.appendChild(this.el);
componentWillUnmount() {
modalRoot.removeChild(this.el);
render() {
return ReactDOM.createPortal(this.props.children, this.el);
FC Modal
import React, { useEffect, useRef } from "react";
import { createPortal } from "react-dom";
const modalRoot = document.querySelector("#modal-root") as HTMLElement;
const Modal: React.FC<{}> = ({ children }) => {
const el = useRef(document.createElement("div"));
useEffect(() => {
// Use this in case CRA throws an error about react-hooks/exhaustive-deps
const current = el.current;
// We assume `modalRoot` exists with '!'
modalRoot!.appendChild(current);
return () => void modalRoot!.removeChild(current);
}, []);
return createPortal(children, el.current);
export default Modal;
import React, { Component, ErrorInfo, ReactNode } from "react";
interface Props {
children: ReactNode;
interface State {
hasError: boolean;
class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false
public static getDerivedStateFromError(_: Error): State {
// Update state so the next render will show the fallback UI.
return { hasError: true };
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("Uncaught error:", error, errorInfo);
public render() {
if (this.state.hasError) {
return <h1>Sorry.. there was an error</h1>;
return this.props.children;
export default ErrorBoundary;