1+ /*
2+ * Copyright (c) 2023 Huawei Technologies Co.,Ltd.
3+ *
4+ * openInula is licensed under Mulan PSL v2.
5+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
6+ * You may obtain a copy of Mulan PSL v2 at:
7+ *
8+ * http://license.coscl.org.cn/MulanPSL2
9+ *
10+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
11+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
12+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
13+ * See the Mulan PSL v2 for more details.
14+ */
15+
16+ import { isSame } from '../renderer/utils/compare' ;
17+ import {
18+ useState ,
19+ useRef ,
20+ useEffect ,
21+ useLayoutEffect ,
22+ useMemo
23+ } from '../renderer/hooks/HookExternal' ;
24+
25+ type StoreChangeListener = ( ) => void ;
26+ type Unsubscribe = ( ) => void ;
27+ type Store < T > = { value : T ; getSnapshot : ( ) => T }
28+
29+ export function useSyncExternalStore < T > (
30+ subscribe : ( onStoreChange : StoreChangeListener ) => Unsubscribe ,
31+ getSnapshot : ( ) => T ,
32+ ) : T {
33+ // 获取当前Store快照
34+ const value = getSnapshot ( ) ;
35+
36+ // 用于强制重新渲染和存储store引用,非普通state
37+ // 需要强制更新时调用 `forceUpdate({inst})`,由于是新的对象引入,会导致组件强制更新
38+ const [ { store } , forceUpdate ] = useState < { store : Store < T > } > ( {
39+ store : {
40+ value,
41+ getSnapshot
42+ }
43+ } ) ;
44+
45+ function reRenderIfStoreChange ( ) {
46+ if ( didSnapshotChange ( store ) ) {
47+ forceUpdate ( { store } ) ;
48+ }
49+ }
50+
51+ // 必须在 layout 阶段(绘制前)同步完成,以便后续的访问到正确的value。
52+ useLayoutEffect ( ( ) => {
53+ // 同步更新 快照值和 getSnapshot 函数引用,确保它们始终是最新的。
54+ // subscribe 更新,代表数据源变化,也需要更新
55+ store . value = value ;
56+ store . getSnapshot = getSnapshot ;
57+
58+ reRenderIfStoreChange ( ) ;
59+ } , [ subscribe , value , getSnapshot ] ) ;
60+
61+
62+ useEffect ( ( ) => {
63+ // useLayoutEffect和useEffect存在时机,store可能会变化
64+ reRenderIfStoreChange ( ) ;
65+
66+ // 开始订阅,返回值以在卸载组件是取消订阅
67+ return subscribe ( reRenderIfStoreChange ) ;
68+ } , [ subscribe ] ) ;
69+
70+
71+ return value ;
72+ }
73+
74+ /**
75+ * 检查快照是否发生变化
76+ * @param store Store实例
77+ * @returns 返回Store状态是否发生变化
78+ */
79+ function didSnapshotChange < T > ( store : Store < T > ) {
80+ const latestGetSnapshot = store . getSnapshot ;
81+ const prevValue = store . value ;
82+ try {
83+ const nextValue = latestGetSnapshot ( ) ;
84+ return ! isSame ( prevValue , nextValue ) ;
85+ } catch ( error ) {
86+ return true ;
87+ }
88+ }
89+
90+ // 用于追踪已渲染快照的实例类型
91+ type SelectionInstance < Selection > =
92+ | { hasValue : true ; value : Selection }
93+ | { hasValue : false ; value : null } ;
94+
95+ // 确保返回的类型既是原始类型 T,又确实是个函数
96+ function isFunction < T > ( value : T ) : value is T & ( ( ...args : any [ ] ) => any ) {
97+ return typeof value !== 'function' ;
98+ }
99+
100+ // 与useSyncExternalStore相同,但支持选择器和相等性判断参数
101+ export function useSyncExternalStoreWithSelector < Snapshot , Selection > (
102+ subscribe : ( onStoreChange : StoreChangeListener ) => Unsubscribe ,
103+ getSnapshot : ( ) => Snapshot ,
104+ getServerSnapshot : void | null | ( ( ) => Snapshot ) ,
105+ selector : ( snapshot : Snapshot ) => Selection ,
106+ isEqual ?: ( a : Selection , b : Selection ) => boolean ,
107+ ) : Selection {
108+ // 用于缓存已渲染的store快照值
109+ const instRef = useRef < SelectionInstance < Selection > > ( {
110+ hasValue : false ,
111+ value : null
112+ } ) ;
113+
114+ // 创建带选择器的快照获取函数
115+ const snapshotWithSelector = useMemo ( ( ) => {
116+ // 使用闭包变量追踪缓存状态,在 getSnapshot / selector / isEqual 不变时
117+ let initialized = false ;
118+ let cachedSnapshot : Snapshot ;
119+ let cachedSelection : Selection ;
120+
121+ const memoizedSelectorFn = ( snapshot : Snapshot ) => {
122+ // 首次调用时的处理
123+ if ( ! initialized ) {
124+ initialized = true ;
125+ cachedSnapshot = snapshot ;
126+ const selection = selector ( snapshot ) ;
127+
128+ // 尝试复用当前渲染值
129+ if ( isFunction ( isEqual ) && instRef . current . hasValue ) {
130+ const current = instRef . current . value ;
131+ if ( isEqual ( current , selection ) ) {
132+ cachedSelection = current ;
133+ return current ;
134+ }
135+ }
136+
137+ cachedSelection = selection ;
138+ return selection ;
139+ }
140+
141+ // 尝试复用之前的结果
142+ const previousSnapshot = cachedSnapshot ;
143+ const previousSelection = cachedSelection ;
144+
145+ // 快照未变化时直接返回之前的选择结果
146+ if ( isSame ( previousSnapshot , snapshot ) ) {
147+ return previousSelection ;
148+ }
149+
150+ // 快照已变化,需要计算新的选择结果
151+ const newSelection = selector ( snapshot ) ;
152+
153+ // 使用自定义相等判断函数检查数据是否真正发生变化
154+ if ( isFunction ( isEqual ) && isEqual ( previousSelection , newSelection ) ) {
155+ // 虽然选择结果相等,但仍需更新快照避免保留旧引用
156+ cachedSnapshot = snapshot ;
157+ return previousSelection ;
158+ }
159+
160+ // 更新缓存并返回新结果
161+ cachedSnapshot = snapshot ;
162+ cachedSelection = newSelection ;
163+ return newSelection ;
164+ } ;
165+
166+ return ( ) => memoizedSelectorFn ( getSnapshot ( ) ) ;
167+ } , [ getSnapshot , selector , isEqual ] ) ;
168+
169+ const selectedValue = useSyncExternalStore ( subscribe , snapshotWithSelector ) ;
170+
171+ // 更新实例状态
172+ useEffect ( ( ) => {
173+ instRef . current . hasValue = true ;
174+ instRef . current . value = selectedValue ;
175+ } , [ selectedValue ] ) ;
176+
177+ return selectedValue ;
178+ }
0 commit comments