Wenzi

React 请求数据别再使用 useEffect 和 useState,试试 SWR 吧!

蚊子前端博客
发布于 2024/10/09 23:39
仅需一行代码,就可以简化项目中数据请求的逻辑

我们平时在 React 中请求数据时,很多场景都会这么写:

const App = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const request = async () => {
    setLoading(true);
    try {
      const response = await fetch("/api");
      const json = await response.json();
      setData(json);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    request();
  }, []);
};

多个页面中都有类似的场景时,每次都要写多个 useState()、useEffect(),设置 loading 等。这些复用的功能可以抽离出一个数据请求的 hook。

swr 则进行了很好地封装,帮助我们减少代码的重复。同时,还有更多灵活的配置。接下来我们来稍微了解下 swr 的使用。

嘻嘻嘻

SWR 由 Next.js(React 框架)背后的同一团队创建,质量上肯定是有保障的。

1. 简单使用 #

swr 的使用原型:

// 原型
const { data, error, isValidating, mutate } = useSWR<T>(key, fetcher, options);

swr 还支持 typescript 的泛型,可以设置返回的数据格式。

再看下他的实际使用:

import useSWR from "swr";

const App = () => {
  const { data, error, isValidating, mutate } = useSWR<Item[]>(
    "/api",
    async () => {
      const response = await fetch("/api");
      const { code, data, msg } = await response.json();
      if (code == 200) {
        return data;
      }
      message.error(msg || "获取数据失败");
      return [];
    }
  );
  return (
    <Spin spinning={isValidating}>
      <div>{data}</div>
    </Spin>
  );
};

组件库 swr 接收 3 个参数:

  1. key:请求的 key,可以是字符串、数组、object 等格式;该 key 用于标记当前次的请求,而且 key 会作为第二个请求函数的参数进行传入;
  2. 请求函数 fetcher,不限制任何请求库,可以是 xhr、fetch、axios 等;该请求函数的参数,就是第一个参数 key,按需使用即可;该函数返回 Promise;
  3. 配置 config,该参数可选,可以按照使用场景配置即可:选项

swr共返回了 5 个字段:

  1. data :请求成功返回的数据;如未完全加载,返回 undefined;
  2. error : fetcher 抛出的错误(或者是 undefined);
  3. isValidating :请求是否正在进行中;
  4. mutate :请求成功后,更新数据;在保持请求参数不变的前提更新数据
  5. isLoading :是否有一个正在进行中的请求且当前没有“已加载的数据“;mutate()触发的重新请求不会导致该数据的变化;

2. key 的使用 #

swr 的第一个参数 key 在数据请求中,有着多种作用:

  1. 可以是字符串、数组、object 等格式;key 会作为第二个回调函数的参数进行传入;
  2. 内部会以此来缓存数据;短时间内多次执行相同 key 的请求时,只会触发一次网络请求;
  3. key 变动时会自动触发请求;
  4. 可以通过mutate(key)全局触发其他模块 key 对应的请求;
  5. key 为null不触发请求,因此可以通过控制 key 是否为 null 来手动触发请求;

我们设置一个场景:App.js 展示列表的数据,在弹窗 Modal.js 中可以新增和编辑数据,弹窗中完成后,需要重新更新列表的数据:

// 在 App.js 中
import useSWR from "swr";

const App = () => {
  const [pageInfo, setPageInfo] = useState({ page: 1, pageSize: 10 });

  const { data } = useSWR({ key: "list", pageInfo }, async ({ pageInfo }) => {
    // key 中的 pageInfo 字段,可以传入到当前的请求函数中
    const response = await fetch(
      `/api/list?page=${pageInfo.page}&pageSize=${pageInfo.pageSize}`
    );
    return response.json();
  });
};

在弹窗中:

// 在 Modal.js 中
import useSWR, { mutate } from "swr";

const Task = ({ open, id }) => {
  // 只有在打开弹窗时,并且是编辑时,才触发请求
  const { data } = useSWR(open && id ? `data-info-${id}` : null, () => {
    const response = await fetch(
      `/api/info?id=${id}`
    );
    return response.json();
  });

  // 新增或编辑完成,需更新列表
  const handleOk = () => {
    // 若是需要触发其他key对应的请求,则需要从全局引入mutate,
    // 若要更新的key不是字符串,需要通过过滤函数来过滤出对应的key
    mutate(item => item.key === 'list');
  }

  return <Modal onOk={handleOk}></Modal>
};

