デザイナーだけどRustを使ってみたいからYewに入門してみた

## この記事の概要

Rust、流行ってますよね。
自分は「良い感じらしい」以上の情報を持っていなかったのですが、手を出してみたくなりました。

しかし、体系的に覚えようと思ってチュートリアルを眺めてもかなり難しく見えます。
というわけで「WebアプリケーションをRustで作れる」というYewで、まずは雰囲気を知ろうと思い挑戦しました。

## Yewへのリンクなど

Yewの公式ドキュメントはこちらです。

https://yew.rs/

基本的には[チュートリアル](https://yew.rs/docs/tutorial)に倣って進めますが、気になった箇所を多少調べながら記事を書きました

ヘッダーに言語変更のUIがあり`日本語`も選べますが、ほとんど翻訳されていません。
素直に英語で進めた方が良い気がします。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/d02afa71-9e2a-229b-2569-f661ca6bdfa5.png)

## 環境構築

`Getting Started`ではRustのインストール方法までは記載されていないため、自分でセットアップしましょう。
最近弊社の同僚が環境準備のための記事を書いていたので紹介します。

https://qiita.com/mziyut/items/3127eb3d975346b1d795

アプリケーションの構築やバンドル、Webへの配信のためにTrunkを追加します。

https://trunkrs.dev/

```shell
cargo install --locked trunk
```

WebAssemblyのブラウザ用コンパイルターゲットとして`wasm32-unknown-unknown`を追加します。

```shell
rustup target add wasm32-unknown-unknown
```

プロジェクトのセットアップをします。
`cargo new`で作成するか、ディレクトリを作った後に`cargo init`します。

```shell:パターン1
cargo new yew-app
cd yew-app
```

```shell:パターン2
mkdir yew-app
cd yew-app
cargo init
```

パターン1の方が少ないコマンドで済みますが、自分はうっかり先にディレクトリを切っていたのでパターン2で作りました。
どちらも中身は変わりません。

```shell
.
├── .gitignore
├── Cargo.toml
└── src
    └── main.rs
```

## コンソールへのHello World

ここで一度`Cargo run`してみます。

```shell
cargo run
```

するとコンソールに`Hello, world!`と表示され、`target`ディレクトリと`Cargo.lock`が生成されました。

```shell
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
    ├── 以下略
```

## ブラウザへのHello World

ここまでは単にCargoのコマンドで、ようやくYewを使っていきます。
まずは`Cargo.toml`を編集します。

```diff_toml:Cargo.toml
  [package]
  name = "my-first-yew"
  version = "0.1.0"
  edition = "2021"

  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

  [dependencies]
+ yew = { version = "0.20", features = ["csr"] }
```

次に、`src/main.rc`を触ってみます。

```diff_rust:src/main.rs
+ use yew::prelude::*;
+ 
+ #[function_component(App)]
+ fn app() -> Html {
+     html! {
+         <h1>{ "Hello World" }</h1>
+     }
+ }
+ 
  fn main() {
-     println!("Hello, world!");
+     yew::Renderer::<App>::new().render();
  }
```

新規に`index.html`を作り、コードを書きます。

```html:index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My first Yew</title>
  </head>
  <body></body>
</html>
```

ローカルサーバーを立ち上げます。

```shell
trunk serve --open
```

非常にシンプルなページが開きました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/6d32262b-f7e2-c207-5601-09db9c9b44d0.png)

## 興味本位の実験

「単なる文字列しか渡していないし、`{}`無しでもいけるんじゃないの？」と思って外してみました。

```diff_rust:src/main.rs
  use yew::prelude::*;
  
  #[function_component(App)]
  fn app() -> Html {
      html! {
-         <h1>{ "Hello World" }</h1>
+         <h1>"Hello World"</h1>
      }
  }
  
  fn main() {
      yew::Renderer::<App>::new().render();
  }
```

ダメみたいですね。

