【Rust】Yewを使ってフロント実装します。その１: 使い方編

こんにちは！

[前回](https://qiita.com/matilda-ray-kerria/items/a680bc7e593495c9f161)は Rust と [Yew](https://yew.rs/) を使って開発を始める準備を行いましたね！
今回のアーティクルでは開発を進めるためのヒントや Yew の一部機能を紹介します。

ではさっそくやっていきましょう！

## 基本操作

### Console

Yew は WASM にコンパイルしますが、 DOM を操作して動的な Web ページを作ることができるクレートです。

オブジェクトを出したり消したり、時には少し複雑な計算をしたり、そんな時には操作の過程で途中の値や状態を出力したい場合があります。 Javascript では `console` を使用してブラウザコンソールから確認する方法がよく知られていますね。 Yew 上で `console` を使用したいときは、 `gloo` クレートを使用します。

```bash: gloo crate追加
cargo add gloo
```

```rust: main.rs
use gloo::console;
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    console::log!("Hello log!");
    console::debug!("Hello debug!");
    console::info!("Hello info!");
    console::warn!("Hello warn!");
    console::assert!(true, "Hello world!");
    console::assert!(false, "This will fail!");
    console::table!([0, 1, 2], ["a", "b"]);
    html! {
        <div>
            {"hello yew"}
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

`gloo` の `console` モジュールは Javascript における `console` のようにブラウザコンソールを操作するためのものです。 `console` のすべての機能が再現されているわけではありませんが、これを使えばデバッグ作業も捗るでしょう！
他にどのような機能を持ち、どのように使うのかは API のリファレンスを確認してみてください！

https://docs.rs/gloo/latest/gloo/console/index.html

:::note info
`gloo` クレートは Rust で WASM 開発を行うための様々な機能を提供するため、他のモジュールについても調べてみるととても役に立つでしょう！
:::

### プロパティによるイベントハンドリング

オブジェクトに対するイベントを引き金に何かアクションを起こしたいときはイベントトリガープロパティを使用します。

ボタンを押した時にアクションを起こしたいときは `onclick` プロパティを使用します。

```rust: onclick
use gloo::console;
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let click = |_| console::log!("button is clicked");

    html! {
        <button onclick={click}>
            { "Click me!" }
        </button>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

ここで `onclick` プロパティに渡している `click` は引数を1つ受け取る（そして引数を使わない）クロージャです。ボタンがクリックされると `MouseEvent` を受け取り、 `click` が実行されます。

:::note warn
プロパティ名は `onclick` のように全て小文字にする必要があります。 `onClick` のようなキャメルケースだと型エラーになってしまいます。
:::

文字の入力欄が変更されるたびにアクションを起こしたいときは `oninput` プロパティを使用します。

```bash: web-sys crate追加
cargo add web-sys
```

```rust: oninput
use gloo::console;
use web_sys::HtmlInputElement;
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let text_change = {
        move |event: InputEvent| {
            console::log!(event
                .target_dyn_into::<HtmlInputElement>()
                .expect("cast failed")
                .value());
        }
    };

    html! {
        <input type="text" value={ "edit here!" } oninput={text_change} />
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

イベントによってはイベントハンドラに明示的に受け取るイベント型を指定する必要があるので注意しましょう。
イベントからDOM要素を取り出す場合は要素型の情報も必要になるので、必要に応じて `web_sys` クレートも追加して使用しましょう。

:::note info
イベントリスナープロパティやイベント型の情報一覧は Yew のドキュメントに記述されているので確認してみましょう！
:::

https://yew.rs/docs/concepts/html/events#available-events

### フラグメント `<></>`

Yew でコンポーネントを記述していると、複数のコンポーネントを並べて返したいときがあります。

```rust: コンパイルエラー
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    html! {
        <div>
            {"Hello"}
        </div>
        <div>
            {"Yew"}
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

しかし、一つのコンポーネントのみを受け渡しするべき場所で複数のコンポーネントを並べてしまうとコンパイルエラーとなってしまいます。
複数のコンポーネントを並べて受け渡ししたいときは、フラグメント `<></>` で囲うようにしましょう。

```rust: フラグメント <>< / > で囲う
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    html! {
        <>
            <div>
                {"Hello"}
            </div>
            <div>
                {"Yew"}
            </div>
        </>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

## yew::functional hooks

Yew で DOM 操作をするときには、ストラクトコンポーネント、または関数コンポーネントを作成して用いることになります。

ストラクトコンポーネントはその名の通り、ストラクトなのでフィールドを保持してコンポーネントの状態を管理することができます。

一方、関数コンポーネントは最後にHTMLオブジェクトを返す関数なので、フィールドや変数を保持することができません。
つまり、関数コンポーネントでは状態を保持することができないのですが、 `yew::functional` モジュールには関数コンポーネントでも状態を保持するための機能が用意されています。
関数コンポーネントで状態を管理するための機能 **hook** をいくつか紹介します。

### `use_state`

ボタンを押すたびに表示するカウントを 1 ずつ増やしたり、 2 倍にしたりするアプリケーションを作りたいとします。
そして次のように書いてみました。

```rust
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let mut count = 0_i128;
    let plus = { move |_| count = count.saturating_add(1) };
    let double = { move |_| count = count.saturating_mul(2) };

    html! {
        <div>
            <button onclick={ plus }>{"+1"}</button>
            <button onclick={ double }>{"x2"}</button>
            <p>{ count }</p>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

この例では（そもそも型制約でコンパイルできないのですが、動作したとしても） `App` コンポーネントは関数コンポーネントなので描画される度に `count` は 0 になってしまいます。
（さらに言えば、再描画すらされないので絶対に表示内容が変わりません。）

そこで、利用できるのが `use_state` です。
`count` を `use_state` を使って状態として管理することで、 `count` を設定する度に描画が実行され、 値が 0 になることもなくなります。

```rust: use_state
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let count = use_state(|| 0_i128);
    let plus = {
        let count = count.clone();
        move |_| count.set(count.saturating_add(1))
    };
    let double = {
        let count = count.clone();
        move |_| count.set(count.saturating_mul(2))
    };

    html! {
        <div>
            <button onclick={ plus }>{"+1"}</button>
            <button onclick={ double }>{"x2"}</button>
            <p>{ *count }</p>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

`use_state` の引数は **初期値を返す関数** で、 `UseStateHandle` を返します。
`UseStateHandle` は `set` メソッドを持っており、 `set` メソッドに値を渡して実行すると `UseStateHandle` が保持する値を変更することができます。
`UseStateHandle` が保持する値は Deref `*` で参照することができます。

また、 `set` メソッドが実行されると、その `UseStateHandle` を持つコンポーネントが再描画されます。
「+1」ボタンを押すと `count` に 1 を加算した値が `set` され、 `count` が保持する値が変更されると同時に再描画されます。すると、再描画時の `App` の実行では `count` の値が `set` した値になるので、 `p` タグの中身も変更されます。

:::note info
この **hook** のおかげで、関数コンポーネントでも状態を保持することができるようになります！
関数コンポーネントでは状態を管理するためにさまざまな hook を利用しなければいけないので面倒に感じるかもしれません。 しかし、ストラクトコンポーネントでは自身に状態を持ち続け、 Rust における所有権やライフタイムのシステムを含めて状態を管理しなければならないため、関数コンポーネントを利用した方がコードの記述を煩雑にせずに済ませやすくなります。
:::

### `use_effect`

次に紹介するのは `use_effect` です。
`use_effect` はコンポーネントがマウントされた時と再描画が起きた時に実行される関数を登録するための hook です。

```rust: use_effect
use gloo::console;
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let count0 = use_state(|| 0_i128);
    let plus = {
        let count = count0.clone();
        move |_| count.set(count.saturating_add(1))
    };

    let count1 = use_state(|| 1_i128);
    let double = {
        let count = count1.clone();
        move |_| count.set(count.saturating_mul(2))
    };

    use_effect(|| {
        console::log!("effect");
    });

    html! {
        <div>
            <button onclick={ plus }>{"+1"}</button>
            <p>{ *count0 }</p>
            <button onclick={ double }>{"x2"}</button>
            <p>{ *count1 }</p>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

`use_effect` の引数は **実行する関数** で、コンポーネントがマウントされた時と再描画が起きた時に実行されます。
上記の例では、 `App` コンポーネントがマウントされた時とボタンを押した時に `console::log!("effect")` が実行されます。
ブラウザコンソールを開きながらページをリロードしたり、ボタンを押して試してみてください。

再描画が起きる度に実行ではなく、特定の状態が変更された時に実行したい場合は `use_effect_with` を使用します。

```rust: use_effect_with
use gloo::console;
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let count0 = use_state(|| 0_i128);
    let plus = {
        let count = count0.clone();
        move |_| count.set(count.saturating_add(1))
    };

    let count1 = use_state(|| 1_i128);
    let double = {
        let count = count1.clone();
        move |_| count.set(count.saturating_mul(2))
    };

    use_effect_with(*count1, |v| {
        console::log!(format!("effect: {}", *v));
    });

    html! {
        <div>
            <button onclick={ plus }>{"+1"}</button>
            <p>{ *count0 }</p>
            <button onclick={ double }>{"x2"}</button>
            <p>{ *count1 }</p>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

`use_effect_with` の第１引数は 変更を検知したい値で、第２引数は **実行する関数** です。
第２引数に指定する関数は、変更された第１引数に指定した値を受け取って実行されます。

ブラウザコンソールを開きながらボタンを押して、「x2」ボタンを押したときだけ実行されることを確かめてください。

`use_effect_with` ではコンポーネントがマウントされた最初の描画時のみ実行されるようにすることもできます。
その場合は `()` を第１引数へ渡します。

また、実行する関数からの戻り値として関数を返すと、コンポーネントがアンマウントされる時にその関数が実行されます。

```rust: use_effect_with init and destroy
use gloo::console;
use yew::prelude::*;

#[function_component]
fn UseEffect() -> Html {
    let count = use_state(|| 0_i128);
    let plus = {
        let count = count.clone();
        move |_| count.set(count.saturating_add(1))
    };

    use_effect_with((), |v| {
        console::log!(format!("effect: {:?}", v));

        || console::log!("cleanup")
    });

    html! {
        <div>
            <button onclick={ plus }>{"+1"}</button>
            <p>{ *count }</p>
        </div>
    }
}

#[function_component]
fn App() -> Html {
    let flag = use_state(|| false);
    let toggle = {
        let flag = flag.clone();
        move |_| flag.set(!*flag)
    };

    html! {
        <div>
            <p>{ "check on console" }</p>
            <button onclick={ toggle }>{ if *flag { "hide" } else { "show" } }</button>
            if *flag { <UseEffect /> }
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

ブラウザコンソールを開きながらボタンを押してみてください。
カウンターが出現する時に `effect` が実行され、カウンターが消える時に `cleanup` が実行されることが確認できます。

:::note info
初期状態を設定する時に何か処理が必要な場合や、コンポーネントがアンマウントされる時に何か処理をしたい場合はこのように `use_effect_with` を使用しましょう。
:::

### `use_context`

外部からコンポーネントにデータを渡すには、通常コンポーネントのプロパティを経由します。
しかし、コンポーネントの階層が深くなると、親コンポーネントから子コンポーネント、孫コンポーネントとデータを渡すのが面倒になってきます。
そんな時に便利なのが `use_context` です。

```rust: use_context
use yew::prelude::*;

#[function_component]
fn GrandchildComponent() -> Html {
    let string = use_context::<String>().expect("no context found");

    html! {
        <>
            <p> { "there is in grandchild" } </p>
            <p> { string } </p>
        </>
    }
}

#[function_component]
fn ChildComponent() -> Html {
    let string = use_context::<String>().expect("no context found");

    html! {
        <>
            <p> { "there is in child" } </p>
            <p> { string } </p>
            <GrandchildComponent />
        </>
    }
}

#[function_component]
fn App() -> Html {
    let string = String::from("how are you?");

    html! {
        <>
            <p> { string.clone() } </p>
            <ContextProvider<String> context={string}>
                <ChildComponent />
            </ContextProvider<String>>
        </>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

`ContextProvider` は `use_context` で使用するコンテキストを提供するコンポーネントです。
`ContextProvider` で囲まれていれば子コンポーネントでも孫コンポーネントでも `use_context` でコンテキストを受け取ることができます！

:::note info
`ContextProvider` には `use_context` で受け取るコンテキストの型を指定する必要がありますが、この型には `Clone` と `PartialEq` トレイトが実装されている必要があります。
自分で作成したストラクトをコンテキストに指定したい場合は `Clone` と `PartialEq` トレイトを実装し忘れないように注意しましょう。
:::

:::note warn
`ContextProvider` と `use_context` を用いればPropsを経由せずに子孫コンポーネントでデータを受け取ることができて便利な反面、規模が大きく複雑なプロジェクトになるとどこで提供されたコンテキストなのか分かりづらくなり可読性が悪くなってしまう場合もあるので気を付けましょう。
:::

### `use_reducer`

最後に紹介するのは `use_reducer` です。

`use_reducer` は `use_state` と似ていて、コンポーネントの状態を管理するための hook です。
`use_state` とは違い、 `use_reducer` では任意の値ではなく `Reducible` トレイトを実装したストラクトを作成して渡す必要があります。
つまり、 `use_reducer` では状態を更新するための処理をストラクトが持つことになるという違いもあります。

```rust: use_reducer
use std::rc::Rc;

use yew::prelude::*;

enum ReducerAction<T> {
    Increment,
    Add(T),
}

#[derive(Default)]
struct ReducerState {
    count: i128,
}

impl Reducible for ReducerState {
    type Action = ReducerAction<i128>;

    fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
        let next_count = match action {
            ReducerAction::Increment => self.count.saturating_add(1),
            ReducerAction::Add(int) => self.count.saturating_add(int),
        };

        Self { count: next_count }.into()
    }
}

#[function_component]
fn App() -> Html {
    let counter = use_reducer(ReducerState::default);

    let increment = {
        let counter = counter.clone();
        move |_| counter.dispatch(ReducerAction::Increment)
    };
    let add_three = {
        let counter = counter.clone();
        move |_| counter.dispatch(ReducerAction::Add(3))
    };

    html! {
        <div>
            <button onclick={ increment }>{"+1"}</button>
            <button onclick={ add_three }>{"+3"}</button>
            <p>{ counter.count }</p>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

`use_state` では任意の値を `set` することで状態を変更していましたが、 `use_reducer` では `dispatch` に `Action` を渡すことで状態を変更します。
`Action` によって更新の処理を切り替えることで、 `use_reducer` では限られたインターフェースを提供しながら複雑な状態の更新を行うことができます。
`Action` の列挙型が値を持つように実装すれば、その値を使って `dispatch` を実行することもできます。

***

例をたくさん考えたので長くなってしまいましたね。
他にもいくつかの hooks が用意されています。詳しくはドキュメントを確認してみてください！

https://docs.rs/yew/latest/yew/functional/index.html#functions


***

関数コンポーネントに `use_state` 、 `use_effect` ... 気付いた方も多いかと思いますが、 Yew は [React](https://react.dev/) にとてもよく似ています。 React での開発経験がある方は Yew での開発も行いやすいでしょう！

しかし、 現状では Yew で開発を行うにはいくつか問題もあります。

- React の機能を使ったライブラリは代替できない
Javascript における多くのライブラリは Rust のクレートでまかなえるでしょう。 しかし、 React の機能を必要とするライブラリを使いたくなったときは Yew を使って自分で実装を再現するか、誰かが実装して公開されるのを待つしかありません。
- ESLint のように `html!` マクロ内をフォーマットする方法がない
`html!` マクロ内には Rust と HTML 文法が混在した記述になりますが、このマクロ内のコードをフォーマットする方法は今のところありません。
- 正式なリリースバージョンではないので破壊的な変更が行われる可能性がある
このアーティクル執筆時点では Yew の最新バージョンは `0.21.0` です。 例えば `use_effect_with` はバージョン `0.20.0` では `use_effect_with_deps` でしたし、 hook の引数の順番もいくつか変更されました。 バージョンアップの度に API へ破壊的な変更を行われる可能性があるため、現状の Yew を利用してプロジェクトを進めるとマイグレーションにかかるコストが増大してしまう危険性があります。

このようにいくつか懸念もありますが、 Yew は公式ドキュメントが充実しており、マイグレーションガイドも公開されています。
正式に `1.0.0` がリリースされれば Yew を利用した拡張コンポーネントクレートの開発とメンテナンスも安定していくことでしょう。
将来に期待できますね！

***

不定期の更新になりますが、何か簡単な機能を持った（だけど便利で誰かの役に立つ）アプリケーションを開発するところまで連載したいと思っています。
アイディア募集中です！よろしくお願いいたします！
ありがとうございました！

***

この文章の一部は Chat-GPT 3.5 によって和文校正されています。

## お知らせ

DONUTSでは新卒中途問わず積極的に採用活動を行っています。
詳細はこちらをご確認ください。

https://www.donuts.ne.jp/recruit/

ジョブカン事業部のエンジニア募集はこちら。

https://recruit.jobcan.jp/donuts/show/B001/1396297