弄懂自定义 Hooks 不难,改变开发认知有点不习惯

2,581 阅读7分钟

前言

我之前总结逻辑重用的时候,就一直在思考一个问题。

对于逻辑复用,render props 和 高阶组件都可以实现,同样官方说 Hooks 也可以实现,且还是在不增加额外的组件的情况下。

但是我在项目代码中,没有找到自定义 Hooks 的身影,自己也很少写。所以是为什么呢?

遇到问题表示查漏补缺的机会来了。

遇到技术点先问几个为什么

偶尔我会先问自己几个为什么,然后去寻找答案。带着明确的目标,不容易漏掉重要的讯息。

  1. 什么是自定义 Hooks?很难实现吗?
  2. 自定义 Hooks 和普通函数有什么区别?
  3. 如何创建一个自定义 Hook?
  4. 为什么需要自定义 Hooks?哪些场景会用到?
  5. 为什么我好像平时用不到?

自定义 Hooks 很难吗?

自定义 hooks可以看做是 React Hooks 的之外的自由延伸。本质上还是 JavaScript 函数。主要用途是实现逻辑重用。

React Hooks、函数、逻辑重用,根据这些关键字观察,实现自定义 Hooks 应该不是很难。

自定义 Hooks 和普通函数有什么区别?

看了关于自定义 Hooks 的介绍,我冒出来了第二个小问号:

这跟普通的逻辑复用函数有什么区别呢?

于是,我认真阅读了React的官方文档,发现主要有两点区分:

  • 名字应该始终以 use 开头。
  • 在自定义 Hook 中调用其他 Hook。

如何创建一个自定义 Hook?

因为Hook 本质是 JavaScript 函数,所以创建自定义 Hook其实就是声明一个名字以 use 开头的函数。

useDateRange

这个自定义 Hook 的过程算是意外惊喜,我在看完官方的例子之后,一直在想我实际开发中有哪些部分可以通过自定义 Hook实现逻辑的重用。

然后意外发现了项目中存在的可优化空间,后续会陆续产出优化的经验。

这里我先以项目中常出现的日期选择器为例,演示一下创建自定义 Hook 的全过程。

自定义 Hook 介绍

当前的需求如下:

  • 日期选择器,需要设置可选日期范围,超出1天~10天的范围不可选择。
  • 选择某个日期之后,需要计算跟当天日期相差的天数,并回显到页面上。

注:日期选择器使用的 antd 提供的组件,所有的参数均可以在官网查看。

UI 效果

可选日期效果

相差天数回显

可复用逻辑提取

可复用的逻辑主要包括三个部分:日期选择器的可选限制、时间发生变化的回调函数、相差天数变量。

日期选择器的可选限制

moment.js 的 add 方法可以计算指定时间之后(或之前)的某个时间。结合 startOf 方法和 endOf 方法使用,可以确定可选范围的起始日期。

时间发生变化的回调函数

当选定日期的时候,回调函数会返回选中的日期。

相差天数

通过时间回调函数拿到当前选择的日期,使用 moment.js 的 diff 方法,与今天的日期做比较。diff 方法的第二个参数决定差值的度量单位,day表示天数。

/**
 * @description 时间选择器的限制和相差天数
 */
import React, { useState, useCallback, useMemo } from 'react';
import moment from 'moment';

const useDateRange = () => {
  // 相差天数
  let [dayDiff, setDayDiff] = useState(0);
  /**
   * 可选时间范围 1~10
   */
  const disabledDate = useCallback(current => {
    const startTimer = moment()
      .add(+1, 'days')
      .startOf('day');
    const endTimer = moment()
      .add(+10, 'days')
      .endOf('day');
    return current && (current < startTimer || current > endTimer);
  }, []);

  /**
   * 时间选择器选择操作 获取相差天数
   */
  const dateChange = useCallback(dates => {
    let nowDate = new Date();
    let startData = moment(nowDate).format('YYYY-MM-DD');
    let endData = moment(dates).format('YYYY-MM-DD');
    dayDiff = moment(endData).diff(moment(startData), 'day');
    setDayDiff(dayDiff);
  }, []);

  return { dayDiff, disabledDate, dateChange };
};
export default useDateRange;

使用 useDateRange

引入自定义的 useDateRange 这个 Hook,就可以拿到传入的参数了。

/**
 * @description 日期选择页
 */
import React from 'react';
import { DatePicker } from 'antd';
import useDateRange from './hooks/useDateRange';

