跳到主要内容前端框架选型指南:React、Vue 与 Angular 对比分析 | 极客日志JavaScriptNode.jsReact Native大前端
前端框架选型指南:React、Vue 与 Angular 对比分析
前端三大框架 React、Vue 和 Angular 各有优劣。React 生态灵活但需自行组装,Vue 上手简单且渐进式,Angular 规范严谨适合企业级。选型应基于团队能力、项目周期及维护成本。本文深入解析三者核心机制、常见陷阱及性能优化策略,提供创业 MVP、中大型应用及金融后台等场景的选型建议。掌握底层原理比盲目跟随趋势更重要,避免在技术选型的路上走弯路。
前端框架选型指南:React、Vue 还是 Angular?
说实话,干了几年前端,最怕听到的就是老板突然在项目群里@所有人:'咱们新项目用哪个框架?你们讨论一下,下午给我个方案。'
然后群里就开始炸锅——React 党说'生态好',Vue 党说'上手快',Angular 党(如果公司真有的话)说'规范强'。吵到最后,老板一拍大腿:'那就用 Angular 吧,听起来比较高级。'
行吧,半年后项目延期三个月,老板又问:'当初为啥选 Angular?'
这种戏码我见过太多次了。所以今天咱不整那些虚的,就聊聊这三个框架到底咋回事。不是告诉你哪个最好——这世上没有最好的框架,只有最适合你当下处境的框架。
新手村:这三个货到底啥来头?
先给刚入行的兄弟们补个课。React、Vue、Angular,江湖人称'前端三剑客',但性格完全不一样。
React:Facebook 的野孩子
2013 年 Facebook 开源的,初衷是解决他们那个巨型动态新闻 feed 的性能问题。这货最大的特点是——它其实不算完整框架,官方就管自己叫'用于构建用户界面的 JavaScript 库'。
啥意思呢?React 只管视图层,路由、状态管理、HTTP 请求这些?自己找第三方库去。所以 React 生态特别野,同一个功能有八百种解决方案,选起来能逼死选择困难症。
但灵活是真灵活。你想怎么写就怎么写,函数式编程可以,类组件也行,现在 Hooks 当道,以前那些高阶组件(HOC)和 render props 的写法也没完全死透。
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('前端小白');
useEffect(() => {
document.title = `${name} 点击了 ${count} 次`;
return () => {
console.();
};
}, [count, name]);
(
);
}
;
log
'组件要卸载了,清理一下'
return
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h2>你好,{name}!</h2>
<p>你点击了这个按钮 {count} 次</p>
{/* 事件处理直接写箭头函数,或者引用外部函数也行 */}
<button onClick={() => setCount(count + 1)}>点我 +1</button>
<button onClick={() => setCount(0)}>重置</button>
{/* 输入框双向绑定?不,React 是单向数据流,需要手动处理 onChange */}
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="输入你的名字" style={{ marginLeft: '10px' }} />
</div>
export
default
Counter
看到没?JSX 语法(就是 HTML 嵌在 JS 里那个)一开始看着怪,写习惯了真香。而且 React 的理念特别纯粹:UI 是状态的函数。状态变了,UI 自动重新渲染,就这么简单。
Vue:尤雨溪的温柔一刀
2014 年尤大(尤雨溪)发布的,最早是个人项目,后来火得一塌糊涂。Vue 的定位是渐进式框架——你可以只用它的核心功能做增强,也可以全家桶梭哈搞大型项目。
Vue 最大的卖点是上手快。模板语法接近 HTML,指令(v-if、v-for 这些)直观得像在写伪代码。而且官方把路由(Vue Router)和状态管理(Vuex/Pinia)都给你准备好了,不用像 React 那样到处找库。
<!-- Vue 3 组合式 API 写法,推荐新手直接学这个,别碰 Options API 了 -->
<template>
<!-- 模板里直接写 HTML,指令用 v-开头,很直观 -->
<div>
<h2>你好,{{ name }}!</h2>
<p>你点击了这个按钮 {{ count }} 次</p>
<!-- 事件绑定用@符号,比 React 的 onClick 更简洁 -->
<button @click="increment">点我 +1</button>
<button @click="reset">重置</button>
<!-- v-model 就是双向绑定,输入框和 name 状态自动同步,不用写 onChange -->
<input v-model="name" placeholder="输入你的名字" />
<!-- 条件渲染,v-if 直接控制元素显示隐藏 -->
<p v-if="count > 10">点这么多次,手不累吗?</p>
</div>
</template>
<script setup>
// Vue 3 的<script setup>语法,写起来比 React 还简洁
import { ref, watch, onMounted } from 'vue'
// ref 创建响应式数据,count.value 才是实际值,模板里不用写.value
const count = ref(0)
const name = ref('前端小白')
// 方法直接定义成函数
const increment = () => {
count.value++
}
const reset = () => {
count.value = 0
}
// watch 监听变化,比 React 的 useEffect 更语义化
watch(count, (newVal, oldVal) => {
console.log(`count 从${oldVal}变成了${newVal}`)
if (newVal > 20) {
alert('别点了,按钮要坏了!')
}
})
// 生命周期钩子,onMounted 相当于 mounted
onMounted(() => {
console.log('组件挂载完成,可以在这里请求数据')
// 模拟异步请求
setTimeout(() => {
name.value = '加载完成的后端大佬'
}, 1000)
})
</script>
<style scoped>
/* scoped 表示样式只作用于当前组件,不会污染全局 */
.counter-box {
padding: 20px;
border: 1px solid #42b983; /* Vue 绿,懂的都懂 */
border-radius: 8px;
}
.warning {
color: #ff6b6b;
font-weight: bold;
}
</style>
注意看,Vue 把模板、脚本、样式都塞在一个 .vue 文件里,这种单文件组件(SFC)的写法特别符合直觉。而且 v-model 双向绑定省了多少代码?React 里你要写 value+onChange 两个属性,Vue 一行搞定。
Angular:Google 的正规军
2016 年发布的 Angular 2+(别跟 AngularJS 混了,那是老祖宗,已经入土了),Google 的亲儿子,TypeScript 的亲兄弟。这货的特点是大而全,官方提供了一整套解决方案:路由、HTTP 客户端、表单处理、动画、测试…你想得到的它都有。
但代价是重。学习曲线陡得吓人,概念一大堆:模块(NgModule)、依赖注入(DI)、装饰器(Decorator)、RxJS 响应式编程…新手看文档能看哭。
import { Component, OnInit, OnDestroy } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-user-list',
template: `
<div>
<h2>用户列表</h2>
<!-- Angular 模板语法,*ngIf 是结构指令,控制元素是否存在 -->
<div *ngIf="loading">加载中...</div>
<!-- *ngFor 循环渲染列表,trackBy 提高性能 -->
<ul *ngIf="!loading">
<li *ngFor="let user of users; trackBy: trackByUserId">
{{ user.name }} - {{ user.email }}
<!-- 事件绑定用圆括号() -->
<button (click)="selectUser(user)">选择</button>
</li>
</ul>
<!-- 属性绑定用方括号[] -->
<img [src]="currentUser?.avatar" [alt]="currentUser?.name" />
<!-- 双向绑定用[()],俗称'香蕉盒' -->
<input [(ngModel)]="searchText" placeholder="搜索用户" />
</div>
`,
styles: [`
.user-container { padding: 20px; }
.loading { color: #666; }
li { margin: 10px 0; }
`]
})
export class UserListComponent implements OnInit, OnDestroy {
users: User[] = [];
loading: boolean = true;
currentUser: User | null = null;
searchText: string = '';
private destroy$ = new Subject<void>();
constructor(private http: HttpClient) {}
ngOnInit(): void {
this.loadUsers();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
private loadUsers(): void {
this.http.get<User[]>('/api/users').pipe(
takeUntil(this.destroy$)
).subscribe({
next: (data) => {
this.users = data;
this.loading = false;
},
error: (err) => {
console.error('加载失败:', err);
this.loading = false;
}
});
}
selectUser(user: User): void {
this.currentUser = user;
}
trackByUserId(index: number, user: User): number {
return user.id;
}
}
interface User {
id: number;
name: string;
email: string;
avatar?: string;
}
看到没?Angular 代码量明显多一截,但每一行都有它的道理。TypeScript 的类型检查能在编译期发现错误,依赖注入让测试变得超简单,RxJS 虽然难学但处理异步数据流真的强。
核心机制:那些面试必问的八股文,到底是啥意思?
好了,基础介绍完了,咱们深入聊聊底层。别一听到'虚拟 DOM'、'响应式系统'就头大,其实没那么玄乎。
React:虚拟 DOM 和 Diff 算法,真的快吗?
React 最引以为傲的就是虚拟 DOM(Virtual DOM)。简单说,就是在内存里用 JavaScript 对象模拟真实的 DOM 树,数据变化时先比较新旧虚拟 DOM 的差异(Diff),然后只更新真正变化的部分。
但这里有个误区:虚拟 DOM 不是为了快,而是为了可维护。直接操作真实 DOM 确实慢,但现代浏览器的优化已经很好了。虚拟 DOM 的真正价值是让你不用手动去算该更新哪个节点,React 帮你搞定。
function reconcile(oldVNode, newVNode) {
if (oldVNode.type !== newVNode.type) {
return createNewNode(newVNode);
}
if (oldVNode.props !== newVNode.props) {
updateProps(oldVNode.dom, oldVNode.props, newVNode.props);
}
const oldChildren = oldVNode.children;
const newChildren = newVNode.children;
reconcileChildren(oldChildren, newChildren);
return oldVNode.dom;
}
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
// 假设 todo.id 是数据库主键,唯一且稳定
<li key={todo.id}>
<input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} />
{todo.text}
</li>
))}
</ul>
);
}
重点来了:React 18 引入了并发特性(Concurrent Features),包括自动批处理(Automatic Batching)、过渡更新(Transitions)和 Suspense 的改进。这意味着 React 可以中断渲染过程,优先处理高优先级更新(比如用户输入),低优先级的(比如搜索结果)可以稍微等等。
import { useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
startTransition(() => {
const searchResults = heavySearch(value);
setResults(searchResults);
});
};
return (
<div>
<input value={query} onChange={handleChange} style={{
opacity: isPending ? 0.8 : 1
}} />
{isPending && <span>搜索中...</span>}
<ul>
{results.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
Vue:响应式系统,到底怎么'响应'的?
Vue 3 的响应式系统基于Proxy(Vue 2 是 Object.defineProperty,有局限性)。简单说,就是 Vue 在你访问或修改数据时'偷听',然后自动触发相关的更新。
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key);
return target[key];
},
set(target, key, value) {
const oldValue = target[key];
target[key] = value;
if (oldValue !== value) {
trigger(target, key);
}
return true;
}
});
}
const state = reactive({ count: 0 });
function render() {
return h('div', state.count);
}
state.count++;
Vue 的**计算属性(Computed)和侦听器(Watch)**也是基于这个响应式系统:
<script setup>
import { ref, computed, watch } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// computed 是缓存的,只有依赖变化时才重新计算
const fullName = computed(() => {
console.log('计算 fullName')
// 只会在 firstName 或 lastName 变化时执行
return firstName.value + ' ' + lastName.value
})
// 可写的 computed,有 getter 和 setter
const fullNameWritable = computed({
get: () => firstName.value + ' ' + lastName.value,
set: (newValue) => {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
// watch 监听特定数据变化,适合执行副作用
watch(firstName, (newVal, oldVal) => {
console.log(`姓从${oldVal}变成了${newVal}`)
// 可以在这里发请求、操作 DOM 等
})
// watchEffect 自动追踪依赖,不用指定监听谁
watchEffect(() => {
// 只要这里面用到的响应式数据变了,就会重新执行
console.log('当前全名:', fullName.value)
// 如果这里还用了其他 ref,那些也会被自动追踪
})
</script>
Vue 的响应式最爽的是自动追踪依赖,你不用像 React 那样手动声明依赖数组(那个 [] 经常忘写,然后产生 bug)。
Angular:依赖注入和 RxJS,企业级标配
Angular 的**依赖注入(DI)**系统是它区别于前两者的核心。简单说,就是你需要什么服务,告诉 Angular 一声,它自动给你准备好,不用你自己 new。
@Injectable({ providedIn:'root'
export class UserService {
private apiUrl = '/api/users';
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
}
@Component({
selector: 'app-profile',
template: `<div>{{ user$ | async }}</div>`
})
export class ProfileComponent {
user$ = this.userService.getUserById(123);
constructor(private userService: UserService) {}
}
RxJS 是 Angular 处理异步的核心。Observable、Subject、Operator 这些概念一开始很劝退,但掌握后处理复杂数据流真的爽。
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged, switchMap, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
@Component({
selector: 'app-search',
template: `
<input [formControl]="searchControl" placeholder="搜索..." />
<ul>
<li *ngFor="let result of searchResults$ | async">{{ result }}</li>
</ul>
`
})
export class SearchComponent implements OnInit {
searchControl = new FormControl('');
searchResults$ = of([]);
constructor(private searchService: SearchService) {}
ngOnInit() {
this.searchResults$ = this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => {
if (!term) return of([]);
return this.searchService.search(term).pipe(
catchError(err => {
console.error('搜索失败', err);
return of([]);
})
);
})
);
}
}
看到没?RxJS 一行代码搞定了防抖、去重、错误处理、取消旧请求,这在 React 或 Vue 里你得写多少 useEffect 或者 watch?当然,学习成本也是真的高。
那些让人头秃的坑,我都替你踩过了
理论讲完,说点实在的。每个框架都有坑,提前知道能少熬几个通宵。
React 的坑:生态太散,选择困难症晚期
Redux?MobX?Zustand?Recoil?Jotai?React Query?SWR?
- 小应用:useState + useContext 够用,别折腾
- 中等应用:Zustand(简单)或 React Query(服务端状态)
- 大型应用:Redux Toolkit(虽然啰嗦但规范)或 MobX(面向对象党福音)
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
user: null,
increment: () => set((state) => ({ count: state.count + 1 })),
setUser: (user) => set({ user }),
fetchUser: async (id) => {
const res = await fetch(`/api/users/${id}`);
const user = await res.json();
set({ user });
}
}));
function UserProfile() {
const user = useStore(state => state.user);
const fetchUser = useStore(state => state.fetchUser);
useEffect(() => {
fetchUser(123);
}, [fetchUser]);
return <div>{user?.name}</div>;
}
这是 React Hooks 最经典的坑,新手必踩:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count);
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>{count}</div>;
}
setCount(prev => prev + 1);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, [count]);
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
const timer = setInterval(() => {
console.log(countRef.current);
}, 1000);
}, []);
ESLint 插件 react-hooks/exhaustive-deps 一定要开,但有时候它提示的依赖你不想加,这时候要想想是不是逻辑设计有问题。
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, []);
}
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
Vue 的坑:2 升 3 的痛,谁升谁知道
如果你维护的是 Vue 2 项目,想升 Vue 3,做好心理准备。虽然官方有迁移工具,但复杂项目基本得重构:
- Options API → Composition API(虽然兼容,但新特性不支持)
- Vuex → Pinia(Vue 3 推荐的状态管理)
- 事件总线(Event Bus)→ Mitt(Vue 3 实例上没有 on/off/on/off 了)
- 过滤器(Filters)→ 计算属性或方法(Vue 3 删了)
- 模板里的
key 用法变了(v-for 和 v-if 优先级也变了)
{{ message | capitalize }}
const bus = new Vue();
bus.$on('event', handler);
bus.$emit('event', data);
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
{{capitalize(message)}}
import mitt from 'mitt';
const bus = mitt();
bus.on('event', handler);
bus.emit('event', data);
Vue 2 用 Object.defineProperty,无法监听新增属性和数组索引;Vue 3 用 Proxy 好了很多,但还是有坑:
this.user.age = 25;
this.$set(this.user, 'age', 25);
const state = reactive({ count: 0, name: 'vue' });
const { count } = state;
count++;
import { toRefs } from 'vue';
const state = reactive({ count: 0, name: 'vue' });
const { count } = toRefs(state);
count.value++;
return { state };
Vue 3 支持了类似 React 的 Suspense,但用法有差异:
<template>
<Suspense>
<template #default>
<!-- 异步组件 -->
<AsyncComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
<!-- 错误处理要用 ErrorBoundary 组件,Vue 3.2+ 支持 -->
<ErrorBoundary>
<Suspense>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
</template>
<script setup>
// 异步组件定义
const AsyncComponent = defineAsyncComponent(() => import('./HeavyComponent.vue'));
// 或者带加载状态和错误处理
const AsyncComponentWithOptions = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200, // 延迟显示 loading,避免闪烁
timeout: 3000 // 超时时间
});
</script>
Angular 的坑:重,是真的重
Angular 默认把很多功能都打包进去,Hello World 可能都几百 KB。优化手段:
const routes: Routes = [{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}];
@Component({
standalone: true,
imports: [CommonModule, RouterModule],
template: `...`
})
export class MyComponent {}
{
"architect": {
"build": {
"configurations": {
"production": {
"optimization": true,
"aot": true,
"buildOptimizer": true,
"budgets": [
{ "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }
]
}
}
}
}
}
Angular 里到处都是 Observable,忘记取消订阅就是内存泄漏:
ngOnInit() {
this.http.get('/api/data').subscribe(data => {
this.data = data;
});
}
data$ = this.http.get('/api/data');
private destroy$ = new Subject<void>();
ngOnInit() {
this.http.get('/api/data').pipe(takeUntil(this.destroy$)).subscribe(data => this.data = data);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
constructor() {
this.http.get('/api/data').pipe(takeUntilDestroyed())
.subscribe(data => this.data = data);
}
Angular 默认的变更检测策略是'检查所有组件',大应用会卡:
@Component({
selector: 'app-heavy',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div>{{ data.name }}</div>
<button (click)="update()">更新</button>
`
})
export class HeavyComponent {
@Input() data!: { name: string };
constructor(private cd: ChangeDetectorRef) {}
update() {
this.cd.markForCheck();
}
}
实战场景:到底该选哪个?
说了这么多,落地到具体项目怎么选?我列几个真实场景:
场景 1:创业公司 MVP,三周上线
- 模板语法上手零成本,后端都能看懂
- 官方生态完整,不用到处找库
- Vite 构建快,热更新秒开
- 招人相对便宜(不是贬低 Vue,是事实)
<!-- 三天搭个后台管理,这种代码后端都能写 -->
<template>
<div>
<el-table :data="list" v-loading="loading">
<el-table-column prop="name" label="名称" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button @click="handleEdit(row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
const list = ref([])
const loading = ref(false)
const fetchData = async () => {
loading.value = true
try {
const res = await request.get('/api/list')
list.value = res.data
} finally {
loading.value = false
}
}
const handleEdit = (row) => {
// 打开编辑弹窗或跳转页面
console.log('编辑', row)
}
onMounted(fetchData)
</script>
Element Plus + Vue 3 + Vite,这种组合搭后台快得飞起。
场景 2:中大型 C 端应用,长期维护
选 React + TypeScript。生态成熟,招人容易,社区活跃。
- 招聘市场 React 岗位最多,不怕招不到人
- TypeScript 支持最好(毕竟 JSX 就是 TS 团队搞的)
- 性能优化手段多(memo、useMemo、useCallback、代码分割)
- 跨平台(React Native)生态成熟,以后想转移动端方便
import React, { memo, useCallback, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
}
interface UserCardProps {
user: User;
onSelect: (id: number) => void;
isSelected: boolean;
}
const UserCard = memo<UserCardProps>(({ user, onSelect, isSelected }) => {
const handleClick = useCallback(() => {
onSelect(user.id);
}, [onSelect, user.id]);
const displayName = useMemo(() => {
return user.role === 'admin' ? `👑 ${user.name}` : user.name;
}, [user.name, user.role]);
return (
<div className={`user-card ${isSelected ? 'selected' : ''}`} onClick={handleClick}>
<h3>{displayName}</h3>
<p>{user.email}</p>
<span className={`badge ${user.role}`}>{user.role}</span>
</div>
);
});
export const UserList: React.FC = () => {
const { data: users, isLoading, error } = useQuery<User[]>({
queryKey: ['users'],
queryFn: async () => {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('加载失败');
return res.json();
}
});
const [selectedId, setSelectedId] = React.useState<number | null>(null);
if (isLoading) return <div>加载中...</div>;
if (error) return <div>出错了:{error.message}</div>;
return (
<div className="user-list">
{users?.map(user => (
<UserCard key={user.id} user={user} isSelected={selectedId === user.id} onSelect={setSelectedId} />
))}
</div>
);
};
场景 3:金融/企业级后台,强类型控团队
选 Angular。虽然重,但规范性强,适合大团队协作。
- TypeScript 第一公民,类型安全到极致
- 架构强制分层(模块、服务、组件),不容易写乱
- 测试支持极好(依赖注入方便 Mock)
- 适合那种'一个项目维护十年'的企业级应用
export interface Transaction {
id: string;
amount: number;
currency: Currency;
timestamp: Date;
status: TransactionStatus;
}
export type Currency = 'CNY' | 'USD' | 'EUR';
export type TransactionStatus = 'pending' | 'completed' | 'failed';
@Injectable({ providedIn: 'root' })
export class TransactionService {
private readonly apiUrl = '/api/transactions';
constructor(
private http: HttpClient,
private logger: LoggerService,
private auth: AuthService
) {}
getTransactions(filters: TransactionFilter): Observable<Transaction[]> {
const params = new HttpParams({ fromObject: filters as any });
return this.http.get<Transaction[]>(this.apiUrl, { params }).pipe(
tap(data => this.logger.log('Fetched transactions', data)),
catchError(this.handleError('getTransactions', []))
);
}
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
this.logger.error(`${operation} failed: ${error.message}`);
return of(result as T);
};
}
}
@Component({
selector: 'app-transaction-list',
template: `
<div>
<app-filter-panel (filterChange)="onFilterChange($event)" />
<div *ngIf="loading">加载中...</div>
<div *ngIf="error">{{ error }}</div>
<table *ngIf="!loading && !error">
<thead>
<tr>
<th>ID</th>
<th>金额</th>
<th>状态</th>
<th>时间</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let t of transactions">
<td>{{ t.id }}</td>
<td>{{ t.amount | currency:t.currency }}</td>
<td>
<app-status-badge [status]="t.status" />
</td>
<td>{{ t.timestamp | date:'yyyy-MM-dd HH:mm' }}</td>
</tr>
</tbody>
</table>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TransactionListComponent implements OnInit {
transactions: Transaction[] = [];
loading = false;
error: string | null = null;
constructor(private transactionService: TransactionService) {}
ngOnInit() {
this.loadTransactions();
}
onFilterChange(filters: TransactionFilter) {
this.loadTransactions(filters);
}
private loadTransactions(filters?: TransactionFilter) {
this.loading = true;
this.error = null;
this.transactionService.getTransactions(filters || {}).subscribe({
next: (data) => {
this.transactions = data;
this.loading = false;
},
error: (err) => {
this.error = err.message;
this.loading = false;
}
});
}
}
看到没?Angular 的代码量是大了点,但每一层职责清晰,十年后的新同事接手也能看懂(大概)。
性能优化:别让你的页面卡成 PPT
不管选哪个框架,性能优化都是逃不掉的。说几个通用的招:
React 优化三板斧
const ExpensiveComponent = memo(function MyComponent({ data, onUpdate }) {
return <div>{/* 复杂渲染逻辑 */}</div>;
}, (prevProps, nextProps) => {
return prevProps.id === nextProps.id;
});
const sortedList = useMemo(() => {
return list.sort((a, b) => b.score - a.score);
}, [list]);
const handleSubmit = useCallback((values) => {
api.submit(values).then(() => {
onSuccess();
});
}, [onSuccess]);
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>
);
}
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList height={500} itemCount={items.length} itemSize={35}>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
);
}
Vue 优化三板斧
<script setup>
import { ref, computed, shallowRef, markRaw } from 'vue'
// 1. 大数据用 shallowRef,避免深层响应式开销
const hugeList = shallowRef([]);
// 只有.value 变化时触发更新,不监听内部属性
// 2. computed 缓存复杂计算
const filteredList = computed(() => {
return list.value.filter(item => item.active);
});
// 3. v-once 和 v-memo 减少重渲染
</script>
<template>
<!-- v-once 只渲染一次,之后跳过 -->
<div v-once>
<h1>{{ title }}</h1>
<!-- title 变了也不更新 -->
</div>
<!-- v-memo 根据依赖缓存,只有依赖变了才更新 -->
<div v-memo="[valueA, valueB]">
<!-- 复杂内容,只有 valueA 或 valueB 变时才重渲染 -->
<HeavyComponent :a="valueA" :b="valueB" />
</div>
<!-- 虚拟滚动,同 React -->
<RecycleScroller :items="list" :item-size="32" key-field="id" v-slot="{ item }">
<div>{{ item.name }}</div>
</RecycleScroller>
</template>
Angular 优化三板斧
@Component({ changeDetection: ChangeDetectionStrategy.OnPush })
import { ScrollingModule } from '@angular/cdk/scrolling';
@Component({
template: `
<cdk-virtual-scroll-viewport itemSize="50">
<div *cdkVirtualFor="let item of items">{{ item }}</div>
</cdk-virtual-scroll-viewport>
`
})
const worker = new Worker(new URL('./app.worker', import.meta.url));
worker.onmessage = ({ data }) => {
console.log('计算结果:', data);
};
worker.postMessage({ action: 'heavyCalculation', payload: hugeData });
最后说几句心里话
写到这里快六千字了,估计能看到这的都是真爱。最后聊点技术之外的:
我见过太多团队,项目就三个页面,非要上微前端;明明是个表单,非要抽成'配置化中台';TypeScript 类型写得比业务代码还长。真的,没必要。技术是用来解决问题的,不是制造问题的。
不管 React、Vue 还是 Angular,底层都是 JavaScript。原型链、闭包、异步、事件循环这些搞不明白,用啥框架都写出一堆 bug。建议定期回去啃《JavaScript 高级程序设计》,常读常新。
再牛的框架,遇到不配合的团队也是白搭。选型时要考虑:
- 团队现有技术栈(重构成本)
- 成员学习意愿(有人抗拒 TS,强上会出事)
- 招聘难度(小城市招 Angular 真的难)
别当框架原教旨主义者。React 党别瞧不起 Vue'太简单',Vue 党别觉得 React'太复杂',Angular 党…嗯,Angular 用户通常没空参与争论,他们在写单元测试。
现在的趋势是融合——Vue 3 吸收了 React 的 Hooks 思想,React 也在学 Vue 的编译优化(React Compiler),Angular…Angular 在努力让自己不那么重(Standalone 组件就是例子)。
建议先沟通,拿出数据(性能、维护成本、招聘难度)说服他。如果说服不了,且公司技术氛围确实落后,那…你懂的,简历该更新就更新。技术人的时间很宝贵,别在刀耕火种的年代浪费青春。
好了,就写到这。希望这篇能帮你在选型时少纠结一会儿。记住,没有银弹,只有权衡。选定了就深入学,别三天两头换框架,那样永远在半桶水晃荡。
祝你的项目永不延期,npm install 从不报错,生产环境永不出 bug。🍻
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online