stylistを使ってYewでスタイルを書く

## この記事の概要

[先日初めてYewを触ってみた](https://qiita.com/xrxoxcxox/items/5ea71b3c613b8b076bd8)のですが、[CSS関連はまだスタンダードが決まっていない](https://github.com/yewstack/yew/discussions/2003)ようでした。
せっかくコンポーネントベースでWebアプリケーションが作れるので、スタイルも上手くコンポーネント内に閉じておきたいです。

ひとまず、現状では唯一のスタイリングソリューション[^solutions]であるstylistを使ってスタイルを書く方法を記事にしてみました。

[^solutions]: いくつかの候補がありますが、stylist以外はあくまでコンポーネントライブラリです。https://yew.rs/docs/more/css

https://github.com/futursolo/stylist-rs

## 前提

Yewのプロジェクトがセットアップ済みだとします。

また、いくつか記法がある中で`styled_component`記法は説明しません。
何故なら筆者があまり好まないからです。[^styled-component]

[^styled-component]: とは言え、Reactで有名なstyled-componentsと違い、タグとスタイルが結びつきはしないのでそこまで否定的でもありません。

## 導入

`Cargo.toml`に`stylist`を追加します。
2022年12月19日現在、stylistの最新バージョンは`0.11.0`です（ちなみにYewは`0.20.0`）。
`features = ["yew_integration"]`も必要なのでお忘れなく。

```diff_toml:Cargo.toml
  [dependencies]
+ stylist = { version = "0.11.0", features = ["yew_integration"] }
  yew = { version = "0.20.0", features = ["csr"] }
```

## グローバルなスタイル

現状はこのようなコードだとします。
ここにスタイルを追加していきます。

```rust:src/main.rs
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let counter = use_state(|| 0);
    let increment = {
        // ボタン押下時の処理
    };
    let decrement = {
        // ボタン押下時の処理
    };

    html! {
        <>
            <div>
                <div>
                    <h1>{ "Counter with Yew" }</h1>
                    <span>{ *counter }</span>
                    <div>
                        <button onclick={decrement}>{ "-1" }</button>
                        <button onclick={increment}>{ "+1" }</button>
                    </div>
                </div>
            </div>
        </>
    }
}

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

まず、`stylist::yew::Global;`を追加します。

```diff_rust:src/main.rs
+ use stylist::yew::Global;
  use yew::prelude::*;
```

グローバルに適用したいスタイルを定義します。
`box-sizing`とか`font-family`とか、そういうものをイメージしてください。

examplesではRaw stringの形式（`r#"string"#`）で書かれていましたが、軽く試した限りではLiteralsなら何でも大丈夫なようです。

```diff_rust:src/main.rs
  #[function_component]
  fn App() -> Html {
      // 中略
+     let global_style = css!(r#"
+         * {
+             // スタイル色々
+         }
+ 
+         html {
+             // スタイル色々
+         }
+         
+         body {
+             // スタイル色々
+         }
+     "#);
```

その後は`html!`の中に`Global`コンポーネントを挿入すればページ全体にスタイルが適用されます。

```diff_rust:src/main.rs
      html! {
          <>
+             <Global css={global_style} />
              <div>
                  <div>
                      <h1>{ "Counter with Yew" }</h1>
                      <span>{ *counter }</span>
                      <div>
                          <button onclick={decrement}>{ "-1" }</button>
                          <button onclick={increment}>{ "+1" }</button>
                      </div>
                  </div>
              </div>
          </>
    }
```

## スコープの区切られたスタイル

再び、モジュールを追加します。

```diff_rust:src/main.rs
+ use stylist::{css, style};
  use stylist::yew::Global;
  use yew::prelude::*;
```

定義したい分だけスタイルを追加します。

```diff_rust:src/main.rs
  #[function_component]
  fn App() -> Html {
      // 中略
      let global_style = css!(r#"
          // 中略
      "#);
+     let container_style = style!(r#"
+         // スタイル色々
+     "#).expect("Failed to mount style");
+     let panel_style = style!(r#"
+         // スタイル色々
+     "#).expect("Failed to mount style");
+     let title_style = style!(r#"
+         // スタイル色々
+     "#).expect("Failed to mount style");
+     // 以下同様に、必要なだけスタイルを追加する
```

マークアップに反映させます。

```diff_rust:src/main.rs
      html! {
          <>
              <Global css={global_style} />
-             <div>
-                 <div>
-                     <h1>{ "Counter with Yew" }</h1>
-                     <span>{ *counter }</span>
-                     <div>
+             <div class={container_style}>
+                 <div class={panel_style}>
+                     <h1 class={title_style}>{ "Counter with Yew" }</h1>
+                     <span class={counter_style}>{ *counter }</span>
+                     <div class={button_area_style}>
                          <button onclick={decrement}>{ "-1" }</button>
                          <button onclick={increment}>{ "+1" }</button>
                      </div>
                  </div>
              </div>
          </>
    }
```

すると、このようにハッシュ値付きのスタイルとなって生成されます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/b66d26cc-e2d5-74dd-4ec2-dcd6ef320fdf.png)

## マルチクラス

よくある使用法だと思うので、マルチクラスについても少し触れておきます。
スタイルを定義する際は変わりません。

```diff_rust:src/main.rs
+     let button_style = style!(r#"
+         // 基本となるボタンのスタイル
+     "#).expect("Failed to mount style");
+ 
+     let button_increment_style = style!(r#"
+         background-color: #55c500; // incrementだけで使うスタイル
+     "#).expect("Failed to mount style");
+ 
+     let button_decrement_style = style!(r#"
+         background-color: #eee; // decrementだけで使うスタイル
+     "#).expect("Failed to mount style");
```

マークアップに追加する際は少し書式が変わります。

```diff_rust:src/main.rs
      html! {
          <>
              <Global css={global_style} />
              <div class={container_style}>
                  <div class={panel_style}>
                      <h1 class={title_style}>{ "Counter with Yew" }</h1>
                      <span class={counter_style}>{ *counter }</span>
                      <div class={button_area_style}>
-                         <button onclick={decrement}>{ "-1" }</button>
-                         <button onclick={increment}>{ "+1" }</button>
+                         <button onclick={decrement} class={classes!(button_style.clone(), button_decrement_style)}>{ "-1" }</button>
+                         <button onclick={increment} class={classes!(button_style.clone(), button_increment_style)}>{ "+1" }</button>
                      </div>
                  </div>
              </div>
          </>
      }
```

1. `classes!`で囲う必要がある
    - シングルクラスのときは`class={class_name}`で大丈夫
    - マルチクラスの場合は`class={class_one, class_two}`ではNG
        - `class={classes!(class_one, class_two)}`が正しい
        - シングルクラスの際に`class={classes!(class_name)}`でも問題ないので、後から書き換えるのが面倒なら常に`classes!`をつけておくのもアリ？
1. 同じスタイルの場合は`.clone()`が必要
    - これは少し自信がない（ワークアラウンドかもしれない）
    - stylistのドキュメントの中には「こうすると良い」の記載がなかったものの、純粋に筆者がRustを分かって無さすぎるだけな可能性がある
        - 分かる方がいたらぜひコメントで教えてください！

## 最後に

日本語だろうが英語だろうが紹介されている記事が全然見当たらず、公式ドキュメントをずっと見ていました。
どのオプションをいつ使うと最適なのかがまだあまり分かっていないので、この記事は速攻でdeprecatedになる可能性があります。

余談ですが、公式ドキュメントの冒頭に以下の記載があります。

> Add the following to your Cargo.toml:
> 
> ```
> stylist = "0.10"
> ```

しかし最新が0.11.0で、しかもBreaking Changesが入っていてドキュメント通りに書いても動きませんでした。
調べても全然出てこない中、packagesの`Cargo.toml`を見たら`version = "0.11.0"`なのを発見。
バージョンを上げたら問題なく動いたのですが、こういうときの勘の悪さ（？）を痛感しました。

---

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

https://twitter.com/xrxoxcxox

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

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