npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@jueinin/vue-query

v0.2.2

Published

基于 vue composition api 的网络请求库

Downloads

8

Readme

基于 vue composition api 的网络请求库

React Query得到启发。

可以理解为基于 vue 的 react query, 实在是太好用了,所以想移植到 vue 中来。

功能概览

  • 兼容 vue2 和 3 版本。使用 vue2.x 版本,你需要先安装 @vue/composition-api依赖。thanks to vue-demi
  • 不限数据获取方式,你可以使用其他网络请求库例如axios,或者umi request,也可以使用 HTTP 或者 graphql
  • 请求自动缓存,当数据过期或者浏览器窗口重新 focus 时自动重新从服务端获取数据
  • 并行请求、依赖请求
  • 取消请求
  • 开箱即用的滚动恢复
  • 支持 tree shaking

Documentation

安装

$ npm i --save @jueinin/vue-query
# or
$ yarn add @jueinin/vue-query

快速上手

get 请求

方便起见,以下示例均使用基于 vue3.0 的 tsx 编写,和模板差不多的。

import Axios from "axios";
import { defineComponent } from "@vue/runtime-core";
import { useQuery } from "./useQuery";
const Todos = defineComponent({
    setup() {
        const query = useQuery<string[]>("/api/test", (url) => {
            return Axios.get({ url }).then((value) => value.data);
        });
        return () => {
            return (
                <ul>
                    {query.data?.map((todo) => (
                        <li>{todo}</li>
                    ))}
                </ul>
            );
        };
    },
});

例子

codesandbox

Queries

创建一个简单的请求

调用 useQuery,并且至少传入以下两个参数来创建请求

  • queryKey 一个能代表你请求的数据的 queryKey,可以传任何能被序列化的值,一般来说传请求的地址这个字符串即可。
  • queryFn 一个返回 Promise 的异步函数,函数的参数就是 queryKey,如果 queryKey 是数组类型,queryKey 数组会被展开一个个地传入(使用拓展操作符...),否则直接传给 queryFn。
import Axios from "axios";
import { defineComponent } from "vue";
import { useQuery } from "./useQuery";
const Todos = defineComponent({
    setup() {
        const page = 2;
        const query = useQuery<string[]>(["/api/data", page], (url, page) => {
            return Axios.get({ url: url + "?page=" + page }).then((value) => value.data);
        });
        return () => {
            if (query.isLoading) {
                return <div>Loading...</div>;
            }
            // 出现错误时,渲染错误信息
            if (query.error) {
                return <div>{query.error.message}</div>;
            }
            return (
                <div>
                    <ul>
                        {query.data.map((todo) => (
                            <li>{todo}</li>
                        ))}
                    </ul>
                </div>
            );
        };
    },
});

观察 queryKey

如果queryKey改变了,vue-query会自动向服务端请求最新的数据,为了使vue-query可以观察到queryKey的改变,你需要传递一个 Ref 类型 的值给queryKey,推荐使用 computed 方法来得到这个 Ref 类型的值。

点击下一页时,自动请求下一页数据

import Axios from "axios";
import { defineComponent, ref, computed } from "vue";
import { useQuery } from "./useQuery";
const Todos = defineComponent({
    setup() {
        const page = ref(1);
        const query = useQuery<string[]>(
            // 这里将会观察page的值,page改变重新请求
            computed(() => ["/api/articles", page.value]),
            (url, page) => {
                return Axios.get({ url: url + "/" + page }).then((value) => value.data);
            }
        );
        return () => {
            if (query.isLoading) {
                return <div>Loading...</div>;
            }
            if (query.error) {
                return <div>{query.error.message}</div>;
            }
            return (
                <div>
                    <ul>
                        {query.data.map((todo) => (
                            <li>{todo}</li>
                        ))}
                    </ul>
                    // 改变页码
                    <button onClick={() => page.value++}>next page</button>
                </div>
            );
        };
    },
});

也可以传入一个对象来请求

useQuery({
    queryKey: "/api/test",
    queryFn: (url) => Axios.get({ url }),
    config: {},
});

并行请求

vue-query开箱即用地支持并行请求

依赖请求

依赖请求指,一个请求依赖于另一个请求。通常是当前请求依赖于上一个请求的数据

