前端框架选型指南:React、Vue还是Angular?别再踩坑了!

前端框架选型指南:React、Vue还是Angular?别再踩坑了!

前端框架选型指南:React、Vue还是Angular?别再踩坑了!

前端框架选型指南:React、Vue还是Angular?别再踩坑了!


说实话,我干了五年前端,最怕听到的就是老板突然在项目群里@所有人:“咱们新项目用哪个框架?你们讨论一下,下午给我个方案。”

然后群里就开始炸锅——React党说"生态好",Vue党说"上手快",Angular党(如果公司真有的话)说"规范强"。吵到最后,老板一拍大腿:“那就用Angular吧,听起来比较高级。”

…行吧,半年后项目延期三个月,老板又问:“当初为啥选Angular?”

这种戏码我见过太多次了。所以今天咱不整那些虚的,就聊聊这三个框架到底咋回事。不是告诉你哪个最好——这世上没有最好的框架,只有最适合你当下处境的框架。


新手村:这三个货到底啥来头?

先给刚入行的兄弟们补个课。React、Vue、Angular,江湖人称"前端三剑客",但性格完全不一样。

React:Facebook的野孩子

2013年Facebook开源的,初衷是解决他们那个巨型动态新闻 feed 的性能问题。这货最大的特点是——它其实不算完整框架,官方就管自己叫"用于构建用户界面的JavaScript库"。

啥意思呢?React只管视图层,路由、状态管理、HTTP请求这些?自己找第三方库去。所以React生态特别野,同一个功能有八百种解决方案,选起来能逼死选择困难症。

但灵活是真灵活。你想怎么写就怎么写,函数式编程可以,类组件也行,现在Hooks当道,以前那些高阶组件(HOC)和render props的写法也没完全死透。

// React 函数组件 + Hooks 写法,现在最主流 import React, { useState, useEffect } from 'react'; // 一个计数器组件,简单但五脏俱全 function Counter() { // useState就是React的状态钩子,count是当前值,setCount是修改函数 const [count, setCount] = useState(0); // 还有一个name状态,说明可以定义多个useState const [name, setName] = useState('前端小白'); // useEffect处理副作用,比如数据获取、订阅、手动修改DOM等 useEffect(() => { // 组件挂载时执行,相当于以前的componentDidMount document.title = `${name} 点击了 ${count} 次`; // 返回的函数是清理函数,组件卸载时执行,相当于componentWillUnmount return () => { console.log('组件要卸载了,清理一下'); }; }, [count, name]); // 依赖数组,只有count或name变化时才重新执行 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响应式编程…新手看文档能看哭。

// Angular组件示例,感受一下什么叫"企业级"import{ Component, OnInit, OnDestroy }from'@angular/core';import{ HttpClient }from'@angular/common/http';// HTTP客户端是内置的import{ Subject }from'rxjs';// RxJS是Angular的好基友import{ takeUntil }from'rxjs/operators';// @Component装饰器,定义组件的元数据@Component({ selector:'app-user-list',// 组件标签名,使用时就写<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; } `]})exportclassUserListComponentimplementsOnInit, OnDestroy {// 类型定义是强制的,这也是Angular的优势之一 users: User[]=[]; loading:boolean=true; currentUser: User |null=null; searchText:string='';// private表示私有属性,Angular推荐显式声明访问修饰符private destroy$ =newSubject<void>();// 依赖注入!HttpClient不需要new,Angular自动帮你创建实例constructor(private http: HttpClient){}// 生命周期钩子,OnInit是组件初始化时调用ngOnInit():void{this.loadUsers();}// OnDestroy是组件销毁时调用,用于清理订阅,防止内存泄漏ngOnDestroy():void{this.destroy$.next();this.destroy$.complete();}// 加载用户数据,RxJS的流式处理privateloadUsers():void{this.http.get<User[]>('/api/users').pipe(// takeUntil配合destroy$实现自动取消订阅,避免内存泄漏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;// 这里可以触发其他逻辑,比如路由跳转或状态更新}// trackBy函数,帮助Angular识别哪些数据变了,优化重渲染性能trackByUserId(index:number, user: User):number{return user.id;}}// 接口定义,TypeScript的核心优势interfaceUser{ 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帮你搞定。

