티스토리 뷰

Rust 코드로 작성하는 WASM (with react)

EventListener

이번 포스트는 EventListener를 이용하여 마우스 이벤트 또는 키보드 이벤트를 배워보도록 하겠습니다.

rust에서는 javascript의 EventListener를 지원하기위해 gloo라는 라이브러리가 있습니다.

먼저 gloo 라이브러리를 cargo의 dependencies에 추가해줍니다.

// Cargo.toml
...
[dependencies]
...
gloo="0.3.0"
...

click event

그런다음 wasm 프로젝트의 lib.rs에 rust 함수를 작성해보겠습니다.

Rust 코드

pub mod wasm_event;
use wasm_event::*;

#[wasm_bindgen]
pub fn on_click(id: String, btn_val: String) {
  click_event(id, btn_val);
}

wasm_event라는 module을 만들고 해당 module을 전체가져오기하는 코드이며, 해당 module내에는 click_event함수가 존재합니다.

작성된 click_event함수를 살펴봅시다.

use gloo::{events::EventListener};

pub fn click_event(id: String, btn_val: String) {
  use super::wasm_document;
  let button = wasm_document::document_get_element_by_id(id);
  button.set_text_content(Some(&btn_val));
  let on_click = EventListener::new(&button, "click", move |_event| {
    web_sys::console::log_1(&String::from("button click!!").into());
  });

  on_click.forget();
}

이전에 post한 document편에서 만들어둔 id로 html 요소를 가져오는 함수를 이용하여 button element를 지정했습니다.

(document편 참고)

https://kmj24.tistory.com/189

 

Rust 코드로 작성하는 WASM(feat. react) (2) document

Rust 코드로 작성하는 WASM (with react) Document getElementById method javascript의 Dom method 중 getElementById를 wasm으로 구현할 수 있습니다. web_sys::Document의 get_element_by_id(id) 메서드를 이용..

kmj24.tistory.com

 

이제 button변수에는 button이 담겨져 있고 button에 대한 메서드를 사용할 수 있습니다.

먼저 button의 내용을 설정하는 set_text_content메서드를 호출하여 parameter로 받아온 문자열을 넣어주었습니다.

해당 method의 인자값으로는 흐름제어문법(Some())으로 넣어줍니다.

그런 다음 onClick이벤트를 만들어줍니다.

위에서 종속성으로 추가한 gloo의 EventListener를 가져옵니다.

let on_click = EventListener::new(&button, "click", move |_event| {
  web_sys::console::log_1(&String::from("button click!!").into());
});

on_click.forget();

위 코드는 새로운 button에 EventListener를 등록하며, 버튼을 클릭할때마다 console창에 "button click!!"이 뜨게 됩니다.

on_click.forget();을 작성한 이유는 EventListener를 지속시키기 위함입니다.

rust언어의 특성상 scope 밖으로 벗어나면 memory는 drop이 되어버립니다.

따라서 등록된 on_click EventListener는 없던것이 되어버리는거죠.

그것을 방지하는 코드입니다.

rust gloo document에서는 이렇게 안내합니다.

This should only be used when you want the EventListener to last forever, otherwise it will leak memory!

이 기능은 EventListener가 영원히 지속되기를 원할 때만 사용해야 합니다. 그렇지 않으면 메모리가 누출됩니다!

rust코드 작성이 완성되었다면 wasm-package를 build합니다.

그리고 react로 돌아와서 wasm을 호출하면 됩니다.

첫번째 argument는 button의 id를 넘겨주었고, 두번째 argument는 button의 text를 지정합니다.

react 코드

import React, { useEffect } from 'react'
import EventComponent from '../../component/event/EventComponent';

function EventContainer() {
  import('./../../../wasm-rust/pkg').then(module => {
    module.on_click("wasm-onclick", "button");
  })

  return (
    <button id='wasm-onclick' />
  );
}

export default EventContainer;

그리고 button을 클릭했을때 "button click!!" 문자열이 console창에 찍히면 성공입니다.

button내 text를 입력하지 않았지만 "button"문자열이 설정되어있고, 해당 버튼을 click할때마다 console창에 문자열이 출력됩니다.

 

버튼을 클릭하면 화면에 문자열을 입력하도록 하는 코드를  추가해봅시다.

Rust 코드

pub fn click_event(id: String, btn_val: String) {
  use super::wasm_document;
  let message_tag = wasm_document::document_get_element_by_id(String::from("message"));

  let button = wasm_document::document_get_element_by_id(id);
  button.set_text_content(Some(&btn_val));
  
  let on_click = EventListener::new(&button, "click", move |_event| {
    web_sys::console::log_1(&String::from("button click!!").into());
    message_tag.set_text_content(Some("일반 버튼 클릭 이벤트"));
  });

  on_click.forget();
}

버튼을 누르면 id가 'message'인 태그에 text가 설정됩니다.

같은 동작을 하는 버튼을 하나 더 추가해보겠습니다.

