Vue 3 与 React 常用写法对比

这份笔记从实际代码出发,对比 Vue 3(<script setup> + Composition API)与 React(函数组件 + Hooks)在常见场景下的写法差异。例子尽量保持最小化,方便直接复制到项目里跑通。


1. 声明响应式状态

Vue 用 ref 把值包成响应式对象,脚本里读写要加 .value,模板里自动解包。

vue 复制代码
<script setup>
import { ref } from "vue";
const name = ref("John");
</script>

<template>
  <h1>Hello {{ name }}</h1>
</template>

React 用 useState 返回当前值和 setter,更新时必须调用 setter。

jsx 复制代码
import { useState } from "react";

export default function Name() {
  const [name] = useState("John");
  return <h1>Hello {name}</h1>;
}

差异:Vue 的状态是"响应式引用",React 的状态是"不可变快照"。


2. 挂载后修改状态

Vue 把逻辑直接写在 <script setup> 里,配合 onMounted

vue 复制代码
<script setup>
import { ref, onMounted } from "vue";
const name = ref("John");
onMounted(() => { name.value = "Jane"; });
</script>

<template>
  <h1>Hello {{ name }}</h1>
</template>

React 需要 useEffect 并在空依赖数组时触发,更新仍要走 setter。

jsx 复制代码
import { useState, useEffect } from "react";

export default function Name() {
  const [name, setName] = useState("John");
  useEffect(() => { setName("Jane"); }, []);
  return <h1>Hello {name}</h1>;
}

3. 计算属性 / 派生值

Vue 用 computed 缓存派生值,依赖变化时自动重新计算。

vue 复制代码
<script setup>
import { ref, computed } from "vue";
const count = ref(10);
const doubleCount = computed(() => count.value * 2);
</script>

<template>
  <div>{{ doubleCount }}</div>
</template>

React 里简单的派生值直接在渲染时计算;复杂场景可用 useMemo

jsx 复制代码
import { useState } from "react";

export default function DoubleCount() {
  const [count] = useState(10);
  const doubleCount = count * 2;
  return <div>{doubleCount}</div>;
}

4. 样式绑定

Vue 模板里用 class,单文件组件里用 <style scoped> 控制作用域;行内样式写字符串。

vue 复制代码
<template>
  <h1 class="title">I am red</h1>
  <button style="font-size: 10rem;">I am a button</button>
</template>

<style scoped>
.title { color: red; }
</style>

React 用 className,行内样式是对象,属性名用驼峰。

jsx 复制代码
import "./style.css";

export default function CssStyle() {
  return (
    <>
      <h1 className="title">I am red</h1>
      <button style={{ fontSize: "10rem" }}>I am a button</button>
    </>
  );
}

5. 列表渲染

Vue 用 v-for,必须加 :key

vue 复制代码
<script setup>
const colors = ["red", "green", "blue"];
</script>

<template>
  <ul>
    <li v-for="color in colors" :key="color">{{ color }}</li>
  </ul>
</template>

React 用数组的 map(),同样要传 key

jsx 复制代码
export default function Colors() {
  const colors = ["red", "green", "blue"];
  return (
    <ul>
      {colors.map((color) => <li key={color}>{color}</li>)}
    </ul>
  );
}

6. 事件处理(计数器示例)

Vue 用 @click 绑定事件,直接修改 .value

vue 复制代码
<script setup>
import { ref } from "vue";
const count = ref(0);
function increment() { count.value++; }
</script>

<template>
  <p>Counter: {{ count }}</p>
  <button @click="increment">+1</button>
</template>

React 用 onClick,并且必须用 setCount 更新状态。

jsx 复制代码
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  const increment = () => setCount((prev) => prev + 1);
  return (
    <>
      <p>Counter: {count}</p>
      <button onClick={increment}>+1</button>
    </>
  );
}

7. 获取 DOM 引用

Vue 在模板里写 ref="inputElement",脚本里声明同名 ref(null),挂载后通过 .value 访问。

vue 复制代码
<script setup>
import { ref, onMounted } from "vue";
const inputElement = ref(null);
onMounted(() => inputElement.value.focus());
</script>

<template>
  <input ref="inputElement" />
</template>

React 用 useRef(null) 创建引用,通过 .current 访问。

jsx 复制代码
import { useEffect, useRef } from "react";

export default function InputFocused() {
  const inputElement = useRef(null);
  useEffect(() => { inputElement.current.focus(); }, []);
  return <input type="text" ref={inputElement} />;
}

8. 条件渲染

Vue 用 v-if / v-else-if / v-else,语义清晰。

vue 复制代码
<template>
  <span v-if="light === 'red'">STOP</span>
  <span v-else-if="light === 'orange'">SLOW DOWN</span>
  <span v-else>GO</span>
</template>

React 用 && 或三元表达式完成同样的分支判断。

jsx 复制代码
return (
  <p>
    {light === "red" && <span>STOP</span>}
    {light === "orange" && <span>SLOW DOWN</span>}
    {light === "green" && <span>GO</span>}
  </p>
);

9. 挂载时获取外部数据

Vue 用 onMounted 执行初始化逻辑。

