跳到主要内容

3 篇博文 含有标签「Dioxus」

查看所有标签

深入 Dioxus 信号系统

1. 创建信号

在 Dioxus 中,可以使用 use_state 钩子创建状态:

let count = use_state(&cx, || 0);
  • 解释use_state 接受一个上下文引用 &cx 和一个初始化函数,返回一个状态和一个用于更新状态的函数。

2. 读取和更新信号

读取和更新状态的方式如下:

let current_value = *count; // 读取当前值
count.set(new_value); // 设置新值
  • 解释
    • *count:解引用以获取当前状态的值。
    • count.set(new_value):更新状态为 new_value,并触发组件重新渲染。

3. 状态更新的核心原理:基于 PartialEq 的变化检测

Dioxus 的信号系统通过底层实现的 PartialEq trait 来判断状态值的变化,从而决定是否触发组件的重新渲染。

原理讲解

  1. 信号变化检测

    • 每次调用 set 方法更新信号时,Dioxus 会通过调用值的 PartialEq 方法比较新值和旧值。
    • 如果 PartialEq::eq 返回 true(即值相等),Dioxus 会跳过重新渲染。
    • 如果 PartialEq::eq 返回 false(即值不相等),Dioxus 会将信号标记为“已变化”,并通知依赖此信号的组件重新渲染。
  2. 示例代码

    fn Counter(cx: Scope) -> Element {
    let count = use_state(&cx, || 0);

    cx.render(rsx! {
    button {
    onclick: move |_| count.set(*count + 1),
    "Count: {count}"
    }
    })
    }
    • count.set(*count + 1) 被调用时:
      1. count 的新值(*count + 1)与旧值(*count)通过 PartialEq 比较。
      2. 如果值不同,则触发组件重新渲染。
  3. 为什么使用 PartialEq

    • 性能优化:通过 PartialEq 判断是否需要重新渲染,避免不必要的性能开销。
    • 灵活性:支持自定义比较逻辑。开发者可以为自定义类型实现 PartialEq,仅比较关键字段或按需定义相等性规则。
  4. 自定义类型的 PartialEq 实现: 如果你有一个自定义数据类型,可以通过实现 PartialEq 控制变化检测逻辑。例如:

    #[derive(Clone)]
    struct CustomData {
    field1: i32,
    field2: String,
    }

    // 实现 PartialEq
    impl PartialEq for CustomData {
    fn eq(&self, other: &Self) -> bool {
    self.field1 == other.field1 // 仅比较 field1
    }
    }

原理优势

  • 避免冗余渲染:与 React 的 Virtual DOM diff 不同,Dioxus 在状态变化时直接通过 PartialEq 比较值,大幅减少无效计算。
  • 精细化控制:开发者可以通过自定义 PartialEq 提供更高的灵活性。
  • 内置优化:对基本类型如 i32StringPartialEq 比较成本极低。

4. 小结

Dioxus 的信号系统基于 PartialEq 实现细粒度的变化检测和状态更新机制。这种机制为开发者提供了:

  1. 高效的状态管理:减少不必要的重新渲染,提高性能。
  2. 灵活的比较逻辑:通过自定义 PartialEq 实现更复杂的状态变化判断。
  3. 简单的 API:通过自动追踪和更新依赖,简化组件开发。

这种自动化和精细化的状态管理方案,使得 Dioxus 在响应式编程中具有独特优势,为开发者带来更佳的开发体验。

鱼雪

引言

在现代前端开发中,状态管理是一个核心话题。React 通过 useStateuseReducer 等 Hooks 实现状态管理, 而 Dioxus 则采用了基于信号(Signal)的响应式方案。

本文将深入对比这两种方案的异同,帮助你更好地理解和使用 Dioxus 的状态管理系统。

基础概念对比

React 的状态管理

在 React 中,我们通常这样管理状态:

function Counter() {
const [count, setCount] = useState(0);

return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}

Dioxus 的信号系统

而在 Dioxus 中,使用信号来管理状态:

fn Counter(cx: Scope) -> Element {
let count = use_state(&cx, || 0);

cx.render(rsx! {
button {
onclick: move |_| count.set(*count + 1),
"Count: {count}"
}
})
}

核心区别

  1. 状态更新机制

    • React: 通过 setState 触发重渲染。
    • Dioxus: 通过信号的变化自动触发组件更新, 本质上通过 PartialEq 实现自动更新
  2. 响应式特性

    • React: 需要手动处理依赖关系(如在 useEffect 中指定依赖)。
    • Dioxus: 自动追踪依赖,实现细粒度更新。

