[译]SWR 文档 – 远程数据请求的React Hooks封装

GitHub: https://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总是可以快速展示并且保持响应。

快速开始

1
2
3
4
5
6
7
8
9
10
11
12
13
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可以是任何异步函数,因此你可以使用你喜欢的数据请求库来处理这个部分。

查看更多SWR例子
最佳实践的例子

开始使用

进入你的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刷新一次数据:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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。你可以使用任何库处理数据请求,例如:

1
2
3
4
5
6
7
8
9
10
import fetch from 'unfetch'

const fetcher = url => fetch(url).then(r => r.json())

function App() {

const { data } = useSWR('/api/data', fetcher)

// ...
}

或者使用GraphQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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会中止请求。

1
2
3
4
5
6
7
8
// 条件请求
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允许你的远程请求数据依赖其他数据。它可以最大限度的保持平行(避免瀑布法开发),同时保证当下一次数据请求要求动态数据时进行连续请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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接收的参数组成:

1
2
3
const { data: user } = useSWR(['/api/user', token], fetchWithToken)

const { data: orders } = useSWR(user ? ['/api/orders', user] : null, fetchWithUser)

请求的key值现在与所有值的变化都关联了。在每次渲染时,SWR对参数进行浅对比,如果有任何更改,就会触发revalidation。

请记住,在渲染时,你不应该重新创建对象,不然每次渲染都会被当做不同的对象:

1
2
3
4
5
// 不要这样写,每次渲染都会当做改变
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:内部)的实现.

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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,你可以先更新你的本地数据,等请求结束再用最新的数据替换。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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”:

1
2
3
4
5
6
// false,可以禁止revalidation

mutate('/api/user', newUser, false)
// updateUser是一个请求的Promise,返回更新后的文档

mutate('/api/user', updateUser(newUser))

基于当前数据的修改(Mutate Based on Current Data)

在很多场景中,API只返回单条数据,你需要把这条数据追加到列表中。

使用mutate,你可以传入一个异步函数,这个函数会接收当前的缓存值,你可以返回一个更新后的文档。

1
2
3
4
5
6
7
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时, 你需要使用返回值更新缓存。

每次调用下面的函数时,会返回更新后的文档,或者抛出错误。

1
2
3
4
5
6
7
try {

const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {

// 处理更新user时抛出的错误
}

有限制的mutate(Bound Mutate())

useSWR返回的SWR对象中也包含mutate函数,这个函数预先限制key为传入的key。

功能与全局mutate一样,但是不需要传入key参数:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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选项:

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
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重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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”:

这种方式在js开始下载之前开始预加载数据。因此后面的请求可以重用这个结果(当然也包括SWR)。

另一个选择是有条件的预加载。你可以写一个函数重新请求并且设置缓存:

1
2
3
4
5
6
function prefetch() {

mutate('/api/data', fetch('/api/data').then(res => res.json()))
// 第二个参数是Promise
// 当Promise resolve时,SWR将会使用这个结果
}

当你需要重新加载资源时也可以使用他们(例如hovering a link)。
和Next.js里的页面预加载(page preload)一起使用,就可以立即加载下一页和数据。