WebSocket 与页面数据刷新
上节说道页面数据以及全局数据的传递和使用。但是这些数据都是服务端获取的,之后在客户端水合之后就变成了静态的数据,那么如何能够让这些数据能够根据后端数据的变化而变化呢。
实时刷新前端数据和服务器数据同步是目前 SaaS 平台中参见的提升 UX 的手段。
这里我们需要借助 WebSocket 的能力。
在开始之前,我们首先需要梳理下数据流。前面提到的三种方法:
- 把 RSC 中获取到的数据通过 props 透传到子组件,后续子组件都通过消费 props 获取数据源。
- 把 RSC 中获取到的数据通过 context 方式传递,后续子组件都通过消费 context 获取数据源
- 使用
cache
函数缓存数据源,后续 RSC 都可以复用缓存的数据源。此方式仅限于 RSC。
前两种可以理解为同一种方法,透传 props 的方式在结构比较复杂的场景中并不适用,更倾向于使用 context 传递。
而最后一种方式,你必须全部使用 RSC 去编写所有需要使用数据的组件,对于这些需要根据 WS 事件驱动数据更新的场景,也不是很好用。使用这个方式不仅写法复杂,也会造成数据的重复获取和滞后。
Next.js RSC 中的数据刷新
既然提到了 RSC,那么来说一说在这种业务下的 Next.js RSC 的使用。
Next.js 的 RSC 中驱动数据的更新的方式可以使用 revalidateTags
或者 revalidatePath
。这里我们使用 revalidateTag
举例。
这个案例我们编写一个前端展示服务器时间的 Demo。利用 WebSocket 事件推送当前的服务器事件。在应用 SSR 时首先需要通过 API 获取当前的服务器时间,后续则通过 WS 事件。
Important此为 Demo,在正式生成环境中此方法显示服务器时间是错误的,你可能需要考虑 NTP 协议对时的算法。
简单编写一个 WSS。
每 1 秒向 WS Client 发送 update 事件,并且附加当前时间。
编写 WS 连接组件。
编写 revalidateTime
方法,需要在 WS 事件触发。
那么,我们可以看到这样的效果。当 WS 事件到达,浏览器就会重新请求 RSC payload 去刷新这个页面。
这样是无法直接消费 WS 事件的数据乐观更新页面数据,而是重新需要 RSC 造成了资源开销和数据更新滞后。
但是注意的是,上面的方法仅限于使用 Next.js 提供的 fetch
方法,在一些场景中你可以还是会选择其他的请求库,例如 axios 或者 XHR 等等,对于这些时候此方法并不适用,局限性比较大。
Important为了避免又要被某人喷,这里注明下。
revalidateTags
和revalidatePath
其实并不是为这个场景准备的,这里只是提供一个思路。这两个方法的使用场景基本都是在 Next.js 编写的 全栈应用 中的,在这些应用中不仅 API 和 DB 都在 Next.js 中管理,当 DB 数据变更就可以在调用
revalidateTags
或者revalidatePath
刷新数据。而在 WS 场景中使用此方法只能说投机取巧,因此我不建议使用,但是通过这个例子你可以了解这个方法的大致用法。
上面的 DEMO 位于: nextjs-book/tree/main/demo/ws
使用 Jotai 更新数据源粒度化组件重渲染
而这个场景,个人认为最好的方式就是通过 Context 方式去传递数据了,但是 Context 如果直接使用 React useState 托管的数据向下传递,那么在更新比较复杂的对象时,无法让使用到数据源的组件细粒度更新。所以这里选择 Jotai 作为 Store。你也可以使用其他状态库,如果依旧选择 Context 这里也提供建议使用 use-context-selector
我们需要的实现的数据流向大概为:
页面在服务端渲染的数据,注入到 DataProvider 中,然后内部实现把数据扔到 Jotai Atom 去管理。子组件需要使用数据源则直接消费 Context 获取 Atom,再用 Jotai 提供的 selector 方法提取需要用到的字段,以实现细粒度更新。
那么在服务端渲染时给到的数据,在注入到 DataProvider 之后,就不会再使用了。所有需要用到这个数据源的地方都应该从 Jotai 中取用。
现在我们来编写一个 Demo 说明一下这个事。
首先编写一个 DataProvider。
这个 Providers 把初始数据输入到 ModelDataProvider
中,然后创建一个全局 atom 去托管这个数据,对外暴露一个方法可以修改这个数据。向下,利用 Context 隔离的特点,可以复写 Context 链上的 Atom,在同一个页面上可以同时展示不同的数据源,一个为全局数据,其他的为 Context 复写的 atom 数据。这个特征会在实战中讲解,这里只需要先关注 setGlobalModelData
即可。
上面代码的封装可以在这个仓库中找到:
首先,创建 ModelDataContext 相关:
在 layout.tsx
中,初始化 WS,和 jotai Provider。获取数据源,输入到 ModelDataProvider
。
在 WS 中这样去更新数据:
数据渲染组件:
现在编写这个页面,模拟两个端,左侧模拟为 C 端,右侧为 CMS 管理平台,进行数据录入。
可以看到,WS 事件触发的数据更新。
上述代码位于:
codesandbox VM 会限流,建议拉代码到本地试试,另外貌似 codesandbox 不支持 ws 连接,还是建议本地执行。
下一节,讲讲请求那些事。
最后更新于 2024/7/26 16:15:59
本书还在编写中..
前往 https://innei.in/posts/tech/my-first-nextjs-book-here#comment 发表你的观点吧。