// 看看React的Diff策略简化版(伪代码,帮助理解) function reconcile(oldVNode, newVNode) { // 如果类型不同,直接销毁重建 if (oldVNode.type !== newVNode.type) { return createNewNode(newVNode); } // 类型相同,比较props if (oldVNode.props !== newVNode.props) { updateProps(oldVNode.dom, oldVNode.props, newVNode.props); } // 递归比较子节点,key的作用就在这里! const oldChildren = oldVNode.children; const newChildren = newVNode.children; // 有key时,React用key来匹配新旧子节点,减少不必要的移动 // 这就是为什么列表渲染一定要有key,而且不能用index! reconcileChildren(oldChildren, newChildren); return oldVNode.dom; } // 实际开发中,key用唯一ID,别偷懒用index 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([]); // useTransition标记非紧急更新 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={{ // isPending为true时,输入框稍微变灰,提示用户"在加载了" 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在你访问或修改数据时"偷听",然后自动触发相关的更新。

// Vue 3响应式原理简化版(伪代码)functionreactive(obj){returnnewProxy(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);}returntrue;}});}// 实际使用const state =reactive({count:0});// 在组件里functionrender(){// 访问state.count,触发get,被收集依赖returnh('div', state.count);}// 修改state.count,触发set,组件重新渲染 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表示它可以被注入@Injectable({ providedIn:'root'// 全局单例,整个应用共享一个实例})exportclassUserService{private apiUrl ='/api/users';constructor(private http: HttpClient){}// 注入HttpClientgetUsers(): Observable<User[]>{returnthis.http.get<User[]>(this.apiUrl);}getUserById(id:number): Observable<User>{returnthis.http.get<User>(`${this.apiUrl}/${id}`);}}// 在组件中使用@Component({ selector:'app-profile', template:`<div>{{ user$ | async }}</div>`// async管道自动订阅/取消订阅})exportclassProfileComponent{// 直接注入服务,Angular会处理实例创建和生命周期 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> `})exportclassSearchComponentimplementsOnInit{ searchControl =newFormControl(''); searchResults$ =of([]);// 初始为空数组的Observableconstructor(private searchService: SearchService){}ngOnInit(){// 监听输入框变化,处理防抖、去重、取消上次请求等this.searchResults$ =this.searchControl.valueChanges.pipe(debounceTime(300),// 等300ms不输入了才触发,减少请求次数distinctUntilChanged(),// 值没变就不触发(比如输入"aa"然后删一个a又输回来)switchMap(term =>{// switchMap会自动取消上次的Observable,防止竞态条件if(!term)returnof([]);returnthis.searchService.search(term).pipe(catchError(err =>{console.error('搜索失败', err);returnof([]);// 出错时返回空数组,不让流断掉}));}));}}

看到没?RxJS一行代码搞定了防抖、去重、错误处理、取消旧请求,这在React或Vue里你得写多少useEffect或者watch?当然,学习成本也是真的高。


那些让人头秃的坑,我都替你踩过了

理论讲完,说点实在的。每个框架都有坑,提前知道能少熬几个通宵。

React的坑:生态太散,选择困难症晚期

坑1:状态管理选哪个?

Redux?MobX?Zustand?Recoil?Jotai?React Query?SWR?

新手直接懵。我的建议是:

  • 小应用:useState + useContext 够用,别折腾
  • 中等应用:Zustand(简单)或React Query(服务端状态)
  • 大型应用:Redux Toolkit(虽然啰嗦但规范)或MobX(面向对象党福音)
// Zustand示例,比Redux简单十倍 import { create } from 'zustand'; // 创建store,就是普通的hook const useStore = create((set) => ({ count: 0, user: null, // 直接写修改逻辑,不需要action、reducer那些概念 increment: () => set((state) => ({ count: state.count + 1 })), setUser: (user) => set({ user }), // 异步操作直接写async函数 fetchUser: async (id) => { const res = await fetch(`/api/users/${id}`); const user = await res.json(); set({ user }); } })); // 在组件中使用,自动优化重渲染(只订阅用到的状态) function UserProfile() { // 只订阅user,count变了不会触发重渲染 const user = useStore(state => state.user); const fetchUser = useStore(state => state.fetchUser); useEffect(() => { fetchUser(123); }, [fetchUser]); return <div>{user?.name}</div>; } 