深入 Dioxus 信号系统

1. 创建信号

在 Dioxus 中,可以使用 use_state 钩子创建状态:

let count = use_state(&cx, || 0);
  • 解释use_state 接受一个上下文引用 &cx 和一个初始化函数,返回一个状态和一个用于更新状态的函数。

2. 读取和更新信号

读取和更新状态的方式如下:

let current_value = *count; // 读取当前值
count.set(new_value); // 设置新值
  • 解释
    • *count:解引用以获取当前状态的值。
    • count.set(new_value):更新状态为 new_value,并触发组件重新渲染。

3. 派生状态

在 React 中:

function DoubleCounter() {
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => count * 2, [count]);

return <div>Double: {doubleCount}</div>;
}

在 Dioxus 中,可以使用 use_memo 创建派生状态:

fn DoubleCounter(cx: Scope) -> Element {
let count = use_state(&cx, || 0);
let double_count = use_memo(&cx, move || *count * 2, (*count,));

cx.render(rsx! {
div { "Double: {double_count}" }
})
}
  • 解释
    • use_memo 接受上下文引用、计算函数和依赖元组,返回计算结果。当依赖发生变化时,重新计算。

4. 异步状态处理

Dioxus 提供了 use_future 用于处理异步状态:

fn AsyncCounter(cx: Scope) -> Element {
let count = use_state(&cx, || 0);

let async_value = use_future(&cx, (*count,), |&count| async move {
// 异步操作
gloo_timers::future::TimeoutFuture::new(100).await;
count * 2
});

cx.render(rsx! {
div { "Async value: {async_value.value().unwrap_or(&0)}" }
})
}
  • 解释
    • use_future 接受上下文引用、依赖元组和异步函数,返回一个包含异步结果的状态。当依赖变化时,重新执行异步函数。

状态共享方案对比

1. Props 传递

React:

function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} setCount={setCount} />;
}

Dioxus:

fn Parent(cx: Scope) -> Element {
let count = use_state(&cx, || 0);
cx.render(rsx! {
Child { count: *count, set_count: count.setter() }
})
}
  • 解释:在 Dioxus 中,count.setter() 返回一个用于更新状态的函数,可以作为属性传递给子组件。

2. Context 使用

React:

const CountContext = createContext();

function Provider({ children }) {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={{ count, setCount }}>
{children}
</CountContext.Provider>
);
}

Dioxus:

#[derive(Clone)]
struct CountState {
count: UseState<i32>,
}

fn Provider(cx: Scope) -> Element {
let count = use_state(&cx, || 0);
cx.provide_context(CountState { count });

cx.render(rsx! {
// 子组件
})
}
  • 解释provide_context 用于在组件树中提供上下文,子组件可以使用 use_context 获取共享的状态。

3. 全局状态

Dioxus 支持全局状态管理:

static COUNT: AtomRef<i32> = |_| 0;

fn GlobalCounter(cx: Scope) -> Element {
let count = use_atom_ref(&cx, COUNT);
cx.render(rsx! {
button {
onclick: move |_| *count.write() += 1,
"Global count: {count.read()}"
}
})
}
  • 解释
    • AtomRef 定义了一个全局状态。
    • use_atom_ref 用于在组件中访问全局状态的引用,readwrite 方法用于读取和修改全局状态的值。

最佳实践建议

  1. 选择合适的状态范围

    • 局部状态:使用 use_state
    • 共享状态:使用 Context
    • 全局状态:使用 AtomRef
  2. 性能优化

    • 合理使用 use_memo 缓存派生状态。
    • 避免状态嵌套过深。
    • 尽量将状态分解为粒度更细的信号。
  3. 组件设计

    • 使用明确的属性接口。
    • 将复杂状态逻辑抽象为自定义 Hook。
    • 对于全局状态,尽量使用模块化设计避免耦合。

总结

Dioxus 的信号系统提供了一种更加自动化和细粒度的状态管理方案。相比 React 的手动依赖追踪,它能够:

  1. 自动追踪和更新依赖。
  2. 提供更简洁的 API。
  3. 实现更高效的细粒度更新。
  4. 更自然地处理异步状态。

虽然学习曲线可能稍陡,但掌握后能够带来更好的开发体验和运行时性能。

参考资料


本文首发于 https://yuxuetr.com,转载请注明出处。

鱼雪

