@wcstack/state 1.3.15 → 1.3.17
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.ja.md +22 -16
- package/README.md +21 -15
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -29,14 +29,14 @@ CDNスクリプトはカスタム要素の定義を登録するだけ — ロー
|
|
|
29
29
|
|
|
30
30
|
```html
|
|
31
31
|
<!-- 自動初期化 — これだけで動作します -->
|
|
32
|
-
<script type="module" src="https://
|
|
32
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### CDN(手動初期化)
|
|
36
36
|
|
|
37
37
|
```html
|
|
38
38
|
<script type="module">
|
|
39
|
-
import { bootstrapState } from 'https://
|
|
39
|
+
import { bootstrapState } from 'https://esm.run/@wcstack/state';
|
|
40
40
|
bootstrapState();
|
|
41
41
|
</script>
|
|
42
42
|
```
|
|
@@ -149,27 +149,33 @@ CDNスクリプトはカスタム要素の定義を登録するだけ — ロー
|
|
|
149
149
|
|
|
150
150
|
## 状態の更新
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
`@wcstack/state` では、すべての状態は**パス**を持ちます — `count`、`user.name`、`items` のように。状態をリアクティブに更新するには、**パスに代入**します:
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
```javascript
|
|
155
|
+
this.count = 10; // パス "count"
|
|
156
|
+
this["user.name"] = "Bob"; // パス "user.name"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
ルールはひとつ: **パスに代入すれば、DOMは自動的に更新される。**
|
|
160
|
+
|
|
161
|
+
### なぜ `this.user.name = "Bob"` では動かないのか
|
|
155
162
|
|
|
156
|
-
|
|
163
|
+
`this.user.name` は、まず `this.user` で `user` オブジェクトを読み取り(パスの読み取り)、そのプレーンオブジェクトの `.name` に設定します — これはパスへの代入ではないため、変更は検知されません:
|
|
157
164
|
|
|
158
165
|
```javascript
|
|
159
|
-
// ✅
|
|
160
|
-
this.count = 10;
|
|
166
|
+
// ✅ パスへの代入 — 変更が検知される
|
|
161
167
|
this["user.name"] = "Bob";
|
|
162
168
|
|
|
163
|
-
// ❌
|
|
164
|
-
this.user.name = "Bob";
|
|
169
|
+
// ❌ パスへの代入ではない — 変更は検知されない
|
|
170
|
+
this.user.name = "Bob";
|
|
165
171
|
```
|
|
166
172
|
|
|
167
173
|
### 配列
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
同じルールです: パスに新しい配列を代入します。破壊的メソッド(`push`, `splice`, `sort` 等)はパスへの代入なしに配列をその場で変更するため、非破壊的な代替メソッドを使用します:
|
|
170
176
|
|
|
171
177
|
```javascript
|
|
172
|
-
// ✅
|
|
178
|
+
// ✅ 新しい配列をパスに代入 — 変更が検知される
|
|
173
179
|
this.items = this.items.concat({ id: 4, text: "New" });
|
|
174
180
|
this.items = this.items.toSpliced(index, 1);
|
|
175
181
|
this.items = this.items.filter(item => !item.done);
|
|
@@ -177,7 +183,7 @@ this.items = this.items.toSorted((a, b) => a.id - b.id);
|
|
|
177
183
|
this.items = this.items.toReversed();
|
|
178
184
|
this.items = this.items.with(index, newValue);
|
|
179
185
|
|
|
180
|
-
// ❌
|
|
186
|
+
// ❌ その場での変更 — パスへの代入なし、変更は検知されない
|
|
181
187
|
this.items.push({ id: 4, text: "New" });
|
|
182
188
|
this.items.splice(index, 1);
|
|
183
189
|
this.items.sort((a, b) => a.id - b.id);
|
|
@@ -550,7 +556,7 @@ export default {
|
|
|
550
556
|
};
|
|
551
557
|
```
|
|
552
558
|
|
|
553
|
-
3階層のネスト、5つの仮想プロパティ — すべてが1つのフラットなオブジェクト内に並んで定義されています。各レベルは任意の深さの値を参照でき、`$getAll`
|
|
559
|
+
3階層のネスト、5つの仮想プロパティ — すべてが1つのフラットなオブジェクト内に並んで定義されています。各レベルは任意の深さの値を参照でき、`$getAll` による集約は下位から上位へ自然に流れます。コンポーネントベースのフレームワークでは、一般的に各ネストレベルに個別のコンポーネントを作成し、算出値をツリーの上位に渡す方法が採られます。パス getter は、すべての定義を1箇所にまとめるという異なるトレードオフを提供します。
|
|
554
560
|
|
|
555
561
|
### getter の戻り値のサブプロパティへのアクセス
|
|
556
562
|
|
|
@@ -834,14 +840,14 @@ export default {
|
|
|
834
840
|
|
|
835
841
|
`@wcstack/state` は Shadow DOM または Light DOM を使用したカスタム要素との双方向状態バインディングに対応しています。
|
|
836
842
|
|
|
837
|
-
|
|
843
|
+
多くのフレームワークでは、コンポーネント間の状態共有に props バケツリレー、Context Provider、外部ストア(Redux, Pinia)といったパターンが用いられます。`@wcstack/state` は異なるアプローチを採ります。親子コンポーネントは**パスの契約**で接続されます。親は `data-wcs` で外部の状態パスを子コンポーネントのプロパティにバインドし、子は自身の状態を通常どおり読み書きするだけです:
|
|
838
844
|
|
|
839
845
|
1. 子コンポーネントは自身の状態プロキシを通じて親の状態を参照・更新します — props もイベントも親の存在を意識する必要もありません。
|
|
840
846
|
2. 親の状態が変更されると、Proxy の `set` トラップが影響するパスを参照している子のバインディングへ自動的に通知します。
|
|
841
847
|
3. 結合点は**パス名のみ**であるため、双方とも疎結合を保ち、独立してテスト可能です。
|
|
842
848
|
4. コストはパス解決(初回アクセス後はキャッシュにより O(1))と依存グラフを通じた変更伝播のみです。
|
|
843
849
|
|
|
844
|
-
|
|
850
|
+
これは、コンポーネントレベルの抽象化ではなくパス解決に基づく、コンポーネント間状態管理への軽量なアプローチです。
|
|
845
851
|
|
|
846
852
|
### コンポーネント定義(Shadow DOM)
|
|
847
853
|
|
|
@@ -998,7 +1004,7 @@ customElements.define("my-component", MyComponent);
|
|
|
998
1004
|
| `$disconnectedCallback` | 要素が DOM から削除された時 | 不可(同期のみ) |
|
|
999
1005
|
| `$updatedCallback(paths, indexesListByPath)` | 状態変更が適用された後に呼び出し | 戻り値は未使用(待機されない) |
|
|
1000
1006
|
|
|
1001
|
-
リアクティブ Proxy は全てのプロパティ代入を変更として検知するため、標準の `async/await`
|
|
1007
|
+
リアクティブ Proxy は全てのプロパティ代入を変更として検知するため、標準の `async/await` とプロパティへの直接代入だけで非同期処理は完結します。ローディングフラグ、取得データ、エラーメッセージの更新は全てプロパティ代入であり、非同期状態管理のための追加の抽象化を必要としません。
|
|
1002
1008
|
|
|
1003
1009
|
- フック内の `this` は読み書き可能な状態プロキシです
|
|
1004
1010
|
- `$connectedCallback` は要素が接続される**たびに**呼ばれます(削除後の再接続を含む)。再確立が必要なセットアップ処理に適しています
|
package/README.md
CHANGED
|
@@ -29,14 +29,14 @@ The CDN script only registers the custom element definition — nothing else hap
|
|
|
29
29
|
|
|
30
30
|
```html
|
|
31
31
|
<!-- Auto-initialization — this is all you need -->
|
|
32
|
-
<script type="module" src="https://
|
|
32
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### CDN (manual initialization)
|
|
36
36
|
|
|
37
37
|
```html
|
|
38
38
|
<script type="module">
|
|
39
|
-
import { bootstrapState } from 'https://
|
|
39
|
+
import { bootstrapState } from 'https://esm.run/@wcstack/state';
|
|
40
40
|
bootstrapState();
|
|
41
41
|
</script>
|
|
42
42
|
```
|
|
@@ -149,27 +149,33 @@ Default name is `"default"` (no `@` needed).
|
|
|
149
149
|
|
|
150
150
|
## Updating State
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
In `@wcstack/state`, every piece of state has a **path** — like `count`, `user.name`, or `items`. To update state reactively, **assign to the path**:
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
```javascript
|
|
155
|
+
this.count = 10; // path "count"
|
|
156
|
+
this["user.name"] = "Bob"; // path "user.name"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
That's the one rule: **assign to the path, and the DOM updates automatically.**
|
|
160
|
+
|
|
161
|
+
### Why `this.user.name = "Bob"` Doesn't Work
|
|
155
162
|
|
|
156
|
-
|
|
163
|
+
`this.user.name` first reads the `user` object via `this.user` (a path read), then sets `.name` on that plain object — this is not a path assignment, so the change is not detected:
|
|
157
164
|
|
|
158
165
|
```javascript
|
|
159
166
|
// ✅ Path assignment — change detected
|
|
160
|
-
this.count = 10;
|
|
161
167
|
this["user.name"] = "Bob";
|
|
162
168
|
|
|
163
|
-
// ❌
|
|
164
|
-
this.user.name = "Bob";
|
|
169
|
+
// ❌ Not a path assignment — change NOT detected
|
|
170
|
+
this.user.name = "Bob";
|
|
165
171
|
```
|
|
166
172
|
|
|
167
173
|
### Arrays
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
The same rule applies: assign a new array to the path. Mutating methods (`push`, `splice`, `sort`, ...) modify the array in place without path assignment, so use non-destructive alternatives:
|
|
170
176
|
|
|
171
177
|
```javascript
|
|
172
|
-
// ✅
|
|
178
|
+
// ✅ New array assigned to path — change detected
|
|
173
179
|
this.items = this.items.concat({ id: 4, text: "New" });
|
|
174
180
|
this.items = this.items.toSpliced(index, 1);
|
|
175
181
|
this.items = this.items.filter(item => !item.done);
|
|
@@ -177,7 +183,7 @@ this.items = this.items.toSorted((a, b) => a.id - b.id);
|
|
|
177
183
|
this.items = this.items.toReversed();
|
|
178
184
|
this.items = this.items.with(index, newValue);
|
|
179
185
|
|
|
180
|
-
// ❌
|
|
186
|
+
// ❌ In-place mutation — no path assignment, change NOT detected
|
|
181
187
|
this.items.push({ id: 4, text: "New" });
|
|
182
188
|
this.items.splice(index, 1);
|
|
183
189
|
this.items.sort((a, b) => a.id - b.id);
|
|
@@ -550,7 +556,7 @@ export default {
|
|
|
550
556
|
};
|
|
551
557
|
```
|
|
552
558
|
|
|
553
|
-
Three levels of nesting, five virtual properties — all defined side by side in a single flat object. Each level can reference values from any depth, and aggregation flows naturally from bottom to top via `$getAll`. In component-based frameworks
|
|
559
|
+
Three levels of nesting, five virtual properties — all defined side by side in a single flat object. Each level can reference values from any depth, and aggregation flows naturally from bottom to top via `$getAll`. In component-based frameworks, the typical approach is to create a separate component for each nesting level and pass computed values through the tree. Path getters offer a different trade-off by keeping all definitions in one place.
|
|
554
560
|
|
|
555
561
|
### Accessing Sub-Properties of Getter Results
|
|
556
562
|
|
|
@@ -834,14 +840,14 @@ Filters can be chained with `|`:
|
|
|
834
840
|
|
|
835
841
|
`@wcstack/state` supports bidirectional state binding with custom elements using Shadow DOM or Light DOM.
|
|
836
842
|
|
|
837
|
-
|
|
843
|
+
Many frameworks use patterns like prop drilling, context providers, or external stores (Redux, Pinia) to share state across components. `@wcstack/state` takes a different approach: parent and child components are connected through **path contracts** — the parent binds an outer state path to an inner component property via `data-wcs`, and the child simply reads and writes its own state as usual:
|
|
838
844
|
|
|
839
845
|
1. The child references and updates the parent's state through its own state proxy — no props, no events, no awareness of the parent.
|
|
840
846
|
2. When the parent's state changes, the Proxy `set` trap automatically notifies any child bindings that reference the affected path.
|
|
841
847
|
3. Because the only coupling is the **path name**, both sides remain loosely coupled and independently testable.
|
|
842
848
|
4. The cost is path resolution (cached at O(1) after first access) plus change propagation through the dependency graph.
|
|
843
849
|
|
|
844
|
-
This provides a
|
|
850
|
+
This provides a lightweight approach to cross-component state management based on path resolution rather than component-level abstractions.
|
|
845
851
|
|
|
846
852
|
### Component Definition (Shadow DOM)
|
|
847
853
|
|
|
@@ -998,7 +1004,7 @@ State objects can define `$connectedCallback`, `$disconnectedCallback`, and `$up
|
|
|
998
1004
|
| `$disconnectedCallback` | When the element is removed from the DOM | No (sync only) |
|
|
999
1005
|
| `$updatedCallback(paths, indexesListByPath)` | After state updates are applied | Return value is ignored (not awaited) |
|
|
1000
1006
|
|
|
1001
|
-
Since the reactive proxy detects every property assignment as a change, standard `async/await` with direct property updates is sufficient for asynchronous operations — loading flags, fetched data, and error messages are all just property assignments
|
|
1007
|
+
Since the reactive proxy detects every property assignment as a change, standard `async/await` with direct property updates is sufficient for asynchronous operations — loading flags, fetched data, and error messages are all just property assignments, without requiring additional abstractions for async state management.
|
|
1002
1008
|
|
|
1003
1009
|
- `this` inside hooks is the state proxy with full read/write access
|
|
1004
1010
|
- `$connectedCallback` is called **every time** the element is connected (including re-insertion after removal), making it suitable for setup that should be re-established
|
package/package.json
CHANGED