English

シグナル

ページの状態は Rust の構造体 1 つで表し、シグナルはそのフィールド 1 つを指す型付きの参照です。

シグナル構造体

serde の Serialize と Deserialize と並べて Signals を derive します。この derive はフィールドごとにそのフィールドと同じ名前で Sig を返す関数を追加します。Sig<Counter, i32> は「シグナル構造体 Counter の中の i32」と読みます。

use hypersig::{Sig, Signals};
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
struct Counter {
    count: i32,
    label: String,
}

// The derive adds one function per field, named after the field.
let count: Sig<Counter, i32> = Counter::count();
let label: Sig<Counter, String> = Counter::label();

assert_eq!(count.path(), "count");
assert_eq!(label.path(), "label");

ブラウザは構造体を 1 つの JSON オブジェクトとして持ち、式も文も差分も Sig を通してそのフィールドを指します。打ち間違えたフィールド名には呼び出せる関数がないので、次のようにコンパイルが通りません。

use hypersig::Signals;
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
struct Counter {
    count: i32,
}

// error[E0599]: no associated function or constant named `cuont` found for struct `Counter`
let count = Counter::cuont();

Sig<S, T> の S はあるページのシグナルを別のページで使うことも防ぎます。Counter のページの要素は El<Counter> で、Counter のシグナルを使った式しか受け取りません。

フィールドの中のフィールド

フィールド自体を Signals の構造体にもできます。その中へは at でたどり、結果はほかと同じ Sig になります。パスの深さはフィールド 8 つまでです。

use hypersig::html::span;
use hypersig::Signals;
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
struct Address {
    city: String,
}

#[derive(Signals, Serialize, Deserialize)]
struct Account {
    name: String,
    address: Address,
}

// `at` reaches a field of the struct a signal holds.
let city = Account::address().at(Address::city());
assert_eq!(city.path(), "address.city");

let line = span::<Account>().bind_text(city);

値や構造体の Vec はリストで、リスト で説明します。Option のフィールドには is_some、is_none、unwrap_or があります。

最初の描画

Page::new が初期値を受け取り、Page::render がその値ですべての束縛を評価します。テキスト、表示と非表示、クラス、属性、フォームの値、リストの項目は、ランタイムを読み込む前から HTML に入っています。ランタイムはそれらの要素を作り直さず、そのまま使います。データは <body> に置いたシグナル構造体の JSON なので、ブラウザはページを表示するために何も取得しません。

計算されるシグナル

Page::computed(signal, value) は、フィールドをほかのフィールドを使った式の値に合わせ続けます。束縛はそれぞれ式を評価する代わりに、ほかのフィールドと同じようにこの値を読み、値が変わったときだけ評価し直します。計算されるシグナルは宣言した順に評価されるので、先に宣言したものを読めます。

実行結果

cart.rs
use hypersig::html::{div, p, span};
use hypersig::style::pattern::{hstack, stack};
use hypersig::style::value::rem;
use hypersig::{Page, Signals};
use hypersig_ui::{ButtonSize, ButtonVariant, button};
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
pub struct Item {
    name: String,
    price: f64,
}

#[derive(Signals, Serialize, Deserialize)]
pub struct Cart {
    item: Item,
    quantity: i32,
    total: f64,
}

pub fn page() -> Page<Cart> {
    let item = Cart::item();
    let quantity = Cart::quantity();
    let total = Cart::total();
    let initial = Cart {
        item: Item {
            name: "緑茶"
                .into(),
            price: 4.5,
        },
        quantity: 1,
        total: 0.0,
    };
    Page::new(
        "カート",
        initial,
    )
    .computed(total, item.at(Item::price()) * quantity.to_f64())
    .child(
        div()
            .css(stack(rem(0.75)))
            .child(
                p().child(span().bind_text(item.at(Item::name())))
                    .text("、単価 $")
                    .child(span().bind_text(item.at(Item::price()).to_fixed(2))),
            )
            .child(
                div()
                    .css(hstack(rem(0.75)))
                    .child(
                        button(ButtonVariant::Outline, ButtonSize::Sm)
                            .attr_when("disabled", quantity.le(1))
                            .on_click(quantity.set(quantity - 1))
                            .text("-"),
                    )
                    .child(span().bind_text(quantity.to_text()))
                    .child(
                        button(ButtonVariant::Outline, ButtonSize::Sm)
                            .on_click(quantity.set(quantity + 1))
                            .text("+"),
                    ),
            )
            .child(
                p().text("合計 $")
                .child(span().bind_text(total.to_fixed(2))),
            ),
    )
}
文やサーバーが計算されるフィールドに値を設定することは止められません。設定した値は、式が読むフィールドが変わるまで残ります。