Vue3 與 React 全方面對比

1. 編程風格 & 視圖風格

1.1 編程風格

  1. React 語法少、難度大;Vue 語法多,難度小

例如指令:

Vue

<input v-model="username"/>

<ul>
    <li v-for="(item,index) in list" :key="index">{{ item }}</li>
</ul>

React

<input value={username} onChange={e => setUsername(e.target.value)}/>


<ul>
{ list.map((item,index) => <li key={index}>{item}</li>) }
</ul>

Vue 給我們提供了很多的指令功能,而這些功能在 React 中基本都需要我們使用原生 js 來實現。

所以會有很多人說: "使用 Vue 實際上你就是在操作 Vue,使用 React 實際上你是在操作 js"。

  1. React 魔改少,手動實現;Vue 魔改多,自動完成。

例如事件:

Vue

<button @click="handleClick('hello')">點擊</button>

const handleClick = (msg) => {
  console.log('msg')
}

React

<button onClick="handleClick('hello')">點擊</button>

const handleClick = (msg) => {
    return () => {
        console.log(msg)
    }
}

像在點擊事件中傳參數這種功能:

  1. 我們知道 dom 的點擊事件是需要我們傳遞一個函數過去的,就像在 React 中例子一樣,你的 handleClick 肯定需要返回一個函數(或者在 jsx 中寫箭頭函數調用 handleClick)。

  2. 而在 Vue 中可以在 @click 中直接調用 handleClick 函數,而這個函數又沒有返回一個新的函數,按道理這樣調用 handleClick 是會返回 undefined 的,但是由於 Vue 底層做了魔改優化,使得我們不再需要在返回一個函數。

上面兩個例子中,我們說不上哪種好哪種不好,只能說你更喜歡哪一種。React 中的實現更符合 js 的邏輯但卻稍顯麻煩,Vue 中的實現簡單但卻沒有遵循原生 js 的特點。

編程風格上的總結:就像我們前面講的,Vue 寫起來更像是寫 Vue 代碼,React 寫起來更像是寫 JavaScript 代碼。

1.2 視圖風格

  1. Vue 採用 <template> 字符串模板。更貼近 HTML,學習成本低,但有時候不靈活。

  2. React 採用 JSX 語法,更類似於 js ,限制比較多,(像一些關鍵字 classfor,單標籤要閉合、屬性要駝峯、組件名要大寫等等這些都要注意),但是可以跟模板語法很好的進行結合

比如下面是一個通過 level 的值來渲染不同的標籤在 Vue 和 React 中的不同實現

Vue

<template>
    <h1 v-if="level === 1">標題1</h1>
    <h2 v-if="level === 2">標題2</h1>
</template>

React

let App = () => {
    const level = 1
    const Tag = 'h' + level
    return (
        <div>
            { <Tag>標題{level}</Tag>}
        </div>
    )
}

可以想象,如果當我們的條件判斷很多時,使用 JSX 的方式會比使用模版字符串要靈活的多。

注意Vue 一開始並不直接支持 JSX ,在 Vue 2.1.0 版本中,Vue 引入了 render 函數來代替模板,這使得使用 JSX 作爲組件渲染函數成爲可能。在Vue 2.1.0版本後的 create-vue 和 Vue CLI 都有預置的 JSX 語法支持。所以說在 Vue 中如果你想寫 JSX 這個它也是支持的,但是在 React 是沒辦法用字符串模板的方式寫。

2. 組件 & 路由 & 狀態管理

2.1 組件風格

  1. Vue2 中採用 選項式 API,但是由於它不夠靈活,而且 this 指向不夠簡單,Vue3 中給我們提供了 組合式 API 的寫法,組合式 API 更偏向函數式編程的方式,它的複用能力和組合的能力更強,而且沒有 this 指向問題,也是 Vue 比較推薦的寫法。

  2. React 在 16.8 版本之前都是採用類組件的方式開發,類組件也會有 this 指向以及寫起來很繁瑣難度大的問題,在 16.8 之後 React 提供了函數組件的寫法,其實函數組件和 Vue 的 組合式 API 是很像的,它的組合和複用的能力更強,而且也沒有 this 指向問題,比類組件寫起來簡單很多,也是 React 比較推薦的寫法

Vue 組件示意圖:

<template>
  <div class="my-component">
    <!-- HTML模板 -->
  </div>
</template>

<script>
export default {
  // JavaScript代碼
}
</script>

<style>
.my-component {
  /* CSS樣式 */
}
</style>

React 組件示意圖:

import React from 'react';
import './MyComponent.css';

