第一个Ember应用 #
一、项目概述 #
我们将创建一个简单的待办事项(Todo)应用,通过这个项目学习Ember.js的核心概念:
- 创建和显示待办事项
- 标记完成状态
- 删除待办事项
- 数据过滤
二、创建项目 #
2.1 初始化项目 #
bash
# 创建新项目
ember new todo-app
# 进入项目目录
cd todo-app
# 启动开发服务器
ember serve
访问 http://localhost:4200,你将看到Ember的欢迎页面。
2.2 项目结构 #
text
todo-app/
├── app/
│ ├── app.js
│ ├── index.html
│ ├── router.js
│ ├── routes/
│ │ └── application.js
│ ├── templates/
│ │ └── application.hbs
│ └── styles/
│ └── app.css
├── config/
│ └── environment.js
├── tests/
├── package.json
└── ember-cli-build.js
三、创建路由 #
3.1 配置路由 #
修改 app/router.js:
javascript
import EmberRouter from '@ember/routing/router';
import config from 'todo-app/config/environment';
export default class Router extends EmberRouter {
location = config.locationType;
rootURL = config.rootURL;
}
Router.map(function () {
this.route('todos', { path: '/' });
this.route('about');
});
3.2 生成路由文件 #
bash
# 生成todos路由
ember generate route todos
# 生成about路由
ember generate route about
3.3 路由文件 #
app/routes/todos.js:
javascript
import Route from '@ember/routing/route';
export default class TodosRoute extends Route {
model() {
return [
{ title: '学习Ember.js', completed: false, id: 1 },
{ title: '创建第一个应用', completed: true, id: 2 },
{ title: '部署到生产环境', completed: false, id: 3 },
];
}
}
四、创建模板 #
4.1 应用模板 #
修改 app/templates/application.hbs:
handlebars
<header class="header">
<h1>Todo App</h1>
<nav>
<LinkTo @route="todos">首页</LinkTo>
<LinkTo @route="about">关于</LinkTo>
</nav>
</header>
<main class="main">
{{outlet}}
</main>
<footer class="footer">
<p>使用Ember.js构建</p>
</footer>
4.2 Todos模板 #
修改 app/templates/todos.hbs:
handlebars
<section class="todoapp">
<header class="todo-header">
<h2>待办事项</h2>
</header>
<section class="todo-list">
<ul>
{{#each @model as |todo|}}
<li class="{{if todo.completed 'completed'}}">
<div class="view">
<input type="checkbox" checked={{todo.completed}} />
<label>{{todo.title}}</label>
<button class="destroy">删除</button>
</div>
</li>
{{/each}}
</ul>
</section>
<footer class="todo-footer">
<span class="todo-count">
剩余 {{this.remainingCount}} 项
</span>
</footer>
</section>
4.3 About模板 #
修改 app/templates/about.hbs:
handlebars
<div class="about">
<h2>关于本应用</h2>
<p>这是一个使用Ember.js构建的待办事项应用示例。</p>
<p>功能包括:</p>
<ul>
<li>添加待办事项</li>
<li>标记完成状态</li>
<li>删除待办事项</li>
<li>过滤显示</li>
</ul>
<LinkTo @route="todos">返回首页</LinkTo>
</div>
五、添加样式 #
修改 app/styles/app.css:
css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
min-height: 100vh;
}
.header {
background: #667eea;
color: white;
padding: 20px;
text-align: center;
}
.header nav a {
color: white;
margin: 0 15px;
text-decoration: none;
}
.header nav a:hover {
text-decoration: underline;
}
.main {
max-width: 600px;
margin: 30px auto;
padding: 0 20px;
}
.todoapp {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.todo-header {
padding: 20px;
border-bottom: 1px solid #eee;
}
.todo-header h2 {
color: #333;
}
.todo-list ul {
list-style: none;
}
.todo-list li {
padding: 15px 20px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
}
.todo-list li.completed label {
text-decoration: line-through;
color: #999;
}
.todo-list .view {
display: flex;
align-items: center;
width: 100%;
}
.todo-list input[type='checkbox'] {
margin-right: 15px;
width: 20px;
height: 20px;
}
.todo-list label {
flex: 1;
font-size: 16px;
}
.todo-list .destroy {
background: #ff6b6b;
color: white;
border: none;
padding: 5px 15px;
border-radius: 4px;
cursor: pointer;
}
.todo-list .destroy:hover {
background: #ee5a5a;
}
.todo-footer {
padding: 15px 20px;
background: #f9f9f9;
color: #666;
}
.footer {
text-align: center;
padding: 30px;
color: #999;
}
.about {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.about h2 {
margin-bottom: 20px;
color: #333;
}
.about ul {
margin: 15px 0 20px 20px;
}
.about li {
margin: 5px 0;
}
六、创建组件 #
6.1 生成组件 #
bash
# 生成单个待办事项组件
ember generate component todo-item
# 生成添加待办事项组件
ember generate component todo-input
6.2 TodoItem组件 #
app/components/todo-item.hbs:
handlebars
<li class="{{if @todo.completed 'completed'}}">
<div class="view">
<input
type="checkbox"
checked={{@todo.completed}}
{{on "change" (fn this.toggleComplete @todo)}}
/>
<label>{{@todo.title}}</label>
<button
class="destroy"
type="button"
{{on "click" (fn this.deleteTodo @todo)}}
>
删除
</button>
</div>
</li>
app/components/todo-item.js:
javascript
import Component from '@glimmer/component';
import { action } from '@ember/object';
export default class TodoItemComponent extends Component {
@action
toggleComplete(todo) {
todo.completed = !todo.completed;
}
@action
deleteTodo(todo) {
if (this.args.onDelete) {
this.args.onDelete(todo);
}
}
}
6.3 TodoInput组件 #
app/components/todo-input.hbs:
handlebars
<form class="todo-input" {{on "submit" this.addTodo}}>
<input
type="text"
placeholder="添加新的待办事项..."
value={{this.newTitle}}
{{on "input" this.updateTitle}}
/>
<button type="submit">添加</button>
</form>
app/components/todo-input.js:
javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class TodoInputComponent extends Component {
@tracked newTitle = '';
@action
updateTitle(event) {
this.newTitle = event.target.value;
}
@action
addTodo(event) {
event.preventDefault();
if (this.newTitle.trim()) {
if (this.args.onAdd) {
this.args.onAdd(this.newTitle);
}
this.newTitle = '';
}
}
}
七、更新主模板 #
7.1 更新Todos模板 #
修改 app/templates/todos.hbs:
handlebars
<section class="todoapp">
<header class="todo-header">
<h2>待办事项</h2>
<TodoInput @onAdd={{this.addTodo}} />
</header>
<section class="todo-list">
<ul>
{{#each @model as |todo|}}
<TodoItem @todo={{todo}} @onDelete={{this.deleteTodo}} />
{{/each}}
</ul>
</section>
<footer class="todo-footer">
<span class="todo-count">剩余 {{this.remainingCount}} 项</span>
</footer>
</section>
7.2 创建控制器 #
bash
ember generate controller todos
app/controllers/todos.js:
javascript
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class TodosController extends Controller {
@tracked nextId = 4;
get remainingCount() {
return this.model.filter((todo) => !todo.completed).length;
}
@action
addTodo(title) {
this.model.pushObject({
id: this.nextId++,
title: title,
completed: false,
});
}
@action
deleteTodo(todo) {
this.model.removeObject(todo);
}
}
八、运行项目 #
8.1 启动服务器 #
bash
ember serve
8.2 访问应用 #
打开浏览器访问 http://localhost:4200。
8.3 功能测试 #
- 查看待办事项列表
- 添加新的待办事项
- 勾选完成待办事项
- 删除待办事项
- 导航到关于页面
九、项目总结 #
通过这个简单的待办事项应用,我们学习了:
9.1 核心概念 #
| 概念 | 说明 |
|---|---|
| Router | 定义URL与应用状态的映射 |
| Route | 加载数据并渲染模板 |
| Template | 使用Handlebars语法渲染视图 |
| Component | 可复用的UI组件 |
| Controller | 处理用户交互逻辑 |
9.2 Ember约定 #
- 路由文件位于
app/routes/ - 模板文件位于
app/templates/ - 组件文件位于
app/components/ - 控制器文件位于
app/controllers/
9.3 下一步学习 #
- 使用Ember Data持久化数据
- 添加更多功能(过滤、编辑)
- 编写测试用例
- 部署到生产环境
恭喜你完成了第一个Ember应用!接下来让我们深入了解Ember的项目结构。
最后更新:2026-03-28