Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SWR 不完全指南 #115

Closed
yuxino opened this issue Nov 2, 2020 · 0 comments
Closed

SWR 不完全指南 #115

yuxino opened this issue Nov 2, 2020 · 0 comments

Comments

@yuxino
Copy link
Owner

yuxino commented Nov 2, 2020

背景

  • 第一次看到swr这个库是在githubtimeline,不知道哪个人点了star吧。因为swr是来自now这个team,于是有点感兴趣。了解了一下背景,还是国内大佬shuding的作品。大佬非常有意思,造的东西都很惊艳,比如Y86处理器这个项目,我一直觉得CSAPP并没有那么有趣吧,也不是,并没有那么那么内啥,但是大佬让我看到了另一面,一个枯燥的编译器指令集在前端页面上也能发光发热,打造出如此赛博朋克的感觉,这是真正喜欢技术的人才能办到的事情,也是我追求的GEEK的感觉吧,沉浸在自己觉得有意义的事情里,是非常容易让人感到满足与骄傲的。
  • 言归正传,我们看源码应该带着反向的思维去想象为什么要这么做,以及我们为什么要这么做,最近投入到了vueposu的开源里面,我们想实现一个vue版本的swr,所以我们必须去理解为什么swr要这么做,它这么做会导致什么问题,以及我们是否有办法在我们release第一个vueposu的版本的时候避免掉这些问题, SO LET'S GO, 让我们来探索 SWR
  • swr全拼是stale-while-revalidate。第一感觉他来自 HTTP RFC 5861
    • 其实和RFC那个并没有什么关联关系,代码里也没有与cache-control有任何的交互。
    • shuding也说了: The name “SWR” is derived from stale-while-revalidate, a cache invalidation strategy popularized by HTTP RFC 5861.
    • 所以说只是名字来自这里罢了
  • 来看swr的三个比较核心的状态返回: const { data, error, isValidating } = useSWR('/api/user', fetcher)
    • dataerror很好理解吧,就是数据和异常
    • isValidating是什么呢? 翻译一下正在验证正在验证是什么鬼,肯定不对,但是它的行为类似 isLoading,我们也可以完全理解它就是我们平时说的 isLoading
      • 那么这个isValidating是怎么来的呢。
      • 注意stale-while-revalidate的最后一个单词。
      • revalidate就是获取数据在swr里。那么很自然,获取数据就是isValidating,大概是根据revalidate变种过来的。

