@tramvai/state 2.70.0 → 2.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/connect/Provider.es.js +8 -0
- package/lib/connect/Provider.js +12 -0
- package/lib/connect/Subscription.es.js +37 -0
- package/lib/connect/Subscription.js +41 -0
- package/lib/connect/connectAdvanced.es.js +159 -0
- package/lib/connect/connectAdvanced.js +170 -0
- package/lib/connect/context.es.js +6 -0
- package/lib/connect/context.js +11 -0
- package/lib/connect/hooks/useActions.es.js +17 -0
- package/lib/connect/hooks/useActions.js +25 -0
- package/lib/connect/hooks/useConsumerContext.es.js +12 -0
- package/lib/connect/hooks/useConsumerContext.js +16 -0
- package/lib/connect/hooks/useSelector.es.js +53 -0
- package/lib/connect/hooks/useSelector.js +63 -0
- package/lib/connect/hooks/useStore.es.js +37 -0
- package/lib/connect/hooks/useStore.js +41 -0
- package/lib/connect/hooks/useStoreSelector.es.js +11 -0
- package/lib/connect/hooks/useStoreSelector.js +15 -0
- package/lib/connect/index.es.js +91 -0
- package/lib/connect/index.js +107 -0
- package/lib/connect/scheduling.es.js +12 -0
- package/lib/connect/scheduling.js +16 -0
- package/lib/connect/selectorFactory.es.js +95 -0
- package/lib/connect/selectorFactory.js +101 -0
- package/lib/connect/toProps/mapContextToProps.es.js +23 -0
- package/lib/connect/toProps/mapContextToProps.js +34 -0
- package/lib/connect/toProps/mapStateToProps.es.js +17 -0
- package/lib/connect/toProps/mapStateToProps.js +27 -0
- package/lib/connect/toProps/mergeProps.es.js +36 -0
- package/lib/connect/toProps/mergeProps.js +42 -0
- package/lib/connect/toProps/wrapMapToProps.es.js +73 -0
- package/lib/connect/toProps/wrapMapToProps.js +85 -0
- package/lib/connect/utils/verifyFunction.es.js +10 -0
- package/lib/connect/utils/verifyFunction.js +18 -0
- package/lib/connect/utils/verifyMapToProps.es.js +20 -0
- package/lib/connect/utils/verifyMapToProps.js +28 -0
- package/lib/connect/utils/verifyPlainObject.es.js +10 -0
- package/lib/connect/utils/verifyPlainObject.js +18 -0
- package/lib/createEvent/createEvent.es.js +22 -0
- package/lib/createEvent/createEvent.js +31 -0
- package/lib/createReducer/createReducer.es.js +60 -0
- package/lib/createReducer/createReducer.js +64 -0
- package/lib/devTools/constants.es.js +3 -0
- package/lib/devTools/constants.js +7 -0
- package/lib/{index_middleware.es.js → devTools/devTools.es.js} +2 -36
- package/lib/{index_middleware.js → devTools/devTools.js} +4 -40
- package/lib/devTools/index.es.js +21 -0
- package/lib/devTools/index.js +23 -0
- package/lib/devTools/middleware.es.js +37 -0
- package/lib/devTools/middleware.js +45 -0
- package/lib/dispatcher/childDispatcherContext.es.js +46 -0
- package/lib/dispatcher/childDispatcherContext.js +50 -0
- package/lib/dispatcher/dispatcher.es.js +105 -0
- package/lib/dispatcher/dispatcher.js +110 -0
- package/lib/dispatcher/dispatcherContext.es.js +279 -0
- package/lib/dispatcher/dispatcherContext.js +288 -0
- package/lib/dispatcher/storeSubscribe.es.js +8 -0
- package/lib/dispatcher/storeSubscribe.js +12 -0
- package/lib/index.es.js +16 -1246
- package/lib/index.js +36 -1281
- package/lib/logger.es.js +3 -0
- package/lib/logger.js +7 -0
- package/lib/stores/BaseStore.es.js +53 -0
- package/lib/stores/BaseStore.js +61 -0
- package/lib/stores/SimpleEmitter.es.js +30 -0
- package/lib/stores/SimpleEmitter.js +34 -0
- package/package.json +6 -7
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import compose from '@tinkoff/utils/function/compose';
|
|
2
|
+
import { subscribe } from './storeSubscribe.es.js';
|
|
3
|
+
import { SimpleEmitter } from '../stores/SimpleEmitter.es.js';
|
|
4
|
+
|
|
5
|
+
const eventExecutor = (event, handlerFns) => {
|
|
6
|
+
const keys = Object.keys(handlerFns);
|
|
7
|
+
for (let index = 0; index < keys.length; index++) {
|
|
8
|
+
const storeName = keys[index];
|
|
9
|
+
const handlerFn = handlerFns[storeName];
|
|
10
|
+
if (!handlerFn) {
|
|
11
|
+
throw new Error(`${storeName} does not have a handler for action ${event.type}`);
|
|
12
|
+
}
|
|
13
|
+
handlerFn(event.payload, event.type);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
// Конвертация старого типа экшенов в новый
|
|
17
|
+
const convertAction = (actionOrNameEvent, payload) => {
|
|
18
|
+
if (typeof actionOrNameEvent === 'string') {
|
|
19
|
+
return {
|
|
20
|
+
payload,
|
|
21
|
+
type: actionOrNameEvent,
|
|
22
|
+
error: false,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return actionOrNameEvent;
|
|
26
|
+
};
|
|
27
|
+
// Это форкнутый вариант dispatchr
|
|
28
|
+
class DispatcherContext extends SimpleEmitter {
|
|
29
|
+
/**
|
|
30
|
+
* @param context The context to be used for store instances
|
|
31
|
+
*/
|
|
32
|
+
constructor(dispatcher, context, initialState, middlewares) {
|
|
33
|
+
super();
|
|
34
|
+
this.applyDispatch = (event) => {
|
|
35
|
+
const eventHandlers = this.dispatcher.handlers[event.type] || [];
|
|
36
|
+
if (!eventHandlers.length) {
|
|
37
|
+
if (process.env.NODE_ENV === 'development') {
|
|
38
|
+
console.warn(`
|
|
39
|
+
The event "${event.type}" has been dispatched, but no reducers for this event were registered.
|
|
40
|
+
Have you forgot to register reducer or add event handler in existing reducer?
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
return event.payload;
|
|
44
|
+
}
|
|
45
|
+
this.applyHandlers(event, eventHandlers);
|
|
46
|
+
return event.payload;
|
|
47
|
+
};
|
|
48
|
+
this.dispatcher = dispatcher;
|
|
49
|
+
this.storeInstances = {};
|
|
50
|
+
this.storeUnsubscribeCallbacks = {};
|
|
51
|
+
this.context = context;
|
|
52
|
+
this.dispatcherInterface = {
|
|
53
|
+
getContext: () => this.context,
|
|
54
|
+
getStore: this.getStore.bind(this),
|
|
55
|
+
};
|
|
56
|
+
this.rehydratedStoreState = {};
|
|
57
|
+
this.fullState = {};
|
|
58
|
+
// Заполняем стейт данными
|
|
59
|
+
if (initialState) {
|
|
60
|
+
this.rehydrate(initialState);
|
|
61
|
+
}
|
|
62
|
+
if (middlewares === null || middlewares === void 0 ? void 0 : middlewares.length) {
|
|
63
|
+
this.applyDispatch = this.applyMiddlewares(middlewares);
|
|
64
|
+
}
|
|
65
|
+
// Инцииализируем уже имеющиеся сторы
|
|
66
|
+
Object.keys(this.dispatcher.stores).forEach((store) => {
|
|
67
|
+
this.getStore(store);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
applyMiddlewares(middlewares) {
|
|
71
|
+
let dispatch = (...args) => {
|
|
72
|
+
throw new Error('Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.');
|
|
73
|
+
};
|
|
74
|
+
const api = {
|
|
75
|
+
getState: this.getState.bind(this),
|
|
76
|
+
subscribe: this.subscribe.bind(this),
|
|
77
|
+
dispatch: (...args) => dispatch(...args),
|
|
78
|
+
};
|
|
79
|
+
dispatch = compose(...middlewares.map((middleware) => middleware(api)))(this.applyDispatch);
|
|
80
|
+
return dispatch;
|
|
81
|
+
}
|
|
82
|
+
storeSubscribe(storeName, storeInstance) {
|
|
83
|
+
const subscribeHandler = () => {
|
|
84
|
+
const newState = storeInstance.getState();
|
|
85
|
+
if (newState !== this.fullState[storeName]) {
|
|
86
|
+
this.fullState = {
|
|
87
|
+
...this.fullState,
|
|
88
|
+
[storeName]: newState,
|
|
89
|
+
};
|
|
90
|
+
this.emit('change');
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
subscribe(storeInstance, subscribeHandler);
|
|
94
|
+
const unsubscribe = () => {
|
|
95
|
+
storeInstance.off('change', subscribeHandler);
|
|
96
|
+
};
|
|
97
|
+
this.storeUnsubscribeCallbacks[storeName] = unsubscribe;
|
|
98
|
+
}
|
|
99
|
+
// eslint-disable-next-line max-statements
|
|
100
|
+
getStore(storeClass) {
|
|
101
|
+
let storeClassPrepared;
|
|
102
|
+
let optional = false;
|
|
103
|
+
if (typeof storeClass === 'object') {
|
|
104
|
+
storeClassPrepared = storeClass.store;
|
|
105
|
+
optional = storeClass.optional;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
storeClassPrepared = storeClass;
|
|
109
|
+
}
|
|
110
|
+
const storeName = this.dispatcher.getStoreName(storeClassPrepared);
|
|
111
|
+
if (!this.storeInstances[storeName]) {
|
|
112
|
+
let Store = this.dispatcher.stores[storeName];
|
|
113
|
+
if (!Store) {
|
|
114
|
+
if (typeof storeClassPrepared === 'function') {
|
|
115
|
+
this.dispatcher.registerStore(storeClassPrepared);
|
|
116
|
+
Store = storeClassPrepared;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
if (optional) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
throw new Error(`Store ${storeName} was not registered.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const storeInstance = new Store(this.dispatcherInterface);
|
|
126
|
+
this.storeInstances[storeName] = storeInstance;
|
|
127
|
+
if (this.rehydratedStoreState && this.rehydratedStoreState[storeName]) {
|
|
128
|
+
const state = this.rehydratedStoreState[storeName];
|
|
129
|
+
if (storeInstance.rehydrate) {
|
|
130
|
+
storeInstance.rehydrate(state);
|
|
131
|
+
}
|
|
132
|
+
this.rehydratedStoreState[storeName] = null;
|
|
133
|
+
}
|
|
134
|
+
this.storeSubscribe(storeName, storeInstance);
|
|
135
|
+
// TODO: убрать после того отпадёт надобность связывать сторы router и application
|
|
136
|
+
if (Store.dependencies) {
|
|
137
|
+
Store.dependencies.forEach((dependencyStoreClass) => {
|
|
138
|
+
const dependency = this.getStore(dependencyStoreClass);
|
|
139
|
+
subscribe(dependency, () => {
|
|
140
|
+
storeInstance.emit('change');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return this.storeInstances[storeName];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Dispatches a new action or throws if one is already in progress
|
|
149
|
+
* @param eventOrType Name of the event to be dispatched
|
|
150
|
+
* @param payload Parameters to describe the action
|
|
151
|
+
* @throws {Error} if store has handler registered that does not exist
|
|
152
|
+
*/
|
|
153
|
+
dispatch(eventOrType, payload) {
|
|
154
|
+
if (!eventOrType) {
|
|
155
|
+
throw new Error(`eventOrType parameter ${eventOrType} is invalid.`);
|
|
156
|
+
}
|
|
157
|
+
// конвертим старый тип экшенов
|
|
158
|
+
const event = convertAction(eventOrType, payload);
|
|
159
|
+
return this.applyDispatch(event);
|
|
160
|
+
}
|
|
161
|
+
applyHandlers(action, handlers) {
|
|
162
|
+
const handlerFns = {};
|
|
163
|
+
const storeInstanceGet = (store) => {
|
|
164
|
+
if (handlerFns[store.name]) {
|
|
165
|
+
// Don't call the default if the store has an explicit action handler
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const storeInstance = this.getStore(store.name);
|
|
169
|
+
if (!storeInstance) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (typeof store.handler === 'function') {
|
|
173
|
+
handlerFns[store.name] = store.handler.bind(storeInstance);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
177
|
+
if (!storeInstance[store.handler]) {
|
|
178
|
+
throw new Error(`${store.name} does not have a method called ${store.handler}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
handlerFns[store.name] = storeInstance[store.handler].bind(storeInstance);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const handlersLength = handlers.length;
|
|
185
|
+
for (let index = 0; index < handlersLength; index++) {
|
|
186
|
+
storeInstanceGet(handlers[index]);
|
|
187
|
+
}
|
|
188
|
+
return eventExecutor(action, handlerFns);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Returns a raw data object representation of the current state of the
|
|
192
|
+
* dispatcher and all store instances. If the store implements a shouldDehdyrate
|
|
193
|
+
* function, then it will be called and only dehydrate if the method returns `true`
|
|
194
|
+
* @method dehydrate
|
|
195
|
+
* @returns dehydrated dispatcher data
|
|
196
|
+
*/
|
|
197
|
+
dehydrate() {
|
|
198
|
+
const stores = {};
|
|
199
|
+
const keys = Object.keys(this.storeInstances);
|
|
200
|
+
for (let i = 0; i < keys.length; i++) {
|
|
201
|
+
const storeName = keys[i];
|
|
202
|
+
const store = this.storeInstances[storeName];
|
|
203
|
+
if (!store.dehydrate()) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
stores[storeName] = store.dehydrate();
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
stores,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Takes a raw data object and rehydrates the dispatcher and store instances
|
|
214
|
+
* @method rehydrate
|
|
215
|
+
* @param dispatcherState raw state typically retrieved from `dehydrate` method
|
|
216
|
+
*/
|
|
217
|
+
rehydrate(dispatcherState) {
|
|
218
|
+
if (dispatcherState.stores) {
|
|
219
|
+
const keys = Object.keys(dispatcherState.stores);
|
|
220
|
+
for (let index = 0; index < keys.length; index++) {
|
|
221
|
+
const storeName = keys[index];
|
|
222
|
+
this.rehydratedStoreState[storeName] = dispatcherState.stores[storeName];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
subscribe(...args) {
|
|
227
|
+
if ('storeName' in args[0]) {
|
|
228
|
+
const reducer = args[0];
|
|
229
|
+
const callback = args[1];
|
|
230
|
+
const reducerInstance = this.getStore(reducer);
|
|
231
|
+
const listener = () => {
|
|
232
|
+
const state = this.getState(reducer);
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
234
|
+
callback(state);
|
|
235
|
+
};
|
|
236
|
+
reducerInstance.on('change', listener);
|
|
237
|
+
return () => {
|
|
238
|
+
reducerInstance.off('change', listener);
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const callback = args[0];
|
|
242
|
+
const listener = () => {
|
|
243
|
+
const state = this.getState();
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
245
|
+
callback(state);
|
|
246
|
+
};
|
|
247
|
+
this.on('change', listener);
|
|
248
|
+
return () => {
|
|
249
|
+
this.off('change', listener);
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
getState(reducer) {
|
|
253
|
+
if (reducer) {
|
|
254
|
+
return this.fullState[reducer.storeName];
|
|
255
|
+
}
|
|
256
|
+
return this.fullState;
|
|
257
|
+
}
|
|
258
|
+
// Для отложенной инициализации контекста, в будующем нужно удалить
|
|
259
|
+
setContext(context) {
|
|
260
|
+
this.context = context;
|
|
261
|
+
}
|
|
262
|
+
hasStore(store) {
|
|
263
|
+
return store.storeName in this.storeInstances && this.dispatcher.hasStore(store);
|
|
264
|
+
}
|
|
265
|
+
registerStore(store) {
|
|
266
|
+
this.dispatcher.registerStore(store);
|
|
267
|
+
this.getStore(store);
|
|
268
|
+
}
|
|
269
|
+
unregisterStore(store) {
|
|
270
|
+
const { storeName } = store;
|
|
271
|
+
this.dispatcher.unregisterStore(store);
|
|
272
|
+
this.storeUnsubscribeCallbacks[storeName]();
|
|
273
|
+
delete this.storeUnsubscribeCallbacks[storeName];
|
|
274
|
+
delete this.rehydratedStoreState[storeName];
|
|
275
|
+
delete this.storeInstances[storeName];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export { DispatcherContext, convertAction };
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var compose = require('@tinkoff/utils/function/compose');
|
|
6
|
+
var storeSubscribe = require('./storeSubscribe.js');
|
|
7
|
+
var SimpleEmitter = require('../stores/SimpleEmitter.js');
|
|
8
|
+
|
|
9
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
|
+
|
|
11
|
+
var compose__default = /*#__PURE__*/_interopDefaultLegacy(compose);
|
|
12
|
+
|
|
13
|
+
const eventExecutor = (event, handlerFns) => {
|
|
14
|
+
const keys = Object.keys(handlerFns);
|
|
15
|
+
for (let index = 0; index < keys.length; index++) {
|
|
16
|
+
const storeName = keys[index];
|
|
17
|
+
const handlerFn = handlerFns[storeName];
|
|
18
|
+
if (!handlerFn) {
|
|
19
|
+
throw new Error(`${storeName} does not have a handler for action ${event.type}`);
|
|
20
|
+
}
|
|
21
|
+
handlerFn(event.payload, event.type);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
// Конвертация старого типа экшенов в новый
|
|
25
|
+
const convertAction = (actionOrNameEvent, payload) => {
|
|
26
|
+
if (typeof actionOrNameEvent === 'string') {
|
|
27
|
+
return {
|
|
28
|
+
payload,
|
|
29
|
+
type: actionOrNameEvent,
|
|
30
|
+
error: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return actionOrNameEvent;
|
|
34
|
+
};
|
|
35
|
+
// Это форкнутый вариант dispatchr
|
|
36
|
+
class DispatcherContext extends SimpleEmitter.SimpleEmitter {
|
|
37
|
+
/**
|
|
38
|
+
* @param context The context to be used for store instances
|
|
39
|
+
*/
|
|
40
|
+
constructor(dispatcher, context, initialState, middlewares) {
|
|
41
|
+
super();
|
|
42
|
+
this.applyDispatch = (event) => {
|
|
43
|
+
const eventHandlers = this.dispatcher.handlers[event.type] || [];
|
|
44
|
+
if (!eventHandlers.length) {
|
|
45
|
+
if (process.env.NODE_ENV === 'development') {
|
|
46
|
+
console.warn(`
|
|
47
|
+
The event "${event.type}" has been dispatched, but no reducers for this event were registered.
|
|
48
|
+
Have you forgot to register reducer or add event handler in existing reducer?
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
return event.payload;
|
|
52
|
+
}
|
|
53
|
+
this.applyHandlers(event, eventHandlers);
|
|
54
|
+
return event.payload;
|
|
55
|
+
};
|
|
56
|
+
this.dispatcher = dispatcher;
|
|
57
|
+
this.storeInstances = {};
|
|
58
|
+
this.storeUnsubscribeCallbacks = {};
|
|
59
|
+
this.context = context;
|
|
60
|
+
this.dispatcherInterface = {
|
|
61
|
+
getContext: () => this.context,
|
|
62
|
+
getStore: this.getStore.bind(this),
|
|
63
|
+
};
|
|
64
|
+
this.rehydratedStoreState = {};
|
|
65
|
+
this.fullState = {};
|
|
66
|
+
// Заполняем стейт данными
|
|
67
|
+
if (initialState) {
|
|
68
|
+
this.rehydrate(initialState);
|
|
69
|
+
}
|
|
70
|
+
if (middlewares === null || middlewares === void 0 ? void 0 : middlewares.length) {
|
|
71
|
+
this.applyDispatch = this.applyMiddlewares(middlewares);
|
|
72
|
+
}
|
|
73
|
+
// Инцииализируем уже имеющиеся сторы
|
|
74
|
+
Object.keys(this.dispatcher.stores).forEach((store) => {
|
|
75
|
+
this.getStore(store);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
applyMiddlewares(middlewares) {
|
|
79
|
+
let dispatch = (...args) => {
|
|
80
|
+
throw new Error('Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.');
|
|
81
|
+
};
|
|
82
|
+
const api = {
|
|
83
|
+
getState: this.getState.bind(this),
|
|
84
|
+
subscribe: this.subscribe.bind(this),
|
|
85
|
+
dispatch: (...args) => dispatch(...args),
|
|
86
|
+
};
|
|
87
|
+
dispatch = compose__default["default"](...middlewares.map((middleware) => middleware(api)))(this.applyDispatch);
|
|
88
|
+
return dispatch;
|
|
89
|
+
}
|
|
90
|
+
storeSubscribe(storeName, storeInstance) {
|
|
91
|
+
const subscribeHandler = () => {
|
|
92
|
+
const newState = storeInstance.getState();
|
|
93
|
+
if (newState !== this.fullState[storeName]) {
|
|
94
|
+
this.fullState = {
|
|
95
|
+
...this.fullState,
|
|
96
|
+
[storeName]: newState,
|
|
97
|
+
};
|
|
98
|
+
this.emit('change');
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
storeSubscribe.subscribe(storeInstance, subscribeHandler);
|
|
102
|
+
const unsubscribe = () => {
|
|
103
|
+
storeInstance.off('change', subscribeHandler);
|
|
104
|
+
};
|
|
105
|
+
this.storeUnsubscribeCallbacks[storeName] = unsubscribe;
|
|
106
|
+
}
|
|
107
|
+
// eslint-disable-next-line max-statements
|
|
108
|
+
getStore(storeClass) {
|
|
109
|
+
let storeClassPrepared;
|
|
110
|
+
let optional = false;
|
|
111
|
+
if (typeof storeClass === 'object') {
|
|
112
|
+
storeClassPrepared = storeClass.store;
|
|
113
|
+
optional = storeClass.optional;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
storeClassPrepared = storeClass;
|
|
117
|
+
}
|
|
118
|
+
const storeName = this.dispatcher.getStoreName(storeClassPrepared);
|
|
119
|
+
if (!this.storeInstances[storeName]) {
|
|
120
|
+
let Store = this.dispatcher.stores[storeName];
|
|
121
|
+
if (!Store) {
|
|
122
|
+
if (typeof storeClassPrepared === 'function') {
|
|
123
|
+
this.dispatcher.registerStore(storeClassPrepared);
|
|
124
|
+
Store = storeClassPrepared;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
if (optional) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`Store ${storeName} was not registered.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const storeInstance = new Store(this.dispatcherInterface);
|
|
134
|
+
this.storeInstances[storeName] = storeInstance;
|
|
135
|
+
if (this.rehydratedStoreState && this.rehydratedStoreState[storeName]) {
|
|
136
|
+
const state = this.rehydratedStoreState[storeName];
|
|
137
|
+
if (storeInstance.rehydrate) {
|
|
138
|
+
storeInstance.rehydrate(state);
|
|
139
|
+
}
|
|
140
|
+
this.rehydratedStoreState[storeName] = null;
|
|
141
|
+
}
|
|
142
|
+
this.storeSubscribe(storeName, storeInstance);
|
|
143
|
+
// TODO: убрать после того отпадёт надобность связывать сторы router и application
|
|
144
|
+
if (Store.dependencies) {
|
|
145
|
+
Store.dependencies.forEach((dependencyStoreClass) => {
|
|
146
|
+
const dependency = this.getStore(dependencyStoreClass);
|
|
147
|
+
storeSubscribe.subscribe(dependency, () => {
|
|
148
|
+
storeInstance.emit('change');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return this.storeInstances[storeName];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Dispatches a new action or throws if one is already in progress
|
|
157
|
+
* @param eventOrType Name of the event to be dispatched
|
|
158
|
+
* @param payload Parameters to describe the action
|
|
159
|
+
* @throws {Error} if store has handler registered that does not exist
|
|
160
|
+
*/
|
|
161
|
+
dispatch(eventOrType, payload) {
|
|
162
|
+
if (!eventOrType) {
|
|
163
|
+
throw new Error(`eventOrType parameter ${eventOrType} is invalid.`);
|
|
164
|
+
}
|
|
165
|
+
// конвертим старый тип экшенов
|
|
166
|
+
const event = convertAction(eventOrType, payload);
|
|
167
|
+
return this.applyDispatch(event);
|
|
168
|
+
}
|
|
169
|
+
applyHandlers(action, handlers) {
|
|
170
|
+
const handlerFns = {};
|
|
171
|
+
const storeInstanceGet = (store) => {
|
|
172
|
+
if (handlerFns[store.name]) {
|
|
173
|
+
// Don't call the default if the store has an explicit action handler
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const storeInstance = this.getStore(store.name);
|
|
177
|
+
if (!storeInstance) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (typeof store.handler === 'function') {
|
|
181
|
+
handlerFns[store.name] = store.handler.bind(storeInstance);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
185
|
+
if (!storeInstance[store.handler]) {
|
|
186
|
+
throw new Error(`${store.name} does not have a method called ${store.handler}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
handlerFns[store.name] = storeInstance[store.handler].bind(storeInstance);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const handlersLength = handlers.length;
|
|
193
|
+
for (let index = 0; index < handlersLength; index++) {
|
|
194
|
+
storeInstanceGet(handlers[index]);
|
|
195
|
+
}
|
|
196
|
+
return eventExecutor(action, handlerFns);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Returns a raw data object representation of the current state of the
|
|
200
|
+
* dispatcher and all store instances. If the store implements a shouldDehdyrate
|
|
201
|
+
* function, then it will be called and only dehydrate if the method returns `true`
|
|
202
|
+
* @method dehydrate
|
|
203
|
+
* @returns dehydrated dispatcher data
|
|
204
|
+
*/
|
|
205
|
+
dehydrate() {
|
|
206
|
+
const stores = {};
|
|
207
|
+
const keys = Object.keys(this.storeInstances);
|
|
208
|
+
for (let i = 0; i < keys.length; i++) {
|
|
209
|
+
const storeName = keys[i];
|
|
210
|
+
const store = this.storeInstances[storeName];
|
|
211
|
+
if (!store.dehydrate()) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
stores[storeName] = store.dehydrate();
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
stores,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Takes a raw data object and rehydrates the dispatcher and store instances
|
|
222
|
+
* @method rehydrate
|
|
223
|
+
* @param dispatcherState raw state typically retrieved from `dehydrate` method
|
|
224
|
+
*/
|
|
225
|
+
rehydrate(dispatcherState) {
|
|
226
|
+
if (dispatcherState.stores) {
|
|
227
|
+
const keys = Object.keys(dispatcherState.stores);
|
|
228
|
+
for (let index = 0; index < keys.length; index++) {
|
|
229
|
+
const storeName = keys[index];
|
|
230
|
+
this.rehydratedStoreState[storeName] = dispatcherState.stores[storeName];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
subscribe(...args) {
|
|
235
|
+
if ('storeName' in args[0]) {
|
|
236
|
+
const reducer = args[0];
|
|
237
|
+
const callback = args[1];
|
|
238
|
+
const reducerInstance = this.getStore(reducer);
|
|
239
|
+
const listener = () => {
|
|
240
|
+
const state = this.getState(reducer);
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
242
|
+
callback(state);
|
|
243
|
+
};
|
|
244
|
+
reducerInstance.on('change', listener);
|
|
245
|
+
return () => {
|
|
246
|
+
reducerInstance.off('change', listener);
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const callback = args[0];
|
|
250
|
+
const listener = () => {
|
|
251
|
+
const state = this.getState();
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
253
|
+
callback(state);
|
|
254
|
+
};
|
|
255
|
+
this.on('change', listener);
|
|
256
|
+
return () => {
|
|
257
|
+
this.off('change', listener);
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
getState(reducer) {
|
|
261
|
+
if (reducer) {
|
|
262
|
+
return this.fullState[reducer.storeName];
|
|
263
|
+
}
|
|
264
|
+
return this.fullState;
|
|
265
|
+
}
|
|
266
|
+
// Для отложенной инициализации контекста, в будующем нужно удалить
|
|
267
|
+
setContext(context) {
|
|
268
|
+
this.context = context;
|
|
269
|
+
}
|
|
270
|
+
hasStore(store) {
|
|
271
|
+
return store.storeName in this.storeInstances && this.dispatcher.hasStore(store);
|
|
272
|
+
}
|
|
273
|
+
registerStore(store) {
|
|
274
|
+
this.dispatcher.registerStore(store);
|
|
275
|
+
this.getStore(store);
|
|
276
|
+
}
|
|
277
|
+
unregisterStore(store) {
|
|
278
|
+
const { storeName } = store;
|
|
279
|
+
this.dispatcher.unregisterStore(store);
|
|
280
|
+
this.storeUnsubscribeCallbacks[storeName]();
|
|
281
|
+
delete this.storeUnsubscribeCallbacks[storeName];
|
|
282
|
+
delete this.rehydratedStoreState[storeName];
|
|
283
|
+
delete this.storeInstances[storeName];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
exports.DispatcherContext = DispatcherContext;
|
|
288
|
+
exports.convertAction = convertAction;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const subscribe = (store, handler) => {
|
|
6
|
+
// подписываемся на изменения
|
|
7
|
+
store.on('change', handler);
|
|
8
|
+
// вызываем сразу, чтобы заполнить начальное состояние
|
|
9
|
+
handler();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
exports.subscribe = subscribe;
|