vue3基礎(chǔ)知識(shí)剖析
前言
前段時(shí)間,由新東方出品直播帶貨品牌
東方甄選火爆全網(wǎng),其中最受大家關(guān)注的主播董宇輝,用網(wǎng)友的調(diào)侃來(lái)說(shuō)“長(zhǎng)著一個(gè)顆粒無(wú)收的臉,卻擁有五谷豐登的靈魂”。他在直播中推薦五常大米時(shí)說(shuō):“廚房里充滿了飯香,就是人間浪漫。”,介紹水蜜桃:“這個(gè)水蜜桃,美好的像穿越大峽谷的風(fēng),像仲夏夜的夢(mèng)”。賣牛排,告訴觀眾這是“Original Cutting”。讓網(wǎng)友贊嘆不絕,大家都說(shuō)這買的不是吃的,買的是知識(shí)付費(fèi),買的是靈魂洗禮。最重要的是,他盡然還是一個(gè)英語(yǔ)老師。很多人感嘆,好好讀書太重要了,因?yàn)橹R(shí)能給人帶來(lái)力量,帶來(lái)高貴的靈魂。相比那些快節(jié)奏、聲嘶力竭、充滿商業(yè)誘導(dǎo)的的直播模式,簡(jiǎn)直就是降維打擊。
從筆者的角度來(lái)看,董宇輝的成功并非偶然,能夠飽讀詩(shī)書,一定源于自己多年不斷的思考跟總結(jié),不斷的追求學(xué)習(xí)的本質(zhì)才能讓自己在無(wú)意之間沉淀的像個(gè)詩(shī)人,像個(gè)哲學(xué)家。這背后的付出,常人肯定無(wú)法想象。作家周嶺說(shuō)過(guò)
“所謂的學(xué)習(xí),不是努力,努力,在努力。而是反饋,反饋,再反饋。光靠一味的輸入,而不輸出,這種學(xué)習(xí)大概率是低效率的”。就像咱們前端技術(shù)圈一樣,框架層出不窮,版本迭代快的讓人無(wú)法喘息。很多小伙伴都焦慮的吶喊,學(xué)不動(dòng)了。筆者認(rèn)為,真正高效的學(xué)習(xí)一定是需要在輸入的同時(shí),要有很好的輸出,讓自己積累更多的正向反饋,就像我們平時(shí)學(xué)習(xí)某一種技術(shù)棧一樣,光是一味的學(xué)習(xí)不行,還要做出高質(zhì)量的實(shí)踐跟輸出才行!
筆者這篇文章會(huì)從vue3基礎(chǔ)的知識(shí)點(diǎn)開始剖析,特別是在將
composition API的時(shí)候,在代碼示例中不會(huì)一上來(lái)就使用setup語(yǔ)法糖,而是用早期的setup函數(shù),這樣方便于初學(xué)的小伙伴們理解跟學(xué)習(xí)。文章篇幅較大,接下來(lái),請(qǐng)您花個(gè)10分鐘耐心的看完,或許會(huì)有不一樣的收貨。
聲明
- 本文中下邊所有的示例代碼都可以直接訪問(wèn)這個(gè)網(wǎng)站點(diǎn)擊這里查看效果需要源代碼的小伙伴可以在評(píng)論區(qū)下留言,或者私信我。
- 本文章的講解的所有實(shí)例面向?qū)ue3的初學(xué)者,如有講解不到位,或者有偏差的地方,歡迎大家留言指出。
vue3.0有哪些新特性
- Composition Api (最重要的新特性)
- 組件通信
- 生命周期
- 自定義Hook
- 插槽
- v-model的更改
- 更加純粹的Tree-shaking
- 配合狀態(tài)管理的Pinia
- 配合升級(jí)的vue-router 4.x
- 配合升級(jí)的打包工具vite
- 配合TS在項(xiàng)目中自由使用
vue3.0的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 使用
vue3最大的優(yōu)勢(shì)個(gè)人認(rèn)為倒不是它的Api,而是配合使用的vite打包工具,特別是大型項(xiàng)目本地啟動(dòng)要比當(dāng)前的webpack5要快至少2倍以上(項(xiàng)目中測(cè)試過(guò)) - 比起
vue 2.x,Composition Api的優(yōu)勢(shì)要明顯的多,如果習(xí)慣了setup語(yǔ)法糖的寫法,你會(huì)發(fā)現(xiàn)爽的飛起,很多之前在vue 2.x中大量重復(fù)邏輯不存在了 - 底層通過(guò)
Proxy來(lái)實(shí)現(xiàn)雙向綁定,性能上提升了很多 - 對(duì)
TypeScript支持度更好,可以很愉快的在項(xiàng)目中使用TypeScript
缺點(diǎn)
- 如果還有
IE情節(jié)的公司,那vue3確實(shí)不太適合,因?yàn)?code>vue3已經(jīng)拋棄了對(duì)IE11的支持,再說(shuō)了 微軟人家自己都不打算維護(hù)IE了,兄弟們,放棄IE擁抱chrome吧! Composition Api的寫法需要花一點(diǎn)點(diǎn)時(shí)間來(lái)適應(yīng),畢竟學(xué)習(xí)新語(yǔ)法還是需要成本的
如何解鎖vue3.0
體驗(yàn)vue3.0的4中姿勢(shì)
- 通過(guò)CDN
<script src="https://unpkg.com/vue@next"></script>
- npm
# 最新穩(wěn)定版 npm install vue@next npm install -D @vue/compiler-sfc
如果你是從Vue 2.x升級(jí)的,請(qǐng)注意 @vue/compiler-sfc 替換掉了 vue-template-compiler
- vue-cli
npm install -g @vue/cli vue upgrade --next
- vite
npm init vite@latest <project-name> -- --template vue cd <project-name> npm install npm run dev
推薦使用第4種方式,直接使用官方推薦最新的vite打包工具,直接初始化項(xiàng)目。
核心的composition API
setup
setup是vue3提出的一個(gè)非常重要的選項(xiàng),也是Composition Api最為核心的語(yǔ)法之一。setup執(zhí)行時(shí)機(jī)是在beforeCreate之前執(zhí)行的。setup返回的是一個(gè)對(duì)象,對(duì)象中的所有屬性都是可以在template中使用setup中不能使用thissetup中注冊(cè)生命周期onMounted、watch、computed等,我們會(huì)在下邊詳細(xì)講解
setup參數(shù)
- props
- context
<script>
export default {
setup (props, context) {
return {}
}
}
</script>setup語(yǔ)法糖
既然上邊提到了setup語(yǔ)法,那就有必要把setup語(yǔ)法糖介紹一下,我們?cè)趯?shí)際的項(xiàng)目開發(fā)中在熟悉了setup語(yǔ)法的本質(zhì)后,也推薦大家使用setup語(yǔ)法糖來(lái)編寫,這樣也可以大大提升開發(fā)效率。
- 不需要像上述一樣
return,只需要在<script setup>中聲明一下即可 - 任何在
<script setup>聲明的頂層的綁定 (包括聲明的變量,函數(shù)聲明,以及import引入的內(nèi)容) 都可以在模板中直接使用 - 組件在語(yǔ)法糖中可以自動(dòng)注冊(cè),無(wú)需再通過(guò)
components進(jìn)行注冊(cè)
<script setup>
import {ref} from 'vue'
let property = ref('這里是響應(yīng)式屬性');
// 這里我們引入了子組件SetUp.vue
import SetUp from '@/components/SetUp.vue'
</script>ref、reactive
ref跟reactive都是vue3中用來(lái)做數(shù)據(jù)定義使用的,如同vue2中在data中做數(shù)據(jù)定義一樣,示例代碼如下:
<template>
<h3>{{ state.count }}</h3>
<h3>{{ num }}</h3>
<el-button @click="handleAdd" type="primary">ref計(jì)算</el-button>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
const num = ref(0)
const state = reactive({ count: 1 })
function handleAdd() {
state.count++;
num.value += 2;
}
return {
state,
num,
handleAdd
}
}
}
</script>ref跟reactive的區(qū)別在哪呢?很多人分不清楚,網(wǎng)上有很多文章簡(jiǎn)單的定義為ref負(fù)責(zé)處理基本數(shù)據(jù)類型的雙向綁定,reactive負(fù)責(zé)處理對(duì)象的雙向綁定。其實(shí),這樣筆者會(huì)覺(jué)得給很多初學(xué)者帶來(lái)很多誤導(dǎo),其實(shí)ref也可以處理對(duì)象的雙向綁定,就像下邊這段代碼一樣。
<template>
<el-button @click="handleAdd" type="primary">ref計(jì)算</el-button>
<h3>{{ obj.count }}</h3>
</template>
<script>
export default {
setup() {
// ref 對(duì)象雙向綁定
const obj = ref({ count: 1 })
function handleAdd() {
obj.value.count = obj.value.count + 1
}
return {
obj,
handleAdd
}
}
}
</script>watch跟watchEffect
watchEffect
- 當(dāng)傳入一個(gè)函數(shù)時(shí),可以響應(yīng)式的自動(dòng)收集依賴,當(dāng)依賴變更時(shí)重新運(yùn)行該函數(shù);
- 使用是需要配置
flush: post,否則依賴在監(jiān)聽時(shí)無(wú)法被立即更新 - 也可以使用
stop來(lái)立即停止對(duì)函數(shù)的監(jiān)聽
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import {ref, watchEffect} from 'vue'
export default {
setup() {
const root = ref(null)
watchEffect(() => {
console.log(`watchEffect監(jiān)聽:${root.value}`);
}, {
flush: 'post'
})
return {
root
}
},
}
</script>
watch
watchAPI 與選項(xiàng)式 APIthis.$watch(以及相應(yīng)的watch選項(xiàng)) 完全等效。watch需要偵聽特定的數(shù)據(jù)源,并在單獨(dú)的回調(diào)函數(shù)中執(zhí)行副作用。默認(rèn)情況下,它也是惰性的——即回調(diào)僅在偵聽源發(fā)生變化時(shí)被調(diào)用。
與 watchEffect 相比,watch :
- 是一個(gè)返回任意值的
getter函數(shù) - 是一個(gè)包裝的對(duì)象,可以是
ref對(duì)象、也可以reactive對(duì)象 - 可以同時(shí)監(jiān)聽多個(gè)數(shù)據(jù)源
- 監(jiān)聽是需要配置
deep: true,否則回調(diào)函數(shù)無(wú)法被觸發(fā)
<template>
<h3>監(jiān)聽單個(gè)數(shù)據(jù)源1:{{state1.count}}</h3>
<button @click="handleWatchSingle1">watch監(jiān)聽測(cè)試1</button>
<h3>監(jiān)聽單個(gè)數(shù)據(jù)源2:{{state2}}</h3>
<button @click="handleWatchSingle2">watch監(jiān)聽測(cè)試2</button>
<h3>監(jiān)聽復(fù)雜對(duì)象數(shù)據(jù)源:{{state3.player}}</h3>
<button @click="handleWatchSingle3">watch監(jiān)聽測(cè)試3</button>
</template>
<script>
import {ref, reactive, watch} from 'vue'
export default {
setup() {
const state1 = reactive({ count: 1 })
const state2 = ref(0)
const state3 = reactive({
player: {
name: 'James',
achievement: ['4次NBA常規(guī)賽mvp', '03年選秀狀元', '4次NBA總冠軍']
}
})
watch(() => state1.count, (newVal, oldVal) => {
console.log('watch監(jiān)聽reactive中的newVal:', newVal);
console.log('watch監(jiān)聽reactive中的oldVal:', oldVal);
})
watch(() => state2.value, (newVal, oldVal) => {
console.log('watch監(jiān)聽ref中的newVal:', newVal);
console.log('watch監(jiān)聽ref中的oldVal:', oldVal);
})
watch(() => state3.player, (newVal, oldVal) => {
console.log('watch監(jiān)聽復(fù)雜對(duì)象中的newVal:', newVal);
console.log('watch監(jiān)聽復(fù)雜對(duì)象中的oldVal:', oldVal);
}, {
deep: true,
// immediate: true
})
// 同時(shí)監(jiān)聽多個(gè)值
// watch([() => state1.count, state2.value], ([newVal1, newVal2], [oldVal1, oldVal2]) => {
// console.log('watch監(jiān)聽中的newVal:', newVal1, newVal2);
// console.log('watch監(jiān)聽oldVal:', oldVal1, oldVal2);
// })
function handleWatchSingle1() {
state1.count++
}
function handleWatchSingle2() {
state2.value++
}
function handleWatchSingle3() {
state3.player = {
name: 'Wade',
achievement: ['3次NBA總冠軍', '曾經(jīng)的熱火三巨頭之一', '1次NBA總決賽mvp']
}
}
return {
state1,
state2,
state3,
handleWatchSingle1,
handleWatchSingle2,
handleWatchSingle3
}
},
}
</script>
computed(計(jì)算屬性)
- 接受一個(gè)
getter函數(shù),并根據(jù)getter的返回值返回一個(gè)不可變的響應(yīng)式ref對(duì)象。 - 接受一個(gè)具有
get和set函數(shù)的對(duì)象,用來(lái)創(chuàng)建可寫的ref對(duì)象
<template>
<div style="margin-top:30">
<h3>computedNum值為:{{computedNum}}</h3>
<h3>computedNum2值為:{{computedNum}}</h3>
<button @click="handleComputed">computed計(jì)算測(cè)試</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const state = ref(1)
const computedNum = computed(() => {
return state.value + 1
})
console.log('computed緩存后的值:', computedNum.value);
// 只可讀屬性,不可寫,會(huì)拋出警告 Write operation failed: computed value is readonly
function handleComputed() {
computedNum.value++
}
const computedNum2 = computed({
get: () => state.value + 2,
set: val => {
count.value = val - 0
}
})
return {
computedNum,
computedNum2,
handleComputed
}
},
}
</script>
組件通信
組件通信這塊跟vue2的區(qū)別不大,我們就拿常用的props跟emit來(lái)講解一下。
props
- 父級(jí)組件向子組件傳遞數(shù)據(jù)
emit
- 子組件想父組件傳遞數(shù)據(jù)
- 需要通過(guò)
emits選項(xiàng)來(lái)定義組件可觸發(fā)的事件
父組件
<template>
<Children :msg1="msg1" :msg2="msg2" @childClick="handleClick" />
</template>
<script>
import {ref, reactive} from 'vue';
import Children from './children.vue'
export default {
setup() {
const msg1 = ref('給子組件傳遞的消息1')
const msg2 = reactive({
name: '給子組件傳遞的消息2'
})
return {
msg1,
msg2
}
},
methods: {
handleClick(val) {
console.log('接收子組件emit過(guò)來(lái)的數(shù)據(jù):', val);
}
},
components: { Children }
}
</script>子組件
<template>
<div style="margin-top: 30px">props傳遞給子組件的消息:{{ msg1 }}</div>
<button @click="$emit('childClick', 6666)" style="margin-top: 30px">向父組件emits事件</button>
</template>
<script>
export default {
props: ['msg1', 'msg2'],
emits: ['childClick'],
setup(props) {
console.log('子組件接收父級(jí)組件傳遞過(guò)來(lái)的消息:', props);
},
}
</script>插槽
vue2中的使用
子組件
<template>
<slot name="title"></slot>
</template>父組件
<template slot="title">
<h2>周嶺:《認(rèn)知覺(jué)醒》</h2>
<template>vue3中的使用
vue3插槽中提供了v-slot:name 寫法,我們就拿作用域插槽來(lái)舉例
子組件
我們定一個(gè)可循環(huán)的插槽content
<template>
<!-- <slot name="title"></slot> -->
<div v-for="(item, index ) in items" :key="index">
<slot :item="item" name="content"></slot>
</div>
</template>
<script setup>
import {ref} from 'vue';
const items = ref(['認(rèn)知覺(jué)醒', '認(rèn)知驅(qū)動(dòng)']);
</script>父組件
父組件中可以有兩種方式來(lái)引入子組件中的插槽,其一是通過(guò)v-slot:content="scopend"的方式,其二是通過(guò)簡(jiǎn)寫#content="{item}"的方式
<template>
<SlotChild>
<!-- <template v-slot:content="scoped">
<div>{{ scoped.item }}</div>
</template> -->
<template #content="{item}">
<div>{{ item }}</div>
</template>
</SlotChild>
</template>
<script setup>
import SlotChild from './SlotChild.vue'
</script>生命周期
vue3的聲明周期如果是使用選項(xiàng)性Api的話,原來(lái)的生命周期鉤子可以照常使用,那如果選用vue3組合式Api的話,生命周期需要通過(guò)import引入的方式在setup中調(diào)用。下圖是vue3跟vu2聲明周期的區(qū)別

