@fozy-labs/rx-toolkit 0.5.0-rc.2 → 0.5.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/README.md +2 -2
- package/dist/query/api/createResourceDuplicator.d.ts +4 -0
- package/dist/query/api/createResourceDuplicator.js +2 -0
- package/dist/query/core/Opertation/OperationAgent.d.ts +1 -2
- package/dist/query/core/Opertation/OperationAgent.js +2 -2
- package/dist/query/core/QueriesCache.d.ts +1 -2
- package/dist/query/core/Resource/ResourceAgent.d.ts +2 -4
- package/dist/query/core/Resource/ResourceAgent.js +2 -34
- package/dist/query/core/Resource/ResourceDuplicator.d.ts +73 -0
- package/dist/query/core/Resource/ResourceDuplicator.js +227 -0
- package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +35 -0
- package/dist/query/core/Resource/ResourceDuplicatorAgent.js +110 -0
- package/dist/query/index.d.ts +1 -0
- package/dist/query/index.js +1 -0
- package/dist/query/react/useResourceAgent.d.ts +7 -2
- package/dist/query/react/useResourceAgent.js +10 -1
- package/dist/query/types/Resource.types.d.ts +1 -1
- package/dist/signals/react/useSignal.js +8 -1
- package/docs/devtools/README.md +2 -2
- package/docs/migrations/0.5.0.md +1 -1
- package/docs/options/README.md +15 -14
- package/docs/signals/README.md +40 -63
- package/docs/usage/react/README.md +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ RxToolkit решает эти проблемы, предоставляя сво
|
|
|
46
46
|
// Описываем логику в обычном JavaScript
|
|
47
47
|
const count$ = Signal.create(0);
|
|
48
48
|
const doubled$ = Signal.compute(() => count$() * 2);
|
|
49
|
-
const increment = () => count$.set(count
|
|
49
|
+
const increment = () => count$.set(count$() + 1);
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
###### Подключаем к фреймворку
|
|
@@ -81,7 +81,7 @@ const clicker$ = fromEvent(document, 'click').pipe(
|
|
|
81
81
|
const clickCount$ = signalize(clicker$);
|
|
82
82
|
const doubled$ = Signal.compute(() => clickCount$() * 2);
|
|
83
83
|
|
|
84
|
-
console.log(doubled
|
|
84
|
+
console.log(doubled$()); // Всегда актуальное значение
|
|
85
85
|
|
|
86
86
|
// Или наоборот, получаем событие из сигнала
|
|
87
87
|
const on10click$ = doubled$.obs.pipe(
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { DuplicatorOptions, ResourceDuplicator, DuplicatorDefinition } from "../../query/core/Resource/ResourceDuplicator";
|
|
2
|
+
import { ResourceDefinition } from "../../query/types";
|
|
3
|
+
export declare const createResourceDuplicator: <ARGS, RESULT, SELECTED = never>(options: DuplicatorOptions<DuplicatorDefinition<ResourceDefinition<ARGS, RESULT, SELECTED>>>) => ResourceDuplicator<DuplicatorDefinition<ResourceDefinition<ARGS, RESULT, SELECTED>>>;
|
|
4
|
+
export type ResourceDuplicatorCreateFn<ARGS, RESULT, SELECTED = never> = (options: DuplicatorOptions<DuplicatorDefinition<ResourceDefinition<ARGS, RESULT, SELECTED>>>) => ResourceDuplicator<DuplicatorDefinition<ResourceDefinition<ARGS, RESULT, SELECTED>>>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { OperationAgentInstanse, OperationDefinition } from "../../../query/types";
|
|
2
|
-
import { Computed } from "../../../signals";
|
|
3
2
|
import type { Operation } from "./Operation";
|
|
4
3
|
export declare class OperationAgent<D extends OperationDefinition> implements OperationAgentInstanse<D> {
|
|
5
4
|
private _operation;
|
|
6
5
|
private _operations$;
|
|
7
|
-
state$:
|
|
6
|
+
state$: import("../../../signals/types").ComputeFn<{
|
|
8
7
|
isLoading: boolean;
|
|
9
8
|
isDone: boolean;
|
|
10
9
|
isSuccess: boolean;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Computed, Signal } from "../../../signals";
|
|
2
2
|
export class OperationAgent {
|
|
3
3
|
_operation;
|
|
4
|
-
_operations$ =
|
|
4
|
+
_operations$ = Signal.create({
|
|
5
5
|
current$: null,
|
|
6
6
|
}, { isDisabled: true });
|
|
7
|
-
state$ =
|
|
7
|
+
state$ = Computed.create(() => {
|
|
8
8
|
const operations = this._operations$.get();
|
|
9
9
|
const currState = operations.current$?.value$.get();
|
|
10
10
|
// Нет текущего состояния — дефолт
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { ReactiveCache } from "../../query/lib/ReactiveCache";
|
|
2
|
-
import { shallowEqual } from "../../common/utils";
|
|
3
2
|
export declare class QueriesCache<KEY, VALUE> {
|
|
4
3
|
private _cacheLifeTime;
|
|
5
4
|
private readonly _cache;
|
|
6
|
-
constructor(_cacheLifeTime?: number | false, compareArgsFn?:
|
|
5
|
+
constructor(_cacheLifeTime?: number | false, compareArgsFn?: ((a: KEY, b: KEY) => boolean));
|
|
7
6
|
getQueryCache(args: KEY): ReactiveCache<VALUE> | undefined;
|
|
8
7
|
createQueryCache(args: KEY, initialState: VALUE): ReactiveCache<VALUE>;
|
|
9
8
|
values(): MapIterator<ReactiveCache<VALUE>>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ResourceAgentInstance, ResourceDefinition, ResourceQueryState } from "../../../query/types";
|
|
1
|
+
import { ResourceAgentInstance, ResourceDefinition } from "../../../query/types";
|
|
3
2
|
import type { Resource } from "./Resource";
|
|
4
3
|
export declare class ResourceAgent<D extends ResourceDefinition> implements ResourceAgentInstance<D> {
|
|
5
4
|
private _resource;
|
|
6
5
|
private _resources$;
|
|
7
|
-
state$:
|
|
6
|
+
state$: import("../../../signals/types").ComputeFn<{
|
|
8
7
|
isInitiated: boolean;
|
|
9
8
|
isLoading: boolean;
|
|
10
9
|
isInitialLoading: boolean;
|
|
@@ -30,7 +29,6 @@ export declare class ResourceAgent<D extends ResourceDefinition> implements Reso
|
|
|
30
29
|
args: NonNullable<D["Args"]> | undefined;
|
|
31
30
|
}>;
|
|
32
31
|
constructor(_resource: Resource<D>);
|
|
33
|
-
getState(values: D["Args"]): ResourceQueryState<D>;
|
|
34
32
|
initiate(args: D["Args"], force?: boolean): void;
|
|
35
33
|
compareArgs(args: D["Args"], otherArgs: D["Args"]): boolean;
|
|
36
34
|
private _next;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Computed, Signal } from "../../../signals";
|
|
2
2
|
export class ResourceAgent {
|
|
3
3
|
_resource;
|
|
4
|
-
_resources$ =
|
|
4
|
+
_resources$ = Signal.create({
|
|
5
5
|
previous$: null,
|
|
6
6
|
current$: null,
|
|
7
7
|
}, { isDisabled: true });
|
|
8
|
-
state$ =
|
|
8
|
+
state$ = Computed.create(() => {
|
|
9
9
|
const resources = this._resources$.get();
|
|
10
10
|
let prevState;
|
|
11
11
|
const currState = resources.current$?.value$.get();
|
|
@@ -68,38 +68,6 @@ export class ResourceAgent {
|
|
|
68
68
|
constructor(_resource) {
|
|
69
69
|
this._resource = _resource;
|
|
70
70
|
}
|
|
71
|
-
getState(values) {
|
|
72
|
-
const cache = this._resource.getQueryCache(values);
|
|
73
|
-
if (!cache) {
|
|
74
|
-
return {
|
|
75
|
-
isInitiated: false,
|
|
76
|
-
isLoading: false,
|
|
77
|
-
isInitialLoading: false,
|
|
78
|
-
isDone: false,
|
|
79
|
-
isSuccess: false,
|
|
80
|
-
isError: false,
|
|
81
|
-
isLocked: false,
|
|
82
|
-
isReloading: false,
|
|
83
|
-
error: undefined,
|
|
84
|
-
data: undefined,
|
|
85
|
-
args: undefined,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
const state = cache.value;
|
|
89
|
-
return {
|
|
90
|
-
isInitiated: state.isInitiated,
|
|
91
|
-
isLoading: state.isLoading,
|
|
92
|
-
isInitialLoading: state.isLoading && !state.isDone,
|
|
93
|
-
isDone: state.isDone,
|
|
94
|
-
isSuccess: state.isSuccess,
|
|
95
|
-
isError: state.isError,
|
|
96
|
-
isLocked: state.isLocked,
|
|
97
|
-
isReloading: state.isReloading,
|
|
98
|
-
error: state.error ?? undefined,
|
|
99
|
-
data: state.data ?? undefined,
|
|
100
|
-
args: state.args ?? undefined,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
71
|
initiate(args, force = false) {
|
|
104
72
|
const current = this._resources$.peek().current$;
|
|
105
73
|
const cache = this._resource.getQueryCache(args);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ResourceDefinition } from "../../../query/types";
|
|
2
|
+
import { CoreResourceQueryState, Resource } from "./Resource";
|
|
3
|
+
import { ReadableSignalLike } from "../../../signals/types";
|
|
4
|
+
import { Observable, Subject } from "rxjs";
|
|
5
|
+
import { ResourceDuplicatorAgent } from "./ResourceDuplicatorAgent";
|
|
6
|
+
export type DuplicatorOptions<D extends DuplicatorDefinition> = {
|
|
7
|
+
resource: Resource<D['RESOURCE_DEFINITION']>;
|
|
8
|
+
getArgKey: (item: D['ARGS_ITEM']) => string | number;
|
|
9
|
+
getDataKey: (item: D['DATA_ITEM']) => string | number;
|
|
10
|
+
cacheLifetime?: number | false;
|
|
11
|
+
};
|
|
12
|
+
export type DuplicatorDefinition<D extends ResourceDefinition = ResourceDefinition> = {
|
|
13
|
+
ARGS_ITEM: D['Args'] extends Array<any> ? D['Args'][number] : never;
|
|
14
|
+
DATA_ITEM: D['Data'] extends Array<any> ? D['Data'][number] : never;
|
|
15
|
+
RESOURCE_DEFINITION: D;
|
|
16
|
+
};
|
|
17
|
+
type State<D extends DuplicatorDefinition> = CoreResourceQueryState<D['RESOURCE_DEFINITION']> & {
|
|
18
|
+
unreleasedArgs?: D['ARGS_ITEM'][];
|
|
19
|
+
};
|
|
20
|
+
type Cache<D extends DuplicatorDefinition> = ComputedReactiveCache<State<D>>;
|
|
21
|
+
export type CoreResourceDuplicatorCache<D extends DuplicatorDefinition> = Cache<D>;
|
|
22
|
+
export declare class ResourceDuplicator<D extends DuplicatorDefinition> {
|
|
23
|
+
private _options;
|
|
24
|
+
private _fis;
|
|
25
|
+
private _caches;
|
|
26
|
+
private get _resource();
|
|
27
|
+
constructor(_options: DuplicatorOptions<D>);
|
|
28
|
+
getQueryCache(args: D['ARGS_ITEM'][]): Cache<D> | undefined;
|
|
29
|
+
createCache(args: D['ARGS_ITEM'][]): Cache<D>;
|
|
30
|
+
initiate(args: D['ARGS_ITEM'][], cache?: Cache<D>): Cache<D>;
|
|
31
|
+
serialize(args: D['ARGS_ITEM'][]): string;
|
|
32
|
+
compareArgs(a: D['ARGS_ITEM'][], b: D['ARGS_ITEM'][]): boolean;
|
|
33
|
+
createAgent: () => ResourceDuplicatorAgent<D>;
|
|
34
|
+
/** @deprecated */
|
|
35
|
+
d_init(args: D['ARGS_ITEM'][]): {
|
|
36
|
+
value$: import("../../../signals/types").ComputeFn<State<D>>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare class ComputedReactiveCache<T> {
|
|
40
|
+
/**
|
|
41
|
+
* Реактивное значене (Observable)
|
|
42
|
+
*/
|
|
43
|
+
value$: ReadableSignalLike<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Значение без сайд-эффектов (для использования в DevTools)
|
|
46
|
+
*/
|
|
47
|
+
spy$: Observable<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Subject, уведомляющий об очистке кэша.
|
|
50
|
+
*/
|
|
51
|
+
onClean$: Subject<T>;
|
|
52
|
+
closed: boolean;
|
|
53
|
+
private _getValue;
|
|
54
|
+
/**
|
|
55
|
+
* Создает новый экземпляр `ReactiveCacheItem`.
|
|
56
|
+
*
|
|
57
|
+
* @param options Параметры для настройки элемента кэша.
|
|
58
|
+
* @param options.initialState Начальное состояние кэша.
|
|
59
|
+
* @param options.cacheLifeTime Время жизни кэша в миллисекундах (по умолчанию 60_000).
|
|
60
|
+
*/
|
|
61
|
+
constructor(options: {
|
|
62
|
+
obs: Observable<T>;
|
|
63
|
+
getValue: () => T;
|
|
64
|
+
cacheLifeTime: number | false;
|
|
65
|
+
});
|
|
66
|
+
private _getOnRefCountZero;
|
|
67
|
+
get value(): T;
|
|
68
|
+
/**
|
|
69
|
+
* Завершает работу кэша, закрывая все потоки и уведомляя об очистке.
|
|
70
|
+
*/
|
|
71
|
+
complete(): void;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Signal, signalize } from "../../../signals";
|
|
2
|
+
import { finalize, ReplaySubject, share, Subject, takeUntil, timer } from "rxjs";
|
|
3
|
+
import { ResourceDuplicatorAgent } from "./ResourceDuplicatorAgent";
|
|
4
|
+
export class ResourceDuplicator {
|
|
5
|
+
_options;
|
|
6
|
+
_fis = new Map();
|
|
7
|
+
_caches;
|
|
8
|
+
get _resource() {
|
|
9
|
+
return this._options.resource;
|
|
10
|
+
}
|
|
11
|
+
constructor(_options) {
|
|
12
|
+
this._options = _options;
|
|
13
|
+
this._caches = new Map();
|
|
14
|
+
}
|
|
15
|
+
getQueryCache(args) {
|
|
16
|
+
const key = this.serialize(args);
|
|
17
|
+
return this._caches.get(key);
|
|
18
|
+
}
|
|
19
|
+
createCache(args) {
|
|
20
|
+
const key = this.serialize(args);
|
|
21
|
+
const { value$ } = this.d_init(args);
|
|
22
|
+
const cache = new ComputedReactiveCache({
|
|
23
|
+
cacheLifeTime: this._options.cacheLifetime ?? 60_000,
|
|
24
|
+
getValue: () => value$.get(),
|
|
25
|
+
obs: value$.obs,
|
|
26
|
+
});
|
|
27
|
+
cache.onClean$.subscribe(() => {
|
|
28
|
+
args.forEach(arg => {
|
|
29
|
+
const argKey = this._options.getArgKey(arg);
|
|
30
|
+
const fi = this._fis.get(argKey);
|
|
31
|
+
if (!fi)
|
|
32
|
+
return;
|
|
33
|
+
fi.k--;
|
|
34
|
+
if (fi.k <= 0) {
|
|
35
|
+
this._fis.delete(argKey);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
this._caches.delete(key);
|
|
39
|
+
});
|
|
40
|
+
this._caches.set(key, cache);
|
|
41
|
+
return cache;
|
|
42
|
+
}
|
|
43
|
+
initiate(args, cache) {
|
|
44
|
+
const cacheInstance = cache ?? this.getQueryCache(args) ?? this.createCache(args);
|
|
45
|
+
const unreleasedArgs = cacheInstance.value.unreleasedArgs;
|
|
46
|
+
if (unreleasedArgs && unreleasedArgs.length !== 0) {
|
|
47
|
+
this._resource.initiate(unreleasedArgs);
|
|
48
|
+
}
|
|
49
|
+
const uninitiatedCaches = new Set();
|
|
50
|
+
console.log({ uninitiatedCaches, fis: this._fis });
|
|
51
|
+
args.forEach(arg => {
|
|
52
|
+
const argKey = this._options.getArgKey(arg);
|
|
53
|
+
let fi = this._fis.get(argKey);
|
|
54
|
+
if (fi && !fi.cache.value.isInitiated) {
|
|
55
|
+
uninitiatedCaches.add(fi.cache);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
uninitiatedCaches.forEach((c) => {
|
|
59
|
+
this._resource.initiate(c.value.args, { cache: c });
|
|
60
|
+
});
|
|
61
|
+
return cacheInstance;
|
|
62
|
+
}
|
|
63
|
+
serialize(args) {
|
|
64
|
+
if (!args)
|
|
65
|
+
return '';
|
|
66
|
+
const argsKeys = args.map(a => this._options.getArgKey(a));
|
|
67
|
+
return argsKeys.join('|');
|
|
68
|
+
}
|
|
69
|
+
compareArgs(a, b) {
|
|
70
|
+
return this.serialize(a) === this.serialize(b);
|
|
71
|
+
}
|
|
72
|
+
createAgent = () => {
|
|
73
|
+
return new ResourceDuplicatorAgent(this);
|
|
74
|
+
};
|
|
75
|
+
/** @deprecated */
|
|
76
|
+
d_init(args) {
|
|
77
|
+
const argsKeys = args.map(a => this._options.getArgKey(a));
|
|
78
|
+
const releasedCaches = new Set();
|
|
79
|
+
const unreleasedArgs = [];
|
|
80
|
+
args.forEach(arg => {
|
|
81
|
+
const argKey = this._options.getArgKey(arg);
|
|
82
|
+
let fi = this._fis.get(argKey);
|
|
83
|
+
if (!fi || !fi.cache.value.isInitiated) {
|
|
84
|
+
unreleasedArgs.push(arg);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
fi.k++;
|
|
88
|
+
releasedCaches.add(fi.cache);
|
|
89
|
+
});
|
|
90
|
+
const queryCache = this._resource.createQueryCache(unreleasedArgs);
|
|
91
|
+
unreleasedArgs.forEach(arg => {
|
|
92
|
+
const argKey = this._options.getArgKey(arg);
|
|
93
|
+
let fi = this._fis.get(argKey);
|
|
94
|
+
if (!fi) {
|
|
95
|
+
fi = {
|
|
96
|
+
k: 1,
|
|
97
|
+
cache: queryCache,
|
|
98
|
+
};
|
|
99
|
+
this._fis.set(argKey, fi);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
value$: Signal.compute(() => {
|
|
104
|
+
const itemsAcc = [
|
|
105
|
+
queryCache.value$.get(),
|
|
106
|
+
];
|
|
107
|
+
for (const rc of releasedCaches) {
|
|
108
|
+
itemsAcc.push(rc.value$.get());
|
|
109
|
+
}
|
|
110
|
+
const isNotInitiated = itemsAcc.some(i => !i.isInitiated);
|
|
111
|
+
const baseReturn = {
|
|
112
|
+
transactions: null,
|
|
113
|
+
abortController: null,
|
|
114
|
+
args,
|
|
115
|
+
savedData: null,
|
|
116
|
+
data: null,
|
|
117
|
+
error: null,
|
|
118
|
+
isError: false,
|
|
119
|
+
isLoading: false,
|
|
120
|
+
isReloading: false,
|
|
121
|
+
isDone: false,
|
|
122
|
+
isSuccess: false,
|
|
123
|
+
isLocked: false,
|
|
124
|
+
isInitiated: true,
|
|
125
|
+
lockCount: 0,
|
|
126
|
+
unreleasedArgs,
|
|
127
|
+
};
|
|
128
|
+
if (isNotInitiated)
|
|
129
|
+
return {
|
|
130
|
+
...baseReturn,
|
|
131
|
+
isInitiated: false,
|
|
132
|
+
};
|
|
133
|
+
const isError = itemsAcc.some(i => i.isError);
|
|
134
|
+
if (isError) {
|
|
135
|
+
const firstError = itemsAcc.find(i => i.isError);
|
|
136
|
+
return {
|
|
137
|
+
...baseReturn,
|
|
138
|
+
isError: true,
|
|
139
|
+
isDone: true,
|
|
140
|
+
error: firstError.error,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const isLoading = itemsAcc.some(i => i.isLoading);
|
|
144
|
+
if (isLoading)
|
|
145
|
+
return {
|
|
146
|
+
...baseReturn,
|
|
147
|
+
isLoading: true,
|
|
148
|
+
};
|
|
149
|
+
const dataAcc = [];
|
|
150
|
+
itemsAcc.forEach(item => {
|
|
151
|
+
item.data?.forEach((d) => {
|
|
152
|
+
const dataKey = this._options.getDataKey(d);
|
|
153
|
+
const index = argsKeys.findIndex(ak => ak === dataKey);
|
|
154
|
+
if (index === -1)
|
|
155
|
+
return;
|
|
156
|
+
dataAcc[index] = d;
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
...baseReturn,
|
|
161
|
+
isSuccess: true,
|
|
162
|
+
isDone: true,
|
|
163
|
+
data: dataAcc,
|
|
164
|
+
};
|
|
165
|
+
}, { isDisabled: true }),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export class ComputedReactiveCache {
|
|
170
|
+
/**
|
|
171
|
+
* Реактивное значене (Observable)
|
|
172
|
+
*/
|
|
173
|
+
value$;
|
|
174
|
+
/**
|
|
175
|
+
* Значение без сайд-эффектов (для использования в DevTools)
|
|
176
|
+
*/
|
|
177
|
+
spy$;
|
|
178
|
+
/**
|
|
179
|
+
* Subject, уведомляющий об очистке кэша.
|
|
180
|
+
*/
|
|
181
|
+
onClean$ = new Subject();
|
|
182
|
+
closed = false;
|
|
183
|
+
_getValue;
|
|
184
|
+
/**
|
|
185
|
+
* Создает новый экземпляр `ReactiveCacheItem`.
|
|
186
|
+
*
|
|
187
|
+
* @param options Параметры для настройки элемента кэша.
|
|
188
|
+
* @param options.initialState Начальное состояние кэша.
|
|
189
|
+
* @param options.cacheLifeTime Время жизни кэша в миллисекундах (по умолчанию 60_000).
|
|
190
|
+
*/
|
|
191
|
+
constructor(options) {
|
|
192
|
+
const cacheLifeTime = options.cacheLifeTime ?? 60_000;
|
|
193
|
+
this.spy$ = options.obs.pipe(takeUntil(this.onClean$));
|
|
194
|
+
this.value$ = signalize(options.obs.pipe(finalize(() => {
|
|
195
|
+
this.complete();
|
|
196
|
+
}), share({
|
|
197
|
+
connector: () => new ReplaySubject(1),
|
|
198
|
+
resetOnRefCountZero: this._getOnRefCountZero(cacheLifeTime),
|
|
199
|
+
resetOnComplete: true,
|
|
200
|
+
})));
|
|
201
|
+
this._getValue = options.getValue;
|
|
202
|
+
}
|
|
203
|
+
_getOnRefCountZero(cacheLifeTime) {
|
|
204
|
+
if (cacheLifeTime === false) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
if (cacheLifeTime <= 0) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
return () => {
|
|
211
|
+
return timer(cacheLifeTime);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
get value() {
|
|
215
|
+
return this._getValue();
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Завершает работу кэша, закрывая все потоки и уведомляя об очистке.
|
|
219
|
+
*/
|
|
220
|
+
complete() {
|
|
221
|
+
if (this.closed)
|
|
222
|
+
return;
|
|
223
|
+
this.closed = true;
|
|
224
|
+
this.onClean$.next(this._getValue());
|
|
225
|
+
this.onClean$.complete();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ResourceAgentInstance } from "../../../query/types";
|
|
2
|
+
import { ResourceDuplicator, DuplicatorDefinition } from "../../../query/core/Resource/ResourceDuplicator";
|
|
3
|
+
export declare class ResourceDuplicatorAgent<D extends DuplicatorDefinition> implements ResourceAgentInstance<D['RESOURCE_DEFINITION']> {
|
|
4
|
+
private _resource;
|
|
5
|
+
private _resources$;
|
|
6
|
+
state$: import("../../../signals/types").ComputeFn<{
|
|
7
|
+
isInitiated: boolean;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
isInitialLoading: boolean;
|
|
10
|
+
isDone: boolean;
|
|
11
|
+
isSuccess: boolean;
|
|
12
|
+
isError: boolean;
|
|
13
|
+
isLocked: boolean;
|
|
14
|
+
isReloading: boolean;
|
|
15
|
+
error: undefined;
|
|
16
|
+
data: undefined;
|
|
17
|
+
args: D["ARGS_ITEM"][];
|
|
18
|
+
} | {
|
|
19
|
+
isInitiated: boolean;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
isInitialLoading: boolean;
|
|
22
|
+
isDone: boolean;
|
|
23
|
+
isSuccess: boolean;
|
|
24
|
+
isError: boolean;
|
|
25
|
+
isLocked: boolean;
|
|
26
|
+
isReloading: boolean;
|
|
27
|
+
error: {} | undefined;
|
|
28
|
+
data: NonNullable<D["RESOURCE_DEFINITION"]["Data"]> | undefined;
|
|
29
|
+
args: NonNullable<D["RESOURCE_DEFINITION"]["Args"]> | undefined;
|
|
30
|
+
}>;
|
|
31
|
+
constructor(_resource: ResourceDuplicator<D>);
|
|
32
|
+
initiate(args: D['ARGS_ITEM'][], force?: boolean): void;
|
|
33
|
+
compareArgs(args: D['ARGS_ITEM'][], otherArgs: D['ARGS_ITEM'][]): boolean;
|
|
34
|
+
private _next;
|
|
35
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Computed, Signal } from "../../../signals";
|
|
2
|
+
export class ResourceDuplicatorAgent {
|
|
3
|
+
_resource;
|
|
4
|
+
_resources$ = Signal.create({
|
|
5
|
+
previous$: null,
|
|
6
|
+
current$: null,
|
|
7
|
+
}, { isDisabled: true });
|
|
8
|
+
state$ = Computed.create(() => {
|
|
9
|
+
const resources = this._resources$.get();
|
|
10
|
+
let prevState;
|
|
11
|
+
const currState = resources.current$?.value$.get();
|
|
12
|
+
// Отлавливаем кейс, когда ресурс был спрошен.
|
|
13
|
+
// На данные момент единсвенная причина сброса - resetAllQueriesCache(),
|
|
14
|
+
// но в будущем могут быть и другие причины, что потребует доработку.
|
|
15
|
+
if (currState && !currState.isInitiated) {
|
|
16
|
+
this._resource.initiate(currState.args, resources.current$);
|
|
17
|
+
return {
|
|
18
|
+
isInitiated: true,
|
|
19
|
+
isLoading: true,
|
|
20
|
+
isInitialLoading: true,
|
|
21
|
+
isDone: false,
|
|
22
|
+
isSuccess: false,
|
|
23
|
+
isError: false,
|
|
24
|
+
isReloading: false,
|
|
25
|
+
error: undefined,
|
|
26
|
+
data: undefined,
|
|
27
|
+
// TODO вообще нет точного представлния, как блокировака доложна работать.
|
|
28
|
+
// Мб тут стоит брать currState.isLocked.
|
|
29
|
+
isLocked: false,
|
|
30
|
+
args: currState.args,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (!currState?.isDone) {
|
|
34
|
+
prevState = resources.previous$?.value;
|
|
35
|
+
}
|
|
36
|
+
// Нет текущего состояния — дефолт
|
|
37
|
+
if (!currState) {
|
|
38
|
+
return {
|
|
39
|
+
isInitiated: false,
|
|
40
|
+
isLoading: false,
|
|
41
|
+
isInitialLoading: false,
|
|
42
|
+
isDone: false,
|
|
43
|
+
isSuccess: false,
|
|
44
|
+
isError: false,
|
|
45
|
+
isLocked: false,
|
|
46
|
+
isReloading: false,
|
|
47
|
+
error: undefined,
|
|
48
|
+
data: undefined,
|
|
49
|
+
args: undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Если идёт загрузка, но есть успешные данные из прошлого запроса — показываем их
|
|
53
|
+
const isShowPrev = currState.isLoading && prevState && prevState.isSuccess;
|
|
54
|
+
return {
|
|
55
|
+
isInitiated: currState.isInitiated || !!prevState,
|
|
56
|
+
isLoading: currState.isLoading,
|
|
57
|
+
isInitialLoading: currState.isLoading && !currState.isDone && !prevState?.isDone,
|
|
58
|
+
isDone: currState.isDone,
|
|
59
|
+
isSuccess: currState.isSuccess,
|
|
60
|
+
isError: currState.isError,
|
|
61
|
+
isLocked: currState.isLocked,
|
|
62
|
+
isReloading: currState.isReloading,
|
|
63
|
+
error: isShowPrev ? prevState.error ?? undefined : currState.error ?? undefined,
|
|
64
|
+
data: isShowPrev ? prevState.data ?? undefined : currState.data ?? undefined,
|
|
65
|
+
args: currState.args ?? undefined,
|
|
66
|
+
};
|
|
67
|
+
}, { isDisabled: true });
|
|
68
|
+
constructor(_resource) {
|
|
69
|
+
this._resource = _resource;
|
|
70
|
+
}
|
|
71
|
+
initiate(args, force = false) {
|
|
72
|
+
const current = this._resources$.peek().current$;
|
|
73
|
+
const cache = this._resource.getQueryCache(args);
|
|
74
|
+
if (!cache) {
|
|
75
|
+
const newCache = this._resource.initiate(args);
|
|
76
|
+
this._next(newCache);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (force || !(cache.value.isDone || cache.value.isLoading)) {
|
|
80
|
+
this._resource.initiate(args, cache);
|
|
81
|
+
}
|
|
82
|
+
if (current !== cache) {
|
|
83
|
+
this._next(cache);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
compareArgs(args, otherArgs) {
|
|
87
|
+
return this._resource.compareArgs(args, otherArgs);
|
|
88
|
+
}
|
|
89
|
+
_next(newCache) {
|
|
90
|
+
const { previous$, current$ } = this._resources$.peek();
|
|
91
|
+
if (!current$) {
|
|
92
|
+
this._resources$.set({
|
|
93
|
+
previous$: null,
|
|
94
|
+
current$: newCache,
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!current$.value$.peek().isDone && previous$?.value$.peek().isDone) {
|
|
99
|
+
this._resources$.set({
|
|
100
|
+
previous$: previous$,
|
|
101
|
+
current$: newCache,
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this._resources$.set({
|
|
106
|
+
previous$: current$,
|
|
107
|
+
current$: newCache,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/query/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './api/createResource';
|
|
2
2
|
export * from './api/createOperation';
|
|
3
3
|
export * from './api/resetAllQueriesCache';
|
|
4
|
+
export * from './api/createResourceDuplicator';
|
|
4
5
|
export * from './SKIP_TOKEN';
|
|
5
6
|
export * from './react/useResourceAgent';
|
|
6
7
|
export * from './react/useResourceRef';
|
package/dist/query/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './api/createResource';
|
|
2
2
|
export * from './api/createOperation';
|
|
3
3
|
export * from './api/resetAllQueriesCache';
|
|
4
|
+
export * from './api/createResourceDuplicator';
|
|
4
5
|
export * from './SKIP_TOKEN';
|
|
5
6
|
export * from './react/useResourceAgent';
|
|
6
7
|
export * from './react/useResourceRef';
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { Prettify,
|
|
1
|
+
import { Prettify, ResourceAgentInstance, ResourceDefinition, ResourceQueryState } from "../../query/types";
|
|
2
2
|
import { SKIP } from "../../query/SKIP_TOKEN";
|
|
3
|
+
import { ResourceDuplicatorAgent } from "../../query/core/Resource/ResourceDuplicatorAgent";
|
|
4
|
+
import { DuplicatorDefinition } from "../../query/core/Resource/ResourceDuplicator";
|
|
3
5
|
type Result<D extends ResourceDefinition> = Prettify<ResourceQueryState<D>>;
|
|
4
|
-
|
|
6
|
+
type WithCreateAgent<D extends ResourceDefinition> = {
|
|
7
|
+
createAgent: () => ResourceAgentInstance<D> | ResourceDuplicatorAgent<DuplicatorDefinition<D>>;
|
|
8
|
+
};
|
|
9
|
+
export declare function useResourceAgent<D extends ResourceDefinition>(res: WithCreateAgent<D>, ...argss: D['Args'] extends void ? [] | [typeof SKIP] : [D['Args'] | typeof SKIP]): Result<D>;
|
|
5
10
|
export {};
|
|
@@ -12,9 +12,18 @@ export function useResourceAgent(res, ...argss) {
|
|
|
12
12
|
}
|
|
13
13
|
return agent;
|
|
14
14
|
});
|
|
15
|
-
if (
|
|
15
|
+
if (!compare(args, prevArgsRef.current, agent)) {
|
|
16
16
|
prevArgsRef.current = args;
|
|
17
17
|
agent.initiate(args);
|
|
18
18
|
}
|
|
19
19
|
return useSignal(agent.state$);
|
|
20
20
|
}
|
|
21
|
+
function compare(args, prevArgs, agent) {
|
|
22
|
+
if (args === SKIP && prevArgs === SKIP) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (args === SKIP || prevArgs === SKIP) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return agent.compareArgs(args, prevArgs);
|
|
29
|
+
}
|
|
@@ -72,7 +72,7 @@ export type ResourceAgentInstance<D extends ResourceDefinition> = {
|
|
|
72
72
|
/** Инициирует запрос с указанными аргументами */
|
|
73
73
|
initiate(args: D["Args"], force?: boolean): void;
|
|
74
74
|
/** Сравнивает аргументы между собой */
|
|
75
|
-
compareArgs(args1: D["Args"], args2: D["Args"]):
|
|
75
|
+
compareArgs(args1: D["Args"], args2: D["Args"]): boolean;
|
|
76
76
|
};
|
|
77
77
|
/**
|
|
78
78
|
* Состояние запроса ресурса
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useEventHandler } from "../../common/react";
|
|
3
3
|
export function useSignal(signal$) {
|
|
4
|
+
const doUpdateRef = React.useRef(true);
|
|
4
5
|
const subscribe = React.useCallback((update) => {
|
|
5
6
|
const subscription = signal$.obs.subscribe(() => {
|
|
6
|
-
|
|
7
|
+
doUpdateRef.current = true;
|
|
8
|
+
queueMicrotask(() => {
|
|
9
|
+
if (!doUpdateRef.current)
|
|
10
|
+
return;
|
|
11
|
+
update();
|
|
12
|
+
});
|
|
7
13
|
});
|
|
8
14
|
return () => {
|
|
9
15
|
subscription.unsubscribe();
|
|
10
16
|
};
|
|
11
17
|
}, [signal$]);
|
|
12
18
|
const getSnapshot = useEventHandler(() => {
|
|
19
|
+
doUpdateRef.current = false;
|
|
13
20
|
return signal$.peek();
|
|
14
21
|
});
|
|
15
22
|
return React.useSyncExternalStore(subscribe, getSnapshot);
|
package/docs/devtools/README.md
CHANGED
|
@@ -74,7 +74,7 @@ DefaultOptions.update({
|
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
**Может пригодиться:**
|
|
77
|
-
- Если в вашей среде
|
|
77
|
+
- Если в вашей среде невозможно установить браузерное расширение
|
|
78
78
|
- Для мобильной отладки
|
|
79
79
|
|
|
80
80
|
---
|
|
@@ -206,7 +206,7 @@ interface DevtoolsLike {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
interface DevtoolsStateLike<T = any> {
|
|
209
|
-
(newState: T): void;
|
|
209
|
+
(newState: T | '$COMPLETED' | '$CLEANED'): void;
|
|
210
210
|
}
|
|
211
211
|
```
|
|
212
212
|
|
package/docs/migrations/0.5.0.md
CHANGED
package/docs/options/README.md
CHANGED
|
@@ -42,7 +42,7 @@ DefaultOptions.update({
|
|
|
42
42
|
// Логирование
|
|
43
43
|
console.error('[RxToolkit Query Error]', error);
|
|
44
44
|
|
|
45
|
-
// Отправка в систему мониторинга
|
|
45
|
+
// Отправка в абстакную систему мониторинга
|
|
46
46
|
Sentry.captureException(error, {
|
|
47
47
|
tags: { source: 'rx-toolkit-query' }
|
|
48
48
|
});
|
|
@@ -64,26 +64,27 @@ DefaultOptions.update({
|
|
|
64
64
|
**Тип:** `(() => string | null) | null`
|
|
65
65
|
**По умолчанию:** `null`
|
|
66
66
|
|
|
67
|
-
Функция для получения имени текущего scope.
|
|
67
|
+
Функция для получения имени текущего scope.
|
|
68
|
+
Можено, например, подключить к DI систему, для раширенного devtools нейминга.
|
|
68
69
|
|
|
69
70
|
```typescript
|
|
70
71
|
import { DefaultOptions } from '@fozy-labs/rx-toolkit';
|
|
71
72
|
|
|
72
|
-
// SSR: изоляция данных между запросами
|
|
73
|
-
let currentRequestId: string | null = null;
|
|
74
|
-
|
|
75
73
|
DefaultOptions.update({
|
|
76
|
-
getScopeName: () =>
|
|
74
|
+
getScopeName: () => MyDiAbsractDi.getCurrentScopeName(),
|
|
77
75
|
});
|
|
78
76
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
// Объявляем класс
|
|
78
|
+
class Counter {
|
|
79
|
+
value$ = Signal.create(0, '{scope}/Counter/value$');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// В другом месте приложения
|
|
83
|
+
function ChannelCounter() {
|
|
84
|
+
const counter = MyDiAbsractDi.resolve<Counter>('Counter', 'ChannelScope');
|
|
85
|
+
console.log(counter.value$()); // Devtools покажет имя сигнала как "ChannelScope/Counter/value$"
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
87
88
|
```
|
|
88
89
|
|
|
89
90
|
---
|
package/docs/signals/README.md
CHANGED
|
@@ -19,11 +19,11 @@ RxSignals — это реактивная система управления с
|
|
|
19
19
|
```typescript
|
|
20
20
|
import { Signal } from '@fozy-labs/rx-toolkit';
|
|
21
21
|
|
|
22
|
-
const name =
|
|
23
|
-
const age =
|
|
22
|
+
const name = Signal.create('John');
|
|
23
|
+
const age = Signal.create(25);
|
|
24
24
|
|
|
25
25
|
// Чтение значения (с отслеживанием зависимостей)
|
|
26
|
-
console.log(name
|
|
26
|
+
console.log(name()); // "John"
|
|
27
27
|
|
|
28
28
|
// Чтение значения без отслеживания
|
|
29
29
|
console.log(name.peek()); // "John"
|
|
@@ -41,7 +41,7 @@ subscription.unsubscribe();
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
**API Signal:**
|
|
44
|
-
- `get()` — получить значение и зарегистрировать зависимость (для использования внутри Computed/Effect)
|
|
44
|
+
- `()`|`get()` — получить значение и зарегистрировать зависимость (для использования внутри Computed/Effect)
|
|
45
45
|
- `peek()` — получить значение без регистрации зависимости
|
|
46
46
|
- `set(value)` — установить новое значение
|
|
47
47
|
- `obs` — RxJS Observable для подписки на изменения
|
|
@@ -51,58 +51,42 @@ subscription.unsubscribe();
|
|
|
51
51
|
Создает вычисляемое значение, которое автоматически обновляется при изменении зависимостей.
|
|
52
52
|
|
|
53
53
|
```typescript
|
|
54
|
-
import { Signal
|
|
54
|
+
import { Signal } from '@fozy-labs/rx-toolkit';
|
|
55
55
|
|
|
56
|
-
const firstName =
|
|
57
|
-
const lastName =
|
|
56
|
+
const firstName = Signal.create('John');
|
|
57
|
+
const lastName = Signal.create('Doe');
|
|
58
58
|
|
|
59
|
-
const fullName =
|
|
59
|
+
const fullName = Signal.compute(() => `${firstName()} ${lastName()}`);
|
|
60
60
|
|
|
61
|
-
console.log(fullName
|
|
61
|
+
console.log(fullName()); // "John Doe"
|
|
62
62
|
|
|
63
63
|
firstName.set('Jane');
|
|
64
|
-
console.log(fullName
|
|
64
|
+
console.log(fullName()); // "Jane Doe"
|
|
65
65
|
|
|
66
66
|
// Подписка на изменения
|
|
67
67
|
fullName.obs.subscribe(name => console.log(name));
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
**API Computed:**
|
|
71
|
-
- `get()` — получить вычисленное значение с регистрацией зависимости
|
|
71
|
+
- `()`|`get()` — получить вычисленное значение с регистрацией зависимости
|
|
72
72
|
- `peek()` — получить значение без регистрации зависимости
|
|
73
73
|
- `obs` — RxJS Observable для подписки на изменения
|
|
74
74
|
|
|
75
75
|
Также на данный момент Computed
|
|
76
76
|
|
|
77
|
-
+Важно: отличие от RxJS
|
|
78
|
-
+
|
|
79
|
-
+ - Computed: по умолчанию `Computed.obs` применяет `distinctUntilChanged()` — это значит, что подписчики не будут получать повторных эмиссий, если новое значение строго равно (`===`) предыдущему. Такое поведение предотвращает лишние рендеры и обработки, когда значение фактически не изменилось.
|
|
80
|
-
+ - Signal: базовый `Signal` использует `BehaviorSubject` и при вызове `set()` будет эмитить значение независимо от того, изменилось оно или нет (если вам нужно предотвратить повторные эмиссии для сигнала, применяйте операторы RxJS к `signal.obs`, например `signal.obs.pipe(distinctUntilChanged())`).
|
|
81
|
-
+
|
|
82
|
-
+Пример: если у вас есть `const total = Signal.compute(() => a.get() + b.get())`, то при изменении `a` или `b` `total.obs` сработает только если новое значение суммы отличается от предыдущего (по `===`). Если нужен особый критерий сравнения, используйте `distinctUntilChanged` с собственной функцией сравнения:
|
|
83
|
-
+
|
|
84
|
-
+```ts
|
|
85
|
-
+import { distinctUntilChanged } from 'rxjs';
|
|
86
|
-
+
|
|
87
|
-
+total.obs.pipe(distinctUntilChanged((prev, next) => /* ваша логика */));
|
|
88
|
-
+```
|
|
89
|
-
+
|
|
90
|
-
+Рекомендация: рассчитывайте на то, что `Computed` избавляет от лишних эмиссий по-умолчанию; если вам нужно другое поведение, применяйте RxJS-операторы к `obs` или создавайте `Computed`, возвращающий другую форму данных (например объект с версией) для более тонкого контроля.
|
|
91
|
-
+
|
|
92
|
-
|
|
93
77
|
### Effect
|
|
94
78
|
|
|
95
79
|
Создает побочный эффект, который автоматически выполняется при изменении используемых сигналов.
|
|
96
80
|
|
|
97
81
|
```typescript
|
|
98
|
-
import { Signal
|
|
82
|
+
import { Signal } from '@fozy-labs/rx-toolkit';
|
|
99
83
|
|
|
100
|
-
const count =
|
|
101
|
-
const message =
|
|
84
|
+
const count = Signal.create(0);
|
|
85
|
+
const message = Signal.create('Hello');
|
|
102
86
|
|
|
103
|
-
const effect =
|
|
87
|
+
const effect = Signal.effect(() => {
|
|
104
88
|
// Выведет: "Hello: 0" при инициализации
|
|
105
|
-
console.log(`${message
|
|
89
|
+
console.log(`${message()}: ${count()}`);
|
|
106
90
|
});
|
|
107
91
|
|
|
108
92
|
count.set(1); // Выведет: "Hello: 1"
|
|
@@ -117,8 +101,9 @@ effect.unsubscribe();
|
|
|
117
101
|
Effect поддерживает возврат функции очистки, которая вызывается перед следующим выполнением или при отписке:
|
|
118
102
|
|
|
119
103
|
```typescript
|
|
120
|
-
const effect =
|
|
121
|
-
|
|
104
|
+
const effect = Signal.effect(() => {
|
|
105
|
+
count(); // Создаем подписку на count (тк не работает при асинхронных операциях)
|
|
106
|
+
const timer = setInterval(() => count(), 1000);
|
|
122
107
|
|
|
123
108
|
// Cleanup - вызывается перед повторным выполнением эффекта
|
|
124
109
|
return () => {
|
|
@@ -127,43 +112,35 @@ const effect = new Effect(() => {
|
|
|
127
112
|
});
|
|
128
113
|
```
|
|
129
114
|
|
|
130
|
-
## Функциональный стиль
|
|
115
|
+
## Функциональный vs классовый стиль
|
|
131
116
|
|
|
132
|
-
|
|
117
|
+
RxSignals поддерживает как функциональный, так и классовый стили создания сигналов, позволяя выбрать подход в зависимости от предпочтений и архитектуры приложения.
|
|
118
|
+
#### Функциональный стиль (рекомендуемый)
|
|
133
119
|
|
|
134
|
-
|
|
120
|
+
Используйте статические методы `Signal.create`,`Signal.compute` и `Signal.effect` для создания сигналов.
|
|
121
|
+
Этот стиль лаконичен, похож на SolidJS и подходит для большинства случаев:
|
|
122
|
+
|
|
123
|
+
```tszz
|
|
135
124
|
import { Signal } from '@fozy-labs/rx-toolkit';
|
|
136
125
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Computed в функциональном стиле
|
|
142
|
-
doubled$ = Signal.compute(() => this.count$() * 2);
|
|
143
|
-
squared$ = Signal.compute(() => (this.doubled$() / 2) ** 2);
|
|
144
|
-
|
|
145
|
-
increment = () => this.count$.set(this.count$.peek() + 1);
|
|
146
|
-
decrement = () => this.count$.set(this.count$.peek() - 1);
|
|
147
|
-
reset = () => this.count$.set(0);
|
|
148
|
-
}
|
|
126
|
+
const count = Signal.create(0);
|
|
127
|
+
const doubled = Signal.compute(() => count() * 2);
|
|
128
|
+
const logEffect = Signal.effect(() => console.log(doubled()));
|
|
129
|
+
```
|
|
149
130
|
|
|
150
|
-
|
|
131
|
+
#### Классовый стиль
|
|
151
132
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
133
|
+
Создавайте экземпляры классов Signal, Computed и Effect напрямую.
|
|
134
|
+
Этот стиль более явный, похож на RxJs и полезен для наследования или сложной логики,
|
|
135
|
+
учтите, что вызов `()` недоступен и нужно использовать `get()`:
|
|
155
136
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
console.log(store.doubled$()); // 2
|
|
159
|
-
console.log(store.squared$()); // 1
|
|
160
|
-
```
|
|
137
|
+
```ts
|
|
138
|
+
import { Signal, Computed, Effect } from '@fozy-labs/rx-toolkit';
|
|
161
139
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
- `signal$.obs` — RxJS Observable
|
|
140
|
+
const count = new Signal(0);
|
|
141
|
+
const doubled = new Computed(() => count.get() * 2);
|
|
142
|
+
const logEffect = new Effect(() => console.log(doubled.get()));
|
|
143
|
+
```
|
|
167
144
|
|
|
168
145
|
### ReadonlySignal
|
|
169
146
|
|
|
@@ -246,8 +246,8 @@ class CounterStore {
|
|
|
246
246
|
count$ = Signal.create(0, 'counter');
|
|
247
247
|
doubled$ = Signal.compute(() => this.count$() * 2);
|
|
248
248
|
|
|
249
|
-
increment = () => this.count$.set(this.count
|
|
250
|
-
decrement = () => this.count$.set(this.count
|
|
249
|
+
increment = () => this.count$.set(this.count$() + 1);
|
|
250
|
+
decrement = () => this.count$.set(this.count$() - 1);
|
|
251
251
|
reset = () => this.count$.set(0);
|
|
252
252
|
}
|
|
253
253
|
|