mcpField
G
gpui-patterns

gpui-patterns

github.com/geoffjay开发848 次安装52 周前更新
代码仓库

关于 gpui-patterns

面向使用 GPUI 构建可维护应用的开发者参考技能,整理了组件组合、状态、事件、操作、渲染和生命周期相关模式。

主要功能

  1. 01组件组合模式
  2. 02模型视图与上下文状态
  3. 03订阅和选择性更新
  4. 04鼠标、键盘与点击处理
  5. 05操作与按键绑定
  6. 06渲染和生命周期指南

适用场景

  1. 01组织 GPUI 视图组件
  2. 02选择状态与订阅模式
  3. 03实现输入处理和操作
  4. 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();
    }
}

资源

官方文档
常见模式参考
  • 模型-视图:状态管理模式
  • 容器-呈现器:职责分离
  • 复合组件:协同工作的相关组件
  • 操作系统:用于用户交互的命令模式
  • 订阅:用于响应式更新的观察者模式
最佳实践
  • 保存订阅,以防止清理问题
  • 谨慎使用 cx.notify()
  • 优先采用组合而非继承
  • 保持渲染方法纯粹
  • 妥善处理错误
  • 记录组件 API
  • 测试组件行为

评论

支持 Markdown