SWR 突变不会使用类的静态方法作为获取器来更新缓存

Tal*_*ofe 6 javascript typescript reactjs swr

我正在使用该swr包。我成功地使用 获取数据useSWR,但是当我尝试改变数据时 - 它不起作用,并且 的缓存状态swr不会改变(正如它应该的那样)。

我创建了自定义钩子:

import useSWR from 'swr';

import BackendService from '@/services/backend';

const useBackend = <D, E = unknown>(path: string | null) => {
    const { data, error, isLoading, mutate } = useSWR<D, E>(path, BackendService.get);

    return { data, error, isLoading, mutate };
};

export default useBackend;
Run Code Online (Sandbox Code Playgroud)

这是我的BackendService

import { preload } from 'swr';

import type { IHttpMethod } from '@/interfaces/http';

class BackendService {
    private static routesWithRefreshToken: string[] = ['/user/auth'];

    private static fetcher = async <R = unknown, D = unknown>(
        path: string,
        method: IHttpMethod,
        data?: D,
    ) => {
        const requestPath = import.meta.env.VITE_BACKEND_URL + path;
        const withRefresh = this.routesWithRefreshToken.includes(path);
        const token = withRefresh ? localStorage.getItem('token') : sessionStorage.getItem('token');

        const res = await fetch(requestPath, {
            method,
            headers: {
                'Authorization': `Bearer ${token}`,
                'Content-Type': 'application/json',
            },
            body: data ? JSON.stringify(data) : undefined,
        });

        if (!res.ok) {
            throw new Error();
        }

        const resData = await res.json().catch(() => undefined);

        return resData as R;
    };

    public static get = <R = unknown>(path: string) => {
        return this.fetcher<R, null>(path, 'GET');
    };

    public static post = <R = unknown, D = unknown>(path: string, data?: D) => {
        return this.fetcher<R, D>(path, 'POST', data);
    };

    public static patch = <R = unknown, D = unknown>(path: string, data?: D) => {
        return this.fetcher<R, D>(path, 'PATCH', data);
    };

    public static delete = <R = unknown>(path: string) => {
        return this.fetcher<R, null>(path, 'DELETE');
    };

    public static preload = (path: string) => {
        return preload(path, this.get);
    };
}

export default BackendService;
Run Code Online (Sandbox Code Playgroud)

现在,我有以下代码:

import React, { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import type { IGetAllSecretsResponseData } from '@exlint.io/common';

import useBackend from '@/hooks/use-backend';
import BackendService from '@/services/backend';

import SecretManagementView from './SecretManagement.view';

interface IProps {}

const SecretManagement: React.FC<IProps> = () => {
    const navigate = useNavigate();

    const { data: getAllSecretsResponseData, mutate: getAllSecretsMutate } =
        useBackend<IGetAllSecretsResponseData>('/user/secrets');

    const hasSecrets = useMemo(() => {
        if (!getAllSecretsResponseData) {
            return false;
        }

        return getAllSecretsResponseData.secrets.length > 0;
    }, [getAllSecretsResponseData]);

    const onRevokeAllSecrets = async () => {
        await getAllSecretsMutate(
            async () => {
                await BackendService.delete('/user/secrets');

                return {
                    secrets: [],
                };
            },
            {
                optimisticData: { secrets: [] },
                rollbackOnError: true,
            },
        );

        navigate('', { replace: true });
    };

    return <SecretManagementView hasSecrets={hasSecrets} onRevokeAllSecrets={onRevokeAllSecrets} />;
};

SecretManagement.displayName = 'SecretManagement';
SecretManagement.defaultProps = {};

export default React.memo(SecretManagement);
Run Code Online (Sandbox Code Playgroud)

因此,当onRevokeAllSecrets执行时 - 缓存状态不会改变。

谁能告诉我为什么吗?我检查了一下,我的BackendService.delete通话成功完成。


我还尝试更改我的自定义挂钩,useBackend如下所示:

import { useCallback } from 'react';
import useSWR, { useSWRConfig, type KeyedMutator } from 'swr';

import BackendService from '@/services/backend';

const useBackend = <D, E = unknown>(path: string | null) => {
    const { mutate: globalMutate } = useSWRConfig();
    const { data, error, isLoading } = useSWR<D, E>(path, BackendService.get);

    const mutator: KeyedMutator<D> = useCallback(
        (data, options) => globalMutate(path, data, options),
        [path],
    );

    return {
        data,
        error,
        isLoading,
        mutate: mutator,
    };
};

export default useBackend;
Run Code Online (Sandbox Code Playgroud)

但它仍然没有帮助(同样的问题)


我也尝试提供revalidate突变:

        await getAllSecretsMutate(
            async () => {
                await BackendService.delete('/user/secrets');

                return { secrets: [] };
            },
            {
                optimisticData: { secrets: [] },
                rollbackOnError: true,
                revalidate: false,
            },
        );
Run Code Online (Sandbox Code Playgroud)

然后它起作用了——缓存确实改变了。但我导航到其他页面然后返回 - 缓存再次无效。

无论如何,如果添加 revalidate: false 确实会更新缓存,那么我会说,当我使用 revalidate: true (默认)时,一旦异步更新解析,重新验证就会从我的服务器带来非空数据?但就是这样 - 我检查了服务器的响应,它用空数组响应:

在此输入图像描述


另外,我不认为static方法的行为会导致该问题,因为我将其重构BackendService为如下所示:

import { preload } from 'swr';

import type { IHttpMethod, IRefreshTokenRoute } from '@/interfaces/http';

const routesWithRefreshToken: IRefreshTokenRoute[] = [
    { method: 'GET', path: '/user/auth' },
    { method: 'GET', path: '/user/auth/refresh-token' },
];

const fetcher = async <R = unknown, D = unknown>(path: string, method: IHttpMethod, data?: D) => {
    const endpointPath = import.meta.env.VITE_BACKEND_URL + path;

    const withRefresh = !!routesWithRefreshToken.find(
        (route) => route.method === method && route.path === path,
    );

    const token = (withRefresh ? localStorage : sessionStorage).getItem('token');

    const res = await fetch(endpointPath, {
        method,
        headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json',
        },
        body: data ? JSON.stringify(data) : undefined,
    });

    if (!res.ok) {
        throw new Error();
    }

    const resData = await res.json().catch(() => undefined);

    return resData as R;
};

const get = <R = unknown>(path: string) => {
    return fetcher<R, null>(path, 'GET');
};

const post = <R = unknown, D = unknown>(path: string, data?: D) => {
    return fetcher<R, D>(path, 'POST', data);
};

const patch = <R = unknown, D = unknown>(path: string, data?: D) => {
    return fetcher<R, D>(path, 'PATCH', data);
};

const deleter = <R = unknown>(path: string) => {
    return fetcher<R, null>(path, 'DELETE');
};

const preloader = (path: string) => {
    return preload(path, get);
};

const BackendService = {
    fetcher,
    get,
    post,
    patch,
    deleter,
    preloader,
};

export default BackendService;
Run Code Online (Sandbox Code Playgroud)

但这没有帮助。

Tal*_*ofe 0

我通过强制突变解决了这个问题:

import useSWR, { type KeyedMutator } from 'swr';
import { useCallback } from 'react';

import BackendService from '@/services/backend';

const useBackend = <D, E = unknown>(path: string | null) => {
    const { data, error, isLoading, mutate } = useSWR<D, E>(path, BackendService.get);

    /**
     * * Forced mutation is required for synchronization issues,
     * * as noted here: /sf/answers/5305757621/
     * * The second mutation call uses cache therefore not extra API call network is made
     */
    const forcedMutation: KeyedMutator<D> = useCallback(
        async (mutationData, options) => {
            const data = await mutate(mutationData, options);

            await mutate();

            return data;
        },
        [mutate],
    );

    return { data, error, isLoading, mutate: forcedMutation };
};

export default useBackend;
Run Code Online (Sandbox Code Playgroud)

请阅读评论以了解更多..