@pravosleva/reactive-engine 1.2.5-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 -2
- 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/package.json +1 -1
package/README.md
CHANGED
|
@@ -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"}
|