Ember组件参数与签名 #

一、组件参数(Args) #

1.1 参数基础 #

组件通过 this.args 访问传入的参数:

javascript
// app/components/user-card.js
import Component from '@glimmer/component';

export default class UserCardComponent extends Component {
  get displayName() {
    return this.args.user?.name || '匿名用户';
  }
}
handlebars
{{! 使用组件}}
<UserCard @user={{@currentUser}} />

1.2 参数特点 #

  • 只读:参数不能在组件内部修改
  • 响应式:参数变化时组件自动更新
  • 可选:参数可以不传递
javascript
export default class ButtonComponent extends Component {
  get variant() {
    // 参数有默认值
    return this.args.variant || 'primary';
  }

  get disabled() {
    // 参数可选
    return this.args.disabled ?? false;
  }
}

二、组件签名(Signature) #

2.1 定义签名 #

签名用于定义组件的接口类型:

typescript
// app/components/user-card.ts
import Component from '@glimmer/component';

interface UserCardSignature {
  // 允许的参数
  Args: {
    user: User;
    size?: 'small' | 'medium' | 'large';
    showEmail?: boolean;
    onEdit?: (user: User) => void;
  };
  // 产出块参数
  Blocks: {
    default: [user: User];
  };
  // 允许的HTML属性
  Element: HTMLDivElement;
}

export default class UserCardComponent extends Component<UserCardSignature> {
  // 组件实现
}

2.2 签名组成部分 #

typescript
interface ComponentSignature {
  // Args: 组件参数类型
  Args: {
    [key: string]: any;
  };

  // Blocks: 产出块参数类型
  Blocks: {
    default?: [...any];
    [blockName: string]: [...any] | undefined;
  };

  // Element: 根元素类型
  Element: HTMLElement | null;
}

2.3 完整签名示例 #

typescript
// app/components/modal.ts
import Component from '@glimmer/component';

interface ModalSignature {
  Args: {
    isOpen: boolean;
    title?: string;
    size?: 'small' | 'medium' | 'large';
    onClose: () => void;
    onConfirm?: () => void;
  };
  Blocks: {
    default: [];
    header: [];
    body: [];
    footer: [];
  };
  Element: HTMLDivElement;
}

export default class ModalComponent extends Component<ModalSignature> {
  get modalClass() {
    const classes = ['modal'];
    if (this.args.size) {
      classes.push(`modal-${this.args.size}`);
    }
    if (this.args.isOpen) {
      classes.push('modal-open');
    }
    return classes.join(' ');
  }
}

三、参数类型 #

3.1 基本类型 #

typescript
interface MyComponentSignature {
  Args: {
    // 字符串
    title: string;
    // 数字
    count: number;
    // 布尔值
    isActive: boolean;
    // 可选参数
    description?: string;
  };
}

3.2 对象类型 #

typescript
interface UserCardSignature {
  Args: {
    user: {
      id: string;
      name: string;
      email: string;
      avatar?: string;
    };
  };
}

3.3 函数类型 #

typescript
interface ButtonSignature {
  Args: {
    onClick: (event: MouseEvent) => void;
    onHover?: (isHovering: boolean) => void;
  };
}

3.4 联合类型 #

typescript
interface AlertSignature {
  Args: {
    type: 'success' | 'warning' | 'error' | 'info';
    size?: 'small' | 'medium' | 'large';
  };
}

四、块参数 #

4.1 定义块参数 #

typescript
interface ListSignature {
  Args: {
    items: T[];
  };
  Blocks: {
    // 默认块产出 item 和 index
    default: [item: T, index: number];
  };
}

