首页 > 其他分享 >Tauri教程-进阶篇-第二节 命令机制

Tauri教程-进阶篇-第二节 命令机制

时间:2025-01-15 12:01:55浏览次数:3  
标签:tauri 教程 invoke Tauri 进阶篇 command message my fn

请添加图片描述

“如果结果不如你所愿,就在尘埃落定前奋力一搏。”——《夏目友人帐》
“有些事不是看到了希望才去坚持,而是因为坚持才会看到希望。”——《十宗罪》
“维持现状意味着空耗你的努力和生命。”——纪伯伦

Tauri 技术教程 * 第五章 Tauri的进阶教程
第二节 命令机制

一. 概述

本章节中我们将了解在前端项目中,如何调用Rust端的代码。

Tauri 提供了一个简单但功能强大的command系统,用于从 Web 应用调用 Rust 函数。命令可以接受参数并返回值。它们还可以返回错误和async

二. 命令机制

Tauri 提供了一个命令功能,用于以类型安全的方式访问 Rust 函数,以及一个动态的事件系统。本章节我们先了解下他们的概念,然后了解它的工作内容和方式。

1. 命令 Command

Tauri 提供了一个简单但功能强大的command系统,用于从 Web 应用调用 Rust 函数。命令可以接受参数并返回值。它们还可以返回错误和async

2. 基本概念

1. 命令注释

定义的命令需要采用 注释#[tauri::command] 进行修饰。如下:

#[tauri::command]
fn my_custom_command() {
  println!("I was invoked from JavaScript!");
}

命令的构成由:

  • 注释

  • Rust函数基本构成

    这里要注意:命令名称必须全局唯一。

2. 构造函数注入

通过构建函数进行注入,tauri::generate_handler 如下:

tauri::generate_handler![my_custom_command]

3. Invoke 函数

invoke函数的获取方式有2种

  • api 方式获取

    import { invoke } from ‘@tauri-apps/api/core’;

  • 全局命令获取

    1. 在tauri.conf.json 中配置 app.withGlobalTauri 为true;
    2. const invoke = window.TAURI.core.invoke;

前端调用方式:

invoke('my_custom_command');

4. 参数定义

参数定义的方式如下:

#[tauri::command]
fn my_custom_command(invoke_message: String) {
  println!("I was invoked from JavaScript, with this message: {}", invoke_message);
}

调用方式如下:

invoke('my_custom_command', { invokeMessage: 'Hello!' });

这里注意下:前端参数的名称与后端参数的名称:

后端是invoke_message,前端是json 需要是:invokeMessage,如果你需要保持一致可以采用snake_case 属性进行指定,如下:

#[tauri::command(rename_all = "snake_case")]
fn my_custom_command(invoke_message: String) {}

invoke('my_custom_command', { invoke_message: 'Hello!' });

5. 返回值定义

返回值的定义:实例如下,和普通的函数定义没有区别

#[tauri::command]
fn my_custom_command() -> String {
  "Hello from Rust!".into()
}

在前端处理时,invoke 函数返回的是一个 promise,使用方式如下:

invoke('my_custom_command').then((message) => console.log(message));

返回大批量数据或者文件流时,需要结合 tauri::ipc::Response 来使用,方式如下:

use tauri::ipc::Response;
#[tauri::command]
fn read_file() -> Response {
  let data = std::fs::read("/path/to/file").unwrap();
  tauri::ipc::Response::new(data)
}

3. 异常处理