坑2:闭包陷阱(Stale Closure)

这是React Hooks最经典的坑,新手必踩:

function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { // 这里count永远是0,因为effect只执行一次,捕获了初始值 console.log(count); // 永远输出0 setCount(count + 1); // 永远设置成1,然后停住 }, 1000); return () => clearInterval(timer); }, []); // 空依赖数组,只执行一次 return <div>{count}</div>; // 显示1,然后不动 } // 正确写法1:使用函数式更新 setCount(prev => prev + 1); // 拿到最新的state // 正确写法2:把count加入依赖数组(但这样每次count变都会重置定时器) useEffect(() => { const timer = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(timer); }, [count]); // 正确写法3:用useRef(如果需要最新的值但又不想触发effect重跑) const countRef = useRef(count); countRef.current = count; // 每次渲染都更新ref useEffect(() => { const timer = setInterval(() => { console.log(countRef.current); // 永远是最新值 }, 1000); }, []); // 空依赖也没事 

坑3:useEffect依赖数组写不全

ESLint插件react-hooks/exhaustive-deps一定要开,但有时候它提示的依赖你不想加,这时候要想想是不是逻辑设计有问题。

// 错误示例:忘记加依赖 function ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); }, []); // 警告:缺少依赖roomId // 如果roomId变了,effect不会重新执行,连接到错的房间! } // 正确写法 useEffect(() => { const connection = createConnection(roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); // 加上roomId,切换房间时自动重连 

Vue的坑:2升3的痛,谁升谁知道

坑1:Vue 2到Vue 3的迁移

如果你维护的是Vue 2项目,想升Vue 3,做好心理准备。虽然官方有迁移工具,但复杂项目基本得重构:

  • Options API → Composition API(虽然兼容,但新特性不支持)
  • Vuex → Pinia(Vue 3推荐的状态管理)
  • 事件总线(Event Bus)→ Mitt(Vue 3实例上没有 o n / on/ on/off了)
  • 过滤器(Filters)→ 计算属性或方法(Vue 3删了)
  • 模板里的key用法变了(v-for和v-if优先级也变了)
// Vue 2的写法,Vue 3不支持了// 过滤器{{ message | capitalize }}// 事件总线const bus =newVue(); bus.$on('event', handler); bus.$emit('event', data);// Vue 3的替代方案// 过滤器用计算属性或方法constcapitalize=(str)=> str.charAt(0).toUpperCase()+ str.slice(1);{{capitalize(message)}}// 事件总线用Mittimport mitt from'mitt';const bus =mitt(); bus.on('event', handler); bus.emit('event', data);

坑2:响应式丢失

Vue 2用Object.defineProperty,无法监听新增属性和数组索引;Vue 3用Proxy好了很多,但还是有坑:

// Vue 2的坑:新增属性不响应this.user.age =25;// 不触发更新!this.$set(this.user,'age',25);// 必须用$set// Vue 3的坑:解构响应式对象会丢失响应性const state =reactive({count:0,name:'vue'});const{ count }= state;// count变成普通数字了! count++;// 不会触发更新// Vue 3正确做法:用toRefs保持响应性import{ toRefs }from'vue';const state =reactive({count:0,name:'vue'});const{ count }=toRefs(state);// count是ref,count.value才是值 count.value++;// 正常触发更新// 或者在setup里直接返回state,模板里用state.countreturn{ state };// 模板:<div>{{ state.count }}</div>

坑3:异步组件和Suspense

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的坑:重,是真的重

坑1:启动慢,打包大

Angular默认把很多功能都打包进去,Hello World可能都几百KB。优化手段:

// 1. 懒加载路由,按需加载模块const routes: Routes =[{ path:'admin',loadChildren:()=>import('./admin/admin.module').then(m => m.AdminModule)// 只有访问/admin时才加载}];// 2. 使用Standalone组件(Angular 14+),不用NgModule@Component({ standalone:true,// 独立组件 imports:[CommonModule, RouterModule],// 直接导入依赖 template:`...`})exportclassMyComponent{}// 3. 构建时优化,angular.json配置{"architect":{"build":{"configurations":{"production":{"optimization":true,"aot":true,// 提前编译"buildOptimizer":true,"budgets":[// 设置打包体积预算{"type":"initial","maximumWarning":"500kb","maximumError":"1mb"}]}}}}}

坑2:RxJS内存泄漏

Angular里到处都是Observable,忘记取消订阅就是内存泄漏:

// 错误写法:组件销毁后订阅还在ngOnInit(){this.http.get('/api/data').subscribe(data =>{this.data = data;});}// 正确写法1:用async管道,模板里自动管理订阅 data$ =this.http.get('/api/data');// 模板:{{ data$ | async }}// 正确写法2:用takeUntil模式private destroy$ =newSubject<void>();ngOnInit(){this.http.get('/api/data').pipe(takeUntil(this.destroy$)).subscribe(data =>this.data = data);}ngOnDestroy(){this.destroy$.next();this.destroy$.complete();}// 正确写法3:Angular 16+的takeUntilDestroyed(最简洁)import{ takeUntilDestroyed }from'@angular/core/rxjs-interop';constructor(){this.http.get('/api/data').pipe(takeUntilDestroyed())// 自动在组件销毁时取消订阅.subscribe(data =>this.data = data);}

坑3:变更检测性能问题

Angular默认的变更检测策略是"检查所有组件",大应用会卡:

// 改成OnPush策略,只有输入变化或事件触发时才检查@Component({ selector:'app-heavy', changeDetection: ChangeDetectionStrategy.OnPush,// 关键! template:` <div>{{ data.name }}</div> <button (click)="update()">更新</button> `})exportclassHeavyComponent{@Input() data!:{ name:string};// 只有data引用变化时才重渲染constructor(private cd: ChangeDetectorRef){}update(){// 手动触发变更检测this.cd.markForCheck();// 或者立即检测// this.cd.detectChanges();}}

实战场景:到底该选哪个?

说了这么多,落地到具体项目怎么选?我列几个真实场景:

场景1:创业公司MVP,三周上线

选Vue 3。别犹豫,就图一个快。

  • 模板语法上手零成本,后端都能看懂
  • 官方生态完整,不用到处找库
  • 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)生态成熟,以后想转移动端方便