<template>
<div id="test">
<h3>{{ counter }}</h3>
<button @click="handleClick">聲明周期測(cè)試</button>
</div>
</template>
<script>
import {
ref,
onMounted,
onBeforeMount,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
export default {
setup() {
const counter = ref(0);
console.log('....');
function handleClick() {
counter.value += 1;
}
onBeforeMount(() => {
console.log("組件掛載之前");
});
onMounted(() => {
console.log("DOM掛載完成");
});
onBeforeUpdate(() => {
console.log("DOM更新之前", document.getElementById("test").innerHTML);
});
onUpdated(() => {
console.log("DOM更新完成", document.getElementById("test").innerHTML);
});
onBeforeUnmount(() => {
console.log("實(shí)例卸載之前");
});
onUnmounted(() => {
console.log("實(shí)例卸載之后");
});
return {
counter,
handleClick
}
},
}
</script>
vue-router 4.0
vue-router 3.x跟vue-router 4.x比起來(lái)寫法上的區(qū)別
vue-router 3.x
// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(Router)
const router = new Router({
routes
})
export default router
// main.js
import Vue from 'vue'
import router from './router'
// ...
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})vue-router 4.x
// router/index.js
import { createRouter } from 'vue-router'
import routes from './routes'
const router = createRouter({
history: createWebHistory(), // history模式
routes
})
// main.js
import { createApp } from 'vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')- 將
new Router()改成createRouter() - 將
mode: 'history'改成history: createWebHistory()
Composition API
useRouter、useRoute
通過(guò)useRouter進(jìn)行路由跳轉(zhuǎn)
<template>
<div class="mg30">
<el-button @click="handleJump" type="primary">關(guān)于我們</el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const handleJump = (query) => {
router.push({
name: "about",
query: {
id: 1
}
})
}
</script>通過(guò)useRoute來(lái)獲取傳遞過(guò)來(lái)的id
<template>
<div>關(guān)于我們</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log('id>>>', route.query.id);
</script>路由守衛(wèi)
全局守衛(wèi)
/router/index.js
詳情頁(yè)面meta中添加登錄標(biāo)識(shí)needLogin
let routes = [
{
path: '/detail',
name: 'detail',
component: () => import('@/views/detail.vue'),
meta: {
needLogin: true
}
}
]main.js
添加守衛(wèi)
import router from './router'
// 全局路由守衛(wèi)
router.beforeEach((to, from) => {
if (to.meta.needLogin) {
return {
name: 'login'
}
}
})路由獨(dú)享守衛(wèi)
/router/index.js
let routes = [
{
path: '/category/:id',
name: 'category',
component: () => import('@/views/category.vue'),
beforeEnter: (to, from) => {
// 如果不是正確的分類,跳轉(zhuǎn)到NotFound的頁(yè)面
console.log('id>>>>', to.params.id);
if (!["0", "1", "2"].includes(to.params.id)) {
return {
name: "NotFound",
// 這個(gè)是在地址欄保留輸入的信息,否則地址欄會(huì)非常的丑
params: { pathMatch: to.path.split("/").slice(1) },
query: to.query,
hash: to.hash,
};
}
}
}
]組件內(nèi)部守衛(wèi)
<template>
<div>關(guān)于我們</div>
</template>
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 頁(yè)面內(nèi)部的路由守衛(wèi)
onBeforeRouteLeave((to, from) => {
const answer = window.confirm('是否確認(rèn)離開')
if (answer) {
console.log('不離開');
return false
}
})
// 對(duì)于一個(gè)帶有動(dòng)態(tài)參數(shù)的路徑 /category/:catId,在 /category/1 和 /category/2 之間跳轉(zhuǎn)的時(shí)候, 會(huì)觸發(fā)onBeforeRouteUpdate的路由鉤子函數(shù),在鉤子函數(shù)中可以進(jìn)行數(shù)據(jù)的更新。
onBeforeRouteUpdate((to, from) => {
console.log('to>>>', to);
console.log('from>>>', from);
// if (to.params.id !== from.params.id) {
// userData.value = await fetchUser(to.params.id)
// }
})
</script>keep-alive 和 transition 必須用在 router-view 內(nèi)部
// vue-router 3
<keep-alive>
<router-view />
</keep-alive>
// vue-router 4
<router-view v-slot="{component}">
<keep-alive>
<component :is="component" />
</keep-alive>
</router-view> style新特性
跟vue2不同的是,vue3中提供了提供了很多不同的選擇器方便我們?cè)跇邮骄帉懮细拥撵`活多變。
深度選擇器
類似于sass語(yǔ)法中的v::deep,不過(guò)vue3中的樣式自帶深度作用域
<style scoped>
.parent :deep(div) {
margin-bottom: 10px;
}
</style>
<template>
<div class="parent">
<div class="set-up">:deep 深度作用域測(cè)試</div>
</div>
</template>全局選擇器
不用像vue2一樣寫全局作用域時(shí),需要單獨(dú)開啟一個(gè)style標(biāo)簽,同時(shí)去掉scoped屬性;vue3提供了一種便捷的寫法,只需要使用global屬性傳遞你想全局修改的樣式即可。
<template>
<div>全局選擇器測(cè)試</div>
<p :class="$style.green">module樣式測(cè)試</p>
</template>
<style scoped>
:global(div) {
color: red;
}
</style><style module> 標(biāo)簽會(huì)被編譯為 CSS Modules 并且將生成的 CSS 類作為 $style 對(duì)象的鍵暴露給組件。
<template>
<p :class="$style.green">module樣式測(cè)試</p>
</template>
<style module>
.green {
color: green;
}
</style>通過(guò)module自定義注入名稱
<template>
<p :class="classes.blue">useCssModule樣式測(cè)試</p>
</template>
<style module="classes">
.blue {
color: blue;
}
</style>
與組合式 API 一同使用
<script>
import { h, useCssModule } from 'vue'
export default {
setup() {
const style = useCssModule()
return () =>
h(
'div',
{
class: style.success
},
'Task complete!'
)
}
}
</script>
<style module>
.success {
color: #090;
}
</style>
Typescript基礎(chǔ)&項(xiàng)目中如何使用Typescript
對(duì)于TS,筆者認(rèn)為小項(xiàng)目中也不必集成TS,反倒會(huì)提升項(xiàng)目的編譯成本。那如果是大型項(xiàng)目的話,有必要嘗試接入TS,一方面可以減少不必要的類型判斷及文檔注釋,同時(shí)可以及早的發(fā)現(xiàn)錯(cuò)誤,做靜態(tài)類型檢查時(shí)就可以及時(shí)的發(fā)現(xiàn)問(wèn)題。另一方面,類、接口的使用更易于構(gòu)建和維護(hù)組件;那么,對(duì)于初學(xué)者我們有必要對(duì)TS的一些基本用法做一下普及。
基本的數(shù)據(jù)類型
/**
* @description: 基本的數(shù)據(jù)類型
* @return {*} boolean(布爾值)number(數(shù)值) Array<number> (泛型數(shù)組)Object (對(duì)象)null undefined
*/
let isDone: boolean = false;
console.log('isDon', isDone);
let num: number = 1;
console.log('num', num);
let str: string = '認(rèn)知覺(jué)醒';
console.log('str', str);
let arr: number[] = [1, 2, 3];
console.log('arr', arr);
// 泛型數(shù)組
let arr2: Array<number> = [1, 2, 3]
console.log('arr2', arr2);
let obj: Object = { id: 1 }
console.log('obj', obj);
let u: undefined = undefined
console.log('u', u);
let n: null = null;
console.log('n', n);
枚舉
// 數(shù)字類型枚舉與數(shù)字類型
enum CardSuit {
Clubs,
Diamonds,
Hearts,
Spades
}
console.log('CardSuit', CardSuit.Clubs); // 0
let col = CardSuit.Clubs;
col = 0 // 安全有效的
console.log('col', col); // 0
// 數(shù)字類型枚舉與字符串類型
enum Tristate {
False,
True,
Unkonw
}
console.log('字符串', Tristate[0]); // 'False'
console.log('number', Tristate['False']); // 0
console.log('字符串', Tristate[Tristate.False]); // 'False'
// 字符串枚舉
enum LogLevel {
info = 'info',
warn = 'warn',
error = 'error'
}
console.log('LogLevel', LogLevel.info); // 'info'
元祖
/**
* @description: 元祖
* @return {*} 允許數(shù)組各元素的類型不必相同
*/
let x: [string, number, boolean];
x = ['hello', 10, true];
console.log('正確元祖', x); // ['hello', 10, true]
// y = [10, 'hello', false]
// console.log('錯(cuò)誤的元祖', y);
任意值 Any
/**
* @description: 任意值 Any
* @return {*} 表示任意類型, 通常用于不確定內(nèi)容的類型,比如用戶的輸入或者是第三方庫(kù)代碼;實(shí)際項(xiàng)目中,此類型建議少用
*/
let notSure: any = 4;
notSure = 'maybe a string instead';
console.log('notSure', notSure); // 'maybe a string instead'
notSure = true;
console.log('notSure', notSure); // true
空值 void
/**
* @description: 空值 void
* @return {*} 與any相反,通常用于函數(shù),表示沒(méi)有返回值
*/
const voidFunc = (): void => {
console.log('這個(gè)函數(shù)沒(méi)有返回任何值');
// return msg; // 不能return
}
voidFunc()
interface
/**
* @description: 接口 interface
* @return {*} 類型契約,跟我們平時(shí)與服務(wù)端接口要先定義字段是一個(gè)道理
*/
interface Point {
x: number
y: number
z?: number
readonly l: number
}
const point: Point = { x: 10, y: 20, z: 30, l: 40 }
console.log('point', point);
const point2: Point = { x: '10', y: 20, z: 30 } // Error x應(yīng)該是Number類型
const point3: Point = { x: 10, y: 20, z: 30 } // Error l字段也是必傳
const point4: Point = { x: 10, y: 20, z: 30, l: 40, m: 50 } // Error m字段沒(méi)有定義
const point5: Point = { x: 10, y: 20, l: 40 } // 正常
point5.l = 20; // Error l字段是只讀類型,不能修改
函數(shù)參數(shù)類型與返回值類型
/**
* @description: 函數(shù)參數(shù)類型與返回值類型
* @return {*}
*/
function sum(a: number, b: number): number {
return a + b;
}
console.log('sum', sum(2, 3)); // 5
// 配合interface使用
interface Point {
x: number
y: number
}
function sum2({x, y}: Point): number {
return x + y;
}
console.log('sum2', sum2({x: 1, y: 2})); // 3
泛型
/**
* @description: 泛型
* @return {*} 泛型的意義在于函數(shù)的重用性,設(shè)計(jì)原則希望組件不僅能夠支持當(dāng)前的數(shù)據(jù)類型,同時(shí)也支持未來(lái)的數(shù)據(jù)類型
* 語(yǔ)法:<T>(arg: T): T
*/
// 比如我們最初設(shè)計(jì)函數(shù)identity 入?yún)镾tring
function identity(arg: String) {
return arg;
}
console.log(identity('hello')); // hello
// 后來(lái)隨著業(yè)務(wù)的迭代我們又需要支持 Number
function identity2(arg: String) {
return arg;
}
console.log(identity(2)); // Argument of type 'number' is not assignable to parameter of type 'String'
// 那我們?yōu)槭裁床挥胊ny呢?使用any會(huì)導(dǎo)致丟失掉一些信息,我們無(wú)法確定要返回值到底是屬于什么數(shù)據(jù)類型
const hello1: String = 'Hello vue3';
const hello2: Number = 666;
function say<T>(arg: T): T {
return arg;
}
console.log('泛型1:', say(hello1)); // Hello vue3
console.log('泛型2:', say(hello2)); // 666
// 泛型約束
// 我們使用同樣的例子,加了一個(gè)console,但是很不幸運(yùn),報(bào)錯(cuò)了,因?yàn)榉盒蜔o(wú)法保證每種類型都有.length 屬性
const hello3: String = 'Hello vue3';
function say2<T>(arg: T): T {
console.log(arg.length); // Property 'length' does not exist on type 'T'
return arg;
}
console.log('泛型3:', say2(hello3)); // Hello vue3
interface Lengthwise {
length: number
}
function say3<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
console.log(say3(1)); // Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
console.log(say3({ value: 'hello vue', length: 10 })); // '{ value: 'hello vue', length: 10 }'
交叉類型
interface foo {
x: number
}
interface bar {
b: string
}
type intersection = foo & bar
const result: intersection = {
x: 10,
b: 'hello'
}
console.log('result', result);聯(lián)合類型
/**
* @description: 聯(lián)合類型
* @return {*} 表示一個(gè)值可以為幾種數(shù)據(jù)類型之一
*/
type arg = string | number | boolean
const foo = (arg: arg): any => {
console.log('arg', arg);
}
foo(1)
foo('1')
foo(true)
函數(shù)重載
/**
* @description: 函數(shù)重載
* @return {*} 1個(gè)函數(shù)可以執(zhí)行多項(xiàng)任務(wù)的能力
*/
// add函數(shù),它可以接收string類型的參數(shù)進(jìn)行拼接,也可以接收number類型的參數(shù)進(jìn)行相加
function add <T, U>(arg1: T, arg2: U) {
// 在實(shí)現(xiàn)上我們要注意嚴(yán)格判斷兩個(gè)參數(shù)的類型是否相等,而不能簡(jiǎn)單的寫一個(gè) arg1 + arg2
if (typeof arg1 === 'string' && typeof arg2 === 'string') {
return arg1 + arg2
} else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
return arg1 + arg2
}
}
console.log('number類型相加', add(1, 2));
console.log('string類型拼接', add('1', '2'));
vue3項(xiàng)目中如何集成TS
- 首先,你可以在初始化項(xiàng)目的時(shí)候就選擇TS模板,直接將TS相關(guān)配置集成到項(xiàng)目中去。
- 當(dāng)然,你也可以手動(dòng)去配置TS
安裝TS
npm i typescript
項(xiàng)目根目錄新建tsconfig.json文件,用于TS的編譯基礎(chǔ)文件
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}- 項(xiàng)目中使用
script標(biāo)簽中聲明langg="ts",然后就可以愉快的使用TS的項(xiàng)目語(yǔ)法了,下邊這段代碼只是一些簡(jiǎn)單的示例。
<template>
<div>
<h2>標(biāo)題:{{book.title}}</h2>
<h2>作者:{{book.author}}</h2>
<h2>出版日期:{{book.year}}</h2>
<hr>
<h3>{{allTitle}}</h3>
<el-button @click="setTitle('我是傳入的數(shù)據(jù)')" type="primary">設(shè)置數(shù)據(jù)</el-button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, toRefs, reactive } from 'vue';
// 定義Book接口
interface Book {
title: String
author: String
year?: Number,
handleChangeName?(): void
}
export default defineComponent ({
data() {
let book: Book = {
title: 'vue3 typescript',
author: "vue Team",
year: 2020,
}
return {
book
}
},
setup() {
let year1 = ref<String | Number>('2022')
console.log('year1', year1.value);
// 第一種方式
// const book1: Book = reactive({
// name: year1.value,
// desc: "vue3 進(jìn)階學(xué)習(xí)加油",
// setNamechange(){
// this.name = "我是新設(shè)置的"
// }
// });
// // 第二種方式
// const book2 = reactive<Book>({
// name: "vue3--typeScript",
// desc: "學(xué)習(xí)ts加油",
// year: 2020,
// });
// // 第三種方式
// const book3 = reactive({
// name: "vue3--typeScript-第三種方式",
// desc: "ts類型第三種方式",
// year: 2022,
// }) as Book;
return {
// ...toRefs(book1),
// book2,
// book3,
// year1,
};
},
computed: {
// 返回值類型為String
allTitle(): String {
return `歡迎語(yǔ) : ${this.book.title}`
}
},
methods: {
// 入?yún)镾tring 返回空值
setTitle(arg: String): void {
this.book.title = arg;
this.book.year = 2022
this.book.author = '尤雨溪'
}
}
})
</script>狀態(tài)管理Pinia
由于本文章篇幅較大,會(huì)在之后的文章中單獨(dú)來(lái)講解。
到此這篇關(guān)于vue3基礎(chǔ)知識(shí)剖析的文章就介紹到這了,更多相關(guān)vue3基礎(chǔ)知識(shí)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue路由跳轉(zhuǎn)攜帶參數(shù)的方式總結(jié)
我們知道在vue中每個(gè)頁(yè)面都需要在路由中聲明,下面這篇文章主要給大家介紹了關(guān)于vue路由跳轉(zhuǎn)攜帶參數(shù)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
淺談Vue.js中如何實(shí)現(xiàn)自定義下拉菜單指令
這篇文章主要介紹了淺談Vue.js中如何實(shí)現(xiàn)自定義下拉菜單指令,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
基于vue寫一個(gè)全局Message組件的實(shí)現(xiàn)
這篇文章主要介紹了基于vue寫一個(gè)全局Message組件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
vue-cli 自定義指令directive 添加驗(yàn)證滑塊示例
本篇文章主要介紹了vue-cli 自定義指令directive 添加驗(yàn)證滑塊示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
用Vue.extend構(gòu)建消息提示組件的方法實(shí)例
本篇文章主要介紹了用Vue.extend構(gòu)建消息提示組件的方法實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08