import Axios from "axios";
import { defineComponent, ref, computed } from "vue";
import { useQuery } from "./useQuery";
const Todos = defineComponent({
    setup() {
        const page = ref(1);
        const query = useQuery<string[]>("/api/test", (url) => {
            return Axios.get({ url }).then((value) => value.data);
        });
        const dependentQuery = useQuery<string[]>(
            "/api/test",
            (url) => {
                return Axios.get({ url }).then((value) => value.data);
            },
            // 这里传入ref类型值,这样enabled属性就变得可观察了
            computed(() => ({
                // query.data首先是undefined,当上一个请求执行完毕后,这个请求才会执行
                enabled: query.data,
            }))
        );
        return () => {
            if (query.isLoading) {
                return <div>Loading...</div>;
            }
            if (query.error) {
                return <div>{query.error.message}</div>;
            }
            return (
                <div>
                    <ul>
                        {query.data.map((todo) => (
                            <li>{todo}</li>
                        ))}
                    </ul>
                    <button onClick={() => page.value++}>next page</button>
                </div>
            );
        };
    },
});

取消请求

一般而言,我们并不会取消请求,而是直接忽略对请求数据的处理。为什么呢:

  • 对大多数情况而言直接不处理即可。
  • 不是所有的网络请求方式都能方便的取消请求。
  • 就算能够取消请求,他们的实现方式也各不相同。

不过如果遇到了需要请求大量数据的场景,请求可能会耗时比较久,这个时候取消请求就变得有必要了。

以下以 Axios 为例,取消请求

import { CancelToken } from "axios";

const query = useQuery("/api/test", (url) => {
    const source = CancelToken.source();
    const promise = axios.get({
        url,
        cancelToken: source.token,
    });
    // cancel方法来取消请求
    promise.cancel = () => {
        source.cancel("cancel request");
    };
    return promise;
});

全局 isFetching 状态

只要有 query 请求正在进行,全局的 isFetching 状态就会为 true,这个适合作为全局的 isFetching 状态。

import { useIsFetching } from "@jueinin/vue-query";
import { defineComponent } from "vue";
import { NetworkIndicator } from "someUILib";
const App = defineComponent({
    setup() {
        const globalIsFetching = useIsFetching();
        return () => <div>{globalIsFetching.value && <NetworkIndicator />}</div>;
    },
});

缓存

请求结束后,会自动关联queryKey缓存请求结果。使用stale while revalidate内存缓存策略

概览

- 缓存和queryKey的hash值对应
- 默认情况下,成功请求后缓存结果立即过期(但不会被删除,见下文),这个行为可以通过配置staleTime这个option来改变
- queryKey改变了的话,vue-query会自动请求,他会首先检查这个queryKey有没有对应的缓存,有缓存并且缓存数据没有过期的话,直接返回缓存值,不再请求。如果缓存过期了,就返回缓存值,并重新请求并更新缓存值。
- 默认的缓存时间是5分钟,默认情况下缓存会在5分钟后从内存中释放。这个选项可以通过配置cacheTime来改变

以下以cacheTime为5分钟,staleTime为0举个例子
- 我们调用了一个新的useQuery('todos',fetchTodoFn)。
    - 因为是第一次使用这个queryKey,所以isLoading会为true,isFetching也是true,然后执行网络请求。
    - 获取请求后,以queryKey的hash值,作为缓存的标识符。把数据缓存起来。
    - 根据statleTime来决定这个缓存何时过期。默认是0,所以会立即过期。
- 在其他地方我们再次调用了useQuery,使用相同的queryKey
    - 由于缓存中有相应的缓存数据,所以这个请求会立即从缓存中返回数据。
    - 检查缓存是否过期,过期了就请求一下服务端,拿到最新的数据。
- 5分钟后删除缓存的数据。

useMutation

useQuery不同,useMutation一般用来执行 POST 请求,用来创建,更新,删除数据。 useMutation 的请求不会参与缓存处理。 基础 mutation

import { defineComponent } from "vue";
const App = defineComponent({
    setup() {
        const mutation = useMutation(() => Promise.resolve("dd"));
        const requestData = () => {
            console.log("we will request data");
            mutation.mutate();
        };
        return () => {
            return <div onClick={requestData}>点击获取数据。数据: {mutation.data}</div>;
        };
    },
});

