2025. 5. 7. 11:20ㆍTauri/Tutorial
Tauri Text Editor
기능
- 파일 열기: 파일 내용 확인
- 파일 저장
Frontend
전체 코드
src/index.html
<html>
<head>
</head>
<body onload="init()">
<div>
<button onclick="openDialog()">Open File</button>
<button onclick="saveDialog()">Save File</button>
</div>
</body>
<textarea id="contents"></textarea>
<script>
const invoke = window.__TAURI__.core.invoke;
const listen = window.__TAURI__.event.listen;
async function openDialog() {
await invoke("open_file");
}
async function saveDialog() {
let contents = document.querySelector("#contents").value;
await invoke("save_file", { contents : contents});
}
function init() {
listen("contents", data => {
document.querySelector("#contents").value = data.payload;
});
}
</script>
</html>
코드 분석
const invoke = window.__TAURI__.core.invoke;
const listen = window.__TAURI__.event.listen;
core.invoke 함수는 저번 포스팅에서 설명했으므로 패스
event 함수는 백엔드와 프런트엔드를 연결하여 emit 하거나 listen 할 수 있게 한다.
그중 listen 함수를 호출하여 백엔드의 이벤트를 수신할 수 있게 한다.
function init() {
listen("contents", data => {
document.querySelector("#contents").value = data.payload;
});
수신한 데이터를 #contents 필드에 넣어주는 초기화 함수이다.
<body onload="init()">으로 호출한다.
함수의 내용은 백엔드에서 emit한 "contents" 변수의 내용 data를 #contents 필드에 넣는다.
=> file open 기능에 사용된다.
payload를 알아보기 위해 Tauri의 리스너 콜백 데이터 구조를 살펴보겠다.
{
event: "contents", // 이벤트 이름
id: 42, // 내부 식별자(숫자)
payload: ... // ★ 보낸 쪽이 실어 준 실제 데이터
}
data.payload를 통해 이벤트의 실제 데이터인 payload에 접근하는 구조이다.
마지막으로 saveDialog() 함수를 살펴보겠다.
async function saveDialog() {
let contents = document.querySelector("#contents").value;
await invoke("save_file", { contents : contents});
}
간단한 구조!
백엔드의 save_file 함수에 invoke 하여 contents를 입력으로 줄 것이다.
Backend
전체 코드
src-tauri/src/lib.rs
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
use tauri_plugin_dialog::DialogExt;
use tauri::{AppHandle, Emitter};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![save_file, open_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn open_file( app : AppHandle ){
std::thread::spawn(move || {
let file_path = app.dialog().file().blocking_pick_file().unwrap();
let file = file_path.to_string();
let contents = std::fs::read_to_string(file).unwrap();
app.emit("contents", contents).unwrap();
});
}
#[tauri::command]
fn save_file( app : AppHandle, contents : String ){
std::thread::spawn(move || {
let file_path = app.dialog().file().blocking_save_file().unwrap();
let file = file_path.to_string();
std::fs::write(file, contents).unwrap();
app.emit("save_state", "saved").unwrap();
});
}
코드 분석
use tauri_plugin_dialog::DialogExt;
use tauri::{AppHandle, Emitter};
tauri_plugin_dialog는 파일 열기 / 저장 등을 담당한다.
tauri_plugin_dialog 크레이트를 설치해야 한다. 설치 명령어는 아래와 같다.
cargo add tauri_plugin_dialog
AppHandle : 윈도우 열기·닫기, 플러그인 접근, 전역 상태 관리 등 백엔드 쪽에서 앱을 제어할 때 사용
Emitter : Tauri 내부 이벤트 버스에 이벤트를 발행(emit) 할 수 있는 트레이트
이제 open_file, save_file 함수를 차례대로 살펴보자.
#[tauri::command]
fn open_file( app : AppHandle ){
std::thread::spawn(move || {
let file_path = app.dialog().file().blocking_pick_file().unwrap();
let file = file_path.to_string();
let contents = std::fs::read_to_string(file).unwrap();
app.emit("contents", contents).unwrap();
});
}
함수의 input은 AppHandle
std::thread::spawn(move || {...});
Rust의 강력함을 나타내는 thread이다.
새 thread를 만들어서 안쪽 클로저를 실행하라 라는 뜻이다.
move 키워드를 통해 외부 변수의 소유권을 클로저로 이동시킨다.
이를 통해 메인 thread와의 블로킹을 방지하고, 소유권을 확보하여 앱의 안정성을 높인다.
소유권 개념은... 나는 아직 좀 어렵다. 알기야 안다만...
클로저를 보자.
let file_path = app.dialog().file().blocking_pick_file().unwrap();
let file = file_path.to_string();
let contents = std::fs::read_to_string(file).unwrap();
app.emit("contents", contents).unwrap();
dialog 함수에서 file을 선택할 수 있게 "파일 선택 대화 상자"를 띄우고,
path를 문자열로 변환하여 file 변수에 저장한다.
이 file의 텍스트를 읽어서 contents에 저장한다.
이후 "contents" 이벤트를 emit 한다. payload는 contents 변수의 내용.
즉, 파일을 선택하면 그 내용물을 이벤트로 emit 한다.
frontend에서 본 listen 함수를 통해 프론트와 백엔드의 데이터 통신을 연결한다!
#[tauri::command]
fn save_file( app : AppHandle, contents : String ){
std::thread::spawn(move || {
let file_path = app.dialog().file().blocking_save_file().unwrap();
let file = file_path.to_string();
std::fs::write(file, contents).unwrap();
app.emit("save_state", "saved").unwrap();
});
}
save_file은 프론트에 보낼 정보가 없다. 다만 잘 저장되었다는 신호를 emit 하게 만들었다.
달라진 건 read와 write의 차이, blocking_save_file() 함수를 사용한다는 차이이다.
'Tauri > Tutorial' 카테고리의 다른 글
[5] Tauri 새 창 및 메뉴 버튼 만들기 (0) | 2025.05.07 |
---|---|
[3] Tauri Tutorial (button을 통해 프론트엔드와 백엔드를 연결해보자) (0) | 2025.05.03 |
[2] Tauri 설치 및 환경 설정 방법 (0) | 2025.05.03 |
[1] Tauri란? 간단하게 알아보자 (vs Electron) (0) | 2025.05.03 |