GitHub: github.com/zeit/swr
官网: SWR: React Hooks for Remote Data Fetching
介绍
SWR是提供远程数据请求的React Hooks库。
SWR是由stable-while-revalidate的首字母组成,是由HTTP RFC 5861普及的缓存失效策略。SWR优先返回缓存数据(stable),然后再发送远程请求(revalidate),最后更新最新的数据。
特性:
- 数据请求与传输协议无关
- 快速页面导航
- 聚焦时自动请求数据
- 轮询
- 请求去重
- 本地数据变化
- 分页
- 支持TS
- 支持SSR
- 支持React Suspense
- 支持React Native
- 少量API 等等等......
使用SWR,组件可以持续自动更新数据。因此UI总是可以快速展示并且保持响应。
快速开始
import useSWR from 'swr'
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>
}
在这个例子中,useSWR接收key和fetcher为参数。key是这个请求的唯一标识,通常是这个API的URL。fetcher函数接收key为参数,并异步返回数据。
useSWR返回2个值: data 和 error。当请求在进行中时,data的值为undefined。当请求结束,并返回response时,将基于fetcher的返回值设置data和error,并重新渲染组件。
注意,fetcher可以是任何异步函数,因此你可以使用你喜欢的数据请求库来处理这个部分。
开始使用
进入你的React项目,执行如下命令
yarn add swr
或者使用npm
npm install swr
API
const { data, error, isValidating, mutate } = useSWR(key, fetcher, options)
参数
- key:string | function | array | null,请求的唯一标识高级用法
- fetcher:可选参数,返回Promise的函数,用于请求远程数据细节
- options:object, 可选参数,SWR hook接收的选项对象
返回值
- data:fetcher函数返回的Promise,resolve时的数据(请求未返回时为undefined)
- error:fetcher函数抛出的错误(或者为undefined)
- isValidating:true为loading状态(请求进行中或者数据重新有效中)
- mutate: (data?: any, shouldRevalidate?: any) => void,用于改变缓存数据的函数
选项
- suspense = false:是否启用React Suspense模式细节
- fetcher = undefined:默认的fetcher函数
- initialData:返回的默认数据(注意:这个是预设钩子)
- revalidateOnFocus = true:聚焦窗口时,自动获取数据
- revalidateOnReconnect = true:浏览器重新联网时自动获取数据(通过:navigator.onLine)
- refreshInterval = 0:轮询间隔时间(默认不启用轮询)
- refreshWhenHidden = false:当窗口不可见时继续轮询(当refreshInterval启用时)
- refreshWhenOffline = true:浏览器离线时继续轮询(由navigator.onLine决定)
- shouldRetryOnError = true:fetcher发生错误时是否重试细节
- dedupingInterval = 2000:防抖,频发出发revalidate,会进行防抖策略处理
- focusThrottleInterval = 5000:在这个时间间隔内,revalidate只执行一次
- loadingTimeout = 3000:触发onLoadingSlow事件的超时时间,即请求超过规定时间未结束,则触发onLoadingSlow回调
- errorRetryInterval = 5000:错误重试的时间间隔
- errorRetryCount:错误重试的最大次数
- onLoadingSlow(key, config):请求时间过长,会触发的回调函数(参见loadingTimeout)
- onSuccess(data, key, config):请求成功时的回调函数
- onError(err, key, config):请求返回error时的回调函数
- onErrorRetry(err, key, config, revalidate, revalidateOps):error重试的处理函数细节
- compare(a,b):用来对比返回数据是否真的发生改变的函数,避免欺骗性重新渲染。默认使用fast-deep-equal
慢网时(2G,<=70kbps),errorRetryInterval默认为10s,loadingTimeout默认为5s
可以通过全局配置来提供默认选项值。
例子
全局配置(Global Configuration)
SWRConfig可以提供全局默认配置。
在下面的例子中,所有的swrs将使用相同的fetcher来请求JSON数据,并每隔3s刷新一次数据:
import useSWR, { SWRConfig } from 'swr'
function Dashboard () {
const { data: events } = useSWR('/api/events')
const { data: projects } = useSWR('/api/projects')
const { data: user } = useSWR('/api/user', { refreshInterval: 0 }) // 禁止刷新
}
function App() {
return (
<SWRConfig
value = {{
refreshInterval: 3000,
fetcher: (...args) => fetch(...args).then(res => res.json())
}}
>
<Dashboard />
</SWRConfig>
)
}
数据请求(Data Fetching)
Fetcher是一个接收key为参数的函数,并返回一个值或者Promise。你可以使用任何库处理数据请求,例如:
import fetch from 'unfetch'
const fetcher = url => fetch(url).then(r => r.json())
function App() {
const { data } = useSWR('/api/data', fetcher)
// ...
}
或者使用GraphQL:
import { request } from 'graphql-request'
const API = 'https://api.graph.cool/simple/v1/movies'
const fetcher = query => request(API, query)
function App () {
const { data, error } = useSWR(
`{
Movie(title: "Inception") {
releaseDate
actors {
name
}
}
}`,
fetcher
)
// ...
}
如果你想传递变量给GraphQL query,查看这里
注意,如果提供了全局的fetcher,则useSWR中可以省略fetcher参数
有条件的请求(Conditional Fetching)
给useSWR传递null或者一个函数作为key,可以根据条件发起数据请求。如果函数抛出错误或者返回false,SWR会中止请求。
// 条件请求
const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)
// 返回false
const { data } = useSWR(() => shouldFetch ? '/api/data' : null, fethcer)
// 抛出错误
const { data } = useSWR(() => '/api/data?uid=' + user.id, fetcher)
依赖请求(Dependent Fetching)
SWR允许你的远程请求数据依赖其他数据。它可以最大限度的保持平行(避免瀑布法开发),同时保证当下一次数据请求要求动态数据时进行连续请求。
function MyProjects() {
const { data: user } = useSWR('/api/user')
const { data: projects } = useSWR(() => '/api/projects?uid=' + user.id)
/*
传递给swr的key为函数时,取此函数的返回值为key的值。如果这个函数抛出错误,swr就知道某些依赖没有准备好。在此例子当中是user。
*/
if (!projects) return 'loading'
return 'You have ' + projects.length + 'projects'
}
多个参数(Multiple Arguments)
在某些场景中,传递多个参数(可以是任何值或者对象)给fetcher函数是很有用的。例如:
useSWR('/api/user', url => fetchWithToken(url, token))
这是不正确的。因为数据的唯一标识(缓存的index)是”/api/data”,因此即使token发生变化,SWR依然会使用相同的key,从而返回错误的数据。
你应该使用数组作为key参数,这个数组是由fetcher接收的参数组成:
const { data: user } = useSWR(['/api/user', token], fetchWithToken)
const { data: orders } = useSWR(user ? ['/api/orders', user] : null, fetchWithUser)
请求的key值现在与所有值的变化都关联了。在每次渲染时,SWR对参数进行浅对比,如果有任何更改,就会触发revalidation。
请记住,在渲染时,你不应该重新创建对象,不然每次渲染都会被当做不同的对象:
// 不要这样写,每次渲染都会当做改变
useSWR(['/api/user', { id }], query)
// 应该传入稳定的值
useSWR(['/api/user', id], (url, id) => query(user, { id }))
Dan Abramov在这里解释的很好。
手动触发(Manually Revalidate)
调用mutate(key),可以向所有使用相同key的SWR广播一条revalidation信息。
这个例子展示了,当用户点击“退出”按钮时,自动重新获取登录信息(e.g:内部)的实现.
import useSWR, { mutate } from 'swr'
function App() {
return (
<div>
<Profile />
<button
onClick = (() => {
// 设置cookie过期
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
// 发布广播,所有使用相同key值的SWR重新获取数据
mutate('/api/user');
})
>
Logout
</button>
</div>
)
}
修改和发送请求(Mutation and Post Request)
在很多案例中,先修改本地数据这种方式,可以在感觉上提高响应速度——不需要等待远程数据。
使用mutate,你可以先更新你的本地数据,等请求结束再用最新的数据替换。
import useSWR, { mutate } from 'swr'
function Profile() {
const { data } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button
onClick={ async () => {
const newName = data.name.toUpperCase()
// 发送请求,更新数据
await requestUpdateUsername(newName)
// 立即更新本地数据,并重新请求数据
// 注意:传入mutate的key,没有限制
mutate('/api/user', { ...data, name: newName })
}}
>
Uppercase my name!
</button>
</div>
)
}
点击上例中的按钮,会发送一个POST请求修改远程数据,本地更新客户端数据,并尝试请求最新的数据。
但是很多POST APIs只是直接返回更新的数据,因此我们没有必要再次revalidate。 下面的例子展示了如何使用”local mutate - request - update”:
// false,可以禁止revalidation
mutate('/api/user', newUser, false)
// updateUser是一个请求的Promise,返回更新后的文档
mutate('/api/user', updateUser(newUser))
基于当前数据的修改(Mutate Based on Current Data)
在很多场景中,API只返回单条数据,你需要把这条数据追加到列表中。
使用mutate,你可以传入一个异步函数,这个函数会接收当前的缓存值,你可以返回一个更新后的文档。
mutate('/api/users', async users => {
const user = await fetcher('/api/users/1');
return [user, ...users.slice(1)]
})
从Mutate中返回数据(Returned Data from Mutate)
也许,当你传入一个promise或者一个async函数给mutate时, 你需要使用返回值更新缓存。
每次调用下面的函数时,会返回更新后的文档,或者抛出错误。
try {
const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {
// 处理更新user时抛出的错误
}
有限制的mutate(Bound Mutate())
useSWR返回的SWR对象中也包含mutate函数,这个函数预先限制key为传入的key。
功能与全局mutate一样,但是不需要传入key参数:
import useSWR from 'swr'
function Profile() {
const { data, mutate } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={ async () => {
const newName = data.name.toUpperCase()
await requestUpdateUsername(newName)
mutate({ ...data, name: newName })
}}>
UpperCase my name!
</button>
</div>
)
}
使用Next.js实现SSR(SSR with Next.js)
使用initialData选项,你可以传入一个初始值给hook。它在很多SSR解决方案中都工作的很好,例如Next.js中的getServerSideProps:
export asyn function getServerSideProps() {
const data = await fetcher('/api/data')
return { props: { data } }
}
function App(props) {
const initialData = props.data
const { data } = useSWR('/api/data', fetcher, { initialData })
return <div>{data}</div>
}
上例保持SSR的同时,在客户端也可以很好的应用SWR。这意味着数据可以是动态的,并且随着时间和用户交互不断更新。
Suspense模式( Suspense Mode)
在应用React Suspense时,可以开启suspense选项:
import { Suspense } from 'react'
import useSWR from 'ssr'
function Profile() {
const { data } = useSWR('/api/user', fetcher, { suspense: true })
return <div>hello, { data.name }</div>
}
function App() {
return (
<Suspense fallback = {<div>loading...</div>}>
<Profile />
</Suspense>
)
}
在Suspense模式下,data使用时请求响应的数据(因此你不需要检查数据是否为undefined)。但是如果发生错误,你需要使用error Boundary去捕获错误。
/注意,ssr模式下不支持Suspense/
错误重试(Error Retires)
SWR默认采用 exponential backoff algorithm(指数退避算法)处理错误重试。你可以通过阅读源码了解更多。 也可以通过onErrorRetry重写:
useSWR(key, fetcher, {
onErrorRetry: (error, key, option, revalidation, { retryCount }) => {
if (retryCount >= 10) return
if (error.status === 404) return
// 5s后重试
setTimeout(() => revalidation({ retryCount: retryCount + 1}), 5000)
}
})
预加载数据(Prefetching Data)
在SWR中有很多方式可以实现预加载。对于顶层请求,高度推荐rel = “preload”:
<link rel="preload" href="/api/data" as="fetch" crossorigin="anonymous" />
这种方式在js开始下载之前开始预加载数据。因此后面的请求可以重用这个结果(当然也包括SWR)。
另一个选择是有条件的预加载。你可以写一个函数重新请求并且设置缓存:
function prefetch() {
mutate('/api/data', fetch('/api/data').then(res => res.json()))
// 第二个参数是Promise
// 当Promise resolve时,SWR将会使用这个结果
}
当你需要重新加载资源时也可以使用他们(例如hovering a link)。 和Next.js里的页面预加载(page preload)一起使用,就可以立即加载下一页和数据。