Open2

dioxus scrap

sora_miisora_mii

プロパティとインラインプロパティ

Dioxusのコンポーネントは Propsを引数にとりElementを返す関数です。

マニュアルは以下にあります。

https://dioxuslabs.com/guide/components/propsmacro.html

#[derive(PartialEq, Props)]
struct VoteButtonProps {
    score: i32
}

fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
    cx.render(rsx!{
        div {
            div { "+" }
            div { "{cx.props.score}"}
            div { "-" }
        }
    })
}

Propsをderiveに設定することで、StructをPropsとして、コンポーネントに渡せるようになります。

マニュアルにも記載がありますが、大きな文字列を持つPropの場合借用として渡す方法が有用です。
その場合には <'a> のアノテーションをつけ、ライフタイムを明示します。

#[derive(Props)]
struct TitleCardProps<'a> {
    title: &'a str,
}

fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
    cx.render(rsx!{
        h1 { "{cx.props.title}" }
    })
}

インラインProps

#[inline_props]
fn TitleCard(cx: Scope, title: String) -> Element {
    cx.render(rsx!{
        h1 { "{title}" }
    })
}

inline_props マクロを用いると上記のような引数でPropsを定義することができます。
ただ、これはマクロで、下記のコードと同等になります。

#[derive(Props, PartialEq)]
struct TitleCardProps {
    title: String,
}

fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
    cx.render(rsx!{
        h1 { "{cx.props.title}" }
    })
}

メモ化

Dioxusではより効率的にレンダリングするために、メモ化(Memoization)を使用します。
これはコンポーネントのPropsに渡された内容が変更されたとき再レンダリングを行うかどうか判断するためのプロセスです。
コンポーネントのプロパティが変更されても出力に影響がない場合、コンポーネントを再レンダリングする必要がない場合に、レンダリングを抑制できるため時間を節約できます。

コンポーネントが再レンダリングを必要とするか、同じままかどうかを判断するために、PartialEqを使用します。このため、deriveにPartialEqを設定する必要があります。

また、この再レンダリングを必要とするかどうかを自作の関数で判定することも可能ですが、手動で判定する場合相当注意深くコーディングを行う必要があります。

sora_miisora_mii

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

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}"}
    }
    })
}