@wcstack/state 1.30.0 → 1.32.0
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 +265 -6
- package/README.md +267 -6
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +82 -3
- package/dist/index.esm.js +1441 -192
- package/dist/index.esm.js.map +1 -1
- package/dist/manifest.esm.js +28 -8
- package/dist/parser.esm.js +28 -8
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -769,19 +769,20 @@ export default {
|
|
|
769
769
|
+ this["regions.*.prefectures.*.cities.*.name"];
|
|
770
770
|
},
|
|
771
771
|
|
|
772
|
-
// 県レベル —
|
|
772
|
+
// 県レベル — 市からの集約。`indexes` 省略時はループ文脈([$1, $2])が
|
|
773
|
+
// 既定になるので、この県の市だけが合計される
|
|
773
774
|
get "regions.*.prefectures.*.totalPopulation"() {
|
|
774
|
-
return this.$getAll("regions.*.prefectures.*.cities.*.population"
|
|
775
|
+
return this.$getAll("regions.*.prefectures.*.cities.*.population")
|
|
775
776
|
.reduce((a, b) => a + b, 0);
|
|
776
777
|
},
|
|
777
778
|
|
|
778
|
-
// 地方レベル —
|
|
779
|
+
// 地方レベル — 県からの集約(文脈 [$1] でこの地方に絞られる)
|
|
779
780
|
get "regions.*.totalPopulation"() {
|
|
780
|
-
return this.$getAll("regions.*.prefectures.*.totalPopulation"
|
|
781
|
+
return this.$getAll("regions.*.prefectures.*.totalPopulation")
|
|
781
782
|
.reduce((a, b) => a + b, 0);
|
|
782
783
|
},
|
|
783
784
|
|
|
784
|
-
// トップレベル —
|
|
785
|
+
// トップレベル — ループ文脈なし。[] は「マッチ全件」
|
|
785
786
|
get totalPopulation() {
|
|
786
787
|
return this.$getAll("regions.*.totalPopulation", [])
|
|
787
788
|
.reduce((a, b) => a + b, 0);
|
|
@@ -872,6 +873,36 @@ export default {
|
|
|
872
873
|
|
|
873
874
|
4. **直接インデックスアクセス** — 数値インデックスで特定の要素にアクセスすることもできます:`this["users.0.name"]` はループコンテキストなしで `users[0].name` に解決されます。
|
|
874
875
|
|
|
876
|
+
### getter は state に対して純粋であること
|
|
877
|
+
|
|
878
|
+
getter のキャッシュを無効化するのは**依存グラフだけ**で、依存グラフに載るのは getter が **`this` を通して読んだもの**だけです。それ以外の入力は無効化から見えないため、**最初に計算した値がそのまま残り続けます**:
|
|
879
|
+
|
|
880
|
+
```javascript
|
|
881
|
+
// ❌ 二度と再計算されない — 依存グラフ上の何も変化しないため
|
|
882
|
+
get stamp() { return `${this.label} @ ${Date.now()}`; } // Date.now() は追跡外
|
|
883
|
+
get theme() { return document.body.dataset.theme; } // DOM は追跡外
|
|
884
|
+
get total() { return this.price * exchangeRate; } // モジュール変数は追跡外
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
規則は「**`this` を通してのみ読む。getter から state を書かない・DOM を触らない**」です。追跡外の入力をどうしても使いたい場合は、その入力を state に持たせてパスに代入する(通常の契約に戻す)か、以下の逃げ道を使ってください:
|
|
888
|
+
|
|
889
|
+
| API | 用途 |
|
|
890
|
+
|---|---|
|
|
891
|
+
| `this.$trackDependency(path)` | 依存を明示的に追加し、そのパスの変更でこの getter を dirty にする |
|
|
892
|
+
| `this.$postUpdate(path)` | 追跡外の入力が変わったことを getter の外から通知する |
|
|
893
|
+
| `this.$untrackDependency(fn)` | 依存として登録せずにパスを読む(上の対称) |
|
|
894
|
+
|
|
895
|
+
```javascript
|
|
896
|
+
// ✅ 時計を state 側で刻み、getter は純粋なまま
|
|
897
|
+
export default {
|
|
898
|
+
now: Date.now(),
|
|
899
|
+
get stamp() { return `${this.label} @ ${this.now}`; },
|
|
900
|
+
$connectedCallback() { setInterval(() => { this.now = Date.now(); }, 1000); },
|
|
901
|
+
};
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
getter の例外は握り潰されません。評価された場所(バインディングの適用・`$watch` の評価・自分での読み取り)でそのまま表面化します。
|
|
905
|
+
|
|
875
906
|
### ループインデックス変数(`$1`, `$2`, ...)
|
|
876
907
|
|
|
877
908
|
getter やイベントハンドラ内で、`this.$1`、`this.$2` などで現在のループイテレーションのインデックスを取得できます(0始まりの値、1始まりの命名):
|
|
@@ -913,6 +944,7 @@ export default {
|
|
|
913
944
|
| API | 説明 |
|
|
914
945
|
|---|---|
|
|
915
946
|
| `this.$getAll(path, indexes?)` | ワイルドカードパスにマッチする全ての値を取得 |
|
|
947
|
+
| `this.$setAll(path, indexes, value, options?)` | ワイルドカードパスにマッチする全アドレスへ一括書き込み |
|
|
916
948
|
| `this.$resolve(path, indexes, value?)` | ワイルドカードパスを特定のインデックスで解決 |
|
|
917
949
|
| `this.$postUpdate(path)` | 指定パスの更新通知を手動で発行 |
|
|
918
950
|
| `this.$trackDependency(path)` | キャッシュ無効化のための依存関係を手動で登録 |
|
|
@@ -937,6 +969,66 @@ export default {
|
|
|
937
969
|
};
|
|
938
970
|
```
|
|
939
971
|
|
|
972
|
+
`indexes` はパスの `*` に対する**前方一致の接頭辞**です。不足した階層は全展開され、`[]` は常に「マッチ全件」を意味します。**省略**した場合はループ文脈の添字(`[$1, $2, ...]`)が既定になり、パスが文脈と共有するワイルドカード階層に敷かれます:
|
|
973
|
+
|
|
974
|
+
```javascript
|
|
975
|
+
export default {
|
|
976
|
+
regions: [ /* { prefectures: [ { population: … }, … ] } */ ],
|
|
977
|
+
// ループ文脈 [$1] — 省略すると現在の地方に絞られる
|
|
978
|
+
get "regions.*.total"() {
|
|
979
|
+
return this.$getAll("regions.*.prefectures.*.population").reduce((a, b) => a + b, 0);
|
|
980
|
+
},
|
|
981
|
+
// ループ文脈なし — 省略は全展開([] と同じ)
|
|
982
|
+
get grandTotal() {
|
|
983
|
+
return this.$getAll("regions.*.total").reduce((a, b) => a + b, 0);
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
文脈がパスより深い分は切り詰められます(`[$1, $2]` の文脈は `*` 1 本のパスを `[$1]` で絞ります)。一方、文脈がループ添字を持っているのにパスと**ワイルドカード階層をまったく共有しない**場合 —— たとえば `regions.*` の getter 内での `$getAll("users.*.name")` —— は、黙って全 users を読む代わりに **throw** します。文脈の添字は別のリストのものであり、流用しても無視しても書き手の意図とは食い違うためです。この形では添字を明示してください(全件なら `[]`)。
|
|
989
|
+
|
|
990
|
+
#### `$setAll` — 配列要素を作り直さずに一括更新
|
|
991
|
+
|
|
992
|
+
`$setAll` は `$getAll` の書き側の対称形で、ワイルドカードパスにマッチする全アドレスへ書き込みます。狙いは記述の短さではなく、**配列そのものを保つ**ことです。`this.users = this.users.map(...)` のように作り直すと ListIndex・行 getter のキャッシュ・差分描画がまとめて捨てられますが、`$setAll` は行ごとの in-place な書き込みに分解するのでリストの同一性が保たれます。
|
|
993
|
+
|
|
994
|
+
```javascript
|
|
995
|
+
export default {
|
|
996
|
+
users: [{ selected: false }, { selected: false }],
|
|
997
|
+
|
|
998
|
+
toggleAll(e) {
|
|
999
|
+
this.$setAll("users.*.selected", [], e.target.checked); // ブロードキャスト
|
|
1000
|
+
},
|
|
1001
|
+
invertAll() {
|
|
1002
|
+
this.$setAll("users.*.selected", [], cur => !cur); // mapper
|
|
1003
|
+
},
|
|
1004
|
+
rankTopThree() {
|
|
1005
|
+
// undefined を返したアドレスはスキップされる(=この行は変えない)
|
|
1006
|
+
this.$setAll("users.*.score", [], (cur, i) => i < 3 ? cur * 2 : undefined);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
形は 3 つあり、3 番目だけは明示的に要求する必要があります。
|
|
1012
|
+
|
|
1013
|
+
| 第 3 引数 | 意味 |
|
|
1014
|
+
|---|---|
|
|
1015
|
+
| 関数 | **mapper** — マッチしたアドレスごとに `(current, ...indexes)` で呼ばれる |
|
|
1016
|
+
| それ以外 | **ブロードキャスト** — 配列も含め、同じ値が全アドレスに書かれる |
|
|
1017
|
+
| 配列 + `{ spread: true }` | **spread** — マッチ順に 1 件ずつ配る |
|
|
1018
|
+
|
|
1019
|
+
配列が既定でブロードキャストされるのは、対象プロパティ自体が配列型でありうるためです。`$setAll("users.*.tags", [], ["admin"])` は「全員に `["admin"]`」なのか「1 人目に `"admin"`」なのか判別できません。`{ spread: true }` を明示すればこの推測が消え、長さがマッチ件数と噛み合わなければ黙って誤配せずに throw します。
|
|
1020
|
+
|
|
1021
|
+
`indexes` の意味は `$getAll` と同じ**前方一致の接頭辞**(不足はその階層を全展開)ですが、**省略はできません**。書き込みには暗黙のループ文脈を与えないため、`for` テンプレートの中でも `this.$setAll("users.*.selected", [], true)` は現在行ではなく**全行**を意味します。
|
|
1022
|
+
|
|
1023
|
+
```javascript
|
|
1024
|
+
this.$setAll("matrix.*.*", [0], 0); // 0 行目だけ全列
|
|
1025
|
+
this.$setAll("users.*", [], rows, { spread: true }); // 配列を保ったまま各行を差し替え
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
`undefined` はどの形でも書き込まれず「このアドレスはスキップ」を意味します。mapper が `return` を忘れて全行を潰す事故を防ぐためで、クリアしたい場合は `null` を使います。戻り値は実際に書き込んだ件数です。
|
|
1029
|
+
|
|
1030
|
+
なお `$setAll` は依存解決を一括化する仕組みではありません。描画は 1 バッチに畳まれますが、書き込みは 1 件ずつ登録されるので、コストは置き換え対象の手書きループと同じです。得られるのはリストが保たれることであって、実行回数の削減ではありません。
|
|
1031
|
+
|
|
940
1032
|
#### `$resolve` — 明示的なインデックスでのアクセス
|
|
941
1033
|
|
|
942
1034
|
`$resolve` は特定のワイルドカードインデックスの値を読み書きします:
|
|
@@ -1627,6 +1719,48 @@ $streams: {
|
|
|
1627
1719
|
|
|
1628
1720
|
完全な契約 —— ライフサイクルと所有権・restart セマンティクス・flush 粒度・スコープ外リスト —— は [docs/streams.ja.md](docs/streams.ja.md) を参照してください。
|
|
1629
1721
|
|
|
1722
|
+
## 評価のきっかけ(demand root)
|
|
1723
|
+
|
|
1724
|
+
パス getter は **lazy** です。誰も読まなければ一度も評価されません。したがって「この getter は走るか」は getter 自身を読んでも決まりません —— **需要(demand)がどこから来るか**で決まります。
|
|
1725
|
+
|
|
1726
|
+
需要の根は **3 つだけ**です:
|
|
1727
|
+
|
|
1728
|
+
| 根 | 場所 | 描画に依存するか |
|
|
1729
|
+
|---|---|---|
|
|
1730
|
+
| **live DOM バインディング** | `data-wcs` / mustache / コメントバインディング | **する**(その要素が消えると需要も消える) |
|
|
1731
|
+
| **`$watch` の宣言** | state 側 | しない(headless) |
|
|
1732
|
+
| **`$streams` の `args`** | state 側 | しない(起動・restart のたびに評価される) |
|
|
1733
|
+
|
|
1734
|
+
**`$updatedCallback` は根ではありません。** それは「バインディングが適用された結果」の報告であり、需要を作りません。
|
|
1735
|
+
|
|
1736
|
+
### 描画がプログラムの意味論を変えうる
|
|
1737
|
+
|
|
1738
|
+
この 3 つのうち 1 つ目が DOM にあることの帰結として、**表示専用のつもりの要素が購読の実体になり得ます**。実際に踏んだ例が [`examples/state-intersect-scroll`](../../examples/state-intersect-scroll) にあります:
|
|
1739
|
+
|
|
1740
|
+
```html
|
|
1741
|
+
<!-- 表示のつもりだった要素。これが唯一の需要の根だった -->
|
|
1742
|
+
<b data-wcs="textContent: $streamStatus.pageResult"></b>
|
|
1743
|
+
```
|
|
1744
|
+
|
|
1745
|
+
```javascript
|
|
1746
|
+
// $updatedCallback は binding 駆動 —— 上の <b> を消すと paths に現れなくなり、
|
|
1747
|
+
// フィードの commit が黙って止まる
|
|
1748
|
+
$updatedCallback(paths) {
|
|
1749
|
+
if (!paths.includes("$streamStatus.pageResult")) return;
|
|
1750
|
+
this.items = this.items.concat(this.pageResult.items);
|
|
1751
|
+
}
|
|
1752
|
+
```
|
|
1753
|
+
|
|
1754
|
+
**規則:** 描画に依存させたくないロジックは、`$watch`(または `$streams` の `args`)に根を置いてください。`$updatedCallback` は「描かれたものに追随する」用途に限ります。
|
|
1755
|
+
|
|
1756
|
+
上の例は `$watch` に置き換え済みで、`<b>` は表示専用に戻っています。この形(`$updatedCallback` が、どのバインディングにも現れないパスを判定に使っている)は **`wcs/updated-callback-unbound`** として静的に検出されます。
|
|
1757
|
+
|
|
1758
|
+
### 残る制約
|
|
1759
|
+
|
|
1760
|
+
需要の根が 3 か所に分かれること自体は変わりません。**ある getter が評価されるかを知るには、その 3 か所(ページの全バインディング・全 `$watch`・全 `$streams.args`)を見る必要があり、getter の定義だけを読んでも分かりません。** lint と DevTools の配線カバレッジはこの照合を機械にやらせるためのものです。
|
|
1761
|
+
|
|
1762
|
+
なお `$watch` に宣言したスカラー getter は **eager** になります(接続時に 1 回、以後は依存に触れたバッチごとに評価)。ワイルドカード行の getter は eager 化しません(初回評価がリスト全体を舐めるため)。
|
|
1763
|
+
|
|
1630
1764
|
## Watch(`$watch`)
|
|
1631
1765
|
|
|
1632
1766
|
`$updatedCallback` は **binding 駆動** です。その更新で live DOM binding が実際に適用された path だけを報告するため、**描画していない値の変化は見えません**。**`$watch`** はその headless 版で、ページ上でそのパスがバインドされているかどうかに関わらず、state の変化で発火します(**ワイルドカードの行パスだけは例外**で、headless に成立させるには `$listKeys` が要ります。後述)。
|
|
@@ -1678,6 +1812,8 @@ $streams: {
|
|
|
1678
1812
|
| ハンドラ間 | `$watch` の宣言順 | **宣言を並べ替える** |
|
|
1679
1813
|
| 同一パスの行間 | `indexes` 昇順 | 固定 |
|
|
1680
1814
|
|
|
1815
|
+
**機構間の層を動かす唯一のもの**が、`state` 参加者を受け付ける `<wcs-view-transition>` です。バインディング適用 —— したがって `$updatedCallback` —— がフレームで着地する一方、`$watch` と `$streams` restart は state アドレスを消費し DOM を見ないので、drain がキューされた microtask に留まります。タグがある間の順序は `$watch` → `$streams` restart → `$updatedCallback` です。この層を並べ替えるものはページ上でこれ 1 つだけです。[docs/timing-and-firing-contract.ja.md](https://github.com/wcstack/wcstack/blob/main/docs/timing-and-firing-contract.ja.md) §4.3 を参照してください。
|
|
1816
|
+
|
|
1681
1817
|
主なルール:
|
|
1682
1818
|
|
|
1683
1819
|
- **自 state のみ** —— パスに `@stateName` は書けません。他の state 要素の watch は宣言時に拒否されます。
|
|
@@ -1939,6 +2075,101 @@ export default {
|
|
|
1939
2075
|
- `$updatedCallback(paths, indexesListByPath)` は、その drain で live binding が適用された path の一覧を受け取ります。binding のない state 書き込みでは呼ばれず、`paths` にも現れません。ワイルドカードをもつパスが更新された場合は、`indexesListByPath` から対象のインデックス情報も取得可能です。`async` を使用できますが、戻り値は await されません。
|
|
1940
2076
|
- Web Component を使用している場合は、コンポーネント側に `async $stateReadyCallback(stateProp)` を定義おくことで、`bind-component` でバインドした状態が利用可能になった瞬間にフックとして呼び出されます。
|
|
1941
2077
|
|
|
2078
|
+
## 遷移アニメーション
|
|
2079
|
+
|
|
2080
|
+
入場アニメーションにこのパッケージは要らない。新しい `for` 行も mount する `if` 分岐も「新しく挿入された要素」なので、素の CSS で足りる。
|
|
2081
|
+
|
|
2082
|
+
```css
|
|
2083
|
+
li {
|
|
2084
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
2085
|
+
@starting-style { opacity: 0; transform: translateY(-4px); }
|
|
2086
|
+
}
|
|
2087
|
+
```
|
|
2088
|
+
|
|
2089
|
+
そこへ届かないのが**退場**と**移動**。削除された行は同期で detach され、並べ替えには中間状態が無い。[`@wcstack/view-transition`](https://github.com/wcstack/wcstack/tree/main/packages/view-transition) を足すと drain の DOM 変更が View Transition の中で行われ、変更前の状態はブラウザがスナップショットしてくれる。
|
|
2090
|
+
|
|
2091
|
+
```html
|
|
2092
|
+
<script type="module" src="https://esm.run/@wcstack/view-transition/auto"></script>
|
|
2093
|
+
<wcs-view-transition naming="auto"></wcs-view-transition>
|
|
2094
|
+
```
|
|
2095
|
+
|
|
2096
|
+
そのタグが `state` 参加者を受け付けている間、知っておくべき帰結が 2 つある。
|
|
2097
|
+
|
|
2098
|
+
- drain は microtask ではなくフレームで着地する。state に書いてから `await Promise.resolve()` で DOM を読むコードは遷移を待つ必要がある。`$updatedCallback` はバインディング適用の直後という*位置*こそ変わらないが、その適用ごと 1 フレーム後ろへずれる。
|
|
2099
|
+
- `$watch` と `$streams` restart は元の microtask に留まるため、`$updatedCallback` の**前**に走るようになる。
|
|
2100
|
+
|
|
2101
|
+
適用すべきバインディングが実際にあるバッチだけがタグへ渡されるので、headless なパスへの書き込みが遷移を起こすことはない。タグが無ければ drain は従来どおり。[docs/timing-and-firing-contract.ja.md](https://github.com/wcstack/wcstack/blob/main/docs/timing-and-firing-contract.ja.md) §4.3 参照。
|
|
2102
|
+
|
|
2103
|
+
## 診断と失敗の扱い
|
|
2104
|
+
|
|
2105
|
+
### 存在しないパスへの配線は報告されます
|
|
2106
|
+
|
|
2107
|
+
配線したパスが state 上で解決しないことが**確実**なとき、バインド確立時(`$watch` は宣言時)に 1 回だけ警告します。診断 code はコンソール・`@wcstack/lint`・VS Code 拡張で共通です:
|
|
2108
|
+
|
|
2109
|
+
```
|
|
2110
|
+
[@wcstack/state] [wcs/binding-path-missing] Bound path "user.nmae" does not resolve on state "default":
|
|
2111
|
+
"nmae" is not declared. Did you mean "name"? Updates to this path will be silently
|
|
2112
|
+
dropped. Validate statically: npx @wcstack/lint <file>.
|
|
2113
|
+
```
|
|
2114
|
+
|
|
2115
|
+
| 状況 | 挙動 |
|
|
2116
|
+
|---|---|
|
|
2117
|
+
| ネストしたパスの打ち間違い(`user.nmae`) | `console.warn`(`wcs/binding-path-missing`)。更新は届かないままなので、直すのは書き手 |
|
|
2118
|
+
| トップレベルのパスの打ち間違い(`cout`) | 読み取り時に throw。文面は上と同じ語彙(did-you-mean 付き) |
|
|
2119
|
+
| `$watch` のキーの打ち間違い | `console.warn`(`wcs/watch-path-missing`)。単一セグメントでも報告する |
|
|
2120
|
+
|
|
2121
|
+
判定は**過小近似**です。静的に決められない形では黙ります —— 誤検知でページを騒がせないことを優先しているためで、以下はすべて警告しません:
|
|
2122
|
+
|
|
2123
|
+
- 親が `null` / `undefined`(初期値 `null` に後から代入する形)
|
|
2124
|
+
- 初期値が空配列のリストの行フィールド(行の形が分からない)
|
|
2125
|
+
- 途中の getter の戻り値のサブプロパティ
|
|
2126
|
+
- mapped な `bind-component` の子スコープ(パスの正本は親側)
|
|
2127
|
+
- `$` 始まりの予約名前空間(`$command.*` など)
|
|
2128
|
+
|
|
2129
|
+
裏を返すと、**警告が出ない = 正しい保証にはなりません**。網羅した検査は `npx @wcstack/lint <file>` 側で行ってください。
|
|
2130
|
+
|
|
2131
|
+
### 添字の本数・階数・循環も検査されます
|
|
2132
|
+
|
|
2133
|
+
パス文字列から機械的に決まる整合は、実行時にも lint にも同じ診断 code で現れます。
|
|
2134
|
+
|
|
2135
|
+
| 診断 | 何を見るか | 直し方 |
|
|
2136
|
+
|---|---|---|
|
|
2137
|
+
| `wcs/index-arity` | `$resolve(path, indexes)` は `*` の本数と**厳密一致**、`$getAll(path, indexes)` / `$setAll(path, indexes, …)` は**上限**(不足は「残りの階層を全展開」という正当な接頭辞) | 本数を合わせる |
|
|
2138
|
+
| `wcs/wildcard-rank` | パスの `*` の本数(と `$N` の N)が、囲む `for` の段数を超えていないか | `for` を足すか、`$resolve(path, indexes)` で行を明示する |
|
|
2139
|
+
| `wcs/getter-cycle` | パス getter どうしが循環参照していないか | 循環を断つ |
|
|
2140
|
+
|
|
2141
|
+
`$resolve` / `$getAll` の**添字の超過は以前は黙って捨てられ**、取り違えたまま「もっともらしい値」が返っていました。現在はどちらもエラーです:
|
|
2142
|
+
|
|
2143
|
+
```javascript
|
|
2144
|
+
// ❌ "*" は 1 本しか無いのに 2 本渡している → 以前は items[0] の値が返っていた
|
|
2145
|
+
this.$resolve("items.*.price", [row, col]);
|
|
2146
|
+
|
|
2147
|
+
// ✅ 2 次元なら 2 本
|
|
2148
|
+
this.$resolve("matrix.*.*", [row, col]);
|
|
2149
|
+
// ✅ $getAll の不足は「残りを全部」の意味なので正当
|
|
2150
|
+
this.$getAll("matrix.*.*", [row]);
|
|
2151
|
+
```
|
|
2152
|
+
|
|
2153
|
+
### バインディング 1 本の失敗は 1 本に閉じ込められます
|
|
2154
|
+
|
|
2155
|
+
バインディングの適用が throw しても、そのバッチの残り・`$updatedCallback`・`$watch`・`$streams` の restart はすべて続行します。失敗は握り潰されず、`console.error` と DevTools(`state:binding-apply-error`)に出ます。
|
|
2156
|
+
|
|
2157
|
+
```
|
|
2158
|
+
[@wcstack/state] binding "text: items.*.label" failed to apply; the rest of this batch continues.
|
|
2159
|
+
```
|
|
2160
|
+
|
|
2161
|
+
隔離しない場合、1 本の throw が「値は新しいのに DOM は途中まで」という半端な状態を作り、しかも `$watch` と stream の restart が丸ごと消えていました(README のこの下にある発火順の契約が黙って破れる)。
|
|
2162
|
+
|
|
2163
|
+
### 値と DOM は巻き戻しません
|
|
2164
|
+
|
|
2165
|
+
異常系はすべて「報告して続行」で、適用済みの値を戻すことはありません。これは以下で共通の姿勢です:
|
|
2166
|
+
|
|
2167
|
+
| 機構 | 上限 | 超過時 |
|
|
2168
|
+
|---|---|---|
|
|
2169
|
+
| 因果伝播の hop | 32 | その transaction の未処理レコードのみ quarantine |
|
|
2170
|
+
| `$watch` の書き込み連鎖 | 32 | そのバッチの watch 発火をスキップ |
|
|
2171
|
+
| バインディングの適用失敗 | — | その 1 本のみスキップ |
|
|
2172
|
+
|
|
1942
2173
|
## 設定
|
|
1943
2174
|
|
|
1944
2175
|
`bootstrapState()` に部分的な設定オブジェクトを渡します:
|
|
@@ -1960,13 +2191,41 @@ bootstrapState({
|
|
|
1960
2191
|
|---|---|---|
|
|
1961
2192
|
| `bindAttributeName` | `'data-wcs'` | バインディング属性名 |
|
|
1962
2193
|
| `tagNames.state` | `'wcs-state'` | 状態要素のタグ名 |
|
|
1963
|
-
| `locale` | `'en'` |
|
|
2194
|
+
| `locale` | `<html lang>`、無ければ `'en'` | ロケール依存フィルタ(`locale` / `date` / `time` / `datetime`)のロケール — [ロケール](#ロケール)を参照 |
|
|
1964
2195
|
| `debug` | `false` | デバッグモード |
|
|
1965
2196
|
| `enableMustache` | `true` | `{{ }}` 構文の有効化 |
|
|
1966
2197
|
| `enableDirectionalInitialSync` | `true` | 方向認識のバインディング authority(`#init=` / `#sync=` バインド modifier)— [バインディング authority](#バインディング-authority-init--sync) 参照。既定 on。`false` で opt-out |
|
|
1967
2198
|
| `enablePropagationContext` | `true` | バインド間の因果伝播トラッキング(echo/diamond のループ防止)。既定 on。`false` で opt-out |
|
|
1968
2199
|
| `enableContractAnalyzer` | `false` | opt-in の開発時 contract analyzer(`analyzeContract` を公開) |
|
|
1969
2200
|
|
|
2201
|
+
### ロケール
|
|
2202
|
+
|
|
2203
|
+
ロケールで書式化するフィルタは 4 つある — `locale` / `date` / `time` / `datetime`。
|
|
2204
|
+
これらは `config.locale` を読み、その既定は **`<html lang>`** である。
|
|
2205
|
+
|
|
2206
|
+
```html
|
|
2207
|
+
<html lang="ja-JP">
|
|
2208
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
2209
|
+
```
|
|
2210
|
+
|
|
2211
|
+
他に何も要らない。`<html lang>` はページの言語を書く HTML 標準の場所であり、
|
|
2212
|
+
そこを既定にすればロケールの正本が 1 つで済む。同時に、**CDN 一発のページが
|
|
2213
|
+
ロケールを設定できるようになる** — `auto` は `bootstrapState()` を引数なしで呼ぶので、
|
|
2214
|
+
これが無いと渡す口が無かった。明示指定(`bootstrapState({ locale })`)は常に優先し、
|
|
2215
|
+
不正な BCP-47 タグは `Intl` の中で落ちる前に警告して無視する。
|
|
2216
|
+
|
|
2217
|
+
**`config.locale` を後から変えても何も再描画されない。** これは state ではなく
|
|
2218
|
+
グローバル設定なので、依存グラフに載らない。フィルタ自体はバインド構築時に
|
|
2219
|
+
取り込むのではなく**適用のたびに読む**ので、別の理由で再描画されたバインドは新しい値を
|
|
2220
|
+
拾う — 起動順序の事故から復帰するには足りるが、ページの言語を切り替えるには足りない。
|
|
2221
|
+
言語はページが描画される前に決めること。マークアップに `<html lang>` を書くか、
|
|
2222
|
+
`<head>` の同期スクリプトで書けば構造的にそうなる。
|
|
2223
|
+
|
|
2224
|
+
呼び出しごとの上書き(`price|locale(fr-FR)`)は従来どおり使え、こちらはバインド式の
|
|
2225
|
+
一部なのでバインド時に固定される。リロードなしで言語を切り替えたい場合は
|
|
2226
|
+
[docs/i18n-design.md](../../docs/i18n-design.md) を参照。短く言えば、翻訳はフィルタでは
|
|
2227
|
+
なくパスに置く。
|
|
2228
|
+
|
|
1970
2229
|
> この 3 つは **architecture-hardening** 機能で、規範は `docs/architecture-hardening/` に
|
|
1971
2230
|
> あります。`enablePropagationContext` は**既定 on** — write-path コストは一方向バインドで
|
|
1972
2231
|
> ほぼゼロ(echo しうる双方向 wire のみ因果 bookkeeping を行う)で、フラグは恒久的な
|