约定优于配置 #
一、什么是约定优于配置 #
“约定优于配置”(Convention over Configuration)是Ember.js的核心设计理念,源自Ruby on Rails框架。它的核心思想是:
- 框架提供合理的默认约定
- 开发者遵循约定即可快速开发
- 只在必要时才进行配置
- 减少决策,提高效率
1.1 与其他框架对比 #
| 框架 | 理念 | 特点 |
|---|---|---|
| Ember.js | 约定优于配置 | 开箱即用,决策少 |
| React | 配置优先 | 灵活,需选择方案 |
| Vue.js | 渐进式 | 可选约定,灵活配置 |
| Angular | 约定+配置 | 有约定但可配置 |
二、Ember的核心约定 #
2.1 文件命名约定 #
Ember使用一致的文件命名规则:
text
类型 文件名 类名
─────────────────────────────────────────────
Route posts.js PostsRoute
Controller posts.js PostsController
Component user-card.js UserCardComponent
Model post.js PostModel
Service session.js SessionService
Helper format-date.js formatDate
Modifier autofocus.js autofocus
2.2 目录结构约定 #
text
app/
├── routes/ # 路由文件
│ └── posts.js
├── controllers/ # 控制器文件
│ └── posts.js
├── templates/ # 模板文件
│ └── posts.hbs
├── components/ # 组件文件
│ ├── user-card.js
│ └── user-card.hbs
├── models/ # 模型文件
│ └── post.js
└── services/ # 服务文件
└── session.js
2.3 URL与路由约定 #
javascript
// app/router.js
Router.map(function () {
this.route('posts');
this.route('posts', function () {
this.route('new');
this.route('edit', { path: '/:post_id/edit' });
});
});
| 路由配置 | URL | 模板文件 |
|---|---|---|
this.route('posts') |
/posts |
templates/posts.hbs |
this.route('new') (嵌套) |
/posts/new |
templates/posts/new.hbs |
this.route('edit', {path: '/:id/edit'}) |
/posts/123/edit |
templates/posts/edit.hbs |
三、自动解析机制 #
3.1 路由自动解析 #
当用户访问 /posts 时,Ember自动:
text
1. 查找 app/routes/posts.js
2. 实例化 PostsRoute
3. 调用 model() 加载数据
4. 查找 app/controllers/posts.js
5. 查找 app/templates/posts.hbs
6. 渲染模板
3.2 组件自动解析 #
使用 <UserCard /> 组件时,Ember自动查找:
text
app/components/user-card.js
app/components/user-card.hbs
或
app/components/user-card/index.js
app/components/user-card/index.hbs
3.3 模型自动解析 #
在路由中使用 this.store.findRecord('post', 1) 时:
text
Ember 自动查找:
app/models/post.js
app/adapters/post.js (或 application.js)
app/serializers/post.js (或 application.js)
四、减少配置的场景 #
4.1 路由配置 #
其他框架可能需要:
javascript
// 需要手动配置路由、控制器、模板的关联
const routes = [
{
path: '/posts',
component: PostsComponent,
controller: PostsController,
template: 'posts.html',
},
];
Ember只需:
javascript
// 只需定义路由
Router.map(function () {
this.route('posts');
});
// 其他都由约定自动处理
4.2 组件注册 #
其他框架可能需要:
javascript
// 需要手动注册组件
import UserCard from './components/UserCard.vue';
app.component('UserCard', UserCard);
Ember自动处理:
handlebars
{{! 直接使用,无需注册}}
<UserCard @user={{@currentUser}} />
4.3 服务注入 #
其他框架可能需要:
javascript
// 需要手动配置依赖注入
import { Container } from 'some-di-library';
const container = new Container();
container.register('session', SessionService);
Ember自动处理:
javascript
// 自动注入,无需配置
import { inject as service } from '@ember/service';
export default class MyComponent extends Component {
@service session;
}
五、约定的好处 #
5.1 提高开发效率 #
bash
# 一条命令生成完整功能
ember generate resource post
# 自动创建:
# - app/models/post.js
# - app/routes/posts.js
# - app/templates/posts.hbs
# - tests/...
5.2 代码一致性 #
javascript
// 所有项目结构一致,新成员快速上手
// 项目A
app/routes/posts.js
app/controllers/posts.js
app/templates/posts.hbs
// 项目B(相同结构)
app/routes/posts.js
app/controllers/posts.js
app/templates/posts.hbs
5.3 减少决策 #
| 决策 | 其他框架 | Ember |
|---|---|---|
| 目录结构 | 需要设计 | 约定好 |
| 文件命名 | 需要决定 | 约定好 |
| 路由配置 | 需要配置 | 约定好 |
| 状态管理 | 需要选择 | 内置 |
| 数据层 | 需要选择 | 内置 |
5.4 易于维护 #
javascript
// 看到URL就知道文件位置
// URL: /posts/123/edit
// 路由: app/routes/posts/edit.js
// 模板: app/templates/posts/edit.hbs
// 模型: app/models/post.js
六、何时需要配置 #
虽然Ember强调约定,但配置仍然灵活:
6.1 自定义适配器 #
javascript
// app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
export default class ApplicationAdapter extends RESTAdapter {
namespace = 'api/v1';
host = 'https://api.example.com';
buildURL(...args) {
return super.buildURL(...args) + '.json';
}
}
6.2 自定义序列化器 #
javascript
// app/serializers/application.js
import RESTSerializer from '@ember-data/serializer/rest';
export default class ApplicationSerializer extends RESTSerializer {
normalizeResponse(store, primaryModelClass, payload) {
return super.normalizeResponse(...arguments);
}
}
6.3 自定义路由行为 #
javascript
// app/routes/posts.js
import Route from '@ember/routing/route';
export default class PostsRoute extends Route {
// 自定义模型加载
async model() {
const response = await fetch('/custom-api/posts');
return response.json();
}
// 自定义渲染
renderTemplate() {
this.render('posts/list');
}
}
七、CLI与约定 #
Ember CLI是约定的最佳实践工具:
7.1 代码生成 #
bash
# 生成路由
ember generate route about
# 创建: app/routes/about.js
# 创建: app/templates/about.hbs
# 更新: app/router.js
# 生成组件
ember generate component user-card
# 创建: app/components/user-card.js
# 创建: app/components/user-card.hbs
# 创建: tests/integration/components/user-card-test.js
# 生成模型
ember generate model post title:string body:text
# 创建: app/models/post.js
# 创建: tests/unit/models/post-test.js
# 生成完整资源
ember generate resource comment
# 创建: 路由、模板、模型、测试
7.2 蓝图自定义 #
可以自定义生成模板:
javascript
// blueprints/component/files/app/components/__name__.js
import Component from '@glimmer/component';
export default class <%= classifiedModuleName %>Component extends Component {
// 自定义模板内容
}
八、约定最佳实践 #
8.1 遵循命名规范 #
javascript
// 好的命名
app/routes/user-profile.js // UserProfileRoute
app/components/user-avatar.js // UserAvatarComponent
app/models/blog-post.js // BlogPostModel
// 避免的命名
app/routes/userProfile.js // 不推荐驼峰
app/components/useravatar.js // 不推荐无分隔
app/models/blog_post.js // 不推荐下划线
8.2 保持结构一致 #
text
// 推荐的结构
app/
├── routes/
│ ├── posts/
│ │ ├── index.js
│ │ ├── new.js
│ │ └── edit.js
│ └── posts.js
├── templates/
│ ├── posts/
│ │ ├── index.hbs
│ │ ├── new.hbs
│ │ └── edit.hbs
│ └── posts.hbs
8.3 利用CLI生成 #
bash
// 使用CLI而不是手动创建
ember generate route posts/new # 正确
// 手动创建文件 # 不推荐
九、约定与灵活性 #
Ember的约定并不限制灵活性:
9.1 可以覆盖约定 #
javascript
// 自定义模板渲染位置
export default class PostsRoute extends Route {
renderTemplate() {
this.render('posts/custom-template');
}
}
// 自定义控制器
export default class PostsRoute extends Route {
setupController(controller, model) {
super.setupController(controller, model);
// 自定义逻辑
}
}
9.2 可以扩展约定 #
javascript
// 扩展基础适配器
import ApplicationAdapter from './application';
export default class PostAdapter extends ApplicationAdapter {
pathForType() {
return 'blog-posts';
}
}
十、总结 #
"约定优于配置"带来的好处:
| 好处 | 说明 |
|---|---|
| 效率 | 减少决策,快速开发 |
| 一致性 | 团队代码风格统一 |
| 可维护 | 结构清晰,易于理解 |
| 可扩展 | 约定可覆盖,灵活配置 |
掌握Ember的约定是高效开发的关键。遵循约定,让框架为你工作,而不是你为框架工作。
最后更新:2026-03-28