当 React 首次出现时,其最引人注目的特性之一就是“单向数据流”。这仍然在 React 文档的 “React 思考” 页面中进行了概述。
层次结构顶部的组件会将你的数据模型作为 prop 接收。如果你更改了底层数据模型并再次调用
root.render()
,则 UI 将会更新。你可以看到 UI 如何更新以及在哪里进行更改。React 的单向数据流(也称为单向绑定)使一切保持模块化和快速。
其理念是,数据只能单向地流经你的应用程序,因此你的应用程序更容易直观理解和推断。
这被总结为“UI 是状态的函数”,或 ui = fn(state)
。每当某些状态因操作而改变时,视图就会重新渲染。迄今为止,已经创建了许多复杂的“状态管理”解决方案,以促进使用这种心理模型构建应用程序。
然而,这里很少被承认的问题是,这种“单向数据流”有点用词不当。它实际上是客户端上的单向数据流。但是,仅在客户端上拥有数据很少是实际的。大多数时候,你需要持久化数据——同步它——这意味着你需要数据双向流动:在客户端和服务器之间。
许多状态管理工具只帮助你管理客户端上的状态,但它们不能帮助你有效地跨越 网络鸿沟:客户端上的状态和服务器上的状态之间的差距。
进入 Remix:“Remix 的主要功能之一是简化与服务器的交互,以便将数据加载到组件中。”Remix 将数据流扩展到网络,使其真正成为单向循环:从服务器(状态)到客户端(视图),然后再返回服务器(操作)。
当说你的“UI 是状态的函数”时,一种更细致的方式来解开该语句中的假设是:UI 是你的远程状态和你的本地状态的函数。在传统的 React 应用程序中,所有状态都存在于客户端,你想要持久化的部分必须跳出“单向数据流”,并通过网络同步到服务器。你可以想象,这是一个容易出现 bug 的区域。
然而,在 Remix 中,“UI 作为状态的函数”的想法得到了转变,因为远程状态可以更容易地与本地状态分离。“有什么区别?”你问。这样想。
远程状态是任何需要持久化的数据,例如用户数据。此状态(例如,用户有多少未读通知?)存储在客户端之外,并通过 Remix 机制(如 loaders 和 actions)协调到你的应用程序中。
(注意:Remix 还通过 transitions 和 fetchers 提供围绕持久数据传输的状态信息,从而帮助你跨越网络鸿沟——无需你自己通过布尔值(如 isLoading
)或枚举(如 initial | loading | success | failed
)来跟踪每个网络请求的状态)。
相比之下,本地状态是临时数据,可以丢失(例如通过刷新),而不会对用户体验产生负面影响。此状态(例如,显示用户通知的下拉列表是否打开?)通过 React 状态或本地存储等机制存储在客户端上。重要的是,它不需要持久化并通过网络同步,从而降低了复杂性和潜在的 bug。
表单、fetchers、loaders、actions,这些都是 Remix 中的“状态管理”解决方案(尽管我们不这样称呼它们)。它们为你提供了保持客户端和服务器之间持久状态同步的工具,确保数据在你的应用程序中和网络中以单向循环的方式流动:从 loaders 到组件,再到 action,然后再回到 loaders。
使用 Remix,你的 UI 成为跨网络的状态函数,而不仅仅是本地函数。一个 有趣的类比是,Remix 的数据抽象是 React 的虚拟 DOM 抽象。
在 React 中,你无需自己更新 DOM。你设置状态,虚拟 DOM 会进行所有差异比较,以确定如何对 DOM 进行高效的更新。Remix 将这个想法扩展到 API 层,以实现持久化数据。
在 Remix 中,你无需担心客户端状态与服务器同步。你使用 mutation “设置状态”,loaders 会接管以重新获取最新数据并更新你的组件视图。
希望这有助于说明 Remix 如何大幅降低构建更好网站所需的复杂性。正如 Kent 在 他在 RenderATL 的演讲 中所说,由于 Remix 在 JavaScript 之前工作,这对你的用户来说是一个胜利,因为他们获得了渐进增强支持的体验。但这对你作为开发人员来说也是一个胜利,因为你不需要构建传统上与状态管理解决方案耦合的所有复杂性。
当你使用 Remix 时,你无需担心应用程序状态管理。Redux、Apollo,这些工具都很酷,但是当你使用 Remix 时,你不需要它们,因为我们甚至不需要客户端 JavaScript 就能使整个应用程序正常工作……[想想你正在构建的应用程序],并假设你可以丢弃所有与应用程序状态管理相关的代码……这就是你使用 Remix 时的情况。如果它可以在没有浏览器 JavaScript 的情况下工作,这意味着你不需要浏览器中任何需要状态管理的东西。