这份笔记从实际代码出发,对比 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 语法糖,自动处理 value 和 input 事件。
vue
<script setup>
import { ref } from "vue";
const text = ref("Hello World");
</script>
<template>
<p>{{ text }}</p>
<input v-model="text" />
</template>
React 需要手动把 value 和 onChange 绑定到状态上,形成受控组件。
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 和不可变数据来管理状态和副作用。两者没有绝对优劣,理解响应式模型后,换框架时主要是写法习惯上的迁移。