我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

上一篇文章里面,苏南大叔说可以使用useMemo()缓存普通函数的昂贵计算逻辑结果。在文章的最后,特别强调了useMemo()作用于普通函数,而不能作用于异步函数。异步函数的关键词有:asyncawaitpromiseresolvereject.then()等等。本文将使用react的另外一个非官方钩子useAsyncMemo(),实现异步函数结果的缓存。

苏南大叔:React缓存,如何使用useAsyncMemo()缓存异步函数结果? - useasyncmemo缓存异步函数结果
React缓存,如何使用useAsyncMemo()缓存异步函数结果?(图6-1)

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0create-react-app@5.0.1react-router-dom@6.27.0react@18.3.1use-async-memo@1.2.5useAsyncMemo()是个钩子,所以只能用在函数式组件里面。

接口代码

异步函数大部分情况下是成功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>
  );
}

苏南大叔:React缓存,如何使用useAsyncMemo()缓存异步函数结果? - effect
React缓存,如何使用useAsyncMemo()缓存异步函数结果?(图6-2)

这里为了避免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>
  );
}

苏南大叔:React缓存,如何使用useAsyncMemo()缓存异步函数结果? - 死循环
React缓存,如何使用useAsyncMemo()缓存异步函数结果?(图6-3)

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>
  );
}

苏南大叔:React缓存,如何使用useAsyncMemo()缓存异步函数结果? - 基本写法
React缓存,如何使用useAsyncMemo()缓存异步函数结果?(图6-4)

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>
  );
}

苏南大叔:React缓存,如何使用useAsyncMemo()缓存异步函数结果? - 默认值
React缓存,如何使用useAsyncMemo()缓存异步函数结果?(图6-5)

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} />
    </>
  );
}

苏南大叔:React缓存,如何使用useAsyncMemo()缓存异步函数结果? - 非常不错的例子
React缓存,如何使用useAsyncMemo()缓存异步函数结果?(图6-6)

这段代码的理解上,就是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);
});

相关文章

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   react