关于javascript:TSreactreact中常用的类型整理

33次阅读

共计 14838 个字符,预计需要花费 38 分钟才能阅读完成。

  1. React.FC 的注解是有些问题的,在是否优先应用这个类型作为注解上存在一部分争议,因为这个类型毁坏了 JSX.LibraryManagedAttributes,导致其疏忽了函数和类组件的 defaultsProps,displayName 这样的参数, 详见,另外,其不能像 class 组件一样返回 props 的 children 详见(显式的定义 children 属性,或更改源码可解决这个问题)。还有一点,FC 在 @types/react18 之前总是隐式的定义好 children,即便你的 Props 注解并没有定义 children,你依然能够在参数里解构出它。
  2. 在 @types/react 版本 16.8 和 18 之间能够应用 React.VoidFunctionComponent 或 React.VFC 代替它, 它规定要想在函数体内应用 props 必须显示的定义它
  3. 因为编译器的限度 在函数组件中,不能返回除 jsx 和 null 以外的值,如果真的须要返回除这两种之外的值,能够应用类型断言,例如
const MyArrayComponent = () => (Array(5).fill(<div/>) as any) as JSX.Element

4.

React.FC<Props> React.Component<Props,state>
  1. 开发泛型 class 组件
// 在应用的时候束缚它的 props 类型 
type SelectProps<T> = {items: T[]}; 
class Select<T> extends React.Component<SelectProps<T>, {}> {} 
// 应用 
const Form = () => <Select<string> items={['a','b']}>
  1. 开发泛型函数
function foo<T>(x:T): T{return x}
// 箭头泛型函数须要用 extends 提醒编辑器这是个泛型
const foo = <T extends Record<string,unknow>>() => {}

7.React.ReactElement 能够通过传入泛型,来注解类或函数组件的实例化后果

class MyAwesomeComponent extends React.Component {render() {return <div>Hello</div>;}
}

const foo: React.ReactElement<MyAwesomeComponent> = <MyAwesomeComponent />; // Okay
const bar: React.ReactElement<MyAwesomeComponent> = <NotMyAwesomeComponent />; // Error
  1. useState<>() . 奇淫巧技:
const [user,setUser] = React.useState<IUser | null>(null); 
const [user,setUser] = React.useState<IUser>({} as IUser)
  1. reducer 函数的形参里 intialState 的类型注解能够间接 typeOf 它取出它的类型,

action 参数能够应用联结类型活 AnyAction 注解(from’redux‘),

泛型注解

import {Reducer} from 'redux';

export function reducer:

Reducer<AppState, Action>() {}

10.useRef<>(),奇淫巧技:

// 如果能够的话尽量应用的类型具体点
// 比方应用 HTMLDivElement 就比 HTMLElement 好很多,比 Element 好更多
function Foo(){const divRef = useRef<HTMLDivElement>(null);
return <div>etc<div/>
}

 11. 类型断言:as/ 泛型注解(React 里不能用)/ 去除 null 或 undefined 断言!

// 非空断言操作符
const fooRef = useRef<string>(null!)
const foo = name!.chartAt(0))
  1. 函数的执行后果在给其余变量进行赋值,会发现该变量的注解有问题
function fn(){return ['1',false] };
type AType = string[]
let a:AType = fn() // error
// 1. 将其变为 return ['1',false] as any[] 或者 return ['1',false] as string[],如果是只读的能够 as const
// 2. type Atype = (string | boolean)[], 但已不符合实际意义
// 3. react 团队举荐自定义钩子 return 两个以上的值时能够应用对象

13.createContext

type Theme = 'light' | 'dark'
const TeemeContext = React.createContext<Theme>('dark')
// 创立 {} 用断言  const Context = React.createContext({} as ContextState);

const sampleAppContext: TeemeContext = 'light';

export const App = () => (<AppCtx.Provider value={sampleAppContext}>...</AppCtx.Provider>
);


