@pravosleva/reactive-engine 1.2.4-beta → 1.2.6-beta
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 +0 -3
- package/dist/react/hooks/useReactiveValue/useReactiveValue.cjs +1 -1
- package/dist/react/hooks/useReactiveValue/useReactiveValue.cjs.map +1 -1
- package/dist/react/hooks/useReactiveValue/useReactiveValue.d.ts +36 -3
- package/dist/react/hooks/useReactiveValue/useReactiveValue.d.ts.map +1 -1
- package/dist/react/hooks/useReactiveValue/useReactiveValue.mjs +10 -6
- package/dist/react/hooks/useReactiveValue/useReactiveValue.mjs.map +1 -1
- package/dist/react/hooks/useReactiveValue0/useReactiveValue0.cjs +1 -1
- package/dist/react/hooks/useReactiveValue0/useReactiveValue0.cjs.map +1 -1
- package/dist/react/hooks/useReactiveValue0/useReactiveValue0.d.ts +38 -2
- package/dist/react/hooks/useReactiveValue0/useReactiveValue0.d.ts.map +1 -1
- package/dist/react/hooks/useReactiveValue0/useReactiveValue0.mjs +11 -8
- package/dist/react/hooks/useReactiveValue0/useReactiveValue0.mjs.map +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.mjs +4 -5
- package/package.json +1 -1
- package/dist/_virtual/_rolldown/runtime.cjs +0 -1
- package/dist/react/createObserver.cjs +0 -2
- package/dist/react/createObserver.cjs.map +0 -1
- package/dist/react/createObserver.d.ts +0 -70
- package/dist/react/createObserver.d.ts.map +0 -1
- package/dist/react/createObserver.mjs +0 -62
- package/dist/react/createObserver.mjs.map +0 -1
- package/dist/react/createObserver.test.d.ts +0 -2
- package/dist/react/createObserver.test.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@ A lightweight, type-safe reactive engine built with TypeScript, featuring Depend
|
|
|
5
5
|
- 🇬🇧 [In English](https://pravosleva.pro/reactive-engine/en)
|
|
6
6
|
- 🇷🇺 [In Russian](https://pravosleva.pro/reactive-engine)
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
## 🎯 What Problems This Library Solves
|
|
10
9
|
|
|
11
10
|
When building large-scale React applications, developers constantly run into architectural bottlenecks imposed by built-in state tools. `@pravosleva/reactive-engine` is designed to elegantly solve the following pain points:
|
|
@@ -108,9 +107,7 @@ const counter = engine.use(logic.counter)
|
|
|
108
107
|
<template>
|
|
109
108
|
<div>
|
|
110
109
|
<div>Vue 3 Signal Example</div>
|
|
111
|
-
|
|
112
110
|
<code>{{ counter }}</code>
|
|
113
|
-
|
|
114
111
|
<div>
|
|
115
112
|
<button @click="logic.inc">
|
|
116
113
|
+ INC
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("react");var t=t=>{let n=(0,e.useMemo)(()=>typeof t==`function`?t():t,[t]),
|
|
1
|
+
let e=require("react");var t=t=>{let n=typeof t==`function`,r=(0,e.useRef)(t);(0,e.useEffect)(()=>{r.current=t},[t]);let i=(0,e.useMemo)(()=>typeof t==`function`?t():t,[n?void 0:t]),a=(0,e.useCallback)(e=>i.subscribe(e),[i]),o=(0,e.useCallback)(()=>i.value,[i]);return(0,e.useEffect)(()=>()=>{n&&i&&typeof i.destroy==`function`&&i.destroy()},[i,n]),(0,e.useSyncExternalStore)(a,o)};exports.useReactiveValue=t;
|
|
2
2
|
//# sourceMappingURL=useReactiveValue.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useReactiveValue.cjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue/useReactiveValue.ts"],"sourcesContent":["import { useSyncExternalStore, useCallback, useMemo, useEffect } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"useReactiveValue.cjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue/useReactiveValue.ts"],"sourcesContent":["import { useSyncExternalStore, useCallback, useMemo, useEffect, useRef } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n * Нативный React-хук для извлечения текущего значения из реактивных примитивов ядра\n * (Signal, Computed, Resource) и автоматического управления подпиской на рендеринг.\n *\n * Опирается на каноничную шим-прослойку `useSyncExternalStore`, что гарантирует 100%\n * совместимость с механизмами батчинга обновлений React 18+ и Concurrent Mode (защита от Tearing).\n *\n * ### Особенности управления памятью:\n * 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет\n * только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,\n * что сохраняет глобальное состояние системы в безопасности.\n * 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана\n * функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.\n * При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.\n *\n * @template T - Тип данных, инкапсулированных внутри реактивного элемента.\n * @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.\n * @returns {T} Актуальное синхронизированное значение реактивного элемента.\n *\n * @example\n * ```tsx\n * import { useReactiveValue } from '@pravosleva/reactive-engine/react';\n * import { userInfoService } from '~/store';\n *\n * // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса\n * export const CounterDisplay = () => {\n * const counter = useReactiveValue(userInfoService.counter);\n * return <span>Значение: {counter}</span>;\n * };\n *\n * // Сценарий 2: Использование ленивой фабрики для локальных вычислений (авто-очистка ядра при анмаунте)\n * export const FilteredList = ({ query }: { query: string }) => {\n * const filteredData = useReactiveValue(() =>\n * engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))\n * );\n * return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;\n * };\n * ```\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n const isFactory = typeof input === 'function';\n\n // NOTE: СТАБИЛИЗАЦИЯ ФАБРИКИ: Сохраняем ссылку на функцию в ref,\n // чтобы не перезапускать вычисления, если разработчик передал инлайн-стрелочную функцию.\n const factoryRef = useRef(input);\n useEffect(() => {\n factoryRef.current = input;\n }, [input]);\n\n // Вычисляем элемент строго один раз при инициализации, либо при изменении стабильной ссылки на готовый сигнал\n const reactiveItem = useMemo(() => {\n if (typeof input === 'function') {\n // Вызываем фабрику только при первом проходе\n return input();\n }\n return input; // Если передан готовый сигнал/ресурс — используем его напрямую\n }, [isFactory ? undefined : input]); // Стабильный массив зависимостей для фабрик!\n\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback)\n },\n [reactiveItem]\n )\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value\n }, [reactiveItem])\n\n // NOTE: БЕЗОПАСНАЯ ОЧИСТКА ПАМЯТИ:\n // Вызываем .destroy() ТОЛЬКО если объект пришел из локальной фабрики `() => engine.computed(...)`.\n // Если это глобальный сигнал из сервиса — мы делаем только стандартный unmount подписки через useSyncExternalStore.\n useEffect(() => {\n return () => {\n if (isFactory && reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n }\n }, [reactiveItem, isFactory])\n\n return useSyncExternalStore(subscribe, getSnapshot)\n}\n"],"mappings":"uBAkDA,IAAa,EAAuB,GAA+B,CACjE,IAAM,EAAY,OAAO,GAAU,WAI7B,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAK,GAC/B,EAAA,EAAA,UAAA,KAAgB,CACd,EAAW,QAAU,CACvB,EAAG,CAAC,CAAK,CAAC,EAGV,IAAM,GAAA,EAAA,EAAA,QAAA,KACA,OAAO,GAAU,WAEZ,EAAM,EAER,EACN,CAAC,EAAY,IAAA,GAAY,CAAK,CAAC,EAE5B,GAAA,EAAA,EAAA,YAAA,CACH,GACQ,EAAa,UAAU,CAAa,EAE7C,CAAC,CAAY,CACf,EAEM,GAAA,EAAA,EAAA,YAAA,KACG,EAAa,MACnB,CAAC,CAAY,CAAC,EAajB,OARA,EAAA,EAAA,UAAA,SACe,CACP,GAAa,GAAgB,OAAO,EAAa,SAAY,YAC/D,EAAa,QAAQ,CAEzB,EACC,CAAC,EAAc,CAAS,CAAC,GAE5B,EAAA,EAAA,qBAAA,CAA4B,EAAW,CAAW,CACpD"}
|
|
@@ -6,10 +6,43 @@ interface ObservableItem<T> {
|
|
|
6
6
|
}
|
|
7
7
|
type ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Нативный React-хук для извлечения текущего значения из реактивных примитивов ядра
|
|
10
|
+
* (Signal, Computed, Resource) и автоматического управления подпиской на рендеринг.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* Опирается на каноничную шим-прослойку `useSyncExternalStore`, что гарантирует 100%
|
|
13
|
+
* совместимость с механизмами батчинга обновлений React 18+ и Concurrent Mode (защита от Tearing).
|
|
14
|
+
*
|
|
15
|
+
* ### Особенности управления памятью:
|
|
16
|
+
* 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет
|
|
17
|
+
* только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,
|
|
18
|
+
* что сохраняет глобальное состояние системы в безопасности.
|
|
19
|
+
* 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана
|
|
20
|
+
* функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.
|
|
21
|
+
* При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.
|
|
22
|
+
*
|
|
23
|
+
* @template T - Тип данных, инкапсулированных внутри реактивного элемента.
|
|
24
|
+
* @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.
|
|
25
|
+
* @returns {T} Актуальное синхронизированное значение реактивного элемента.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* import { useReactiveValue } from '@pravosleva/reactive-engine/react';
|
|
30
|
+
* import { userInfoService } from '~/store';
|
|
31
|
+
*
|
|
32
|
+
* // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса
|
|
33
|
+
* export const CounterDisplay = () => {
|
|
34
|
+
* const counter = useReactiveValue(userInfoService.counter);
|
|
35
|
+
* return <span>Значение: {counter}</span>;
|
|
36
|
+
* };
|
|
37
|
+
*
|
|
38
|
+
* // Сценарий 2: Использование ленивой фабрики для локальных вычислений (авто-очистка ядра при анмаунте)
|
|
39
|
+
* export const FilteredList = ({ query }: { query: string }) => {
|
|
40
|
+
* const filteredData = useReactiveValue(() =>
|
|
41
|
+
* engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))
|
|
42
|
+
* );
|
|
43
|
+
* return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;
|
|
44
|
+
* };
|
|
45
|
+
* ```
|
|
13
46
|
*/
|
|
14
47
|
export declare const useReactiveValue: <T>(input: ReactiveInput<T>) => T;
|
|
15
48
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useReactiveValue.d.ts","sourceRoot":"","sources":["../../../../src/react/hooks/useReactiveValue/useReactiveValue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC,UAAU,cAAc,CAAC,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,SAAS,CAAA;IAC9C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,KAAK,aAAa,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;AAErE
|
|
1
|
+
{"version":3,"file":"useReactiveValue.d.ts","sourceRoot":"","sources":["../../../../src/react/hooks/useReactiveValue/useReactiveValue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC,UAAU,cAAc,CAAC,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,SAAS,CAAA;IAC9C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,KAAK,aAAa,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,OAAO,aAAa,CAAC,CAAC,CAAC,KAAG,CA0C7D,CAAA"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { useCallback as e, useEffect as t, useMemo as n,
|
|
1
|
+
import { useCallback as e, useEffect as t, useMemo as n, useRef as r, useSyncExternalStore as i } from "react";
|
|
2
2
|
//#region src/react/hooks/useReactiveValue/useReactiveValue.ts
|
|
3
|
-
var
|
|
4
|
-
let
|
|
3
|
+
var a = (a) => {
|
|
4
|
+
let o = typeof a == "function", s = r(a);
|
|
5
|
+
t(() => {
|
|
6
|
+
s.current = a;
|
|
7
|
+
}, [a]);
|
|
8
|
+
let c = n(() => typeof a == "function" ? a() : a, [o ? void 0 : a]), l = e((e) => c.subscribe(e), [c]), u = e(() => c.value, [c]);
|
|
5
9
|
return t(() => () => {
|
|
6
|
-
|
|
7
|
-
}, [
|
|
10
|
+
o && c && typeof c.destroy == "function" && c.destroy();
|
|
11
|
+
}, [c, o]), i(l, u);
|
|
8
12
|
};
|
|
9
13
|
//#endregion
|
|
10
|
-
export {
|
|
14
|
+
export { a as useReactiveValue };
|
|
11
15
|
|
|
12
16
|
//# sourceMappingURL=useReactiveValue.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useReactiveValue.mjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue/useReactiveValue.ts"],"sourcesContent":["import { useSyncExternalStore, useCallback, useMemo, useEffect } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"useReactiveValue.mjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue/useReactiveValue.ts"],"sourcesContent":["import { useSyncExternalStore, useCallback, useMemo, useEffect, useRef } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T;\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n * Нативный React-хук для извлечения текущего значения из реактивных примитивов ядра\n * (Signal, Computed, Resource) и автоматического управления подпиской на рендеринг.\n *\n * Опирается на каноничную шим-прослойку `useSyncExternalStore`, что гарантирует 100%\n * совместимость с механизмами батчинга обновлений React 18+ и Concurrent Mode (защита от Tearing).\n *\n * ### Особенности управления памятью:\n * 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет\n * только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,\n * что сохраняет глобальное состояние системы в безопасности.\n * 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана\n * функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.\n * При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.\n *\n * @template T - Тип данных, инкапсулированных внутри реактивного элемента.\n * @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.\n * @returns {T} Актуальное синхронизированное значение реактивного элемента.\n *\n * @example\n * ```tsx\n * import { useReactiveValue } from '@pravosleva/reactive-engine/react';\n * import { userInfoService } from '~/store';\n *\n * // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса\n * export const CounterDisplay = () => {\n * const counter = useReactiveValue(userInfoService.counter);\n * return <span>Значение: {counter}</span>;\n * };\n *\n * // Сценарий 2: Использование ленивой фабрики для локальных вычислений (авто-очистка ядра при анмаунте)\n * export const FilteredList = ({ query }: { query: string }) => {\n * const filteredData = useReactiveValue(() =>\n * engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))\n * );\n * return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;\n * };\n * ```\n */\nexport const useReactiveValue = <T>(input: ReactiveInput<T>): T => {\n const isFactory = typeof input === 'function';\n\n // NOTE: СТАБИЛИЗАЦИЯ ФАБРИКИ: Сохраняем ссылку на функцию в ref,\n // чтобы не перезапускать вычисления, если разработчик передал инлайн-стрелочную функцию.\n const factoryRef = useRef(input);\n useEffect(() => {\n factoryRef.current = input;\n }, [input]);\n\n // Вычисляем элемент строго один раз при инициализации, либо при изменении стабильной ссылки на готовый сигнал\n const reactiveItem = useMemo(() => {\n if (typeof input === 'function') {\n // Вызываем фабрику только при первом проходе\n return input();\n }\n return input; // Если передан готовый сигнал/ресурс — используем его напрямую\n }, [isFactory ? undefined : input]); // Стабильный массив зависимостей для фабрик!\n\n const subscribe = useCallback(\n (reactCallback: () => void) => {\n return reactiveItem.subscribe(reactCallback)\n },\n [reactiveItem]\n )\n\n const getSnapshot = useCallback(() => {\n return reactiveItem.value\n }, [reactiveItem])\n\n // NOTE: БЕЗОПАСНАЯ ОЧИСТКА ПАМЯТИ:\n // Вызываем .destroy() ТОЛЬКО если объект пришел из локальной фабрики `() => engine.computed(...)`.\n // Если это глобальный сигнал из сервиса — мы делаем только стандартный unmount подписки через useSyncExternalStore.\n useEffect(() => {\n return () => {\n if (isFactory && reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy();\n }\n }\n }, [reactiveItem, isFactory])\n\n return useSyncExternalStore(subscribe, getSnapshot)\n}\n"],"mappings":";;AAkDA,IAAa,KAAuB,MAA+B;CACjE,IAAM,IAAY,OAAO,KAAU,YAI7B,IAAa,EAAO,CAAK;CAC/B,QAAgB;EACd,EAAW,UAAU;CACvB,GAAG,CAAC,CAAK,CAAC;CAGV,IAAM,IAAe,QACf,OAAO,KAAU,aAEZ,EAAM,IAER,GACN,CAAC,IAAY,KAAA,IAAY,CAAK,CAAC,GAE5B,IAAY,GACf,MACQ,EAAa,UAAU,CAAa,GAE7C,CAAC,CAAY,CACf,GAEM,IAAc,QACX,EAAa,OACnB,CAAC,CAAY,CAAC;CAajB,OARA,cACe;EACX,AAAI,KAAa,KAAgB,OAAO,EAAa,WAAY,cAC/D,EAAa,QAAQ;CAEzB,GACC,CAAC,GAAc,CAAS,CAAC,GAErB,EAAqB,GAAW,CAAW;AACpD"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("react");var t=t=>{let n=(0,e.useMemo)(()=>typeof t==`function`?t():t,[t]),[
|
|
1
|
+
let e=require("react");var t=t=>{let n=typeof t==`function`,r=(0,e.useRef)(t);(0,e.useEffect)(()=>{r.current=t},[t]);let i=(0,e.useMemo)(()=>typeof t==`function`?t():t,[n?void 0:t]),[a,o]=(0,e.useState)(i.value),s=(0,e.useRef)(o);return(0,e.useEffect)(()=>{s.current=o},[o]),a!==i.value&&o(i.value),(0,e.useEffect)(()=>{let e=i.subscribe(e=>{s.current(e)});return()=>{e(),n&&i&&typeof i.destroy==`function`&&i.destroy()}},[i,n]),a};exports.useReactiveValue0=t;
|
|
2
2
|
//# sourceMappingURL=useReactiveValue0.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useReactiveValue0.cjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue0/useReactiveValue0.ts"],"sourcesContent":["import { useState, useEffect, useRef, useMemo } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"useReactiveValue0.cjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue0/useReactiveValue0.ts"],"sourcesContent":["import { useState, useEffect, useRef, useMemo } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n * Изоморфный фоллбэк-хук для извлечения текущего значения из реактивных примитивов ядра\n * (Signal, Computed, Resource) и управления подпиской в окружениях **React 16.8+ и React 17**.\n *\n * В отличие от современной версии `useReactiveValue`, данный хук спроектирован на базе классической\n * связки `useState` + `useEffect`. Он содержит встроенный механизм упреждающей синхронизации\n * (Pre-emptive Synchronization Pattern), предотвращающий рассинхронизацию данных (Tearing) и мерцание UI\n * во время асинхронных микрозадач планировщика React.\n *\n * ### Особенности управления памятью:\n * 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет\n * только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,\n * что сохраняет глобальное состояние системы в безопасности.\n * 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана\n * функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.\n * При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.\n *\n * @template T - Тип данных, инкапсулированных внутри реактивного элемента.\n * @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.\n * @returns {T} Актуальное синхронизированное значение реактивного элемента.\n *\n * @example\n * ```tsx\n * import { useReactiveValue0 } from '@pravosleva/reactive-engine/react';\n * import { userInfoService } from '~/store';\n *\n * // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса в legacy-компоненте\n * export const LegacyCounterDisplay = () => {\n * const counter = useReactiveValue0(userInfoService.counter);\n * return <span>Значение: {counter}</span>;\n * };\n *\n * // Сценарий 2: Использование ленивой фабрики во React 17 проектах (авто-очистка ядра при анмаунте)\n * export const LegacyFilteredList = ({ query }: { query: string }) => {\n * const filteredData = useReactiveValue0(() =>\n * engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))\n * );\n * return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;\n * };\n * ```\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const isFactory = typeof input === 'function';\n\n // NOTE: СТАБИЛИЗАЦИЯ ИНПУТА: защищает от бесконечных циклов при инлайн-стрелочных фабриках\n const factoryRef = useRef(input);\n useEffect(() => {\n factoryRef.current = input;\n }, [input]);\n\n // Вычисляем элемент строго 1 раз при монтировании, либо при смене ссылки на готовый сигнал\n const reactiveItem = useMemo(() => {\n if (typeof input === 'function') {\n return input(); // Ленивый вызов фабрики ровно один раз за жизнь компонента\n }\n return input;\n }, [isFactory ? undefined : input]); // Игнорируем ссылки инлайн-функций\n\n const [state, setState] = useState<T>(reactiveItem.value)\n const setStateRef = useRef(setState)\n\n // Держим ссылку на setState всегда актуальной для безопасных асинхронных вызовов\n useEffect(() => {\n setStateRef.current = setState\n }, [setState])\n\n // NOTE: СИНХРОНИЗАЦИЯ НА ОПЕРЕЖЕНИЕ (Теккущее значение):\n // Если между фазой рендера и выполнением useEffect значение в ядре библиотеки успело измениться,\n // мы синхронно подтягиваем актуальный стейт, исключая \"слепые пятна\" и мерцание UI.\n if (state !== reactiveItem.value) {\n setState(reactiveItem.value);\n }\n\n useEffect(() => {\n // Привязываем подписку ядра к триггеру обновлений React\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue)\n })\n\n return () => {\n unsubscribe();\n\n // NOTE: БЕЗОПАСНАЯ ОЧИСТКА ПАМЯТИ:\n // Метод .destroy() вызывается СТРОГО если объект был порожден локальной фабрикой.\n // Глобальные сигналы сервисов остаются в безопасности и не стираются из движка!\n if (isFactory && reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy()\n }\n }\n }, [reactiveItem, isFactory])\n\n return state\n}\n"],"mappings":"uBAoDA,IAAa,EAAwB,GAA+B,CAClE,IAAM,EAAY,OAAO,GAAU,WAG7B,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAK,GAC/B,EAAA,EAAA,UAAA,KAAgB,CACd,EAAW,QAAU,CACvB,EAAG,CAAC,CAAK,CAAC,EAGV,IAAM,GAAA,EAAA,EAAA,QAAA,KACA,OAAO,GAAU,WACZ,EAAM,EAER,EACN,CAAC,EAAY,IAAA,GAAY,CAAK,CAAC,EAE5B,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAwB,EAAa,KAAK,EAClD,GAAA,EAAA,EAAA,OAAA,CAAqB,CAAQ,EAgCnC,OA7BA,EAAA,EAAA,UAAA,KAAgB,CACd,EAAY,QAAU,CACxB,EAAG,CAAC,CAAQ,CAAC,EAKT,IAAU,EAAa,OACzB,EAAS,EAAa,KAAK,GAG7B,EAAA,EAAA,UAAA,KAAgB,CAEd,IAAM,EAAc,EAAa,UAAW,GAAa,CACvD,EAAY,QAAQ,CAAQ,CAC9B,CAAC,EAED,UAAa,CACX,EAAY,EAKR,GAAa,GAAgB,OAAO,EAAa,SAAY,YAC/D,EAAa,QAAQ,CAEzB,CACF,EAAG,CAAC,EAAc,CAAS,CAAC,EAErB,CACT"}
|
|
@@ -6,9 +6,45 @@ interface ObservableItem<T> {
|
|
|
6
6
|
}
|
|
7
7
|
type ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>);
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Изоморфный фоллбэк-хук для извлечения текущего значения из реактивных примитивов ядра
|
|
10
|
+
* (Signal, Computed, Resource) и управления подпиской в окружениях **React 16.8+ и React 17**.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
+
* В отличие от современной версии `useReactiveValue`, данный хук спроектирован на базе классической
|
|
13
|
+
* связки `useState` + `useEffect`. Он содержит встроенный механизм упреждающей синхронизации
|
|
14
|
+
* (Pre-emptive Synchronization Pattern), предотвращающий рассинхронизацию данных (Tearing) и мерцание UI
|
|
15
|
+
* во время асинхронных микрозадач планировщика React.
|
|
16
|
+
*
|
|
17
|
+
* ### Особенности управления памятью:
|
|
18
|
+
* 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет
|
|
19
|
+
* только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,
|
|
20
|
+
* что сохраняет глобальное состояние системы в безопасности.
|
|
21
|
+
* 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана
|
|
22
|
+
* функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.
|
|
23
|
+
* При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.
|
|
24
|
+
*
|
|
25
|
+
* @template T - Тип данных, инкапсулированных внутри реактивного элемента.
|
|
26
|
+
* @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.
|
|
27
|
+
* @returns {T} Актуальное синхронизированное значение реактивного элемента.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* import { useReactiveValue0 } from '@pravosleva/reactive-engine/react';
|
|
32
|
+
* import { userInfoService } from '~/store';
|
|
33
|
+
*
|
|
34
|
+
* // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса в legacy-компоненте
|
|
35
|
+
* export const LegacyCounterDisplay = () => {
|
|
36
|
+
* const counter = useReactiveValue0(userInfoService.counter);
|
|
37
|
+
* return <span>Значение: {counter}</span>;
|
|
38
|
+
* };
|
|
39
|
+
*
|
|
40
|
+
* // Сценарий 2: Использование ленивой фабрики во React 17 проектах (авто-очистка ядра при анмаунте)
|
|
41
|
+
* export const LegacyFilteredList = ({ query }: { query: string }) => {
|
|
42
|
+
* const filteredData = useReactiveValue0(() =>
|
|
43
|
+
* engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))
|
|
44
|
+
* );
|
|
45
|
+
* return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;
|
|
46
|
+
* };
|
|
47
|
+
* ```
|
|
12
48
|
*/
|
|
13
49
|
export declare const useReactiveValue0: <T>(input: ReactiveInput<T>) => T;
|
|
14
50
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useReactiveValue0.d.ts","sourceRoot":"","sources":["../../../../src/react/hooks/useReactiveValue0/useReactiveValue0.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC,UAAU,cAAc,CAAC,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;IACjB,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,SAAS,CAAA;IAC9C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,KAAK,aAAa,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;AAErE
|
|
1
|
+
{"version":3,"file":"useReactiveValue0.d.ts","sourceRoot":"","sources":["../../../../src/react/hooks/useReactiveValue0/useReactiveValue0.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC,UAAU,cAAc,CAAC,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;IACjB,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,SAAS,CAAA;IAC9C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,KAAK,aAAa,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EAAE,OAAO,aAAa,CAAC,CAAC,CAAC,KAAG,CAmD9D,CAAA"}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { useEffect as e, useMemo as t, useRef as n, useState as r } from "react";
|
|
2
2
|
//#region src/react/hooks/useReactiveValue0/useReactiveValue0.ts
|
|
3
3
|
var i = (i) => {
|
|
4
|
-
let a =
|
|
4
|
+
let a = typeof i == "function", o = n(i);
|
|
5
|
+
e(() => {
|
|
6
|
+
o.current = i;
|
|
7
|
+
}, [i]);
|
|
8
|
+
let s = t(() => typeof i == "function" ? i() : i, [a ? void 0 : i]), [c, l] = r(s.value), u = n(l);
|
|
5
9
|
return e(() => {
|
|
6
|
-
|
|
7
|
-
}, [
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
c.current(e);
|
|
10
|
+
u.current = l;
|
|
11
|
+
}, [l]), c !== s.value && l(s.value), e(() => {
|
|
12
|
+
let e = s.subscribe((e) => {
|
|
13
|
+
u.current(e);
|
|
11
14
|
});
|
|
12
15
|
return () => {
|
|
13
|
-
e(), a && typeof
|
|
16
|
+
e(), a && s && typeof s.destroy == "function" && s.destroy();
|
|
14
17
|
};
|
|
15
|
-
}, [a]),
|
|
18
|
+
}, [s, a]), c;
|
|
16
19
|
};
|
|
17
20
|
//#endregion
|
|
18
21
|
export { i as useReactiveValue0 };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useReactiveValue0.mjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue0/useReactiveValue0.ts"],"sourcesContent":["import { useState, useEffect, useRef, useMemo } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"useReactiveValue0.mjs","names":[],"sources":["../../../../src/react/hooks/useReactiveValue0/useReactiveValue0.ts"],"sourcesContent":["import { useState, useEffect, useRef, useMemo } from 'react'\nimport { CleanupFn } from '../../../core'\n\ninterface ObservableItem<T> {\n readonly value: T\n subscribe: (cb: (val: T) => void) => CleanupFn\n destroy?: () => void\n}\n\ntype ReactiveInput<T> = ObservableItem<T> | (() => ObservableItem<T>)\n\n/**\n * Изоморфный фоллбэк-хук для извлечения текущего значения из реактивных примитивов ядра\n * (Signal, Computed, Resource) и управления подпиской в окружениях **React 16.8+ и React 17**.\n *\n * В отличие от современной версии `useReactiveValue`, данный хук спроектирован на базе классической\n * связки `useState` + `useEffect`. Он содержит встроенный механизм упреждающей синхронизации\n * (Pre-emptive Synchronization Pattern), предотвращающий рассинхронизацию данных (Tearing) и мерцание UI\n * во время асинхронных микрозадач планировщика React.\n *\n * ### Особенности управления памятью:\n * 1. **Глобальные сигналы (Сервисы/Синглтоны):** При размонтировании компонента хук выполняет\n * только стандартную отписку от обновлений. Метод `.destroy()` **не вызывается**,\n * что сохраняет глобальное состояние системы в безопасности.\n * 2. **Локальные фабрики (`() => engine.computed(...)`):** Если в качестве аргумента передана\n * функция-фабрика, хук понимает, что вычисление создано локально для этого экрана.\n * При анмаунте компонента хук автоматически вызовет `.destroy()`, предотвращая утечки памяти в ядре.\n *\n * @template T - Тип данных, инкапсулированных внутри реактивного элемента.\n * @param {ReactiveInput<T>} input - Готовый реактивный элемент ядра или ленивая функция-фабрика, возвращающая его.\n * @returns {T} Актуальное синхронизированное значение реактивного элемента.\n *\n * @example\n * ```tsx\n * import { useReactiveValue0 } from '@pravosleva/reactive-engine/react';\n * import { userInfoService } from '~/store';\n *\n * // Сценарий 1: Прямая подписка на долгоживущий сигнал сервиса в legacy-компоненте\n * export const LegacyCounterDisplay = () => {\n * const counter = useReactiveValue0(userInfoService.counter);\n * return <span>Значение: {counter}</span>;\n * };\n *\n * // Сценарий 2: Использование ленивой фабрики во React 17 проектах (авто-очистка ядра при анмаунте)\n * export const LegacyFilteredList = ({ query }: { query: string }) => {\n * const filteredData = useReactiveValue0(() =>\n * engine.computed(() => userInfoService.list.value.filter(item => item.includes(query)))\n * );\n * return <ul>{filteredData.map(item => <li key={item}>{item}</li>)}</ul>;\n * };\n * ```\n */\nexport const useReactiveValue0 = <T>(input: ReactiveInput<T>): T => {\n const isFactory = typeof input === 'function';\n\n // NOTE: СТАБИЛИЗАЦИЯ ИНПУТА: защищает от бесконечных циклов при инлайн-стрелочных фабриках\n const factoryRef = useRef(input);\n useEffect(() => {\n factoryRef.current = input;\n }, [input]);\n\n // Вычисляем элемент строго 1 раз при монтировании, либо при смене ссылки на готовый сигнал\n const reactiveItem = useMemo(() => {\n if (typeof input === 'function') {\n return input(); // Ленивый вызов фабрики ровно один раз за жизнь компонента\n }\n return input;\n }, [isFactory ? undefined : input]); // Игнорируем ссылки инлайн-функций\n\n const [state, setState] = useState<T>(reactiveItem.value)\n const setStateRef = useRef(setState)\n\n // Держим ссылку на setState всегда актуальной для безопасных асинхронных вызовов\n useEffect(() => {\n setStateRef.current = setState\n }, [setState])\n\n // NOTE: СИНХРОНИЗАЦИЯ НА ОПЕРЕЖЕНИЕ (Теккущее значение):\n // Если между фазой рендера и выполнением useEffect значение в ядре библиотеки успело измениться,\n // мы синхронно подтягиваем актуальный стейт, исключая \"слепые пятна\" и мерцание UI.\n if (state !== reactiveItem.value) {\n setState(reactiveItem.value);\n }\n\n useEffect(() => {\n // Привязываем подписку ядра к триггеру обновлений React\n const unsubscribe = reactiveItem.subscribe((newValue) => {\n setStateRef.current(newValue)\n })\n\n return () => {\n unsubscribe();\n\n // NOTE: БЕЗОПАСНАЯ ОЧИСТКА ПАМЯТИ:\n // Метод .destroy() вызывается СТРОГО если объект был порожден локальной фабрикой.\n // Глобальные сигналы сервисов остаются в безопасности и не стираются из движка!\n if (isFactory && reactiveItem && typeof reactiveItem.destroy === 'function') {\n reactiveItem.destroy()\n }\n }\n }, [reactiveItem, isFactory])\n\n return state\n}\n"],"mappings":";;AAoDA,IAAa,KAAwB,MAA+B;CAClE,IAAM,IAAY,OAAO,KAAU,YAG7B,IAAa,EAAO,CAAK;CAC/B,QAAgB;EACd,EAAW,UAAU;CACvB,GAAG,CAAC,CAAK,CAAC;CAGV,IAAM,IAAe,QACf,OAAO,KAAU,aACZ,EAAM,IAER,GACN,CAAC,IAAY,KAAA,IAAY,CAAK,CAAC,GAE5B,CAAC,GAAO,KAAY,EAAY,EAAa,KAAK,GAClD,IAAc,EAAO,CAAQ;CAgCnC,OA7BA,QAAgB;EACd,EAAY,UAAU;CACxB,GAAG,CAAC,CAAQ,CAAC,GAKT,MAAU,EAAa,SACzB,EAAS,EAAa,KAAK,GAG7B,QAAgB;EAEd,IAAM,IAAc,EAAa,WAAW,MAAa;GACvD,EAAY,QAAQ,CAAQ;EAC9B,CAAC;EAED,aAAa;GAMX,AALA,EAAY,GAKR,KAAa,KAAgB,OAAO,EAAa,WAAY,cAC/D,EAAa,QAAQ;EAEzB;CACF,GAAG,CAAC,GAAc,CAAS,CAAC,GAErB;AACT"}
|
package/dist/react/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./ReactiveEngine4React.cjs"),t=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./ReactiveEngine4React.cjs"),t=require("./hooks/useReactiveSubscription/useReactiveSubscription.cjs"),n=require("./hooks/useReactiveValue/useReactiveValue.cjs"),r=require("./hooks/useReactiveValue0/useReactiveValue0.cjs");require("./hooks/index.cjs"),exports.ReactiveEngine=e.ReactiveEngine4React,exports.useReactiveSubscription=t.useReactiveSubscription,exports.useReactiveValue=n.useReactiveValue,exports.useReactiveValue0=r.useReactiveValue0;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAE7D,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAE7D,OAAO,EAAE,oBAAoB,IAAI,cAAc,EAAE,CAAA;AACjD,cAAc,SAAS,CAAA"}
|
package/dist/react/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ReactiveEngine4React as e } from "./ReactiveEngine4React.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { useReactiveValue0 as a } from "./hooks/useReactiveValue0/useReactiveValue0.mjs";
|
|
2
|
+
import { useReactiveSubscription as t } from "./hooks/useReactiveSubscription/useReactiveSubscription.mjs";
|
|
3
|
+
import { useReactiveValue as n } from "./hooks/useReactiveValue/useReactiveValue.mjs";
|
|
4
|
+
import { useReactiveValue0 as r } from "./hooks/useReactiveValue0/useReactiveValue0.mjs";
|
|
6
5
|
import "./hooks/index.mjs";
|
|
7
|
-
export { e as ReactiveEngine, t as
|
|
6
|
+
export { e as ReactiveEngine, t as useReactiveSubscription, n as useReactiveValue, r as useReactiveValue0 };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));exports.__toESM=s;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("react");t=e.__toESM(t,1);var n=e=>({children:n})=>{let r=(0,t.useRef)(0),i=(0,t.useRef)(null),a=(0,t.useRef)(null),o=(0,t.useRef)(null),s=(0,t.useMemo)(()=>({subscribe(e){return i.current=e,()=>{i.current=null,a.current&&(a.current(),a.current=null,o.current=null)}},getSnapshot(){return r.current}}),[]);(0,t.useEffect)(()=>()=>{a.current&&a.current()},[]),a.current||(a.current=e.effect(()=>{i.current&&(r.current++,queueMicrotask(()=>{i.current&&i.current()}))}),o.current=Array.from(e.allEffects).pop());let c=e.activeEffect;e.activeEffect=o.current;let l=null;try{l=n()}finally{e.activeEffect=c}return(0,t.useSyncExternalStore)(s.subscribe,s.getSnapshot),l},r=e=>n=>{let r=r=>{let i=(0,t.useRef)(0),a=e,o=`activeEffect`in a?`activeEffect`:`_activeEffect`,s=(0,t.useRef)(null),{subscribe:c,getSnapshot:l}=(0,t.useMemo)(()=>({subscribe:t=>{let n=e.effect(()=>{i.current++,t()}),r=Array.from(a.allEffects||[]);return s.current=r[r.length-1],()=>{n(),s.current=null}},getSnapshot:()=>i.current}),[]);(0,t.useSyncExternalStore)(c,l,l);let u={cleanups:new Set,run:()=>{}},d=s.current||u,f=a[o];a[o]=d;let p=null;try{p=typeof n==`function`&&!n.prototype?.isReactComponent?n(r):t.default.createElement(n,r)}finally{a[o]=f}return p};return r.displayName=`Observer(${n.displayName||n.name||`Component`})`,r};exports.createObserver=r,exports.createObserverComponent=n;
|
|
2
|
-
//# sourceMappingURL=createObserver.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createObserver.cjs","names":[],"sources":["../../src/react/createObserver.tsx"],"sourcesContent":["import React, { useSyncExternalStore, useMemo, useEffect, useRef } from 'react'\nimport { ReactiveEngine } from '../core'\n\n/**\n * Свойства для инлайн-компонента {@link Observer}.\n */\ninterface ObserverProps {\n /**\n * Функция-рендер, возвращающая JSX-разметку.\n * Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.\n */\n children: () => React.ReactNode;\n}\n\n/**\n * Фабрика для создания инлайн-компонента `<Observer>`.\n *\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.\n *\n * @example\n * ```tsx\n * import { createObserverComponent, engine } from '@pravosleva/reactive-engine';\n *\n * const Observer = createObserverComponent(engine);\n * const priceSignal = engine.signal(100);\n *\n * export const MyComponent = () => {\n * return (\n * <div>\n * <h1>Статический тяжелый контент (не перерисовывается)</h1>\n * <Observer>\n * {() => <span>Живая цена: {priceSignal.value}</span>}\n * </Observer>\n * </div>\n * );\n * };\n * ```\n */\nexport const createObserverComponent = (engine: ReactiveEngine) => {\n return ({ children }: ObserverProps): any => {\n const versionRef = useRef(0);\n const onStoreChangeRef = useRef<(() => void) | null>(null);\n\n // Ссылки для удержания инстанса эффекта и функции отписки\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const effectObjRef = useRef<any>(null);\n\n // 1. Создаем стабильный мост подписки для React ОДИН раз за жизнь компонента\n const storeBridge = useMemo(() => {\n return {\n subscribe(onStoreChange: () => void) {\n onStoreChangeRef.current = onStoreChange;\n return () => {\n onStoreChangeRef.current = null;\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n effectObjRef.current = null;\n }\n };\n },\n getSnapshot() {\n return versionRef.current;\n }\n };\n }, []);\n\n // Очистка при полном размонтировании из DOM\n useEffect(() => {\n return () => {\n if (unsubscribeRef.current) unsubscribeRef.current();\n };\n }, []);\n\n // 2. ЛЕЗИ-ИНИЦИАЛИЗАЦИЯ: Создаем реактивный эффект ядра ровно 1 раз.\n // Этот эффект будет жить вечно вместе с компонентом и никогда не сбросит свои подписки!\n if (!unsubscribeRef.current) {\n unsubscribeRef.current = engine.effect(() => {\n // Если этот эффект триггерится асинхронно из-за изменения сигнала в ядре:\n if (onStoreChangeRef.current) {\n versionRef.current++; // Инкрементируем версию снапшота для React\n\n // Мягко пушим React на ререндер через микрозадачу\n queueMicrotask(() => {\n if (onStoreChangeRef.current) {\n onStoreChangeRef.current();\n }\n });\n }\n });\n\n // Хак: Вытаскиваем ссылку на сам объект эффекта, который только что зарегился в ядре\n effectObjRef.current = Array.from((engine as any).allEffects).pop();\n }\n\n // 3. ГЛАВНАЯ МАГИЯ: Каждый раз, когда React заходит на рендер (включая StrictMode),\n // мы вручную подменяем activeEffect ядра на НАШ долгоживущий эффект на время выполнения children().\n // Это заставляет ядро автоматически ДОБАВЛЯТЬ новые сигналы в зависимости и никогда не терять старые!\n const prevActiveEffect = (engine as any).activeEffect;\n (engine as any).activeEffect = effectObjRef.current;\n\n let result: React.ReactNode = null;\n try {\n result = children(); // Синхронно выполняем рендер компонента и собираем сигналы\n } finally {\n // Возвращаем глобальный контекст ядра в исходное состояние\n (engine as any).activeEffect = prevActiveEffect;\n }\n\n // Подключаем нативныйuseSyncExternalStore к нашей стабильной версии стейта\n useSyncExternalStore(storeBridge.subscribe, storeBridge.getSnapshot);\n\n return result as unknown as React.ReactElement;\n };\n};\n\ninterface TriggerProps {\n engine: ReactiveEngine;\n Component: React.ComponentType<any>;\n props: any;\n onTrigger: () => void;\n}\n\n/**\n * Вспомогательный компонент, изолирующий выполнение пользовательского компонента\n * от функции safeRun движка во время сбора реактивных зависимостей.\n */\nconst ReactiveRenderTrigger = ({ engine, Component, props, onTrigger }: TriggerProps) => {\n let renderedElement: React.ReactNode = null;\n\n // 1. Создаем эффект движка. Он инициализируется пустым, чтобы не оборачивать\n // вызов компонента в safeRun и не ломать правила хуков React.\n const unsub = engine.effect(() => {\n // Этот коллбек сработает, когда изменятся сигналы, считанные в JSX ниже\n onTrigger();\n });\n\n // Преобразуем инстанс движка в any, чтобы достучаться до внутренних (private/protected) свойств\n const enginePrivate = engine as any;\n\n // 2. Извлекаем объект созданного эффекта из внутренней коллекции движка.\n // Если у вас свойство называется иначе, проверьте имя в коде: this.allEffects.add(effectObj);\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n const currentEffectObj = allEffectsArray[allEffectsArray.length - 1];\n\n // Определяем точное имя свойства активного эффекта.\n // Если в коде класса оно написано как activeEffect, то используем его через any.\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffectObj;\n\n try {\n // 3. Вызываем компонент напрямую в экосистеме React.\n // Все внутренние хуки (useEffect, useRef) теперь работают штатно.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // 4. Возвращаем движок в исходное состояние и подчищаем временный эффект\n enginePrivate[activeEffectKey] = prevActive;\n unsub();\n }\n\n return renderedElement;\n};\n\n/**\n * Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.\n * Полный аналог `observer` из MobX.\n *\n * @template P - Объект пропсов оборачиваемого компонента.\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Функция-декоратор, преобразующая обычный компонент в реактивный.\n *\n * @example\n * ```tsx\n * import { createObserver } from '@pravosleva/reactive-engine';\n * import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка\n *\n * const observer = createObserver(engine);\n * const counterSignal = engine.signal(0);\n *\n * // Компонент автоматически перерисуется при изменении counterSignal.value\n * export const CounterView = observer(() => {\n * return (\n * <div>\n * <p>Счетчик: {counterSignal.value}</p>\n * <button onClick={() => counterSignal.value++}>+1</button>\n * </div>\n * );\n * });\n * ```\n *\n * @source\n */\nexport const createObserver = (engine: ReactiveEngine) => {\n return <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {\n\n const ObserverComponent = (props: P) => {\n const versionRef = useRef(0);\n const enginePrivate = engine as any;\n\n // Опеределяем внутреннее имя свойства активного эффекта в вашем движке\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n // Хранилище для инстанса долгоживущего эффекта движка\n const effectObjRef = useRef<any>(null);\n\n // Интеграция с подписками React 18+ через useSyncExternalStore\n const { subscribe, getSnapshot } = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n // Создаем ОДИН долгоживущий эффект на весь жизненный цикл компонента.\n // Он уничтожится только тогда, когда React размонтирует компонент.\n const unsub = engine.effect(() => {\n // При изменении сигналов инкрементируем версию и уведомляем React\n versionRef.current++;\n onStoreChange();\n });\n\n // Нам нужно получить объект этого эффекта.\n // Из вашего кода метода `effect`: эффект добавляется в `this.allEffects.add(effectObj)`.\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n effectObjRef.current = allEffectsArray[allEffectsArray.length - 1];\n\n return () => {\n unsub(); // Чистим эффект при размонтировании\n effectObjRef.current = null;\n };\n },\n getSnapshot: () => versionRef.current\n };\n }, []);\n\n // Подписываем компонент. React сам управляет вызовами subscribe/unsubscribe в StrictMode.\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // --- ФАЗА СБОРА ЗАВИСИМОСТЕЙ ---\n // Если эффект уже создан (компонент смонтирован), мы временно подставляем его в движок.\n // Если это самый первый рендер (эффекта еще нет в subscribe), мы берем временный \"заглушечный\"\n // объект эффекта, чтобы движок не упал, а сигналы зарегистрировали чтение.\n const fallbackEffect = { cleanups: new Set(), run: () => { } };\n const currentEffect = effectObjRef.current || fallbackEffect;\n\n // Перед рендером подменяем активный эффект\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffect;\n\n let renderedElement: React.ReactNode = null;\n try {\n // Вызываем компонент напрямую. Хуки типа useEffect работают идеально,\n // так как они не обернуты в safeRun движка.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // После рендера возвращаем исходное состояние движка\n enginePrivate[activeEffectKey] = prevActive;\n }\n\n return renderedElement;\n };\n\n ObserverComponent.displayName = `Observer(${Component.displayName || Component.name || 'Component'})`;\n return ObserverComponent as React.ComponentType<P>;\n };\n};\n"],"mappings":"6FAuCA,IAAa,EAA2B,IAC9B,CAAE,cAAmC,CAC3C,IAAM,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAC,EACrB,GAAA,EAAA,EAAA,OAAA,CAA+C,IAAI,EAGnD,GAAA,EAAA,EAAA,OAAA,CAA6C,IAAI,EACjD,GAAA,EAAA,EAAA,OAAA,CAA2B,IAAI,EAG/B,GAAA,EAAA,EAAA,QAAA,MACG,CACL,UAAU,EAA2B,CAEnC,MADA,GAAiB,QAAU,MACd,CACX,EAAiB,QAAU,KACvB,EAAe,UACjB,EAAe,QAAQ,EACvB,EAAe,QAAU,KACzB,EAAa,QAAU,KAE3B,CACF,EACA,aAAc,CACZ,OAAO,EAAW,OACpB,CACF,GACC,CAAC,CAAC,GAGL,EAAA,EAAA,UAAA,SACe,CACP,EAAe,SAAS,EAAe,QAAQ,CACrD,EACC,CAAC,CAAC,EAIA,EAAe,UAClB,EAAe,QAAU,EAAO,WAAa,CAEvC,EAAiB,UACnB,EAAW,UAGX,mBAAqB,CACf,EAAiB,SACnB,EAAiB,QAAQ,CAE7B,CAAC,EAEL,CAAC,EAGD,EAAa,QAAU,MAAM,KAAM,EAAe,UAAU,CAAC,CAAC,IAAI,GAMpE,IAAM,EAAoB,EAAe,aACzC,EAAgB,aAAe,EAAa,QAE5C,IAAI,EAA0B,KAC9B,GAAI,CACF,EAAS,EAAS,CACpB,QAAU,CAER,EAAgB,aAAe,CACjC,CAKA,OAFA,EAAA,EAAA,qBAAA,CAAqB,EAAY,UAAW,EAAY,WAAW,EAE5D,CACT,EAqFW,EAAkB,GACH,GAA8D,CAEtF,IAAM,EAAqB,GAAa,CACtC,IAAM,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAC,EACrB,EAAgB,EAGhB,EAAkB,iBAAkB,EAAgB,eAAiB,gBAGrE,GAAA,EAAA,EAAA,OAAA,CAA2B,IAAI,EAG/B,CAAE,YAAW,gBAAA,EAAA,EAAA,QAAA,MACV,CACL,UAAY,GAA8B,CAGxC,IAAM,EAAQ,EAAO,WAAa,CAEhC,EAAW,UACX,EAAc,CAChB,CAAC,EAIK,EAAkB,MAAM,KAAK,EAAc,YAAc,CAAC,CAAC,EAGjE,MAFA,GAAa,QAAU,EAAgB,EAAgB,OAAS,OAEnD,CACX,EAAM,EACN,EAAa,QAAU,IACzB,CACF,EACA,gBAAmB,EAAW,OAChC,GACC,CAAC,CAAC,GAGL,EAAA,EAAA,qBAAA,CAAqB,EAAW,EAAa,CAAW,EAMxD,IAAM,EAAiB,CAAE,SAAU,IAAI,IAAO,QAAW,CAAE,CAAE,EACvD,EAAgB,EAAa,SAAW,EAGxC,EAAa,EAAc,GACjC,EAAc,GAAmB,EAEjC,IAAI,EAAmC,KACvC,GAAI,CAGF,AAGE,EAHE,OAAO,GAAc,YAAc,CAAC,EAAU,WAAW,iBACxC,EAAuB,CAAK,EAE7B,EAAA,QAAM,cAAc,EAAW,CAAK,CAE1D,QAAU,CAER,EAAc,GAAmB,CACnC,CAEA,OAAO,CACT,EAGA,MADA,GAAkB,YAAc,YAAY,EAAU,aAAe,EAAU,MAAQ,YAAY,GAC5F,CACT"}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { default as React } from 'react';
|
|
2
|
-
import { ReactiveEngine } from '../core';
|
|
3
|
-
/**
|
|
4
|
-
* Свойства для инлайн-компонента {@link Observer}.
|
|
5
|
-
*/
|
|
6
|
-
interface ObserverProps {
|
|
7
|
-
/**
|
|
8
|
-
* Функция-рендер, возвращающая JSX-разметку.
|
|
9
|
-
* Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.
|
|
10
|
-
*/
|
|
11
|
-
children: () => React.ReactNode;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Фабрика для создания инлайн-компонента `<Observer>`.
|
|
15
|
-
*
|
|
16
|
-
* @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.
|
|
17
|
-
* @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```tsx
|
|
21
|
-
* import { createObserverComponent, engine } from '@pravosleva/reactive-engine';
|
|
22
|
-
*
|
|
23
|
-
* const Observer = createObserverComponent(engine);
|
|
24
|
-
* const priceSignal = engine.signal(100);
|
|
25
|
-
*
|
|
26
|
-
* export const MyComponent = () => {
|
|
27
|
-
* return (
|
|
28
|
-
* <div>
|
|
29
|
-
* <h1>Статический тяжелый контент (не перерисовывается)</h1>
|
|
30
|
-
* <Observer>
|
|
31
|
-
* {() => <span>Живая цена: {priceSignal.value}</span>}
|
|
32
|
-
* </Observer>
|
|
33
|
-
* </div>
|
|
34
|
-
* );
|
|
35
|
-
* };
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export declare const createObserverComponent: (engine: ReactiveEngine) => ({ children }: ObserverProps) => any;
|
|
39
|
-
/**
|
|
40
|
-
* Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.
|
|
41
|
-
* Полный аналог `observer` из MobX.
|
|
42
|
-
*
|
|
43
|
-
* @template P - Объект пропсов оборачиваемого компонента.
|
|
44
|
-
* @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.
|
|
45
|
-
* @returns Функция-декоратор, преобразующая обычный компонент в реактивный.
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```tsx
|
|
49
|
-
* import { createObserver } from '@pravosleva/reactive-engine';
|
|
50
|
-
* import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка
|
|
51
|
-
*
|
|
52
|
-
* const observer = createObserver(engine);
|
|
53
|
-
* const counterSignal = engine.signal(0);
|
|
54
|
-
*
|
|
55
|
-
* // Компонент автоматически перерисуется при изменении counterSignal.value
|
|
56
|
-
* export const CounterView = observer(() => {
|
|
57
|
-
* return (
|
|
58
|
-
* <div>
|
|
59
|
-
* <p>Счетчик: {counterSignal.value}</p>
|
|
60
|
-
* <button onClick={() => counterSignal.value++}>+1</button>
|
|
61
|
-
* </div>
|
|
62
|
-
* );
|
|
63
|
-
* });
|
|
64
|
-
* ```
|
|
65
|
-
*
|
|
66
|
-
* @source
|
|
67
|
-
*/
|
|
68
|
-
export declare const createObserver: (engine: ReactiveEngine) => <P extends object>(Component: React.ComponentType<P>) => React.ComponentType<P>;
|
|
69
|
-
export {};
|
|
70
|
-
//# sourceMappingURL=createObserver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createObserver.d.ts","sourceRoot":"","sources":["../../src/react/createObserver.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2D,MAAM,OAAO,CAAA;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAExC;;GAEG;AACH,UAAU,aAAa;IACrB;;;OAGG;IACH,QAAQ,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,cAAc,MACpD,cAAc,aAAa,KAAG,GA2EvC,CAAC;AAuDF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,cAAc,MAC3C,CAAC,SAAS,MAAM,EAAE,WAAW,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAwEpF,CAAC"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import e, { useEffect as t, useMemo as n, useRef as r, useSyncExternalStore as i } from "react";
|
|
2
|
-
//#region src/react/createObserver.tsx
|
|
3
|
-
var a = (e) => ({ children: a }) => {
|
|
4
|
-
let o = r(0), s = r(null), c = r(null), l = r(null), u = n(() => ({
|
|
5
|
-
subscribe(e) {
|
|
6
|
-
return s.current = e, () => {
|
|
7
|
-
s.current = null, c.current && (c.current(), c.current = null, l.current = null);
|
|
8
|
-
};
|
|
9
|
-
},
|
|
10
|
-
getSnapshot() {
|
|
11
|
-
return o.current;
|
|
12
|
-
}
|
|
13
|
-
}), []);
|
|
14
|
-
t(() => () => {
|
|
15
|
-
c.current && c.current();
|
|
16
|
-
}, []), c.current || (c.current = e.effect(() => {
|
|
17
|
-
s.current && (o.current++, queueMicrotask(() => {
|
|
18
|
-
s.current && s.current();
|
|
19
|
-
}));
|
|
20
|
-
}), l.current = Array.from(e.allEffects).pop());
|
|
21
|
-
let d = e.activeEffect;
|
|
22
|
-
e.activeEffect = l.current;
|
|
23
|
-
let f = null;
|
|
24
|
-
try {
|
|
25
|
-
f = a();
|
|
26
|
-
} finally {
|
|
27
|
-
e.activeEffect = d;
|
|
28
|
-
}
|
|
29
|
-
return i(u.subscribe, u.getSnapshot), f;
|
|
30
|
-
}, o = (t) => (a) => {
|
|
31
|
-
let o = (o) => {
|
|
32
|
-
let s = r(0), c = t, l = "activeEffect" in c ? "activeEffect" : "_activeEffect", u = r(null), { subscribe: d, getSnapshot: f } = n(() => ({
|
|
33
|
-
subscribe: (e) => {
|
|
34
|
-
let n = t.effect(() => {
|
|
35
|
-
s.current++, e();
|
|
36
|
-
}), r = Array.from(c.allEffects || []);
|
|
37
|
-
return u.current = r[r.length - 1], () => {
|
|
38
|
-
n(), u.current = null;
|
|
39
|
-
};
|
|
40
|
-
},
|
|
41
|
-
getSnapshot: () => s.current
|
|
42
|
-
}), []);
|
|
43
|
-
i(d, f, f);
|
|
44
|
-
let p = {
|
|
45
|
-
cleanups: /* @__PURE__ */ new Set(),
|
|
46
|
-
run: () => {}
|
|
47
|
-
}, m = u.current || p, h = c[l];
|
|
48
|
-
c[l] = m;
|
|
49
|
-
let g = null;
|
|
50
|
-
try {
|
|
51
|
-
g = typeof a == "function" && !a.prototype?.isReactComponent ? a(o) : e.createElement(a, o);
|
|
52
|
-
} finally {
|
|
53
|
-
c[l] = h;
|
|
54
|
-
}
|
|
55
|
-
return g;
|
|
56
|
-
};
|
|
57
|
-
return o.displayName = `Observer(${a.displayName || a.name || "Component"})`, o;
|
|
58
|
-
};
|
|
59
|
-
//#endregion
|
|
60
|
-
export { o as createObserver, a as createObserverComponent };
|
|
61
|
-
|
|
62
|
-
//# sourceMappingURL=createObserver.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createObserver.mjs","names":[],"sources":["../../src/react/createObserver.tsx"],"sourcesContent":["import React, { useSyncExternalStore, useMemo, useEffect, useRef } from 'react'\nimport { ReactiveEngine } from '../core'\n\n/**\n * Свойства для инлайн-компонента {@link Observer}.\n */\ninterface ObserverProps {\n /**\n * Функция-рендер, возвращающая JSX-разметку.\n * Сигналы, прочитанные внутри этого коллбека, будут автоматически отслеживаться.\n */\n children: () => React.ReactNode;\n}\n\n/**\n * Фабрика для создания инлайн-компонента `<Observer>`.\n *\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Специальный React-компонент для точечной изоляции реактивных обновлений в JSX.\n *\n * @example\n * ```tsx\n * import { createObserverComponent, engine } from '@pravosleva/reactive-engine';\n *\n * const Observer = createObserverComponent(engine);\n * const priceSignal = engine.signal(100);\n *\n * export const MyComponent = () => {\n * return (\n * <div>\n * <h1>Статический тяжелый контент (не перерисовывается)</h1>\n * <Observer>\n * {() => <span>Живая цена: {priceSignal.value}</span>}\n * </Observer>\n * </div>\n * );\n * };\n * ```\n */\nexport const createObserverComponent = (engine: ReactiveEngine) => {\n return ({ children }: ObserverProps): any => {\n const versionRef = useRef(0);\n const onStoreChangeRef = useRef<(() => void) | null>(null);\n\n // Ссылки для удержания инстанса эффекта и функции отписки\n const unsubscribeRef = useRef<(() => void) | null>(null);\n const effectObjRef = useRef<any>(null);\n\n // 1. Создаем стабильный мост подписки для React ОДИН раз за жизнь компонента\n const storeBridge = useMemo(() => {\n return {\n subscribe(onStoreChange: () => void) {\n onStoreChangeRef.current = onStoreChange;\n return () => {\n onStoreChangeRef.current = null;\n if (unsubscribeRef.current) {\n unsubscribeRef.current();\n unsubscribeRef.current = null;\n effectObjRef.current = null;\n }\n };\n },\n getSnapshot() {\n return versionRef.current;\n }\n };\n }, []);\n\n // Очистка при полном размонтировании из DOM\n useEffect(() => {\n return () => {\n if (unsubscribeRef.current) unsubscribeRef.current();\n };\n }, []);\n\n // 2. ЛЕЗИ-ИНИЦИАЛИЗАЦИЯ: Создаем реактивный эффект ядра ровно 1 раз.\n // Этот эффект будет жить вечно вместе с компонентом и никогда не сбросит свои подписки!\n if (!unsubscribeRef.current) {\n unsubscribeRef.current = engine.effect(() => {\n // Если этот эффект триггерится асинхронно из-за изменения сигнала в ядре:\n if (onStoreChangeRef.current) {\n versionRef.current++; // Инкрементируем версию снапшота для React\n\n // Мягко пушим React на ререндер через микрозадачу\n queueMicrotask(() => {\n if (onStoreChangeRef.current) {\n onStoreChangeRef.current();\n }\n });\n }\n });\n\n // Хак: Вытаскиваем ссылку на сам объект эффекта, который только что зарегился в ядре\n effectObjRef.current = Array.from((engine as any).allEffects).pop();\n }\n\n // 3. ГЛАВНАЯ МАГИЯ: Каждый раз, когда React заходит на рендер (включая StrictMode),\n // мы вручную подменяем activeEffect ядра на НАШ долгоживущий эффект на время выполнения children().\n // Это заставляет ядро автоматически ДОБАВЛЯТЬ новые сигналы в зависимости и никогда не терять старые!\n const prevActiveEffect = (engine as any).activeEffect;\n (engine as any).activeEffect = effectObjRef.current;\n\n let result: React.ReactNode = null;\n try {\n result = children(); // Синхронно выполняем рендер компонента и собираем сигналы\n } finally {\n // Возвращаем глобальный контекст ядра в исходное состояние\n (engine as any).activeEffect = prevActiveEffect;\n }\n\n // Подключаем нативныйuseSyncExternalStore к нашей стабильной версии стейта\n useSyncExternalStore(storeBridge.subscribe, storeBridge.getSnapshot);\n\n return result as unknown as React.ReactElement;\n };\n};\n\ninterface TriggerProps {\n engine: ReactiveEngine;\n Component: React.ComponentType<any>;\n props: any;\n onTrigger: () => void;\n}\n\n/**\n * Вспомогательный компонент, изолирующий выполнение пользовательского компонента\n * от функции safeRun движка во время сбора реактивных зависимостей.\n */\nconst ReactiveRenderTrigger = ({ engine, Component, props, onTrigger }: TriggerProps) => {\n let renderedElement: React.ReactNode = null;\n\n // 1. Создаем эффект движка. Он инициализируется пустым, чтобы не оборачивать\n // вызов компонента в safeRun и не ломать правила хуков React.\n const unsub = engine.effect(() => {\n // Этот коллбек сработает, когда изменятся сигналы, считанные в JSX ниже\n onTrigger();\n });\n\n // Преобразуем инстанс движка в any, чтобы достучаться до внутренних (private/protected) свойств\n const enginePrivate = engine as any;\n\n // 2. Извлекаем объект созданного эффекта из внутренней коллекции движка.\n // Если у вас свойство называется иначе, проверьте имя в коде: this.allEffects.add(effectObj);\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n const currentEffectObj = allEffectsArray[allEffectsArray.length - 1];\n\n // Определяем точное имя свойства активного эффекта.\n // Если в коде класса оно написано как activeEffect, то используем его через any.\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffectObj;\n\n try {\n // 3. Вызываем компонент напрямую в экосистеме React.\n // Все внутренние хуки (useEffect, useRef) теперь работают штатно.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // 4. Возвращаем движок в исходное состояние и подчищаем временный эффект\n enginePrivate[activeEffectKey] = prevActive;\n unsub();\n }\n\n return renderedElement;\n};\n\n/**\n * Функция высшего порядка (HOC) для автоматического отслеживания сигналов внутри React-компонента.\n * Полный аналог `observer` из MobX.\n *\n * @template P - Объект пропсов оборачиваемого компонента.\n * @param engine - Экземпляр реактивного движка {@link ReactiveEngine}.\n * @returns Функция-декоратор, преобразующая обычный компонент в реактивный.\n *\n * @example\n * ```tsx\n * import { createObserver } from '@pravosleva/reactive-engine';\n * import { engine } from '~/utils/ReactiveEngine'; // Ваш локальный экземпляр движка\n *\n * const observer = createObserver(engine);\n * const counterSignal = engine.signal(0);\n *\n * // Компонент автоматически перерисуется при изменении counterSignal.value\n * export const CounterView = observer(() => {\n * return (\n * <div>\n * <p>Счетчик: {counterSignal.value}</p>\n * <button onClick={() => counterSignal.value++}>+1</button>\n * </div>\n * );\n * });\n * ```\n *\n * @source\n */\nexport const createObserver = (engine: ReactiveEngine) => {\n return <P extends object>(Component: React.ComponentType<P>): React.ComponentType<P> => {\n\n const ObserverComponent = (props: P) => {\n const versionRef = useRef(0);\n const enginePrivate = engine as any;\n\n // Опеределяем внутреннее имя свойства активного эффекта в вашем движке\n const activeEffectKey = 'activeEffect' in enginePrivate ? 'activeEffect' : '_activeEffect';\n\n // Хранилище для инстанса долгоживущего эффекта движка\n const effectObjRef = useRef<any>(null);\n\n // Интеграция с подписками React 18+ через useSyncExternalStore\n const { subscribe, getSnapshot } = useMemo(() => {\n return {\n subscribe: (onStoreChange: () => void) => {\n // Создаем ОДИН долгоживущий эффект на весь жизненный цикл компонента.\n // Он уничтожится только тогда, когда React размонтирует компонент.\n const unsub = engine.effect(() => {\n // При изменении сигналов инкрементируем версию и уведомляем React\n versionRef.current++;\n onStoreChange();\n });\n\n // Нам нужно получить объект этого эффекта.\n // Из вашего кода метода `effect`: эффект добавляется в `this.allEffects.add(effectObj)`.\n const allEffectsArray = Array.from(enginePrivate.allEffects || []);\n effectObjRef.current = allEffectsArray[allEffectsArray.length - 1];\n\n return () => {\n unsub(); // Чистим эффект при размонтировании\n effectObjRef.current = null;\n };\n },\n getSnapshot: () => versionRef.current\n };\n }, []);\n\n // Подписываем компонент. React сам управляет вызовами subscribe/unsubscribe в StrictMode.\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // --- ФАЗА СБОРА ЗАВИСИМОСТЕЙ ---\n // Если эффект уже создан (компонент смонтирован), мы временно подставляем его в движок.\n // Если это самый первый рендер (эффекта еще нет в subscribe), мы берем временный \"заглушечный\"\n // объект эффекта, чтобы движок не упал, а сигналы зарегистрировали чтение.\n const fallbackEffect = { cleanups: new Set(), run: () => { } };\n const currentEffect = effectObjRef.current || fallbackEffect;\n\n // Перед рендером подменяем активный эффект\n const prevActive = enginePrivate[activeEffectKey];\n enginePrivate[activeEffectKey] = currentEffect;\n\n let renderedElement: React.ReactNode = null;\n try {\n // Вызываем компонент напрямую. Хуки типа useEffect работают идеально,\n // так как они не обернуты в safeRun движка.\n if (typeof Component === 'function' && !Component.prototype?.isReactComponent) {\n renderedElement = (Component as Function)(props);\n } else {\n renderedElement = React.createElement(Component, props);\n }\n } finally {\n // После рендера возвращаем исходное состояние движка\n enginePrivate[activeEffectKey] = prevActive;\n }\n\n return renderedElement;\n };\n\n ObserverComponent.displayName = `Observer(${Component.displayName || Component.name || 'Component'})`;\n return ObserverComponent as React.ComponentType<P>;\n };\n};\n"],"mappings":";;AAuCA,IAAa,KAA2B,OAC9B,EAAE,kBAAmC;CAC3C,IAAM,IAAa,EAAO,CAAC,GACrB,IAAmB,EAA4B,IAAI,GAGnD,IAAiB,EAA4B,IAAI,GACjD,IAAe,EAAY,IAAI,GAG/B,IAAc,SACX;EACL,UAAU,GAA2B;GAEnC,OADA,EAAiB,UAAU,SACd;IAEX,AADA,EAAiB,UAAU,MACvB,EAAe,YACjB,EAAe,QAAQ,GACvB,EAAe,UAAU,MACzB,EAAa,UAAU;GAE3B;EACF;EACA,cAAc;GACZ,OAAO,EAAW;EACpB;CACF,IACC,CAAC,CAAC;CAWL,AARA,cACe;EACX,AAAI,EAAe,WAAS,EAAe,QAAQ;CACrD,GACC,CAAC,CAAC,GAIA,EAAe,YAClB,EAAe,UAAU,EAAO,aAAa;EAE3C,AAAI,EAAiB,YACnB,EAAW,WAGX,qBAAqB;GACnB,AAAI,EAAiB,WACnB,EAAiB,QAAQ;EAE7B,CAAC;CAEL,CAAC,GAGD,EAAa,UAAU,MAAM,KAAM,EAAe,UAAU,CAAC,CAAC,IAAI;CAMpE,IAAM,IAAoB,EAAe;CACzC,EAAgB,eAAe,EAAa;CAE5C,IAAI,IAA0B;CAC9B,IAAI;EACF,IAAS,EAAS;CACpB,UAAU;EAER,EAAgB,eAAe;CACjC;CAKA,OAFA,EAAqB,EAAY,WAAW,EAAY,WAAW,GAE5D;AACT,GAqFW,KAAkB,OACH,MAA8D;CAEtF,IAAM,KAAqB,MAAa;EACtC,IAAM,IAAa,EAAO,CAAC,GACrB,IAAgB,GAGhB,IAAkB,kBAAkB,IAAgB,iBAAiB,iBAGrE,IAAe,EAAY,IAAI,GAG/B,EAAE,cAAW,mBAAgB,SAC1B;GACL,YAAY,MAA8B;IAGxC,IAAM,IAAQ,EAAO,aAAa;KAGhC,AADA,EAAW,WACX,EAAc;IAChB,CAAC,GAIK,IAAkB,MAAM,KAAK,EAAc,cAAc,CAAC,CAAC;IAGjE,OAFA,EAAa,UAAU,EAAgB,EAAgB,SAAS,UAEnD;KAEX,AADA,EAAM,GACN,EAAa,UAAU;IACzB;GACF;GACA,mBAAmB,EAAW;EAChC,IACC,CAAC,CAAC;EAGL,EAAqB,GAAW,GAAa,CAAW;EAMxD,IAAM,IAAiB;GAAE,0BAAU,IAAI,IAAI;GAAG,WAAW,CAAE;EAAE,GACvD,IAAgB,EAAa,WAAW,GAGxC,IAAa,EAAc;EACjC,EAAc,KAAmB;EAEjC,IAAI,IAAmC;EACvC,IAAI;GAGF,AAGE,IAHE,OAAO,KAAc,cAAc,CAAC,EAAU,WAAW,mBACxC,EAAuB,CAAK,IAE7B,EAAM,cAAc,GAAW,CAAK;EAE1D,UAAU;GAER,EAAc,KAAmB;EACnC;EAEA,OAAO;CACT;CAGA,OADA,EAAkB,cAAc,YAAY,EAAU,eAAe,EAAU,QAAQ,YAAY,IAC5F;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createObserver.test.d.ts","sourceRoot":"","sources":["../../src/react/createObserver.test.tsx"],"names":[],"mappings":""}
|