AgentSkillsCN

04 Patterns

04 模式

SKILL.md

Xilem Patterns and Best Practices

Component Pattern

Structure your app as composable components:

rust
use xilem::view::{flex_col, label, text_button};
use xilem::{WidgetView, core::Edit};

// Component: A function that takes state and returns a view
fn counter_component(count: i32, on_increment: impl Fn(&mut AppState) + Send + Sync + 'static) 
    -> impl WidgetView<Edit<AppState>> 
{
    flex_col((
        label(format!("Count: {}", count)),
        text_button("Increment", on_increment),
    ))
}

// Usage in parent
fn app_logic(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    counter_component(state.count, |s| s.count += 1)
}

State Management

Centralized State

rust
struct AppState {
    user: UserState,
    todos: Vec<Todo>,
    ui: UiState,
}

struct UiState {
    filter: Filter,
    editing_id: Option<usize>,
}

Using Lens for Modular Components

rust
use xilem::core::lens;

fn app_logic(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    flex_col((
        // User component only sees UserState
        lens(user_profile, |s: &mut AppState, ()| &mut s.user),
        // Todo list only sees Vec<Todo>
        lens(todo_list, |s: &mut AppState, ()| &mut s.todos),
    ))
}

List Rendering

Static Lists

rust
fn todo_list(todos: &mut Vec<Todo>) -> impl WidgetView<Edit<Vec<Todo>>> + use<> {
    let items: Vec<_> = todos
        .iter()
        .enumerate()
        .map(|(i, todo)| {
            todo_item(i, todo.clone())
        })
        .collect();
    
    flex_col(items)
}

Filtered Lists

rust
fn filtered_todos(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    let items: Vec<_> = state.todos
        .iter()
        .enumerate()
        .filter(|(_, todo)| match state.filter {
            Filter::All => true,
            Filter::Active => !todo.done,
            Filter::Completed => todo.done,
        })
        .map(|(i, todo)| todo_item_view(i, todo))
        .collect();
    
    flex_col(items)
}

Async Operations

Using task for One-shot Operations

rust
use xilem::view::task;

fn data_loader(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    flex_col((
        if state.loading {
            Either::A(spinner())
        } else {
            Either::B(data_display(&state.data))
        },
        task(
            |proxy| async move {
                let data = fetch_data().await;
                proxy.send_message(LoadComplete(data));
            },
            |state: &mut AppState, msg: LoadComplete| {
                state.data = msg.0;
                state.loading = false;
            },
        ),
    ))
}

Using worker for Continuous Processing

rust
use xilem::view::worker;

fn search_view(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    flex_col((
        text_input(state.query.clone(), |s, v| s.query = v),
        worker(
            state.query.clone(),
            |query| async move {
                search_api(&query).await
            },
            |state: &mut AppState, results| {
                state.results = results;
            },
        ),
        results_list(&state.results),
    ))
}

Conditional Rendering

Using Option

rust
fn maybe_error(error: &Option<String>) -> Option<impl WidgetView<Edit<AppState>>> {
    error.as_ref().map(|e| label(format!("Error: {}", e)))
}

// In flex_col, Option<impl View> is valid
flex_col((
    main_content(),
    maybe_error(&state.error),
))

Using Either/OneOf

rust
use xilem::core::one_of::Either;

fn conditional_view(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    if state.logged_in {
        Either::A(dashboard(state))
    } else {
        Either::B(login_form(state))
    }
}

Performance Optimization

Memoization

rust
use xilem::core::memoize;

fn expensive_view(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    flex_col((
        // Only rebuilds when data changes
        memoize(state.data.clone(), |data| {
            expensive_computation_view(data)
        }),
        // Always rebuilds
        dynamic_controls(state),
    ))
}

Frozen for Static Content

rust
use xilem::core::frozen;

fn app_logic(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    flex_col((
        // Never rebuilds
        frozen(|| header()),
        // Dynamic content
        main_content(state),
        frozen(|| footer()),
    ))
}

Error Handling

rust
fn fallible_view(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    match &state.result {
        Ok(data) => Either::A(data_view(data)),
        Err(e) => Either::B(error_view(e)),
    }
}

Form Patterns

rust
struct FormState {
    name: String,
    email: String,
    errors: HashMap<String, String>,
}

fn form_view(form: &mut FormState) -> impl WidgetView<Edit<FormState>> + use<> {
    flex_col((
        text_input(form.name.clone(), |f, v| f.name = v)
            .placeholder("Name"),
        form.errors.get("name").map(|e| label(e.clone())),
        
        text_input(form.email.clone(), |f, v| f.email = v)
            .placeholder("Email"),
        form.errors.get("email").map(|e| label(e.clone())),
        
        text_button("Submit", |f: &mut FormState| {
            f.errors.clear();
            if f.name.is_empty() {
                f.errors.insert("name".into(), "Name required".into());
            }
            if !f.email.contains('@') {
                f.errors.insert("email".into(), "Invalid email".into());
            }
        }),
    ))
}

Multiple Windows

rust
use xilem::{window, WindowOptions};

fn app_logic(state: &mut AppState) -> impl WidgetView<Edit<AppState>> + use<> {
    (
        window(main_window_content(state), WindowOptions::new("Main")),
        state.show_settings.then(|| {
            window(settings_content(state), WindowOptions::new("Settings"))
        }),
    )
}