說起邏輯復用颁井,熟悉 react 小伙伴們一口道出了 HOC [高階組件] 厅贪。沒錯,高階組件可以實現(xiàn)邏輯復用雅宾,在 hook 之前 react 還有挺多不錯的方案卦溢。那么,讓我們來淺談 HOC 與 自定義 hook秀又。
HOC邏輯復用
說起HOC单寂,我想到了兩個標簽:1.【嵌套】 2.【一直嵌套】
讓我們來深入場景,舉個例子:
以封裝一個 input 雙向綁定為例
我們經(jīng)常會這樣去做一個雙向綁定
// ...
state = {
value: 1
};
onChange = (e: any) => {
this.setState({
value: e.target.value
});
};
// ...
<input value={this.state.value} onChange={this.onChange} />;
假設(shè)在一個組件內(nèi)有多個 input 我們希望可以更好的去復用「雙向綁定」的邏輯吐辙,于是我們對這塊邏輯用 HOC 進行抽象:
HOCInput.tsx
const HOCInput = (WrappedComponent: any) => {
return class extends React.Component<
{},
{
fields: {
[key: string]: {
value: string;
onChange: (e: any) => void;
};
};
}
> {
constructor(props: any) {
super(props);
this.state = {
fields: {}
};
}
setField = (name: string) => {
if (!this.state.fields[name]) {
this.state.fields[name] = {
value: "",
onChange: (event: any) => {
this.state.fields[name].value = event.target.value;
this.forceUpdate();
}
};
}
return {
value: this.state.fields[name].value,
onChange: this.state.fields[name].onChange
};
};
getFieldValueTrim = (name: string) => {
return this.state.fields[name]
? this.state.fields[name].value.trim()
: "";
};
render() {
const { setField, getFieldValueTrim } = this;
const newProps = { setField, getFieldValueTrim };
return <WrappedComponent {...this.props} {...newProps} />;
}
};
};
在 Vue 中有不錯的 v-model.trim 語法糖【自動去掉字符串頭尾空格】宣决,避免我們提交的時候有多余的空格。所以昏苏,這里我們實現(xiàn)這樣的功能并將 getFieldValueTrim
方法掛到 props 上尊沸。
調(diào)用
Demo1Component.tsx
class Demo1Component extends React.Component<{
setField?: (name: string) => { value: string; onChange: (e: any) => {} };
getFieldValueTrim?: (name: string) => string;
}> {
render() {
const { setField, getFieldValueTrim } = this.props;
console.log("name :>> ", getFieldValueTrim!("name"));
return (
<div>
<input {...setField!("name")} />
<br />
<input {...setField!("email")} />
</div>
);
}
}
// 嵌套
const Demo1 = HOCInput(Demo1Component);
//...
<Demo1 />
// ...
這樣,我們就用 HOC 完成了一個邏輯復用贤惯。假設(shè)洼专,我們還有一個或多個「邏輯」需要抽象成一個高階組件呢?
如:我想要點擊按鈕隨機切換 input 框的背景顏色孵构。
那就讓我們繼續(xù)封裝 HOC
HOCInputBgColor.tsx
const HOCInputBgColor = (initialColor: string) => (WrappedComponent: any) => {
return class extends React.Component<{}, { color: string }> {
state = {
color: initialColor
};
getRandomColor = () => {
const randomNum = () => Math.floor(Math.random() * 100);
return `rgb(${randomNum()},${randomNum()},${randomNum()})`;
};
handleChangeColor = () => this.setState({ color: this.getRandomColor() });
render() {
const newProps = {
color: this.state.color,
handleChangeColor: this.handleChangeColor
};
return <WrappedComponent {...this.props} {...newProps} />;
}
};
};
在原來的組件上進行調(diào)用
Demo1Component.tsx
class Demo1Component extends React.Component<{
setField?: (name: string) => { value: string; onChange: (e: any) => {} };
getFieldValueTrim?: (name: string) => string;
color?: string;
handleChangeColor?: () => void;
}> {
render() {
const {
setField,
getFieldValueTrim,
color,
handleChangeColor
} = this.props;
return (
<div>
<input style={{ background: color! }} {...setField!("name")} />
<br />
<button onClick={handleChangeColor!}>change bg-color</button>
</div>
);
}
}
/
const Demo1 = HOCInput(HOCInputBgColor("rgb(158,158,158)")(Demo1Component));
當我們有更多的 HOC 時屁商,那么就會一直嵌套下去,好在有ts裝飾器的支持颈墅,讓我們看這個「嵌套」看著更加舒適蜡镶,如:
@HOCInput
@HOCInputBgColor("rgb(158,158,158)")
class Demo1Component extends React.Component { }
我們也不再需要重新把組件賦值給一個變量雾袱,在調(diào)用組件的時候,直接 <Demo1Component />
官还。
HOC缺點
當組件在調(diào)用多個HOC時芹橡,會調(diào)用 props 上 HOC 傳遞下來的 值/方法,如上面的例子:
const {
setField,
getFieldValueTrim,
color,
handleChangeColor
} = this.props;
要是 HOC 一多命名就要形成規(guī)范望伦,否則將有可能導致重命名發(fā)生覆蓋林说。這算是 HOC 的一個缺點吧。
自定義 hook 邏輯復用
官網(wǎng):自定義 hook 解決了以前在 React 組件無法靈活共享邏輯的問題屯伞。
我們直接把上面的例子改成 hook 版看看腿箩。
useInput.ts【自定義 Hook 名稱需要以 “use” 開頭】
const useInput = (
initialValue = ""
): [{ value: string; onChange: (e: any) => void }, string] => {
const [value, setValue] = useState(initialValue);
const onChange = (e: any) => {
setValue(e.target.value);
};
return [
{
value,
onChange
},
`${value}`.trim()
];
};
使用
Demo2.tsx
const Demo2: React.FC = () => {
const [nameIpt, name] = useInput();
const [emailIpt, email] = useInput();
console.log("Hook-name :>> ", name);
console.log("Hook-email :>> ", email);
return (
<div>
<input {...nameIpt} />
<br />
<input {...emailIpt} />
</div>
);
};
可以明顯的看到幾個優(yōu)點:
代碼更簡潔
不存在重命名覆蓋
解釋:現(xiàn)在的可復用狀態(tài)沒有像 HOC 掛到被包裝組件的 this.props 上了,我們都知道 hook 的寫法可以暴露出一個數(shù)組:[ 值 , 方法 ]愕掏。在使用的時候度秘,可以用解構(gòu)的手法來實現(xiàn)對數(shù)組內(nèi)變量名的自定義顶伞,保證命名不重復饵撑。沒有嵌套
如果還有更多的邏輯需要被抽象,我們只管繼續(xù)封裝 useXxx
唆貌,然后在組件中進行使用滑潘。
如上面講的 HOCInputBgColor
高階組件,我們也可以用 hook版進行封裝锨咙,如 useInputBgColor
语卤,小伙伴們,動手試試看吧~
總結(jié)
在數(shù)據(jù)的處理中酪刀,我們知道在處理“平級”的數(shù)據(jù)粹舵,往往比嵌套的、樹形的數(shù)據(jù)來得簡單骂倘。
就如:
const arr = [1, [2, 3, [4, 5]]];
arr.flat('Infinity');
// [1, 2, 3, 4, 5]
個人覺得 自定義hook 就類似這樣一個“拉平”眼滤,讓我們對于數(shù)據(jù)的處理更直觀,更不容易犯錯历涝。