Show a product商品を1つ表示する

Start with one HTML file. You'll show a product kept in state and make the “Add to cart” button count clicks — the whole loop of wcstack, in about forty lines.

HTMLファイル1つから始めます。stateに置いた商品を表示し、「Add to cart」ボタンでクリック数を数えます。wcstackの基本の流れが、40行ほどにすべて入っています。

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

The resultできあがり

This is the page you are about to write, running. Click the button and watch the badge in the header.

これから書くページが動いているところです。ボタンを押して、ヘッダーのバッジを見てください。

Live — this step's pageライブ — このステップのページOpen in a new tab ↗新しいタブで開く ↗

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を読み込み、スタイルシートがショップの見た目を付けます。

index.htmlthe whole fileファイル全体
<!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を自分で触ることはありません。

Try itやってみよう

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で説明します。

terminal
$ npx @wcstack/lint index.html

0 error(s), 0 warning(s), 0 info