diff --git a/CHANGELOG.md b/CHANGELOG.md index a27facc..70fd8d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ All notable changes to `hy-event-store` are documented in this file. the original callback reference. - Document the supported package-name import path for WeChat Mini Programs and add a package-root entry-point regression test. +- Add `getState(key)` for reading a declared state value without subscribing to + updates. ## 1.4.0 - 2026-08-19 diff --git a/README.md b/README.md index c2e8fe7..43fc5fd 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,16 @@ async function loadData() { onState invokes its callback immediately with the current value, then again when that key changes. +Read one declared value without subscribing to updates: + +~~~js +const status = store.getState("status") +~~~ + +getState does not notify listeners and throws for an undeclared key. +It returns the current value as-is; it is not a deep clone or deep read-only +snapshot. + Subscribe to several keys with onStates: ~~~js @@ -190,6 +200,7 @@ Create a store with a required state object and an optional | onStates(keys, callback) | Subscribe to several declared keys and immediately receive their initial snapshot. Later callbacks contain changed keys only. | | offStates(keys, callback) | Remove a multi-key subscription. | | setState(key, value) | Update a declared state key. | +| getState(key) | Read one declared state value without subscribing. | | dispatch(actionName, ...args) | Run an action and return its value unchanged, including a Promise. | ## Usage notes diff --git a/README.zh-CN.md b/README.zh-CN.md index af4965c..e72b144 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -140,6 +140,15 @@ async function loadData() { onState 会先立即以当前值调用一次回调,之后在对应状态键变更时再次调用。 +如需读取一个已声明的状态值,但不订阅更新,可以使用: + +~~~js +const status = store.getState("status") +~~~ + +getState 不会通知监听器,读取未声明的键时会抛出错误。它会原样 +返回当前值,不会创建深拷贝,也不是深层只读快照。 + 使用 onStates 订阅多个状态键: ~~~js @@ -180,6 +189,7 @@ store.offStates(["count", "status"], renderProfile) | onStates(keys, callback) | 订阅多个已声明状态键,并立即收到初始快照;后续回调只包含变化的键。 | | offStates(keys, callback) | 取消多状态订阅。 | | setState(key, value) | 更新已声明的状态键。 | +| getState(key) | 读取一个已声明的状态值,不建立订阅。 | | dispatch(actionName, ...args) | 运行 action,并原样返回它的结果,包括 Promise。 | ## 使用注意事项 diff --git a/ROADMAP.md b/ROADMAP.md index 5ad183c..3d3cea6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -26,7 +26,7 @@ These items need API design or compatibility research before implementation: - Document and test CommonJS and Mini Program import paths; native ESM remains a separate compatibility item ([#27](https://github.com/coderwhy/hy-event-store/issues/27)). -- Evaluate a read-only state getter +- Add a read-only state getter ([#26](https://github.com/coderwhy/hy-event-store/issues/26)). - Explore an opt-in full snapshot for `onStates` ([#24](https://github.com/coderwhy/hy-event-store/issues/24)). diff --git a/Test/event-store.test.js b/Test/event-store.test.js index 9ab3c8e..83e2e1a 100644 --- a/Test/event-store.test.js +++ b/Test/event-store.test.js @@ -53,6 +53,21 @@ test("onStates reports the initial snapshot and changed key until removed", () = ]) }) +test("getState reads a declared value without notifying listeners", () => { + const store = new HYEventStore({ state: { count: 0 } }) + let notifications = 0 + store.onState("count", () => { + notifications += 1 + }) + + assert.equal(store.getState("count"), 0) + assert.equal(notifications, 1) + store.setState("count", 1) + assert.equal(store.getState("count"), 1) + assert.equal(notifications, 2) + assert.throws(() => store.getState("missing"), /does not contain/) +}) + test("dispatch returns synchronous action results", () => { const store = new HYEventStore({ state: { count: 1 }, diff --git a/Test/types.test.ts b/Test/types.test.ts index 3dba997..0217cce 100644 --- a/Test/types.test.ts +++ b/Test/types.test.ts @@ -45,12 +45,16 @@ store.onStates(["count", "status"], change => { store.setState("count", 1) store.setState("status", "ready") +const currentCount: number = store.getState("count") +const currentStatus: "idle" | "ready" = store.getState("status") const nextCount: number = store.dispatch("increment", 2) const loaded: Promise = store.dispatch("load", "home") // @ts-expect-error State keys must exist in the declared state object. store.setState("missing", 1) +// @ts-expect-error getState only accepts declared state keys. +store.getState("missing") // @ts-expect-error State values must match their declared key type. store.setState("count", "1") // @ts-expect-error Action arguments are inferred from the action signature. diff --git a/index.d.ts b/index.d.ts index 0f4f5ca..68aa542 100644 --- a/index.d.ts +++ b/index.d.ts @@ -99,6 +99,8 @@ export class HYEventStore< stateValue: State[Key] ): void; + getState>(stateKey: Key): State[Key]; + dispatch>( actionName: ActionName, ...args: ActionArguments diff --git a/src/event-store.js b/src/event-store.js index be0822f..e131f8f 100644 --- a/src/event-store.js +++ b/src/event-store.js @@ -123,6 +123,13 @@ class HYEventStore { this.state[stateKey] = stateValue } + getState(stateKey) { + if (Object.keys(this.state).indexOf(stateKey) === -1) { + throw new Error("the state does not contain your key") + } + return this.state[stateKey] + } + dispatch(actionName, ...args) { if (typeof actionName !== "string") { throw new TypeError("the action name must be string type")