在Tauri 编程中,异常或者错误的处理需要 实现serde::Serialize,在程序开发中我闷可以使用Result 来返回处理信息,也可以自定义错误来返回处理信息,下面我闷来演示下,如何使用这2种方式

  • Result 的处理

    #[tauri::command]
    fn login(user: String, password: String) -> Result<String, String> {
      if user == "tauri" && password == "tauri" {
        // resolve
        Ok("logged_in".to_string())
      } else {
        // reject
        Err("invalid credentials".to_string())
      }
    }
    
  • 自定义错误类型,这里我们使用thiserror库辅助构建

    #[derive(Debug, thiserror::Error)]
    enum Error {
      #[error(transparent)]
      Io(#[from] std::io::Error),
      #[error("failed to parse as string: {0}")]
      Utf8(#[from] std::str::Utf8Error),
    }
    
    #[derive(serde::Serialize)]
    #[serde(tag = "kind", content = "message")]
    #[serde(rename_all = "camelCase")]
    enum ErrorKind {
      Io(String),
      Utf8(String),
    }
    
    impl serde::Serialize for Error {
      fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
      where
        S: serde::ser::Serializer,
      {
        let error_message = self.to_string();
        let error_kind = match self {
          Self::Io(_) => ErrorKind::Io(error_message),
          Self::Utf8(_) => ErrorKind::Utf8(error_message),
        };
        error_kind.serialize(serializer)
      }
    }
    
    #[tauri::command]
    fn read() -> Result<Vec<u8>, Error> {
      let data = std::fs::read("/path/to/file")?;
      Ok(data)
    }
    

    此时我们在前端使用Invoke的catch 捕获时就会得到一个 { kind: ‘io’ | ‘utf8’, message: string }`错误对象:

    type ErrorKind = {
      kind: 'io' | 'utf8';
      message: string;
    };
    
    invoke('read').catch((e: ErrorKind) => {});
    

4. 异步操作

异步操作也是我们在交互种普遍采用的一种方式,那么在tauri中我们怎么去实现它,

开启的方式比较简单,只需要在命令需要异步运行,将其声明为async,如下:

#[tauri::command]
async fn my_custom_command(value: String) -> String {
    .......
}

在 async 操作需要注意

异步命令使用 在单独的异步任务上执行,没有async 的命令将在主线程上执行

异步返回类型的定义:

在返回类型的定义时,推荐大家采用Result<a,b>的方式来进行处理;

  • Result<String, ()>返回一个字符串,并且没有错误。
  • Result<(), ()>返回null
  • Result<bool, Error>返回布尔值或错误。

代码示例:

// Result<String, ()>
#[tauri::command]
async fn my_custom_command(value: &str) -> Result<String, ()> {
  some_async_function().await;
  Ok(format!(value))
}

前端的处理方式:没有什么区别

invoke('my_custom_command', { value: 'Hello, Async!' }).then(() =>
  console.log('Completed!')
);

5. 数据传输

在Tauri 项目开发中,我们会遇到流的操作,关于如何操作流,Tauri 提供了通道技术,用于应对流传输的数据机制。即: tauri::ipc::Channel;例如下载进度、子进程输出和 WebSocket 消息。

使用起来也是比较简单的,下面我们来看一个下载的示例代码

在Rest中定义下载命令

use tauri::{AppHandle, ipc::Channel};
use serde::Serialize;

#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", tag = "event", content = "data")]
enum DownloadEvent<'a> {
  #[serde(rename_all = "camelCase")]
  Started {
    url: &'a str,
    download_id: usize,
    content_length: usize,
  },
  #[serde(rename_all = "camelCase")]
  Progress {
    download_id: usize,
    chunk_length: usize,
  },
  #[serde(rename_all = "camelCase")]
  Finished {
    download_id: usize,
  },
}

#[tauri::command]
fn download(app: AppHandle, url: String, on_event: Channel<DownloadEvent>) {
  let content_length = 1000;
  let download_id = 1;

  on_event.send(DownloadEvent::Started {
    url: &url,
    download_id,
    content_length,
  }).unwrap();

  for chunk_length in [15, 150, 35, 500, 300] {
    on_event.send(DownloadEvent::Progress {
      download_id,
      chunk_length,
    }).unwrap();
  }

  on_event.send(DownloadEvent::Finished { download_id }).unwrap();
}

以上示例中我们定义了2个内容:

  • DownloadEvent

    枚举类,里面包含了下载文件的信息(地址 大小 id),进度

  • download

    接受2个参数,一个下载地址,一个事件处理

前端代码:

import { invoke, Channel } from '@tauri-apps/api/core';

type DownloadEvent =
  | {
      event: 'started';
      data: {
        url: string;
        downloadId: number;
        contentLength: number;
      };
    }
  | {
      event: 'progress';
      data: {
        downloadId: number;
        chunkLength: number;
      };
    }
  | {
      event: 'finished';
      data: {
        downloadId: number;
      };
    };

const onEvent = new Channel<DownloadEvent>();
onEvent.onmessage = (message) => {
  console.log(`got download event ${message.event}`);
};

await invoke('download', {
  url: 'xxxxxx',
  onEvent,
});

6. 命令交互

在定义的命令中,我们可以访问 WebviewWindow AppHandle 状态 原始请求对象,下面我们来具体看下如何去使用他们

1. 访问 WebviewWindow

#[tauri::command]
async fn my_custom_command(webview_window: tauri::WebviewWindow) {
  println!("WebviewWindow: {}", webview_window.label());
}

2. 访问 WebviewWindow

#[tauri::command]
async fn my_custom_command(app_handle: tauri::AppHandle) {
  let app_dir = app_handle.path_resolver().app_dir();
  use tauri::GlobalShortcutManager;
  app_handle.global_shortcut_manager().register("CTRL + U", move || {});
}

3. 访问状态

状态我们会在后续的状态管理中详细了解它,这里我们先了解操作它的方式,在状态管理中我闷还会在进行探讨。

Tauri 通过 tauri::Builder.manage 来绑定和管理状态,使用时科通过tauri::State 处理绑定的状态即可

如下:

// 定义一个状态信息
struct MyState(String);

#[tauri::command]
fn my_custom_command(state: tauri::State<MyState>) {  // 使用
  assert_eq!(state.0 == "some state value", true);
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
  tauri::Builder::default()
    // 绑定并管理
    .manage(MyState("some state value".into()))
    .invoke_handler(tauri::generate_handler![my_custom_command])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

4. 访问 请求对象

在请求处理中,有时我们需要访问包含原始主体有效信息和请求标头的完整对象,可以通过tauri::ipc::Request对象进行处理

#[tauri::command]
fn upload(request: tauri::ipc::Request) -> Result<(), Error> {

  // request.body()  request.headers()
  // upload...

  Ok(())
}

在前端,你可以调用invoke()来发送原始请求体,

__TAURI__.core.invoke('upload', {}, {
  headers: {
    Authorization: 'apikey',
  },
});

7. 命令抽取

在项目开发中,我们不可能将所有的命令都定义在主程序中,这样不利于开发管理,也不利于修改,通常我们会将命令单独提取为一个文件或者多个文件,在主程序中使用他们,以达到开发分工的情况,提升开发效率和维护便捷性。

  • 定义一个command.rs 在 src-tauri src 目录下

    #[tauri::command]
    fn cmd_a() -> String {
      "Command a".to_string()
    }
    
    #[tauri::command]
    fn cmd_b() -> String {
      "asdsa".to_string()
    }
    
    // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
    #[tauri::command]
    pub fn greet(name: &str) -> String {
        format!("Hello, {}! You've been greeted from Rust!", name)
    }
    
    
  • 在main 中 配置

    mod command;
    
    #[cfg_attr(mobile, tauri::mobile_entry_point)]
    pub fn run() {
        tauri::Builder::default()
     	....
        .invoke_handler(tauri::generate_handler![command::greet])
        .....
    }
    
    

标签:tauri,教程,invoke,Tauri,进阶篇,command,message,my,fn
From: https://blog.csdn.net/sxlesq/article/details/145157529

相关文章

  • Tauri教程-进阶篇-第一节 自定义启动画面
    “如果结果不如你所愿,就在尘埃落定前奋力一搏。”——《夏目友人帐》“有些事不是看到了希望才去坚持,而是因为坚持才会看到希望。”——《十宗罪》“维持现状意味着空耗你的努力和生命。”——纪伯伦Tauri技术教程*第五章Tauri的进阶教程第一节自定义启动画面......
  • 2025最新Python安装教程,PyCharm安装授权教程【附安装包】
    Python安装1、打开Python官网下载安装包:WelcometoPython.org注意:由于官网下载速度较慢,我这边将官网下载的安装包提前打包成了压缩文件,需要的同学可以直接点击这里免下载!2、下载完成后打开安装包: 3、按照下图,先勾选最下方两个配置选项,然后选择上方的自定义安装4、这里......
  • 【FLUX教程】重磅回归:麦橘团队发布FLUX最新亚洲人像模型!
    大家好,就在昨天,麦橘团队推出了基于FLUX的最新亚洲人像模型。麦橘模型在SD15时代是大家画小姐姐的首选模型,几乎所有用过麦橘模型的同学都说好。然而,它却在SDXL,FLUX时代,慢慢淡出了大家的视野。不过,它并没有消失,经过一段时间的沉寂,麦橘团队再次推出基于FLUX微调的最新的majic......
  • DevExpress WPF 中文教程:Grid - 如何创建列并将其绑定到数据属性?
    DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。无论是Office办公软件的衍伸产品,还是以数据为中心......
  • IntelliJ idea 2023.2.2 下载及破解教程
    本方法适用于全系列版本。介绍IDEA和JetBrains系列所有软件(IntelliJIDEA、CLion、PhpStorm、GoLand、PyCharm、WebStorm、Rider、DataGrip、RubyMine、AppCode、DataSpell、Gateway、dotCover、dotTrace、dotTrace等等)的激活破解。JetBrains系列软件数量很多,你需要的话一个一个......
  • 《鸿蒙开发-鸿蒙教程-答案之书》组件margin左和右等于没偏?
    《鸿蒙开发-鸿蒙教程-答案之书》组件margin左和右等于没偏?鸿蒙的margin和Android的margin还是有点区别,Android的margin没有任何毛病,但是鸿蒙margin会导致组件偏移回来的。因为鸿蒙的组件可以超越父组件,往外面宽展。那怎么办呢?先看个有问题的代码:@Entry@Componentstruc......
  • 【零基础SD教程】2024最细自学Stable Diffusion全套教程!附 Sd 安装包,拿走不谢
    看:哈哈是不是很漂亮?但这些都不是真实存在的直接通过AI生成的美女达到如此逼真的地步是怎么做到的呢?那么接下来就是:学习生成小姐姐的正确姿势首先需要在你的电脑中安装一个「stable-diffusion」接下来就推荐几个大模型,以下均为个人根据SD软件出图的效......
  • Xbox:Xbox游戏测试与质量保证技术教程_2024-07-19_21-13-40.Tex
    Xbox:Xbox游戏测试与质量保证技术教程游戏测试基础游戏测试的重要性游戏测试是游戏开发过程中不可或缺的一环,它确保游戏在发布前能够提供给玩家一个流畅、无bug、且具有高度娱乐性的体验。测试的重要性在于:确保游戏质量:通过测试,可以发现并修复游戏中的各种问题,如图形错误......
  • LangGraph 教程:初学者综合指南(1)
    关键概念图结构LangGraph设计的核心是基于图形的应用程序工作流程表示。该图包含两个主要元素:节点-工作的构建块:LangGraph中的每个节点代表应用程序中的一个不同的工作或操作单元。这些节点本质上是封装特定任务的Python函数。此任务可能涉及多种操作,例如:与LLM直......
  • AI绘画stable diffusion入门基础教程(非常详细),AI绘画入门到精通,收藏这一篇就够了!
    现在学习AI绘画的人越来越多,这是属于"生成式人工智能(AIGC)"的春天。4月初,第一批被AI取代的人已出现,据媒体报道,一家游戏公司的原画团队被大面积裁员;4月末,AI又悄悄将手"伸"向网店模特岗位,使用AI模特取代真人模特只需要多投喂数据集,例如采用loRa在stablediffusion上训练出一个......