Skip to content

Vue vs React 对比学习

前言:从您对 Vue 的理解出发

在开始这次对比学习之前,您分享了对 Vue 的几点核心认识,这非常精准,也构成了我们后续所有讨论的基石:

  1. MVVM 模式:您提到了 Vue 的核心是 MVVM,这是一种将视图(View)和数据(Model)通过一个中间层(ViewModel)连接起来的架构模式。
  2. 组件化:您很喜欢 Vue 的组件化特性,它允许我们将 UI 拆分成独立、可复用的单元。
  3. 指令系统:您形象地称之为“V杠” (v-) 指令,它们是 Vue 赋予 HTML 的超能力,用以实现渲染逻辑。
  4. 单文件组件 (SFC):您认为将 HTML, CSS, 和 JS 写在同一个 .vue 文件中的方式,极大地提升了代码的可维护性。
  5. 官方生态:您还了解在大型项目中,有 Vue RouterPinia (您提到的“营养”) 这样的官方库来统一管理路由和状态。

这份学习笔记将以您的这些认知为“大本营”,每当我们探索 React 的一个新概念时,都会首先回到这里,看看它对应着 Vue 世界里的哪一部分,它们之间是貌合神离,还是殊途同归。

现在,让我们从最高层的设计思想开始。


第一章:顶层设计思想:MVVM vs. UI 函数库

1.1 Vue 的 MVVM 血统

您提到的 MVVM 是理解 Vue 设计哲学的关键。让我们把它拆解开来看:

  • Model (模型):在 Vue 中,这通常指的就是组件 <script> 部分里 datasetup 函数返回的那些原始 JavaScript 对象(您的业务数据)。
  • View (视图):这对应的是组件的 <template> 部分,也就是用户最终在屏幕上看到的 HTML 结构。
  • ViewModel (视图模型):这是 Vue 的灵魂所在。Vue 实例本身扮演了这个角色。它是一个连接器,做了两件核心的事情:
    1. 数据绑定 (Data-Binding):它将你的数据(Model)“绑定”到视图(View)上。当数据改变时,ViewModel 会自动更新视图,你无需手动操作 DOM。
    2. DOM 监听 (DOM Listeners):它会监听视图上的事件(比如点击),当事件发生时,它可以去更新你的数据(Model)。

这种双向的、自动的同步机制,让开发者可以专注于业务逻辑(操作数据),而不用去关心繁琐的 DOM 操作。这正是 Vue “数据驱动视图”理念的体现。

1.2 React 的定位:一个专注于 UI 的函数式库

与 Vue 不同,React 官方给自己的定位是一个用于构建用户界面的 JavaScript 库,而不是一个大而全的“框架”。它只关心 MVVM 中的 V(视图)部分。

React 的核心思想可以概括为一个非常简洁的公式:

UI = f(state)

这个公式的意思是:用户界面(UI)是当前状态(state)的一个纯函数映射

让我们来解读这个“函数式”思想:

  1. 组件即函数:在现代 React 中,一个组件就是一个 JavaScript 函数。这个函数接收一些输入(props),并返回一段描述 UI 的内容(JSX)。
  2. 状态是输入:组件内部的状态(state)和从外部传入的属性(props)是这个函数的全部输入。
  3. UI 是输出:组件函数运行后,输出的是一个轻量级的 UI 描述对象(虚拟 DOM),而不是直接的 DOM 操作。
  4. 单向数据流:当 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 不做规定,这既是灵活性,也是初学者的困惑点。常见的方式有:
    1. 传统 CSS 文件:像上面例子中那样,为每个组件创建一个对应的 .css 文件并导入。但这无法避免样式全局污染的问题。
    2. CSS Modules:一种流行的方案,构建工具会自动将你写的 CSS 类名转换成一个唯一的哈希值,实现了类似 Vue scoped 的效果。
    3. CSS-in-JS:更彻底的方案,如 styled-componentsEmotion 库,允许你完全在 JavaScript 中编写 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 表达式。

  • 数据和属性绑定

    jsx
    const message = '学习 React JSX';
    const imageUrl = '/path/to/logo.png';
    
    const element = (
      <div>
        <p>{message.toUpperCase()}</p> {/* 可以是任何表达式 */}
        <img src={imageUrl} alt="logo" />
      </div>
    );

    注意,属性名需要遵循 JS 的驼峰式命名,例如 classNameonClick

  • 逻辑控制:用纯 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化”,即“万物皆组件”。

    jsx
    import { 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 非常简单:

    javascript
    import 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-VuePreact
项目定位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 函数)。
    javascript
    import { 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 学习笔记已全部完成。希望它能帮助您清晰地理解这两个优秀框架的设计与取舍。