20
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

完走賞を目指す @xrxoxcxoxAdvent Calendar 2022

Day 20

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

Last updated at Posted at 2022-12-19

この記事の概要

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

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

Yewへのリンクなど

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

基本的にはチュートリアルに倣って進めますが、気になった箇所を多少調べながら記事を書きました

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

環境構築

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

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

cargo install --locked trunk

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

rustup target add wasm32-unknown-unknown

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

パターン1
cargo new yew-app
cd yew-app
パターン2
mkdir yew-app
cd yew-app
cargo init

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

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

コンソールへのHello World

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

cargo run

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

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

ブラウザへのHello World

ここまでは単にCargoのコマンドで、ようやくYewを使っていきます。
まずはCargo.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を触ってみます。

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を作り、コードを書きます。

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>

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

trunk serve --open

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

興味本位の実験

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

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();
  }

ダメみたいですね。

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

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

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

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ずつ増えていきます。

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

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

  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();
  }

ちゃんと動いています。

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

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

touch style.css

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

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設計が必要そうです。

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

最後に

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

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

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


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

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

20
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?