// 如果想创立默认值为 null 或 undefined 能够 React.createContext<string>(undefined!),不然在应用. 时
// 会报没有相干 api 能够调用, 但这样失去了类型平安,或者能够应用一个 helper 函数来帮忙咱们创立,让咱们不再思考 undeined
// 的状况
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>
  );
}
//
// 整合 useContext,createContext,useState 为一体
export function createCtx<A>(defaultValue: A) {
  type UpdateType = React.Dispatch<
    React.SetStateAction<typeof defaultValue>
  >;
  const defaultUpdate: UpdateType = () => defaultValue;
  const ctx = React.createContext({
    state: defaultValue,
    update: defaultUpdate,
  });
  function Provider(props: React.PropsWithChildren<{}>) {const [state, update] = React.useState(defaultValue);
    return <ctx.Provider value={{state, update}} {...props} />;
  }
  return [ctx, Provider] as const; // alternatively, [typeof ctx, typeof Provider]
}

// usage

const [ctx, TextProvider] = createCtx("someText");
export const TextContext = ctx;
export function App() {
  return (
    <TextProvider>
      <Component />
    </TextProvider>
  );
}
export function Component() {const { state, update} = React.useContext(TextContext);
  return (
    <label>
      {state}
      <input type="text" onChange={(e) => update(e.target.value)} />
    </label>
  );
}
// 更好的 createContextApi
https://gist.github.com/sw-yx/f18fe6dd4c43fddb3a4971e80114a052

14.useImperativeHandle, forwardRef

export interface MyInputHandles {focus(): void;
}
const MyInput: RefForwardingComponent<MyInputHandles, MyInputProps> = (props, ref) => {const inputRef = useRef<HTMLInputElement>(null);
    
    useImperativeHandle(ref, () =>({focus: () => {if(inputRef.current) {inputRef.current.focus();
            }
        }
    }))
    
    return <Input {...props} ref={inputRef}>    
}

export default forwardRef(MyInput)
  1. React.Component 里如果为了更好的援用 state 能够在 React.Component<MyState> 和 state:MyState {}两处做注解
  2. props 的注解不必标记 readOnly。因为在增加到泛型组件时,会主动增加 ReadOnly
  3. class properties
pointer: number
  1. getDerivedStateFromProps
static getDerivedStateFromProps(props:Props, state:State): Partial<State> | null {}
  1. 当您须要函数的返回值确定状态时能够应用 ReturnType
static getDerivedStateFromProps(props:Props, state:State): Partial<State> | null {}
  1. ts 中就你就能够不必写 defaultProps 了

 21. 如何优雅的取出 component 的 props

const GreetComponent = ({name, age}: {name:string, age:25}) => (<div>{`Hello, my name is ${name}, ${age}`}</div>
);

type ComponentProps<T> = T extends
  | React.ComponentType<infer P>
  | React.Component<infer P>
  ? JSX.LibraryManagedAttributes<T, P>
  : never;

const TestComponent = (props: ComponentProps<typeof GreetComponent>) => {return <h1 />;};

  1. react 中常见的 ts 类型
type AppProps = {
  message: string;
  count: number;
  disabled: boolean;
  /** array of a type! */
  names: string[];
  /** string literals to specify exact string values, with a union type to join them together */
  status: "waiting" | "success";
  /** any object as long as you dont use its properties (NOT COMMON but useful as placeholder) */
  obj: object;
  obj2: {}; // almost the same as `object`, exactly the same as `Object`
  /** an object with any number of properties (PREFERRED) */
  obj3: {
    id: string;
    title: string;
  };
  /** array of objects! (common) */
  objArr: {
    id: string;
    title: string;
  }[];
  /** a dict object with any number of properties of the same type */
  dict1: {[key: string]: MyTypeHere;
  };
  dict2: Record<string, MyTypeHere>; // equivalent to dict1
  /** any function as long as you don't invoke it (not recommended) */
  onSomething: Function;
  /** function that doesn't take or return anything (VERY COMMON) */
  onClick: () => void;
  /** function with named prop (VERY COMMON) */
  onChange: (id: number) => void;
  /** alternative function type syntax that takes an event (VERY COMMON) */
  onClick(event: React.MouseEvent<HTMLButtonElement>): void;
  /** an optional prop (VERY COMMON!) */
  optional?: OptionalType;
};
  1. react 中的注解
