前置工作

  1. 浏览React Doc 中 TS 局部
  2. 浏览 TypeScript Playground 中 React 局部

    引入 React:永远应用命名空间导入(namespace import)

    import * as React from 'react';import { useState } from 'react'; // 能够独自导出一遍外部项import * as ReactDom from 'react-dom';

    不举荐 import React from 'react'。为什么?
    这种引入形式被称为 default export 。不举荐的起因是第React 仅仅是作为一个命名空间存在的,咱们并不会像应用一个值一样来应用 React。从 React 16.13.0 开始,React 的导出形式也曾经更正为 export {xxx, ...} 的模式了(commit)。之所以default export还能够应用,是因为目前 React 的构建产物是 Commonjs 标准的,webpack 等构建工具做了兼容。

组件开发

1. 尽量应用Function Component申明,即 React.FC:

export interface Props {  /** The user's name */  name: string;  /** Should the name be rendered in bold */  priority?: boolean}const PrintName: React.FC<Props> = (props) => {  return (    <div>      <p style={{ fontWeight: props.priority ? "bold" : "normal" }}>{props.name}</p>    </div>  )}

我个别在开发时应用 vscode snippets 疾速生成:

"Functional, Folder Name": {    "prefix": "ff",    "body": [      "import * as React from 'react';",      "",      "const { useRef, useState, useEffect, useMemo } = React;",      "",      "",      "interface ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props {",      "",      "}",      "",            "const defaultProps: ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props = {};",            "",      "const ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}: React.FC<${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props> = (props: React.PropsWithChildren<${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props> = defaultProps) => {",      "  const { } = props;",      "",      "  return (",      "",      "  );",      "};",      "",      "export default ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/};",      ""    ],    "description": "Generate a functional component template"  },

Hook 相干

举荐装置vscode 插件:React Hooks Snippets 疾速写 hook,进步开发效率。

2. useState<T>: 当状态初始值为空时,举荐写出残缺泛型,否则能够主动推断类型。

起因:一些状态初始值为空时,须要显示地申明类型:

const [user, setUser] = React.useState<User | null>(null)

留神:如果初始值为 undefined,可不必在泛型中加上 undefined。

3. useMemo() 和 useCallback() 会隐式推断类型,举荐不传泛型

注:不要常常用useCallback,因为也会减少开销。仅当:

  • 包装在 React.memo()(或 shouldComponentUpdate )中的组件承受回调 prop;
  • 当函数用作其余 hooks 的依赖项时 useEffect(...,[callback])

4. 自定义hook如果返回为数组,须要手动增加 const 断言:

function useLoading() {  const [isLoading, setLoading] = React.useState(false);  const load = (aPromise: Promise<any>) => {    setLoading(true)    return aPromise.then(() => setLoading(false));  }  // 理论须要: [boolean, typeof load] 类型  // 而不是主动推导的:(boolean | typeof load)[]  return [isLoading, load] as const;}

或者能够间接定义返回类型:

export function useLoading(): [  boolean,  (aPromise: Promise<any>) => Promise<any>] {  const [isLoading, setState] = React.useState(false)  const load = (aPromise: Promise<any>) => {    setState(true)    return aPromise.then(() => setState(false))  }  return [isLoading, load]}

其余

5. 应用默认参数值代替默认属性

interface GreetProps { age?: number }const defaultProps: GreetProps = { age: 21 };const Greet = (props: GreetProps = defaultProps) => {  /* ... */}

起因:Function Component 的 defaultProps 最终会被废除,不举荐应用。
如果仍要应用defaultProps,举荐如下形式:

interface IProps {  name: string}const defaultProps = {  age: 25,};// 类型定义type GreetProps = IProps & typeof defaultProps;const Greet = (props: GreetProps) => <div></div>Greet.defaultProps = defaultProps;// 应用const TestComponent = (props: React.ComponentProps<typeof Greet>) => {  return <h1 />}const el = <TestComponent name="foo" />

6. 倡议应用 Interface 定义组件 props(TS 官网举荐做法),应用 type 也可,不强制

type 和 interface 的区别:type 类型不能二次编辑,而 interface 能够随时扩大。

7. 应用 ComponentProps 获取未被导出的组件参数类型,应用 ReturnType 获取返回值类型

获取组件参数类型:

// 获取参数类型import { Button } from 'library' // 然而未导出props typetype ButtonProps = React.ComponentProps<typeof Button> // 获取propstype AlertButtonProps = Omit<ButtonProps, 'onClick'> // 去除onClickconst AlertButton: React.FC<AlertButtonProps> = props => (  <Button onClick={() => alert('hello')} {...props} />)

获取返回值类型:

// 获取返回值类型function foo() {  return { baz: 1 }}type FooReturn = ReturnType<typeof foo> // { baz: number }

8. 对 type 或 interface 进行正文时尽量应用 /* /,以便取得更好的类型提醒

/* ✅ *//** * @param a 正文1 * @param b 正文2 */type Test = {  a: string;  b: number;};const testObj: Test = {  a: '123',  b: 234,};

当 hover 到 Test 时类型提醒更为敌对:

9. 组件 Props ts 类型标准

type AppProps = {  /** string */  message: string;  /** number */  count: number;  /** boolean */  disabled: boolean;  /** 根本类型数组 */  names: string[];  /** 字符串字面量 */  status: 'waiting' | 'success';  /** 对象:列出对象全副属性 */  obj3: {    id: string;    title: string;  };  /** item 为对象的数组 */  objArr: {    id: string;    title: string;  }[];  /** 字典*/  dict: Record<string, MyTypeHere>;  /** 任意齐全不会调用的函数 */  onSomething: Function;  /** 没有参数&返回值的函数 */  onClick: () => void;  /** 携带参数的函数 */  onChange: (id: number) => void;  /** 携带点击事件的函数, 不要再用 e: any 了 */  onClick(e: React.MouseEvent<HTMLButtonElement>): void;  /** 可选的属性 */  optional?: OptionalType;  children: React.ReactNode; // 最佳,反对所有类型(JSX.Element, JSX.Element[], string)  renderChildren: (name: string) => React.ReactNode;  style?: React.CSSProperties;  onChange?: React.FormEventHandler<HTMLInputElement>; // 表单事件};

10. 组件中 event 解决

常见的Eventl类型:

React.SyntheticEvent<T = Element>React.ClipboardEvent<T = Element>React.DragEvent<T = Element>React.FocusEvent<T = Element>React.FormEvent<T = Element>React.ChangeEvent<T = Element>React.KeyboardEvent<T = Element>React.MouseEvent<T = Element>React.TouchEvent<T = Element>React.PointerEvent<T = Element>React.UIEvent<T = Element>React.WheelEvent<T = Element>React.AnimationEvent<T = Element>React.TransitionEvent<T = Element>

定义事件回调函数:

type changeFn = (e: React.FormEvent<HTMLInputElement>) => void;

如果不太关怀事件的类型,能够间接应用 React.SyntheticEvent,如果指标表单有想要拜访的自定义命名输出,能够应用类型扩大:

const App: React.FC = () => {  const onSubmit = (e: React.SyntheticEvent) => {    e.preventDefault();    const target = e.target as typeof e.target & {      password: { value: string; };    }; // 类型扩大    const password = target.password.value;  };  return (    <form onSubmit={onSubmit}>      <div>        <label>          Password:          <input type="password" name="password" />        </label>      </div>      <div>        <input type="submit" value="Log in" />      </div>    </form>  );};

11. 尽量应用 optional channing

12. 尽量应用 React.ComponentProps<typeof Component> 缩小非必要props导出

13. 不要在 type 或 interface 中应用函数申明

/** ✅ */interface ICounter {  start: (value: number) => string}/** ❌ */interface ICounter1 {  start(value: number): string}

14. 当部分组件联合多组件进行组件间状态通信时,如果不是特地简单,则不须要用mobx或者 倡议联合 useReducer() 和 useContext() 一起应用,频繁的组件间通信最佳实际:

Store.ts

import * as React from 'react';export interface State {  state1: boolean;  state2: boolean;}export const initState: State = {  state1: false,  state2: true,};export type Action = 'action1' | 'action2';export const StoreContext = React.createContext<{  state: State;  dispatch: React.Dispatch<Action>;}>({ state: initState, dispatch: value => { /** noop */ } });export const reducer: React.Reducer<State, Action> = (state, action) => { // 用 reducer 的益处之一是能够拿到之前的 state  switch (action) {    case 'action1':      return { ...state, state1: !state.state1 };    case 'action2':      return { ...state, state2: !state.state2 };    default:      return state;  }};

WithProvider.tsx

import * as React from 'react';import { StoreContext, reducer, initState } from './store';const { useReducer } = React;const WithProvider: React.FC<Record<string, unknown>> = (props: React.PropsWithChildren<Record<string, unknown>>) => {  // 将 state 作为根节点的状态注入到子组件中  const [state, dispatch] = useReducer(reducer, initState);  const { children } = props;  return <StoreContext.Provider value={{ state, dispatch }}>{children}</StoreContext.Provider>;};export default WithProvider;

父组件:

import * as React from 'react';import WithProvider from './withProvider';import Component1 from './components/Component1';import Component2 from './components/Component2';const { useRef, useState, useEffect, useMemo } = React;interface RankProps {}const defaultProps: RankProps = {};const Rank: React.FC<RankProps> = (props: React.PropsWithChildren<RankProps> = defaultProps) => {  const {} = props;  return (    <WithProvider>      <Component1 />      <Component2 />    </WithProvider>  );};export default Rank;

子组件只须要 import StoreContext 之后 useContext() 就能失去 state 和 dispatch

import * as React from 'react';import { StoreContext } from '../../store';const { useContext } = React;interface Component1Props {}const defaultProps: Component1Props = {};const Component1: React.FC<Component1Props> = (props: React.PropsWithChildren<Component1Props> = defaultProps) => {  const { state, dispatch } = useContext(StoreContext);  const {} = props;  return (    <>      state1: {state.state1 ? 'true' : 'false'}      <button        type="button"        onClick={(): void => {          dispatch('action1');        }}      >        changeState1 with Action1      </button>    </>  );};export default React.memo(Component1); // 倡议有context 的中央最好 memo 一下,进步性能

Store.tsWithProvider.tsx 能够配置成 vscode snippets,须要用到时间接应用。

参考

[1] React + TypeScript 实际
[2] 精读《React Hooks 最佳实际》

欢送在评论或 issue 中探讨,指出不合理之处,补充其余最佳实际~