vue 复制代码
<script setup>
import { ref, onMounted } from "vue";
const pageTitle = ref("");
onMounted(() => { pageTitle.value = document.title; });
</script>

<template>
  <p>Page title: {{ pageTitle }}</p>
</template>

React 用 useEffect 空依赖实现同样的效果。

jsx 复制代码
import { useState, useEffect } from "react";

export default function PageTitle() {
  const [pageTitle, setPageTitle] = useState("");
  useEffect(() => { setPageTitle(document.title); }, []);
  return <p>Page title: {pageTitle}</p>;
}

10. 定时器与清理

Vue 在 onUnmounted 里清理副作用,防止内存泄漏。

vue 复制代码
<script setup>
import { ref, onUnmounted } from "vue";
const time = ref(new Date().toLocaleTimeString());
const timer = setInterval(() => {
  time.value = new Date().toLocaleTimeString();
}, 1000);
onUnmounted(() => clearInterval(timer));
</script>

<template>
  <p>Current time: {{ time }}</p>
</template>

React 在 useEffect 里返回清理函数。

jsx 复制代码
import { useState, useEffect } from "react";

export default function Time() {
  const [time, setTime] = useState(new Date().toLocaleTimeString());
  useEffect(() => {
    const timer = setInterval(() => {
      setTime(new Date().toLocaleTimeString());
    }, 1000);
    return () => clearInterval(timer);
  }, []);
  return <p>Current time: {time}</p>;
}

11. Props 声明与校验

Vue 用 defineProps 声明 props,支持类型、必填、默认值。

vue 复制代码
<script setup>
const props = defineProps({
  name: { type: String, required: true, default: "" },
  age: { type: Number, required: true, default: null },
  favouriteColors: { type: Array, required: true, default: () => [] },
  isAvailable: { type: Boolean, required: true, default: false }
});
</script>

<template>
  <p>My name is {{ props.name }}</p>
  <p>My age is {{ props.age }}</p>
  <p>My favourite colors are {{ props.favouriteColors.join(",") }}</p>
  <p>I am {{ props.isAvailable ? "available" : "not available" }}</p>
</template>

React 常用 prop-types 做运行时校验,默认值通过解构赋值给出。

jsx 复制代码
import PropTypes from "prop-types";

export default function UserProfile({
  name = "",
  age = null,
  favouriteColors = [],
  isAvailable = false
}) {
  return (
    <>
      <p>My name is {name}</p>
      <p>My age is {age}</p>
      <p>My favourite colors are {favouriteColors.join(",")}</p>
      <p>I am {isAvailable ? "available" : "not available"}</p>
    </>
  );
}

UserProfile.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  favouriteColors: PropTypes.arrayOf(PropTypes.string).isRequired,
  isAvailable: PropTypes.bool.isRequired
};

12. 子组件向父组件传事件

Vue 子组件用 defineEmits 声明事件,调用 emit 触发;父组件用 @事件名 监听。

vue 复制代码
<!-- AnswerButton.vue -->
<script setup>
const emit = defineEmits(["yes", "no"]);
</script>

<template>
  <button @click="emit('yes')">YES</button>
  <button @click="emit('no')">NO</button>
</template>
vue 复制代码
<!-- App.vue -->
<script setup>
import { ref } from "vue";
import AnswerButton from "./AnswerButton.vue";
const isHappy = ref(true);
</script>

<template>
  <p>Are you happy?</p>
  <AnswerButton @yes="isHappy = true" @no="isHappy = false" />
  <p style="font-size: 50px">{{ isHappy ? "😊" : "😞" }}</p>
</template>

React 直接通过 props 传递回调函数。

jsx 复制代码
// AnswerButton.jsx
export default function AnswerButton({ onYes, onNo }) {
  return (
    <>
      <button onClick={onYes}>YES</button>
      <button onClick={onNo}>NO</button>
    </>
  );
}
jsx 复制代码
// App.jsx
import { useState } from "react";
import AnswerButton from "./AnswerButton.jsx";

export default function App() {
  const [isHappy, setIsHappy] = useState(true);
  return (
    <>
      <p>Are you happy?</p>
      <AnswerButton onYes={() => setIsHappy(true)} onNo={() => setIsHappy(false)} />
      <p style={{ fontSize: 50 }}>{isHappy ? "😊" : "😞"}</p>
    </>
  );
}

13. 插槽 / children

Vue 用 <slot> 接收父组件传入的内容,可设置默认内容。

vue 复制代码
<!-- FunnyButton.vue -->
<template>
  <button>
    <slot><span>No content found</span></slot>
  </button>
</template>
vue 复制代码
<!-- App.vue -->
<template>
  <FunnyButton />
  <FunnyButton>I got content!</FunnyButton>
</template>

React 用 children prop 实现相同效果。

jsx 复制代码
// FunnyButton.jsx
export default function FunnyButton({ children }) {
  return <button>{children || <span>No content found</span>}</button>;
}
jsx 复制代码
// App.jsx
export default function App() {
  return (
    <>
      <FunnyButton />
      <FunnyButton>I got content!</FunnyButton>
    </>
  );
}

