模块化的请求数据统一管理的构想

在一个复杂的业务场景下,一个页面中被拆分多个组件,或者是多个组件共同组成一个独立的模块而不受到其他模块的影响。如下图所示。

上图展现的典型页面中,有三个模块,侧栏的 asset list,中间的 asset detail,右边的 related asset。每个模块的数据独立性都由一个 Query 托管。这里以流行的 React Query 为例。

但是我们发现,这三个模块虽然是独立但是数据却是相通的。如下所示。

而用现在的方式去管理每个模块的数据好像也没有不妥,毕竟目前不需要关心数据重复带来的问题。能在数据产生之后渲染正确的 UI 这是静态页面最简单的方法。

现在新的需求的加入,需要对数据实时的修改做乐观更新。也就是,当后端数据的更新,触发前端对应数据也需要更新。

这个时候,使用 React Query 的场景下,分别对三个模块的 Query 做相应数据的乐观更新了。

现在业务场景再升级。在每个 asset 中加入了 user 的属性,然后 user 也需要实时变化反映在 UI。由于 user 的加入,现在 query 1 中 asset 和 user 数据是耦合的,一个 user 会对应多个 asset,那么现在的直接嵌入的 user 的数据必定是冗余的。那么在更新的数据时候,我应该怎么去更新,我可能要全部遍历一遍去判断要不要去更新。这样的数据结构对于后端来说,是最简单的,毕竟 user 作为引用数据,后端在数据库查询只要两个 select 或者直接连表就行了。而对于实时数据驱动 UI 的前端来说,这样的数据在对乐观更新或者数据的局部更新确实噩梦。而下边的图中的业务场景也只是三个模块而已,在实际的业务中远不止下图的复杂度。

那么,在实际的场景下,有没有办法去解决这一个问题。

首先,需要后端和前端达成共识,将冗余数据消除,换句话说,后端不能把数据库连表产生的数据直接扔给前端。而是在请求中设立一个对象专门托管这些引用数据。

比如下面的数据:

json 复制代码
{
  "data": [
    {
      "user": {
        "id": "1234axz",
        "name": "John Doe"
      },
      "id": "ax4axz",
      "name": "Fake"
    },
    {
      "user": {
        "id": "1234axq",
        "name": "John Doe"
      },
      "id": "ax4axc",
      "name": "Fake 2"
    }
  ]
}

转成为例如这样的数据:

json 复制代码
{
  "data": [
    {
      "userId": "1234axz",
      "id": "ax4axz",
      "name": "Fake"
    },
    {
      "userId": "1234axz",
      "id": "ax4axc",
      "name": "Fake 2"
    }
  ],
  "objects": {
    "users": {
      "1234axz": {
        "id": "1234axz",
        "name": "John Doe"
      }
    }
  }
}

这样就消除了多余数据,并且数据的分层明确,在 data 中的数据是我们查询的而其中引用的其他数据放置在 objects 中,objects 后续可以扩展出其他的数据类型。

前端需要对各个模块的数据请求之后,在一个集中式的数据层进行管理。我们可以简单的用 zustand 或者 jotai 建立每个数据对象的表管理。这里简单的代码示例如下。

tsx 复制代码
import { useShallow } from 'zustand/react/shallow'
import { create } from 'zustand'

type Id = string
export type CollectionType<T extends { id: string }> = Record<Id, T>

const useAssetStore = create<{
  data: CollectionType<AssetModel>
}>(() => {
  return { data: {} }
})

export const {
  add: addDrips,
  patch: patchDrip,
  remove: deleteDrip,
  get: getDrip,
  getAll: getAllDrips,
} = baseCollectionMethodsFactory(useAssetStore) // baseCollectionMethodsFactory 作为你的 collection 方法扩展

export const useAssetSelector = <Value>(
  id: string,
  selector: (data?: AssetModel) => Value,
) => {
  return useAssetStore(
    useShallow(({ data }) => {
      const drip = data[id]
      return selector(drip)
    }),
  )
}

经过这样的处理之后,现在的页面上的数据变化如下:

asset updated event 事件触发之后,我们不再需要去关心散布各个模块中的托管的 Query,找到 Query 再去做更新,而是直接对 data collection 中的数据操作,这样既不会疏漏,而且借助 zustand/jotai 的能力可以更加细粒度的渲染 UI。

更好的阅读体验原文出处:innei.in/posts/progr...

相关推荐
herogus丶4 分钟前
【Chrome】‘Good助手‘ 扩展程序使用介绍
前端·chrome
独立开阀者_FwtCoder7 分钟前
面试官:为什么在 Vue3 中 ref 变量要用 .value?
前端·javascript·vue.js
NetX行者10 分钟前
基于Vue 3的AI前端框架汇总及工具对比表
前端·vue.js·人工智能·前端框架·开源
独立开阀者_FwtCoder11 分钟前
手握两大前端框架,Vercel 再出手拿下 Nuxt.js,对前端有什么影响?
前端·javascript·vue.js
独立开阀者_FwtCoder11 分钟前
弃用 html2canvas!快 93 倍的截图神器!
前端·javascript·vue.js
weixin_3993806926 分钟前
TongWeb8.0.9.0.3部署后端应用,前端访问后端报405(by sy+lqw)
前端
伍哥的传说1 小时前
H3初识——入门介绍之常用中间件
前端·javascript·react.js·中间件·前端框架·node.js·ecmascript
洛小豆1 小时前
深入理解Pinia:Options API vs Composition API两种Store定义方式完全指南
前端·javascript·vue.js
洛小豆1 小时前
JavaScript 对象属性访问的那些坑:她问我为什么用 result.id 而不是 result['id']?我说我不知道...
前端·javascript·vue.js