組件複用方式 | 優點 | 劣勢 | 狀態 |
---|---|---|---|
類組件(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 的聲明文件中,對函數組件單獨定義了一個類型 -- 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>
);
複製代碼
FC
是 FunctionComponent
的簡寫,這個類型定義了默認的 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
類組件須要繼承 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
中,類組件須要爲 props
和 state
定義類型。ui
咱們如今要利用高階組件包裝一下 <HelloClass />
,包裝後的組件有一個新屬性 loading
,經過該屬性控制被包裝組件的 顯示/隱藏。
指定被包裝組件的類型爲 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
也是一種函數組件,對比類組件,明顯簡化了許多:
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
聲明文件把這些約束關係都用泛型定義好了。