React自定义hook,ahooks的useRequest()钩子如何使用?
发布于 作者:苏南大叔 来源:程序如此灵动~useRequest()
是ahooks
的成名钩子,使用范围很广。当然,它其实就相当于axios
/fetch
/jquery.get
等等的变种,可以用于react
程序和远程服务器端的交互。它的业务范围,类似于:路由loader
属性或者useFetcher
钩子,甚至覆盖了react
的SWR
范围。
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0
,create-react-app@5.0.1
,react-router-dom@6.27.0
,react@18.3.1
,ahooks@3.8.1
。本文描述的来自ahooks
的useRequest()
。
准备工作
本文基础代码,使用create-react-app
获得,先安装ahooks
,是关键前提条件。
create-react-app test
cd test
npm i ahooks --save
本文使用setTimeout
模拟接口请求,具体参考下文:
简单例子
这里有两个例子,一个是官方文档给出的。另外一个是苏南大叔改写的例子。
最简单例子
这个是官方给出的最简单的例子:
api.js
:
export function fetchData() {
return fetch("http://localhost:8888/who").then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch data");
}
return response.json();
});
}
app.js
:
import React from "react";
import { useRequest } from "ahooks";
import { fetchData } from './api.js';
function App() {
const { data, loading, error } = useRequest(fetchData);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return (
<div>
<h1>Data:</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default App;
useRequest()
,默认情况下,就是立即执行的,组件加载和更新的时候,都会加载。然而,官方也提供了功能定制,定制其执行的时机。
修改版demo
苏南大叔根据自己的理解,对官方的入门例子做了一些改编,写了另外一个demo
。fetchRemote.js
:
// 真实请求
function fetchDataReal() {
return fetch("http://localhost:3222/get").then(async (response) => {
if (!response.ok) {
throw new Error(await response.text());
}
return response.json();
});
}
// 虚拟请求
function fetchDataFake() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve({
code: 1,
data: { id: Date.now().toString().slice(-6), name: "苏南大叔" },
});
} else {
reject({ code: 0, message: "发生了错误" });
}
}, 3000);
});
}
export function fetchData(...args) {
console.log("network", args);
// 函数目标:
// 1. 返回json/string:.then({return json})或者resolove(json)
// 2. 或者 reject({message:"string"})
// 3. 或者 throw new Error("string")
// return fetchDataReal();
return fetchDataFake();
}
App.js
:
import React from "react";
import { useRequest } from "ahooks";
import { fetchData } from "./fetchRemote.js";
function Status(props) {
if (props.loading) {
return <p>Loading...</p>;
}
if (props.error) {
return <p>Error: {props.error.message}</p>;
}
}
function App() {
const { data, loading, error, run } = useRequest(fetchData);
return (
<div>
<button onClick={run} disabled={loading}>
{loading ? "loading" : "请求数据"}
</button>
<Status loading={loading} error={error}></Status>
<pre>{!loading && !error && JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default App;
和官方的例子也差不多,只是融合进去了自己对虚拟请求的代码。参考文章:
两点体会
1、要想useRequest()
的error
状态正确呈现,那么相关的远程函数里面的错误就要及时的throw
或者reject()
。绝对不能catch
,否则error
的状态就很难得到保证了。
2、它这个data
似乎是有些问题的,第二个请求如果失败的话,也不会清空第一次请求的结果data
,会造成比较大的误会。所以,需要仔细判断处理。
核心代码 / 参数传递
主要的核心内容,就是这里了。
const { data, loading, error, run } = useRequest(fetchData);
这里的重点就是函数fetchData
,注意:这里没有()
,没有传递参数。所以,对于传递参数的需求,个人建议可以略作修改,比如:
const { data, loading, error, run } = useRequest( () => fetchData(123) );
或者:
const fetchDataGo = () => {
fetchData(123);
}
//...
const { data, loading, error, run } = useRequest(fetchDataGo);
因为上面的run
,也代表着这个取数据的函数。所以,最好把带参数的函数调用,再做一次封装。免得发生混乱。
各种选项
官方提供了很多选项,功能相当详细,各种细节都有考虑到,不过苏南大叔都用不到。useRequest()
官方文档如下:
不默认执行
const { data, loading, error, run } = useRequest(fetchData,{
loadingDelay: 300,
manual: true,
// refreshDeps: [uid], // 和手工选项冲突
});
因为这个函数是默认执行的,组件加载或刷新的时候,就会默认发出网络请求。所以,加个手动开关还是比较有必要的。比如:manual: true
。其实,就是不默认执行的意思。和run
或者runsync
是否生效无关。
至于run
和runsync
这两个傀儡函数,苏南大叔的理解就是使用run
吧。毕竟和官方的思路最契合,官方对error
做管理。
loading状态延迟
loadingDelay
,官方说法叫做“防闪烁”。如果网络数据接口非常快就返回数据的话,界面就会闪烁。其实就是切换到loading
状态,又马上切换走了。设置这个值,就可以延迟loading
状态的切换。
经过测试,苏南大叔发现这个东东就是个掩耳盗铃的设置。仅仅是延迟变成loading的状态,实际请求早已发生。因为界面上一些组件也是依赖这个loading
状态进行表现的。所以,延迟loading
,就会使得其它的一些组件延迟刷新了。体验也不一定好。
依赖项refreshDeps
这个设置还是比较有意思的,但是开启它的前提就是manual: false
。虽然官方没说这事,但是经过多次实验也是可以知道的。关联的参数,也必须是useState
的。否则没效果。估计useReactive
的变量,应该也行。
import React, { useState } from "react";
import { useRequest } from "ahooks";
import { fetchData } from "./fetchRemote.js";
function Status(props) {
if (props.loading) {
return <p>Loading...</p>;
}
if (props.error) {
return <p>Error: {props.error.message}</p>;
}
}
function App() {
const [uid, setUid] = useState(1);
const { data, loading, error, run } = useRequest(() => fetchData(uid), {
loadingDelay: 300,
refreshDeps: [uid], // 和手工选项冲突
});
return (
<div>
<button
onClick={() => {
setUid(uid + 1);
}}
disabled={loading}
>
设置uid,{uid}
</button>
<Status loading={loading} error={error}></Status>
<pre>{!loading && !error && JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default App;
结语
远程数据请求,总是一个老生常谈,还每次都能谈出新花样的问题。更多react
经验文章,请参考:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。