Callback

要素がクリックした時の走らせる処理の実装方法を見ていきます。

以下は最小のコンポーネントの書き方です。

use yew::prelude::*;

struct MyComponent {}

impl Component for MyComponent {
  type Message = ();
  type Properties = ();
  fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
    Self {}
  }

  fn update(&mut self, _message: Self::Message) -> ShouldRender {
    true
  }

  fn view(&self) -> Html {
    html! {
      <div>{"Hello Yew"}</div>
    }
  }
}

修正が必要なのは以下の 3 点です。

  1. 関連型Messageをちゃんと埋める
  2. onclickハンドラを設定する
  3. updateメソッドを発火する為のComponentLinkを扱えるようにする

今回はボタンをクリックする度onoffテキストが切り替わる感じのものを作ってみます。

関連型 Message をちゃんと埋める

以下のようなenum型で定義しておきます。

enum Message {
  ButtonClick
}

また、関連型の方も更新しておきます。

type Message = Message;

onclick ハンドラを設定する

以下のことをする為にviewメソッドを更新します。

  1. self.activeによるテキストの切り替え
  2. self.linkによるonclickハンドラの設定

と、その前に両方ともself経由なのでcreateとこのコンポーネント構造体の修正が必要です。

struct MyComponent {
  link: ComponentLink<Self>,
  active: bool,
}

linkcreateの第 2 引数として渡されイベントハンドラの設定に必要なものなので、もしイベントハンドラを使うのであれば構造体に持たせなければなりません。
activeで適当にデフォルト値を設定します。

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
  Self {
    link,
    active: false,
  }
}

viewに戻ります。textifで適当に切り替えています。
そしてイベントハンドラの方ですがlinkcallbackメソッドにクロージャーを渡しその中でMessageenumのどれかを返すようにします。

fn view(&self) -> Html {
  let text = if self.active {
    "on -> off"
  } else {
    "off -> on"
  };

  html! {
    <button onclick=self.link.callback(|_| Message::ButtonClick)>{text}</button>
  }
}

このクロージャーが返した値は次にupdateメソッドの第 1 引数に渡されます。


fn update(&mut self, message: Self::Message) -> ShouldRender {
  match message {
    Message::ButtonClick => self.active = !self.active,
  };

  true
}

Message::ButtonClickな時はactiveboolを反対にしています。

updateメソッドでtrueを返すとコンポーネントの再レンダリングが走るので、それによってボタンの中のテキストの切り替えがされるハズです。

サンプル

nju33-com/get-started-yew-callback から動作確認が行えます。

親コンポーネントのコールバックを呼ぶ

onclickのようなプロパティを自作コンポーネントに渡すと、子から親を更新することができます。

これは例えば以下のような感じの構造になります。

struct Child {
  onclick: Callback<ParentMessage>
}

// <Child onclick=self.link.callback(|_| ParentMessage::Foo) />

但しそのまま子要素の例えば<button />onclickに渡せるのではなく、一旦子コンポーネントでupdateメソッドを呼び、その中でコールバックのemitメソッドを呼び出す必要があります。

// <button onclick=self.link.callback(|_| ChildMessage::Foo)>

fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
  Self { props, link }
}

// ...

fn update(&mut self, message: Self::Message) -> ShouldRender {
  match message {
    Message::Foo => self.props.onclick.emit()
  };
  
  true
}

こうすることで子コンポーネントのボタンがクリックされた時に、プロパティとして渡されたself.link.callback(closure)closureが実行されます。それにより親のupdateメソッドが呼ばれ、親コンポーネントの更新ができます。

ちなみにemitする時は 1 つ値を渡すことができます。そしてそれはclosureへ渡されるので、その値を見て返す値を変えたり、タプル構造体をupdateメソッドに渡したりといった事もできます。

<Child onclick=self.link.callback(|value| ParentMessage::Foo(value)) />