千家信息网

react.js中swr的原理是什么

发表于:2025-02-12 作者:千家信息网编辑
千家信息网最后更新 2025年02月12日,本篇内容主要讲解"react.js中swr的原理是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"react.js中swr的原理是什么"吧!一、什么是s
千家信息网最后更新 2025年02月12日react.js中swr的原理是什么

本篇内容主要讲解"react.js中swr的原理是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"react.js中swr的原理是什么"吧!

一、什么是swr

useSWR 是 react hooks 中一个比较有意思的组件,既可以作为请求库,也可以作为状态管理的缓存用,SWR 的名字来源于"stale-while-revalidate", 是在HTTP RFC 5861标准中提出的一种缓存更新策略 :

首先从缓存中取数据,然后去真实请求相应的数据,最后将缓存值和最新值做对比,如果缓存值与最新值相同,则不用更新,否则用最新值来更新缓存,同时更新UI展示效果。

useSWR 可以作为请求库来用:

//fetchimport useSWR from 'swr'import fetch from 'unfetch'const fetcher = url => fetch(url).then(r => r.json())function App () {  const { data, error } = useSWR('/api/data', fetcher)  // ...}//axiosconst fetcher = url => axios.get(url).then(res => res.data)function App () {  const { data, error } = useSWR('/api/data', fetcher)  // ...}//graphqlimport { request } from 'graphql-request'const fetcher = query => request('https://api.graph.cool/simple/v1/movies', query)function App () {  const { data, error } = useSWR(    `{      Movie(title: "Inception") {        releaseDate        actors {          name        }      }    }`,    fetcher  )  // ...}

此外,因为相同的 key 总是返回相同的实例,在 useSWR 中只保存了一个 cache 实例,因此 useSWR 也可以当作全局的状态管理机。比如可以全局保存用户名称 :

import useSWR from 'swr';function useUser(id: string) {  const { data, error } = useSWR(`/api/user`, () => {    return {      name: 'yuxiaoliang',      id,    };  });  return {    user: data,    isLoading: !error && !data,    isError: error,  };}export default useUser;

具体的 swr 的用法不是本文的重点,具体可以看文档,本文用一个例子来引出对于 swr 原理的理解:

const sleep = async (times: number) => {    return new Promise(resolve => {      setTimeout(() => {        resolve();      }, times);    });};const { data: data500 } = useSWR('/api/user', async () => {    await sleep(500);    return { a: '500 is ok' };});const { data: data100 } = useSWR('/api/user', async () => {    await sleep(100);    return { a: '100 is ok' };});

上述的代码中输出的是 data100 和 data500 分别是什么?

答案是:

data100和data500都输出了{a:'500 is ok '}

原因也很简单,在swr默认的时间内(默认是 2000 毫秒),对于同一个 useSWRkey ,这里的 key'/api/user' 会进行重复值清除, 只始终 2000 毫秒内第一个 keyfetcher 函数来进行缓存更新。

带着这个例子,我们来深入读读 swr 的源码

二、swr的源码

我们从 useSWR 的 API 入手,来读一读 swr 的源码。首先在 swr 中本质是一种内存中的缓存更新策略,所以在 cache.ts 文件中,保存了缓存的 map

(1)cache.ts 缓存

class Cache implements CacheInterface {   constructor(initialData: any = {}) {    this.__cache = new Map(Object.entries(initialData))    this.__listeners = []  }  get(key: keyInterface): any {    const [_key] = this.serializeKey(key)    return this.__cache.get(_key)  }  set(key: keyInterface, value: any): any {    const [_key] = this.serializeKey(key)    this.__cache.set(_key, value)    this.notify()  }  keys() {      }  has(key: keyInterface) {    }  clear() {    }  delete(key: keyInterface) {    }  serializeKey(key: keyInterface): [string, any, string] {    let args = null    if (typeof key === 'function') {      try {        key = key()      } catch (err) {        // dependencies not ready        key = ''      }    }    if (Array.isArray(key)) {      // args array      args = key      key = hash(key)    } else {      // convert null to ''      key = String(key || '')    }    const errorKey = key ? 'err@' + key : ''    return [key, args, errorKey]  }  subscribe(listener: cacheListener) {    if (typeof listener !== 'function') {      throw new Error('Expected the listener to be a function.')    }    let isSubscribed = true    this.__listeners.push(listener)    return () => {       //unsubscribe    }  }  // Notify Cache subscribers about a change in the cache  private notify() {     }

上述是 cache 类的定义,本质其实很简单,维护了一个 map 对象,以 key 为索引,其中key 可以是字符串,函数或者数组,将 key 序列化的方法为:serializeKey