```shell
error: expected a valid html element
 --> src/main.rs:7:17
  |
7 |             <h1>"Hello World"</h1>
  |                 ^^^^^^^^^^^^^

error: could not compile `my-first-yew` due to previous error
2022-12-18T13:20:59.548671Z ERROR ❌ error
error from HTML pipeline

Caused by:
    0: error from asset pipeline
    1: error during cargo build execution
    2: cargo call returned a bad status
```

## ちょっとしたロジックの追加

今は静的なテキストだけなので、簡単なロジックを追加します。

```diff_rust:src/main.rs
  use yew::prelude::*;
  
  #[function_component]
  fn App() -> Html {
+     let counter = use_state(|| 0);
+     let onclick = {
+         let counter = counter.clone();
+         move |_| {
+             let value = *counter + 1;
+             counter.set(value);
+         }
+     };
+ 
      html! {
-         <h1>{ "Hello World" }</h1>
+         <div>
+             <button {onclick}>{ "+1" }</button>
+             <p>{ *counter }</p>
+         </div>
      }
  }
  
  fn main() {
      yew::Renderer::<App>::new().render();
  }
```

このようになりました。
ボタンをクリックすると数字が1ずつ増えていきます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/bd1dc65f-c1ea-c4c6-af2f-b2003e4de9e3.gif)

これで終わりなのもねえ……と思い、もう少しだけいじってみます。

地味な変更として、関数名が`onclick`のときは`<button {onclick}>`で済みましたが、名前を変えたので`<button onclick={increment}>`にしないといけません。

```diff_rust
  use yew::prelude::*;
  
  #[function_component]
  fn App() -> Html {
      let counter = use_state(|| 0);
-     let onclick = {
+     let increment = {
          let counter = counter.clone();
          move |_| {
              let value = *counter + 1;
              counter.set(value);
          }
      };
+     let decrement = {
+         let counter = counter.clone();
+         move |_| {
+             let value = *counter - 1;
+             counter.set(value);
+         }
+     };
  
      html! {
          <div>
-             <button {onclick}>{ "+1" }</button>
+             <button onclick={increment}>{ "+1" }</button>
+             <button onclick={decrement}>{ "-1" }</button>
              <p>{ *counter }</p>
          </div>
      }
  }
  
  fn main() {
      yew::Renderer::<App>::new().render();
  }
```

ちゃんと動いています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/e12541e5-9ad7-3210-1b56-cd45f478fe00.gif)

まだ若干物足りないので、スタイルもつけておきましょう。
とは言ってもYewではなくTrunkの範囲です。

まずはCSSファイルを作成します。

```shell
touch style.css
```

`index.html`にCSSへのlinkを追加します。
通常は`rel="stylesheet"`なのが`rel="css"`なのに注意です。
また、`data-trunk`属性が必要です。

```diff_html:index.html
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+     <link data-trunk rel="css" href="style.css" />
      <title>My first Yew</title>
    </head>
    <body></body>
  </html>
```

あとは普通にスタイルを書けばOKです。
ただし、クラス名にハッシュがつくとかそういうのは無いので、それなりにちゃんとしたCSS設計が必要そうです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/7a920559-9266-f772-014e-4e53d4373557.gif)

TrunkにおけるCSSやSassファイルの追加の方法は以下のページに記載されています。

https://trunkrs.dev/assets/

## 最後に

現段階ではRustらしいことは何もしていませんが、それでも途中やたらエラーが出ていました。
例えば`decrement`の関数を作成したけど、まだ`button`に適用していない段階では以下のエラーが表示されました。

```
   |
17 |         move |_| {
   |               ^
   |
help: consider giving this closure parameter an explicit type
   |
17 |         move |_: _| {
   |                +++
```

今まで自分が書いてきたコードでは「定義してるのに未使用です」程度のエラーしか出なかったので驚いています。
体系的に理解するとまでいくと難しそうですが、RustでWebアプリケーションを作るのが当たり前になる日も来るかもしれませんから、素振りと思ってもう少し練習してみます。

---

最後まで読んでくださってありがとうございます！
Twitterでも情報を発信しているので、良かったらフォローお願いします！

https://twitter.com/xrxoxcxox

Devトークでのお話してくださる方も募集中です！

https://jobs.qiita.com/employers/qiita-inc/dev_talks/489