export default class ListComponent extends Component<ListSignature<T>> {
  <ul>
    {{#each @items as |item index|}}
      <li>{{yield item index}}</li>
    {{/each}}
  </ul>
}

4.2 多个块 #

typescript
interface CardSignature {
  Blocks: {
    default: [];
    header: [];
    footer: [];
  };
}
handlebars
{{! app/components/card.hbs}}
<div class="card">
  <div class="card-header">
    {{yield to="header"}}
  </div>
  <div class="card-body">
    {{yield}}
  </div>
  <div class="card-footer">
    {{yield to="footer"}}
  </div>
</div>
handlebars
{{! 使用}}
<Card>
  <:header>
    <h2>标题</h2>
  </:header>
  
  <p>内容</p>
  
  <:footer>
    <button>操作</button>
  </:footer>
</Card>

五、元素类型 #

5.1 指定根元素 #

typescript
interface ButtonSignature {
  Element: HTMLButtonElement;
  Args: {
    type?: 'button' | 'submit' | 'reset';
    disabled?: boolean;
  };
}
handlebars
{{! app/components/button.hbs}}
<button
  type={{or @type "button"}}
  disabled={{@disabled}}
  ...attributes
>
  {{yield}}
</button>

5.2 splat属性 #

使用 ...attributes 将HTML属性传递给根元素:

handlebars
{{! app/components/button.hbs}}
<button
  type={{or @type "button"}}
  ...attributes
>
  {{yield}}
</button>
handlebars
{{! 使用 - 传递额外的HTML属性}}
<Button
  type="submit"
  class="btn-primary"
  id="submit-btn"
  data-testid="submit-button"
>
  提交
</Button>

5.3 无根元素组件 #

typescript
interface TextSignature {
  Element: null; // 无根元素
  Args: {
    text: string;
  };
}

六、参数验证 #

6.1 运行时验证 #

javascript
import Component from '@glimmer/component';
import { assert } from '@ember/debug';

export default class UserCardComponent extends Component {
  constructor(owner, args) {
    super(owner, args);

    assert('user parameter is required', this.args.user);
    assert('user must have a name', this.args.user.name);
  }
}

6.2 默认值处理 #

javascript
export default class AlertComponent extends Component {
  get type() {
    return this.args.type || 'info';
  }

  get dismissible() {
    return this.args.dismissible ?? true;
  }

  get timeout() {
    return this.args.timeout ?? 5000;
  }
}

七、参数最佳实践 #

7.1 参数命名约定 #

handlebars
{{! 好的命名}}
<UserCard
  @user={{@user}}
  @size="large"
  @showEmail={{true}}
  @onEdit={{this.handleEdit}}
/>

{{! 避免}}
<UserCard
  @u={{@user}}
  @s="large"
  @show={{true}}
  @edit={{this.handleEdit}}
/>

7.2 布尔参数 #

handlebars
{{! 推荐方式}}
<Input @disabled={{true}} />
<Input @disabled={{false}} />

{{! 简写方式(仅适用于true)}}
<Input @disabled />

7.3 回调参数命名 #

handlebars
{{! 事件回调使用 on 前缀}}
<Button @onClick={{this.handleClick}} />
<Input @onChange={{this.handleChange}} />

{{! 动作回调使用 on 前缀}}
<Modal @onClose={{this.closeModal}} @onConfirm={{this.confirmAction}} />

7.4 可选参数文档 #

typescript
/**
 * UserCard 组件
 * 
 * @param user - 用户对象(必需)
 * @param size - 卡片大小:small | medium | large(可选,默认 medium)
 * @param showEmail - 是否显示邮箱(可选,默认 false)
 * @param onEdit - 编辑回调函数(可选)
 */
interface UserCardSignature {
  Args: {
    user: User;
    size?: 'small' | 'medium' | 'large';
    showEmail?: boolean;
    onEdit?: (user: User) => void;
  };
}

八、复杂参数场景 #

8.1 配置对象 #

typescript
interface DataTableSignature {
  Args: {
    data: any[];
    columns: ColumnConfig[];
    config?: {
      sortable?: boolean;
      filterable?: boolean;
      pagination?: boolean;
      pageSize?: number;
    };
  };
}

interface ColumnConfig {
  key: string;
  label: string;
  sortable?: boolean;
  render?: (value: any, row: any) => string;
}
handlebars
<DataTable
  @data={{@users}}
  @columns={{this.columns}}
  @config={{hash
    sortable=true
    pagination=true
    pageSize=10
  }}
/>

8.2 泛型组件 #

typescript
interface ListSignature<T> {
  Args: {
    items: T[];
    renderItem?: (item: T) => string;
  };
  Blocks: {
    default: [item: T, index: number];
  };
}

export default class ListComponent<T> extends Component<ListSignature<T>> {
}

九、TypeScript集成 #

9.1 类型安全的组件 #

typescript
// app/components/user-form.ts
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

interface User {
  id: string;
  name: string;
  email: string;
}

interface UserFormSignature {
  Args: {
    user?: User;
    onSubmit: (user: Partial<User>) => void;
    onCancel: () => void;
  };
}

export default class UserFormComponent extends Component<UserFormSignature> {
  @tracked name = this.args.user?.name ?? '';
  @tracked email = this.args.user?.email ?? '';

  @action
  handleSubmit(event: Event) {
    event.preventDefault();
    this.args.onSubmit({
      name: this.name,
      email: this.email,
    });
  }

  @action
  updateName(event: Event) {
    this.name = (event.target as HTMLInputElement).value;
  }

  @action
  updateEmail(event: Event) {
    this.email = (event.target as HTMLInputElement).value;
  }
}

9.2 类型检查 #

handlebars
{{! TypeScript会检查参数类型}}
<UserForm
  @user={{@currentUser}}
  @onSubmit={{this.handleSubmit}}
  @onCancel={{this.handleCancel}}
/>

{{! 错误示例 - 类型不匹配}}
<UserForm
  @user={{123}}
  {{! Error: Type 'number' is not assignable to type 'User | undefined'}}
/>

十、总结 #

组件参数与签名要点:

概念 说明
Args 组件参数定义
Blocks 产出块参数定义
Element 根元素类型
…attributes HTML属性透传
@tracked 状态追踪

良好的组件接口设计是构建可维护应用的基础。

最后更新:2026-03-28