亮点

  • 第一个亮点我认为是 window.focus吧。 比如我们把鼠标移出网页,一阵子之后回来,swr非常的智能,会帮我们自动调用 revalidate,来尝试获取一次最新的数据,更新页面。但这个其实是把双刃剑,有时候不太合适。但是可以关,默认是打开的。

  • 第二个两点就是 cache。 有人说前端 除了 cache 就是 在处理 cache的问题。 可见 cache 在前端里面是如此的重要。举个例子吧,在线计算充值金额,众所周知,前端虽然可以算,你也可以算,只要你勇于背锅。挑战绩效拿最低。以及出了事,卷普滚蛋的心态,你可以接,不然还是让服务端算吧,计算金额其实是个蛮频繁的操作,前端需要做好debounce或者throttle的处理避免多余的网络请求,最好是debounce吧。

    • 但是因为是异步任务,发生什么都是有可能的。
    • 比如说用户此时输入了两个请求在很短的时间内。一个是5000元,一个是500元。
      • 500元是先发出的请求
      • 5000元是后发出的请求
        • req5000 -----------------> res5000
        • req500 ------------> res500
      • 注意看这里5000块钱是先发出去的请求,而500是后发出去的,表示用户删除了一个0
      • 如果你没注意过这件事情的话会发生如下画面
        • 页面先变成了res500的请求结果,显示的是500块钱对应的充值结果
        • 然后飞快的变成了res5000的请求结果,显示的是5000元对应的充值结果
      • 这会让用户狂喜,500块居然能充5000块钱的数目,于是充钱,发现实际到账 500多。
      • 用户炸毛,截图投诉客服
      • 被研发组老大抓起来痛扁
      • 晚上在浅色被单里哭泣
    • 但是你还是有办法处理这个事情的,就是让后端返回个字段,比如你充值多少钱就给回多一个叫 origin的字段,代表原来的充值金额
      • 你只要对比origin和你输入框的金额是否一样,一样的话就显示那个结果,不一样就显示菊花就好了。
    • 但是,你有了swr,真没必要这么做。它能帮你避过这个坑。
      • swr 能够处理这种竞态问题,只会以最后一个发起的结果为准。
  • 第三个点,我一直认为它可以作为 state manager, 但不单单是 state manager, 更加贴切的是 remote state manager。它可以作为 redux 甚至一些东西的代替品。 非常 awesome ,这个特性,可能是无心插柳柳成荫吧。

  • 第四个点,轮询,不用写的多复杂他能帮你做好这件事。

  • 第五点,滚动条位置恢复

  • 第六点,线性的代码逻辑

    这个图表示的是常见的数据依赖的请求图示

    我们看到了这里有些请求时互相依赖的,有些是不依赖的,对于依赖的我们可以用await,写出类似这样的代码(伪)。

    const res1 = await request(...);
    const res2 = await request(res1);
    const res3 = await request(...)

    但是这会导致问题,请求不是并行的,会阻塞所有的请求。有些并不是依赖这个请求的请求也给阻塞了,比如res3,但是 swr 可以帮你避免这个问题,最大程度的进行并行的请求,写出类似这样的代码(伪)。

    const res1 = useSWR(...);
    const res2 = useSWR(res1);
    const res3 = useSWR(...)

    尽最大可能让所有请求都是并行的,而互相依赖的请求保持顺序执行。

  • 第七点,状态的优化。在SWR里面比较用心的一点是状态都是useRef的,也就是说不出意外不会乱更新视图,只要你没用到,大佬通过这一段控制依赖收集。如果用到了就设置成true,设置成true的状态会在dispatch的时候触发 rerender。但是在vue里面不用做这个事情,vue帮忙做了。

  • 第八点,SWR会对俩次的结果进行[深比较](https://github.com/epoberezkin/fast-deep-equal),这样可以避免重复的视图更新。

设计上的思考

  • muateboundMutate 以及 revalidate 的关系

    boundMutate 是源码里面的称呼。是 useSWR 调用之后返回的那个 mutate,这里为了做区分特意写了出来。

    mutate('xxx')const { mutate } = useSWR('xxx'),这俩是等价的。

    直觉上来说 mutate 就是手动改变状态,事实上它也确实是这样子。然后就会写出这样的代码,点击C2的时候变化内容。

    import useSWR from "swr";
    
    let id = 0;
    
    const fetcher = () =>
      new Promise((res) => {
        setTimeout(() => res(id++), 200);
      });
    
    export default function App() {
      const C1 = () => {
        const { data } = useSWR("fetch", fetcher);
        return <span>{data}</span>;
    };
    
    const C2 = () => {
      const { data, mutate } = useSWR("fetch", fetcher);
      return <span onClick={() => mutate("x", false)}>{data}</span>;
    };
    
    return (
      <div>
        <C1 /> || <C2 />
      </div>
      );
    }

    那么实际上点击的时候,会有个速度非常快的变化,先变成了x,再变成2。因为不传 mutate的第二个参数时候, SWR 会默认进行一次 revalidate 。 如果想保持为 x,那就再传一个参数mutate("x", false),这样表示不会自动调用revalidate了。

比较操蛋的事情

  • 首先第一个参数可以传方法,我也没咋看文档一开始,我吼兴奋啊,难道可以写fetcher, 帮我获取数据,然后自动缓存,然鹅刚写就报错了,结果只能返回 string, 说明这压根就不是这么用的,而且只是方法的话,估计也算不出稳定的 key,无法做到缓存这么一说。

  • 第二个,如果你有两个同名的key,你分别给他们传不一样的 options。他俩分别会产生俩种不一样的调用 revalidate 的行为。如果是俩都写了 refreshInterval 可能会让你抓狂。hmmm, 如果同事俩个时间差在 2ms 内,只会调用同一个,看起来,只是看起来,但是如果俩个时间差在 2ms以上,两个都会给同时调用。还有各种奇奇怪的情况,反正同个 key 的话抽出来,不要传不一样的 options 不然会导致一些奇怪的问题。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant