React & Typescript:組件的入門實例

React 組件的演化

組件複用方式 優點 劣勢 狀態
類組件(Class 發展時間長,接受度普遍 只能繼承父類 做爲一種傳統開發模式,會長期存在
Mixin 能夠複製任意對象的任意多個方法,實現組件間的複用 組件間相互依賴、耦合,可能產生衝突,不利於維護 被官方拋棄
高階組件(HOC) 利用裝飾器模式,在不改變組件的基礎上,動態地爲其添加新的能力 嵌套過多調試困難,須要遵循某些約定(不改變原始組件,透傳 props 等) 能力強大,普遍引用
Hooks 替代類組件,多個 Hooks 間互不影響,避免嵌套地獄,開發效率高 切換新思惟須要成本 React 的將來(官方主推)

函數組件

普通函數組件

上一篇 中,咱們建立了一個無狀態組件(沒有狀態影響,做爲純靜態展現) <Hello />,同時它也是一個函數組件。react

import React from "react";
import { Button } from "antd";

interface Greeting {
  name: string;
  firstName: string;
  lastName: string;
}

const Hello = (props: Greeting) => <Button>hello {props.name}</Button>;

Hello.defaultProps = {
  firstName: "",
  lastName: "",
};

export default Hello;
複製代碼

React.FC

在 react 的聲明文件中,對函數組件單獨定義了一個類型 -- React.FC:typescript

type React.FC<P = {}> = React.FunctionComponent<P>
複製代碼

如今使用 React.FC 從新定義一下 <Hello />數組

const Hello: React.FC<Greeting> = ({ name, firstName, lastName }) => (
  <Button>hello {name}</Button>
);
複製代碼

使用 React.FC 後的區別

FCFunctionComponent 的簡寫,這個類型定義了默認的 props (如 children)。markdown

const Hello: React.FC<Greeting> = ({ name, firstName, lastName, children }) => (
  <Button>hello {name}</Button>
);
複製代碼

在使用 React.FC後定義 defaultProps 時,默認屬性必須是可選的(這和普通函數組件不一樣):antd

interface Greeting {
  name: string;
  firstName?: string;
  lastName?: string;
}

const Hello: React.FC<Greeting> = ({ name, firstName, lastName, children }) => (
  <div> <Button>hello {name}</Button> </div>
);

Hello.defaultProps = {
  firstName: "",
  lastName: "",
};
複製代碼

小結

TypeScript 中,函數組件須要爲 props 定義類型。app

類組件

Component

類組件須要繼承 Components 組件,在 react 的聲明文件中,Component 被定義爲泛型類:dom

// P: 屬性的類型,默認{}
// S: 狀態的類型,默認{}
// SS: snapshot
(alias) class Component<P = {}, S = {}, SS = any>
複製代碼

實現

將上面的函數組件改形成類組件:函數

// src/componets/demo/HellpClass.tsx
import React, { Component } from "react";
import { Button } from "antd";

interface Greeting {
  name: string;
  firstName: string;
  lastName: string;
}

interface State {
  count: number;
}

class HelloClass extends Component<Greeting, State> {
  // 初始化 state
  state: State = { count: 0 };
  // 默認屬性值
  static defaultProps = {
    firstName: "",
    lastName: "",
  };
  render() {
    return <Button>hello {this.props.name}</Button>;
  }
}

export default HelloClass;
複製代碼

經過 setState<HelloClass /> 添加一個點擊計數功能。post

class HelloClass extends Component<Greeting, State> {
  state: State = { count: 0 };
  static defaultProps = {
    firstName: "",
    lastName: "",
  };
  render() {
    return (
      <> <div>您點擊了 {this.state.count} 次</div> <Button onClick={() => { this.setState({ count: this.state.count + 1 }); }} > hello {this.props.name} </Button> </>
    );
  }
}
複製代碼

小結

TypeScript 中,類組件須要爲 propsstate 定義類型。ui

高階組件

咱們如今要利用高階組件包裝一下 <HelloClass />,包裝後的組件有一個新屬性 loading,經過該屬性控制被包裝組件的 顯示/隱藏。

React.ComponentType

指定被包裝組件的類型爲 React.ComponentType(一種 React 預約義類型),既能夠是類組件,也能夠是函數組件:

type React.ComponentType<P = {}> = React.ComponentClass<P, any> | React.FunctionComponent<P>
複製代碼

實現

添加 <HelloHOC /> 組件:

import React, { Component } from "react";
import HelloClass from "./HelloClass";

interface Loading {
  loading: boolean;
}

/* ** WrapperComponetn: 須要被包裝的組件 */
function HelloHOC<P>(WrapperComponetn: React.ComponentType<P>) {
  // 定義 props 爲 P 和 Loading 的交叉類型
  return class extends Component<P & Loading> {
    render() {
      // 解構 props,拆分出 loading
      const { loading, ...props } = this.props;
      // {...props}:屬性透傳
      return loading ? (
        <div>Loading...</div>
      ) : (
        <WrapperComponetn {...(props as P)} />
      );
    }
  };
}

// 導出通過高階組件包裝後的組件
export default HelloHOC(HelloClass);
複製代碼

有個報錯

咱們在 index.tsx 中引入這個組件,這時會有一個報錯:

import React from "react";
import ReactDOM from "react-dom";
import HelloHOC from "./components/demo/HelloHOC";

ReactDOM.render(
  <HelloHOC name="typescript" loading={true} />,
  document.querySelectorAll(".app")[0]
);

// ERROR! 由於 HelloClass 的靜態屬性 defaultProps 傳不出來。
複製代碼

解決方案:將 defaultProps 設置爲可選屬性。

interface Greeting {
  name: string;
  firstName?: string;
  lastName?: string;
}
複製代碼

小結

TypeScript 中,高階組件的使用會遇到不少類型問題,還有可能遇到一些已知的 bug,但這並非高階組件自己的問題,而是由於 react 聲明文件沒有很好的兼容。其實官方最推薦的是使用 Hooks,下面就再用 Hooks 實現一下吧

hooks

hooks 也是一種函數組件,對比類組件,明顯簡化了許多:

import React, { useEffect, useState } from "react";
import { Button } from "antd";

interface Greeting {
  name: string;
  firstName: string;
  lastName: string;
}

const HelloHooks = (props: Greeting) => {
  // 定義 [組件的狀態,設置狀態的方法],給定狀態的初始值:不須要再定義類型
  const [count, setCount] = useState(0);
  const [text, setText] = useState<string | null>(null);
  return (
    <> <div>您點擊了 {count} 次</div> <Button onClick={() => { setCount(count + 1); }} > hello {props.name} </Button> </>
  );
};

HelloHooks.defaultProps = {
  firstName: "",
  lastName: "",
};

export default HelloHooks;
複製代碼

利用 useEffect 新增一個功能: 點擊超過 5 次給出提示。

const HelloHooks = (props: Greeting) => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState<string | null>(null);

  // 只有當 count 改變時,渲染邏輯纔會執行。
  useEffect(() => {
    if (count > 5) {
      setText("休息一下");
    }
  }, [count]);

  return (
    <> <div> 您點擊了 {count} 次,{text} </div> <Button onClick={() => { setCount(count + 1); }} > hello {props.name} </Button> </>
  );
};
複製代碼

爲何要定義爲泛型?

不用泛型變量,this.props 的類型沒法肯定,在內部只能使用類型斷言來訪問屬性:

(this.props as Greeting).name;
複製代碼

這樣很麻煩,而 React 聲明文件把這些約束關係都用泛型定義好了。

React & TS 系列

相關文章
相關標籤/搜索