目錄
一、基礎語法
1.1、模板 vs JSX
1.2、指令
1.2.1、v-for vs Array.map
1.2.2、v-if vs 三元運算符或者&&
1.2.3、v-bind vs 直接在JSX里寫{變量}
1.2.4、v-show vs style和className
1.2.5、v-html vs dangerouslySetInnerHTML
1.3、數據綁定
1.4、數據初始化
1.5、computed(只能同步)
1.6、watch(可包含異步)
二、生命周期
2.1、周期總結
2.2、關鍵差異
三、組件傳值
3.1、父傳子
3.2、子傳父
3.3、ref(?父組件訪問子組件)
3.4、數據共享
3.5、跨組件通信
3.5.1、事件總線/發布訂閱
3.5.2、Provide-Inject/Context
四、路由
4.1、路由傳參
4.1.1、傳遞參數
4.1.2、接收參數
4.2、路由鉤子
4.2.1、全局守衛
4.2.2、?路由獨享守衛
4.2.3、組件內守衛
4.3、路由配置
一、基礎語法
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
}
// 使用方式
<Routepath="/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) {// 不能訪問thisnext(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)// 模擬beforeRouteLeaveuseEffect(() => {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博客