👻

RustのフレームワークYewを使ってみる②

2023/09/16に公開

前回までのあらすじ

コンテナ環境でYewプロジェクトを開発できるようにディレクトリや各ファイルを作成して、公式のテンプレートを実行してみました。
また、プログラムを確認し、app.rsを公式にしたがって少し書き換えました。

Yewproject/  
├── yewapp/
│   ├── dist
│   ├── src
│   │   ├── app.rs
│   │   └── main.rs
│   ├── target
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── Dockerfile
│   ├── index.html
│   └── index.scss
└── docker-compose.yml

プログラムを簡単にする

これからいろいろな機能を試してみる際に、htmlの表示が単純な方がわかりやすいので、index.scssの内容をすべて削除し、app.rsのhtmlマクロの部分を書き換えます。

app.rs
use yew::prelude::*;

#[function_component]
pub fn App() -> Html {
    html! {
        <main>
            <h1>{ "Hello World!!" }</h1>
            <p>{ "LOGIN" }</p>
            <span>{ "Rust with Yew test" }</span>
        </main>
    }
}

コンソールを使ってみる

今回は、今後のデバッグ用にコンソールを使えるようしてみようと思います。
バージョン0.18.0まではYewがConsoleServiceを提供していたが、バージョン0.19.0以降は、weblogかgloo_consoleの使用が推奨されています。
https://yew.rs/docs/0.18.0/concepts/services/introduction
今回は、gloo_consoleを使用してみます。

app.rs
use yew::prelude::*;
use gloo_console::log;

#[function_component]
pub fn App() -> Html {
    log!("App was loaded");
    html! {
        <main>
            <h1>{ "Hello World!!" }</h1>
            <p>{ "LOGIN" }</p>
            <span>{ "Rust with Yew test" }</span>
        </main>
    }
}

コンソールのログを確認すると「App was loaded」と表示されています。
wasm_bindgenとweb_sysを利用した書き方もできるので、試してみた。

app.rs
use yew::prelude::*;
use wasm_bindgen::prelude::*;
use web_sys::console;

#[function_component]
pub fn App() -> Html {
    console::log_1(&JsValue::from_str("App was loaded"));
    html! {
        <main>
            <h1>{ "Hello World!!" }</h1>
            <p>{ "LOGIN" }</p>
            <span>{ "Rust with Yew test" }</span>
        </main>
    }
}

web_sysクレートのconsoleモジュールは、WebAssemblyに対して、ブラウザ上でJavaScriptのconsoleオブジェクトと同様の機能を提供します。
log_1メソッドは、js_sys::JsValue型の引数を必要とするため、wasm_bindgenクレートを利用します。
gloo_consoleと比較して、少し複雑ですでが、ログに表示する内容の型を細かく指定したりできるようです。
今回は、とりあえず文字列が表示されれば良いので、gloo_consoleを使っていこうと思います。

ファンクションコンポーネントを追加してみる

App関数に#[function_component]というアトリビュート(属性)がついています。
これは、App関数がYewのコンポーネントであることを示しています。
この関数はYewのコンポーネントとして扱われ、html!マクロを使用してHTML要素を定義し、それを返します。
一つのコンポーネントの中にたくさんの機能を詰め込んで書くと、プログラムがわかりにくくなるので、機能などを増やす際には、新たなコンポーネントに分けて書くと都合が良いです。
試しにpタグの内容を新しいコンポーネントに分けて書いてみます。

app.rs
use yew::prelude::*;
use gloo_console::log;

#[function_component]
fn Login() -> Html {
    log!("LOGIN was loaded");
    html! {
        <p>{ "LOGIN" }</p>
    }
}

#[function_component]
pub fn App() -> Html {
    log!("App was loaded");
    html! {
        <main>
            <h1>{ "Hello World!!" }</h1>
            <Login />
            <span>{ "Rust with Yew test" }</span>
        </main>
    }
}

Login関数は、コンソールへのログ表示と元々App関数に記述していたLOGINを表示します。このLogin関数をApp関数のhtml!マクロ内で<Login />というように読み込んでいて、前回表示した内容と全く同じものが表示されます。
https://yew.rs/ja/docs/concepts/html/components#基本

プロパティを使ってみる

プロパティは、Yewが監視し続けることができる基本的なコンポーネントの引数です。
ある型がコンポーネントのプロパティとして扱われるためには、Properptiesトレイトを実装する必要があります。
https://yew.rs/ja/docs/concepts/function-components/properties
Propertiesトレイトは、プロパティの変更を検出し、コンポーネントの再描画をトリガーするのに役立ちます。これにより、不要な再描画を防ぎ、パフォーマンスを向上させることができます。また、プロパティを型付けすることにより、型安全性が向上します。
Deriveマクロを使用すると、簡潔なコードでプロパティを定義できます。

app.rs
use yew::prelude::*;
use gloo_console::log;

#[derive(Properties, PartialEq)]
pub struct Props {
    #[prop_or(true)]
    pub is_loading: bool,
}

#[function_component]
fn Login(props: &Props) -> Html {
    log!("LOGIN was loaded");
    if props.is_loading {
        //trueのときの処理
        html! {
            <>
                <p>{"LOGIN"}</p>
                <p>{format!("is_loading: {}", props.is_loading)}</p>
            </>
        }
    } else {
        //falseのときの処理
        html! {
            <>
                <p>{"LOGOUT"}</p>
                <p>{format!("is_loading: {}", props.is_loading)}</p>
            </>
        }
    }
}

#[function_component]
pub fn App() -> Html {
    log!("App was loaded");
    html! {
        <main>
            <h1>{ "Hello World!!" }</h1>
            <Login />
            <span>{ "Rust with Yew test" }</span>
        </main>
    }
}

Login関数の引数にDeriveマクロで定義したProps(bool型)を追加します。Propsはデフォルトでtrueになるように#[prop_or(true)]としています。
Login関数の中で、is_loadingがtrueかfalseかによって表示内容を変更するようにしています。今回は、is_loadingを変更していないので、デフォルトのtrueの状態で処理されます。

falseをis_loadingに渡す場合は、App関数の<Login />を<Login is_load={false} />と書き換えます。

app.rs
#[function_component]
pub fn App() -> Html {
    log!("App was loaded");
    html! {
        <main>
            <h1>{ "Hello World!!" }</h1>
            <Login is_loading={false}/>
            <span>{ "Rust with Yew test" }</span>
        </main>
    }
}

フラグメント

html!マクロの中で要素が2以上になった時に「空タグ」を使用しました。

app.rs
<>
    <p>{"LOGIN"}</p>
    <p>{format!("is_loading: {}", props.is_loading)}</p>
</>

これは、html!マクロでは、ルートノードが常に1つでなければなりません。この制限を回避するために使用しました(これらは「フラグメント」とも呼ばれます)。
https://yew.rs/ja/docs/concepts/html/fragments

次回

コールバックを使えるようにしたい。

Discussion