美文网首页React
React Hook丨真正的逻辑复用

React Hook丨真正的逻辑复用

作者: 前端精 | 来源:发表于2020-12-03 14:03 被阅读0次

    说起逻辑复用,熟悉 react 小伙伴们一口道出了 HOC [高阶组件] 。没错,高阶组件可以实现逻辑复用,在 hook 之前 react 还有挺多不错的方案。那么,让我们来浅谈 HOC 与 自定义 hook。

    HOC逻辑复用

    说起HOC,我想到了两个标签:1.【嵌套】 2.【一直嵌套】

    让我们来深入场景,举个例子:

    以封装一个 input 双向绑定为例

    我们经常会这样去做一个双向绑定

    // ...
    state = {
      value: 1
    };
    onChange = (e: any) => {
      this.setState({
        value: e.target.value
      });
    };
    // ...
    <input value={this.state.value} onChange={this.onChange} />;
    

    假设在一个组件内有多个 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 语法糖【自动去掉字符串头尾空格】,避免我们提交的时候有多余的空格。所以,这里我们实现这样的功能并将 getFieldValueTrim 方法挂到 props 上。

    调用

    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 完成了一个逻辑复用。假设,我们还有一个或多个「逻辑」需要抽象成一个高阶组件呢?

    如:我想要点击按钮随机切换 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 就类似这样一个“拉平”,让我们对于数据的处理更直观,更不容易犯错。

    相关文章

      网友评论

        本文标题:React Hook丨真正的逻辑复用

        本文链接:https://www.haomeiwen.com/subject/glbjyktx.html