React缓存,如何使用useAsyncMemo()缓存异步函数结果?
发布于 作者:苏南大叔 来源:程序如此灵动~上一篇文章里面,苏南大叔说可以使用useMemo()
缓存普通函数的昂贵计算逻辑结果。在文章的最后,特别强调了useMemo()
作用于普通函数,而不能作用于异步函数。异步函数的关键词有:async
、await
、promise
、resolve
、reject
、.then()
等等。本文将使用react
的另外一个非官方钩子useAsyncMemo()
,实现异步函数结果的缓存。
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0
,create-react-app@5.0.1
,react-router-dom@6.27.0
,react@18.3.1
,use-async-memo@1.2.5
。useAsyncMemo()
是个钩子,所以只能用在函数式组件里面。
接口代码
异步函数大部分情况下是成功resolve()
,但是也存在reject()
或者new Error()
。在苏南大叔以前的文章里面,还分为setTimeout
模拟,以及fetch
真实请求两种情况。
对于本文实验用的接口来说,存在一个是否catch
异常的问题(reject
其实也算个异常):
- 在前面的文章里面,苏南大叔描述,不能
catch
,因为这个要由useRequest
钩子处理。 - 本篇文章里面,情况有变。这个新的
useAsyncMemo()
钩子,不能处理异常。所以,需要自行catch
。
本地代码
ApiService.js
:
const ApiService = {
fetchDataRemote: function (p1) {
return fetch("http://localhost:3222/get?" + p1)
.then((response) => {
return response.json();
})
.catch(() => {
return { message: "fail" }; //这个算正常返回值
});
},
fetchDataFake: function (p1) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ time: Date.now().toString().slice(-6) });
//reject()需要被catch,干脆别发出吧...
}, 3000);
});
},
};
export default ApiService;
远程代码
express.js
:
const express = require("express");
const app = express();
var cors = require("cors");
app.use(cors());
app.all("*", (req, res) => {
let ret = {
time: Date.now().toString().slice(-6),
};
res.send(JSON.stringify(ret));
});
const server = app.listen(process.env.PORT || 3222, () => {
const port = server.address().port;
console.log("http://localhost:%d", port);
});
不使用useAsyncMemo
假设没有这个use-async-memo
的钩子的话,可能代码是这样写的:
现况之useeffeect
import React, { useEffect, useState } from "react";
import ApiService from "./ApiService";
function doRequest(p1) {
// return ApiService.fetchDataRemote(p1);
return ApiService.fetchDataFake(p1);
}
export default function App() {
const [s, setS] = useState(0); // 用作组件更新
const [e, setE] = useState(""); // 用作演示结果
useEffect(() => {
(async () => {
console.log("run e0");
setE(await doRequest());
})();
}, []);
return (
<div>
<div>昂贵值 e: {JSON.stringify(e)}</div>
<button onClick={() => setS(s + 1)}>
组件自身渲染,观察昂贵值是否变化{s}
</button>
</div>
);
}
这里为了避免async
延时更新带来死循环。这里使用useEffect()
来保证其只执行一次。如果有依赖项的话,也可以添加到useEffect()
的第二个参数里面。
现况之死循环
不使用useEffect()
的话,可能会陷入死循环。虽然是个类似setInterval
的效果,并不是while(true)
那样的死循环。
import React, { useEffect, useState } from "react";
import ApiService from "./ApiService";
function doRequest(p1) {
// return ApiService.fetchDataRemote(p1);
return ApiService.fetchDataFake(p1);
}
export default function App() {
const [s, setS] = useState(0); // 用作组件更新
const [e, setE] = useState(""); // 用作演示结果
// useEffect(() => {
(async () => {
console.log("run e0");
setE(await doRequest(s));
})();
// }, [s]);
return (
<div>
<div>昂贵值 e: {JSON.stringify(e)}</div>
<button onClick={() => setS(s + 1)}>
组件自身渲染,观察昂贵值是否变化{s}
</button>
</div>
);
}
useAsyncMemo()
useAsyncMemo()
是个非官方的钩子,它的github
地址是:
作者看上去是个阿里的神人。useAsyncMemo()
对标的就是react
官方的useMemo()
,所以两者的功用也很相似,就是对函数的结果进行缓存。useAsyncMemo()
缓存的是异步函数,useMemo()
缓存的是非异步函数。
官方给出的原型是这样的:
function useAsyncMemo<T>(factory: () => Promise<T>, deps: DependencyList, initial?: T): T
值得注意的是:这里出现了一个initial?
参数。这个参数是useMemo()
里面所没有的。经过代码尝试,发现这个参数是用来提供一个结果占位符的。换句话说,就是这个“昂贵的异步函数”还没有返回值的时候,提前先返回一个值。等异步函数正式返回数据之后,就代替这个占位的值。
使用之前,记得先安装use-async-memo
:
npm i use-async-memo --save
useAsyncMemo()之异步
这个useAsyncMemo()
的依赖项参数,必须填写,不能省略,否则就是死循环。即使没有依赖项,那么,也需要写成[]
。如果需要传递参数的话,依然需要做到:
- 参数取自全局(父级)
- 参数加入依赖项(参数变,函数就变)
import React, { useState } from "react";
import { useAsyncMemo } from "use-async-memo";
import ApiService from "./ApiService";
function doRequest(p1) {
// return ApiService.fetchDataRemote(p1);
return ApiService.fetchDataFake(p1);
}
export default function App() {
const [s, setS] = useState(0); // 用作组件更新
const [n, setN] = useState(1); // 用作useAsyncMemo传参
// const e0 = useAsyncMemo(() => {
// console.log("run e0");
// return doRequest();
// }); // 没有定义依赖,恒定变化,疯狂死循环变化
const e0 = useAsyncMemo(() => {
console.log("run e0");
return doRequest();
}, []); // 无论如何都不变,自身渲染不变,又没有依赖项
const e1 = useAsyncMemo(() => {
console.log("run e1");
return doRequest(n);
}, [n]); //传参n,很合理,注意没出现async和await
const e2 = useAsyncMemo(async () => {
console.log("run e2");
return await doRequest(n);
}, [n]); //和上面的写法很不同,上面没有await和async
return (
<div>
<div>
e0: {JSON.stringify(e0)}[无依赖,恒定不变]
<br />
e1: {JSON.stringify(e1)}[依赖n,n变就变]
<br />
e2: {JSON.stringify(e2)}[依赖n,n变就变]
<br />
</div>
<button onClick={() => setS(s + 1)}>
组件自身渲染, 昂贵值不变。s:{s}
</button>
<br />
<button onClick={() => setN(n + 1)}>n变化, n同时是依赖项(n做参数) n:{n}</button>
</div>
);
}
useAsyncMemo()之默认值
useAsyncMemo()
存在第三个参数,就是这个函数的默认值,就是运算结果没返回的时候的占位值。
import React, { useState } from "react";
import { useAsyncMemo } from "use-async-memo";
import ApiService from "./ApiService";
function doRequest(p1) {
// return ApiService.fetchDataRemote(p1);
return ApiService.fetchDataFake(p1);
}
export default function App() {
const [s, setS] = useState(0); // 用作组件更新
const [n, setN] = useState(1); // 用作useAsyncMemo传参
const e3 = useAsyncMemo(async () => {
if (n > 6) return {};
return await doRequest(n);
}, [n]);
const e4 = useAsyncMemo(
async () => {
if (n > 6) return {};
return await doRequest(n);
},
[n],
{ sunan: "这是默认值" }
);
return (
<div>
<div>
e3: {JSON.stringify(e3)}[依赖n,有条件返回不同值,async+await]
<br />
e4: {JSON.stringify(e4)}[依赖n,存在一个默认值,至少三种变化]
<br />
</div>
<button onClick={() => setS(s + 1)}>
组件自身渲染, 昂贵值不变。s:{s}
</button>
<br />
<button onClick={() => setN(n + 1)}>
n变化, n同时是依赖项(n做参数) n:{n}
</button>
</div>
);
}
useAsyncMemo()更好的例子
这个例子比较具有实际意义,参考:
import { useAsyncMemo } from "use-async-memo";
function MyComponent({ uid }) {
const info = useAsyncMemo(async () => {
const response = await fetch(`http://localhost:3222/get/${uid}`);
return response.json();
}, [uid]);// 这个会引发再次渲染的
if (info === undefined) {
return <div>Loading...</div>;
}
return (
<div>
<p>{info?.email || '--'}</p>
</div>
);
}
export default function App() {
return (
<>
<MyComponent uid={123} />
<MyComponent uid={456} />
</>
);
}
这段代码的理解上,就是useAsyncMemo()
会有个状态的变化过程,会再次引起组件渲染。
对应的接口服务:
const express = require("express");
const app = express();
var cors = require("cors");
app.use(cors());
app.get("/get/:uid", (req, res) => {
// req.params.id <=> req.query.id
let ret = {
email: "sunan" + req.params.uid + "@pku.edu.cn",
time: Date.now().toString().slice(-6),
};
res.send(JSON.stringify(ret));
});
const server = app.listen(process.env.PORT || 3222, () => {
const port = server.address().port;
console.log("http://localhost:%d", port);
});
相关文章
- https://newsn.net/say/js-promise.html
- https://newsn.net/say/js-request-get.html
- https://newsn.net/say/js-request-fake.html
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。