const ChangeDatePage = () => {
  // 使用自定义 Hook
  const { disabledDate, dateChange, dayDiff } = useDateRange();

  return (
    <div>
      <DatePicker disabledDate={disabledDate} onChange={dateChange} />
      <div className="mt20">选择的日期与今天相差: {dayDiff} 天</div>
    </div>
  );
};

export default ChangeDatePage;

自定义 Hooks 的优势和使用场景

等了解了它的优势,基本也就能联想到有哪些使用场景。

优势

自定义 Hooks 主要有两点优势:

  • 逻辑复用
  • 复杂代码分离

使用场景

通过对自定义 Hooks 了解和使用,可以归纳的使用场景主要包括三个大类:

封装可复用的逻辑、监听数据的状态、拆分复杂的逻辑

基于这三类场景,可应用的地方应该不少。未来一段时间,我准备改造一下我们的项目代码,这下事情开始有趣了。

为什么我好像平时用不到?

为何纠结用不到这件事?

先简单说一下最近为什么一直纠结用不到这件事。因为身处「业务驱动技术」的技术科技公司,业务迭代是很迅速的,想在快节奏的开发中,整个前端团队保持一个较高水平的开发水平,不是很容易。

想在逻辑复用上下一些功夫,提升开发效率,团队成员可以腾出一些时间进行技术升级。

开发认知

自定义 Hooks 和普通函数的界限是可变的,加上平时开发的命名不太习惯用 use 开头。

所以用到的少,并不是因为自定义 Hooks 的开发难度或者开发者技术水平,而是改变这个开发认知有点不习惯。

但是个人认为改变是有帮助的。

  • 已知自定义 Hooks 主要用途是进行逻辑复用,那么从 use 开头的函数名,使用者不难联想到该函数方法是不是封装了某个公共的逻辑。
  • 其实除了逻辑复用,复杂逻辑的拆分,也可以考虑用自定义 Hooks 实现。

今日总结

今天除了清楚自定义 Hooks的相关知识点、如何创建以及使用场景。还帮助我衍生了新的开发思路,让我对原以为无法进行逻辑复用的功能,有了新的想法。

后面如果有了更好的开发思路,会持续输出文章分享出来。

彩蛋

今天的彩蛋,讲个之前困扰过我问题以及后来我总结出来的经验。

现阶段一直在写业务代码,感觉技术上没有得到什么提升?

我之前也被这个问题困扰过。后来,我从身边很多优秀的思维中,总结了一些经验,也就不再纠结这个问题。

误区在哪?

技术和业务的关系,不是对立面,而是相辅相成、相得益彰的。尤其现在大多数是业务驱动型公司。已知「技术可推动业务的发展」,同样的业务日趋繁复,倒逼技术的更迭。

日复一日重复的功能?

遇到这个困惑时,不排除一种可能,受思维定式的影响,重复之前的实现方式。

但是功能本身未必不能用更好的方式去实现。

举个例子,搜索项常见于表格页面,因为每个页面的搜索项都不太一样,很长一段时间,都被散落在各个页面中。其实搜索项的类型基本就那几种,于是有同事封装了一个公共组件,只需要传入包含搜索项基础信息的配置数组变量即可。

以前散落在每个页面的搜索项,如果再加上事件的处理,冗余代码量会更多。

<div>
  <label>城市:</label>
  <Input placeholder='请输入城市' />
</div>
<div>
  <label>分类:</label>
  <Select placeholder='请选择'>
    <Select.Option key='1'>分类1</Select.Option>
    <Select.Option key='2'>分类2</Select.Option>
  </Select>
</div>

后来封装成公共组件之后,代码简洁了很多,且大部分逻辑处理在组件中,使用时开发者并不用关心这些。

const fields = [
  {
    label: '城市',
    fieldtype: 'input',
  },
  {
    label: '分类',
    fieldtype: 'select',
    option: [
      {
        key: 1,
        value: '分类1'
      },
      {
        key: 2,
        value: '分类2'
      },
    ]
  },
]
<Search fields={fields} />

工作中找不到项目练手?

我把日常的经验总结为:主动出击和没有条件创造条件。

  • 如果有一定的能力,可以申请功能立项或者参与一些工程化建设项目。自己主动把握机会。
  • 追随开拓者,自己创造条件。从模仿优秀的开源的项目到开发属于自己独立思维的项目过度,也是一个不错的选择。

总结时刻

三个问题和三个解答,解铃还须系铃人,心结还需自己解。

本文正在参加「金石计划 . 瓜分6万现金大奖」