graphql-state: 全新的react状态管理框架

我创建了全一个新的react状态管理框架https://github.com/babyfish-ct/graphql-state

这篇文章不打算介绍框架本身的细节,因为框架提供了入门例子、详细的文档、全面的示例、甚至生动的GIF动画,这里不做简单的重复

本文中,我们讨论为什么要创建这个新的框架,究竟是重复发明轮子,还是有其独到见解和非凡的价值。

如何在react中消费GraphQL服务,可以分为四个层次

1. 第一个层次,提供API和服务端交互。


这个层次的框架以https://github.com/prisma-labs/graphql-request为代表。

这类框架是最基本的最简单的GraphQL客户端,可以使用其API和服务端交互,但没有任何缓存相关的服务。

当你的应用很简单的时候,最小化的功能可以带来最简单的学习难度和使用成本。项目很简单的情况下,每个页面都是数据量很小的信息孤岛,你完全可以通过频繁刷新请求来应对缓存的缺失。

然而,当你遇到稍微复杂度点的应用,多种不同的数据同时呈现在相对复杂的界面上,而且它们之间还存在彼此关联和依赖,你会开始力不从心,因为你不可能置性能于不顾,频繁地刷新所有数据。你迫切需要缓存。

2. 第二个层次,简单的key-value缓存


这个层次的框架以https://github.com/tannerlinsley/react-query为代表。

诚然,react-query在可配置性方面做得非常强大;但是是最大的问题是,缓存是简单的key/value缓存。而事实上,真正的数据模型是图结构的,不同的对象之间会有相互关联。例如

BookStore <--1:n--> Book <--m:n--> Author

按照例子中的关联方式,简单的key/value缓存会轻易导致如下两条数据

缓存数据1:

{
  __typename: "BookStore",
  id: 1,
  name: "O'REILLY",
  books: [
     {
         __typename: "Book",
         id: 2,
         name: "Learning GraphQL",
     },
     {
         __typename: "Book",
         id: 3,
         name: "Effective TypeScript"
     }
  ]
}

缓存数据2:

{
  __typename: "Author",
  id: 9,
  name: "Alex Banks",
  books: [
     {
         __typename: "Book",
         id: 2,
         name: "Learning GraphQL",
     }
  ]
}

在上面的数据中,名称为"Learning GraphQL"的书籍在两个缓存数据中都存在,这种冗余,会在后续数据变更中导致数据不一致问题。

随着对象之间的关系越来越复杂,你会发现要消除冗余带来的副作用越来越困难。你迫切需要将缓存中的数据进行范式化处理,就如同你在关系型数据库中所做的那样。

3. 第三个层次,normalized-cache

这个层次的框架以Apollo Client和Relay为代表。

nomalized-cache就像关系型数据一样存储数据行以及它们之间的关系,高度范式化的数据是没有冗余的。当然,这种内部关系型数据需要和用户使用的层次化数据进行彼此转换,幸运的是,框架很容易将这种转换自动化黑盒化,用户感知不到内部关系型数据的存在。

现在,用户不用担心缓存存在数据冗余了。但是还有一种严重的问题,就是修改操作会非常复杂,需要开发人员维护缓存的一致性。

比如,现有缓存内部的数据如下

{
    "Query": {
        findBooks({"name": "e"}): [{ref: "Book:2"}, {ref: "Book:3"}],
        findBooks({"name": "g"}): [{ref: "Book:2"}]
    },
    "Book:2": {
        id: 2,
        name: "Learning GraphQL"
    },
    "Book:3": {
        id: 3,
        name: "Effective TypeScript"
    }
}

其中, findBooks({"name": ...})表示查询条件,对书的名称进行模糊匹配筛选。

现在,修改数据,把{id:2, name: "Learning GraphQL"}修改为{id:2, name: "Learning TypeScript"}。修改后,新的名称"Learning TypeScript"不再和查询条件{name: "g"}匹配。所以,新的缓存看起来应该如此

{
    "Query": {
        findBooks({"name": "e"}): [{ref: "Book:3"}, {ref: "Book:3"}],
        findBooks({"name": "g"}): [] // 额外修改:旧的数据引用需要消失
    },
    "Book:2": {
        id: 2,
        name: "Learning TypeScript" // 主修改:开发人员的原始意图
    },
    "Book:3": {
        id: 3,
        name: "Effective TypeScript"
    }
}

