React Hook丨真正的邏輯複用

提及邏輯複用,熟悉 react 小夥伴們一口道出了 HOC [高階組件] 。沒錯,高階組件能夠實現邏輯複用,在 hook 以前 react 還有挺多不錯的方案。那麼,讓咱們來淺談 HOC 與 自定義 hook。react

HOC邏輯複用

提及HOC,我想到了兩個標籤:1.【嵌套】 2.【一直嵌套】數組

讓咱們來深刻場景,舉個例子:app

以封裝一個 input 雙向綁定爲例

咱們常常會這樣去作一個雙向綁定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缺點

當組件在調用多個HOC時,會調用 props 上 HOC 傳遞下來的 值/方法,如上面的例子:

const {
  setField,
  getFieldValueTrim,
  color,
  handleChangeColor
} = this.props;

要是 HOC 一多命名就要造成規範,不然將有可能致使重命名發生覆蓋。這算是 HOC 的一個缺點吧。

自定義 hook 邏輯複用

官網:自定義 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>
  );
};

能夠明顯的看到幾個優勢:

  1. 代碼更簡潔
  2. 不存在重命名覆蓋,解釋:如今的可複用狀態沒有像 HOC 掛到被包裝組件的 this.props 上了,咱們都知道 hook 的寫法能夠暴露出一個數組:[ 值 , 方法 ]。在使用的時候,能夠用解構的手法來實現對數組內變量名的自定義,保證命名不重複。
  3. 沒有嵌套

若是還有更多的邏輯須要被抽象,咱們只管繼續封裝 useXxx,而後在組件中進行使用。
如上面講的 HOCInputBgColor 高階組件,咱們也能夠用 hook版進行封裝,如 useInputBgColor,小夥伴們,動手試試看吧~

總結

在數據的處理中,咱們知道在處理「平級」的數據,每每比嵌套的、樹形的數據來得簡單。

就如:

const arr = [1, [2, 3, [4, 5]]];
arr.flat('Infinity'); 
// [1, 2, 3, 4, 5]

我的以爲 自定義hook 就相似這樣一個「拉平」,讓咱們對於數據的處理更直觀,更不容易犯錯。

相關文章
相關標籤/搜索