带参数的 mutation 以及带 callback 的 mutation 示例。

可以给示例的 mutation.mutate 方法传递一个值,这个值作为 useMutationFn 的参数去请求服务端。

useMutation 可以有 onSuccess onError 等 callback,mutation.mutate 的第二个参数也可以传入这些 callback。 mutation.mutate 的 callback 会覆盖 useMutation 的 callback

import { defineComponent } from "vue";
const App = defineComponent({
    setup() {
        // 使用传来的todo向服务端请求新建一个todo
        const createTodoMutation = useMutation(
            (todo: { title: string }) =>
                Axios.post({
                    url: "/api/createTodo",
                    data: todo,
                }),
            {
                onSuccess: (data) => {
                    alert("创建成功!");
                },
            }
        );
        return () => {
            return (
                <div
                    onClick={() => {
                        // 传递一个todo
                        createTodoMutation.mutate(
                            { title: "write doc!" },
                            {
                                // mutate的回调会覆盖useMutation的回调。上面的回调不会生效
                                onSuccess: (data) => {
                                    alert("我会覆盖掉上面的onSuccess回调");
                                },
                            }
                        );
                    }}
                >
                    点击创建todo
                </div>
            );
        };
    },
});

乐观更新

onMutate 先进行乐观更新,并返回一个 rollback 函数,在 onError 或者 onSettled 函数中回滚更新。

const hook = renderHook(() => {
    const count = ref(0);
    const mutation = useMutation((pa: number) => Promise.reject("err"), {
        onMutate: (variable) => {
            count.value++;
            // 返回rollback函数
            return () => {
                count.value--;
            };
        },
        onError: (error, variable, rollback) => {
            rollback();
        },
    });
    return { mutation };
});

api 指南

useQuery

useQuery 通常用于幂等 get 请求。具有自动重试,自动重新获取数据,自动缓存等功能。

const query = useQuery(queryKey, queryFn, config);

参数

  • queryKey any | Ref

    • Required
    • 用来缓存以及获取数据的唯一 key。
    • 如果需要在 queryKey 改变时,重新请求,你需要使用 computed()生成一个 ref 值,传入。
  • queryFn: (...args)=>Promise & {cancel: ()=>void}

    • Required

    • 使用这个函数来请求数据

    • 按序接收 queryKey 的值作为参数。举个例子,queryKey 不是数组时: useQueryKey('todo',(todo: string)=>Promise.resolved('data))

      queryKey 是数组时 useQueryKey(['todo',2],(todo: string,page: number)=>Promise.resolved('data))

    • 必须返回一个 Promise,可选带有一个 cancel 方法,来取消请求。

  • config: 请求配置对象,

    • enabled: boolean
      • 如果是 falsy 值,调用后不会进行请求。当使用此选项时,需要传入 ref 类型的 config,否则无法观察它的变动。
      • 默认为 true。
    • retry: int | false | (failCount: number,error: any)=>boolean
      • 请求失败时,使用此选项进行重试。
      • 为 false 时,不会重新请求。
      • 设置为 int 时,重试指定的次数。
      • 设置为函数时,将会根据函数返回的 boolean 值来决定是否重试
      • 默认值为 3
    • retryDelay: (retryCount: number)=>number
      • 接受请求的重试次数为参数,返回值来决定下次重试的延迟时间。
      • 默认值为attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
    • staleTime: number
      • 设置缓存的过期时间,过期后请求时将会先返回缓存值再请求,如果尚未过期,则只返回缓存值,不再请求。
      • 默认值为 0,缓存生成后会立即过期
    • cacheTime: number
      • 缓存在内存中存在的时间。超时了自动被清理掉。
      • 默认值是 5601000,也就是 5 分钟。
    • initialData: any | () => any
      • 设置初始值,在首次请求时不再请求,直接返回 initialData。
      • 默认值 undefined
    • onSuccess: (data:Data)=>viud
      • promise 成功后的回调函数。
      • 默认值()=>{}
    • onError: (error: Error)=>void
      • promise 失败后的回调函数
      • 默认值()=>{}
    • refetchOnReconnect: boolean
      • 断网重连后是否重新请求服务端数据
      • 默认值 true
    • refetchOnWindowFocus: boolean
      • 浏览器 tab 重新获得焦点后是否重新请求服务端数据
      • 默认值 true
    • refetchInterval: number | false
      • 是否轮询数据,设置为 number 时以这个毫秒数轮询服务端,设置为 false 时取消轮询,注意你可能需要传入 Ref 类型的 config 来动态改变此选项。
      • 默认值 false
    • refetchIntervalInBackground: boolean
      • 是否在当前浏览器 tab 不在前台时轮询。因为不在前台状态下轮询,很多时候没有意义。
      • 默认值 false
  • query:useQuery 返回值,注意返回值尽量不要使用解构赋值去结构,可能会丢失响应性

    • data: Data | undefined
      • queyFn 返回的 promise resolved 的数据
      • 默认值 undefined
    • error: Error | undefined
      • queryFn 返回的 promise rejected 的数据
      • 默认值 undefined
    • isError: boolean
      • 默认值 false
    • isSuccess: boolean
      • 默认值 false
    • isLoading: false
      • 默认值 false
    • status: "loading" | "error" | "success" | "idle"
      • 对上面三个状态的一个封装,初始状态时 idle,请求结束后状态也是 idle
      • 默认值 "idle"
    • retryCount: number
      • 请求失败后的重试次数。
      • 默认值 0
    • isFetching: boolean
      • 是否正在请求,容易跟isLoading混淆,区别在于,在缓存过期时请求 isFetching 为 true,isLoading 为 false。isLoading 仅在首次无缓存请求时为 true。
      • 默认值为 false
    • refetch: (option: RefetchOption)=>void
      • RefetchOption: {force?: boolean},当 force 设置为 true 时,将会忽略缓存处理直接请求。
    • cancel: ()=>void
      • 取消请求,仅当 queryFn 返回的 Promise 有 cancel 方法时可用。你需要通过 cancelToken 等方式自定义一个 promise.cancel 方法

useMutation

一般用于 post 请求,只有在调用返回值的 mutate 方法时才执行。

import { defineComponent } from "vue";

const App = defineComponent({
    setup() {
        const mutation = useMutation(() => Promise.resolve("data"), {
            onError: () => void 0,
            onSuccess: () => void 0,
            onSettled: () => void 0,
            onMutate: () => void 0,
        });
        return () => {
            // 注意不要直接写成onClick={mutation.mutate}, 这样mutate方法会被传入一个event对象
            return <div onClick={() => mutation.mutate()}></div>;
        };
    },
});

参数

  • mutationFn: (variable)=>Promise
    • mutationFn 返回 Promise,通过它来发起请求,参数由返回值的 mutate 方法传入
  • config: object
    type PlainMutationConfig<Variable, Data, Error, MutateValue = any> = {
        // 发送请求时触发
        onMutate?: (variable?: Variable) => MutateValue;
        onSuccess?: (data: Data, variable?: Variable) => void;
        onError?: (error: Error, variable?: Variable, mutableValue?: MutateValue) => void;
        // 请求结束后触发
        onSettled?: (data: Data | undefined, error: Error | undefined, variable?: Variable, mutableValue?: MutateValue) => void;
    };
  • mutation: 返回值
    • data: Data | undefined
      • 返回的数据,默认 undefined
    • error: Error | undefined
      • 错误信息, 默认 undefined
    • isError: boolean
    • isLoading: boolean
    • isSuccess: boolean
    • status: "loading" | "success" | "error"
    • reset: ()=>void
      • 初始化状态,将 data,error,isError,isSuccess,status 设置为

VueQueryProvider

context,用于配置全局 config,可以在这里设置全局的 config,例如设置全局的 stateTime。

const Child = defineComponent({
    setup() {
        // useQuery将会使用staleTime5000的config
        const query = useQuery("api/test", jest.fn().mockResolvedValue("dd"));
        return () => <div></div>;
    },
});
const Parent = defineComponent({
    setup() {
        return () => (
            <div>
                <VueQueryProvider config={{ staleTime: 5000 }}>
                    <Suspense>
                        <Child />
                    </Suspense>
                </VueQueryProvider>
            </div>
        );
    },
});

useIsFetching

返回是否全局中有请求正在处理。

import { useIsFetching } from "@jueinin/vue-query";
const isFetching: Ref<boolean> = useIsFetching();

queryCache

操作缓存的底层接口,一般使用queryManager即可。

import {queryCache} from '@jueinin/vue-query'
type CacheValue<T = any> = {
    storeTime: number;
    data: T;
    getIsStaled: () => boolean;
    // 这个属性可以忽略
    timeoutDeleteNum: number | undefined
}
class QueryCache {
    addToCache: <T>(value: {
        queryKey: PlainQueryKey;
        data: T;
        cacheTime: number;
        staleTime: number;
    }) => void;
    removeFromCache: (queryKey: PlainQueryKey) => void;
    updateCache: {
        <T>(queryKey: PlainQueryKey, updater: (oldData: T) => T): void;
        <T>(queryKey: PlainQueryKey, updater: T): void;
    };
    getCache: (queryKey: PlainQueryKey) => CacheValue<any> | undefined;
    hasCache: (queryKey: PlainQueryKey) => boolean;
    clear: () => void;
}
  • addToCache 添加缓存,添加完成后,会根据cacheTime和staleTime来决定后续缓存的状态。
  • removeFormCache 根据queryKey删除缓存数据。
  • updateCache 更新指定queryKey关联的缓存数据。可以直接传递一个新的缓存数据,也可以传递一个更新函数,返回值为新的缓存数据。
  • getCache 根据queryKey获取缓存数据。返回值为CacheValue
  • hasCache 根据queryKey决定是否存在对应的缓存数据。

queryManager

管理useQuery产生的请求(即Query对象)。Query对象代表一个请求,包含请求的各种状态 isLoading,data,error等请求状态

  • queryManager.queryList 管理的所有Query对象

  • queryManager.createQuery 创建query对象,并请求。通常通过useQuery间接调用。

    • queryKey: Ref<PlainQueryKey>,Ref类型的queryKey
    • queryFn: 和useQuery参数相同,返回Promise
    • config: Ref<Required> 配置,等同useQuery的config
    • 返回query对象。
  • queryManager.removeQuery 删除query对象,参数为query

  • queryManager.getQueryByQueryKey 根据queryKey获取query对象

  • queryManager.prefetchQuery 预请求数据,可以在数据被需要使用之前先请求并缓存数据。例如,可以预先请求下一页的数据,当进入下一页时,数据就可以立刻加载。

    • queryKey
    • queryFn
    • config
    • extraArgs: object
      • force: boolean 是否直接请求,不论缓存是否过期
    • 返回值 Promise<Data>
  • queryManager.getQueriesByQueryKey 根据queryKey查找所有满足条件的query对象

    • queryKeyOrPredication: queryKey或者是一个函数 (queryKey: PlainQueryKey)=>boolean。当为函数时,将会遍历queryManager.queryList 找到返回true值的query,并忽略exact选项。

    当queryKey为非数组类型时,使用deepEqual来匹配相等的query对象。当为数组类型且exact选项为false时可以模糊匹配,例如 ['key1']可以匹配 'key1'['key1', 'key2'], ["key1", "key2", "key3"]

    • options: 可选的option
      • staled: boolean 是否只筛选缓存过期了的query。默认false
      • exact: boolean 当为true时禁用模糊匹配,默认false
  • queryManager.refetchQueries 对所选的query对象重新进行网络请求请求

    • 参数同queryManager.prefetchQuery
  • queryManager.invalidateQueries 使所选query对象对应的缓存失效,并重新请求数据

    • queryKeyOrPredication: 参数同queryManager.getQueriesByQueryKey的参数
    • option.shouldRefetch: 当为false时禁用自动重新获取请求数据
  • queryManager.setQueryData 设置query对应的缓存数据,会同时更新组件渲染新的数据

    • queryKey
    • updater: 更新函数 Data | (data: Data) => Data. 直接传递新的缓存数据,或者通过函数返回一个新的缓存数据。
  • queryManaget.getQueryData 获取缓存数据

    • queryKey
    • 返回缓存数据。