我们平时在 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 个参数:
- key:请求的 key,可以是字符串、数组、object 等格式;该 key 用于标记当前次的请求,而且 key 会作为第二个请求函数的参数进行传入;
- 请求函数 fetcher,不限制任何请求库,可以是 xhr、fetch、axios 等;该请求函数的参数,就是第一个参数 key,按需使用即可;该函数返回 Promise;
- 配置 config,该参数可选,可以按照使用场景配置即可:选项;
swr
共返回了 5 个字段:
- data :请求成功返回的数据;如未完全加载,返回 undefined;
- error :
fetcher
抛出的错误(或者是 undefined); - isValidating :请求是否正在进行中;
- mutate :请求成功后,更新数据;在保持请求参数不变的前提更新数据
- isLoading :是否有一个正在进行中的请求且当前没有“已加载的数据“;
mutate()
触发的重新请求不会导致该数据的变化;
2. key 的使用 #
swr 的第一个参数 key 在数据请求中,有着多种作用:
- 可以是字符串、数组、object 等格式;key 会作为第二个回调函数的参数进行传入;
- 内部会以此来缓存数据;短时间内多次执行相同 key 的请求时,只会触发一次网络请求;
- key 变动时会自动触发请求;
- 可以通过
mutate(key)
全局触发其他模块 key 对应的请求; - 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 的方式,选择不同的引入方式:
- 更新当前 key 的数据,直接使用
useSWR()
的返回值; - 更新其他 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
参数。
- 若只想触发请求,可以直接调用,不用传入任何参数;
- 若想立刻更新本地数据并触发请求,可以将最新的数据给到
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;