diff --git a/cocoscreator_projects/YouleNexus/assets/framework/core/reactive.ts b/cocoscreator_projects/YouleNexus/assets/framework/core/reactive.ts new file mode 100644 index 0000000..b4bfdc0 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/framework/core/reactive.ts @@ -0,0 +1,31 @@ +export type Unsubscribe = () => void; + +export interface ReadonlyReactive { + readonly value: T; + subscribe(fn: (value: T, prev: T) => void): Unsubscribe; // 返回退订函数 +} + +export interface Reactive extends ReadonlyReactive { + value: T; // 可写 +} + +/** 极小响应式信号:value 读写 + 订阅。Object.is 判等(值未变不通知);订阅者抛错隔离。 */ +export function signal(initial: T): Reactive { + let current = initial; + const subs = new Set<(value: T, prev: T) => void>(); + return { + get value(): T { return current; }, + set value(v: T) { + if (Object.is(v, current)) return; + const prev = current; + current = v; + for (const fn of [...subs]) { + try { fn(current, prev); } catch (e) { console.error('[reactive] subscriber error:', e); } + } + }, + subscribe(fn: (value: T, prev: T) => void): Unsubscribe { + subs.add(fn); + return () => { subs.delete(fn); }; + }, + }; +} diff --git a/cocoscreator_projects/framework-tests/platform/reactive.test.ts b/cocoscreator_projects/framework-tests/platform/reactive.test.ts new file mode 100644 index 0000000..3567741 --- /dev/null +++ b/cocoscreator_projects/framework-tests/platform/reactive.test.ts @@ -0,0 +1,45 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { signal } from '../../YouleNexus/assets/framework/core/reactive.ts'; + +test('signal: value 读写', () => { + const s = signal(1); + assert.equal(s.value, 1); + s.value = 2; + assert.equal(s.value, 2); +}); + +test('signal: subscribe 收到 (value, prev)', () => { + const s = signal(1); + let got: [number, number] | null = null; + s.subscribe((v, p) => { got = [v, p]; }); + s.value = 5; + assert.deepEqual(got, [5, 1]); +}); + +test('signal: 退订后不再收到', () => { + const s = signal(0); + let count = 0; + const un = s.subscribe(() => { count++; }); + s.value = 1; + un(); + s.value = 2; + assert.equal(count, 1); +}); + +test('signal: Object.is 判等,值未变不通知', () => { + const s = signal(7); + let count = 0; + s.subscribe(() => { count++; }); + s.value = 7; + assert.equal(count, 0); +}); + +test('signal: 一个订阅者抛错不影响其它', () => { + const s = signal(0); + let reached = false; + s.subscribe(() => { throw new Error('boom'); }); + s.subscribe(() => { reached = true; }); + s.value = 1; + assert.equal(reached, true); +});