AnnGreen1 / article

0 stars 0 forks source link

Vue3 与 React 全方面对比 #19

Open AnnGreen1 opened 3 months ago

AnnGreen1 commented 3 months ago

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的值来渲染不同的标签在VueReact中的不同实现

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-vueVue CLI都有预置的JSX语法支持。所以说在Vue中如果你想写JSX这个它也是支持的,但是在React是没办法用字符串模板的方式写。

2. 组件 & 路由 & 状态管理

2.1 组件风格

  1. Vue2中采用选项式 API,但是由于它不够灵活,而且this指向不够简单,Vue3中给我们提供了组合式API的写法,组合式 API更偏向函数式编程的方式,它的复用能力和组合的能力更强,而且没有this指向问题,也是Vue比较推荐的写法。

  2. React16.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 className="my-component">
      {/* 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/PiniaReact采用Redux/Mobx

区别:

  1. 语法和API的不同:VuexPinia是专门为Vue.js设计的状态管理库,因此它们的语法和API都非常类似。而ReduxMobx可以在任何JavaScript应用程序中使用,因此它们的语法和API与特定的框架无关

  2. 数据流的不同:在Redux中,数据是通过单向数据流进行管理的,即action -> reducer -> store -> view。而在VuexPinia中,数据是通过Vuex storePinia store直接管理的,不需要reducer。而在Mobx中,数据则是通过响应式数据实现的。

  3. 异步处理的不同:在Redux中,异步处理通常需要使用中间件来处理异步操作。而在VuexPinia中,异步操作可以通过actions处理。而在Mobx中,则可以使用async/awaitreaction函数来处理异步操作。

  4. 开销和复杂性的不同:ReduxMobx都需要在应用程序中进行额外的设置和配置,并且在处理大量数据时可能会导致性能问题。而VuexPinia的设置和配置相对简单,并且在大多数情况下可以处理大量数据。

总的来说,VuexPinia适用于Vue.js应用程序,提供了一种简单和直接的状态管理方式,而ReduxMobx则可以在多种应用程序中使用,提供了更灵活的状态管理方案

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 样式对比

Vueclassstyle都有三种写法:字符串、数组、对象

Reactstyle只能写对象,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 ()=>{
      //更新前的触发
   }
})