​
type AppProps = {
  message: string;
  count: number;
  disabled: boolean;
  /** array of a type! */
  names: string[];
  /** string literals to specify exact string values, with a union type to join them together */
  status: "waiting" | "success";
  /** any object as long as you dont use its properties (NOT COMMON but useful as placeholder) */
  obj: object;
  obj2: {}; // almost the same as `object`, exactly the same as `Object`
  /** an object with any number of properties (PREFERRED) */
  obj3: {
    id: string;
    title: string;
  };
  /** array of objects! (common) */
  objArr: {
    id: string;
    title: string;
  }[];
  /** a dict object with any number of properties of the same type */
  dict1: {[key: string]: MyTypeHere;
  };
  dict2: Record<string, MyTypeHere>; // equivalent to dict1
  /** any function as long as you don't invoke it (not recommended) */
  onSomething: Function;
  /** function that doesn't take or return anything (VERY COMMON) */
  onClick: () => void;
  /** function with named prop (VERY COMMON) */
  onChange: (id: number) => void;
  /** alternative function type syntax that takes an event (VERY COMMON) */
  onClick(event: React.MouseEvent<HTMLButtonElement>): void;
  /** an optional prop (VERY COMMON!) */
  optional?: OptionalType;
};

24. 接口与类型别名的差别

类型别名和接口十分类似,在很多状况下你能够自由选择它们。简直所有的性能都在 interface 中可用 type,要害区别在于不能从新关上类型以增加新属性与始终可扩大的接口。

25. 当你想要应用 getDerivedStateFromProps 的返回值作为组建的 state 注解时

// 1. 一般状况
class Comp extends React.Component<
  Props,
  ReturnType<typeof Comp["getDerivedStateFromProps"]>
> {static getDerivedStateFromProps(props: Props) {}}

// 2. 返回函数
type CustomValue = any;
interface Props {propA: CustomValue;}
interface DefinedState {otherStateField: string;}
type State = DefinedState & ReturnType<typeof transformPropsToState>;
function transformPropsToState(props: Props) {
  return {
    savedPropA: props.propA, // save for memoization
    derivedState: props.propA,
  };
}
class Comp extends React.PureComponent<Props, State> {constructor(props: Props) {super(props);
    this.state = {
      otherStateField: "123",
      ...transformPropsToState(props),
    };
  }
  static getDerivedStateFromProps(props: Props, state: State) {if (isEqual(props.propA, state.savedPropA)) return null;
    return transformPropsToState(props);
  }
}
  1. 表单事件