 serializeKey(key: keyInterface): [string, any, string] {    let args = null    if (typeof key === 'function') {      try {        key = key()      } catch (err) {        // dependencies not ready        key = ''      }    }    if (Array.isArray(key)) {      // args array      args = key      key = hash(key)    } else {      // convert null to ''      key = String(key || '')    }    const errorKey = key ? 'err@' + key : ''    return [key, args, errorKey]  }

从上述方法的定义中我们可以看出:

  • 如果传入的 key 是字符串,那么这个字符串就是序列化后的 key

  • 如果传入的 key 是函数,那么执行这个函数,返回的结果就是序列化后的 key

  • 如果传入的 key 是数组,那么通过 hash 方法(类似 hash 算法,数组的值序列化后唯一)序列化后的值就是 key

此外,在 cache 类中,将这个保存了 keyvalue 信息的缓存对象 map ,保存在实例对象 this.__cache 中,这个 this.__cache 对象就是一个 map ,有set get等方法。

(2)事件处理

在swr中,可以配置各种事件,当事件被触发时,会触发相应的重新请求或者说更新函数。swr对于这些事件,比如断网重连,切换 tab 重新聚焦某个 tab 等等,默认是会自动去更新缓存的。

在swr中对事件处理的代码为

const revalidate = revalidators => {    if (!isDocumentVisible() || !isOnline()) return    for (const key in revalidators) {      if (revalidators[key][0]) revalidators[key][0]()    }  }  // focus revalidate  window.addEventListener(    'visibilitychange',    () => revalidate(FOCUS_REVALIDATORS),    false  )  window.addEventListener('focus', () => revalidate(FOCUS_REVALIDATORS), false)  // reconnect revalidate  window.addEventListener(    'online',    () => revalidate(RECONNECT_REVALIDATORS),    false)

上述 FOCUS_REVALIDATORSRECONNECT_REVALIDATORS 事件中保存了相应的更新缓存函数,当页面触发事件visibilitychange(显示隐藏)、focus(页面聚焦)以及online(断网重连)的时候会触发事件,自动更新缓存

(3)useSWR 缓存更新的主体函数

useSWR 是swr的主体函数,决定了如何缓存以及如何更新,我们先来看 useSWR 的入参和形参。

入参:

  • key : 一个唯一值,可以是字符串、函数或者数组,用来在缓存中唯一标识 key

  • fetcher : (可选) 返回数据的函数

  • options : (可选)对于 useSWR 的一些配置项,比如事件是否自动触发缓存更新等等。

出参:

  • data : 与入参 key 相对应的,缓存中相应 keyvalue

  • error : 在请求过程中产生的错误等

  • isValidating : 是否正在请求或者正在更新缓存中,可以做为 isLoading 等标识用。

  • mutate(data?, shouldRevalidate?) : 更新函数,手动去更新相应 keyvalue

从入参到出参,我们本质在做的事情,就是去控制 cache 实例,这个 map 的更新的关键是:

什么时候需要直接从缓存中取值,什么时候需要重新请求,更新缓存中的值

const stateRef = useRef({    data: initialData,    error: initialError,    isValidating: false})const CONCURRENT_PROMISES = {}  //以key为键,value为新的通过fetch等函数返回的值const CONCURRENT_PROMISES_TS = {} //以key为键,value为开始通过执行函数获取新值的时间戳

下面我们来看,缓存更新的核心函数:revalidate

  // start a revalidation  const revalidate = useCallback(    async (      revalidateOpts= {}    ) => {      if (!key || !fn) return false      revalidateOpts = Object.assign({ dedupe: false }, revalidateOpts)      let loading = true      let shouldDeduping =        typeof CONCURRENT_PROMISES[key] !== 'undefined' && revalidateOpts.dedupe      // start fetching      try {        dispatch({          isValidating: true        })        let newData        let startAt        if (shouldDeduping) {                  startAt = CONCURRENT_PROMISES_TS[key]          newData = await CONCURRENT_PROMISES[key]                  } else {                   if (fnArgs !== null) {            CONCURRENT_PROMISES[key] = fn(...fnArgs)          } else {            CONCURRENT_PROMISES[key] = fn(key)          }          CONCURRENT_PROMISES_TS[key] = startAt = Date.now()          newData = await CONCURRENT_PROMISES[key]          setTimeout(() => {            delete CONCURRENT_PROMISES[key]            delete CONCURRENT_PROMISES_TS[key]          }, config.dedupingInterval)        }        const shouldIgnoreRequest =                  CONCURRENT_PROMISES_TS[key] > startAt ||                    (MUTATION_TS[key] &&                     (startAt

到此,相信大家对"react.js中swr的原理是什么"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

0