提及邏輯複用,熟悉 react 小夥伴們一口道出了 HOC [高階組件] 。沒錯,高階組件能夠實現邏輯複用,在 hook 以前 react 還有挺多不錯的方案。那麼,讓咱們來淺談 HOC 與 自定義 hook。react
提及HOC,我想到了兩個標籤:1.【嵌套】 2.【一直嵌套】數組
讓咱們來深刻場景,舉個例子:app
咱們常常會這樣去作一個雙向綁定dom
// ... state = { value: 1 }; onChange = (e: any) => { this.setState({ value: e.target.value }); }; // ... <input value={this.state.value} onChange={this.onChange} />;
假設在一個組件內有多個 input 咱們但願能夠更好的去複用「雙向綁定」的邏輯,因而咱們對這塊邏輯用 HOC 進行抽象:this
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 語法糖【自動去掉字符串頭尾空格】,避免咱們提交的時候有多餘的空格。因此,這裏咱們實現這樣的功能並將 getFieldValueTrim
方法掛到 props 上。code
調用字符串
Demo1Component.tsxget
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 完成了一個邏輯複用。假設,咱們還有一個或多個「邏輯」須要抽象成一個高階組件呢?input
如:我想要點擊按鈕隨機切換 input 框的背景顏色。
那就讓咱們繼續封裝 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} />; } }; };
在原來的組件上進行調用
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 { }
咱們也再也不須要從新把組件賦值給一個變量,在調用組件的時候,直接 <Demo1Component />
。
當組件在調用多個HOC時,會調用 props 上 HOC 傳遞下來的 值/方法,如上面的例子:
const { setField, getFieldValueTrim, color, handleChangeColor } = this.props;
要是 HOC 一多命名就要造成規範,不然將有可能致使重命名發生覆蓋。這算是 HOC 的一個缺點吧。
官網:自定義 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> ); };
能夠明顯的看到幾個優勢:
若是還有更多的邏輯須要被抽象,咱們只管繼續封裝 useXxx
,而後在組件中進行使用。
如上面講的 HOCInputBgColor
高階組件,咱們也能夠用 hook版進行封裝,如 useInputBgColor
,小夥伴們,動手試試看吧~
在數據的處理中,咱們知道在處理「平級」的數據,每每比嵌套的、樹形的數據來得簡單。
就如:
const arr = [1, [2, 3, [4, 5]]]; arr.flat('Infinity'); // [1, 2, 3, 4, 5]
我的以爲 自定義hook 就相似這樣一個「拉平」,讓咱們對於數據的處理更直觀,更不容易犯錯。