从Vue3到React:初学者的对比指南

528 阅读6分钟

我是一个vue开发者,然后最近公司,有一个新的桌面端项目使用的技术栈有react,所以最近就开始学了一些。 本文主要是写一点vue中的常用功能在react应该如何去写。

项目结构与入口文件对比

Vue3的项目入口

在Vue3中,项目的入口一般是main.js文件,在这里我们创建Vue应用实例并挂载到DOM元素上:

// Vue3 的 main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

createApp(App)
  .use(router)
  .use(store)
  .mount('#app')

React的项目入口

而在React中,入口文件通常是index.js(或index.tsx如果使用TypeScript),结构如下:

// React的 index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
);

React的index.js相当于Vue的main.js,都是应用的启动点,但React使用了不同的API进行DOM渲染。单从这个看起来貌似React使用起来好像更加的复杂一点(可能是我看vue看习惯了,哈哈)

React的"三件套"vs Vue的单文件组件

Vue3的单文件组件(.vue)

Vue使用单文件组件(SFC)将HTML、CSS和JavaScript封装在一个.vue文件中:

<!-- Vue3的组件结构 -->
<template>
  <div class="greeting">{{ message }}</div>
</template>

<script setup>
import { ref } from 'vue';
const message = ref('Hello Vue!');
</script>

<style scoped>
.greeting {
  color: blue;
}
</style>

React的组件结构

React则通常将组件定义在.js.jsx文件中,并使用JSX将HTML结构嵌入到JavaScript中:

// React的组件结构
import React, { useState } from 'react';
import './Greeting.css'; // 导入CSS文件

function Greeting() {
  const [message, setMessage] = useState('Hello React!');
  
  return (
    <div className="greeting">{message}</div>
  );
}

export default Greeting;

CSS通常分离到单独的文件中,或使用CSS-in-JS解决方案(这个还没有研究,只是知道有这个东西):

/* Greeting.css */
.greeting {
  color: blue;
}

Vue的写法更贴近传统前端开发,将HTML、CSS和JavaScript以类似我们最初学习web开发时的方式组织在一起。而React采用的组件化思路则将这三者分离处理,特别是通过JSX将HTML结构直接融入JavaScript中,好在我有点点jsx基础,不然还不好搞。

生命周期对比

Vue3的组合式API生命周期

Vue3中使用组合式API管理生命周期:

import { onMounted, onUpdated, onUnmounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    onMounted(() => {
      console.log('组件已挂载');
    });
    
    onUpdated(() => {
      console.log('组件已更新');
    });
    
    onBeforeUnmount(() => {
      console.log('组件即将卸载');
    });
    
    onUnmounted(() => {
      console.log('组件已卸载');
    });
  }
};

React的生命周期

React的函数组件使用useEffect钩子来模拟生命周期行为:

import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  // 相当于componentDidMount和componentDidUpdate
  useEffect(() => {
    console.log('组件已挂载或更新');
    
    // 返回的函数相当于componentWillUnmount
    return () => {
      console.log('组件即将卸载或更新前的清理');
    };
  });
  
  // 仅在挂载和卸载时执行(空依赖数组)
  useEffect(() => {
    console.log('组件已挂载');
    return () => {
      console.log('组件即将卸载');
    };
  }, []);
  
  // 仅在count变化时执行
  useEffect(() => {
    console.log('count已更新:', count);
  }, [count]);
  
  return <div>Count: {count}</div>;
}

React的useEffect钩子通过不同的依赖项配置,可以灵活地模拟各种生命周期行为,这一点比Vue的生命周期钩子更统一,这个我比较喜欢。

响应式数据处理

Vue3的响应式系统

Vue3使用refreactive创建响应式数据:

import { ref, reactive, computed } from 'vue';

// 在setup中
const count = ref(0);
const user = reactive({
  name: 'Alice',
  age: 25
});

// 修改响应式数据
const increment = () => {
  count.value++;
};

const updateUser = () => {
  user.age = 26;
  user.name = 'Bob';
};

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

React的响应式状态管理

React使用useStateuseReducer钩子来管理组件状态:

import React, { useState, useMemo } from 'react';

function Counter() {
  // 创建状态
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({
    name: 'Alice',
    age: 25
  });
  
  // 修改状态
  const increment = () => {
    setCount(count + 1);
    // 或者使用函数式更新
    // setCount(prevCount => prevCount + 1);
  };
  
  const updateUser = () => {
    // React中对象更新需要创建新对象
    setUser({
      ...user,  // 保留原有属性
      age: 26,
      name: 'Bob'
    });
  };
  
  // 类似Vue的computed - useMemo
  const doubleCount = useMemo(() => count * 2, [count]);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <p>User: {user.name}, {user.age}</p>
      <button onClick={increment}>增加</button>
      <button onClick={updateUser}>更新用户</button>
    </div>
  );
}

React与Vue处理状态的主要区别:

  1. React使用setState函数更新状态,而不是直接修改
  2. 对象和数组更新需要创建新引用(不可变数据模式)
  3. 没有自动的响应式系统,需要显式指定依赖
  4. 使用useMemo作为计算属性的替代方案