function MyComponent() {
  // JavaScript代碼
  return (
    <div class>
      {/* HTML模板 */}
    </div>
  );
}

export default MyComponent;

總結:這兩種框架它們的最終趨勢都是函數式編程,不管是 Vue 還是 React 都是推薦我們引入大量內置的函數或者是 use 函數來進行組合並且完成我們的開發需求。而簡化使用面向對象或者是配置的寫法,能簡化我們使用 this 的場景從而提升代碼的靈活度和簡易度。

2.2 路由風格

Vue 採用 Vue-Router;React 採用 React-Router

相比而言 vue 語法更加簡練(useRouter useRoute),而 react 的 use 函數太多,不夠統一化(useLocation、useParams、useSearchParams、useNavigate......)

而像下面這些常規的功能它們都是大差不差的:

  1. 路由表的配置

  2. 嵌套路由

  3. 動態路由

  4. 編程式路由

  5. 守衛路由

Vue-Router 示例代碼

<!-- index.html -->
<div id="app">
  <router-view></router-view>
</div>
// main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import Home from './components/Home.vue'
import About from './components/About.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About }
  ]
})

const app = createApp({
  // 空的 `setup` 函數
  setup() {}
})

app.use(router)
app.mount('#app')
<!-- Home.vue -->
<template>
  <div>
    <h1>Home Page</h1>
    <button @click="goToAbout">Go to About Page</button>
  </div>
</template>

<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

const goToAbout = () => {
  router.push('/about')
}
</script>
<!-- About.vue -->
<template>
  <div>
    <h1>About Page</h1>
    <p>Param: {{ $route.params.id }}</p>
    <router-link to="/">Go to Home Page</router-link>
  </div>
</template>

<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()
const id = route.params.id
</script>

React-Router 示例代碼

import React from 'react'
import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'

const App = () => {
  return (
    <Router>
      <div>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
        </ul>

        <hr/>

        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/about">
            <About />
          </Route>
        </Switch>
      </div>
    </Router>
  )
}

const Home = () => {
  const history = useHistory()
  const handleClick = () => {
    history.push('/about')
  }
  return (
    <div>
      <h1>Home Page</h1>
      <button onClick={handleClick}>Go to About Page</button>
    </div>
  )
}

const About = () => {
  const { id } = useParams()
  return (
    <div>
      <h1>About Page</h1>
      <p>Param: {id}</p>
      <Link to="/">Go to Home Page</Link>
    </div>
  )
}

export default App

2.3 狀態管理風格

Vue 採用 Vuex/Pinia ;React 採用 Redux/Mobx

區別:

  1. 語法和 API 的不同:Vuex 和 Pinia 是專門爲 Vue.js 設計的狀態管理庫,因此它們的語法和 API 都非常類似。而 Redux 和 Mobx 可以在任何 JavaScript 應用程序中使用,因此它們的語法和 API 與特定的框架無關

  2. 數據流的不同:在 Redux 中,數據是通過單向數據流進行管理的,即 action -> reducer -> store -> view。而在 Vuex 和 Pinia 中,數據是通過 Vuex store 或 Pinia store 直接管理的,不需要 reducer。而在 Mobx 中,數據則是通過響應式數據實現的。

  3. 異步處理的不同:在 Redux 中,異步處理通常需要使用中間件來處理異步操作。而在 Vuex 和 Pinia 中,異步操作可以通過 actions 處理。而在 Mobx 中,則可以使用 async/await 或 reaction 函數來處理異步操作。

  4. 開銷和複雜性的不同:Redux 和 Mobx 都需要在應用程序中進行額外的設置和配置,並且在處理大量數據時可能會導致性能問題。而 Vuex 和 Pinia 的設置和配置相對簡單,並且在大多數情況下可以處理大量數據。

總的來說,Vuex 和 Pinia 適用於 Vue.js 應用程序,提供了一種簡單和直接的狀態管理方式,而 Redux 和 Mobx 則可以在多種應用程序中使用,提供了更靈活的狀態管理方案

Pinia 示例代碼

// store.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++
    },
  },
})
<!-- App.vue -->
<template>
  <div>
    <h1>Count: {{ count }}</h1>
    <button @click="incrementCount">Increment</button>
  </div>
</template>

<script setup>
import { defineComponent } from 'vue'
import { useCounterStore } from './store'

const counterStore = useCounterStore()
const count = counterStore.count
const incrementCount = () => {
  counterStore.increment()
}
</script>

<!-- 在根組件中注入 store -->
<script>
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
</script>

Redux Toolkit 示例代碼

// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 0
  },
  reducers: {
    increment(state) {
      state.count++
    }
  }
})

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

export const { increment } = counterSlice.actions;
// App.js
import { useSelector, useDispatch } from 'react-redux'
import { increment } from './store'

function App() {
  const count = useSelector(state => state.counter.count)
  const dispatch = useDispatch()

  const incrementCount = () => {
    dispatch(increment())
  }

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={incrementCount}>Increment</button>
    </div>
  )
}

export default App

// 在根組件中注入 store
import { Provider } from 'react-redux'
import { store } from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

3. 一些基礎功能

3.1 模板對比

Vue 的視圖變化主要通過:指令 + 模板的方式

React 的視圖變化主要通過:原生 JS + 模板的方式

React 的模板比較強大,因爲可以編寫 JSX 結構,所以可以做出更加靈活的結構處理。

3.2 樣式對比

Vue 的 class 和 style 都有三種寫法:字符串、數組、對象

React 的 style 只能寫對象,class 只能字符串,可藉助 classnames 這個庫

兩個框架基本上都可以滿足常見的樣式需求。

3.3 事件對比

Vue 事件功能豐富

React 事件傳參需要高階處理

<!-- Vue -->
<template>
    <ul>
        <li v-for="item,index in list" @click="handleClick(index)"></li>
    </ul>
</template>
<script>
methods: {
 handleClick(index){
 
    }
}
</script>
<!-- React -->
<ul>
{ 
    list.map((v, i)=> <li onClick={handleClick(i)}></li>)
}
</ul>
const handleClick = (index) => {
    return () => {
        console.log(index)
    }    
}

3.4 表單對比

Vue 表單雙向綁定 v-model

React 表單受控與非受控

針對表單操作這一塊來說,Vue 的表單指令 v-model 還是非常靈活的,總體對比要比 React 使用方便且靈活。

3.5 組件通信對比

Vue 父子組件通過 props屬性通信,子父組件通過 emits 方法通信

React 父子組件也是通過 props屬性通信,而子父組件則是通過回調函數通信的

emits 自定義事件和回調函數,實際上是一樣的思想。

跨組件的通信方案也很類似,都是一種依賴注入的方式來實現的。

3.6 邏輯複用

Vue 選項式採用:mixins混入;組合式採用:use函數

React 類組件採用:Render PropsHOC;函數組件:use函數

可以發現組合式 API 和函數組件都是採用 use 函數,所以基本複用是差不多的思想,這也是兩個框架推薦的用法。

3.7 內容分發

Vue 通過插槽,進行接收

React 通過 props.children,進行接收

3.8 DOM 操作

Vue 通過 ref 屬性

React 也通過 ref 屬性處理

思路都是差不多的,就是給元素添加 ref 屬性,在跟對象或字符串綁定在一起,這樣就可以直接獲取到 DOM 元素。

4. 響應式 & 生命週期 & 副作用

4.1 響應式數據對比

Vue 採用響應式數據,底層通過 new Proxy() 進行監控,靈活性更高

React 採用 state 狀態,通過 setState() 方法進行內部 re-render,可控性更強

4.2 生命週期對比

Vue 生命週期鉤子 (常見)

  1. beforeCreate

  2. created

  3. beforeMount

  4. mounted

  5. beforeUpdate

  6. updated

  7. beforeUnmount

  8. unmounted

React 生命週期鉤子 (常見)

  1. constructor

  2. componentDidMount

  3. componentDidUpdate

  4. componentWillUnmount

  5. render 整體對比來看,Vue 的生命週期會更豐富一些,React 生命週期會更簡約一些。

4.3 副作用處理對比

vue 使用,watchEffect()

react 使用,useEffect()

都是處理副作用的方法,用法上還是有很大區別的。

watchEffect 會自動根據所依賴的值進行重渲染,而 useEffect 要明確指定對應的值才能進行重渲染,React 團隊已經給出在未來的版本中可能會改成根據所依賴的值自動進行重渲染的操作,但暫時還不行。

watchEffect 在更新前和卸載前觸發的方式是通過回調函數的參數被調用來實現的,而 useEffect 是通過 return 的返回值來指定的。

// Vue
watchEffect((cb)=>{
 cb(()=>{
       //更新前的觸發
    })
})
// React
useEffect(()=>{
   return ()=>{
      //更新前的觸發
   }
})

結語

作者:前端要努力 QAQ
鏈接:https://juejin.cn/post/7210918245993611301
來源:稀土掘金

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MzEw2dpV5FAoeRQXrWaMXw