11import _ from 'lodash' ;
2- import { spy } from 'mobx' ;
2+ import { spy , untracked } from 'mobx' ;
33import { ROOT_CONTEXT_ID } from '@src/constants' ;
44import Manager from '../../manager' ;
55
66enum Listeners {
77 SPY = 'spy' ,
88}
99
10+ type SpyEvent = Parameters < Parameters < typeof spy > [ 0 ] > [ 0 ] ;
11+
1012/**
1113 * State listener
1214 */
@@ -22,6 +24,17 @@ class StateListener {
2224 */
2325 protected static listeners : Record < Listeners | string , ( ) => void > = { } as never ;
2426
27+ /**
28+ * Last mobx event
29+ * @private
30+ */
31+ private _lastEvent : SpyEvent | null = null ;
32+
33+ /**
34+ * @private
35+ */
36+ private readonly _throttleMs = 16 ;
37+
2538 /**
2639 * @constructor
2740 */
@@ -52,50 +65,102 @@ class StateListener {
5265 }
5366
5467 /**
55- * Get stores state
56- * @protected
68+ * Store snapshot WITHOUT creating mobx dependencies
5769 */
5870 protected getStoresState ( ) : { root : Record < string , any > } {
59- const state : { root : Record < string , any > } = { root : { } } ;
71+ return untracked ( ( ) => {
72+ const state : { root : Record < string , any > } = { root : { } } ;
6073
61- try {
62- const stores = this . manager . getStores ( ) ;
74+ try {
75+ const stores = this . manager . getStores ( ) ;
6376
64- this . manager . getStoresRelations ( ) . forEach ( ( { ids, componentName } , contextId ) => {
65- const key = this . getContextKey ( contextId ) ;
77+ this . manager . getStoresRelations ( ) . forEach ( ( { ids, componentName } , contextId ) => {
78+ const key = this . getContextKey ( contextId ) ;
6679
67- ids . forEach ( ( id ) => {
68- const store = stores . get ( id ) ;
80+ ids . forEach ( ( id ) => {
81+ const store = stores . get ( id ) ;
6982
70- if ( store ) {
71- const storeState = store ?. toJSON ?.( ) ?? Manager . getObservableProps ( store ) ;
83+ if ( store ) {
84+ const storeState = store ?. toJSON ?.( ) ?? Manager . getObservableProps ( store ) ;
7285
73- _ . set ( state , `${ key } .stores.${ id } ` , storeState ) ;
74- _ . set ( state , `${ key } .componentName` , componentName ) ;
75- }
86+ _ . set ( state , `${ key } .stores.${ id } ` , storeState ) ;
87+ _ . set ( state , `${ key } .componentName` , componentName ) ;
88+ }
89+ } ) ;
7690 } ) ;
77- } ) ;
78- } catch ( e ) {
79- // manager has not initialized yet
80- }
91+ } catch {
92+ // manager has not initialized yet
93+ }
8194
82- return state ;
95+ return state ;
96+ } ) ;
8397 }
8498
99+ /**
100+ * Safe clone
101+ * @private
102+ */
103+ private getSafeEvent ( event : SpyEvent ) : SpyEvent {
104+ const normalized = _ . mapValues ( event , ( v ) => {
105+ if ( v == null ) {
106+ return v ;
107+ }
108+
109+ const t = typeof v ;
110+
111+ if ( t === 'string' || t === 'number' || t === 'boolean' ) {
112+ return v ;
113+ }
114+
115+ if ( Array . isArray ( v ) ) {
116+ return { length : v . length } ;
117+ }
118+
119+ if ( t === 'function' || t === 'symbol' ) {
120+ return undefined ;
121+ }
122+
123+ if ( t === 'object' ) {
124+ return { objectType : v ?. constructor ?. name ?? 'Object' } ;
125+ }
126+
127+ return undefined ;
128+ } ) ;
129+
130+ return _ . pickBy ( normalized , ( v ) => v !== undefined ) as SpyEvent ;
131+ }
132+
133+ /**
134+ * Batch sending
135+ * @private
136+ */
137+ private emitChange = _ . throttle (
138+ ( ) => {
139+ const payload = {
140+ event : this . _lastEvent ,
141+ storesState : this . getStoresState ( ) ,
142+ } ;
143+
144+ this . manager ?. [ '__devOnChange' ] ?.( payload ) ;
145+ this . _lastEvent = null ;
146+ } ,
147+ this . _throttleMs ,
148+ { leading : false , trailing : true } ,
149+ ) ;
150+
85151 /**
86152 * Subscribe on stores changes
87153 * @protected
88154 */
89155 public subscribe ( ) : Manager {
90156 StateListener . listeners [ Listeners . SPY ] = spy ( ( event ) => {
91- if ( [ 'report-end' , 'reaction' ] . includes ( event . type ) ) {
157+ if ( [ 'report-end' , 'reaction' ] . includes ( event ? .type ) ) {
92158 return ;
93159 }
94160
95- this . manager ?. [ '__devOnChange' ] ?.( {
96- event : _ . cloneDeep ( event ) ,
97- storesState : this . getStoresState ( ) ,
98- } ) ;
161+ this . _lastEvent = this . getSafeEvent ( event ) ;
162+
163+ this . emitChange ( ) ;
99164 } ) ;
100165
101166 return this . manager ;
0 commit comments