14. 跨层级数据传递

Vue 用 provide / inject,不需要额外包裹 Provider 组件。

vue 复制代码
<!-- App.vue -->
<script setup>
import { ref, provide } from "vue";
import UserProfile from "./UserProfile.vue";

const user = ref({
  id: 1,
  username: "unicorn42",
  email: "unicorn42@example.com"
});

function updateUsername(name) {
  user.value.username = name;
}

provide("user", { user, updateUsername });
</script>

<template>
  <h1>Welcome back, {{ user.username }}</h1>
  <UserProfile />
</template>
vue 复制代码
<!-- UserProfile.vue -->
<script setup>
import { inject } from "vue";
const { user, updateUsername } = inject("user");
</script>

<template>
  <div>
    <h2>My Profile</h2>
    <p>Username: {{ user.username }}</p>
    <p>Email: {{ user.email }}</p>
    <button @click="updateUsername('Jane')">Update username to Jane</button>
  </div>
</template>

React 用 Context:先创建 Context,再用 Provider 包裹子树,后代用 useContext 消费。

jsx 复制代码
// App.jsx
import { createContext, useState } from "react";
import UserProfile from "./UserProfile";

export const UserContext = createContext();

export default function App() {
  const [user, setUser] = useState({
    id: 1,
    username: "unicorn42",
    email: "unicorn42@example.com"
  });

  const updateUsername = (name) =>
    setUser((prev) => ({ ...prev, username: name }));

  return (
    <UserContext.Provider value={{ user, updateUsername }}>
      <h1>Welcome back, {user.username}</h1>
      <UserProfile />
    </UserContext.Provider>
  );
}
jsx 复制代码
// UserProfile.jsx
import { useContext } from "react";
import { UserContext } from "./App";

export default function UserProfile() {
  const { user, updateUsername } = useContext(UserContext);
  return (
    <div>
      <h2>My Profile</h2>
      <p>Username: {user.username}</p>
      <p>Email: {user.email}</p>
      <button onClick={() => updateUsername("Jane")}>Update username to Jane</button>
    </div>
  );
}

15. 表单输入绑定

Vue 用 v-model 语法糖,自动处理 valueinput 事件。

vue 复制代码
<script setup>
import { ref } from "vue";
const text = ref("Hello World");
</script>

<template>
  <p>{{ text }}</p>
  <input v-model="text" />
</template>

React 需要手动把 valueonChange 绑定到状态上,形成受控组件。

jsx 复制代码
import { useState } from "react";

export default function InputHello() {
  const [text, setText] = useState("Hello world");
  return (
    <>
      <p>{text}</p>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </>
  );
}

速查表

场景 Vue 3 React
声明状态 ref("John") useState("John")
读取状态 name.value(脚本)/ {{ name }}(模板) name
更新状态 name.value = "Jane" setName("Jane")
挂载副作用 onMounted(() => {...}) useEffect(() => {...}, [])
清理副作用 onUnmounted(() => {...}) useEffect(() => { return () => {...}; }, [])
计算属性 computed(() => ...) 渲染时计算 / useMemo
事件绑定 @click="handler" onClick={handler}
列表渲染 v-for="item in list" :key="item" list.map(item => <li key={item}>)
条件渲染 v-if / v-else-if / v-else && 或三元表达式
DOM 引用 ref="el" + const el = ref(null) useRef(null) + .current
Props defineProps({...}) prop-types 或 TypeScript
子传父 emit("event") + @event 回调函数 prop
插槽 <slot> children prop
跨层级 provide / inject Context.Provider + useContext
表单绑定 v-model value + onChange

最后一句

Vue 倾向于把响应式能力交给框架自动处理,写出来的代码更接近"声明式模板 + 脚本逻辑";React 则更强调通过显式的 Hook 和不可变数据来管理状态和副作用。两者没有绝对优劣,理解响应式模型后,换框架时主要是写法习惯上的迁移。

相关推荐
jinggongszh1 小时前
使用AI协同开发产品条码规则功能——从“理解方案、原型、接口文档”到“落地真实前端代码”
前端·人工智能·mes系统·mes工程架构
渔夫正在掘金1 小时前
Cordis 中文教程:渐进式构建插件化应用
前端·node.js·ai编程
打呵欠的猫1 小时前
前端团队 3 个月 AI 实践复盘:哪些场景 ROI 最高,哪些是伪需求
前端·ai编程
gyx_这个杀手不太冷静2 小时前
高级前端开发职业规划(2026—2035)
前端·面试·agent
程序员黑豆2 小时前
Java中的null与NullPointerException完全指南:安全处理、实战排查与面试题
java·前端·ai编程
编程快车2 小时前
用 Phaser 4 造一个空间解谜游戏:纯函数棋盘引擎、透明画布分层与轻量双语
前端·程序员
可视之道2 小时前
前端图形编辑器的多选框架:从单选到框选到 Shift 连选
前端
3秒一个大2 小时前
JS 数组去重全方案:从基础到通用万能去重函数
前端·javascript
阿黎梨梨2 小时前
读懂一个 Next.js 全栈笔记应用
前端