使用缓存解耦前端代码的优势

文章讨论了解耦在简化代码和提升项目可维护性中的重要性,介绍了常见的代码划分方法,如视图层、领域层和Infralayer。重点强调了在性能和解耦之间取得平衡,通过例子展示如何在前端进行数据连接,以及如何通过缓存策略降低后端调用,同时关注数据一致性问题和低TTL的适用性。
摘要由CSDN通过智能技术生成

我们可以同意解耦是一个很好的做法,可以简化代码和项目的可维护性。

解耦代码的一种常见方法是将职责划分为不同的层。一个非常常见的划分是:

视图层:负责渲染HTML并与用户交互
领域层:掌管业务逻辑
Infra layer:负责从后端获取数据并返回到域层(这里很常见使用存储库模式,这只是获取数据的合约。合约是唯一的,但您可以有多个实现;例如,一个用于 REST API,另一个用于 GraphQL API。您应该能够在不更改代码中其他部分的情况下更改实现。
让我们看几个用例示例,在这些用例中,将性能置于解耦之上是非常典型的。(剧透:我们可以两者兼而有之。

想象一下,您有一个返回产品列表的端点,其中一个字段是 .响应可以是这样的(我删除了其他字段以制作一个简单的示例):category_id

[
  { id: 1, name: "Product 1", category_id: 1 }, 
  { id: 2, name: "Product 2", category_id: 2 },
  ...
]

我们需要在前端显示类别名称(而不是 ID),因此我们需要调用另一个端点来获取类别名称。该终结点返回如下内容:

[
  { id: 1, name: "Mobile" }, 
  { id: 2, name: "TVs" },
  { id: 3, name: "Keyboards" },
  ...
]

您可以认为后端应该执行 join 并返回多合一请求,但这并不总是可行的。

我们可以在前端,在负责恢复产品的功能或方法中进行连接。我们可以同时执行请求并加入信息。例如:

async function getProductList(): Promise<Product[]> {
  const products = await fetchProducts()
  const categories = await fetchCategories()
  return products.map(product => {
    const category = categories.find(category => category.id === product.category_id);
    return { ...product, category_name: category.name }
  })
}

我们的应用程序不需要知道任何事情,我们需要 2 次调用来恢复信息,我们可以在前端使用没有任何问题。category_name

现在想象一下,您需要显示类别列表;例如,在下拉列表中。您可以重复使用该功能,因为它完全可以满足您的需求。fetchCategories

在您看来,代码是这样的:

<template>
    <dropdown :options="categories" />
    <product-list :products="products" />
</template>
<script lang="ts" setup>
import { fetchCategories, getProductList } from '@/repositories';

const categories = await fetchCategories();

const products = await getProductList();

</script>

.此时,您意识到您正在对同一端点进行 2 次调用以恢复相同的数据 - 您恢复的数据以组成产品列表 - 这在性能、网络负载、后端负载等方面都不好。

此时,你开始思考如何减少对后端的调用次数;在这种情况下,只是为了重用类别列表。您可能会尝试将调用移动到视图并联接产品和类别。

// ❌❌❌ Not nice solution 
<template>
    <dropdown :options="categories" />
    <product-list :products="products" />
</template>

<script lang="ts" setup>
import { fetchCategories, fetchProducts } from '@/repositories';

const categories = await fetchCategories();

const products =  await fetchProducts().map(product => {
  const category = categories.find(category => category.id === product.category_id);
  return { ...product, category_name: category.name };
});
</script>

这样,您解决了性能问题,但又增加了另一个大问题:基础架构、视图和域耦合。现在,您的视图知道基础结构(后端)中数据的形状,并且很难重用代码。我们可以深入研究这个问题,并做更糟糕的事情:如果你的头栏在另一个需要类别列表的组件中,会发生什么?您需要以全局的方式考虑应用程序。

想象一下更复杂的事情:您需要页眉、产品列表、筛选器和页脚中的类别。
在这里插入图片描述
使用前面的方法,你的应用层(Vue、React 等)需要考虑如何获取数据以尽量减少请求。这并不好,因为应用程序层应该专注于视图,而不是基础设施。

使用全球商店
这个问题的一个解决方案是使用全局存储(Vuex、Pinia、Redux 等)来委托请求,并在视图中使用存储。存储只应加载尚未加载的数据,视图不应关心数据的加载方式。这听起来像是缓存,对吧? 我们解决了性能问题,但我们仍然将基础结构和视图耦合在一起。

Infra Cache 来拯救
为了尽可能地解耦基础设施和视图,我们应该将缓存移动到基础设施层(负责从后端获取数据的层)。通过这样做,我们可以随时调用 infra 方法,只对后端执行一个请求;但重要的概念是,域、应用程序和视图对缓存、网络速度、请求数量等一无所知。

基础设施层只是一个通过合约获取数据的层(如何请求数据以及如何返回数据)。遵循解耦原则,我们应该能够在不改变域、应用程序或视图层的情况下改变基础设施的实现。例如,我们可以将使用 REST 的后端替换为使用 GraphQL 的后端,并且无需执行 2 个请求即可获得具有类别名称的产品。但同样,这是基础设施层应该关心的事情,而不是视图。

您可以遵循不同的策略来实现基础设施层中的缓存:HTTP 缓存(代理或浏览器内部缓存),但在这些情况下,为了更好地灵活地使前端的缓存失效,我们的应用程序(再次是基础设施层)最好管理缓存。

如果您使用的是 Axios,则可以使用 Axios Cache Interceptor 来管理基础设施层中的缓存。这个库使缓存变得非常简单:

// Example from axios cache interceptor page
import Axios from 'axios';
import { setupCache } from 'axios-cache-interceptor';

// same object, but with updated typings.
const axios = setupCache(Axios); 

const req1 = axios.get('https://api.example.com/'); 
const req2 = axios.get('https://api.example.com/'); 

const [res1, res2] = await Promise.all([req1, req2]);

res1.cached; // false 
res2.cached; // true

您只需要使用缓存拦截器包装实例,剩下的工作将由库处理。axios

TTL的
TTL 是缓存的有效时间。在此之后,缓存将失效,下一个请求将向后端完成。TTL 是一个非常重要的概念,因为它定义了数据的新鲜程度。

缓存数据时,一个具有挑战性的问题是数据不一致。在我们的示例中,我们可以想到一个购物车。如果它被缓存并且用户添加了新产品,并且你的应用请求获取购物车的更新版本,它将获取缓存的版本,并且用户将看不到新产品。有一些策略可以使缓存失效并解决此问题,但这超出了本文的范围。但是,您需要知道这不是一个微不足道的问题:不同的用例需要不同的策略。

TTL 越长,数据不一致问题就越大,因为在这段时间内可能会发生更多事件。

但是对于我们正在寻找的目标(允许轻松解耦代码),非常低的 TTL(例如,10 秒)足以消除数据不一致问题。

为什么低 TTL 就足够了?
想想用户如何与应用程序交互:

用户将要求提供 URL(它可以是 SPA 或 SSR 页面的一部分)。
应用程序将创建页面布局,挂载独立的组件:页眉、页脚、过滤器和内容(示例中的产品列表)。
每个组件都要求提供它需要执行的数据。
应用程序将呈现包含恢复数据的页面,并将其发送到浏览器 (SSR) 或在 DOM (SPA) 中注入/更新它。
在这里插入图片描述
所有这些过程在每次页面更改中都会重复(可能在 SPA 中部分重复),最重要的是:它会在短时间内(可能是毫秒)执行。因此,在低 TTL 下,我们可以非常确定我们只会对后端发出请求,并且我们不会遇到数据不一致问题,因为在下一页更改或用户交互中缓存过期,我们将获得新数据。

  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小徐博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值