// 一个规范的React组件,带类型定义 import React, { memo, useCallback, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; // React Query处理服务端状态 interface User { id: number; name: string; email: string; role: 'admin' | 'user' | 'guest'; } interface UserCardProps { user: User; onSelect: (id: number) => void; isSelected: boolean; } // memo防止不必要的重渲染,但别滥用,比较也有成本 const UserCard = memo<UserCardProps>(({ user, onSelect, isSelected }) => { // useCallback缓存事件处理函数,避免子组件不必要的重渲染 const handleClick = useCallback(() => { onSelect(user.id); }, [onSelect, user.id]); // useMemo缓存计算结果 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)
  • 适合那种"一个项目维护十年"的企业级应用
// Angular的分层架构示例// 1. Model层exportinterfaceTransaction{ id:string; amount:number; currency: Currency; timestamp: Date; status: TransactionStatus;}exporttypeCurrency='CNY'|'USD'|'EUR';exporttypeTransactionStatus='pending'|'completed'|'failed';// 2. Service层,处理业务逻辑和数据@Injectable({ providedIn:'root'})exportclassTransactionService{privatereadonly apiUrl ='/api/transactions';constructor(private http: HttpClient,private logger: LoggerService,// 注入日志服务private auth: AuthService // 注入认证服务){}getTransactions(filters: TransactionFilter): Observable<Transaction[]>{const params =newHttpParams({ fromObject: filters asany});returnthis.http.get<Transaction[]>(this.apiUrl,{ params }).pipe(tap(data =>this.logger.log('Fetched transactions', data)),catchError(this.handleError('getTransactions',[])));}privatehandleError<T>(operation ='operation', result?:T){return(error:any): Observable<T>=>{this.logger.error(`${operation} failed: ${error.message}`);returnof(result asT);};}}// 3. Component层,只负责展示@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 // 性能优化})exportclassTransactionListComponentimplementsOnInit{ transactions: Transaction[]=[]; loading =false; error:string|null=null;constructor(private transactionService: TransactionService){}ngOnInit(){this.loadTransactions();}onFilterChange(filters: TransactionFilter){this.loadTransactions(filters);}privateloadTransactions(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优化三板斧

