美文网首页
基于SWR的应用与实践

基于SWR的应用与实践

作者: 基因宝研发团队 | 来源:发表于2021-05-18 19:57 被阅读0次

    内容作者为基因宝前端团队【一忱】

    介绍

    SWR是一个轻量的、负责请求数据、缓存数据的库。开发者可以使用hooks来发起请求,使用SWR的组件会自动获取数据。

    基本写法如下:

    async function fetcher(url, params) {
      return await axios.get(url, params);
    };
    
    // 第一个参key,代表一个请求的唯一标识,如果key改变了SWR就会重新执行fetcher,更新缓存。
    // 第二个参数fetcher,一个返回数据的请求函数。
    // 默认每次调用SWR都要传入一个fetcher,也可以在SWR的configContext传一个通用fetcher。
    // 第三个参数为config用来配置请求频率,是否重试,初始数据等。
    const {
        data,
        error,
        isValidating, //loading
        mutate, // 函数,用来更改key对应的缓存
      } = useSWR<Data, Error>('/api/user', fetcher, {
        shouldRetryOnError: true,
        initialData: {
          name: 'X',
        },
        onSuccess () {
    
        },
      });
    

    features

    这里只列举我在使用过程中认为很方便的特性,更详细的介绍参考SWR文档

    • 体积小,压缩后只有5kb,源码不到1000行。
    • 对typescript友好。
    • 使用SWR的组件会自动获取数据,如果参数未改变,useSWR被重新调用时(重新渲染或其他组件使用)就不会重新发请求,而是读取缓存。
    • 如果组件未使用SWR的某个返回值(如某个组件没有使用isValidating)则不会重新渲染。
    • 返回error和loading。
    • 支持传入compare选项对比新旧请求数据避免不必要的渲染。
    • 可利用SWR的缓存特性来替代mobx、redux、context状态管理方案。
    • 支持结合axios这种请求库使用。
    • 支持错误重试。

    常见示例

    • 传参
    import {
      useState,
      useMemo,
    } from 'react';
    import useSWR from 'swr';
    
    function Profile() {
      const [selectedUser, setSelectedUser] = useState(null);
    
      const params = useMemo(() => ({
        id: selectedUser.id,
      }), [
        selectedUser,
      ]);
    
      const {
        data: userList,
      } = useSWR('/api/users');
      // 请求传参:默认key会作为fetcher的第一个参数,如果给fetcher传额外参数需要使用数组写法
      // 上面提到过,第一个参数key的改变是SWR是否更新缓存的标识。你可能会想,这样下次渲染的时候key不是变了吗?
      // 无需担心,SWR内部对数组类型key进行了hash处理,参数未变一定不会造成不必要的渲染。
    
      // 条件请求:key为falsy,SWR不会调用fetcher
      const {
        data: profile,
      } = useSWR(Number.isInteger(selectedUser?.id) ? ['/api/user', params] : null);
      // 依赖请求:SWR通过key函数返回值来判断是否需要调用fetcher
      // 实际上SWR对于依赖请求利用了错误捕获,如果key函数抛出错误就不会调用。
      const {
        data: projects,
      } = useSWR(() => '/api/projects?uid=' + profile.id);
      // 轮询
       const {
        data: projects,
      } = useSWR('/api/polling', {
        refreshInterval: 1000,
      });
    };
    

    更新缓存(mutate)

    import {
      mutate, // 全局mutate
    } from 'swr';
    const {
      data: profile,
      mutate: updateProfile, // 已绑定key的mutate
    } = useSWR('/api/user', {
      onSuccess(newProfile) {
        // 映射新请求的数据
        updateProfile({
          ...newProfile,
          age: 0,
        });
      }
    });
    // mutate: (
    //   data?: Data | Promise<Data> | MutatorCallback<Data>,
    //   shouldRevalidate?: boolean
    // ) => Promise<Data | undefined>
    // mutate的第二个参数shouldRevalidate,是否调用fetcher从远端获取数据
    // 传true的目的是先更改本地数据,等待远端数据返回后替换
    
    // 使用全局mutate重新请求某个key
    const revalidateUser = useCallback(() => {
      mutate('/api/user');
    }, []);
    // 基于已有数据更改
    const updateUser = useCallback(() => {
      updateProfile((currentProfile) => ({
        ...currentProfile,
        name: '*',
      }), false);
    }, []);
    // 异步函数
    const updateUserAsync = () => {
      updateProfile(async () => await Promise.resolve({
        name: '*',
      })), false);
    };
    

    实际使用

    上面的示例是比较原始的写法,实际项目使用中遇到了几个问题

    1. 一个接口被多个组件同时使用,每次都要写useSWR('/api/xxx', config)吗?有点蠢,我应该把他们封装成hook。
    2. 由上一条延伸,一般项目的请求都会放在诸如services&api这种目录。
      试想一下api目录下放着一些POST,DELETE等方法,给SWR调用的GET方法放到另一个目录下吗?很奇怪。
    3. 供SWR发起的请求被我封装成hooks了,能不能把修改数据的请求也封装成hooks,保证一致性?

    说了这么多,根本问题就是目录结构

    参考了许多方案,挑选了一个我认为比较好的方案:
    创建queries和mutations目录,queries放诸如useUserList用来查询数据,mutations放useUpdateUserList用来修改数据。
    这样划分目录还是很清晰的,实际项目中SWR相关的hooks会很多,如果简单粗暴的将它们塞到hooks目录里难免会和其他通用的hooks混淆。

    P.S.mutation实际上并不是SWR中的概念,而是react-query中的。
    篇幅有限,详情可参考SWR开发团队成员SergioXalambrí基于react-query中的mutation概念写的use-mutation,当然你也可以写自己的useMutation。

    管理全局状态

    如果只是需要全局状态管理,完全可以用SWR替代mobx这种库。
    P.S.事实上不使用我接下来介绍的globalState hook,SWR也承担了远程数据的管理,剩下的只有一些本地状态了。

    function useGlobalState() {
      const {
        data,
        mutate,
      } = useSWR('globalState', {
        initialData: initialStore,
      });
    
      return {
        globalState: data,
        mutateGlobalState: mutate,
      };
    };
    
    function Content() {
      const {
        globalState,
      } = useGlobalState();
      
      return globalState.draft.content;
    };
    

    总结

    SWR也是一个比较成熟稳定的库了,国外早就开始流行SWR或react-query,截止目前已经有17.1kstars。
    个人觉得使用SWR提高了我的开发效率,并且简单易学。文章只是列举了一些常见用法和重要特性,SWR的源码有很多巧妙之处,大家更深入了解一下。

    如果你有其他好的idea或文章有误,欢迎讨论与指正。

    参考文章:

    相关文章

      网友评论

          本文标题:基于SWR的应用与实践

          本文链接:https://www.haomeiwen.com/subject/jzpyjltx.html