이번에는 Rust의 Closure기능을 사용한 버튼입니다.

Rust 코드

pub fn closure_button (id: String, btn_val: String) {
  use super::wasm_document;
  let window = wasm_document::get_window();
  let document = wasm_document::get_document(window);

  let closure_document = document.clone();
  let closure_button_document = document.clone();

  let mut warn_string = String::from("#");
  warn_string.push_str(&id);
  warn_string.push_str("be an 'HtmlElement'");

  let closure_button = Closure::wrap(Box::new(move || {
    web_sys::console::log_1(&"closure button".into());
    closure_document
      .get_element_by_id(&"message")
      .expect("should have a button on the page")
      .dyn_ref::<web_sys::HtmlElement>()
      .expect(&warn_string)
      .set_text_content(Some("클로저 버튼 클릭 이벤트"));
  }) as Box<dyn FnMut()>);

  closure_button_document
    .get_element_by_id(&id)
    .unwrap()
    .set_text_content(Some(&btn_val));

  document
    .get_element_by_id(&id)
    .expect("closure-button")
    .dyn_ref::<web_sys::HtmlElement>()
    .expect("#closure-button is not `Html Element`")
    .set_onclick(Some(closure_button.as_ref().unchecked_ref()));

  closure_button.forget();
}

 

react에서 위의 함수들을 호출하여 보겠습니다.

react 코드

function EventContainer() {
  useEffect(() => {
    import('./../../../wasm-rust/pkg').then(module => {
      module.on_click('wasm-onclick', 'button');
    });
    import('../../../wasm-rust/pkg').then(module => {
      module.closurebutton('closure-button');
    })
  }, []);

  return (
  	<>
      <button id='wasm-onclick' />
      <div id='message'></div>
      <button id='closure-button' />
    </>
  );
}

 

mouse move event

이번에는 마우스 이동 이벤트입니다.

p태그를 하나 만들어 p태그영역에서 마우스이동이 감지되면 console창에 출력되도록 해보겠습니다.

Rust 코드

pub fn mouse_move(id: String) {
  let window = wasm_document::get_window();
  let document = wasm_document::get_document(window);
  let division = wasm_document::document_get_element_by_id(id);

  let paragraph = document
                    .create_element("p")
                    .unwrap()
                    .dyn_into::<web_sys::HtmlParagraphElement>()
                    .map_err(|_| ())
                    .unwrap();
  paragraph.set_align("center");
  paragraph.set_inner_html("<hr /> mouse move event <hr />");
  
  let on_move = EventListener::new(&paragraph, "mousemove", move |_event| {
    web_sys::console::log_1(&"mouse moving".into());
  });

  on_move.forget();
  division.append_child(&paragraph).unwrap(); 
}

위에서 학습한 내용과 비슷하게 작성합니다.

EventListener는 "mousemove" 입니다.

parameter로 받아온 id에 대한 태그에 mousemove 이벤트가 추가된 p태그를 삽입합니다.

p태그 아래위로 라인을 그리고 "mouse move event" 문자열을 삽입했습니다.

p태그 영역위에서 마우스가 이동하면 console창에 출력

 

key down event

이번에는 키보드의 key down 이벤트입니다.

EventListener를 추가하는 방식은 위에서 학습한 방식과 같습니다.

하지만 Cargo.toml에 web_sys의 features에 'KeyboardEvent'를 추가합니다.

Cargo.toml

Rust 코드

pub fn keydown_event(input_id: String, output_id: String) {
  let input_tag = wasm_document::document_get_element_by_id(input_id);
  let output_tag = wasm_document::document_get_element_by_id(output_id);

  let on_keydown = EventListener::new(&input_tag, "keydown", move |_event| {
    let keyboard_event = _event
                          .clone()
                          .dyn_into::<web_sys::KeyboardEvent>()
                          .unwrap();
    let mut event_string = String::from("");
    event_string.push_str(&_event.type_());
    event_string.push_str(&" : ");
    event_string.push_str(&keyboard_event.key());

    output_tag.set_text_content(Some(&event_string));
  });

  on_keydown.forget();
}

이를 react에서 사용할 수 있도록 react 코드를 작성해보겠습니다.

function EventContainer() {
  useEffect(() => {
    import('../../../wasm-rust/pkg').then(module => {
      module.input_string('input', 'output-key-event');
    });
  }, []);

  return (
  	<>
      <input id='input'/>
      <div id='output-key-event' /> 
    </>
  );
}

결과

가장 마지막에 입력된 D가 보이며

A, B, C, D 차례대로 출력이 된것을 확인 할 수 있습니다.

 

문서

https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html

 

gloo_events::EventListener - Rust

#[must_use = "event listener will never be called after being dropped"] pub struct EventListener { /* fields omitted */ } RAII type which is used to manage DOM event listeners. When the EventListener is dropped, it will automatically deregister the event l

docs.rs

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함