IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    TS 在 React 中的简单应用 · 看不见我的美 · 是你瞎了眼

    馬腊咯稽发表于 2022-01-17 00:00:00
    love 0
    TS 在 React 中的简单应用

    导入方式

    在 19/20 版本,默认导出可能不可用。

    1
    2
    
    import * as React from 'react';
    import * as ReactDOM from 'react-dom';
    

    组件 props 类型

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    
    type CommonType = {};
    type ComponentProps = {
     count: number;
     message: string;
     disabled: boolean;
     status: 'success' | 'waiting' | 'error';
     objAttr: {};
     objAttrArr: {
     id: string;
     title: string;
     }[];
     dict1: {
     [key: string]: CommonType;
     };
     dict2: Record<string, CommonType>; // 同 dict1
     onSth: Function;
     onChange(): void;
     onClick: () => void;
     onSubmit(event: React.FormEvent<HTMLFormElement>): void;
     onContextMenu: (event: React.MouseEvent<HTMLDivElement>) => void;
     optional?: unknown;
    };
    /**
     * 获取元素的 props 类型:
     * React.HTMLAttributes<HTMLDivElement>
     * React.HTMLProps<HTMLDivElement>
     * 获取组件的 props 类型:
     * React.ComponentProps<typeof App>
     * React.ComponentPropsWithRef<typeof App>
     * React.ComponentPropsWithoutRef<typeof App>
     */
    type MyButtonProps = React.HTMLAttributes<HTMLButtonElement> & {
     type: 'button' | 'submit' | 'reset' | undefined;
    };
    const MyButtonWithForwardRef = React.forwardRef<
     HTMLButtonElement,
     MyButtonProps
    >((props, ref) => (
     <div>
     <button ref={ref} {...props}>
     按钮
     </button>
     </div>
    ));
    export declare interface OtherProps {
     /**
     * JSX.Element React.createElement 的返回值类型
     * React.ReactNode 组件的返回值类型
     */
     children1: JSX.Element; // 没考虑数组
     children2: JSX.Element | JSX.Element[]; // 没考虑字符串
     children3: React.ReactChildren; // 更像是一个方法而不是类型
     children4: React.ReactChild[]; // 稍好,考虑到了数组子元素
     children5: React.ReactNode; // 兼顾到了所有边界情况
     renderChildren: () => React.ReactNode;
     style?: React.CSSProperties;
     onChange?: React.ChangeEventHandler<HTMLInputElement>;
     onSubmit?: React.FormEventHandler<HTMLFormElement>;
     props1: React.ComponentPropsWithoutRef<'button'>;
     props2: React.ComponentPropsWithRef<typeof MyButtonWithForwardRef>; // 通过 typeof 获取组件类型
    }
    

    type 还是 interface:

    1. 在定义库或者第三方环境类型时,使用 interface,因为它方便扩展;
    2. 在定义组件的 props 和 state 类型时,使用 type,因为它更具有约束性;
    3. 在定义联合类型时,type 非常有用;在描述数据结构;implement 或 extends 类型时,interface 更好。

    函数组件

    1
    2
    3
    4
    5
    6
    7
    
    type Props = {
     message: string;
    };
    // 不推荐 (https://github.com/facebook/create-react-app/pull/8177)
    const MessageOne: React.FC<Props> = ({ message }) => <div>{message}</div>;
    // 推荐
    const MessageTwo = ({ message }: Props) => <div>{message}</div>;
    

    不推荐 React.FC 的原因是:

    1. 对组件的 props.children 进行了定义,即使组件不渲染 props.children;
    2. FC 之前还有 SFC、VFC,谁知道还会不会改名字;
    3. 对『组件命名空间模式』支持的不够好。

    类组件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    type MainProps = {
     // readonly 没必要加上,因为 React.Component<P,S> 会将他们标记为 immutable
     readonly age: number;
     message: string;
    };
    type MainState = {
     count: number;
    };
    class Main extends React.Component<MainProps, MainState> {
     year!: number; // 非空断言
     // 为了更好的类型推断
     state: MainState = {
     count: 1
     };
     handleIncrement = (num: number = 1) => {
     this.setState(state => ({
     count: state.count + num
     }));
     };
     static getDerivedStateFromProps(
     nextProps: MainProps,
     prevState: MainState
     ): Partial<MainState> | undefined | null {
     // ...
     return null;
     }
     componentDidMount() {
     this.year = this.props.age * 10;
     }
     render() {
     return (
     <div>
     <span>{this.props.message}</span>
     <span onClick={() => this.handleIncrement()}>{this.state.count}</span>
     <span>{this.year}</span>
     </div>
     );
     }
    }
    

    Hook

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    
    // useState
    export function StateExample() {
     /**
     * show 被推断为 boolean
     * setShow 被推断为 React.Dispatch<React.SetStateAction<boolean>>
     */
     const [show, setShow] = React.useState(false);
     /**
     * user 被推断为 User | null
     * setUser 被推断为 React.Dispatch<React.SetStateAction<User | null>>
     */
     type User = {};
     const [user, setUser] = React.useState<User | null>(null);
     return null;
    }
    // useReducer
    const initialState = { count: 0 };
    type ActionType =
     | { type: 'add'; payload: number }
     | { type: 'dec'; payload: number };
    function reducer(state: typeof initialState, action: ActionType) {
     switch (action.type) {
     case 'add':
     return { count: state.count + action.payload };
     case 'dec':
     return { count: state.count - action.payload };
     default:
     return { count: state.count };
     }
    }
    export function ReducerExample() {
     /**
     * state 被推断为 {count: number}
     * dispatch 被推断为 React.Dispatch<ActionType>
     */
     const [state, dispatch] = React.useReducer(reducer, initialState);
     return (
     <div>
     <span>{state.count}</span>
     <button onClick={() => dispatch({ type: 'add', payload: 1 })}>+</button>
     <button onClick={() => dispatch({ type: 'dec', payload: 1 })}>-</button>
     </div>
     );
    }
    // useRef
    type timerType = ReturnType<typeof setTimeout> | null; // 获取 setTimeout 的返回值
    export function DelayShow({ delay = 3000 }: { delay?: number }) {
     const timer = React.useRef<timerType>(null);
     const [show, setShow] = useState(false);
     useEffect(() => {
     setShow(false);
     timer.current = setTimeout(() => {
     setShow(true);
     }, delay);
     return () => {
     if (timer.current) {
     clearTimeout(timer.current);
     }
     };
     }, [delay]);
     return show ? <span>TikTok</span> : null;
    }
    export const DOMRef: React.FC = ({ children }) => {
     const divRef = React.useRef<HTMLDivElement>(null);
     useLayoutEffect(() => {
     console.log(divRef.current);
     }, []);
     return <div ref={divRef}>{children}</div>;
    };
    // useContext
    type ThemeType = { theme: string };
    export const Theme = React.createContext<ThemeType>({ theme: 'dark' });
    export function ConsumerExample() {
     const value = React.useContext<ThemeType>(Theme);
     return <span>{value.theme}</span>;
    }
    

    事件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    
    const EventExhibit = ({ children }: { children: React.ReactChildren }) => {
     return (
     <button
     onClick={event => {
     /* event 被自动推断为 React.MouseEvent<HTMLButtonElement, MouseEvent> */
     console.log(event);
     }}
     >
     {children}
     </button>
     );
    };
    const InputExhibit = () => {
     const [info, setInfo] = React.useState({ name: '', age: '' });
     // 一种是对『事件对象』添加类型
     const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
     setInfo({ ...info, name: e.target.value });
     };
     // 一种是对『事件处理函数』添加类型
     const handleAgeChange: React.ChangeEventHandler<HTMLInputElement> = e => {
     setInfo({ ...info, age: e.target.value });
     };
     return (
     <div>
     <input type="text" value={info.name} onChange={handleNameChange} />
     <input type="text" value={info.age} onChange={handleAgeChange} />
     </div>
     );
    };
    const FormEvent = () => {
     // React.FormEvent 也可以
     const handleSubmit = (e: React.SyntheticEvent): void => {
     e.preventDefault();
     const target = e.target as typeof e.target & {
     email: { value: string };
     password: { value: string };
     };
     const email = target.email.value;
     const password = target.password.value;
     console.log(email, password);
     };
     return (
     <form onSubmit={handleSubmit}>
     <label htmlFor="email">
     <input type="text" name="email" />
     </label>
     <label htmlFor="password">
     <input type="password" name="password" />
     </label>
     <button type="submit">提交</button>
     </form>
     );
    };
    

    Portals

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    const modelEle = document.querySelector('#modelEle') as HTMLElement;
    const Model: React.FC = ({ children }) => {
     const el = React.useRef(document.createElement('div'));
     useEffect(() => {
     modelEle.appendChild(el.current);
     return () => {
     modelEle.removeChild(el.current);
     };
     }, []);
     return ReactDOM.createPortal(children, el.current);
    };
    const UseModel = () => {
     const [show, setShow] = React.useState(false);
     return (
     <div>
     <div id="modelEle"></div>
     {show && (
     <Model>
     <div>{/* ... */}</div>
     <button onClick={() => setShow(false)}>关闭</button>
     </Model>
     )}
     <button onClick={() => setShow(true)}>打开</button>
     </div>
     );
    };
    

    错误边界

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    type ErrorProps = {
     children: React.ReactNode;
    };
    type ErrorState = {
     hasError: boolean;
    };
    class ErrorBoundary extends React.Component<ErrorProps, ErrorState> {
     state: ErrorState = {
     hasError: false
     };
     static getDerivedStateFromError(error: Error) {
     return { hasError: true };
     }
     componentDidCatch(error: Error, stack: React.ErrorInfo) {
     console.error(error);
     console.error(stack);
     }
     render() {
     if (this.state.hasError) {
     return <div>Error Occuried...</div>;
     }
     return this.props.children;
     }
    }
    

    参考

    • TypeScript + React: Why I don’t Use React.FC
    • Remove React.FC from Typescript Template
    • TypeScript + React: Component Patterns
    • React TypeScript Cheatsheet


沪ICP备19023445号-2号
友情链接