Vue3-admin 快速上手實(shí)戰(zhàn)項(xiàng)目
1、vue3中全部采用函數(shù)式寫法,替換了原來(lái)類的寫法,
2、移除了原有的生命周期函數(shù),和data、computed、watch、method等vue2中的對(duì)象,去掉了this, 并且去除了過(guò)濾器api -> filter
3、vue3源碼全部采用ts編寫,編碼中實(shí)現(xiàn)了對(duì)ts更好的支持
4、vue3完全兼容vue2,在vue3中依然可以按照vue2的方式去寫代碼,而且兩種寫法可以同時(shí)存在,
5、組件中同時(shí)存在兩種寫法時(shí),當(dāng)setup返回值中定義的方法和methods中的方法同名時(shí),會(huì)拋出錯(cuò)誤。
定義的數(shù)據(jù)和data定義的數(shù)據(jù)字段相同時(shí),會(huì)被data定義的字段覆蓋
6、vue3采用proxy的方式實(shí)現(xiàn)數(shù)據(jù)代理,只會(huì)代理第一層數(shù)據(jù) 避免了vue2中對(duì)data的深層遞歸,提升了組件渲染性能
如下所示:
// 在vue3中定義一個(gè)響應(yīng)式數(shù)據(jù)
const state = reactive({data: {obj: {}}});
state.data.obj = xxx;
返回的state是一個(gè)proxy對(duì)象,默認(rèn)只會(huì)對(duì)data進(jìn)行代理
那么vue3是怎么實(shí)現(xiàn)深層數(shù)據(jù)劫持呢,例如我們要修改obj那么是怎么監(jiān)聽到obj的修改呢
當(dāng)我們要對(duì)深層對(duì)象obj進(jìn)行修改時(shí),會(huì)調(diào)用 state.data 的get方法,在get方法中會(huì)對(duì)state.data 進(jìn)行代理,劫持state.data中的屬性, get方法返回的不是state.data本身,而是被proxy代理過(guò)的對(duì)象,從而巧妙的實(shí)現(xiàn)了深層數(shù)據(jù)劫持,在用到該屬性的時(shí)候一定會(huì)調(diào)用父級(jí)的get方法,這時(shí)候才會(huì)去劫持屬性的get和set方法
一、setup函數(shù)
setup函數(shù)是vue3中所有api的入口和出口
vue3中用setup函數(shù)整合了所有的api, setup函數(shù)只執(zhí)行一次,在生命周期函數(shù)前執(zhí)行,所以在setup函數(shù)中拿不到當(dāng)前實(shí)例this,不能用this來(lái)調(diào)用vue2寫法中定義的方法
vue3中去掉了data,使用setup的返回值來(lái)給模板綁定value
return 的對(duì)象如果是常量,不會(huì)變成響應(yīng)式數(shù)據(jù)
this.$emit 用 context.emit 方法來(lái)替代
// props - 組件接受到的屬性, context - 上下文
setup(props, context){
return {
// 要綁定的數(shù)據(jù)和方法
}
}
二、生命周期
生命周期函數(shù),都變成了回調(diào)的形式,寫在setup函數(shù)中
可以一次寫多個(gè)相同的生命周期函數(shù),按照注冊(cè)順序執(zhí)行
setup() {
onMounted(() => {
console.log('組件掛載1');
});
onMounted(() => {
console.log('組件掛載2');
});
onUnmounted(() => {
console.log('組件卸載');
});
onUpdated(() => {
console.log('組件更新');
});
onBeforeUpdate(() => {
console.log('組件將要更新');
});
onActivated(() => {
console.log('keepAlive 組件 激活');
});
onDeactivated(() => {
console.log('keepAlive 組件 非激活');
});
return {};
},
三、ref - 簡(jiǎn)單的響應(yīng)式數(shù)據(jù)
1、ref可以將某個(gè)普通值包裝成響應(yīng)式數(shù)據(jù),僅限于簡(jiǎn)單值,內(nèi)部是將值包裝成對(duì)象,再通過(guò)defineProperty來(lái)處理的
通過(guò)ref包裝的值,取值和設(shè)置值的時(shí)候,需用通過(guò)value來(lái)進(jìn)行設(shè)置
2、可以用ref來(lái)獲取組件的引用,替代this.$refs的寫法
<template>
<div class="mine">
<input v-model="inputVal" />
<button @click="addTodo">添加</button>
<ul>
<li v-for="(item, i) in todoList" :key="i">
{{ item }}
</li>
</ul>
</div>
<div></div>
</template>
setup() {
const inputVal = ref('');
const todoList = ref<string[]>([]);
function addTodo() {
todoList.value.push(inputVal.value);
inputVal.value = '';
}
return {
addTodo,
inputVal,
todoList,
};
},
四、reactive - 數(shù)據(jù)綁定
使用reactive來(lái)對(duì)復(fù)雜數(shù)據(jù)進(jìn)行響應(yīng)式處理,它的返回值是一個(gè)proxy對(duì)象,
在setup函數(shù)中返回時(shí),可以用toRefs對(duì)proxy對(duì)象進(jìn)行結(jié)構(gòu),方便在template中使用
通過(guò)reactive來(lái)創(chuàng)建響應(yīng)式數(shù)據(jù)data,用toRefs來(lái)進(jìn)行解構(gòu),在模板中直接使用 inputVal、todoList
模板中綁定的方法也需要 在setup函數(shù)中定義,并返回,才能綁定到模板中,例如addTodo方法
vue3模板: 一個(gè)templage可以有多個(gè)平級(jí)的標(biāo)簽(vue2中只能在template寫一個(gè)子標(biāo)簽)
<template>
<div class="mine">
<input v-model="inputVal" />
<button @click="addTodo">添加</button>
<ul>
<li v-for="(item, i) in todoList" :key="i">
{{ item }}
</li>
</ul>
</div>
<div></div>
</template>
setup() {
const data = reactive({
inputVal: '',
todoList: [],
});
function addTodo() {
data.todoList.push(data.inputVal);
data.inputVal = '';
}
return {
...toRefs(data),
addTodo,
};
},
五、 computed
計(jì)算屬性,變成了函數(shù)寫法,當(dāng)依賴的值發(fā)生改變時(shí)會(huì)重新計(jì)算
computed包裝后的值,需要用 .value去取值,template中不需要使用.value。
async setup() {
const data = reactive({
a: 10,
b: 20,
});
let sum = computed(() => data.a + data.b);
return { sum };
},
六、 watch
變成了函數(shù)寫法,與vue2中用法相同
// 偵聽一個(gè)
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接偵聽一個(gè)ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
七、watchEffect 新增方法
響應(yīng)式地跟蹤函數(shù)中引用的響應(yīng)式數(shù)據(jù),當(dāng)響應(yīng)式數(shù)據(jù)改變時(shí),會(huì)重新執(zhí)行函數(shù)
const count = ref(0)
// 當(dāng)count的值被修改時(shí),會(huì)執(zhí)行回調(diào)
watchEffect(() => console.log(count.value))
八、 vue-router
組件中使用路由時(shí)用useRoute和useRouter
import {useRoute, useRouter} from 'vue-router'
const route = useRoute(); // 相當(dāng)于 vue2 中的this.$route
const router = useRouter(); // 相當(dāng)于 vue2 中的this.$router
route 用于獲取當(dāng)前路由數(shù)據(jù)
router 用于路由跳轉(zhuǎn)
九、 vuex
使用useStore來(lái)獲取store對(duì)象
從vuex中取值時(shí),要注意必須使用computed進(jìn)行包裝,這樣vuex中狀態(tài)修改后才能在頁(yè)面中響應(yīng)
import {useStore} from 'vuex'
setup(){
const store = useStore(); // 相當(dāng)于 vue2中的 this.$store
store.dispatch(); // 通過(guò)store對(duì)象來(lái)dispatch 派發(fā)異步任務(wù)
store.commit(); // commit 修改store數(shù)據(jù)
let category = computed(() => store.state.home.currentCagegory
return { category }
}
十、用jsx來(lái)定義vue組件
vue3中支持使用jsx語(yǔ)法來(lái)定義vue組件
export const AppMenus = defineComponent({
setup() {
return () => {
return (
<div class="app-menus">
<h1>這是一個(gè)vue組件</h1>
</div>
);
};
},
});
十一、插槽修改
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一個(gè)不帶 name 的 <slot> 出口會(huì)帶有隱含的名字“default”。
在向具名插槽提供內(nèi)容的時(shí)候,我們可以在一個(gè) <template> 元素上使用 v-slot 指令,并以 v-slot 的參數(shù)的形式提供其名稱,也可以使用v-slot的簡(jiǎn)寫方式#, v-slot:header等價(jià)于 #header
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
十二、Suspense 和 異步 setup
<Suspense>
<template #default>
<home-swiper></home-swiper>
</template>
<template #fallback>
<div>loading...</div>
</template>
</Suspense>
組件的setup方法使用異步,頁(yè)面在加載時(shí)會(huì)先顯示 fallback內(nèi)容,當(dāng)setup函數(shù)執(zhí)行完畢才會(huì)正常加載 home-swiper組件
async setup() {
let store = useStore();
let sliderList = computed(() => store.state.home.sliders);
// 計(jì)算屬性,需要多加 .value
if (sliderList.value.length === 0) {
// 緩存 如果沒有數(shù)據(jù),請(qǐng)求接口獲取
await store.dispatch(`home/${Types.SET_SLIDER_LIST}`);
}
return { sliderList };
},