useSyncExternalStore 的应用
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:修能
学而不思则罔,思而不学则殆 。 --- 《论语·为政》
What
useSyncExternalStore
is a React Hook that lets you subscribe to an external store.
useSyncExternalStore
是一个支持让用户订阅外部存储的 Hook。官方文档
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Why
首先,我们这里基于 molecule1.x 的版本抽象了一个简易版的 mini-molecule。
import { EventBus } from "../utils";
type Item = { key: string };
// 声明一个事件订阅
const eventBus = new EventBus();
// 声明模块数据类型
class Model {
constructor(public data: Item[] = [], public current?: string) {}
}
export class Service {
protected state: Model;
constructor() {
this.state = new Model();
}
setState(nextState: Partial<Model>) {
this.state = { ...this.state, ...nextState };
this.render(this.state);
}
private render(state: Model) {
eventBus.emit("render", state);
}
}
export default function Home() {
const state = useExternal();
if (!state) return <div>loading...</div>;
return (
<>
<strong>{state.current || "empty"}</strong>
<ul>
{state.data.map((i) => (
<li key={i.key}>{i.key}</li>
))}
</ul>
</>
);
}
const service = new Service();
function useExternal() {
const [state, setState] = useState<Model | undefined>(undefined);
useEffect(() => {
setState(service.getState());
service.onUpdateState((next) => {
setState(next);
});
}, []);
return state;
}
如上面代码所示,已经实现了从外部存储获取相关数据,并且监听外部数据的更新,并触发函数组件的更新。
接下来实现更新外部数据的操作。
export default function Home() {
const state = useExternal();
if (!state) return <div>loading...</div>;
return (
<>
<ul>
{state.data.map((i) => (
<li key={i.key}>{i.key}</li>
))}
</ul>
+ <button onClick={() => service.insert(`${new Date().valueOf()}`)}>
+ add list
+ </button>
</>
);
}
其实要做的比较简单,就是增加了一个触发的按钮去修改数据即可。
上述这种比较简单的场景下所支持的 useExternal 写起来也是比较简单的。当你的场景越发复杂,你所需要考虑的就越多。就会导致项目的复杂度越来越高。而此时,如果有一个官方出品,有 React 团队做背书的 API 则会舒服很多。
以下是 useSyncExternlaStore 的 shim 版本相关代码:
function useSyncExternalStore(subscribe, getSnapshot, // Note: The shim does not use getServerSnapshot, because pre-18 versions of
// React do not expose a way to check if we're hydrating. So users of the shim
// will need to track that themselves and return the correct value
// from `getSnapshot`.
getServerSnapshot) {
{
if (!didWarnOld18Alpha) {
if (React.startTransition !== undefined) {
didWarnOld18Alpha = true;
error('You are using an outdated, pre-release alpha of React 18 that ' + 'does not support useSyncExternalStore. The ' + 'use-sync-external-store shim will not work correctly. Upgrade ' + 'to a newer pre-release.');
}
}
} // Read the current snapshot from the store on every render. Again, this
// breaks the rules of React, and only works here because of specific
// implementation details, most importantly that updates are
// always synchronous.
var value = getSnapshot();
{
if (!didWarnUncachedGetSnapshot) {
var cachedValue = getSnapshot();
if (!objectIs(value, cachedValue)) {
error('The result of getSnapshot should be cached to avoid an infinite loop');
didWarnUncachedGetSnapshot = true;
}
}
} // Because updates are synchronous, we don't queue them. Instead we force a
// re-render whenever the subscribed state changes by updating an some
// arbitrary useState hook. Then, during render, we call getSnapshot to read
// the current value.
//
// Because we don't actually use the state returned by the useState hook, we
// can save a bit of memory by storing other stuff in that slot.
//
// To implement the early bailout, we need to track some things on a mutable
// object. Usually, we would put that in a useRef hook, but we can stash it in
// our useState hook instead.
//
// To force a re-render, we call forceUpdate({inst}). That works because the
// new object always fails an equality check.
var _useState = useState({
inst: {
value: value,
getSnapshot: getSnapshot
}
}),
inst = _useState[0].inst,
forceUpdate = _useState[1]; // Track the latest getSnapshot function with a ref. This needs to be updated
// in the layout phase so we can access it during the tearing check that
// happens on subscribe.
useLayoutEffect(function () {
inst.value = value;
inst.getSnapshot = getSnapshot; // Whenever getSnapshot or subscribe changes, we need to check in the
// commit phase if there was an interleaved mutation. In concurrent mode
// this can happen all the time, but even in synchronous mode, an earlier
// effect may have mutated the store.
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceUpdate({
inst: inst
});
}
}, [subscribe, value, getSnapshot]);
useEffect(function () {
// Check for changes right before subscribing. Subsequent changes will be
// detected in the subscription handler.
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceUpdate({
inst: inst
});
}
var handleStoreChange = function () {
// TODO: Because there is no cross-renderer API for batching updates, it's
// up to the consumer of this library to wrap their subscription event
// with unstable_batchedUpdates. Should we try to detect when this isn't
// the case and print a warning in development?
// The store changed. Check if the snapshot changed since the last time we
// read from the store.
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceUpdate({
inst: inst
});
}
}; // Subscribe to the store and return a clean-up function.
return subscribe(handleStoreChange);
}, [subscribe]);
useDebugValue(value);
return value;
}
How
针对上述例子进行改造
const service = new Service();
export default function Home() {
const state = useSyncExternalStore(
(cb) => () => service.onUpdateState(cb),
service.getState.bind(service)
);
if (!state) return <div>loading...</div>;
return (
<>
<ul>
{state.data.map((i) => (
<li key={i.key}>{i.key}</li>
))}
</ul>
<button onClick={() => service.insert(`${new Date().valueOf()}`)}>
add list
</button>
</>
);
}
在 Molecule 中使用
import { useContext, useMemo } from 'react';
import type { IMoleculeContext } from 'mo/types';
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { Context } from '../context';
type Selector = keyof IMoleculeContext;
type StateType<T extends keyof IMoleculeContext> = ReturnType<IMoleculeContext[T]['getState']>;
export default function useConnector<T extends Selector>(selector: T) {
const { molecule } = useContext(Context);
const target = useMemo(() => molecule[selector], [molecule]);
const subscribe = useMemo(() => {
return (notify: () => void) => {
target.onUpdateState(notify);
return () => target.removeOnUpdateState(notify);
};
}, []);
return useSyncExternalStore(subscribe, () => target.getState()) as StateType<T>;
}
最后
欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star
- 大数据分布式任务调度系统——Taier
- 轻量级的 Web IDE UI 框架——Molecule
- 针对大数据领域的 SQL Parser 项目——dt-sql-parser
- 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
- 一个速度更快、配置更灵活、使用更简单的模块打包器——ko
- 一个针对 antd 的组件测试工具库——ant-design-testing
useSyncExternalStore 的应用的更多相关文章
- 我的 React 最佳实践
There are a thousand Hamlets in a thousand people's eyes. ----- 威廉·莎士比亚 免责声明:以下充满个人观点,辩证学习 React 目前开 ...
随机推荐
- WorldWind源码剖析系列:WorldWind瓦片调度策略说明
1 基于源码的分析 首先我们来看WorldWind中摄像头变化相关的几个方法的内部逻辑. 1.1 NltTerrainAccessor. GetElevationAt 方法声明:public over ...
- MySQL---索引-性能-配置参数优化
一般来说,要保证数据库的效率,要做好以下四个方面的工作:数 据库设计.sql语句优化.数据库参数配置.恰当的硬件资源和操作系统,这个顺序也表现了这四个工作对性能影响的大小.下面我们逐个阐明: 1.设计 ...
- 【运维必看】Linux命令之lsblk命令
一.命令简介 lsblk命令的英文是"list block",即用于列出所有可用块设备的信息,而且还能显示他们之间的依赖关系,但是它不会列出RAM盘的信息.块设备有硬盘,闪存盘,C ...
- 面向对象(下)的阶段性复习:关键字abstract、模板方法的设计模式、关键字interface、代理模式、工厂的设计模式、类的结构内部类
关键字:abstract abstract: 抽象的1.可以用来修饰:类.方法2.具体的:abstract修饰类:抽象类 * > 此类不能实例化 * > 抽象类中一定有构造器,便于子类实例 ...
- dart安装教程详解
官网 https://dart.dev 关于发布通道和版本字符串 Dart SDK有三个发布通道: 1==>:稳定释放,大约每三个月更新一次: 稳定释放适合生产使用. 2==>:预览发布, ...
- 【异步编程实战】如何实现超时功能(以CompletableFuture为例)
[异步编程实战]如何实现超时功能(以CompletableFuture为例) 由于网络波动或者连接节点下线等种种问题,对于大多数网络异步任务的执行通常会进行超时限制,在异步编程中是一个常见的问题.本文 ...
- IDEA中创建Spring Boot项目(SSM框架)
一.IDEA创建新Maven项目 创建maven项目完成 因为创建多模块项目,删除根目录src目录 二.maven多模块项目配置 需要创建的模块 umetric-web 控制层 umetric-we ...
- 在IDEA如何使用JProfiler性能分析
一.下载地址 https://www.ej-technologies.com/download/jprofiler/files 版本:11 激活码:L-J11-Everyone#speedzodiac ...
- 基于iscsi存储池
命令行 [root@kvm1 ~]# virsh pool-define-as --name stor2 --type iscsi \ > --source-host 192.168.114.1 ...
- DeepSeek提示词工程完全指南:如何用「思维翻译器」激发大模型潜能——附官方提示词和优化案例
DeepSeek提示词工程完全指南:如何用「思维翻译器」激发大模型潜能--附官方提示词和优化案例 字数:约3000字|预计阅读时间:8分钟 之前写了一篇DeepSeek-R1 技术全景解析:从原理到实 ...