React Hook丨真正的逻辑复用

说起逻辑复用,熟悉 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 就类似这样一个“拉平”,让我们对于数据的处理更直观,更不容易犯错。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容