Ember Helper助手 #
一、Helper概述 #
Helper是Ember中用于模板数据转换的函数。它们接收参数,处理后返回结果,非常适合用于:
- 数据格式化
- 数据转换
- 条件判断
- 计算逻辑
1.1 生成Helper #
bash
ember generate helper format-date
生成文件:
text
app/helpers/format-date.js
tests/unit/helpers/format-date-test.js
二、基本Helper #
2.1 简单Helper #
javascript
// app/helpers/format-date.js
import { helper } from '@ember/component/helper';
export default helper(function formatDate([date]) {
if (!date) return '';
const d = new Date(date);
return d.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
});
handlebars
{{! 使用Helper}}
<p>发布于:{{format-date @post.createdAt}}</p>
2.2 带参数的Helper #
javascript
// app/helpers/format-date.js
import { helper } from '@ember/component/helper';
export default helper(function formatDate([date], { format }) {
if (!date) return '';
const d = new Date(date);
switch (format) {
case 'short':
return d.toLocaleDateString();
case 'long':
return d.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
});
case 'relative':
return getRelativeTime(d);
default:
return d.toLocaleDateString();
}
});
function getRelativeTime(date) {
const now = new Date();
const diff = now - date;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days}天前`;
if (hours > 0) return `${hours}小时前`;
if (minutes > 0) return `${minutes}分钟前`;
return '刚刚';
}
handlebars
{{! 默认格式}}
{{format-date @post.createdAt}}
{{! 短格式}}
{{format-date @post.createdAt format="short"}}
{{! 长格式}}
{{format-date @post.createdAt format="long"}}
{{! 相对时间}}
{{format-date @post.createdAt format="relative"}}
三、常用Helper示例 #
3.1 字符串处理 #
javascript
// app/helpers/truncate.js
import { helper } from '@ember/component/helper';
export default helper(function truncate([text], { length = 100, suffix = '...' }) {
if (!text) return '';
if (text.length <= length) return text;
return text.substring(0, length) + suffix;
});
handlebars
<p>{{truncate @post.body length=200}}</p>
3.2 数字格式化 #
javascript
// app/helpers/format-number.js
import { helper } from '@ember/component/helper';
export default helper(function formatNumber([number], { locale = 'zh-CN', style = 'decimal' }) {
if (number === null || number === undefined) return '';
return new Intl.NumberFormat(locale, { style }).format(number);
});
handlebars
{{! 普通数字}}
{{format-number 1234567}}
{{! 货币格式}}
{{format-number @price style="currency"}}
3.3 货币格式化 #
javascript
// app/helpers/format-currency.js
import { helper } from '@ember/component/helper';
export default helper(function formatCurrency([amount], { currency = 'CNY', locale = 'zh-CN' }) {
if (amount === null || amount === undefined) return '';
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
}).format(amount);
});
handlebars
{{! 人民币}}
{{format-currency @price}}
{{! 美元}}
{{format-currency @price currency="USD"}}
3.4 条件Helper #
javascript
// app/helpers/eq.js
import { helper } from '@ember/component/helper';
export default helper(function eq([a, b]) {
return a === b;
});
handlebars
{{#if (eq @user.role "admin")}}
<p>管理员</p>
{{/if}}
3.5 逻辑Helper #
javascript
// app/helpers/and.js
import { helper } from '@ember/component/helper';
export default helper(function and([...args]) {
return args.every(Boolean);
});
// app/helpers/or.js
import { helper } from '@ember/component/helper';
export default helper or([...args]) {
return args.some(Boolean);
});
// app/helpers/not.js
import { helper } from '@ember/component/helper';
export default helper(function not([value]) {
return !value;
});
handlebars
{{#if (and @user.isLoggedIn @user.hasPermission)}}
<p>可以访问</p>
{{/if}}
{{#if (or @user.isAdmin @user.isEditor)}}
<p>管理权限</p>
{{/if}}
{{#if (not @user.isBanned)}}
<p>正常用户</p>
{{/if}}
3.6 数学Helper #
javascript
// app/helpers/math.js
import { helper } from '@ember/component/helper';
export const add = helper(([a, b]) => a + b);
export const sub = helper(([a, b]) => a - b);
export const mul = helper(([a, b]) => a * b);
export const div = helper(([a, b]) => a / b);
export const mod = helper(([a, b]) => a % b);
handlebars
<p>总计:{{add @price @tax}}</p>
<p>折扣:{{mul @price 0.9}}</p>
四、响应式Helper #
4.1 Class-based Helper #
当需要响应数据变化时,使用Class-based Helper:
javascript
// app/helpers/time-ago.js
import Helper from '@ember/component/helper';
import { tracked } from '@glimmer/tracking';
export default class TimeAgoHelper extends Helper {
@tracked interval;
compute([date]) {
this.clearInterval();
this.interval = setInterval(() => {
this.recompute();
}, 60000);
return this.formatTimeAgo(date);
}
formatTimeAgo(date) {
const now = new Date();
const diff = now - new Date(date);
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return '刚刚';
if (minutes < 60) return `${minutes}分钟前`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}小时前`;
const days = Math.floor(hours / 24);
return `${days}天前`;
}
clearInterval() {
if (this.interval) {
clearInterval(this.interval);
}
}
willDestroy() {
this.clearInterval();
super.willDestroy();
}
}
handlebars
<span>{{time-ago @comment.createdAt}}</span>
4.2 带服务的Helper #
javascript
// app/helpers/current-user.js
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
export default class CurrentUserHelper extends Helper {
@service session;
compute([property]) {
const user = this.session.currentUser;
if (!user) return null;
return user[property];
}
}
handlebars
<p>欢迎,{{current-user "name"}}</p>
五、Helper组合 #
5.1 嵌套使用 #
handlebars
{{! Helper嵌套}}
<p>{{truncate (format-date @post.createdAt format="long") length=20}}</p>
{{! 多层嵌套}}
<p>{{format-currency (mul @price 0.9)}}</p>
5.2 与条件组合 #
handlebars
{{#if (gt (format-number @stock) 0)}}
<span class="in-stock">有货</span>
{{else}}
<span class="out-of-stock">缺货</span>
{{/if}}
六、Helper最佳实践 #
6.1 纯函数原则 #
javascript
// 好的做法 - 纯函数
export default helper(function upper([str]) {
return str?.toUpperCase() ?? '';
});
// 避免 - 副作用
export default helper(function log([message]) {
console.log(message); // 副作用
return message;
});
6.2 参数验证 #
javascript
export default helper(function formatDate([date], options) {
if (!date) return '';
if (!(date instanceof Date)) {
date = new Date(date);
}
if (isNaN(date.getTime())) {
return '无效日期';
}
return date.toLocaleDateString();
});
6.3 默认值处理 #
javascript
export default helper(function truncate([text], { length = 100, suffix = '...' }) {
if (!text) return '';
if (text.length <= length) return text;
return text.substring(0, length) + suffix;
});
七、测试Helper #
7.1 单元测试 #
javascript
// tests/unit/helpers/format-date-test.js
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import formatDate from 'my-app/helpers/format-date';
module('Unit | Helper | format-date', function (hooks) {
setupTest(hooks);
test('it formats date correctly', function (assert) {
const date = new Date('2024-01-15');
const result = formatDate([date]);
assert.ok(result.includes('2024'));
});
test('it handles empty values', function (assert) {
assert.strictEqual(formatDate([null]), '');
assert.strictEqual(formatDate([undefined]), '');
});
test('it supports different formats', function (assert) {
const date = new Date('2024-01-15');
const shortResult = formatDate([date], { format: 'short' });
assert.ok(shortResult);
const longResult = formatDate([date], { format: 'long' });
assert.ok(longResult.includes('2024'));
});
});
7.2 集成测试 #
javascript
// tests/integration/helpers/format-date-test.js
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | format-date', function (hooks) {
setupRenderingTest(hooks);
test('it renders formatted date', async function (assert) {
this.set('date', new Date('2024-01-15'));
await render(hbs`{{format-date this.date}}`);
assert.dom(this.element).hasText(/2024/);
});
});
八、常用Helper集合 #
8.1 字符串Helper #
javascript
// app/helpers/upper.js
export default helper(([str]) => str?.toUpperCase() ?? '');
// app/helpers/lower.js
export default helper(([str]) => str?.toLowerCase() ?? '');
// app/helpers/capitalize.js
export default helper(([str]) => {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1);
});
8.2 数组Helper #
javascript
// app/helpers/includes.js
export default helper(([array, value]) => {
return Array.isArray(array) && array.includes(value);
});
// app/helpers/first.js
export default helper(([array]) => array?.[0]);
// app/helpers/last.js
export default helper(([array]) => array?.[array.length - 1]);
8.3 对象Helper #
javascript
// app/helpers/pick.js
export default helper(([object, key]) => object?.[key]);
// app/helpers/keys.js
export default helper(([object]) => Object.keys(object || {}));
// app/helpers/values.js
export default helper(([object]) => Object.values(object || {}));
九、总结 #
Helper是Ember模板中强大的数据处理工具:
| 类型 | 用途 | 示例 |
|---|---|---|
| 简单Helper | 数据转换 | {{format-date date}} |
| 带参数Helper | 可配置转换 | {{format-date date format="long"}} |
| Class-based | 响应式更新 | {{time-ago timestamp}} |
合理使用Helper可以让模板更加简洁,逻辑更加清晰。
最后更新:2026-03-28