在React項目中優雅地使用Typescript

clipboard.png

「優雅」的含義:html

  1. 減小編寫冗餘的類型定義、類型標註,充分利用ts的自動類型推斷,以及外部提供的類型聲明。
  2. 類型安全:提供足夠的類型信息來避免運行時錯誤,讓錯誤暴露在開發期。這些類型信息同時可以提供代碼補全、跳轉到定義等功能。

組件定義

函數組件

import * as React from 'react';
// 若是在tsconfig中設置了"allowSyntheticDefaultImports": true
// 你還能夠更精練地import react:
// import React from "react";

interface IProps {
      // CSSProperties提供樣式聲明的類型信息
      // 用戶傳入style的時候就可以得到類型檢查和代碼補全
      style?: React.CSSProperties;
      // 使用@types/react提供的事件類型定義,這裏指定event.target的類型是HTMLButtonElement
      onClick(event: React.MouseEvent<HTMLButtonElement>): void;
    // ...
}
const MyComponent: React.FC<IProps> = (props) => {
      const { children, ...restProps } = props;
    return <div {...restProps}>{children}</div>;
}
  1. FC是FunctionComponent的縮寫。
  2. IProps無需聲明children屬性的類型。React.FC會自動爲props添加這個屬性類型。react

    固然,若是children指望一個render prop,或者指望其餘特殊的值,那麼你仍是要本身給 children聲明類型,而不是使用默認的 React.ReactNode
  3. props無需作類型標註。

函數組件defaultProps(Deprecate)

若是你須要定義defaultProps,那麼不要使用React.FC,由於React.FC對defaultProps的支持不是很好git

const defaultProps = {
  who: "Johny Five"
};
type IProps = { age: number } & typeof defaultProps;

export const Greet = (props: IProps) => { return <div>123</div> };
Greet.defaultProps = defaultProps;

事實上,一個提議在函數組件中廢棄defaultProps的React rfc已經被接受,因此之後仍是儘可能減小在函數組件上使用defaultProps,使用ES6原生的參數解構+默認參數特性就已經可以知足須要:github

const TestFunction: FunctionComponent<Props> = ({ foo = "bar" }) => <div>{foo}</div>

類組件

interface IProps {
  message: string;
}
interface IState {
  count: number;
}
export class MyComponent extends React.Component<IProps, IState> {
  state: IState = {
    // duplicate IState annotation for better type inference
    count: 0
  };
  render() {
    return (
      <div>
        {this.props.message} {this.state.count}
      </div>
    );
  }
}
  1. 若是你經過聲明state屬性來初始化state,那麼你須要爲這個屬性增長IState類型標註。雖然這與前面的React.Component<IProps, IState>有重複的嫌疑,可是這二者其實是不一樣的:typescript

    • React.Component<IProps, IState>只是標註了基類的state屬性類型。
    • 而當你在子類聲明state時,你能夠爲state標註一個【IState的子類型】做爲override。這樣,this.state會以子類中的state屬性聲明做爲類型信息的來源。
  2. 建議使用函數組件。

可渲染節點類型

可渲染節點就是:能夠直接被組件渲染函數返回的值。redux

與可渲染節點有關的類型定義以下(摘錄自@types/react):數組

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

組件類型

  • React.FC<Props>(即 React.FunctionComponent<Props>
  • React.Component<Props, State>
  • React.ComponentType<Props>(即ComponentClass<P> | FunctionComponent<P>
    在寫HOC的時候常常用到。安全

    const withState = <P extends WrappedComponentProps>(
      WrappedComponent: React.ComponentType<P>,
    ) => { ...

獲取並擴展原生元素的props類型

好比,如下例子獲取並擴展了<button>的props類型:app

export const PrimaryButton = (
  props: Props & React.HTMLProps<HTMLButtonElement>
) => <Button size={ButtonSizes.default} {...props} />;

PrimaryButton可以接受全部原生<button>所接受的props。關鍵在於React.HTMLPropside

獲取並擴展第三方組件的props類型

import { Button } from "library"; // but doesn't export ButtonProps! oh no!
type ButtonProps = React.ComponentProps<typeof Button>; // no problem! grab your own!
type AlertButtonProps = Omit<ButtonProps, "onClick">; // modify
const AlertButton: React.FC<AlertButtonProps> = props => (
  <Button onClick={() => alert("hello")} {...props} />
);

事件類型

@types/react提供了各類事件的類型,好比如下是使用React.FormEvent的例子:

class App extends React.Component<
  {},
  {
    text: string
  }
> {
  state = {
    text: '',
  }
  onChange = (e: React.FormEvent<HTMLInputElement>): void => {
    this.setState({ text: e.currentTarget.value })
  }
  render() {
    return (
      <div>
        <input type="text" value={this.state.text} onChange={this.onChange} />
      </div>
    )
  }
}

在React中,全部事件(包括FormEventKeyboardEventMouseEvent等)都是SyntheticEvent的子類型。他們在@types/react中定義以下:

// DOM事件的基本屬性都定義在這裏
interface BaseSyntheticEvent<E = object, C = any, T = any> {
  nativeEvent: E;
  currentTarget: C;
  target: T;
  bubbles: boolean;
  cancelable: boolean;
  defaultPrevented: boolean;
  eventPhase: number;
  isTrusted: boolean;
  preventDefault(): void;
  isDefaultPrevented(): boolean;
  stopPropagation(): void;
  isPropagationStopped(): boolean;
  persist(): void;
  timeStamp: number;
  type: string;
}
interface SyntheticEvent<T = Element, E = Event> extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {}

// 具體的事件類型:
interface FormEvent<T = Element> extends SyntheticEvent<T> {}
interface KeyboardEvent<T = Element> extends SyntheticEvent<T, NativeKeyboardEvent> {
  altKey: boolean;
  // ...
}
interface MouseEvent<T = Element, E = NativeMouseEvent> extends SyntheticEvent<T, E> {
  altKey: boolean;
  // ...
}
// ...

參考資料

相關文章
相關標籤/搜索