相似的业务代码
俗话说,人活得久,什么都能遇到,前端搬砖久了,各种代码也就遇到了,在这几年的搬砖生活中,尤其是 To B 的项目,不难发现,有很多需求都是重复类似的
比如:
一个输入框,
BA(业务分析师)告诉你 不能输入空格,第二天突然说需求改成中间不能有空格一个展示数据的表格,有一些过滤项和分页,需要将过滤条件和分页页码保存起来,以支持刷新之后保持上一次搜索条件
一个表单项,点击新增按钮, 点击一次,出现一个
input框,还可以删除
…
是不是非常的熟悉且怀念,似乎还有一点马上打开编辑器写上几行代码的冲动
怎么办?
程序员有三宝,复制粘贴头发少,遇到类似的需求,也许会打开以前的项目,复制粘贴代码过来,修修补补搞定,亦或是封装一些 高阶组件 来搞定需求,还有没有其他的解决方案呢?众所周知,react hook 解决了逻辑复用的问题,似乎不错的样子,那么就来尝试一下
去除空格
以 去除空格 这个常见的需求来说,我们自定义一个 useTrimInput 的 hook`
1 | import { useState, useCallback } from 'react'; |
首先定义一个 value state, 用来保存当前 input 的 值,使其是一个受控组件,再定义 setTrimValue 接受当前 event 的 value 值,逻辑非常简单
接下来就需要对当前 state 的 value 进行 trim, fullTrim 参数表示是否去除 首尾和中间所有的空格
定义
trim方法
1 | // utils/string.ts |
最后我们使用一个函数版本的 getter 对 value 进行转换
1 | import { useState, useCallback, useMemo } from 'react'; |
经过简单的封装,这样一个常见的业务场景的自定义 hook 就写好了
效果如下:
1 | import React from 'react'; |

另一个例子:分页和过滤项同步到 url
梳理一下这个需求常见的步骤
- 默认第一页 (page = 1)
- 单击页码 (第二页), 发起 ajax 请求 (page = 2)
- 将当前页码 (2) 同步到 url 上,这时候地址栏为
http://xxx.com/list?page=2 - 如果用户刷新,需要拿到当前地址栏的
{ page: 2 }赋值给Table组件,并且请求 ( page = 2 ) 的数据 - 特殊情况,由于地址栏拿到的都是
String类型的值,过滤项往往除了page=2这种数字之外,还有常见的各种类型 - 可能会有默认值,比如一进页面就显示
http://xxx.com/list?filter=xxx
1 | { |
更恐怖的是从地址栏拿到后,还需要转换成设置之前的数据类型,也就是
?name=test=>{ name: 'test' }?age=18=>{ name: 18 }?success=true=>{ success: true }
细思极恐,仔细梳理之后,分析出几个关键点
- 数据类型之间的互相转换,提供一个类似
mongoose的Schema机制,预先声明好每一个字段的类型和默认值 - 增加删除指定的字段
第 1 点的灵感其实来自项目中一个很厉害同事的灵感,这里借鉴(抄袭)了一下 :)
首先看实现效果
页码同步
不同类型的参数自动转换
代码实现
首先定义支持的数据类型
1 | export enum UseSearchParamsSchemaType { |
然后是返回类型
1 | export interface UseSearchParamsReturn<T> { |
1 | import { useCallback, useMemo, useEffect, useState } from 'react' |
- 我们使用浏览器提供的 URLSearchParams API 来处理 URLSearchParams
- 使用
react-router-dom提供的useHistory和useLocation方便的获取到当前应用的history和location, 用于跳转
接下来是 处理 默认值, 我们规定一个 schema 长这样
1 | const schema = { |
1 | // 将两种定义方式 统一成一种数据结构 |
通过 schema 解析了默认值后,我们需要将 defaultValues 同步设置到 urlSearchParams, urlSearchParams 不支持 set 一个数组,所以只能遍历
1 | const setDefaultValues = useCallback(() => { |
接下来实现 set 方法:用于增加或更新一个字段
1 | // 调用 history.push 更新路由的 search |
将 set(values) 的 values 同步到 urlSearchParams 后,我们调用 history.push 即可更新 url 的 search 参数,
值得注意的是,数组的展开方式是 通过 repeat 的方式,也就是说:
1 | const a = [1, 2]; |
所以这里要区别对待
1 | if (Array.isArray(value)) { |
完成了设置,还差关键的一部,就是数据类型之间的互相转换,有了之前定义好的 Schema, 这非常容易实现
数据类型解析并转换
1 | const parseValue = useCallback( |
- 对于
String类型,我们进行强制类型转换即可String(value), 值得注意,由于 url 是字符串,所以会出现字符串的"undefined", 转成空字符串即可 - 对于
NUMBER类型,同样强制类型转换即可Number(value) - 对于
BOOLEAN类型,得到是字符串的 boolean 值,也就是true|false, 所以这里用value === 'true'(三个等号) 进行不做隐私类型转换的比较,得到真正的boolean值 - 对于
ARRAY类型,很方便,使用 urlSearchParams 提供的getAllapi 即可
这样疯狂操作一波,一个常见的业务需求就搞定了,是不是可以早点下班了呢?其余 remove 和 reset 方法等参不多类似,就不一一列举了,
为了方便使用,我发布了一个 npm 包 react-7h-hooks, 感兴趣的可以查看源码
结语
相对于 高阶组件, hooks 的方式更便于我们封装逻辑,同时代码层级是扁平化的,便于维护,如果不是使用了 hooks, 节省了我 1 个小时的时间,也不会有空写这篇文章了
完。


