或许,你根本不需要全局状态管理
作者:xinglee
原文:https://juejin.cn/post/7223670680826888249
在 hooks 时代,除了富文本编辑器等前端为主导的应用以外,绝大多数的前端应用的客户端状态并不复杂,需要管理的是大多数服务端状态,所以你的应用可能完全不需要全局状态管理。context 加上一个好用的请求库 ( swr or react-query )完全可以解决你的状态管理问题。所以用好请求库(这里推荐使用 swr
),就可以完全不用 redux
之类的全局状态管理工具了。
用法
上面例子中,我们在 Avatar 以及 Content 中都需要用户的信息,而用户的信息一般都是通过后台接口获取。如果我们用 swr 请求
import useSWR from 'swr'
import { Skeleton, Avatar } from "antd"
// 这里的 fetcher 可以替换成自己喜欢的请求库(比如 axios),错误处理也可以在这里统一处理,下面例子中不做处理
const fetcher = (url) => fetch(url).then((res) => res.json())
function useUser(id) {
const { data, isLoading } = useSWR(`/api/user/userInfo?id=${id}`, fetcher)
return { user: data, isLoading }
}
function Avatar(){
const { user , isLoading } = useUser()
if (isLoading) {
// 有木有觉得 loading 很方便
return <Skeleton.Avatar active/>
}
return <Avatar src={user.avatar} />
}
function Content(){
const { user , isLoading } = useUser()
if (isLoading) {
return <Skeleton active/>
}
// 直接用就完事了,自动帮你去重
return <div>welcome back, {user.name}</div>
}
这样,所有需要用户信息数据的组件我们直接调用 useUser
这个 hooks
就可以了,所有组件都是相互 独立
的。我们不需要在父组件中传递数据,并且 swr
还会自动请求 去重
和 缓存
,相同的 key
只会有一个请求。
如果资源是不可变的,即使我们再怎么重新请求也永远不会发生任何改变,那么我们可以禁用它的所有的自动重新请求。
typescript
复制代码
import useSWRImmutable from 'swr/immutable'
// ...
useSWRImmutable(key, fetcher, options)
它具有与普通 useSWR hook 相同的 API 接口。你还可以通过禁用以下重新请求选项来执行相同的操作
useSWR(key, fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
})
// 相当于
useSWRImmutable(key, fetcher)
实践
post 请求如何使用 useSWR
有些表格筛选条件比较多,参数比较复杂。这时候后台给你的接口是 post
接口,而你又想在参数改变时自动请求,这时候我们这样写
const fetcher = ([url, params]) => fetch(url, {
method: 'POST',
body: JSON.stringify(params),
headers: {
'Content-Type': 'application/json'
}
}).then((res) => res.json())
function useList(params) {
const { data, isLoading } = useSWR(['/api/list/listInfo', params], fetcher)
return { List: data, isLoading }
}
从 SWR 1.1.0 开始,object 类型的 keys 可以在内部自动被序列化。
在旧版本(< 1.1.0)中,SWR 会浅比较每次渲染时的参数,如果其中任何一个发生了变化,就会触发重新验证。
所以我们可以把所有影响表格数据的参数都放在一个对象里,这样就可以直接改变对象的值来触发重新请求了。
function List(){
const [params, setParams] = useState({page: 1, pageSize: 10, filter:...})
const { List , isLoading } = useList(params)
const handleSearch = (values) => {
setParams(values)
}
return <div>
<SearchForm onSearch={handleSearch} />
<Table dataSource={List} loading={isLoading}/>
</div>
}
如何首次加载不请求数据
有时候我们需要首次加载不请求数据,只有用户点击按钮或者某个条件满足时才请求数据。由于 hooks 的限制,我们不能直接使用判断语句来控制请求。有两种解决方案
设置 key
为null
,swr
就不会请求数据了
function useList(params) {
const { data, isLoading } = useSWR(params ? ['/api/list/listInfo', params] : null, fetcher)
return { List: data, isLoading }
}
使用 useSWRMutation
,这个hooks
只能手动触发,而不像 useSWR 那样会自动触发。
import useSWRMutation from 'swr/mutation'
// 实现 fetcher
// 额外的参数可以通过第二个参数 `arg` 传入
// 在下例中,`arg` 为 `'my_token'`
async function updateUser(url, { arg }: { arg: string }) {
await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${arg}`
}
})
}
function Profile() {
// 一个类似 useSWR + mutate 的 API,但是它不会自动发送请求
const { trigger } = useSWRMutation('/api/user', updateUser, options?)
return <button onClick={() => {
// 以特定参数触发 `updateUser` 函数
trigger('my_token')
}}>Update User</button>
}
注意这个 hook 不会与其他 useSWRMutation
hook 共享状态。
结尾
swr
还有很多其他的好处,比如 自动重试
,自动重连
等等,还有用于订阅实时数据 ( webSocket ) 的 useSWRSubscription
,用于无限加载的 useSWRInfinite
。这里就不一一介绍了。如果你想了解更多,可以看看 swr 官方文档[1]
参考资料
https://swr.vercel.app/: https://link.juejin.cn?target=https%3A%2F%2Fswr.vercel.app%2F
最后
如果你觉得这篇内容对你挺有启发,我想邀请你帮我个小忙:
点个「喜欢」或「在看」,让更多的人也能看到这篇内容
我组建了个氛围非常好的前端群,里面有很多前端小伙伴,欢迎加我微信「sherlocked_93」拉你加群,一起交流和学习
关注公众号「前端下午茶」,持续为你推送精选好文,也可以加我为好友,随时聊骚。