对复杂对象的更新尤其需要注意,React强调不可变性,每次更新都是替换而非修改。

计算属性与数据监听

Vue3的计算属性和侦听器

import { ref, computed, watch, watchEffect } from 'vue';

const count = ref(0);

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

// 侦听器
watch(count, (newValue, oldValue) => {
  console.log(`count从${oldValue}变为${newValue}`);
});

// 副作用
watchEffect(() => {
  console.log(`当前count: ${count.value}`);
});

React中的等效实现

React使用useMemo实现计算属性,使用useEffect模拟侦听器:

import { useState, useMemo, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  
  // 计算属性 - useMemo
  const doubleCount = useMemo(() => count * 2, [count]);
  
  // 侦听器 - useEffect + 特定依赖
  useEffect(() => {
    console.log(`count变化为: ${count}`);
  }, [count]);
  
  // 副作用 - 类似watchEffect
  useEffect(() => {
    console.log(`当前count: ${count}`);
    // 其他依赖于count的逻辑
  }, [count]); // 指定依赖项以模拟watchEffect
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

条件渲染与列表

Vue3的实现

使用v-ifv-else进行条件渲染,使用v-for进行列表渲染

<template>
  <div v-if="showMessage">欢迎!</div>
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ index }}: {{ item.text }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue';
const showMessage = ref(true);
const items = ref([
  { id: 1, text: '学习Vue' },
  { id: 2, text: '学习React' },
  { id: 3, text: '构建项目' }
]);
</script>

React的实现

这里主要是jsx的写法。

import React, { useState } from 'react';

function ExampleComponent() {
  const [showMessage, setShowMessage] = useState(true);
  const [items, setItems] = useState([
    { id: 1, text: '学习Vue' },
    { id: 2, text: '学习React' },
    { id: 3, text: '构建项目' }
  ]);
  
  return (
    <div>
      {showMessage && <div>欢迎!</div>}
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {index}: {item.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

React使用JavaScript的逻辑运算符和数组方法来实现条件渲染和列表渲染,而不是Vue的特殊指令。

表单处理

Vue3的双向绑定

<template>
  <input v-model="message" />
  <p>Message: {{ message }}</p>
</template>

<script setup>
import { ref } from 'vue';
const message = ref('');
</script>

React中的表单处理

React没有内置的双向绑定,需要手动处理,使用setData:

import React, { useState } from 'react';

function FormExample() {
  const [message, setMessage] = useState('');
  
  const handleChange = (e) => {
    setMessage(e.target.value);
  };
  
  return (
    <div>
      <input 
        value={message} 
        onChange={handleChange} 
      />
      <p>Message: {message}</p>
    </div>
  );
}

这个模块,个人感觉使用起来没有vue快捷。而且在这里我还踩了一个坑,就是我使用input的时候,开始使用onChange方法,结果发现得到的值,会显示中文拼音,然后我就换了blur方法,但是这样的话文本内容就不会改变,因为它更新值是需要手动set嘛,然后blur的话只在最后才set,但是输入期间没有set的过程,所以文本框的内容一直不变,最后是通过onchange里面set一次,然后blur的时候再次set,才得到文本框的最终值(我不知道这种写法,是不是标准写法,有大佬懂的,评论区教教我,谢谢啦)。

组件通信

Vue3的props和emit

父组件:

<template>
  <ChildComponent 
    :message="parentMessage" 
    @update="handleUpdate" 
  />
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const parentMessage = ref('来自父组件的消息');
const handleUpdate = (newValue) => {
  console.log('子组件发送事件:', newValue);
};
</script>

子组件:

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="sendToParent">发送事件</button>
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

const props = defineProps({
  message: String
});

const emit = defineEmits(['update']);

const sendToParent = () => {
  emit('update', '子组件的新数据');
};
</script>

React的props和状态提升

父组件:

import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const [parentMessage, setParentMessage] = useState('来自父组件的消息');
  
  const handleUpdate = (newValue) => {
    console.log('子组件发送事件:', newValue);
  };
  
  return (
    <ChildComponent 
      message={parentMessage} 
      onUpdate={handleUpdate} 
    />
  );
}

子组件:

import React from 'react';

function ChildComponent({ message, onUpdate }) {
  const sendToParent = () => {
    onUpdate('子组件的新数据');
  };
  
  return (
    <div>
      <p>{message}</p>
      <button onClick={sendToParent}>发送事件</button>
    </div>
  );
}

export default ChildComponent;

两者在组件通信方面的概念类似,但语法不同。Vue使用:prop@event语法,而React使用{prop}和回调函数。目前体验上来,感觉react就是,将父组件的函数直接传了过去,子组件使用这个函数,将值带给父组件。

总结对比

特性Vue3React
项目入口main.jsindex.js
组件文件单文件组件(.vue)JSX(.js/.jsx) + CSS
响应式数据ref, reactiveuseState, useReducer
计算属性computeduseMemo
侦听器watch, watchEffectuseEffect
生命周期onMounted等钩子useEffect
条件渲染v-if, v-show&&, 三元运算符
列表渲染v-formap()
表单绑定v-model受控组件
属性传递:propprop={value}
事件传递@eventonEvent={handler}