// 1. React.memo防重渲染(类组件用PureComponent) const ExpensiveComponent = memo(function MyComponent({ data, onUpdate }) { // 只有data或onUpdate引用变化时才重渲染 return <div>{/* 复杂渲染逻辑 */}</div>; }, (prevProps, nextProps) => { // 自定义比较函数,返回true表示不渲染 return prevProps.id === nextProps.id; }); // 2. useMemo缓存计算结果 const sortedList = useMemo(() => { return list.sort((a, b) => b.score - a.score); // 假设排序很耗时 }, [list]); // 3. useCallback缓存函数(配合memo使用) const handleSubmit = useCallback((values) => { api.submit(values).then(() => { onSuccess(); }); }, [onSuccess]); // 4. 代码分割,懒加载组件 const HeavyChart = lazy(() => import('./HeavyChart')); function Dashboard() { return ( <Suspense fallback={<Spinner />}> <HeavyChart /> </Suspense> ); } // 5. 虚拟列表(长列表优化) 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优化三板斧

// 1. OnPush变更检测策略(前面讲过)@Component({ changeDetection: ChangeDetectionStrategy.OnPush })// 2. 虚拟滚动(CDK自带)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> `})// 3. Web Workers处理复杂计算// angular.json配置"webWorkerTsConfig":"tsconfig.worker.json"// 使用const worker =newWorker(newURL('./app.worker',import.meta.url)); worker.onmessage=({ data })=>{console.log('计算结果:', data);}; worker.postMessage({ action:'heavyCalculation', payload: hugeData });

最后说几句心里话

写到这里快六千字了,估计能看到这的都是真爱。最后聊点技术之外的:

1. 别为了技术而技术

我见过太多团队,项目就三个页面,非要上微前端;明明是个表单,非要抽成"配置化中台";TypeScript类型写得比业务代码还长。真的,没必要。技术是用来解决问题的,不是制造问题的。

2. 框架只是工具,基础才是核心

不管React、Vue还是Angular,底层都是JavaScript。原型链、闭包、异步、事件循环这些搞不明白,用啥框架都写出一堆bug。建议定期回去啃《JavaScript高级程序设计》,常读常新。

3. 团队比框架重要

再牛的框架,遇到不配合的团队也是白搭。选型时要考虑:

  • 团队现有技术栈(重构成本)
  • 成员学习意愿(有人抗拒TS,强上会出事)
  • 招聘难度(小城市招Angular真的难)

4. 保持开放心态

别当框架原教旨主义者。React党别瞧不起Vue"太简单",Vue党别觉得React"太复杂",Angular党…嗯,Angular用户通常没空参与争论,他们在写单元测试。

现在的趋势是融合——Vue 3吸收了React的Hooks思想,React也在学Vue的编译优化(React Compiler),Angular…Angular在努力让自己不那么重(Standalone组件就是例子)。

5. 如果老板坚持用jQuery…

建议先沟通,拿出数据(性能、维护成本、招聘难度)说服他。如果说服不了,且公司技术氛围确实落后,那…你懂的,简历该更新就更新。技术人的时间很宝贵,别在刀耕火种的年代浪费青春。


好了,就写到这。希望这篇能帮你在选型时少纠结一会儿。记住,没有银弹,只有权衡。选定了就深入学,别三天两头换框架,那样永远在半桶水晃荡。

祝你的项目永不延期,npm install从不报错,生产环境永不出bug。🍻

在这里插入图片描述

Read more

我用Openclaw + Claude搭了一套自动写作系统,每天省3小时

我用Openclaw + Claude搭了一套自动写作系统,每天省3小时