// 如果不思考性能的话,能够应用内联解决,注解将主动正确生成
const el = (
    <button onClick=(e=>{//...})/>
)
// 如果须要在内部定义类型
handlerChange = (e: React.FormEvent<HTMLInputElement>): void => {//}
// 如果在 = 号的右边进行注解
handlerChange: React.ChangeEventHandler<HTMLInputElement> = e => {//}
// 如果在 form 里 onSubmit 的事件,React.SyntheticEvent, 如果有自定义类型,能够应用类型断言
<form
  ref={formRef}
  onSubmit={(e: React.SyntheticEvent) => {e.preventDefault();
    const target = e.target as typeof e.target & {email: { value: string};
      password: {value: string};
    };
    const email = target.email.value; // typechecks!
    // etc...
  }}
>
  <div>
    <label>
      Email:
      <input type="email" name="email" />
    </label>
  </div>
  <div>
    <input type="submit" value="Log in" />
  </div>
</form>
  1. 事件类型列表
AnimationEvent:css 动画事件
ChangeEvent:<input>,<select> 和 <textarea> 元素的 change 事件
ClipboardEvent:复制,粘贴,剪切事件
CompositionEvent:因为用户间接输出文本而产生的事件(例如,依据浏览器和 PC 的设置,如果你想在美国键盘上输出日文,可能会呈现一个带有额定字符的弹出窗口)
DragEvent:在设施上拖放和交互的事件
FocusEvent: 元素取得焦点的事件
FormEvent: 当表单元素得失焦点 /value 扭转 / 表单提交的事件
InvalidEvent: 当输出的有效性限度失败时触发(例如 <input type="number" max="10">,有人将插入数字 20)
KeyboardEvent: 键盘键入事件
MouseEvent:鼠标挪动事件
PointerEvent:鼠标、笔 / 触控笔、触摸屏)的交互而产生的事件
TouchEvent:用户与触摸设施交互而产生的事件
TransitionEvent:CSS Transition,浏览器反对度不高
UIEvent:鼠标、触摸和指针事件的根底事件。WheelEvent: 在鼠标滚轮或相似的输出设施上滚动
SyntheticEvent:所有上述事件的根底事件。是否应该在不确定事件类型时应用
// 因为 InputEvent 在各个浏览器反对度不一样,所以能够应用 KeyboardEvent 代替

28.createRef 与 forwardRef

class CssThemeProvider extends React.PureComponent<Props> {private rootRef = React.createRef<HTMLDivElement>(); // like this
  render() {return <div ref={this.rootRef}>{this.props.children}</div>;
  }
} 
// 这样的 forwardRef 是可变的,能够在须要的时候给它赋值
type Props = {children: React.ReactNode; type: "submit" | "button"};
export type Ref = HTMLButtonElement;
export const FancyButton = React.forwardRef<Ref, Props>((props, ref) => (<button ref={ref} className="MyClassName" type={props.type}>
    {props.children}
  </button>
));
// 如果心愿它不可变
// type Ref = HTMLButtonElement
// (props, ref: React.Ref<Ref>) =>

// 如果你心愿抓取 forwardRef 组件的 props,能够应用 compoentPropsWithRef

29.ReactDOM.createPortal

// Class
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);
  }
}
// hooks
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;

 30. 错误处理

//option1:应用 react-error-boundary
//option2 :  自定义 boundary component
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;

31. 联结类型的弊病,在对象与对象的联结时无奈精准的进行辨别

interface Admin {role: string;}
interface User {email: string;}

// Method 1: use `in` keyword
function redirect(user: Admin | User) {if ("role" in user) {
    // use the `in` operator for typeguards since TS 2.7+
    routeToAdminPage(user.role);
  } else {routeToHomePage(user.email);
  }
}

// Method 2: custom type guard, does the same thing in older TS versions or where `in` isnt enough
function isAdmin(user: Admin | User): user is Admin {return (user as any).role !== undefined;
}

// Method ...: typeOf 和 instanceof 也可进行不便的类型爱护
  1. 期待已久的非空断言整顿用法(最好是理论解决空值,少用此法)
element.parentNode!.removeChild(element); // ! before the period
myFunction(document.getElementById(dialog.id!)!); // ! after the property accessing
let userID!: string; // definite assignment assertion... be careful!
  1. 用 symbol 创立标识性的 ID 注解
