解放劳动力,玩转自定义 Hook
起因
codeReview
某前端大佬:小白呀,你这段代码好眼熟,怎么到处都有?
小白:......
某前端大佬:小白呀,你这个功能可以封住起来,其它地方也需要
小白:......
某前端大佬:小白呀,你这个小需求,怎么写了两千行代码?
小白:......
小白被问得一脸问号???决定疼定思过,好好优化一番。 Hook 是 React 16.8 的新增特性,正式发布以来,越来越多的项目正在使用函数式组件代替class类组件,Hooks 这一新特性也逐渐被广泛的使用。 然而在开发的过程中,我们发现大部分逻辑是重复且可被复用的,如对数据请求的逻辑处理,状态处理等,同样的代码经常会在不同的文件出现,以至于导致小白被问得一脸问号?。
解法
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
Hooks 对于新手而言很友好,解决了 Class 组件的 this 、 生命周期等学习成本过高的问题,但是也引入了一些规则,如:不要在循环,条件或嵌套函数中调用 Hook,导致可能会遇到一些奇奇怪怪的问题而摸掉几根头发。
自定义 Hook
以获取数据为列
我们假设现在需要获取一个用户信息,你可能会这样子写:
const [user, setUser] = useState<IUser>({
name: "",
age: 0,
});
const getUserInfo = () => {
// 模拟数据请求
const promise = new Promise<IUser>(function (resolve, reject) {
setTimeout(() => {
resolve({
name: "小白",
age: 18,
});
}, 1000);
});
promise.then((res: IUser) => {
setUser(res);
});
};
return (
<div>
<Button onClick={getUserInfo}>获取用户信息</Button>
<br />
<div> name: {user.name}</div>
<div> age: {user.age}</div>
</div>
);
如果我们需要在另一个地方也需要获取用户信息,可以把上面类似的逻辑复制并粘贴到组件中来,但这并不是理想的解决方案。 相反,我没希望它们之间共享同一个逻辑。
提取共享逻辑
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和 Hook 都是函数,所以也同样适用这种方式。
自定义 Hook 是一个函数,规则以 “
use
” 开头,函数内部可以调用其他的 Hook
下面,是我们第一个自定义的 Hook:
// useRequest.tsx
import { useState } from "react";
import { notification } from "antd";
export const useRequest = ([service](url): Function, initData: any) => {
const [data, setData] = useState(initData);
const request = () => {
service()
.then((res: any) => {
setData(res);
})
.catch(() => {
notification.error({
message: "网络错误",
});
});
};
const run = () => request();
return [data, run];
};
我们来分析下做了什么?
在useRequest函数中,我们接收了service、initData作为参数,返回了data、run。
service:请求服务的promise
initData: 初始化数据
data:接口返回数据
run:手动执行函数
与 React 组件不同的是,自定义 Hook 不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么(如果需要的话)。换句话说,它就像一个正常的函数。但是它的名字应该始终以 use
开头,这样可以一眼看出其符合 [Hook 的规则]。
市面上有许多优秀的库,如ahooks,但个人觉得在项目中更加偏向自己定义,因为比较灵活。
优化后的代码
const [user, getUserInfo] = useRequest(getUserInfoApi, { name: "", age: 0 });
return (
<div>
<Button onClick={getUserInfo}>获取用户信息</Button>
<br />
<div> name: {user.name}</div>
<div> age: {user.age}</div>
</div>
);
显而易见,优化后大大减少了代码行数,但我们的useRequest并不完善。
模拟实现ahooks的useRequest部分功能
以终为始,我们先确定好useRequest需要返回什么?从而推算出需要什么参数。 返回参数:
参数 | 说明 | 类型 |
---|---|---|
data | service 返回的数据 | TData \ undefined |
loading | service 是否正在执行 | boolean |
run | 手动触发 service 执行,参数会传递给 service | (...params: TParams) => void |
mutate | 直接修改 data ,用法与 React.setState 一致 | - |
接受的第二个参数Options
参数 | 说明 | 类型 |
---|---|---|
manual | 默认 false 初始化自动执行。设置为 true ,手动调用 run 触发执行。 | boolean |
initData | 初始参数 | any |
defaultParams | 首次默认执行时,传递给 service 的参数 | TParams |
onSuccess | service resolve 时触发 | Function |
onError | service reject 时触发 | Function |
onFinally | service 执行完成时触发 | Function |
实现逻辑代码
// useRequest.tsx
import { useState, useEffect } from "react";
import { notification } from "antd";
import { IOptions } from "../types/useRequest";
export const useRequest = (service: Function, options?: IOptions) => {
const { initData, manual, defaultParams, onSuccess, onError, onFinally } =
options || {};
const [data, setData] = useState(initData);
const [loading, setLoading] = useState(false);
const request = (parameter: any) => {
setLoading(true);
service(parameter)
.then((res: any) => {
setData(res);
// 可能需要给回调传一些参数,如返回数据、参数等
onSuccess && onSuccess(res);
})
.catch((err: unknown) => {
// 可能需要给回调传一些参数,如报错信息等
onError && onError(err);
notification.error({
message: "网络错误",
});
})
.finally(() => {
// 可能需要给回调传一些参数
onFinally && onFinally();
setLoading(true);
});
};
useEffect(() => {
// 初始化执行
!manual && request(defaultParams);
}, []);
// 手动执行
const run = (req: any) => request(req);
// 返回参数
return {
loading,
data,
run,
mutate: setData,
};
};
我们来复习一下,看看我们做了些什么?\
- 我们将数据请求处理逻辑之间的一些共同的代码提取到单独的函数hook中。
- 做了一些兼容,以应对现实场景的不同情况。
最终优化后的代码:
// app.tsx
import { Button } from "antd";
import "./App.css";
import { getUserInfoApi } from "./api/user";
import { useRequest } from "./hooks/useRequest";
function App() {
const { data: user, run: getUserInfo } = useRequest(getUserInfoApi, {
initData: { name: "", age: 0 },
manual: true,
});
return (
<div>
<Button onClick={getUserInfo}>获取用户信息</Button>
<br />
<div> name: {user.name}</div>
<div> age: {user.age}</div>
</div>
);
}
export default App;
总结
我们应寻找一些方法(自定义hook),以达到简化代码逻辑,解决组件杂乱无章的目的。
参考链接
转载自:https://juejin.cn/post/7137177180915646471