【山竹记账前端-react】8.数据请求与缓存失效之 SWR
大纲链接 §
[toc]
0.实现首页 ⇧
实现首页,需要分以下几种情况
- 如果没有登录:可选显示首页,重定向到登录
- 如果登录但没有记账:展示首页
- 如果登录且记过账:记账列表页面
如何知道用户是否登录
- 用 JS 读取
cookie/jwt❌️ - 用 JS 发请求问服务器用户是否登录 ✅️
首页发送验证登录请求,展示分三种情况
- 例如
axios.get('/api/currentUser')- 加载中
return <div>loading</div> - 加载出错
return <div>error</div> - 加载成功内容
return <div>Home</div>
- 加载中
可自行封装通用请求逻辑,示例伪代码
const {loading, error, data} = useAjax('get', '/api/v1/curentUser')
使用成熟的请求库
swr.js
1. swr原理介绍 ⇧
用于数据请求的 React Hooks 库
stale-while-revalidate在重新验证期间过期- 一种由HTTP RFC5861推广的HTTP缓存失效策略
- 这种策略首先从缓存中返回数据(过期的),同时发送fetch请求(重新验证),最后得到最新数据。
- 使用SWR,组件将会不断地、自动获得最新数据流。 UI 也会一直保持快速响应。
缓存失效问题:
- 用户页面一直开着,正好超过了缓存有效时间,导致缓存失效不更新
- 用户的另一台设备更新了数据,原来设备的缓存即失效
- 前端数据只是后端服务器的数据库的缓存
- 前端数据某一时刻一定会失效
- 前端状态管理的本质:缓存管理,即存在缓存失效的问题
需要正确稍慢的结果
- 而不是过期有可能错误但稍快的结果
自动更新的时机
- 与用户交互部分的数据才会去更新
- 用户请求路由跳转
- 用户请求分页
数据获取流派
简单示例
|
|
me是数据唯一标识符,通常是 API URL- 回调是返回数据的异步函数,可以使用
Axios或原生fetch - 负责当
me过期时重新发送请求 - 返回:data 和 error
支持重复请求去除
- 浏览器默认去重:请求路径一直,浏览器取消上一次请求
swr支持一定时间内,合并重复请求- 使用
Axios作为fetcher
2. swr基本使用 ⇧
安装
|
|
在
@/pages/Home.tsx中使用
|
|
使用
https://apifox.com/apihub/中的https://echo.apifox.cn/HTTP 类接口,获取免费的测试接口数据
测试错误
https://echo.apifox.com/status/4041 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21import axios from 'axios' import useSWR from 'swr' function fetcher(urlPath: string) { return axios.get<string>(urlPath) } export const Home: React.FC = () => { const { data, error } = useSWR(`https://echo.apifox.com/status/404`, fetcher) if (error) { return <div>Error, failed to load</div> } if (!data) { return <div>Loading...</div> } window.console.log('useSWR_______________________') window.console.log('error', error) window.console.log('data?.data', data?.data) window.console.log('_______________________useSWR') return ( <div text-6xl>{data?.data}</div> ) }测试成功
https://echo.apifox.com/get?name=frank&age=181 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21import axios from 'axios' import useSWR from 'swr' function fetcher(urlPath: string) { return axios.get<{ args: { name: string, age: number } }>(urlPath) } export const Home: React.FC = () => { const { data, error } = useSWR(`https://echo.apifox.com/get?name=frank&age=18`, fetcher) if (error) { return <div text-6xl>Error, failed to load</div> } if (!data) { return <div text-6xl>Loading...</div> } window.console.log('useSWR_______________________') window.console.log('error', error) window.console.log('data?.data', data?.data) window.console.log('_______________________useSWR') return ( <div text-6xl>{data?.data.args.name + data?.data.args.age}</div> ) }调慢网速
slow 4G可以看到 loading 状态;可以删除整个元素,然后刷新页面查看响应数据类型为
{ args: { name: string, age: number } },在axios.get<>定义
swr@1.x+ 不提供 isLoading 的原因 ⇧
- Best practice for isLoading. What different with isValidating
swr认为只有第一次加载数据中才是loading,之后的请求都只是验证刷新数据,即isValidating- 即
loading只发生一次,当没有数据且没有错误的时候,loading = !error && !data isValidating发生一次以上,当已经有了数据或者错误时,swr会重新调用fetcher去更新1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17import axios from 'axios' import useSWR from 'swr' function fetcher(urlPath: string) { return axios.get<{ args: { name: string, age: number } }>(urlPath) } export const Home: React.FC = () => { const { data, error, isValidating } = useSWR(`https://echo.apifox.com/get?name=frank&age=18`, fetcher) if (error) { return <div text-6xl>Error, failed to load</div> } if (!data) { return <div text-6xl>Loading...</div> } if (isValidating) { return <div>正在获取最新数据...</div> } return ( <div text-6xl>{data?.data.args.name + data?.data.args.age}</div> ) }初学者学习的方法:将问题使用英文描述,发到github的Discussions
点击
useSWR跳转查看对应的类型声明SWRResponse
- 查看ts类型
IsLoadingResponse
swr@2.x +在 SWR 2.0 的更新中,官方引入了一个全新的返回值 isLoading,用以解决之前开发者在区分“初次加载”和“后台重新验证”时遇到的痛点
- 用于第一次展示骨架屏
- SWR 的核心机制是先渲染本地缓存(Stale),同时在后台发起验证(Revalidate)
- 单纯的 isLoading(传统库):关注的是“网络管道”。只要网络连接在建立、数据在传输,它就是 true。它是面向计算机和网络状态的。
SWR 的 isLoading:关注的是“用户感知”。它回答的是“用户眼前是不是一片空白?”。它是面向信息密度和用户心理的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17import axios from 'axios' import useSWR from 'swr' function fetcher(urlPath: string) { return axios.get<{ args: { name: string, age: number } }>(urlPath) } export const Home: React.FC = () => { const { data, error, isValidating, isLoading } = useSWR(`https://echo.apifox.com/get?name=frank&age=18`, fetcher) if (isLoading) { return <div text-6xl>骨架屏加载中...</div> } if (error) { return <div text-6xl>Error, failed to load</div> } if (!data) { return <div text-6xl>Loading...</div> } if (isValidating) { return <div text-6xl>正在获取最新数据...</div> } return ( <div text-6xl>{data?.data.args.name + data?.data.args.age}</div> ) }
参考
- swr isLoading
- swr useSWR 返回值
- swr isLoading
- swr 对于 isLoading 和 isValidating 上的变化
- 结合 isLoading 和 isValidating 以获得更好的用户体验
POST 不能缓存,swr 不支持 POST ⇧
- 使用
mutate来更新数据变更 const { data, error, isValidating, isLoading, mutate } = useSWR(https://echo.apifox.com/get?name=frank&age=18`, fetcher)`单独另外发送变更数据的
POST请求,然后用mutate来更新变更的数据1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27import axios from 'axios' import useSWR from 'swr' function fetcher(urlPath: string) { return axios.get<{ args: { name: string, age: number } }>(urlPath) } export const Home: React.FC = () => { const { data, error, isValidating, isLoading, mutate } = useSWR(`https://echo.apifox.com/get?name=frank&age=18`, fetcher) if (isLoading) { return <div text-6xl>骨架屏加载中...</div> } if (error) { return <div text-6xl>Error, failed to load</div> } if (!data) { return <div text-6xl>Loading...</div> } if (isValidating) { return <div text-6xl>正在获取最新数据...</div> } const onClick = async () => { // 示例请求伪代码 const data = await axios.post('xxx') mutate(data) } return ( <div text-6xl> <div>{data?.data.args.name + data?.data.args.age}</div> <button onClick={onClick}>更新</button> </div> ) }用另外的方法修改了服务器的数据,
swr需要通过mutate来告知数据已更新,再去更新缓存
参考
3. 制作首页 ⇧
基本结构
@/pages/Home.tsx
|
|
填充样式
@/pages/Home.tsx
- 按钮:Add,引入图标
add.svg- 限制宽高
max-w="80%" max-h="80%" - 微调
translate-y="-3px"
- 限制宽高
按钮:开始记账
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35import pig from '@/assets/icons/pig.svg' import add from '@/assets/icons/add.svg' import { cn } from 'cn' export const Home: React.FC = () => { return ( <main flex flex-col justify-center items-center> <div flex justify-center items-center> <img src={pig} alt="pig" w-128px h-130px mt-20vh mb-20vh block /> </div> <div px-16px self-stretch> <button w="100%" className={cn( 'h-48px bg-[var(--button-primary-color)] b-none rounded-8px', 'text-[var(--button-primary-text-color)]', )}> 开始记账 </button> </div> <button className={cn( 'w-56px h-56px', 'bg-[var(--button-primary-color)] rounded-full', 'text-[var(--button-primary-text-color)] text-6xl', 'p-4px text-center flex place-content-center place-items-center-safe', 'fixed bottom-.5em right-.5em', )}> <img src={add} alt="add" max-w="80%" max-h="80%" translate-y="-3px" /> </button> </main> ) }
插播一个老油条的技巧:适配是设计师的事情,不是程序员的事情
- 不做适配,除非设计师出一版适配的设计稿
- 没有设计稿,前端就先不要写代码,使用简陋的画图工具画一版丑丑的设计稿,直到项目经理/老板点头开始做再开始写代码
- 界面样式刷锅设计师,适配用什么方案好,去找设计师
- 只要设计出几个版本,一定给你实现
参考文章
- 初始代码:https://github.com/FrankFang/VS7EHnuf
- 最终代码(只多了两个提交):https://github.com/FrankFang/PntiAz/commits/master
相关文章
- swr
- vue3 的 生态中是否有类似 React SWR 的 hooks 库?
- 相对应的 vue 生态中 pinia colada 在React 中是否有类似相对应的库?
- 请对比 Pinia Colada、TanStack Query Vue/React、swr/swrv、VueUse useFetch 和 Vue Hooks Plus 或 ahooks useRequest
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名