如果说有些前端框架像给你一块地、一批钢筋、再加一本“你想怎么盖都行”的施工手册,那 Ember.js 就完全不是这条路子。
Ember 更像一座已经有完整城市总规的“约定秩序城区”。
这座城区里:
- 路怎么修,先看总路网。
- 楼怎么盖,先看地块编号。
- 市政大厅、档案馆、公交系统、审批流程,默认都给你配好。
- 你当然可以改造,但默认方案本身就是为了让一座城市能稳定运转很多年。
这就是 Ember 存在的原因。
它不是为了让你“随便拼一个页面”而生的。它是为了让一个大型、长期、多人协作、业务流程复杂的 Web 应用,从第一天起就像一座按规划建成的城市,而不是一片越长越乱的野生棚户区。
截至 2026-03-28,Ember 官方 stable channel 给出的稳定安装版本是 Ember 6.11.0,对应的 Ember Data 5.8.0;Ember 6.8 已在 2026-01-09 被提升为 LTS。这个时间点下,Ember 的主路径已经非常明确:
- 新项目默认走 Embroider + Vite
- 新生成的组件和路由模板默认走 严格模式 template tag,也就是
.gjs/.gts - 主流组件写法是 Glimmer 组件 +
@tracked+ 原生 class - 旧时代的
Component.extend、this.get()、observer、经典组件、到处依赖全局解析器的写法,依然会在老项目里看到,但不该再当成主线心智
所以你读这一章,要先接受 Ember 的第一条城市法则:
Ember 不想让你先发明城市规则,再开始建城;它直接把规则、街道、门牌、审批系统一起交给你。
#一、为什么 Ember.js 会存在:因为“自由”在大项目里经常变成“失序”
前端世界有一个常见误区:大家以为“灵活”天然比“约束”高级。
小项目里这话常常成立。可一旦项目变成真正的业务城市,事情就变了。
想象你在盖一座“市民政务城区”:
- 有许可证审批大厅
- 有居民档案
- 有跨部门流程
- 有登录态、通知、搜索、筛选
- 有不同街区之间的跳转
- 有几十个开发者轮班施工
- 这城要运行五年、八年、十年
这时候你最怕的不是“不够灵活”,你最怕的是:
- A 队修路一套标准
- B 队挂门牌一套标准
- C 队修排水又是一套标准
- D 队接档案馆再来一套标准
最后整座城市不是没有能力,而是每个区都能跑,但全城协作成本高得离谱。
Ember 的回答非常直接:
- 路由我给你内建
- 工程骨架我给你脚手架
- 数据层我给你官方路线
- 测试我默认配
- 升级路径我尽量铺平
- 文件结构我不让你瞎摆
所以 Ember 官方一直强调的,是“productive”“battle-tested”“ambitious”。
翻成人话就是:
它不是最爱秀肌肉的新玩具,而是那种给大型组织长期交付用的城市级施工方案。
#二、核心心智模型:Ember 不是“组件框架”,它是“一座按总规运行的城市”
学 Ember,不能只用“组件”那一层去理解。那样你会一直觉得它“怎么这么重”。
你得用整座城市去看它。
在“约定秩序城区”里,最核心的五个角色分别是:
- Router 路由器:城市总路网和交通调度中心,决定用户此刻走到哪条街
- Route 路由对象:每个街区的区政府,负责在进入该区域前把数据、状态、上下文准备好
- Template / Component 模板与组件:楼体图纸和标准化建筑模块,负责把街区长出来
- Service 服务:常驻市政机关,跨区域共享状态与能力
- Store / Model 数据层:城市总档案馆,负责记录、缓存、同步全城的业务实体
如果你只把 Ember 看成“另一个写组件的方案”,你就会误解它。
Ember 真正的心智,不是“页面 = 组件树”,而是:
URL 决定你走进哪片城区,Route 负责调档案,Template 和 Component 负责按图施工,Service 提供跨区公共能力,Store 作为城市总档案馆维持全局一致性。
这套秩序很重要,因为它解决了大项目最难的那类问题:
- 页面数据到底在哪里取
- 共享状态到底放哪
- 一个业务实体到底谁算权威副本
- 路由切换和异步加载怎么统一处理
- 团队里新人一进来,怎么凭目录和命名就找到地方
你会发现,Ember 的很多“限制感”,其实都是在防止城市失序。
#三、CLI 与项目结构:Ember CLI 就是城建总包和门牌发放局
Ember 的上手入口不是“先装十个库再自己决定怎么拼”,而是先用 CLI 把城市骨架立起来。
典型命令大致是这样:
npm install -g ember-cli
ember new permit-city --lang en --strict
cd permit-city
npm start在 2025 年后这条线尤其重要,因为新蓝图已经默认走:
- Vite 作为开发构建体验
- Embroider 作为现代化打包架构
- 严格模式模板
.gjs/.gts作为主要作者体验
也就是说,今天的新城区不是旧时代那种“很多魔法藏在地基下面”的 Ember 了,而是更接近现代 JS 工具链的显式城市。
你可以把 CLI 理解成“城建总包”:
ember new:新建整座城ember generate route permits:新开一个街区,并自动补门牌、街道登记、基础图纸ember generate component permit-card:造一个标准建筑模块ember generate service session:新设一个常驻部门ember generate model permit:在总档案馆里登记一个新实体类型
这正是 Ember 的味道:不是让你空手摆文件,而是让工具按城市法则替你把骨架搭好。
一个现代 Ember 项目,你最该先认识的通常是这些地块:
permit-city/
├── app/
│ ├── components/ # 标准建筑模块
│ ├── models/ # 档案实体定义
│ ├── routes/ # 各街区的区政府逻辑
│ ├── services/ # 常驻市政机关
│ ├── templates/ # 路由模板 / 应用外壳
│ └── router.js # 总路网
├── config/ # 城市运行配置
├── public/ # 公开静态资产
├── tests/ # 验收与巡检系统
├── babel.config.cjs # 现代构建配置
├── package.json
└── .ember-cli这里最有 Ember 味道的一点是:
目录名不是摆设,它们就是城市分区本身。
在别的生态里,文件夹有时只是“大家约定俗成”。在 Ember 里,文件夹经常就是系统认路的一部分。
#四、路由:先修主干道,再谈盖楼
Ember 对路由的态度很“老派 Web”,但恰恰因此很强。
它相信:URL 不是附属品,而是整座城市的主干道。
你先进哪条街、街区之间怎么嵌套、进城前要调哪些档案、加载失败怎么兜底,这些不该零散塞进组件,而应由路由系统统一治理。
一个典型的总路网长这样:
// app/router.js
import EmberRouter from '@embroider/router';
import config from 'permit-city/config/environment';
export default class Router extends EmberRouter {
location = config.locationType;
rootURL = config.rootURL;
}
Router.map(function () {
this.route('permits', function () {
this.route('permit', { path: '/:permit_id' });
});
this.route('citizens');
});这张路网图的意思是:
/permits是许可证大厅/permits/123是 123 号许可证的详情街区/citizens是居民档案区
在 Ember 里,定义一条路,往往就等于同时决定了几件事:
- URL 长什么样
- 对应 route 文件放哪
- 对应 template 文件放哪
- 嵌套路由渲染到谁的
{{outlet}} - 相关测试从哪起步
这就是“门牌号式开发”。
如果你生成一个 route:
ember generate route permitsEmber 会顺手帮你把区政府办公室、楼体图纸、基础验收文件一起建出来。它不是在问你“你喜欢哪种项目结构”,它直接说:“这座城区就按这一套编号法走。”
而且这套路由不是单纯 URL 匹配器,它还是数据与渲染的调度器。
#五、Route:每个街区都有一个“区政府”,负责先把事准备好
很多初学者学 Ember 的第一个拐点,就在这里。
在 React 生态里,你很容易把“数据获取”理解成组件自己的事。
在 Ember 的城市里,页面级数据更像区政府提前调档,而不是楼里某个房间自己跑去档案馆翻卷宗。
看一个典型例子:
// app/routes/permits.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';
export default class PermitsRoute extends Route {
@service store;
model() {
return this.store.findAll('permit');
}
}这里的 model(),就是这个街区的“入区准备流程”。
用户走进 /permits 这条街时:
- Router 发现用户要去
permits PermitsRoute的model()自动执行- Route 去总档案馆
store调permit全量记录 - 数据准备好后,再把模板真正渲染出来
这件事非常 Ember。
因为它的逻辑是:
先决定你去哪,再决定这个地方需要什么数据,然后才开始盖楼。
而不是“先把楼盖出来,楼里组件再各自出去拉材料”。
如果是动态街区:
// app/routes/permits/permit.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';
export default class PermitsPermitRoute extends Route {
@service store;
model(params) {
return this.store.findRecord('permit', params.permit_id);
}
}这里 params.permit_id 就像街区门牌号,Route 拿着门牌去总档案馆调对应卷宗。
Route 的强大,不只在于取数据,还在于它天然对异步友好:
- 返回普通值,可以
- 返回 Promise,也可以
- Promise 没 resolve 之前,路由切换会等待
- 还能配合 loading / error 子状态做区级兜底
这就是 Ember 在大型应用里让人舒服的地方:异步不是散在楼里的电线,而是铺在市政主干道里的统一管网。
#六、模板:不是 JSX 宇宙,而是“按 HTML 施工”的城市图纸
Ember 模板的一个关键气质是:它非常强调 HTML 本身。
你可以把模板理解成建筑图纸。
这份图纸不是“随手拼字符串”,也不是“什么 JS 都能塞进去”,而是强调结构清楚、来源明确、可读性高。
在现代 Ember 里,一个路由级外壳通常会写在 app/templates/application.gjs:
// app/templates/application.gjs
import { LinkTo } from '@ember/routing';
<template>
<header class="city-shell">
<h1>约定秩序城区</h1>
<nav>
<LinkTo @route="permits">许可证大厅</LinkTo>
<LinkTo @route="citizens">居民档案区</LinkTo>
</nav>
</header>
<main>
{{outlet}}
</main>
</template>这里有两个 Ember 味道很重的点。
第一,<LinkTo> 是官方路线内的导航组件。
第二,{{outlet}} 是一个“街区预留渲染口”。
你可以把 application.gjs 看成城市总入口大厅。
所有子街区的楼体,都会被灌进这个大厅里的 {{outlet}}。
如果是 permits 路由模板:
// app/templates/permits.gjs
import PermitCard from 'permit-city/components/permit-card';
<template>
<section>
<h2>许可证大厅</h2>
{{#each @model as |permit|}}
<PermitCard @permit={{permit}} />
{{/each}}
</section>
</template>这里你能看出 Ember 模板的几个核心习惯:
- 路由返回的数据,模板直接从
@model取 - 组件参数用
@参数名 - 当前实例上的字段用
this.xxx - 控制结构像
{{#each}}、{{#if}}依然很重要 - 严格模式下,组件、helper、modifier 更倾向显式 import,而不是全城到处靠“神秘自动识别”
这就是 Ember 模板的城市纪律:
图纸尽量像图纸,不要把钢筋、水泥、审批流全混成一团。
#七、组件:标准建筑模块,不是整座城市的总调度中心
Ember 的组件今天主流是 Glimmer 组件。
你要把它理解成“标准建筑模块”。
它负责:
- 展示局部 UI
- 处理局部交互
- 接收外部参数
- 管理局部状态
它不该默认负责整座街区的入区数据调度。那是 Route 的职责。
看一个典型组件:
// app/components/permit-card.gjs
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';
export default class PermitCardComponent extends Component {
@tracked expanded = false;
toggle = () => {
this.expanded = !this.expanded;
};
approve = async () => {
this.args.permit.status = 'approved';
await this.args.permit.save();
};
<template>
<article class="permit-card">
<h3>{{@permit.title}}</h3>
<p>状态:{{@permit.status}}</p>
<button type="button" {{on "click" this.toggle}}>
{{if this.expanded "收起详情" "查看详情"}}
</button>
{{#if this.expanded}}
<p>{{@permit.description}}</p>
{{/if}}
<button type="button" {{on "click" this.approve}}>
审批通过
</button>
</article>
</template>
}这里你会看到现代 Ember 组件的主心骨:
Component:Glimmer 组件基类@tracked:可追踪状态this.args:外部传入参数{{on "click" ...}}:事件绑定 modifier- 模板和类写在同一个
.gjs文件里
这套模型很像城市里的标准商铺:
- 店铺长什么样,由模板决定
- 店铺自己的开关灯、折叠面板、按钮交互,由组件自己管
- 但店铺不该自己去改城市主路规划,也不该乱接全城总水管
所以写 Ember 组件最重要的一条城市纪律是:
组件负责“楼内事务”,Route 负责“街区调度”。
这条线一旦守住,项目会非常清爽。
#八、服务:常驻市政机关,跨区共享,不随街区卸载
有些能力不属于某一栋楼,也不属于某一个街区,而是整座城市运行时一直在线的。
比如:
- 当前登录人
- 购物车
- WebSocket 连接
- 通知中心
- 地理定位
- 第三方 API 客户端
- 跨页面缓存状态
这些在 Ember 里通常放进 Service。
你可以把 Service 看成“常驻市政机关”。
它的特点是:
- 生命周期跟整个应用走
- 可以被组件、路由、其他服务注入使用
- 适合共享状态和跨区域能力
例如:
// app/services/session.js
import Service from '@ember/service';
import { trackedObject } from '@ember/reactive/collections';
export default class SessionService extends Service {
currentUser = trackedObject({
id: 'u-1',
name: '城区审批员',
role: 'reviewer',
});
get isReviewer() {
return this.currentUser.role === 'reviewer';
}
signOut() {
this.currentUser = trackedObject({
id: null,
name: null,
role: null,
});
}
}然后在组件里注入:
// app/components/city-header.gjs
import Component from '@glimmer/component';
import { service } from '@ember/service';
export default class CityHeaderComponent extends Component {
@service session;
<template>
<header>
<strong>{{this.session.currentUser.name}}</strong>
{{#if this.session.isReviewer}}
<span>审批权限已启用</span>
{{/if}}
</header>
</template>
}这个机制像什么?
像每栋楼都可以拨打“市政热线”,但热线背后是同一个常驻部门,不会因为你从 A 街区走到 B 街区就销毁重建。
不过,很多人也会在这里犯错:
把 Service 当成“全局大垃圾桶”。
这就像把所有业务都塞进“市政府总办公室”,最后任何事都能管,任何事都难维护。
所以 Service 的正确姿势是:
- 共享状态放这里
- 跨页面能力放这里
- 页面级数据主入口仍优先放 Route
- 不要因为偷懒,就把一切都变成全局单例
#九、数据层:Ember Data、store、model,就是城市总档案馆
Ember 的另一个标志性气质,是它对数据层也有官方叙事。
在别的生态里,数据层常常是“你爱选啥选啥”。
在 Ember 的城区里,官方默认会给你一座总档案馆,这就是 Ember Data。
你可以把它拆成三个角色来理解:
- Model:档案类型定义,说明“许可证”“居民”“工单”这些实体长什么样
- Store:总档案馆前台,负责查询、缓存、复用、分发
- Adapter / Serializer:档案馆和外部系统之间的翻译官
Ember Data 默认更偏向 JSON:API 这条官方公路,但它并不强迫你后端只能长成一种样子;需要时你可以自定义 adapter / serializer,或者对某些非标准接口直接用别的请求方式。
先看 Model:
// app/models/permit.js
import Model, { attr, belongsTo } from '@ember-data/model';
export default class PermitModel extends Model {
@attr('string') title;
@attr('string') status;
@attr('string') description;
@attr('date') submittedAt;
@belongsTo('citizen', { async: true, inverse: null }) owner;
}这相当于在档案馆里定义一种卷宗格式:
许可证有标题、状态、说明、提交时间,还有一个关联的申请人。
然后看 Store 怎么用:
// 查单条档案
this.store.findRecord('permit', 'p-42');
// 查全部档案
this.store.findAll('permit');
// 只读缓存,不发请求
this.store.peekRecord('permit', 'p-42');
this.store.peekAll('permit');
// 带条件查询
this.store.query('permit', {
filter: { status: 'pending' }
});再看创建与保存:
let permit = this.store.createRecord('permit', {
title: '夜间施工许可证',
status: 'draft',
description: '申请在 23:00 后施工'
});
await permit.save();你要特别理解 Ember Data 的“城市档案馆心智”:
Store 不是一个临时数组,不是某个页面的局部状态,而是全城业务实体的统一登记处。
它的好处非常大:
- 同一条记录在不同页面、不同组件里能指向一致对象
- 已经调过的档案可以缓存
- 局部修改能更自然地在别处反映
- 你不用每个页面都手动维持“同一人、同一工单、同一许可证”的多份副本同步
这在大型业务系统里太重要了。
因为大系统最怕的,不是“没拿到数据”,而是拿到了三份看起来像同一条、实际却不是同一份对象的数据。
当然,也要说句实话:
Ember Data 很强,但也不是“所有请求都必须经过它”。
如果你有:
- 一次性统计接口
- 非实体化的报表接口
- 第三方服务调用
- 很偏门的协议格式
完全可以放进 Service 或其他请求层。
城市比喻里就是:
居民档案进总档案馆,临时访客登记表没必要也硬塞进人口数据库。
#十、约定优于配置:文件名不是装饰,是门牌号
Ember 最容易让新人又爱又恨的,就是“约定优于配置”。
但你一旦用城市去理解,就会觉得它非常合理。
如果一个街区叫 permits,那 Ember 会默认期待这些门牌:
app/routes/permits.jsapp/templates/permits.gjs
如果它下面还有一个 permit 子街区:
app/routes/permits/permit.jsapp/templates/permits/permit.gjs
如果你有一个组件 permit-card:
app/components/permit-card.gjs
如果你有个服务 session:
app/services/session.js
这就像城市门牌系统。
门牌的价值不是“好看”,而是:
- 邮差能送到
- 消防能定位
- 维修队能找门
- 新人一看就懂
Ember 的约定体系,就是在做这件事。
它在替团队消灭很多无意义选择题:
- route 文件放哪
- template 文件怎么配
- 组件怎么命名
- 模型去哪
- 服务去哪
- 新人第一次进项目该先看什么
当然,Ember 并不是完全不能改路。
比如路由 path 就能改:
Router.map(function () {
this.route('contact', { path: '/getting-in-touch' });
});这就像“街区内部叫市民服务中心,但对外保留旧路名”。
所以 Ember 不是“不能配置”,而是:
先给你一套能跑十年的城市标准;只有真的需要偏离时,再显式偏离。
这和“什么都能配”的差别非常大。
因为“什么都能配”在大多数团队里,最后往往会滑向“每个人都配得不一样”。
#十一、运行机制:Ember 这座城市到底怎么动起来
现在把整座城市串起来。
当用户打开一个 Ember 应用时,发生的事情,大致像这样:
用户访问 URL
|
v
Router 匹配总路网
|
v
进入对应 Route 链
|
v
执行 model() / 相关 hook
|
v
Route 从 Store / API / Service 准备数据
|
v
把数据交给 route template(@model)
|
v
Template 组装 Component
|
v
Glimmer 渲染为 DOM
|
v
页面可交互
|
v
@tracked / args / service / store 中被读取过的数据发生变化
|
v
Autotracking 记录依赖,Glimmer 精准更新受影响的 DOM再画得完整一点:
约定秩序城区运行图
URL
|
v
+--------------------+
| Router 总路网中心 |
+--------------------+
|
v
+--------------------+
| Route 街区政府 |
| model() / redirect |
+--------------------+
|
v
+--------------------------+
| Store 城市总档案馆 |
| findRecord / findAll |
| cache / identity map |
+--------------------------+
|
v
+--------------------------+
| Adapter / Serializer |
| 对接 REST / JSON:API 等 |
+--------------------------+
|
v
API / Backend
Route 准备好的数据
|
v
+---------------------------+
| Template 城市图纸 |
| application.gjs |
| permits.gjs |
| {{outlet}} |
+---------------------------+
|
v
+---------------------------+
| Component 标准建筑模块 |
| args / @tracked / on |
+---------------------------+
|
v
+---------------------------+
| Service 常驻市政机关 |
| session / cart / socket |
+---------------------------+
|
v
+---------------------------+
| Glimmer 渲染调度中心 |
| autotracking + DOM patch |
+---------------------------+这套机制里最值得你记住的是两件事。
第一件,Route 先于页面级展示。
Ember 的天然节奏就是“先入区审批,再开放街区”。
第二件,Ember 的更新模型不是到处手动通知,而是依赖追踪 + 高效重渲染心智。
官方在组件更新思路里强调得很明确:你应该把模板想成“根据当前输入重新求值”,至于 DOM 该怎么最小化更新,由 Glimmer 去处理。
这非常像城市数字孪生系统:
- 哪些传感器值被这个屏幕读过
- 哪个广告牌内容真的变了
- 哪块指示牌不用动
Glimmer 会尽量只更新必要部分,而不是把整条街推倒重来。
#十二、典型代码片段:把一条审批街区完整串一遍
如果把 Ember 的主线压缩成一条最典型的开发路径,大概就是下面这组代码。
先定义路网:
// app/router.js
import EmberRouter from '@embroider/router';
import config from 'permit-city/config/environment';
export default class Router extends EmberRouter {
location = config.locationType;
rootURL = config.rootURL;
}
Router.map(function () {
this.route('permits', function () {
this.route('permit', { path: '/:permit_id' });
});
});再让街区先调档案:
// app/routes/permits.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';
export default class PermitsRoute extends Route {
@service store;
model() {
return this.store.findAll('permit');
}
}定义档案格式:
// app/models/permit.js
import Model, { attr } from '@ember-data/model';
export default class PermitModel extends Model {
@attr('string') title;
@attr('string') status;
@attr('string') description;
}总入口大厅:
// app/templates/application.gjs
import { LinkTo } from '@ember/routing';
<template>
<header>
<h1>市政审批城区</h1>
<LinkTo @route="permits">进入许可证大厅</LinkTo>
</header>
{{outlet}}
</template>街区图纸:
// app/templates/permits.gjs
import PermitCard from 'permit-city/components/permit-card';
<template>
<section>
<h2>许可证大厅</h2>
{{#each @model as |permit|}}
<PermitCard @permit={{permit}} />
{{/each}}
</section>
</template>楼内交互:
// app/components/permit-card.gjs
import Component from '@glimmer/component';
import { on } from '@ember/modifier';
export default class PermitCardComponent extends Component {
approve = async () => {
this.args.permit.status = 'approved';
await this.args.permit.save();
};
<template>
<article>
<h3>{{@permit.title}}</h3>
<p>{{@permit.description}}</p>
<p>当前状态:{{@permit.status}}</p>
<button type="button" {{on "click" this.approve}}>
审批通过
</button>
</article>
</template>
}这段代码的意思,不只是“能跑”。
它更重要的是体现了 Ember 的城市秩序:
- 路怎么进:
router.js - 街区先拿什么档案:
routes/permits.js - 档案长什么样:
models/permit.js - 城市外壳放哪:
application.gjs - 列表街区怎么展示:
permits.gjs - 单个楼块怎么交互:
permit-card.gjs
这不是写法偏好,而是城市治理方式。
#十三、历史包袱要知道,但别把旧城区当新蓝图
Ember 是一个老牌、长期稳定演进的框架,所以你读代码、搜文章、看老项目时,一定会穿越到旧城区。
常见的旧城区痕迹有这些:
Route.extend({ ... })Component.extend({ ... })EmberObjectthis.get('foo')/this.set('foo', value')- computed properties 到处飞
- observers
- 经典组件
- 单独的
.hbs+.js文件配套写法 - 强依赖全局 resolver 自动找组件、helper、modifier
这些都不是“假 Ember”。
它们是 Ember 历史发展的真实遗产。
但你在 2026 视角下建立主线心智时,应该优先抓住今天稳定成立的主路径:
- 原生 class
@tracked- Glimmer 组件
@service.gjs/.gts- 严格模式模板
- 更显式的 import
- Vite + Embroider 新蓝图
城市比喻里,这就像:
旧城区的老楼还在,也能住人;但新城区的城市规范已经换代了。你学规划时,别拿老式木窗审批标准去指导今天的高层综合体。
#十四、常见误区:很多人不是没学会 Ember,而是带着别的城市地图来理解它
#误区 1:Ember 只是“另一个组件框架”
不对。
Ember 有组件层,但它真正的价值在于:路由、数据层、工程骨架、测试、升级路径、约定体系一起形成的整城秩序。
#误区 2:组件自己 fetch 一切更灵活
小块组件偶尔可以这么做。
但页面级主数据在 Ember 里更适合进 Route。否则很快就会出现一栋楼拉一份卷宗、另一栋楼再拉一份、整条街都在重复施工。
#误区 3:所有共享内容都丢 Service
Service 是市政机关,不是宇宙垃圾桶。
全局状态、长生命周期连接、跨页面共享能力适合放这里;页面数据主调度依旧应该先看 Route。
#误区 4:@tracked 只要写过一次就万事大吉
不是。
如果你改的是普通对象/数组的内部结构,可能并不会触发你预期的更新。现代 Ember 提供了 trackedArray、trackedObject 等更明确的响应式集合工具,这很值得用。
#误区 5:Ember Data 是强制宗教
也不是。
Ember Data 是官方档案馆,非常适合“实体化、可缓存、需要一致性”的业务模型。
但统计接口、一次性报表、第三方 API,完全可以走 Service 或别的请求层。
#误区 6:老文章里能跑的写法,今天就是最佳实践
这是 Ember 学习里最危险的坑。
你搜到的很多内容横跨十几年演进史。能跑,不等于该作为新项目主路径。
#误区 7:约定优于配置 = 不可定制
恰恰相反。
Ember 是“先有统一城市法”,不是“禁止改造”。
只是它要求你在偏离默认方案时,偏离得清楚、显式、可追踪。
#十五、什么场景适合 Ember:它最像“长期运营型业务城区”
如果你的项目像下面这些,Ember 往往会非常顺手:
- 长生命周期的后台、管理台、工作流系统
- 业务实体关系复杂,需要统一数据语义
- 页面导航、嵌套路由、筛选、详情、编辑流很重
- 团队人数较多,希望新人快速按目录认路
- 很在意升级稳定性、弃坑成本、组织一致性
- 不想每次开新项目都重新决定路由、状态、构建、测试方案
这类项目像什么?
像:
- 政务审批平台
- CRM / ERP / OMS
- 内容运营后台
- 企业级数据录入与审核系统
- 多步骤工作流应用
- 内部业务中台
这种城市不追求“这栋楼单独很炫”,它追求的是:
整座城十年后依然能维护。
这正是 Ember 的审美。
#十六、什么场景不太适合 Ember:它不是每块地都该用的规划局
也要讲清楚,Ember 不是万能银弹。
下面这些情况,它往往不是默认首选:
- 以营销落地页、内容站、SEO 首屏为核心的网站
- 超轻量的嵌入式小部件
- 团队明确只想要 UI 库,不想接受强约束
- 需要最大化跟随主流生态热点和招聘池
- 服务端渲染、边缘渲染、内容分发是核心叙事的项目
为什么?
因为这些场景更像:
- 临街快闪店
- 宣传展馆
- 极轻量可插拔 kiosk
- 需要和主流 SSR / full-stack 叙事高度贴合的产品
Ember 当然不是做不了其中一些事。
但它的城市气质,天然更偏“长期运营城区”,而不是“爆款商场快装系统”。
#十七、和 React / Vue / Angular / Next 等方案怎么对比:不是谁更强,而是谁在修哪种城
这部分最容易吵架,所以一定要用城市规划视角来看,而不是用“谁更火”来看。
| 方案 | 更像什么 | 核心气质 | 你主要得到什么 | 你主要付出什么 |
|---|---|---|---|---|
| Ember.js | 规划完整的秩序城区 | 强约定、整城治理 | 路由、工程结构、数据层叙事、升级稳定性、一致性 | 生态热度较小,必须接受它的城市法 |
| React | 高质量建筑构件库 | 组件优先、生态超大 | 自由度、生态、组合能力 | 你要自己拼很多城市级决策 |
| Vue | 规划较柔和的新城区 | 渐进、平衡、易上手 | 较低学习曲线、较完整官方生态、模板体验顺滑 | 组织级强约束不如 Ember/Angular |
| Angular | 国家级基础设施总包 | 官方一体化、DI、组织治理强 | 表单、路由、DI、工程规范、企业级一致性 | 体系更重,学习和工程感更强 |
| Next.js | 带服务端能力的 React 商业综合体 | React full-stack、SSR/RSC/缓存/部署叙事强 | 服务端与前端一体化、内容站和产品站路线更成熟 | 仍然建立在 React 世界观上,城市法不是 Ember 那种整城约定 |
把它们说得更直白一点。
React 像给你一套国际通用建筑零件。
零件质量很高,配套市场巨大,但整座城市怎么规划,很多事要你自己定。
Vue 像规划比较温和的新城区。
它保留了很强的开发舒适度,也给你一套比较完整的官方路线,但不会像 Ember 那样把城市编号体系压得那么实。
Angular 像国家级基础设施总包。
它也很完整、很有纪律,甚至比 Ember 更强调官方平台感。
如果说 Ember 是“约定秩序城区”,Angular 更像“制度化工业新区”。
Next.js 则不是 Ember 的同层替代。
它更像 React 上面叠起来的商业综合体方案,特别擅长把:
- 路由
- 渲染策略
- 服务端能力
- 数据获取
- 部署链路
整成一个 full-stack 叙事。
如果你的核心问题是“内容首屏、服务端能力、SEO、边缘部署”,Next 的自然度通常更高。
但如果你的核心问题是“一个长期运营的前端业务城区如何保持十年不乱”,Ember 的整城秩序感反而很有魅力。
所以别问“Ember 会不会被 React/Vue/Next 打败”。
更准确的问法是:
你现在要修的是一座什么样的城?
如果你要的是一座路网、门牌、档案、审批、升级制度都非常统一的业务城区,Ember 依然有极强辨识度。
#十八、真正掌握 Ember 的标志:你开始按“城区职责”分配代码
什么时候算真的入门 Ember?
不是你会写 @tracked。
也不是你能背出 store.findRecord()。
而是你开始本能地这么分工:
- URL 和进入哪个区:先看 Router
- 进入这个区前要拿什么数据:先看 Route
- 这个街区整体怎么摆:看 route template
- 某个局部楼块怎么交互:写 Component
- 跨街区共享什么能力:放 Service
- 哪些是稳定业务实体:进 Model / Store
- 为什么文件放这里:因为这不是随意放置,而是门牌系统
一旦你能按这个秩序思考,Ember 就突然从“有点古典、有点重”的框架,变成一套非常稳的城市规划语言。
它的魅力不在于让你每次都拥有一百种施工自由。
它的魅力在于:当项目真的大起来时,它能让整座城还像一座城。
#十九、收尾总结:Ember.js 是给“长期秩序”设计的,不是给“即时炫技”设计的
用一句话收住整章:
Ember.js 的本质,是把大型 Web 应用当作一座需要长期治理的城市,而不是一堆暂时能拼起来的页面。
所以它才会执着于:
- 路由优先
- 文件结构约定
- 官方数据层叙事
- 服务式共享能力
- 清晰的模板边界
- 稳定升级路径
- 框架级组织秩序
在“约定秩序城区宇宙”里:
router.js是总路网route是区政府template是施工图纸component是标准建筑模块service是常驻市政机关store与model是城市总档案馆@tracked与 Glimmer 是实时调度系统- CLI 和生成器是城建总包与门牌发放局
如果你能一直用这套比喻去理解 Ember,你就不会再把它看成“一个有点老的前端框架”,而会看见它真正的设计野心:
它想解决的,从来不是一个按钮怎么点亮,而是一整座前端业务城市如何长期有序运行。
#官方来源链接清单
- Ember.js 官方首页
- Ember Stable Release Channel
- Ember 官方博客发布列表
- Ember 6.8 Released:默认启用 Embroider 与 Vite、默认严格模式模板
- Ember Guides:Quick Start
- Ember Guides:Orientation / 新建项目与开发服务器
- Ember Guides:Templates are HTML
- Ember Guides:Introducing Components
- Ember Guides:Component State and Actions
- Ember Guides:Template Lifecycle, DOM, and Modifiers
- Ember Guides:Defining Your Routes
- Ember Guides:Specifying a Route’s Model
- Ember Guides:Rendering a Template
- Ember Guides:Services Overview
- Ember Guides:EmberData Introduction
- Ember Guides:Defining Models
- Ember Guides:Finding Records
- Ember Guides:Creating, Updating and Deleting Records
- Ember Guides:Autotracking In-Depth
- React 官方首页
- Vue 官方首页
- Angular 官方首页
- Next.js 官方文档:What is React and Next.js