目录
1.2.4、v-show vs style和className
1.2.5、v-html vs dangerouslySetInnerHTML
一、基础语法
1.1、模板 vs JSX
Vue 使用基于 HTML 的模板语法,React 使用 JSX (JavaScript 的语法扩展)
1.2、指令
1.2.1、v-for vs Array.map
================vue================
<ul>
<li v-for="arrs in items" :key="item.id">
{{ item.name }}
</li>
</ul>
================react================
<ul>
{arrs.map(item => (
<li key="{item.id}">{item.name}</li>
))}
</ul>
1.2.2、v-if vs 三元运算符或者&&
<div v-if="isVisible">显示内容</div>
<div v-else>隐藏内容</div>
=========多条件==========
{isVisible ? (
<div>显示内容</div>
) : (
<div>隐藏内容</div>
)}
==========单条件==========
{isVisible && <div>显示内容</div>}
1.2.3、v-bind vs 直接在JSX里写{变量}
================vue================
<img :src="imageUrl" :alt="imageAlt" />
================react================
<img src={imageUrl} alt={imageAlt} />
1.2.4、v-show vs style和className
<div v-show="isVisible">显示或隐藏</div>
<div style={{ display: isVisible ? 'block' : 'none' }}>显示或隐藏</div>
==============推荐 CSS 类方式==============
<div className={isVisible ? 'visible' : 'hidden'}>显示或隐藏</div>
.hidden { display: none; }
.visible { display: block; }
1.2.5、v-html vs dangerouslySetInnerHTML
================vue================
<div v-html="rawHtml"></div>
================react================
<div dangerouslySetInnerHTML={{ __html: rawHtml }} />
备注:
React 故意命名为
dangerouslySetInnerHTML
以提醒 XSS 风险。必须传入
{ __html: '...' }
对象。
1.3、数据绑定
Vue双向绑定(v-model);
React单向数据流,双向绑定需要通过 value + onChange,推荐受控组件。
<input v-model="message" />
<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value" />
import { useState } from 'react';
function App() {
const [message, setMessage] = useState('');
const handleChange = (e) => {
setMessage(e.target.value);
};
return (
<input
type="text"
value={message}
onChange={handleChange}
/>
);
}
1.4、数据初始化
Vue2:data函数;Vue3:ref和reactive;React:useState 或 useReducer
export default {
data() {
return {
num: 0,
message: "Hello Vue2",
};
},
};
const message=ref("Hello Vue3")
import { useState } from 'react';
function App() {
const [num, setNum] = useState(0);
const [message, setMessage] = useState("Hello React");
return (
<div>
<p>{num}</p>
<p>{message}</p>
</div>
);
}
备注:
useState
用于定义响应式变量,返回[value, setter]
。每次状态更新都会触发组件重新渲染
1.5、computed(只能同步)
Vue 的 computed
用于基于响应式数据派生出新数据,React 可以使用 useMemo
或直接在渲染时计算。注意:computed必须有一个返回值,watch不需要。
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
},
},
==============vue3计算属性==============
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
import { useState, useMemo } from 'react';
function App() {
const [firstName, setFirstName] = useState("John");
const [lastName, setLastName] = useState("Doe");
// 类似 computed,依赖变化时重新计算
const fullName = useMemo(() => {
return `${firstName} ${lastName}`;
}, [firstName, lastName]);
return <p>{fullName}</p>;
}
1.6、watch(可包含异步)
Vue 的监听更声明式(
immediate
/deep
直接配置)Vue 3 的 watchEffect 最接近 React 的
useEffect
,但依赖追踪更智能React 的监听更命令式,其中默认 useEffect 会立即执行,若不需要可加依赖控制,深度监听推荐使用 use-deep-compare-effect 库,避免 JSON.stringify 性能问题。
watch: {
count: {
handler(newVal, oldVal) {
console.log(`count changed: ${newVal}`);
},
immediate: true, // 立即执行一次
deep: true, // 深度监听
},
},
===============vue3侦听 count 的变化===============
watch(
() => count.value,
(newVal, oldVal) => {
console.log(`count changed: ${newVal}`);
},
{ immediate: true, deep: true } // 立即执行 + 深度监听
);
// watchEffect(自动立即执行)
watchEffect(() => {
console.log(`count is: ${count.value}`); // 立即执行 + 自动追踪依赖
});
import { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("立即执行一次 + count 变化时执行");
}, [count]); // 依赖变化时触发
// 仅首次渲染执行(类似 Vue 的 immediate: true)
useEffect(() => {
console.log("仅第一次渲染时执行");
}, []); // 空依赖数组
return (
<button onClick={() => setCount(count + 1)}>
Increment: {count}
</button>
);
}
==============补充:深度监听==============
useEffect(() => {
console.log("user 变化了");
}, [JSON.stringify(user)]); // 监听序列化后的对象(性能较差)
// 或者使用 useDeepCompareEffect(第三方库)
import { useDeepCompareEffect } from 'use-deep-compare';
useDeepCompareEffect(() => {
console.log("user 深层变化");
}, [user]);
二、生命周期
2.1、周期总结
阶段 | Vue 2 | Vue 3 | React类组件 | 函数组件(Hooks) |
初始化 | beforeCreate/created |
setup()替代 | constructor | useState等Hooks |
挂载前 | beforeMount |
onXxx.... | getDerivedStateFromProps | --- |
挂载完成 | mounted |
componentDidMount | useEffect(..., []) | |
更新前 | beforeUpdate |
getDerivedStateFromProps/ shouldComponentUpdate |
--- | |
更新完成 | updated |
componentDidUpdate | useEffect | |
卸载前 | beforeDestroy |
componentWillUnmount | useEffect返回的函数 | |
卸载完成 | destroyed |
onUnmounted | --- | --- |
其中react的函数组件
1、useEffect:组合了componentDidMount、componentDidUpdate和componentWillUnmount
2、useLayoutEffect:类似useEffect,但在DOM更新后同步触发
3、useMemo/useCallback:性能优化,类似shouldComponentUpdate
2.2、关键差异
初始化阶段:
Vue在beforeCreate/created阶段完成响应式数据初始化
React类组件在constructor初始化状态,函数组件在每次渲染都可能初始化
挂载阶段:
Vue的mounted保证子组件也已挂载
React的componentDidMount不保证子组件已完成挂载
更新机制:
Vue自动追踪依赖,数据变化自动触发更新
React需要手动优化(shouldComponentUpdate/PureComponent/memo)
组合式API vs Hooks:
Vue3的setup()只在初始化时运行一次
React函数组件每次渲染都会运行所有Hooks
销毁/卸载:
Vue有明确的beforeDestroy/destroyed(2.x)或onBeforeUnmount/onUnmounted(3.x)
React只有componentWillUnmount或useEffect的清理函数
三、组件传值
3.1、父传子
===========================Vue2===========================
<!-- 父组件 -->
<Child :title="parentTitle" />
<!-- 子组件 -->
<script>
export default {
props: ['title']
}
</script>
===========================Vue3===========================
<!-- 父组件 -->
<Child :title="parentTitle" />
<!-- 子组件 -->
<script setup>
const props = defineProps(['title'])
</script>
// 父组件
<Child title={parentTitle} />
// 子组件
function Child({ title }) {
return <div>{title}</div>
}
3.2、子传父
===========================Vue2===========================
<!-- 子组件 -->
<button @click="$emit('update', newValue)">提交</button>
<!-- 父组件 -->
<Child @update="handleUpdate" />
===========================Vue3===========================
<!-- 子组件 -->
const emit = defineEmits(['imported'])
emit('imported', newValue)
<!-- 父组件 -->
<Child @update="handleUpdate" />
// 子组件
function Child({ onUpdate }) {
return <button onClick={() => onUpdate(newValue)}>提交</button>
}
// 父组件
<Child onUpdate={handleUpdate} />
3.3、ref( 父组件访问子组件)
===========================Vue2===========================
<!-- 父组件 -->
<Child ref="childRef" />
<script>
export default {
mounted() {
this.$refs.childRef.childMethod()
}
}
</script>
===========================Vue3===========================
<!-- 父组件 -->
<Child ref="childRef" />
<script setup>
import { ref, onMounted } from 'vue'
const childRef = ref(null)
onMounted(() => {
childRef.value.childMethod()
})
</script>
// 父组件
function Parent() {
const childRef = useRef(null)
useEffect(() => {
childRef.current.childMethod()
}, [])
return <Child ref={childRef} />
}
// 子组件需要使用 forwardRef
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
childMethod: () => {
console.log('子组件方法被调用')
}
}))
return <div>子组件</div>
})
3.4、数据共享
===========================Vue2===========================
// store.js
export default new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) {
state.count++
}
}
})
// 组件中使用
this.$store.state.count
this.$store.commit('increment')
===========================Vue3===========================
// store.js
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
}
}
})
// 组件中使用
const store = useCounterStore()
store.count
store.increment()
// store.js
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment(state) {
state.value++
}
}
})
export const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
})
// 组件中使用
const count = useSelector(state => state.counter.value)
const dispatch = useDispatch()
dispatch(increment())
3.5、跨组件通信
3.5.1、事件总线/发布订阅
===========================Vue2===========================
// eventBus.js
export const bus = new Vue()
// 组件A
bus.$emit('event-name', data)
// 组件B
bus.$on('event-name', handler)
===========================Vue3===========================
// mitt库
import mitt from 'mitt'
export const emitter = mitt()
// 组件A
emitter.emit('event-name', data)
// 组件B
emitter.on('event-name', handler)
// 自定义事件总线
class EventBus {
constructor() {
this.events = {}
}
// 实现on/emit/off等方法
}
export const eventBus = new EventBus()
// 组件A
eventBus.emit('event-name', data)
// 组件B
useEffect(() => {
const handler = (data) => {}
eventBus.on('event-name', handler)
return () => eventBus.off('event-name', handler)
}, [])
3.5.2、Provide-Inject/Context
// 祖先组件
export default {
provide() {
return {
sharedData: this.sharedData
}
}
}
// 后代组件
export default {
inject: ['sharedData']
}
// 创建Context
const MyContext = createContext()
// 祖先组件
<MyContext.Provider value={sharedData}>
<Child />
</MyContext.Provider>
// 后代组件
const sharedData = useContext(MyContext)
四、路由
4.1、路由传参
4.1.1、传递参数
==========================声明式导航传参==========================
<!-- 传递params参数 -->
<router-link :to="{ name: 'user', params: { id: 123 }}">用户</router-link>
<!-- 传递query参数 -->
<router-link :to="{ path: '/user', query: { id: 123 }}">用户</router-link>
==========================编程式导航传参==========================
// params传参
this.$router.push({ name: 'user', params: { id: 123 } })
// query传参
this.$router.push({ path: '/user', query: { id: 123 } })
==========================Vue3==========================
<router-link :to="{ name: 'User', params: { id: userId }}">用户</router-link>
<router-link :to="{ path: '/user', query: { id: userId }}">用户</router-link>
import { useRouter } from 'vue-router'
const router = useRouter()
router.push({ name: 'User', params: { id: userId } })
router.push({ path: '/user', query: { id: userId } })
==========================声明式导航传参==========================
// 传递params参数
<Link to="/user/123">用户</Link>
// 传递query参数
<Link to="/user?id=123">用户</Link>
==========================编程式导航传参==========================
// params传参
navigate('/user/123')
// query传参
navigate('/user?id=123')
// state传参(不显示在URL中)
navigate('/user', { state: { id: 123 } })
4.1.2、接收参数
// 获取params
this.$route.params.id
// 获取query
this.$route.query.id
=========================Vue3=========================
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
const userId = route.params.id
const userId = route.query.id
</script>
// 获取params
const { id } = useParams()
// 获取query
const [searchParams] = useSearchParams()
const id = searchParams.get('id')
// 获取state
const { state } = useLocation()
const id = state?.id
4.2、路由钩子
4.2.1、全局守卫
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 登录验证逻辑
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login')
} else {
next()
}
})
// 全局后置钩子
router.afterEach((to, from) => {
// 页面访问统计等
})
// 使用自定义Wrapper组件
function PrivateRoute({ children }) {
const location = useLocation()
const isAuthenticated = useAuth()
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />
}
return children
}
// 使用方式
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
4.2.2、 路由独享守卫
{
path: '/dashboard',
component: Dashboard,
beforeEnter: (to, from, next) => {
if (!isAuthenticated) next('/login')
else next()
}
}
// 在组件内使用useEffect模拟
function Dashboard() {
const navigate = useNavigate()
const isAuthenticated = useAuth()
useEffect(() => {
if (!isAuthenticated) {
navigate('/login')
}
}, [isAuthenticated, navigate])
return <div>Dashboard</div>
}
4.2.3、组件内守卫
export default {
beforeRouteEnter(to, from, next) {
// 不能访问this
next(vm => {
// 通过vm访问组件实例
})
},
beforeRouteUpdate(to, from, next) {
// 当前路由改变但组件被复用时调用
next()
},
beforeRouteLeave(to, from, next) {
if (hasUnsavedChanges) {
next(false) // 取消导航
} else {
next()
}
}
}
=============================Vue3=============================
// 组件内守卫
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges) {
return confirm('确定要离开吗?')
}
})
function UserProfile() {
const navigate = useNavigate()
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(true)
// 模拟beforeRouteLeave
useEffect(() => {
const unblock = navigate((location, action) => {
if (hasUnsavedChanges && action === 'POP') {
return window.confirm('确定要离开吗?未保存的更改将会丢失')
}
return true
})
return () => unblock()
}, [hasUnsavedChanges, navigate])
return <div>User Profile</div>
}
4.3、路由配置
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { requiresAuth: true }
},
{
path: '/user/:id',
component: User,
props: true // 将params作为props传递
}
]
const router = createBrowserRouter([
{
path: '/',
element: <Home />,
loader: () => fetchData(), // 数据预加载
shouldRevalidate: () => false // 控制是否重新验证
},
{
path: 'user/:id',
element: <User />,
errorElement: <ErrorPage /> // 错误处理
}
])
这里的路由是vue2/3对比react-router-dom v6
功能 | Vue 2/3 (vue-router) | React (react-router-dom v6) |
路由传参 | params/query分开 | params/query/state多种方式 |
路由守卫 | 全局/独享/组件内都有 | 依赖组件组合和自定义Hook实现 |
路由配置 | 集中式配置 | 集中式和分散式配置 |
动态路由 | addRoute | 原生支持动态路由 |
数据预加载 | 自行实现 | 内置loader机制 |
嵌套路由 | children嵌套 | 嵌套路由布局更直观 |
Vue2生命周期详情:Vue核心概念详解-CSDN博客
Vue2路由详情:Vue2.x(2)_vue2 import-CSDN博客
Vue2Vuex详情:Vue.js实战:深度解析Vuex状态管理与实战应用-CSDN博客
Vue3语法详情:vue3(语法应用集合)_vue3+vite+pinia-CSDN博客
React生命周期与父子通信详情:react实例与总结(一)-CSDN博客
ReactRedux详情:react的redux总结_react redux工作流程-CSDN博客
React路由详情:react路由总结_react路由插件-CSDN博客