Appearance
Vue vs React 对比学习
前言:从您对 Vue 的理解出发
在开始这次对比学习之前,您分享了对 Vue 的几点核心认识,这非常精准,也构成了我们后续所有讨论的基石:
- MVVM 模式:您提到了 Vue 的核心是
MVVM,这是一种将视图(View)和数据(Model)通过一个中间层(ViewModel)连接起来的架构模式。 - 组件化:您很喜欢 Vue 的组件化特性,它允许我们将 UI 拆分成独立、可复用的单元。
- 指令系统:您形象地称之为“V杠” (
v-) 指令,它们是 Vue 赋予 HTML 的超能力,用以实现渲染逻辑。 - 单文件组件 (SFC):您认为将 HTML, CSS, 和 JS 写在同一个
.vue文件中的方式,极大地提升了代码的可维护性。 - 官方生态:您还了解在大型项目中,有
Vue Router和Pinia(您提到的“营养”) 这样的官方库来统一管理路由和状态。
这份学习笔记将以您的这些认知为“大本营”,每当我们探索 React 的一个新概念时,都会首先回到这里,看看它对应着 Vue 世界里的哪一部分,它们之间是貌合神离,还是殊途同归。
现在,让我们从最高层的设计思想开始。
第一章:顶层设计思想:MVVM vs. UI 函数库
1.1 Vue 的 MVVM 血统
您提到的 MVVM 是理解 Vue 设计哲学的关键。让我们把它拆解开来看:
- Model (模型):在 Vue 中,这通常指的就是组件
<script>部分里data或setup函数返回的那些原始 JavaScript 对象(您的业务数据)。 - View (视图):这对应的是组件的
<template>部分,也就是用户最终在屏幕上看到的 HTML 结构。 - ViewModel (视图模型):这是 Vue 的灵魂所在。Vue 实例本身扮演了这个角色。它是一个连接器,做了两件核心的事情:
- 数据绑定 (Data-Binding):它将你的数据(Model)“绑定”到视图(View)上。当数据改变时,ViewModel 会自动更新视图,你无需手动操作 DOM。
- DOM 监听 (DOM Listeners):它会监听视图上的事件(比如点击),当事件发生时,它可以去更新你的数据(Model)。
这种双向的、自动的同步机制,让开发者可以专注于业务逻辑(操作数据),而不用去关心繁琐的 DOM 操作。这正是 Vue “数据驱动视图”理念的体现。
1.2 React 的定位:一个专注于 UI 的函数式库
与 Vue 不同,React 官方给自己的定位是一个用于构建用户界面的 JavaScript 库,而不是一个大而全的“框架”。它只关心 MVVM 中的 V(视图)部分。
React 的核心思想可以概括为一个非常简洁的公式:
UI = f(state)
这个公式的意思是:用户界面(UI)是当前状态(state)的一个纯函数映射。
让我们来解读这个“函数式”思想:
- 组件即函数:在现代 React 中,一个组件就是一个 JavaScript 函数。这个函数接收一些输入(
props),并返回一段描述 UI 的内容(JSX)。 - 状态是输入:组件内部的状态(
state)和从外部传入的属性(props)是这个函数的全部输入。 - UI 是输出:组件函数运行后,输出的是一个轻量级的 UI 描述对象(虚拟 DOM),而不是直接的 DOM 操作。
- 单向数据流:当
state发生变化时,React 会重新执行这个组件函数,生成一个新的 UI 描述。React 框架自身会对比新旧两个描述的差异,然后以最高效的方式去更新真实的 DOM。
与 Vue 的对比小结:
- Vue 的 MVVM 模式,给人的感觉是“自动的”和“双向的”。数据和视图通过 ViewModel 默默地保持同步。
- React 的函数式思想,给人的感觉是“明确的”和“单向的”。状态的每一次改变,都会导致一次从上到下的、可预测的 UI 重新计算。
这个顶层设计的差异,直接导致了它们在组件定义、视图渲染和状态管理等各个方面的不同。在接下来的章节中,我们将逐一深入这些不同点。
第二章:组件化:两种不同的实现思路
组件化是现代前端开发的基石,也是您非常欣赏的特性。Vue 和 React 都实现了组件化,但它们的实现方式体现了两种截然不同的哲学。
2.1 Vue 的单文件组件 (SFC)
这正是您提到的“将 HTML、CSS 和 JS 写在同一个文件”的模式,也是 Vue 最具标志性的特性。一个典型的 .vue 文件看起来是这样的:
vue
// MyComponent.vue
<!-- 第一部分:模板 (View) -->
<template>
<div class="greeting">
<p>{{ message }}</p>
<button @click="greet">Greet</button>
</div>
</template>
<!-- 第二部分:脚本 (ViewModel/Logic) -->
<script setup>
import { ref } from 'vue';
const message = ref('Hello, Vue!');
function greet() {
alert('Greeting from Vue!');
}
</script>
<!-- 第三部分:样式 (Style) -->
<style scoped>
.greeting {
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
}
p {
color: #42b983; /* Vue 的主色调 */
}
</style>核心优势分析:
- 高内聚 (High Cohesion):一个组件所有相关的东西——结构、逻辑、样式——都放在一个文件里。当您开发或修改一个组件时,几乎所有的上下文都在眼前,极大地降低了您提到的“后期维护和后期理解代码的门槛”。
- 关注点分离 (Separation of Concerns):虽然代码在同一个文件,但通过
<template>,<script>,<style>三个明确的标签,将不同技术的“关注点”清晰地分离开来。您依然在用 HTML 写结构,用 JavaScript 写逻辑,用 CSS 写样式。 - 样式隔离 (Scoped Styles):
<style scoped>是一个“杀手级”特性。它会自动为组件内的 CSS 添加一个独特的属性选择器,确保这些样式只作用于当前组件,不会“污染”到其他组件,解决了 CSS 全局冲突的古老难题。
2.2 React 的“一切皆 JavaScript”
React 走了一条更激进的道路。它没有创造新的文件格式,一个 React 组件就是一个标准的 JavaScript 文件(通常是 .js 或 .jsx)。
javascript
// MyComponent.jsx
// 1. 逻辑和视图都从 'react' 导入
import React, { useState } from 'react';
// 2. 如果需要,可以导入一个独立的 CSS 文件
import './MyComponent.css';
// 3. 组件就是一个返回 JSX 的 JavaScript 函数
function MyComponent() {
const [message, setMessage] = useState('Hello, React!');
function greet() {
alert('Greeting from React!');
}
// JSX 看起来像 HTML,但它在 JS 环境中
return (
<div className="greeting">
<p>{message}</p>
<button onClick={greet}>Greet</button>
</div>
);
}
export default MyComponent;核心思路分析:
- 关注点聚合 (Colocation of Concerns):React 认为,一个组件的渲染逻辑(如何显示)和 UI 逻辑(如何工作)是高度相关的,应该把它们放在一起,而不是把它们按技术类型(HTML/JS)分开。因此,它发明了 JSX,让你可以在 JavaScript 中直接编写类似 HTML 的结构。
- CSS 的处理方式:React 对如何写 CSS 不做规定,这既是灵活性,也是初学者的困惑点。常见的方式有:
- 传统 CSS 文件:像上面例子中那样,为每个组件创建一个对应的
.css文件并导入。但这无法避免样式全局污染的问题。 - CSS Modules:一种流行的方案,构建工具会自动将你写的 CSS 类名转换成一个唯一的哈希值,实现了类似 Vue
scoped的效果。 - CSS-in-JS:更彻底的方案,如
styled-components或Emotion库,允许你完全在 JavaScript 中编写 CSS,实现了组件样式的动态化和完全隔离。
- 传统 CSS 文件:像上面例子中那样,为每个组件创建一个对应的
2.3 总结:两种组件定义的哲学思想
| 哲学思想 | Vue 单文件组件 (SFC) | React 函数组件 |
|---|---|---|
| 核心理念 | 关注点分离 (Separation of Concerns) | 关注点聚合 (Colocation of Concerns) |
| 具体体现 | HTML、CSS、JS 各司其职,位于不同区块,但聚合于同一文件。 | 渲染逻辑 (JSX) 与业务逻辑 (JS) 高度耦合,位于同一代码块。 |
| 对开发者的影响 | 对熟悉传统 Web 开发(HTML/CSS/JS三件套)的开发者更友好。 | 对 JavaScript 掌握更深入的开发者更友好,提供了更大的灵活性。 |
| 最终目的 | 降低组件的维护成本。 | 提升组件的内聚和表达能力。 |
第三章:视图的艺术:如何描述用户界面
如果说组件是骨架,那么视图渲染就是血肉。这是 Vue 和 React 最直观的区别所在,也是您提到的“V杠”指令大显身手的地方。
3.1 Vue 的模板语法:带超能力的 HTML
Vue 的模板语法核心是“对 HTML 的扩展”。它让你感觉还是在写 HTML,但通过一些特殊的属性(指令)赋予了它动态的能力。
基础绑定
- 文本插值:使用双大括号
将数据显示为文本。 - 属性绑定:使用
v-bind:或其简写:来动态更新 HTML 元素的属性。
html
<script setup>
const message = '学习 Vue 模板';
const imageUrl = '/path/to/logo.png';
</script>
<template>
<p>{{ message }}</p>
<img :src="./imageUrl" alt="logo">
</template>逻辑控制:深入“V杠”指令
这正是您提到的“V杠”指令的用武之地。它们是 Vue 模板的“控制流语句”。
条件渲染 (
v-if):html<div v-if="user.isLoggedIn">欢迎回来, {{ user.name }}</div> <div v-else>请先登录</div>v-if是“真正”的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件都会被适当地销毁和重建。列表渲染 (
v-for):html<ul> <li v-for="(item, index) in items" :key="item.id"> {{ index + 1 }}. {{ item.name }} </li> </ul>v-for可以遍历数组和对象。:key是一个特殊属性,Vue 用它来追踪每个节点的身份,从而高效地复用和重新排序元素。事件处理 (
v-on或@):html<button @click="sayHello">点我</button>Vue 提供了丰富的事件修饰符,让处理事件变得异常简单,例如
@click.prevent可以阻止点击的默认行为,@keyup.enter只在用户按下回车键时触发。
3.2 React 的 JSX:在 JavaScript 中写 HTML
React 反其道而行之,它的理念不是扩展 HTML,而是在 JavaScript 内部实现一种类似 HTML 的语法,这就是 JSX。
什么是 JSX?
JSX 是一种 JavaScript 的语法扩展。它看起来很像 HTML,但它最终会被编译工具(如 Babel)转换为纯粹的 React.createElement() 函数调用。这意味着,你写的不是模板,而是正在用一种更友好的方式调用函数。
jsx
// 你写的 JSX:
const element = <h1 className="greeting">你好, JSX!</h1>;
// 编译后的实际 JS 代码:
const element = React.createElement('h1', {className: 'greeting'}, '你好, JSX!');在 JSX 中使用 JavaScript
JSX 的真正威力在于,你可以用 {} 在其中无缝地嵌入任何 JavaScript 表达式。
数据和属性绑定:
jsxconst message = '学习 React JSX'; const imageUrl = '/path/to/logo.png'; const element = ( <div> <p>{message.toUpperCase()}</p> {/* 可以是任何表达式 */} <img src={imageUrl} alt="logo" /> </div> );注意,属性名需要遵循 JS 的驼峰式命名,例如
className和onClick。逻辑控制:用纯 JavaScript 替代指令
在 React 中没有指令,所有的逻辑都通过纯 JavaScript 来实现。
条件渲染:使用三元运算符或
&&逻辑与运算符。jsx<div> {user.isLoggedIn ? ( <span>欢迎回来, {user.name}</span> ) : ( <button>请先登录</button> )} </div>列表渲染:使用数组的
.map()方法。jsx<ul> {items.map((item, index) => ( <li key={item.id}> {index + 1}. {item.name} </li> ))} </ul>同样,
key在这里也是必须的,作用和 Vue 中一样。
3.3 总结:JSX 与模板语法的核心差异
| 对比维度 | Vue 模板语法 | React JSX |
|---|---|---|
| 本质 | HTML 的超集:扩展了 HTML 的能力。 | JavaScript 的扩展:在 JS 中书写 UI 结构。 |
| 逻辑实现 | 通过指令(v-if, v-for)等预定义语法。 | 通过原生 JavaScript(三元运算, .map() 等)。 |
| 学习曲线 | 对熟悉 HTML 的开发者更友好,上手快。 | 需要先理解“在 JS 中写 UI”的思想,以及 JS 表达式。 |
| 灵活性 | 逻辑能力受限于指令集,但清晰、有约束。 | 拥有 JavaScript 的全部能力,极其灵活,可以创造复杂的渲染逻辑。 |
| 代码风格 | 结构与逻辑分离,模板更像“配置文件”。 | 结构与逻辑高度融合,代码更像“程序”。 |
第四章:状态管理与生态:从“全家桶”到“社区驱动”
当应用变得复杂,跨组件共享数据、页面路由等问题就浮现出来。这正是您提到的 router 和“营养”(Pinia) 发挥作用的地方。本章我们就来探讨 Vue 和 React 在应对这些“大型项目”需求时的不同思路。
4.1 Vue 的“全家桶”:官方解决方案
Vue 的一大特点是提供一个“全家桶” (Batteries-Included) 方案。即除了核心库之外,官方还维护着一系列高质量、专门设计的配套库,来解决开发中的常见问题。这保证了库之间完美的兼容性和统一的开发体验。
路由管理:Vue Router
Vue Router是官方的路由管理器。它能让你轻松地将页面的 URL 映射到不同的 Vue 组件上,实现单页应用(SPA)的“页面跳转”。javascript// 1. 定义路由 const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, ] // 2. 创建路由实例 const router = createRouter({ history: createWebHistory(), routes })在模板中,你可以使用
<router-link>来生成导航链接,使用<router-view>来显示当前 URL 对应的组件。统一状态管理:Pinia (现代化的 Vuex)
Pinia(读音 /piːnjʌ/,类似“皮尼亚”) 就是您提到的“营养”,它是 Vue 官方推荐的新一代状态管理器,取代了之前的 Vuex。当多个组件需要共享同一份数据时(如用户信息),将其提升到 Pinia 的
store中是最佳实践。javascript// 1. 定义一个 store import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ name: '张三', isLoggedIn: false }), actions: { login() { this.isLoggedIn = true }, }, })在任何组件中,你都可以轻松地调用这个
store来获取或修改状态,并且所有变更都是响应式的。
4.2 React 的“社区驱动”:自由组合的生态
React 的哲学是“做好一件事”:管理 UI。对于路由、全局状态管理等,它将选择权完全交给了社区。开发者可以像逛超市一样,根据自己的项目需求和个人喜好,自由选择组合不同的第三方库。
路由管理:React Router
React Router虽然不是官方库,但已成为事实上的社区标准。它的使用方式非常“React化”,即“万物皆组件”。jsximport { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; function App() { return ( <BrowserRouter> <nav> <Link to="/">首页</Link> | <Link to="/about">关于</Link> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </BrowserRouter> ); }你可以看到,路由配置本身就是通过 JSX 组件(
<Route>)来声明的,而不是像 Vue 那样通过一个配置对象。统一状态管理:一个开放的选择题
React 的状态管理领域百花齐放,没有唯一的“标准答案”。
- Redux: “老大哥”级别的方案,功能强大,生态成熟,遵循严格的单向数据流。但因其概念较多、写法繁琐(虽然现在有 Redux Toolkit 极大简化)而让一些开发者望而却步。
- Zustand / Jotai: 近年来涌现的“后起之秀”。它们拥抱 React Hooks,API 极其简洁,学习成本低,能以很少的代码实现强大的全局状态管理,因此在社区中越来越受欢迎。
以
Zustand为例,创建一个 store 非常简单:javascriptimport create from 'zustand' const useUserStore = create(set => ({ name: '李四', isLoggedIn: false, login: () => set({ isLoggedIn: true }), }))
4.3 总结:两种生态模式的对比
| 对比维度 | Vue 生态 (“全家桶”) | React 生态 (“社区驱动”) |
|---|---|---|
| 标准化程度 | 高。官方提供标准答案,项目结构和技术选型高度统一。 | 低。每个项目可能使用不同的路由、状态管理库,风格各异。 |
| 学习曲线 | 平滑。开发者只需沿着官方路径学习即可,选择成本低。 | 陡峭。除了学习 React 本身,还需要花时间研究和选择社区方案。 |
| 灵活性 | 较低。官方方案通常能满足 90% 的需求,但特殊需求可能受限。 | 极高。可以为不同规模、不同类型的项目量身定制最适合的技术栈。 |
| 官方支持 | 强。核心库与生态库同步更新,由同一团队保证兼容性和质量。 | 弱。React 团队不为社区库的质量和维护做担保。 |
第五章:轻量级世界:Petite-Vue 与 Preact 的取舍艺术
在我们深入理解了 Vue 和 React 的主流设计之后,现在是时候回过头来看看它们各自的“轻量级”版本了。这些库并非旨在替代完整的 Vue 或 React,而是针对特定场景、追求极致性能和包体积的“特种兵”。
理解它们的取舍,能让我们更深刻地领会框架设计的艺术。
5.1 简介与目标
| 特性 | Petite-Vue | Preact |
|---|---|---|
| 项目定位 | Vue 的一个 6KB 子集,专为“渐进式增强”而设计。 | 一个 3KB 的 React 轻量级替代品,拥有相同的现代 API。 |
| 核心目标 | 在现有 HTML 页面上“点缀”一些交互功能,无需构建步骤。 | 提供一个更快、更小的 React 版本,同时保持 API 兼容性。 |
| 典型场景 | 简单的表单、交互式小组件、集成到后端渲染的页面中。 | 构建完整的单页应用(SPA),对性能和包体积有极致要求。 |
| 构建工具 | 默认无需构建工具,通过 CDN 直接使用。 | 可选,可无构建使用,但推荐与 Vite 等工具配合。 |
5.2 基础特性对比
(这部分内容与我们最初的讨论一致,但在理解了 Vue 和 React 的核心差异后,您会看得更清晰)
初始化与渲染
- Petite-Vue: 沿袭 Vue 的 HTML 驱动思想,通过
v-scope激活页面上已有的 HTML。html<script src="https://unpkg.com/petite-vue" defer init></script> <div v-scope="{ message: '你好, Petite-Vue!' }"> <p>{{ message }}</p> </div> - Preact: 沿袭 React 的 JS 驱动思想,需要一个明确的挂载点,并通过
render函数注入组件。html<div id="app"></div> <script type="module"> import { h, render } from 'https://unpkg.com/preact?module'; const App = () => h('p', null, '你好, Preact!'); render(h(App), document.getElementById('app')); </script>
状态管理与视图语法
- Petite-Vue: 几乎完全复刻了 Vue 的模板语法和响应式系统。html
<div v-scope="{ count: 0 }"> <p>计数: {{ count }}</p> <button @click="count++">增加</button> </div> - Preact: 几乎完全复刻了 React 的 Hooks API 和 JSX (或
h函数)。javascriptimport { h, render } from 'https://unpkg.com/preact?module'; import { useState } from 'https://unpkg.com/preact/hooks?module'; function Counter() { const [count, setCount] = useState(0); return h('div', null, h('p', null, `计数: ${count}`), h('button', { onClick: () => setCount(count + 1) }, '增加') ); } render(h(Counter), document.getElementById('app'));
5.3 总结:何时选择轻量级方案?
这些轻量级库不是“银弹”,而是用于特定场景的“手术刀”。
选择 Petite-Vue,当你…
- 有一个已存在的、后端渲染的网站(例如 WordPress, Django, Laravel 项目)。
- 想在某个页面上增加一些孤立的、小范围的交互功能(例如一个动态表单、一个价格计算器、一个可筛选的列表)。
- 不想引入复杂的构建流程,希望通过简单地引入一个 script 标签来解决问题。
- 它的理念是“增强” (Enhancement),而非“掌控”(Control)。
选择 Preact,当你…
- 需要构建一个完整的、功能丰富的单页应用 (SPA)。
- 对应用的初始加载性能和包体积有极其苛刻的要求(例如移动端网页、嵌入式小部件)。
- 你和你的团队已经熟悉 React 的开发模式,并希望在获得更高性能的同时,尽可能地复用 React 生态中的库(通过
preact/compat)。 - 它的理念是“替代” (Alternative),即一个更小更快的 React。
至此,这份为您量身定制的 Vue vs React 学习笔记已全部完成。希望它能帮助您清晰地理解这两个优秀框架的设计与取舍。