Skip to content

Vue 与 React 对比

Vue 和 React 是目前最流行的两大前端框架,很多人在学习或选择时会纠结。本文将从几个核心方面进行对比,帮助你快速理解两者的差异。

概述

特性VueReact
创立时间2014 年2013 年
作者尤雨溪Facebook
学习曲线较平缓较陡峭
社区生态丰富非常丰富
核心理念渐进式、易上手函数式、灵活

组件传参对比

父传子

Vue 使用 props

vue
<!-- Parent.vue -->
<template>
  <ChildComponent message="Hello Vue" :count="10" />
</template>

<script setup>
import ChildComponent from './ChildComponent.vue'
</script>

<!-- Child.vue -->
<template>
  <div>
    <p>{{ message }}</p>
    <p>Count: {{ count }}</p>
  </div>
</template>

<script setup>
defineProps({
  message: String,
  count: {
    type: Number,
    default: 0
  }
})
</script>

React 使用 props

jsx
// Parent.jsx
function Parent() {
  return <ChildComponent message="Hello React" count={10} />
}

// Child.jsx
function Child({ message, count = 0 }) {
  return (
    <div>
      <p>{message}</p>
      <p>Count: {count}</p>
    </div>
  )
}

子传父

Vue 使用 emit

vue
<!-- Child.vue -->
<template>
  <button @click="handleClick">点击发送数据</button>
</template>

<script setup>
const emit = defineEmits(['send-data'])

function handleClick() {
  emit('send-data', '来自子组件的数据')
}
</script>

<!-- Parent.vue -->
<template>
  <ChildComponent @send-data="handleReceived" />
</template>

<script setup>
function handleReceived(data) {
  console.log('收到数据:', data)
}
</script>

React 使用回调函数

jsx
// Child.jsx
function Child({ onSend }) {
  function handleClick() {
    onSend('来自子组件的数据')
  }
  
  return <button onClick={handleClick}>点击发送数据</button>
}

// Parent.jsx
function Parent() {
  function handleReceived(data) {
    console.log('收到数据:', data)
  }
  
  return <Child onSend={handleReceived} />
}

兄弟组件通信

Vue 使用事件总线或状态管理

javascript
// eventBus.js
import { mitt } from 'mitt'
const eventBus = mitt()

// ComponentA.vue
<script setup>
import eventBus from './eventBus.js'

function sendMessage() {
  eventBus.emit('message', '来自组件A')
}
</script>

// ComponentB.vue
<script setup>
import { onMounted, onUnmounted } from 'vue'
import eventBus from './eventBus.js'

onMounted(() => {
  eventBus.on('message', (data) => {
    console.log('收到消息:', data)
  })
})

onUnmounted(() => {
  eventBus.off('message')
})
</script>

React 使用状态提升或事件总线

jsx
// 使用状态提升到父组件
function Parent() {
  const [message, setMessage] = useState('')
  
  return (
    <>
      <ComponentA onMessage={setMessage} />
      <ComponentB message={message} />
    </>
  )
}

路由对比

Vue Router

javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/user/:id', component: User }  // 动态路由
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

<!-- App.vue -->
<template>
  <router-link to="/">首页</router-link>
  <router-link to="/about">关于</router-link>
  <router-view />
</template>

<!-- 组件中跳转 -->
<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

function goToUser(id) {
  router.push('/user/' + id)
}
</script>

React Router

jsx
// App.jsx
import { BrowserRouter, Routes, Route, Link, useNavigate } 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 />} />
        <Route path="/user/:id" element={<User />} />
      </Routes>
    </BrowserRouter>
  )
}

// 组件中跳转
function User() {
  const navigate = useNavigate()
  
  function goToHome() {
    navigate('/')
  }
  
  return <button onClick={goToHome}>返回首页</button>
}

全局状态管理对比

Vue 使用 Pinia(推荐)或 Vuex

Pinia 示例

javascript
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: '计数器'
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    }
  }
})

<!-- 组件中使用 -->
<script setup>
import { useCounterStore } from '../stores/counter'

const store = useCounterStore()

// 直接使用
console.log(store.count)
store.increment()

// 解构使用(保持响应式)
import { storeToRefs } from 'pinia'
const { count, doubleCount } = storeToRefs(store)
</script>

<template>
  <p>{{ store.count }}</p>
  <p>双倍: {{ store.doubleCount }}</p>
  <button @click="store.increment">+1</button>
</template>

Vuex 示例

javascript
// stores/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    asyncIncrement({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  getters: {
    doubleCount: state => state.count * 2
  }
})

// 组件中使用
this.$store.state.count
this.$store.commit('increment')

React 使用 Redux 或 Zustand

Zustand 示例(更简洁)

javascript
// stores/useCounterStore.js
import { create } from 'zustand'

const useCounterStore = create((set) => ({
  count: 0,
  name: '计数器',
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 })
}))

export default useCounterStore

// 组件中使用
import useCounterStore from '../stores/useCounterStore'

function Counter() {
  const { count, increment } = useCounterStore()
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+1</button>
    </div>
  )
}

Redux Toolkit 示例

javascript
// features/counter/counterSlice.js
import { createSlice, configureStore } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1 },
    decrement: (state) => { state.value -= 1 }
  }
})

export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer

// store.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer
  }
})

// 组件中使用
import { useDispatch, useSelector } from 'react-redux'
import { increment, decrement } from '../counterSlice'

function Counter() {
  const dispatch = useDispatch()
  const count = useSelector(state => state.counter.value)
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => dispatch(increment())}>+1</button>
      <button onClick={() => dispatch(decrement())}>-1</button>
    </div>
  )
}

模板语法对比

Vue 使用 HTML 模板

vue
<template>
  <div class="container">
    <h1>{{ title }}</h1>
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
    <button @click="handleClick">点击</button>
  </div>
</template>

React 使用 JSX

jsx
function MyComponent({ title, items }) {
  function handleClick() {
    console.log('点击')
  }
  
  return (
    <div className="container">
      <h1>{title}</h1>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <button onClick={handleClick}>点击</button>
    </div>
  )
}

数据响应式对比

Vue 自动响应式

vue
<script setup>
import { ref, reactive, computed } from 'vue'

// 简单类型用 ref
const count = ref(0)

// 复杂类型用 reactive
const state = reactive({
  name: 'Vue',
  items: []
})

// 计算属性
const doubleCount = computed(() => count.value * 2)

// 修改值
count.value++
// 或者
state.name = '新名称'
</script>

React 需要手动触发更新

jsx
import { useState, useReducer, useMemo } from 'react'

function Counter() {
  // useState 返回状态和更新函数
  const [count, setCount] = useState(0)
  
  // 复杂状态用 useReducer
  const [state, dispatch] = useReducer(reducer, initialState)
  
  // 计算属性用 useMemo
  const doubleCount = useMemo(() => count * 2, [count])
  
  // 修改值
  setCount(count + 1)
}

选择建议

场景推荐原因
快速上手项目Vue学习曲线平缓,文档友好
追求灵活性React自由度更高,生态更广
中小型项目Vue简单场景下更便捷
大型复杂项目React更适合大型团队协作
团队已有经验根据团队技术栈降低学习成本

总结

Vue 和 React 都是优秀的框架,没有绝对的好坏之分:

  • Vue:更注重开发体验,上手简单,适合快速开发
  • React:更注重灵活性和函数式编程,适合大型复杂项目

选择哪个取决于你的项目需求、团队背景和个人喜好。最重要的是掌握核心概念,这样才能在两个框架之间灵活切换。

持续更新中...