@pravosleva/reactive-engine 1.2.4-beta → 1.2.5-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 -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:
|
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":""}
|