这是我目前最重要的一套AI工作流。从信息获取到发布,几乎不用手动完成。 一、为什么我要搭建这套系统? 信息过载的困境 如果你也在持续关注AI,应该会有同样的感受: 信息太多了。 每天打开 X、公众号、GitHub、技术社区,都会冒出大量新内容。 AI模型更新、工具更新、Agent框架、自动化方案…… 想跟上这些信息,本身就已经是一项工作。 手动写作的低效循环 更别说: * 整理信息 * 找选题 * 写文章 * 配图 * 发布到各个平台 如果全部手动完成,写作就会变成一件非常消耗精力的事。 我一度也在这种状态里: 想持续输出,但写作本身占用了太多时间。 一个关键问题 后来我开始思考一个问题: 如果写作这件事可以被"系统化",会发生什么? 于是,我不再把AI当成写作工具。 而是开始搭一套完整的 AI写作工作流。 二、思路转变:从优化写作到优化流程 大多数人的AI写作方式 大多数人使用AI写作,是这样:

为什么“虚拟现实“和“增强现实“不同?——从虚拟到混合的视觉革命

🕶️ 为什么"虚拟现实"和"增强现实"不同?——从虚拟到混合的视觉革命 🌈 大家好,我是无限大,欢迎收看十万个为什么系列文章 希望今天的内容能对大家有所帮助 今天咱们来聊聊VR和AR这个"视觉科技的双生子"!想象一下,你戴着头显在虚拟世界里打游戏,仿佛身临其境;你用手机对着桌子,屏幕上出现一个3D模型,仿佛它真的在桌子上——这些炫酷的体验,都是VR和AR带来的!但你知道它们的区别吗? 🤔 核心问题:VR和AR的区别是什么?它们的技术原理和应用场景有何不同? 很多人觉得VR和AR是"一回事",其实它们差别很大!VR就像"完全进入另一个世界",而AR是"在现实世界里加东西"。今天咱们就来揭开它们的神秘面纱! VR和AR的本质 * 🎮 VR(Virtual Reality):虚拟现实,通过头显完全沉浸在虚拟世界中,

FPGA原理和应用

FPGA原理和应用

大家好,我是良许。 说到 FPGA,可能很多做嵌入式的朋友都听说过,但真正深入了解的可能不多。 作为一名嵌入式程序员,我在工作中虽然主要接触的是单片机和嵌入式 Linux,但在汽车电子领域,FPGA 也是一个非常重要的技术方向。 今天就来和大家聊聊 FPGA 的原理和应用,希望能帮助大家对这个"神秘"的器件有更清晰的认识。 1. FPGA 是什么 1.1 FPGA 的基本概念 FPGA 的全称是 Field Programmable Gate Array,翻译过来就是"现场可编程门阵列"。 这个名字听起来有点拗口,但其实很好理解。 我们可以把 FPGA 想象成一块"电子积木",你可以根据自己的需求,把这些积木搭建成不同的电路结构。 与我们常用的单片机(如 STM32)

基于腾讯云云服务器搭建一个Clawdbot,实现Telegram机器人自动回复

基于腾讯云云服务器搭建一个Clawdbot,实现Telegram机器人自动回复

哈咯大家好,这里依然是码农的搬运工!! 从25年开始,全球都开始走向AI,拥抱AI。 最近博主,也就是我,发现一个国外作者,【Peter Steinberger】在本月推出了一个新的智能体【Clawdbot】,首先我们可以先去官网看一下这个东西是什么:Clawdbot  那么我也是研究了一把,但是这个文档实在是差点把我这个大专生劝退,纯英文,废了九牛二虎之力,我才差不多看懂了。肯定有小伙伴比较好奇,那么文档给你们放出来你们也可以看看:https://docs.molt.bot/start/getting-started OK!话不多说,那我们开始实操一下: 首先呢,看了一下这个文档,安装环境还是不错的,macOS/Linux、Windows【Powershell/CMD】 而且作者还贴心的给了安装命令,这样就省了好大一部分精力。不需要费劲去git拉取代码编译了。【这里需要注意一点,macos系统得14+,作者只有13的系统,所以是没有办法弄mac的】 当然,如果有小伙伴就是头铁,还是想从git上拉代码,那我也给你贴一下这个文档,你来安装: