remix-client-cache

GitHub Repo stars npm GitHub npm npm GitHub top language

remix-client-cache 是一个强大且轻量级的库,专为 Remix.run 打造,用于使用 clientLoaders 在客户端缓存服务器加载器数据。

默认情况下,它使用 stale while revalidate 策略,并在从服务器加载后热交换您的过时信息。它还允许您使特定键或多个键的缓存失效。

它允许您传入您选择的适配器来缓存您的数据。

它带有一个默认适配器,使用内存存储来缓存您的数据。

对 localStorage、sessionStorage 和 localforage 包的一级支持。您只需将它们作为参数提供给 configureGlobalCache

安装

npm install remix-client-cache

基本用法

这是一个使用带有默认内存适配器的 remix-client-cache 的示例用法。

import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { ClientLoaderFunctionArgs } from "@remix-run/react";

import { cacheClientLoader, useCachedLoaderData } from "remix-client-cache";

export const loader = async ({ params }: LoaderFunctionArgs) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${params.user}`
  );
  const user = await response.json();
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return json({ user: { ...user, description: Math.random() } });
};


// Caches the loader data on the client
export const clientLoader = (args: ClientLoaderFunctionArgs) => cacheClientLoader(args);
  
// make sure you turn this flag on
clientLoader.hydrate = true;

export default function Index() {
  // The data is automatically cached for you and hot swapped when refetched
  const { user } = useCachedLoaderData<typeof loader>(); 

  return (
    <div>
      {user.name} <hr /> {user.email}
      <hr />
      {user.username}
      <hr />
      {user.website} <hr />
      {user.description} 
    </div>
  );
}

缓存适配器

该库导出一个接口,您需要实现该接口才能创建自己的缓存适配器。该接口称为 CacheAdapter。它与 Storage 接口非常匹配,并要求您具有以下方法

  • getItem:获取一个键并返回一个 promise,该 promise 解析为存储在该键处的值
  • setItem:获取一个键和一个值并返回一个 promise,该 promise 在存储值时解析
  • removeItem:获取一个键并返回一个 promise,该 promise 在删除值时解析

cacheLoaderData 将使用库附带的默认内存缓存适配器。如果您想要一个高级用例,请确保您提供的适配器实现了 CacheAdapter 接口。

// Inside your entry.client.tsx file 
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

import { configureGlobalCache } from "remix-client-cache";

// You can use the configureGlobalCache function to override the libraries default in-memory cache adapter
configureGlobalCache(() => localStorage); // uses localStorage as the cache adapter

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <RemixBrowser />
    </StrictMode>
  );
});

您可以使用 configureGlobalCache 函数覆盖库的默认内存缓存适配器。它将全局切换到您提供给它的任何适配器。

如果您希望拥有每个路由的适配器,您可以使用 createCacheAdapter 创建适配器并将其提供给您的钩子和函数。


import { createCacheAdapter, useCachedLoaderData } from "remix-client-cache";

const { adapter } = createCacheAdapter(() => localStorage); // uses localStorage as the cache adapter


// Caches the loader data on the client
export const clientLoader = (args: ClientLoaderFunctionArgs) => cacheClientLoader(args, { 
  // We pass our custom adapter to the clientLoader
  adapter
});
  
// make sure you turn this flag on
clientLoader.hydrate = true;

export default function Index() {
  const { user } = useCachedLoaderData<typeof loader>({ 
    // We use the adapter returned by the createCacheAdapter function
    adapter
  });

  return (
    <div>
      {user.name} <hr /> {user.email}
      <hr />
      {user.username}
      <hr />
      {user.website} <hr />
      {user.description} 
    </div>
  );
}


以下是如何使用不同全局适配器的库的一些示例。

configureGlobalCache(() => localStorage); // uses localStorage as the cache adapter
configureGlobalCache(() => sessionStorage); // uses sessionStorage as the cache adapter
configureGlobalCache(() => localforage); // uses localforage as the cache adapter

以及不同的每个路由适配器

const { adapter } = createCacheAdapter(() => localStorage); // uses localStorage as the cache adapter
const { adapter } = createCacheAdapter(() => sessionStorage); // uses sessionStorage as the cache adapter
const { adapter } = createCacheAdapter(() => localforage); // uses localforage as the cache adapter

假设您想使用一个使用数据库存储数据的自定义适配器。

您可以通过实现 CacheAdapter 接口并将其传递给 configureGlobalCachecreateCacheAdapter 函数来做到这一点。

class DatabaseAdapter implements CacheAdapter {
  async getItem(key: string) {
    // get the item from the database
  }

  async setItem(key: string, value: string) {
    // set the item in the database
  }

  async removeItem(key: string) {
    // remove the item from the database
  }
}

configureGlobalCache(() => new DatabaseAdapter()); // uses your custom adapter as the cache adapter globally
const { adapter } = createCacheAdapter(() => new DatabaseAdapter()); // uses your custom adapter as the cache adapter per route

API

createCacheAdapter

创建缓存适配器并返回它的函数。它接受一个参数,即用于存储数据的 adapter

import { createCacheAdapter } from "remix-client-cache";

const { adapter } = createCacheAdapter(() => localStorage); // uses localStorage as the cache adapter

configureGlobalCache

配置全局缓存适配器的函数。它接受一个参数,即用于存储数据的 adapter

import { configureGlobalCache } from "remix-client-cache";

configureGlobalCache(() => localStorage); // uses localStorage as the cache adapter

cacheClientLoader

用于缓存从加载器通过 clientLoader 导出传递到组件的数据。

它接受两个参数,第一个是传递给 clientLoader 函数的 ClientLoaderFunctionArgs 对象,第二个是具有以下属性的对象

  • type - 它告诉客户端加载器是否应该使用正常的缓存机制(其中它存储数据并提前返回而不是重新获取)或是否应该使用 staleWhileRevalidate 机制(其中它返回缓存的数据并在后台重新获取)。
  • key - 用于在缓存中存储数据的键。默认为当前路由路径,包括搜索参数和哈希。(例如,/user/1?name=John#profile)
  • adapter - 用于存储数据的缓存适配器。默认为库附带的内存适配器。
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { ClientLoaderFunctionArgs } from "@remix-run/react"; 
import { cacheClientLoader, useCachedLoaderData } from "remix-client-cache";

export const loader = async ({ params }: LoaderFunctionArgs) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${params.user}`
  );
  const user = await response.json();
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return json({ user: { ...user, description: Math.random() } });
};

export const clientLoader = (args: ClientLoaderFunctionArgs) => cacheClientLoader(args, {
  type: "swr", // default is swr, can also be set to normal
  key: "/user/1" // default is the current route path including search params and hashes
  adapter: () => localStorage // default is the in memory adapter, can be anything your wish
});
clientLoader.hydrate = true;

decacheClientLoader

用于删除从加载器通过 clientLoader 导出传递到组件的数据。

import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { ClientLoaderFunctionArgs } from "@remix-run/react"; 
import { decacheClientLoader, useCachedLoaderData } from "remix-client-cache";

export const loader = async ({ params }: LoaderFunctionArgs) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${params.user}`
  );
  const user = await response.json();
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return json({ user: { ...user, description: Math.random() } });
};
// The data is cached here
export const clientLoader = (args: ClientLoaderFunctionArgs) => cacheClientLoader;
clientLoader.hydrate = true;
// It is de-cached after a successful action submission via the clientAction export
export const clientAction = decacheClientLoader;

接受一个可选对象,其中包含以下属性

  • key - 用于在缓存中存储数据的键。
  • adapter - 用于存储数据的缓存适配器。

useCachedLoaderData

可用于从 clientLoader 导出获取缓存数据的钩子。必须与 cacheClientLoader 一起使用,因为从 cacheClientLoader 返回的数据已增强以考虑 useCachedLoaderData,而不是标准的 useLoaderData 钩子。

import { useCachedLoaderData } from "remix-client-cache";

// Must be used together with cacheClientLoader
export const clientLoader = (args: ClientLoaderFunctionArgs) => cacheClientLoader(args, "swr");
clientLoader.hydrate = true;

export default function Index() {
  // The data is automatically cached for you and hot swapped when refetched
  const { user } = useCachedLoaderData<typeof loader>(); 

  return (
    <div>
      {user.name} <hr /> {user.email}
      <hr />
      {user.username}
      <hr />
      {user.website} <hr />
      {user.description} 
    </div>
  );
}

接受一个可选对象,其中包含以下属性

  • adapter - 用于存储数据的缓存适配器。默认为库附带的内存适配器。

useSwrData

用于获取为您热交换数据的 SWR 组件的钩子。它接受一个参数,即 useCachedLoaderDatauseLoaderData 钩子返回的 loaderData。

import { useCachedLoaderData, useSwrData } from "remix-client-cache";

export const clientLoader = (args: ClientLoaderFunctionArgs) => cacheClientLoader(args);
clientLoader.hydrate = true;

export default function Index() {
  // We do not destructure the data so we can pass in the object into the useSwrData hook
  const loaderData = useLoaderData<typeof loader>(); 
  // You can also use useCachedLoaderData hook with the useSwrData hook
  const loaderData = useCachedLoaderData<typeof loader>(); 
  // Pass the loader data into the hook and the component will handle everything else for you
  const SWR = useSwrData(loaderData);

  return (
    <SWR>
      {/** Hot swapped automatically */}
      {({ user }) => (
        <div>
          {data.name} <hr /> {data.email}
          <hr />
          {data.username}
          <hr />
          {data.website} <hr />
          {data.description} 
        </div>
      )}
    </SWR>
  );
}

invalidateCache

可用于使特定键的缓存失效的实用程序函数。它接受一个参数,即用于在缓存中存储数据的 key。也可以是键数组

import { invalidateCache } from "remix-client-cache";

invalidateCache("/user/1"); // invalidates the cache for the /user/1 route

请记住,这只能在客户端使用,因此可以在 clientLoaderclientAction 导出或组件本身中使用。

useCacheInvalidator

返回一个函数的钩子,该函数可用于使特定键的缓存失效。它接受一个参数,即用于在缓存中存储数据的 key。也可以是键数组

import { useCacheInvalidator } from "remix-client-cache";

export default function Index() {
  const { invalidateCache } = useCacheInvalidator(); 

  return (
    <div>
      // invalidates the cache for the /user/1 route
      <button onClick={ () => invalidateCache("/user/1") }>Invalidate cache</button>
    </div>
  );
}

支持

如果您喜欢该项目,请考虑通过在 Github 上点赞来支持我们。

许可证

MIT

错误

如果您发现错误,请在 GitHub 上的我们的问题跟踪器 中提交问题

贡献

感谢您考虑为 remix-client-cache 做出贡献!我们欢迎任何大小贡献,包括错误报告、功能请求、文档改进或代码更改。

要开始,请分叉此存储库并在新分支中进行更改。准备好提交更改后,请打开一个拉取请求,其中包含对更改的清晰描述以及任何相关问题或拉取请求。

请注意,所有贡献均受我们的 行为准则 约束。参与即表示您同意遵守此准则。

感谢您抽出时间为 remix-client-cache 做出贡献,并帮助使其成为社区中更好的工具!