我们可以通过设置 key 的类型,来更方便控制我们的请求。

听我的,准没错

3. 数据请求 #

useSWR()的第 2 个参数,是请求函数体。这里不限制任何请求库,可以是 xhr、fetch、axios 等。也支持async-await

这个函数体不需要使用try-catch来包裹,若网络请求或者函数体产生了异常或错误,会自动在返回的error字段有体现。因此,我们可以判断返回的 error 是否有数据,来告诉用户出错了。

import useSWR from "swr";

type UserItemData = {
  id: number;
  name: string;
  age: number;
};

const App = () => {
  const { data, isValidating, error } = useSWR<UserItemData[]>(
    "user-list",
    async () => {
      const response = await fetch(`/api/list`);
      const { code, data } = response.json();

      if (code === 0) {
        return data.list.map((user) => ({
          id: user.id,
          name: user.name,
          age: user.age,
        }));
      }
    }
  );

  if (error) {
    return <div>出错啦</div>;
  }
  return (
    <Spin spinning={isValidating}>
      {Array.isArray(data) && data.length ? (
        data.map((user) => <div key={user.id}>{user.name}</div>)
      ) : (
        <div>暂无数据</div>
      )}
    </Spin>
  );
};

在上面的例子中,若网络出错,或者假如 data.list 不存在导致语法错误,就会进入到error的逻辑。

4. 数据更新 #

我们可以通过mutate()来更新数据。

mutate 有两种引入方式,根据更新 key 的方式,选择不同的引入方式:

  1. 更新当前 key 的数据,直接使用useSWR()的返回值;
  2. 更新其他 key 的数据;从全局的swr中引入;

我们一一来看下。

4.1 更新当前 key 的数据 #

绑定数据更改可以更便捷的更改当前 key 数据,它的 key 与传递给 useSWR 的 key 相绑定,并接收 data 作为第一个参数。

import useSWR from "swr";

const App = () => {
  const { data, mutate } = useSWR("/api/user", fetcher);

  return (
    <div>
      <button onClick={() => mutate()}>只触发请求</button>
      <button onClick={() => mutate({ ...data, now: Date.now() })}>
        更新本地数据同时触发请求
      </button>
    </div>
  );
};

使用从 useSWR() 中得到的 mutate() ,不需要再传入key参数。

  1. 若只想触发请求,可以直接调用,不用传入任何参数;
  2. 若想立刻更新本地数据并触发请求,可以将最新的数据给到mutate(data)

使用这种方式更新数据,相比之前使用 useEffect()useState() 的组合请求数据。swr能保留最后一次请求的参数,无须再重新拼接。

睡觉

4.2 全局更新数据 #

若需要更新其他组件里的请求,则需要从全局引入,这里有两种引入方式。

import { useSWRConfig } from "swr";

const App = () => {
  const { mutate } = useSWRConfig();
  mutate(key, data, options);
};

也可以全局引入它:

import { mutate } from "swr";

const App = () => {
  mutate(key, data, options);
};

若 key 是字符串类型的,可以直接触发:mutate("user-list")。若是其他类型,则需要以过滤函数的方式,找到对应的 key:

mutate((item) => item.key === "user-list");

// ✅ 匹配的数组 key 值
mutate((key) => key[0].startsWith("/api"), data);
// ✅ 匹配字符串 key 值
mutate((key) => typeof key === "string" && key.startsWith("/api"), data);

// ❌ ERROR: 更改不确定类型的 key 的数据 (array 或 string)
mutate((key: any) => /\/api/.test(key.toString()));

5. 配置 #

第 3 个参数,可以配置一些参数,这里挑一个常用的说几个:

  • revalidateOnFocus = true: 窗口聚焦时自动重新验证;若数据更新不频繁,可以设置为 false;
  • dedupingInterval = 2000: 删除一段时间内相同 key 的重复请求(以毫秒为单位);
  • errorRetryInterval = 5000: 错误重试的时间间隔(以毫秒为单位);
  • errorRetryCount: 错误重试的最大次数;

更多的配置,可以查看SWR 的 API

标签:reacthooks
阅读(219)
Simple Empty
No data