关于 gpui-patterns
面向使用 GPUI 构建可维护应用的开发者参考技能,整理了组件组合、状态、事件、操作、渲染和生命周期相关模式。
主要功能
- 01组件组合模式
- 02模型视图与上下文状态
- 03订阅和选择性更新
- 04鼠标、键盘与点击处理
- 05操作与按键绑定
- 06渲染和生命周期指南
适用场景
- 01组织 GPUI 视图组件
- 02选择状态与订阅模式
- 03实现输入处理和操作
- 04检查渲染与生命周期代码
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ geoffjay/ claude-plugins --skill gpui-patterns安装后在 Claude Code 中输入/gpui-patterns确认已加载。
详细介绍
GPUI 模式
元数据
此技能提供有关常见 GPUI 模式和最佳实践的全面指导,帮助构建易于维护且高性能的应用程序。
说明
组件组合模式
基本组件结构
use gpui::*;
// View component with state
struct MyView {
state: Model<MyState>,
_subscription: Subscription,
}
impl MyView {
fn new(state: Model<MyState>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&state, |_, _, cx| cx.notify());
Self { state, _subscription }
}
}
impl Render for MyView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let state = self.state.read(cx);
div()
.flex()
.flex_col()
.child(format!("Value: {}", state.value))
}
}
容器/呈现器模式
容器(管理状态和逻辑):
struct Container {
model: Model<AppState>,
_subscription: Subscription,
}
impl Container {
fn new(model: Model<AppState>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&model, |_, _, cx| cx.notify());
Self { model, _subscription }
}
}
impl Render for Container {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let state = self.model.read(cx);
// Pass data to presenter
Presenter::new(state.data.clone())
}
}
呈现器(纯渲染):
struct Presenter {
data: String,
}
impl Presenter {
fn new(data: String) -> Self {
Self { data }
}
}
impl Render for Presenter {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div().child(self.data.as_str())
}
}
复合组件
// Parent component with shared context
pub struct Tabs {
items: Vec<TabItem>,
active_index: usize,
}
pub struct TabItem {
label: String,
content: Box<dyn Fn() -> AnyElement>,
}
impl Tabs {
pub fn new() -> Self {
Self {
items: Vec::new(),
active_index: 0,
}
}
pub fn add_tab(
mut self,
label: impl Into<String>,
content: impl Fn() -> AnyElement + 'static,
) -> Self {
self.items.push(TabItem {
label: label.into(),
content: Box::new(content),
});
self
}
fn set_active(&mut self, index: usize, cx: &mut ViewContext<Self>) {
self.active_index = index;
cx.notify();
}
}
impl Render for Tabs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.child(
// Tab headers
div()
.flex()
.children(
self.items.iter().enumerate().map(|(i, item)| {
tab_header(&item.label, i == self.active_index, || {
self.set_active(i, cx)
})
})
)
)
.child(
// Active tab content
(self.items[self.active_index].content)()
)
}
}
状态管理策略
模型-视图模式
// Model: Application state
#[derive(Clone)]
struct AppState {
count: usize,
items: Vec<String>,
}
// View: Observes and renders state
struct AppView {
state: Model<AppState>,
_subscription: Subscription,
}
impl AppView {
fn new(state: Model<AppState>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&state, |_, _, cx| cx.notify());
Self { state, _subscription }
}
fn increment(&mut self, cx: &mut ViewContext<Self>) {
self.state.update(cx, |state, cx| {
state.count += 1;
cx.notify();
});
}
}
基于上下文的状态
// Global state via context
#[derive(Clone)]
struct GlobalSettings {
theme: Theme,
language: String,
}
impl Global for GlobalSettings {}
// Initialize in app
fn init_app(cx: &mut AppContext) {
cx.set_global(GlobalSettings {
theme: Theme::Light,
language: "en".to_string(),
});
}
// Access in components
impl Render for MyView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = cx.global::<GlobalSettings>();
div()
.child(format!("Language: {}", settings.language))
}
}
订阅模式
基本订阅:
struct Observer {
model: Model<Data>,
_subscription: Subscription,
}
impl Observer {
fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&model, |_, _, cx| {
cx.notify(); // Rerender on change
});
Self { model, _subscription }
}
}
选择性更新:
impl Observer {
fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self {
let _subscription = cx.observe(&model, |this, model, cx| {
let data = model.read(cx);
// Only rerender if specific field changed
if data.important_field != this.cached_field {
this.cached_field = data.important_field.clone();
cx.notify();
}
});
Self {
model,
cached_field: String::new(),
_subscription,
}
}
}
多重订阅:
struct MultiObserver {
model_a: Model<DataA>,
model_b: Model<DataB>,
_subscriptions: Vec<Subscription>,
}
impl MultiObserver {
fn new(
model_a: Model<DataA>,
model_b: Model<DataB>,
cx: &mut ViewContext<Self>,
) -> Self {
let mut subscriptions = Vec::new();
subscriptions.push(cx.observe(&model_a, |_, _, cx| cx.notify()));
subscriptions.push(cx.observe(&model_b, |_, _, cx| cx.notify()));
Self {
model_a,
model_b,
_subscriptions: subscriptions,
}
}
}
事件处理模式
点击事件
div()
.on_click(cx.listener(|this, event: &ClickEvent, cx| {
// Handle click
this.handle_click(cx);
}))
.child("Click me")
键盘事件
div()
.on_key_down(cx.listener(|this, event: &KeyDownEvent, cx| {
match event.key.as_str() {
"Enter" => this.submit(cx),
"Escape" => this.cancel(cx),
_ => {}
}
}))
事件传播
// Stop propagation
div()
.on_click(|event, cx| {
event.stop_propagation();
// Handle click
})
// Prevent default
div()
.on_key_down(|event, cx| {
if event.key == "Tab" {
event.prevent_default();
// Custom tab handling
}
})
鼠标事件
div()
.on_mouse_down(cx.listener(|this, event, cx| {
this.mouse_down_position = Some(event.position);
}))
.on_mouse_move(cx.listener(|this, event, cx| {
if let Some(start) = this.mouse_down_position {
let delta = event.position - start;
this.handle_drag(delta, cx);
}
}))
.on_mouse_up(cx.listener(|this, event, cx| {
this.mouse_down_position = None;
}))
操作系统
定义操作
use gpui::*;
actions!(app, [
Increment,
Decrement,
Reset,
SetValue
]);
// Action with data
#[derive(Clone, PartialEq)]
pub struct SetValue {
pub value: i32,
}
impl_actions!(app, [SetValue]);
注册操作处理程序
impl Counter {
fn register_actions(&mut self, cx: &mut ViewContext<Self>) {
cx.on_action(cx.listener(|this, _: &Increment, cx| {
this.model.update(cx, |state, cx| {
state.count += 1;
cx.notify();
});
}));
cx.on_action(cx.listener(|this, _: &Decrement, cx| {
this.model.update(cx, |state, cx| {
state.count = state.count.saturating_sub(1);
cx.notify();
});
}));
cx.on_action(cx.listener(|this, action: &SetValue, cx| {
this.model.update(cx, |state, cx| {
state.count = action.value;
cx.notify();
});
}));
}
}
分发操作
// From within component
fn handle_button_click(&mut self, cx: &mut ViewContext<Self>) {
cx.dispatch_action(Increment);
}
// With data
fn set_specific_value(&mut self, value: i32, cx: &mut ViewContext<Self>) {
cx.dispatch_action(SetValue { value });
}
// Global action dispatch
cx.dispatch_action_on_window(Reset, window_id);
按键绑定
// Register global keybindings
fn register_keybindings(cx: &mut AppContext) {
cx.bind_keys([
KeyBinding::new("cmd-+", Increment, None),
KeyBinding::new("cmd--", Decrement, None),
KeyBinding::new("cmd-0", Reset, None),
]);
}
元素组合
构建器模式
fn card(title: &str, content: impl IntoElement) -> impl IntoElement {
div()
.flex()
.flex_col()
.bg(white())
.border_1()
.rounded_lg()
.shadow_sm()
.p_6()
.child(
div()
.text_lg()
.font_semibold()
.mb_4()
.child(title)
)
.child(content)
}
条件渲染
div()
.when(condition, |this| {
this.bg(blue_500())
})
.when_some(optional_value, |this, value| {
this.child(format!("Value: {}", value))
})
.map(|this| {
if complex_condition {
this.border_1()
} else {
this.border_2()
}
})
动态子元素
div()
.children(
items.iter().map(|item| {
div().child(item.name.as_str())
})
)
视图生命周期
初始化
impl MyView {
fn new(cx: &mut ViewContext<Self>) -> Self {
// Initialize state
let model = cx.new_model(|_| MyState::default());
// Set up subscriptions
let subscription = cx.observe(&model, |_, _, cx| cx.notify());
// Spawn async tasks
cx.spawn(|this, mut cx| async move {
// Async initialization
}).detach();
Self {
model,
_subscription: subscription,
}
}
}
更新通知
impl MyView {
fn update_state(&mut self, new_data: Data, cx: &mut ViewContext<Self>) {
self.model.update(cx, |state, cx| {
state.data = new_data;
cx.notify(); // Trigger rerender
});
}
}
清理
impl Drop for MyView {
fn drop(&mut self) {
// Manual cleanup if needed
// Subscriptions are automatically dropped
}
}
响应式模式
派生状态
impl Render for MyView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let state = self.model.read(cx);
// Compute derived values
let total = state.items.iter().map(|i| i.value).sum::<i32>();
let average = total / state.items.len() as i32;
div()
.child(format!("Total: {}", total))
.child(format!("Average: {}", average))
}
}
异步更新
impl MyView {
fn load_data(&mut self, cx: &mut ViewContext<Self>) {
let model = self.model.clone();
cx.spawn(|_, mut cx| async move {
let data = fetch_data().await?;
cx.update_model(&model, |state, cx| {
state.data = data;
cx.notify();
})?;
Ok::<_, anyhow::Error>(())
}).detach();
}
}
资源
官方文档
- GPUI GitHub:https://github.com/zed-industries/zed/tree/main/crates/gpui
- Zed 编辑器源代码:真实 GPUI 示例
常见模式参考
- 模型-视图:状态管理模式
- 容器-呈现器:职责分离
- 复合组件:协同工作的相关组件
- 操作系统:用于用户交互的命令模式
- 订阅:用于响应式更新的观察者模式
最佳实践
- 保存订阅,以防止清理问题
- 谨慎使用
cx.notify() - 优先采用组合而非继承
- 保持渲染方法纯粹
- 妥善处理错误
- 记录组件 API
- 测试组件行为
评论
支持 Markdown