状态管理
在 Tauri 应用中,你经常需要跟踪应用的当前状态或管理与之相关的事物的生命周期。Tauri 提供了一种使用 [Manager] API 管理应用状态的简便方法,并且在调用命令时可以读取它。
🌐 In a Tauri application, you often need to keep track of the current state of your application or manage the lifecycle of things associated with it. Tauri provides an easy way to manage the state of your application using the [Manager] API, and read it when commands are called.
以下是一个简单的例子:
🌐 Here is a simple example:
use tauri::{Builder, Manager};
struct AppData { welcome_message: &'static str,}
fn main() { Builder::default() .setup(|app| { app.manage(AppData { welcome_message: "Welcome to Tauri!", }); Ok(()) }) .run(tauri::generate_context!()) .unwrap();}你可以之后使用任何实现 [Manager] 特性的类型来访问你的状态,例如 [App] 实例:
🌐 You can later access your state with any type that implements the [Manager] trait, for example the [App] instance:
let data = app.state::<AppData>();有关更多信息,包括在命令中访问状态,请参阅 访问状态 部分。
🌐 For more info, including accessing state in commands, see the Accessing State section.
🌐 Mutability
在 Rust 中,你不能直接修改在多个线程之间共享的值,或者当所有权通过共享指针(例如 [Arc] 或 Tauri 的 [State])控制时修改值。这样做可能会导致数据竞争(例如,同时发生两个写操作)。
🌐 In Rust, you cannot directly mutate values which are shared between multiple threads or when ownership is controlled through a shared pointer such as [Arc] (or Tauri’s [State]). Doing so could cause data races (for example, two writes happening simultaneously).
为了解决这个问题,你可以使用一个被称为内部可变性的概念。例如,标准库中的[Mutex]可以用来封装你的状态。这允许你在需要修改时锁定值,并在完成后解锁它。
🌐 To work around this, you can use a concept known as interior mutability. For example, the standard library’s [Mutex] can be used to wrap your state. This allows you to lock the value when you need to modify it, and unlock it when you are done.
use std::sync::Mutex;
use tauri::{Builder, Manager};
#[derive(Default)]struct AppState { counter: u32,}
fn main() { Builder::default() .setup(|app| { app.manage(Mutex::new(AppState::default())); Ok(()) }) .run(tauri::generate_context!()) .unwrap();}现在可以通过锁定互斥锁来修改状态:
🌐 The state can now be modified by locking the mutex:
let state = app.state::<Mutex<AppState>>();
// Lock the mutex to get mutable access:let mut state = state.lock().unwrap();
// Modify the state:state.counter += 1;在作用域结束时,或当 MutexGuard 以其他方式被释放时,互斥锁会自动解锁,以便应用的其他部分可以访问和修改其中的数据。
🌐 At the end of the scope, or when the MutexGuard is otherwise dropped, the mutex is unlocked automatically so that other parts of your application can access and mutate the data within.
🌐 When to use an async mutex
引用 Tokio 文档 的话,通常使用标准库的 [Mutex] 而不是 Tokio 提供的异步互斥锁是可以的:
🌐 To quote the Tokio documentation, it’s often fine to use the standard library’s [Mutex] instead of an async mutex such as the one Tokio provides:
与流行的看法相反,在异步代码中使用标准库中的普通互斥锁是可以的,而且通常是首选……异步互斥锁的主要用例是提供对诸如数据库连接等 IO 资源的共享可变访问。
完全阅读所链接的文档是一个好主意,以了解两者之间的权衡。你需要异步互斥锁的一个原因是如果你需要在 await 点之间保持 MutexGuard。
🌐 It’s a good idea to read the linked documentation fully to understand the trade-offs between the two. One reason you would need an async mutex is if you need to hold the MutexGuard across await points.
🌐 Do you need Arc?
在 Rust 中,看到 [Arc] 用于在多个线程之间共享一个值的所有权是很常见的(通常与以 Arc<Mutex<T>> 形式的 [Mutex] 搭配使用)。然而,对于存储在 [State] 中的东西,你不需要使用 [Arc],因为 Tauri 会为你处理这个。
如果 State 的生命周期要求阻止你将状态移动到新线程中,你可以改为将一个 AppHandle 移动到线程中,然后如下面 “使用 Manager 特性访问状态” 部分所示检索你的状态。AppHandle 被刻意设计为克隆成本低,以适用于像这样的用例。
🌐 In case State’s lifetime requirements prevent you from moving your state into a new thread you can instead move an AppHandle into the thread and then retrieve your state as shown below in the “Access state with the Manager trait” section. AppHandles are deliberately cheap to clone for use-cases like this.
🌐 Accessing State
🌐 Access state in commands
#[tauri::command]fn increase_counter(state: State<'_, Mutex<AppState>>) -> u32 { let mut state = state.lock().unwrap(); state.counter += 1; state.counter}有关命令的更多信息,请参见 从前端调用 Rust。
🌐 For more information on commands, see Calling Rust from the Frontend.
🌐 Async commands
如果你正在使用 async 命令并且想使用 Tokio 的异步 Mutex,你可以以相同的方式进行设置,并像这样访问状态:
🌐 If you are using async commands and want to use Tokio’s async Mutex, you can set it up the same way and access the state like this:
#[tauri::command]async fn increase_counter(state: State<'_, Mutex<AppState>>) -> Result<u32, ()> { let mut state = state.lock().await; state.counter += 1; Ok(state.counter)}请注意,如果使用异步命令,返回类型必须是 [Result]。
🌐 Note that the return type must be [Result] if you use asynchronous commands.
🌐 Access state with the [Manager] trait
有时你可能需要在命令之外访问状态,例如在不同的线程中或在像 on_window_event 这样的事件处理器中。在这种情况下,你可以使用实现了 [Manager] 特性的类型(例如 AppHandle)的 state() 方法来获取状态:
🌐 Sometimes you may need to access the state outside of commands, such as in a different thread or in an event handler like on_window_event. In such cases, you can use the state() method of types that implement the [Manager] trait (such as the AppHandle) to get the state:
use std::sync::Mutex;use tauri::{Builder, Window, WindowEvent, Manager};
#[derive(Default)]struct AppState { counter: u32,}
// In an event handler:fn on_window_event(window: &Window, _event: &WindowEvent) { // Get a handle to the app so we can get the global state. let app_handle = window.app_handle(); let state = app_handle.state::<Mutex<AppState>>();
// Lock the mutex to mutably access the state. let mut state = state.lock().unwrap(); state.counter += 1;}
fn main() { Builder::default() .setup(|app| { app.manage(Mutex::new(AppState::default())); Ok(()) }) .on_window_event(on_window_event) .run(tauri::generate_context!()) .unwrap();}当你不能依赖命令注入时,这种方法很有用。例如,如果你需要将状态移到一个使用 AppHandle 更容易的线程中,或者如果你不处于命令上下文中。
🌐 This method is useful when you cannot rely on command injection. For example, if you need to move the state into a thread where using an AppHandle is easier, or if you are not in a command context.
🌐 Mismatching Types
Tauri 中文网 - 粤ICP备13048890号
Nodejs.cn 旗下网站