上面的"主修改"很简单,这本身就是开发人员的意图。但是,"额外修改"就很麻烦了,是缓存中其它已有数据为了适应新的数据修改,而不得不做出的变化

这样的"额外修改"的数量,受缓存中现有数据的多少和数据结构复杂度的影响。从理论层面讲,复杂度无法限制,一个主修改可以会导致无数个额外修改。

要让缓存完成上面的"额外修改",无外乎两种办法。

  1. 手动更改本地缓存,这是性能优越但不一定可行的方法
  2. 让查询Query.findBooks({name: "g"})重新从服务端获取最新数据。这是性能不好但一定可行的方法

对Apollo Client而言:

  1. 修改缓存:https://www.apollographql.com/docs/react/data/mutations/#updating-the-cache-directly
  2. 重新查询:https://www.apollographql.com/docs/react/data/mutations/#refetching-queries

而Relay更主张直接修改缓存:https://relay.dev/docs/guided-tour/updating-data/graphql-mutations/#updater-functions

上文阐述过,这种"额外修改"的复杂度,是无法限制的。可以预见的是,如果UI模块越多,模块内部的数据类型越丰富,数据类型之间的关系越复杂,那么,保证缓存一致性就越困难。

  1. 如果你选择直接修改缓存,你需要编写的代码的逻辑会越来越复杂。
  2. 如果你选择让某些查询重新获取数据,判断哪些查询会被当前修改操作影响而需要重新获取,同样是一件困难的事。

总之,如果你使用了Apollo Client或Relay,你会发现你面对的痛点变成了缓存一致性维护,痛的程度和UI的复杂度正相关。

4. 第四个层次,自动保证缓存的一致性


针对Apollo Client和Relay的问题,我开发了这个全新的状态管理框架。其中最重要的一个功能就是消费GraphQL服务,该框架能够在修改后自动保证缓存的一致性,优先采用直接修改缓存的方式,如果不行就升级为重新查询的方式。无论框架如何抉择,这一切都是自动化的。

具体如何实现这个完美而强大的目标,请移步项目主页。

附:

既然号称react状态管理框架,仅仅支持GraphQL吗?REST呢?
在即将发布的下个版本中,框架基于REST模拟GraphQL,即便针对REST服务端,也能在客户端抽象成GraphQL,享受其强大的语意义和便利性。这个特性叫“GraphQL style, but not GraphQL only”,尽请期待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React Router 是一个用于在 React 应用中实现路由功能的库。它提供了一种声明式的方式来定义和管理应用程序的路由,使得页面之间的导航和状态管理更加方便和灵活。React Router 的原理可以概括为以下几个关键概念和步骤: 1. **路由器(Router):** React Router 提供了多种类型的路由器组件,如 `BrowserRouter`、`HashRouter` 等。路由器组件负责监听 URL 的变化,并将相应的路由信息传递给应用程序。 2. **路由规则(Route):** 使用 `Route` 组件来定义路由规则。每个 `Route` 组件负责匹配 URL,并在匹配成功时渲染对应的组件。可以通过 `path` 属性来指定匹配的路径,通过 `component` 属性来指定要渲染的组件。 3. **导航(Navigation):** React Router 提供了多种导航组件来实现页面之间的跳转,如 `Link`、`NavLink` 等。这些导航组件会生成对应的 `<a>` 标签,并处理点击事件来触发路由的变化。 4. **路由参数(Route Parameters):** 可以通过在路由规则中使用冒号(`:`)来定义动态的路由参数,如 `/users/:id`。在匹配成功后,可以通过 `props.match.params` 来获取路由参数的值。 5. **嵌套路由(Nested Routes):** React Router 支持嵌套路由,即在一个组件内部定义子组件的路由规则。可以通过嵌套的 `Route` 组件来实现。 6. **路由守卫(Route Guards):** React Router 提供了一些钩子函数,如 `beforeEnter`、`beforeLeave` 等,用于实现路由守卫功能。可以在路由跳转前或跳转后执行一些逻辑操作,例如验证用户权限、处理登录状态等。 总的来说,React Router 的原理是通过路由器监听 URL 的变化,根据定义的路由规则匹配对应的组件进行渲染,同时提供导航组件来实现页面之间的跳转。这样可以实现单页面应用(SPA)的路由功能,使得页面的切换和状态管理更加灵活和可控。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值