😸

Dioxus (Rustでfrontend) 入門4

2022/10/23に公開

インタラクティビティの追加

DioxusコンポーネントはPropsを引数にとり、Elementを返す関数です。 とPropsの説明で記載しましたが、
それだとほぼ静的なHTMLコンテンツしか作成することができません。

外部APIからデータを取得したり、 何かしらの数値をカウントアップしたり、外部APIに対してデータを送信したりするためには副作用のある処理をどこかに書かなければなりません。

それを行うために、Event・ステート・タスクの概念を用いてDioxusアプリケーションにインタラクティブ性をもたせる方法について記載します。

イベントハンドラ

ユーザが入力したり、ボタンをクリックしたときに実行するアクションを定義します。

fn app(cx: Scope) -> Element {
    cx.render(rsx!{
        button {
            onclick: move |evt| println!("I've been clicked!"),
            "click me!"
        }
    })
}

Dioxusのデフォルトで定義されている、コンポーネントごとに利用可能なイベントは決まっています。

利用可能なイベントは 下記のコードで定義されていますので、一度ご確認ください。

https://github.com/DioxusLabs/dioxus/blob/b32fd2d2cddce8a1fe79fe83c7575d0699665d34/packages/html/src/events.rs

HookとInternal State

例えば Accordion (今回の例はPattenFly4のAccordionをdioxusで実装した場合)

use dioxus::prelude::*;

#[allow(non_snake_case)]
#[inline_props]
pub fn PfAccordion<'a>(cx: Scope<'a>, children: Element<'a>, title: &'a str, is_open: Option<bool>) -> Element {
    let default_open_flag = !is_open.unwrap_or(false);
    let is_close_accordion = use_state(&cx,|| default_open_flag);
    cx.render(rsx! {
        div {class: "pf-c-accordion",
        h3 {
            button { class: "pf-c-accordion__toggle pf-m-expanded", r#type: "button", aria_expanded: "false",
            onclick: move |_| { is_close_accordion.set(!is_close_accordion.get()); },

                span { class: "pf-c-accordion__toggle-text", "{title}" },
                span { class: "pf-c-accordion__toggle-icon",
                    i { class: "fas fa-angle-right", aria_hidden: "false" }
                }
            }
        }
        div { class: "pf-c-accordion__expanded-content pf-m-expanded",
            div { class: "pf-c-accordion__expanded-content-body", hidden:"{is_close_accordion}" , children,  }
        }
    }
    })
}

このようなコードになりますが、アコーディオンを実現するためには開いている状態と閉じている状態をトグルとして保つ必要があります。
このように状態を持つ必要があるときに利用するのが、ローカルステートです。

   let is_close_accordion = use_state(&cx,|| false);

ではこれを use_stateを使わずに

    let mut is_close_accordion = false;

とした場合にどうなるか見てみましょう。

use dioxus::prelude::*;

#[allow(non_snake_case)]
#[inline_props]
pub fn PfAccordion<'a>(cx: Scope<'a>, children: Element<'a>, title: &'a str, is_open: Option<bool>) -> Element {
    let default_open_flag = !is_open.unwrap_or(false);
    let mut is_close_accordion = !default_open_flag;
    cx.render(rsx! {
        div {class: "pf-c-accordion",
        h3 {
            button { class: "pf-c-accordion__toggle pf-m-expanded", r#type: "button", aria_expanded: "false",
            onclick: move |_| { is_close_accordion = !is_close_accordion; },

                span { class: "pf-c-accordion__toggle-text", "{title}" },
                span { class: "pf-c-accordion__toggle-icon",
                    i { class: "fas fa-angle-right", aria_hidden: "false" }
                }
            }
        }
        div { class: "pf-c-accordion__expanded-content pf-m-expanded",
            div { class: "pf-c-accordion__expanded-content-body", hidden:"{is_close_accordion}" , children,  }
        }
    }
    })
}

このようなコードを書いたとします。
コンパイルは通りますが、 アコーディオンのタイトル部分をクリックしても表示が変わりません。
これはDioxusが再レンダリングが必要であることを判断するための要素がないからです。

では 再レンダリングさせるために state counterを用意し、それをincrementしてみましょう。
これでも再レンダリングされるのは カウンターを表示している部分だけですね。この場合は再レンダリングされるたびに値が初期化されているためですが、レンダリングに影響する状態を管理するためには use_stateを利用しなければならないことは覚えておきましょう。

use dioxus::prelude::*;

#[allow(non_snake_case)]
#[inline_props]
pub fn PfAccordion<'a>(cx: Scope<'a>, children: Element<'a>, title: &'a str, is_open: Option<bool>) -> Element {
    let default_open_flag = !is_open.unwrap_or(false);
    let mut is_close_accordion = !default_open_flag;
    let  state = use_state(&cx,|| 1);
    cx.render(rsx! {
        div {class: "pf-c-accordion",
        h3 {
            button { class: "pf-c-accordion__toggle pf-m-expanded", r#type: "button", aria_expanded: "false",
            onclick: move |_| { is_close_accordion = !is_close_accordion; state.set(state.get() + 1);  },

                span { class: "pf-c-accordion__toggle-text", "{title}" },
                span { class: "pf-c-accordion__toggle-icon",
                    i { class: "fas fa-angle-right", aria_hidden: "false" }
                }
            }
        }
        div { class: "pf-c-accordion__expanded-content pf-m-expanded",
            div { class: "pf-c-accordion__expanded-content-body", hidden:"{is_close_accordion}" , children,  }
        }
        div { "{state}"}
    }
    })
}

Discussion