The resultできあがり
This is the page you are about to write, running. Click the button and watch the badge in the header.
これから書くページが動いているところです。ボタンを押して、ヘッダーのバッジを見てください。
Write the pageページを書く
Make a folder, save the code below in it as index.html, and open the file in your browser — a double-click is enough. There is nothing to install: the one <script> tag in <head> loads @wcstack/state from a CDN, and the stylesheet gives the shop its look.
フォルダを作り、下のコードをindex.htmlとして保存して、ブラウザで開きます(ダブルクリックで十分です)。インストールするものはありません。<head>の<script>タグ1つがCDNから@wcstack/stateを読み込み、スタイルシートがショップの見た目を付けます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Little Roastery</title>
<link rel="stylesheet" href="https://wcstack.github.io/tutorial/shop.css">
<script type="module" src="https://esm.run/@wcstack/state@4/auto"></script>
</head>
<body>
<wcs-state>
<script type="module">
export default {
product: {
name: "House Blend",
description: "Chocolate, caramel, a hint of citrus.",
price: 14,
icon: "☕"
},
cartCount: 0,
addToCart() {
this.cartCount += 1;
}
};
</script>
</wcs-state>
<header class="shop-header">
<h1>Little Roastery</h1>
<p class="cart-badge">Cart: <span data-wcs="textContent: cartCount"></span></p>
</header>
<main class="shop">
<article class="card">
<div class="card-icon" data-wcs="textContent: product.icon"></div>
<h2 data-wcs="textContent: product.name"></h2>
<p data-wcs="textContent: product.description"></p>
<p class="price">$<span data-wcs="textContent: product.price|toFixed(2)"></span></p>
<button type="button" data-wcs="onclick: addToCart">Add to cart</button>
</article>
</main>
</body>
</html>
How it worksしくみ
One script tagscriptタグ1つ
<script type="module" src="https://esm.run/@wcstack/state@4/auto"> defines the <wcs-state> element, and that is all the setup there is: no initialization call, no bundler, no configuration. @4 keeps the page on version 4 when a later major version is released.
<script type="module" src="https://esm.run/@wcstack/state@4/auto">が<wcs-state>要素を定義します。準備はこれだけです。初期化の呼び出しも、バンドラも、設定もいりません。@4は、この先メジャーバージョンが上がっても、ページをバージョン4にとどめます。
The state lives in <wcs-state>stateは<wcs-state>の中に
The object that the inner <script type="module"> exports by default becomes the state: product, cartCount and the method addToCart. A page has one state tree, and every binding on the page reads from it.
内側の<script type="module">がexport defaultしたオブジェクトが、そのままstateになります。ここではproductとcartCount、そしてメソッドaddToCartです。stateの木はページに1本で、ページ上のバインディングはすべてそこから読みます。
A path is the contractパスが唯一の契約
data-wcs="textContent: product.name" reads: set this element's textContent to the value at the path product.name. The HTML holds nothing but that string — no reference to the object, no component instance — so you can rearrange the markup without touching the state, and reshape the state without touching the markup as long as the paths stay.
data-wcs="textContent: product.name"は「この要素のtextContentに、パスproduct.nameの値を入れる」と読みます。HTMLが持っているのはこの文字列だけで、オブジェクトへの参照もコンポーネントのインスタンスもありません。だから、マークアップを組み替えてもstateには触れずに済み、パスさえ保てばstateの形を変えてもマークアップに触れずに済みます。
|toFixed(2) is a filter. It changes the value on its way to the page — 14 becomes "14.00" — and leaves the state alone. A filter takes one value; you'll meet more of them in the next steps.
|toFixed(2)はフィルタです。ページへ向かう途中で値を変え(14が"14.00"になる)、state自体はそのままです。フィルタが受け取る値は1つです。この先のステップでほかのフィルタも出てきます。
An event, then an updateイベントから更新まで
onclick: addToCart calls the method when the button is clicked. Inside a method, this is the state, and this.cartCount += 1 assigns to the path cartCount. That assignment is what wcstack reacts to: every binding that reads cartCount — here, the badge in the header — updates. You never touch the DOM yourself.
onclick: addToCartは、ボタンが押されたときにメソッドを呼びます。メソッドの中のthisはstateで、this.cartCount += 1はパスcartCountへの代入です。wcstackが反応するのはこの代入です。cartCountを読んでいるバインディング(ここではヘッダーのバッジ)がすべて更新されます。DOMを自分で触ることはありません。
Change price: 14 to price: 9.5 and reload: the card shows $9.50. Then change cartCount: 0 to cartCount: 10 — the badge starts at 10 and counts on from there.
price: 14をprice: 9.5に変えて再読み込みすると、カードは$9.50を表示します。続けてcartCount: 0をcartCount: 10にすると、バッジは10から数え始めます。
Common mistakesよくある間違い
{{ }} outside a template<template>の外の{{ }}
wcstack also reads {{ product.name }} in text, but on the page itself the braces stay visible until the state has loaded. Outside a <template>, bind with textContent: as above (or the comment form <!--@@: product.name-->). Inside a template — next step — nothing is shown before binding, and {{ }} is the convenient form. The checker in step 7 points this out as wcs/template-syntax.
wcstackはテキスト中の{{ product.name }}も読みますが、ページに直接書くと、stateが読み込まれるまで波括弧がそのまま見えてしまいます。<template>の外では、上のようにtextContent:で(あるいはコメントの形<!--@@: product.name-->で)バインドします。テンプレートの中(次のステップ)では描画前に何も見えないので、{{ }}が手軽です。ステップ7の検査器は、これをwcs/template-syntaxとして指摘します。
Binding a key you didn't declare宣言していないキーへのバインド
Declare every key you bind in the initial state, even when its value is 0 or null. Remove cartCount: 0 and the badge stays empty, with [wcs/binding-path-missing] Path "cartCount" does not exist on the state tree. in the console. A method can still create the key later, but the binding was made, and failed, when the page loaded.
バインドするキーは、値が0やnullでも、すべて最初のstateに宣言します。cartCount: 0を消すとバッジは空のままになり、コンソールに[wcs/binding-path-missing] Path "cartCount" does not exist on the state tree.が出ます。メソッドが後からそのキーを作ることはできますが、バインディングはページの読み込み時に作られ、そこで失敗しています。
Check確かめる
If you have Node.js, run the checker in the folder. It is optional for now; step 7 explains it.
Node.jsがあれば、フォルダで検査器を実行してみましょう。今は任意です。くわしくはステップ7で説明します。
$ npx @wcstack/lint index.html 0 error(s), 0 warning(s), 0 info