2024 年 12 月 9 日,Dioxus 0.6 重磅发布!这次更新带来了许多全新的工具特性,显著提升了开发者体验,包括移动模拟器支持、热重载、交互式 CLI 等,助力开发者更高效地构建全栈应用。


什么是 Dioxus?

Dioxus 是一个全栈开发框架,支持通过单一代码库构建 Web、桌面和移动应用。我们的目标是打造一个比 Flutter 更强大的框架。Dioxus 专注于:

  • 一流的全栈 Web 支持
  • 类型安全的服务器/客户端通信
  • 极致的性能

0.6 版本的主要亮点

此次发布,我们重新设计了 Dioxus CLI,大幅提升了开发者体验,修复了许多长期存在的问题,并引入了一系列新功能。

1. 全新 Dioxus CLI

以下是 Dioxus CLI 的关键改进:

  • dx serve for mobile:支持在 Android 和 iOS 模拟器及设备上运行应用。
  • 神奇的热重载:支持格式化字符串、属性和嵌套的 rsx!{} 的热重载。
  • 交互式 CLI:借鉴 Astro 的交互式用户体验,重新设计了 Dioxus CLI。
  • 内联堆栈跟踪:直接在终端中捕获 WASM 崩溃和日志。
  • 桌面和移动端的服务器函数支持:为本地应用内联服务器 RPC。

2. 全框架开发者体验改进

我们还在框架的其他方面进行了大量优化:

  • 通知与加载屏幕:开发模式下新增通知与加载屏幕,提升调试体验。
  • 自动补全改进:大幅提升 RSX 的自动补全效果。
  • asset! 稳定化:稳定了集成于原生应用的基于链接器的资源系统。
  • 流式 HTML:支持从服务器到客户端的流式 Suspense 和错误边界。
  • 静态网站生成(SSG)与增量静态生成(ISG):支持更多静态网站构建模式。
  • 事件错误处理:在事件处理器、任务和组件中使用 ? 处理错误。
  • Meta 元素:新增 HeadTitleMetaLink 元素,用于设置文档属性。
  • 同步的 prevent_default:跨平台同步处理事件。
  • onresize 事件处理器:无需 IntersectionObserver 也能跟踪元素大小变化。
  • onvisible 事件处理器:无需 IntersectionObserver 也能跟踪元素可见性。
  • WGPU 集成:支持将 Dioxus 渲染为 WGPU 表面及子窗口的覆盖层。
  • dx bundle:全面支持 Web、iOS 和 Android 平台的打包。
  • JSON 模式:CLI 消息支持 JSON 输出,便于第三方工具和 CI/CD 流程使用。
  • 新模板:新增三个跨平台应用的启动模板。
  • 教程与指南:推出面向 Dioxus 0.6 及后续版本的新教程和指南。
  • 二进制补丁原型:基于纯 Rust 的热重载引擎原型。

重点改进详情

神奇的热重载

Dioxus 的热重载功能实现了前所未有的便利性,不仅支持常规的代码热更新,还能对嵌套的 rsx!{} 结构进行即时刷新。这极大提升了开发效率,尤其是在复杂 UI 开发场景中。

交互式 CLI

新版 CLI 借鉴了 Astro 的交互式设计,提供了更直观的用户体验。例如,在构建项目时,CLI 会根据用户选择动态更新配置,减少不必要的手动操作。

WASM 崩溃与日志捕获

通过内联堆栈跟踪功能,开发者可以直接在终端中查看 WASM 的崩溃原因及日志信息。这有助于快速定位问题,尤其是在调试复杂 Web 应用时。


新功能概览

特性描述
移动模拟器支持在 Android 和 iOS 上快速运行和测试应用。
流式 HTML 支持从服务器流式加载 Suspense 和错误边界,提升性能。
SSG 与 ISG 支持更灵活的静态网站生成和增量更新支持。
WGPU 集成在 WGPU 表面和子窗口中渲染 Dioxus 应用。
新教程与模板快速上手跨平台开发的全新模板和详细指南。
JSON 模式CLI 消息支持 JSON 格式,便于与 CI/CD 集成。
事件处理改进新增同步 prevent_defaultonresize 等功能。
二进制补丁原型纯 Rust 实现的热重载引擎,带来更快的开发迭代体验。

未来发展

Dioxus 将继续优化开发体验,增加对更多平台和场景的支持。我们希望通过 Dioxus,开发者能够更高效地构建现代化应用。

鱼雪