vue3基礎(chǔ)知識剖析
前言
前段時間,由新東方出品直播帶貨品牌
東方甄選
火爆全網(wǎng),其中最受大家關(guān)注的主播董宇輝,用網(wǎng)友的調(diào)侃來說“長著一個顆粒無收的臉,卻擁有五谷豐登的靈魂”
。他在直播中推薦五常大米時說:“廚房里充滿了飯香,就是人間浪漫。”
,介紹水蜜桃:“這個水蜜桃,美好的像穿越大峽谷的風(fēng),像仲夏夜的夢”
。賣牛排,告訴觀眾這是“Original Cutting”
。讓網(wǎng)友贊嘆不絕,大家都說這買的不是吃的,買的是知識付費
,買的是靈魂洗禮
。最重要的是,他盡然還是一個英語老師。很多人感嘆,好好讀書太重要了,因為知識能給人帶來力量,帶來高貴的靈魂。相比那些快節(jié)奏、聲嘶力竭、充滿商業(yè)誘導(dǎo)的的直播模式,簡直就是降維打擊
。
從筆者的角度來看,董宇輝的成功并非偶然,能夠飽讀詩書,一定源于自己多年不斷的思考跟總結(jié),不斷的追求學(xué)習(xí)的本質(zhì)才能讓自己在無意之間沉淀的像個詩人,像個哲學(xué)家。這背后的付出,常人肯定無法想象。作家周嶺說過
“所謂的學(xué)習(xí),不是努力,努力,在努力。而是反饋,反饋,再反饋。光靠一味的輸入,而不輸出,這種學(xué)習(xí)大概率是低效率的”。
就像咱們前端技術(shù)圈一樣,框架層出不窮,版本迭代快的讓人無法喘息。很多小伙伴都焦慮的吶喊,學(xué)不動了。筆者認為,真正高效的學(xué)習(xí)一定是需要在輸入的同時,要有很好的輸出,讓自己積累更多的正向反饋,就像我們平時學(xué)習(xí)某一種技術(shù)棧一樣,光是一味的學(xué)習(xí)不行,還要做出高質(zhì)量的實踐跟輸出才行!
筆者這篇文章會從vue3基礎(chǔ)的知識點開始剖析,特別是在將
composition API
的時候,在代碼示例中不會一上來就使用setup語法糖
,而是用早期的setup函數(shù)
,這樣方便于初學(xué)的小伙伴們理解跟學(xué)習(xí)。文章篇幅較大,接下來,請您花個10分鐘耐心的看完,或許會有不一樣的收貨。
聲明
- 本文中下邊所有的示例代碼都可以直接訪問這個網(wǎng)站點擊這里查看效果需要源代碼的小伙伴可以在評論區(qū)下留言,或者私信我。
- 本文章的講解的所有實例面向?qū)ue3的初學(xué)者,如有講解不到位,或者有偏差的地方,歡迎大家留言指出。
vue3.0有哪些新特性
- Composition Api (最重要的新特性)
- 組件通信
- 生命周期
- 自定義Hook
- 插槽
- v-model的更改
- 更加純粹的Tree-shaking
- 配合狀態(tài)管理的Pinia
- 配合升級的vue-router 4.x
- 配合升級的打包工具vite
- 配合TS在項目中自由使用
vue3.0的優(yōu)缺點
優(yōu)點
- 使用
vue3
最大的優(yōu)勢個人認為倒不是它的Api,而是配合使用的vite
打包工具,特別是大型項目本地啟動要比當前的webpack5
要快至少2倍
以上(項目中測試過) - 比起
vue 2.x
,Composition Api
的優(yōu)勢要明顯的多,如果習(xí)慣了setup語法糖
的寫法,你會發(fā)現(xiàn)爽的飛起,很多之前在vue 2.x
中大量重復(fù)邏輯不存在了 - 底層通過
Proxy
來實現(xiàn)雙向綁定,性能上提升了很多 - 對
TypeScript
支持度更好,可以很愉快的在項目中使用TypeScript
缺點
- 如果還有
IE情節(jié)
的公司,那vue3
確實不太適合,因為vue3
已經(jīng)拋棄了對IE11
的支持,再說了 微軟人家自己都不打算維護IE
了,兄弟們,放棄IE
擁抱chrome
吧! Composition Api
的寫法需要花一點點時間來適應(yīng),畢竟學(xué)習(xí)新語法還是需要成本的
如何解鎖vue3.0
體驗vue3.0的4中姿勢
- 通過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
升級的,請注意 @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打包工具,直接初始化項目。
核心的composition API
setup
setup
是vue3
提出的一個非常重要的選項,也是Composition Api
最為核心的語法之一。setup
執(zhí)行時機是在beforeCreate
之前執(zhí)行的。setup
返回的是一個對象,對象中的所有屬性都是可以在template
中使用setup
中不能使用this
setup
中注冊生命周期onMounted
、watch
、computed
等,我們會在下邊詳細講解
setup參數(shù)
- props
- context
<script> export default { setup (props, context) { return {} } } </script>
setup語法糖
既然上邊提到了setup
語法,那就有必要把setup
語法糖介紹一下,我們在實際的項目開發(fā)中在熟悉了setup語法的本質(zhì)后,也推薦大家使用setup
語法糖來編寫,這樣也可以大大提升開發(fā)效率。
- 不需要像上述一樣
return
,只需要在<script setup>
中聲明一下即可 - 任何在
<script setup>
聲明的頂層的綁定 (包括聲明的變量,函數(shù)聲明,以及import
引入的內(nèi)容) 都可以在模板中直接使用 - 組件在語法糖中可以自動注冊,無需再通過
components
進行注冊
<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
中用來做數(shù)據(jù)定義使用的,如同vue2
中在data
中做數(shù)據(jù)定義一樣,示例代碼如下:
<template> <h3>{{ state.count }}</h3> <h3>{{ num }}</h3> <el-button @click="handleAdd" type="primary">ref計算</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)上有很多文章簡單的定義為ref
負責處理基本數(shù)據(jù)類型的雙向綁定,reactive
負責處理對象的雙向綁定。其實,這樣筆者會覺得給很多初學(xué)者帶來很多誤導(dǎo),其實ref
也可以處理對象的雙向綁定,就像下邊這段代碼一樣。
<template> <el-button @click="handleAdd" type="primary">ref計算</el-button> <h3>{{ obj.count }}</h3> </template> <script> export default { setup() { // ref 對象雙向綁定 const obj = ref({ count: 1 }) function handleAdd() { obj.value.count = obj.value.count + 1 } return { obj, handleAdd } } } </script>
watch跟watchEffect
watchEffect
- 當傳入一個函數(shù)時,可以響應(yīng)式的自動收集依賴,當依賴變更時重新運行該函數(shù);
- 使用是需要配置
flush: post
,否則依賴在監(jiān)聽時無法被立即更新 - 也可以使用
stop
來立即停止對函數(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
watch
API 與選項式 APIthis.$watch
(以及相應(yīng)的watch
選項) 完全等效。watch
需要偵聽特定的數(shù)據(jù)源,并在單獨的回調(diào)函數(shù)中執(zhí)行副作用。默認情況下,它也是惰性的——即回調(diào)僅在偵聽源發(fā)生變化時被調(diào)用。
與 watchEffect
相比,watch
:
- 是一個返回任意值的
getter
函數(shù) - 是一個包裝的對象,可以是
ref
對象、也可以reactive
對象 - 可以同時監(jiān)聽多個數(shù)據(jù)源
- 監(jiān)聽是需要配置
deep: true
,否則回調(diào)函數(shù)無法被觸發(fā)
<template> <h3>監(jiān)聽單個數(shù)據(jù)源1:{{state1.count}}</h3> <button @click="handleWatchSingle1">watch監(jiān)聽測試1</button> <h3>監(jiān)聽單個數(shù)據(jù)源2:{{state2}}</h3> <button @click="handleWatchSingle2">watch監(jiān)聽測試2</button> <h3>監(jiān)聽復(fù)雜對象數(shù)據(jù)源:{{state3.player}}</h3> <button @click="handleWatchSingle3">watch監(jiān)聽測試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ù)雜對象中的newVal:', newVal); console.log('watch監(jiān)聽復(fù)雜對象中的oldVal:', oldVal); }, { deep: true, // immediate: true }) // 同時監(jiān)聽多個值 // 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(計算屬性)
- 接受一個
getter
函數(shù),并根據(jù)getter
的返回值返回一個不可變的響應(yīng)式ref
對象。 - 接受一個具有
get
和set
函數(shù)的對象,用來創(chuàng)建可寫的ref
對象
<template> <div style="margin-top:30"> <h3>computedNum值為:{{computedNum}}</h3> <h3>computedNum2值為:{{computedNum}}</h3> <button @click="handleComputed">computed計算測試</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); // 只可讀屬性,不可寫,會拋出警告 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來講解一下。
props
- 父級組件向子組件傳遞數(shù)據(jù)
emit
- 子組件想父組件傳遞數(shù)據(jù)
- 需要通過
emits
選項來定義組件可觸發(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過來的數(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('子組件接收父級組件傳遞過來的消息:', props); }, } </script>
插槽
vue2中的使用
子組件
<template> <slot name="title"></slot> </template>
父組件
<template slot="title"> <h2>周嶺:《認知覺醒》</h2> <template>
vue3中的使用
vue3
插槽中提供了v-slot:name
寫法,我們就拿作用域插槽來舉例
子組件
我們定一個可循環(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(['認知覺醒', '認知驅(qū)動']); </script>
父組件
父組件中可以有兩種方式來引入子組件中的插槽,其一是通過v-slot:content="scopend"
的方式,其二是通過簡寫#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的聲明周期如果是使用選項性Api的話,原來的生命周期鉤子可以照常使用,那如果選用vue3組合式Api的話,生命周期需要通過import引入的方式在setup中調(diào)用。下圖是vue3跟vu2聲明周期的區(qū)別
<template> <div id="test"> <h3>{{ counter }}</h3> <button @click="handleClick">聲明周期測試</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("實例卸載之前"); }); onUnmounted(() => { console.log("實例卸載之后"); }); return { counter, handleClick } }, } </script>
vue-router 4.0
vue-router 3.x跟vue-router 4.x比起來寫法上的區(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
通過useRouter
進行路由跳轉(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>
通過useRoute
來獲取傳遞過來的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
詳情頁面meta中
添加登錄標識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' } } })
路由獨享守衛(wèi)
/router/index.js
let routes = [ { path: '/category/:id', name: 'category', component: () => import('@/views/category.vue'), beforeEnter: (to, from) => { // 如果不是正確的分類,跳轉(zhuǎn)到NotFound的頁面 console.log('id>>>>', to.params.id); if (!["0", "1", "2"].includes(to.params.id)) { return { name: "NotFound", // 這個是在地址欄保留輸入的信息,否則地址欄會非常的丑 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' // 頁面內(nèi)部的路由守衛(wèi) onBeforeRouteLeave((to, from) => { const answer = window.confirm('是否確認離開') if (answer) { console.log('不離開'); return false } }) // 對于一個帶有動態(tài)參數(shù)的路徑 /category/:catId,在 /category/1 和 /category/2 之間跳轉(zhuǎn)的時候, 會觸發(fā)onBeforeRouteUpdate的路由鉤子函數(shù),在鉤子函數(shù)中可以進行數(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中提供了提供了很多不同的選擇器方便我們在樣式編寫上更加的靈活多變。
深度選擇器
類似于sass
語法中的v::deep
,不過vue3
中的樣式自帶深度作用域
<style scoped> .parent :deep(div) { margin-bottom: 10px; } </style> <template> <div class="parent"> <div class="set-up">:deep 深度作用域測試</div> </div> </template>
全局選擇器
不用像vue2
一樣寫全局作用域時,需要單獨開啟一個style
標簽,同時去掉scoped
屬性;vue3
提供了一種便捷的寫法,只需要使用global
屬性傳遞你想全局修改的樣式即可。
<template> <div>全局選擇器測試</div> <p :class="$style.green">module樣式測試</p> </template> <style scoped> :global(div) { color: red; } </style>
<style module>
標簽會被編譯為 CSS Modules
并且將生成的 CSS
類作為 $style
對象的鍵暴露給組件。
<template> <p :class="$style.green">module樣式測試</p> </template> <style module> .green { color: green; } </style>
通過module自定義注入名稱
<template> <p :class="classes.blue">useCssModule樣式測試</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ǔ)&項目中如何使用Typescript
對于TS,筆者認為小項目中也不必集成TS,反倒會提升項目的編譯成本。那如果是大型項目的話,有必要嘗試接入TS,一方面可以減少不必要的類型判斷及文檔注釋,同時可以及早的發(fā)現(xiàn)錯誤,做靜態(tài)類型檢查時就可以及時的發(fā)現(xiàn)問題。另一方面,類、接口的使用更易于構(gòu)建和維護組件;那么,對于初學(xué)者我們有必要對TS的一些基本用法做一下普及。
基本的數(shù)據(jù)類型
/** * @description: 基本的數(shù)據(jù)類型 * @return {*} boolean(布爾值)number(數(shù)值) Array<number> (泛型數(shù)組)Object (對象)null undefined */ let isDone: boolean = false; console.log('isDon', isDone); let num: number = 1; console.log('num', num); let str: string = '認知覺醒'; 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('錯誤的元祖', y);
任意值 Any
/** * @description: 任意值 Any * @return {*} 表示任意類型, 通常用于不確定內(nèi)容的類型,比如用戶的輸入或者是第三方庫代碼;實際項目中,此類型建議少用 */ 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ù),表示沒有返回值 */ const voidFunc = (): void => { console.log('這個函數(shù)沒有返回任何值'); // return msg; // 不能return } voidFunc()
interface
/** * @description: 接口 interface * @return {*} 類型契約,跟我們平時與服務(wù)端接口要先定義字段是一個道理 */ 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字段沒有定義 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è)計原則希望組件不僅能夠支持當前的數(shù)據(jù)類型,同時也支持未來的數(shù)據(jù)類型 * 語法:<T>(arg: T): T */ // 比如我們最初設(shè)計函數(shù)identity 入?yún)镾tring function identity(arg: String) { return arg; } console.log(identity('hello')); // hello // 后來隨著業(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會導(dǎo)致丟失掉一些信息,我們無法確定要返回值到底是屬于什么數(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 // 泛型約束 // 我們使用同樣的例子,加了一個console,但是很不幸運,報錯了,因為泛型無法保證每種類型都有.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 {*} 表示一個值可以為幾種數(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個函數(shù)可以執(zhí)行多項任務(wù)的能力 */ // add函數(shù),它可以接收string類型的參數(shù)進行拼接,也可以接收number類型的參數(shù)進行相加 function add <T, U>(arg1: T, arg2: U) { // 在實現(xiàn)上我們要注意嚴格判斷兩個參數(shù)的類型是否相等,而不能簡單的寫一個 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項目中如何集成TS
- 首先,你可以在初始化項目的時候就選擇TS模板,直接將TS相關(guān)配置集成到項目中去。
- 當然,你也可以手動去配置TS
安裝TS
npm i typescript
項目根目錄新建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"] }
- 項目中使用
script標簽中聲明langg="ts",然后就可以愉快的使用TS的項目語法了,下邊這段代碼只是一些簡單的示例。
<template> <div> <h2>標題:{{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 進階學(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 `歡迎語 : ${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
由于本文章篇幅較大,會在之后的文章中單獨來講解。
到此這篇關(guān)于vue3基礎(chǔ)知識剖析的文章就介紹到這了,更多相關(guān)vue3基礎(chǔ)知識內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue使用v-model封裝el-pagination組件的全過程
通過封裝el-pagination組件開發(fā)自定義分頁組件的類似文章網(wǎng)上已經(jīng)有很多了,但看了一圈,總是不如意,于是決定還是自己動手搞一個,對v-model封裝el-pagination組件相關(guān)知識感興趣的朋友一起看看吧2021-07-07vue上傳項目到git時,如何忽略node_modules文件夾
這篇文章主要介紹了vue上傳項目到git時,如何忽略node_modules文件夾,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09