type OrderID = string & {readonly brand: unique symbol};
type UserID = string & {readonly brand: unique symbol};
type ID = OrderID | UserID;
function OrderID(id: string) {return id as OrderID;}
function UserID(id: string) {return id as UserID;}
function queryForUser(id: UserID) {// ...}
queryForUser(OrderID("foobar")); // Error, Argument of type 'OrderID' is not assignable 
// to parameter of type 'UserID'
// unique 是一个关键词
// unique T(其中 T 是任何类型)容许在任何地位应用类型,并且名义上用标记标记 T,使来自该地位的 T 只能调配给后果类型。// 它是制作它的每个符号的惟一构造。而后通过交加将其混合到参数类型中,从而生成所有有用的关系
// 译文: 惟一符号的以后行为优先于语法上齐全惟一的符号,然而,如果符号的一个名义子类实际上是须要的,你能够写
// Unique (symbol)来取得一个名义上的品牌符号类型(或 symbol & Unique unknown - 这是完全相同的事件)。惟一
// 符号的行为形式就像锁定在特定申明中的符号,因而在膨胀和控制流时具备非凡能力。名义上的品牌类型在应用上更灵便,// 但不具备弱小的控制流链接的主机申明
// https://github.com/microsoft/TypeScript/pull/33038
// 实例:// type NormalizedPath = unique string;
// type AbsolutePath = unique string;
// type NormalizedAbsolutePath = NormalizedPath & AbsolutePath;

// declare function isNormalizedPath(x: string): x is NormalizedPath;
// declare function isAbsolutePath(x: string): x is AbsolutePath;
// declare function consumeNormalizedAbsolutePath(x: NormalizedAbsolutePath): void;


// const p = "/a/b/c";
// if (isNormalizedPath(p)) {//     if (isAbsolutePath(p)) {//         consumeNormalizedAbsolutePath(p);
//     }
// }

33.Overloading Function

// 1.
function pickCard(x: { suit: string; card: number}[]): number;
function pickCard(x: number): {suit: string; card: number};
function pickCard(x): any {
  // implementation with combined signature
  // ...
}
// 2.
type pickCard = {(x: { suit: string; card: number}[]): number;
  (x: number): {suit: string; card: number};
  // no need for combined signature in this form
  // you can also type static properties of functions here eg `pickCard.wasCalled`
};

34. 取得组件的 props 类型能够应用 React.ComponentProps<typeof Button>

35.interface 定义的 {a:1,b:2} 和 typeof 这样的对象所取得得类型是不一样的,前者岂但校验 value 的类型还有值

36.js 主动转换 ts 的工具

dts-gen
TypeStat
TypeWiz
js-to-ts-converter
TS-migrate used in Airbnb's conversion

37. 自定义钩子的类型定义标准

declare module 'use-untyped-hook' {export interface InputProps { ...}   // type declaration for prop
  export interface ReturnProps {...} // type declaration for return props
  export default function useUntypedHook(
    prop: InputProps
    // ...
  ): ReturnProps;
}

39。为类创立类型定义

import * as React from "react";

declare class SimpleSelect extends React.Component<SimpleSelectProps, any> {}

export interface SimpleSelectProps {
  autofocus?: boolean;
  cancelKeyboardEventOnSelection?: boolean;
  className?: string;
  createFromSearch?(items: OptionValue[], search: string): OptionValue;
  defaultValue?: OptionValue;
  delimiters?: [any];
  disabled?: boolean;
  // ...
}

export default SimpleSelect;
  1. 内置类型
ConstructorParameters:类结构函数参数类型的元组
Exclude:将一个类型从另一个类型排除
Extract:抉择可调配给另一类型的子类型
InstanceType:从一个新类构造函数中取得的实例类型
NonNullable:从类型中排除空和未定义
Parameters:函数的形参类型的元组
Partial:使对象中的所有属性都是可选的
Readonly:将对象中的所有属性设置为只读
ReadonlyArray:创立给定类型的不可变数组
Pick:对象类型的一种子类型,蕴含其键的子集
Record:从键类型到值类型的映射
Required:使对象中的所有属性都是必须的
ReturnType:函数的返回类型

41. 如果你在库的官网类型中遇到 bug,你能够将它们复制到本地,并通过“paths”字段通知 TypeScript 应用你的本地版本。在你 tsconfig.json

{
  "compilerOptions": {
    "paths": {"mobx-react": ["../typings/modules/mobx-react"]
    }
  }
}

正文完
 0