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

6,700 阅读10分钟

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可以是任何异步函数,因此你可以使用你喜欢的数据请求库来处理这个部分。

查看更多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刷新一次数据:

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)一起使用,就可以立即加载下一页和数据。