본문 바로가기
C.W.K.
Stream
Lesson 04 of 06 · published

이벤트: emit & listen

~13 min · tauri, events, ipc, frontend

Level 0웹 관광객
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"command는 묻고 답하는 거야. 이벤트는 아무도 안 물어봤는데 하는 공지고."

당겨오는 게 아니라 밀어주는 것

가끔 코어한텐 프론트엔드가 물어보지도 않은 소식이 생겨. 다운로드가 끝났다든지, 지켜보던 파일이 바뀌었다든지, 타이머가 한 번 돌았다든지, 트레이 메뉴가 눌렸다든지. 그걸 command로 계속 물어보는 건 낭비야. 대신 코어가 이벤트를 emit하고 프론트엔드가 listen해. app.emit("name", payload)가 모든 웹뷰에 방송하고, 프론트엔드의 listen("name", handler)(@tauri-apps/api/event에 있어)가 payload랑 함께 하나씩 받아. payload는 직렬화되는 값이면 뭐든 되고, command 데이터처럼 serde가 실어 보내.

다 뿌릴까, 하나만 찍을까

emit은 모두한테 보내. 창이 여럿인데 하나만 들어야 하면 emit_to("window-label", "name", payload)로 특정 웹뷰만 찍어. 이벤트는 반대 방향으로도 흘러 — 프론트엔드가 emit하고 Rust가 듣는 것도 돼 — 근데 프론트엔드에서 코어로 뭘 요청할 땐 보통 command를 쓰는 게 나아(반환값이랑 에러 처리를 공짜로 받으니까). 이벤트는 던져놓고 잊는 공지에 남겨둬.

리스너는 반드시 치워

listen은 리스너를 떼는 함수를 돌려줘. 컴포넌트에선 effect에서 등록하고 cleanup에서 그 함수를 불러 — 안 그러면 컴포넌트가 마운트될 때마다 핸들러가 겹겹이 쌓여. 메모리 누수에 같은 이벤트가 두 번 세 번 처리되는 고전 버그가 그거야. 딱 한 번만 처리하고 싶은 이벤트엔 once를 써.

Code

코어에서 emit 하기·rust
use tauri::{AppHandle, Manager, Emitter};
use serde::Serialize;

#[derive(Clone, Serialize)]
struct Progress { done: u32, total: u32 }

fn report(app: &AppHandle) {
    // "progress"를 듣는 모든 웹뷰에 방송.
    let _ = app.emit("progress", Progress { done: 7, total: 10 });
    // 또는 label로 창 하나를 노림:
    let _ = app.emit_to("main", "progress", Progress { done: 7, total: 10 });
}
프론트엔드에서 listen 하기 (치우는 것까지)·tsx
import { listen } from "@tauri-apps/api/event";
import { useEffect, useState } from "react";

function ProgressBar() {
  const [p, setP] = useState({ done: 0, total: 0 });
  useEffect(() => {
    // listen은 unlisten 함수로 resolve돼 — cleanup에서 불러.
    const stop = listen<{ done: number; total: number }>("progress", (e) =>
      setP(e.payload),
    );
    return () => { stop.then((un) => un()); };
  }, []);
  return <progress value={p.done} max={p.total} />;
}

External links

Exercise

AppHandle 레슨에서 띄운 백그라운드 스레드가 매초 1씩 커지는 숫자로 'tick' 이벤트를 emit하게 만들어. 프론트엔드에서 그걸 listen하고 최신 값을 그려 — cleanup까지 제대로 해서. 그다음 일부러 cleanup을 빼고 컴포넌트를 다시 마운트해봐(React devtools를 켜거나 토글로). 두 배가 된 핸들러가 곧 레슨이야.
Hint
Rust 쪽은 루프에서 handle.emit("tick", n)을 부르면 돼. React 쪽은 이 모양이야. const stop = listen('tick', ...); return () => stop.then(u => u()). 누수를 보려면 return cleanup을 주석 처리하고 컴포넌트를 다시 마운트해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.