Angular还值得学吗?从React/Vue转过来后,我想说点反话
最近在负责一个大型后台管理系统的重构,原来用React+一堆乱七八糟的状态库,后来整个团队花了三周把核心模块切到了Angular。别急着喷,我知道现在Angular的社区热度远不如那两个,但如果你管过超过50个页面、七八个微前端、十几个开发者协作的屎山,你就明白我为什么愿意回头吃这坨“重量级”饭了。
刚开始我跟你一样烦它的模板语法,什么*ngIf、*ngFor,哪像JSX那么自由,还有那套模块系统,新项目还得自己npm install然后写一堆@NgModule。但后来我发现自己踩的绝大多数React坑,都是因为“太自由”。React组件想怎么组织就怎么组织,但每个人风格不同,代码review时全是吵架。Angular却像公司规章制度,虽然学起来像背法律条文,却能让一堆水平参差不齐的人产出风格统一的代码。说白了,前端团队到后期,拼的不是炫技,是容错率。
如果你自己做过技术选型,一定经历过这种黑暗时刻:React项目为了管理异步请求,引入redux-saga、dva、react-query,还得自己写loading状态;Vue相对好点,但大型项目里那个响应式依赖追踪有时会诡异地把你的对象变成Proxy,调试半天才发现是父组件多传了一个引用。Angular呢,只要启动一次,依赖注入系统帮你理清楚所有服务作用域。默认的ChangeDetectionStrategy.OnPush配合Zone.js,虽然不是完美,但至少不会出现“明明没改数据却自动渲染了”的灵异事件。当然,Zone.js本身在某些浏览器上也会搞出几毫秒的延迟,这时候用ngRxSignal或者手动调一下ChangeDetectorRef.detectChanges(),都能精准控制。
我印象最深的一次迁移,是处理一个带websocket实时推送的交易订单表格。React版用了React.memo + useCallback + useMemo层层优化,表格还是因为父组件state刷新而卡得掉帧。当时屏幕上每秒更新10-20条数据,CPU占用直接飙到90%。迁移到Angular后,整个表格包在独立NgModule里,通过@Input()传一个只读的immutable数组,子组件全用OnPush策略,再用async管道订阅一个BehaviorSubject。结果呢?首屏加载时间从12.8秒降到9.1秒(当然是配合了懒加载),滚动过程中的帧率稳定在58fps左右,CPU占用不到45%。有次Zone.js检测到的事件在iframe里触发,导致刷屏了一小块区域,我直接在那个子组件里重写了ngDoCheck,用自定义比较逻辑只检查必要字段,问题就解决了。这要是在React里,我可能得从concurrent模式到useTransition一路重写一遍才能见鬼得找到底层问题。
有人总说Angular的包体太大,像我们刚建的项目,默认打包就给我整出1.2MB的gzip主包(Angular CLI 16全量引入)。后来我开启了optimization: { scripts: true, styles: true },再加providers里把所有装饰里的服务都改成provideIn: 'root',再把不必要的CommonModule删除,主包压到215KB。你要说React也能做到,没错,但React你得自己去想怎么分割代码、怎么处理样式冲突、怎么统一请求层。Angular是全部都替你规划好了,你就照着规矩来,反而省心。我唯一不满的是它的HttpClient拦截器设计得有点死板——想给每个请求头加一个动态的custom traceId,得自己实现一个HttpInterceptor接口,再往APP_INITIALIZER里初始化某种全局配置,麻烦得要死。对比Axios的拦截器,简单得让人心酸。但转念一想,正因为这种死板,团队里没人能乱写,每个API调用都走同一层认证、错误处理、重试逻辑。几个月的线上数据告诉我,接口超时率从之前的3.7%降到了1.9%,这可不是代码数量能换来的。
如果你正在纠结到底要不要学Angular,我的看法很简单:别把市面上“Angular已死”的言论当回事儿。它是给那些准备长期维护、需求多变、跨团队合作的大型项目准备的手铐和合页——铐住你的“创造力”,也防止项目散架。什么“一套代码跑多端”,什么“响应式RxJS天下无敌”,说实话我也用了好多,但最后留下来的直觉是:Angular不过分追求自由和隐式魔法,它明确告诉你状态从哪来,变化怎么传播。甚至你问它“为什么卡了”,它会跑一条带有依赖图和建议日志的linker路线给你(如果你开了dev mode)。这种在崩溃时能给出具体参数指引的框架,你受委屈时还能骂它两句,不像React/Vue,报错只会报一个“undefined is not an object”让你自己猜。
说了这么多“洗白”的话,我也得承认Angular正在落伍的地方:它对全栈的拥抱越来越弱了。你想在同一个项目里做SSR,还得额外去配Angular Universal,不如Next.js一键上手来得痛快;TypeScript的类型推断在模板里也不是100%智能,每次改一个@Output的事件签名,模板里报个错都要重新编译好几秒。但如果你已经在一个中大型企业里做过一两年业务页面,你就会明白:与“把代码写干净”相比,“让代码不会因为别人的失误而变成原子弹”更重要。Angular是把这种约束内置在了每一个模块、每一个装饰器里。我不是说每个人都得学它,反正如果让我带三五个新人去维护一个用户量百万的边缘系统,我宁可他们写不出骚操作,只规规矩矩跑通migration脚本。
最后给那些刚入坑Angular的同学一个建议:不要一开始就上Ngrx全家桶,很多状态根本不需要Store。先用服务层+BehaviorSubject配合async管道,你会发现自己少写了一堆无意义的Action/Reducer/Effect。等你真的遇到跨组件共享的数据流,再去Ngrx里翻牌不迟。也别迷恋单组件性能优化,先全局把OnPush打开,你会看到80%的卡顿已经消失,剩下的再用Chrome的Performance面板慢慢追踪——记住Zone里那些红色长条,八成是你把某个大数组的push操作放进了组件构造函数里。Angular的曲线虽陡,但每一层都看得见摸得着。踩过的坑,几乎都能在它那句老话里找到答案:“显式的依赖比你脑子里的隐式状态可靠得多。”