vue3基礎知識剖析

前言

前段時間,由新東方出品直播帶貨品牌東方甄選火爆全網,其中最受大傢關註的主播董宇輝,用網友的調侃來說“長著一個顆粒無收的臉,卻擁有五谷豐登的靈魂”。他在直播中推薦五常大米時說:“廚房裡充滿瞭飯香,就是人間浪漫。”,介紹水蜜桃:“這個水蜜桃,美好的像穿越大峽谷的風,像仲夏夜的夢”。賣牛排,告訴觀眾這是“Original Cutting”。讓網友贊嘆不絕,大傢都說這買的不是吃的,買的是知識付費,買的是靈魂洗禮。最重要的是,他盡然還是一個英語老師。很多人感嘆,好好讀書太重要瞭,因為知識能給人帶來力量,帶來高貴的靈魂。相比那些快節奏、聲嘶力竭、充滿商業誘導的的直播模式,簡直就是降維打擊

從筆者的角度來看,董宇輝的成功並非偶然,能夠飽讀詩書,一定源於自己多年不斷的思考跟總結,不斷的追求學習的本質才能讓自己在無意之間沉淀的像個詩人,像個哲學傢。這背後的付出,常人肯定無法想象。作傢周嶺說過“所謂的學習,不是努力,努力,在努力。而是反饋,反饋,再反饋。光靠一味的輸入,而不輸出,這種學習大概率是低效率的”。就像咱們前端技術圈一樣,框架層出不窮,版本迭代快的讓人無法喘息。很多小夥伴都焦慮的吶喊,學不動瞭。筆者認為,真正高效的學習一定是需要在輸入的同時,要有很好的輸出,讓自己積累更多的正向反饋,就像我們平時學習某一種技術棧一樣,光是一味的學習不行,還要做出高質量的實踐跟輸出才行!

筆者這篇文章會從vue3基礎的知識點開始剖析,特別是在將composition API的時候,在代碼示例中不會一上來就使用setup語法糖,而是用早期的setup函數,這樣方便於初學的小夥伴們理解跟學習。文章篇幅較大,接下來,請您花個10分鐘耐心的看完,或許會有不一樣的收貨。

聲明

  • 本文中下邊所有的示例代碼都可以直接訪問這個網站點擊這裡查看效果需要源代碼的小夥伴可以在評論區下留言,或者私信我。
  • 本文章的講解的所有實例面向對vue3的初學者,如有講解不到位,或者有偏差的地方,歡迎大傢留言指出。

vue3.0有哪些新特性

  • Composition Api (最重要的新特性)
  • 組件通信
  • 生命周期
  • 自定義Hook
  • 插槽
  • v-model的更改
  • 更加純粹的Tree-shaking
  • 配合狀態管理的Pinia
  • 配合升級的vue-router 4.x
  • 配合升級的打包工具vite
  • 配合TS在項目中自由使用

vue3.0的優缺點

優點

  • 使用vue3最大的優勢個人認為倒不是它的Api,而是配合使用的vite打包工具,特別是大型項目本地啟動要比當前的webpack5要快至少2倍以上(項目中測試過)
  • 比起vue 2.xComposition Api的優勢要明顯的多,如果習慣瞭setup語法糖的寫法,你會發現爽的飛起,很多之前在vue 2.x中大量重復邏輯不存在瞭
  • 底層通過Proxy來實現雙向綁定,性能上提升瞭很多
  • TypeScript支持度更好,可以很愉快的在項目中使用TypeScript

缺點

  • 如果還有IE情節的公司,那vue3確實不太適合,因為vue3已經拋棄瞭對IE11的支持,再說瞭 微軟人傢自己都不打算維護IE瞭,兄弟們,放棄IE擁抱chrome吧!
  • Composition Api的寫法需要花一點點時間來適應,畢竟學習新語法還是需要成本的

如何解鎖vue3.0

體驗vue3.0的4中姿勢

  • 通過CDN
<script src="https://unpkg.com/vue@next"></script>
  • npm
# 最新穩定版
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

  • setupvue3提出的一個非常重要的選項,也是Composition Api最為核心的語法之一。
  • setup執行時機是在beforeCreate之前執行的。
  • setup返回的是一個對象,對象中的所有屬性都是可以在template中使用
  • setup中不能使用this
  • setup中註冊生命周期onMountedwatchcomputed等,我們會在下邊詳細講解

setup參數

  • props
  • context
<script>
export default {
  setup (props, context) {
    return {}
  }
}
</script>

setup語法糖

既然上邊提到瞭setup語法,那就有必要把setup語法糖介紹一下,我們在實際的項目開發中在熟悉瞭setup語法的本質後,也推薦大傢使用setup語法糖來編寫,這樣也可以大大提升開發效率。

  • 不需要像上述一樣return,隻需要在<script setup>中聲明一下即可
  • 任何在 <script setup> 聲明的頂層的綁定 (包括聲明的變量,函數聲明,以及 import 引入的內容) 都可以在模板中直接使用
  • 組件在語法糖中可以自動註冊,無需再通過components進行註冊
<script setup>
import {ref} from 'vue'
let property = ref('這裡是響應式屬性');
// 這裡我們引入瞭子組件SetUp.vue
import SetUp from '@/components/SetUp.vue'
</script>

ref、reactive

  • refreactive都是vue3中用來做數據定義使用的,如同vue2中在data中做數據定義一樣,示例代碼如下:
<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>
  • refreactive的區別在哪呢?很多人分不清楚,網上有很多文章簡單的定義為ref負責處理基本數據類型的雙向綁定,reactive負責處理對象的雙向綁定。其實,這樣筆者會覺得給很多初學者帶來很多誤導,其實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

  • 當傳入一個函數時,可以響應式的自動收集依賴,當依賴變更時重新運行該函數;
  • 使用是需要配置flush: post,否則依賴在監聽時無法被立即更新
  • 也可以使用stop來立即停止對函數的監聽
<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監聽:${root.value}`);
        }, {
            flush: 'post'
        })
        return {
            root
        }
    },
}
</script>

watch

watch API 與選項式 API this.$watch (以及相應的 watch 選項) 完全等效。watch 需要偵聽特定的數據源,並在單獨的回調函數中執行副作用。默認情況下,它也是惰性的——即回調僅在偵聽源發生變化時被調用。

watchEffect 相比,watch

  • 是一個返回任意值的getter函數
  • 是一個包裝的對象,可以是ref對象、也可以reactive對象
  • 可以同時監聽多個數據源
  • 監聽是需要配置deep: true,否則回調函數無法被觸發
<template>
    <h3>監聽單個數據源1:{{state1.count}}</h3>
    <button @click="handleWatchSingle1">watch監聽測試1</button>
    <h3>監聽單個數據源2:{{state2}}</h3>
    <button @click="handleWatchSingle2">watch監聽測試2</button>
    <h3>監聽復雜對象數據源:{{state3.player}}</h3>
    <button @click="handleWatchSingle3">watch監聽測試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常規賽mvp', '03年選秀狀元', '4次NBA總冠軍']
            }
        })
        watch(() => state1.count, (newVal, oldVal) => {
            console.log('watch監聽reactive中的newVal:', newVal);
            console.log('watch監聽reactive中的oldVal:', oldVal);
        })
        watch(() => state2.value, (newVal, oldVal) => {
            console.log('watch監聽ref中的newVal:', newVal);
            console.log('watch監聽ref中的oldVal:', oldVal);
        })
        watch(() => state3.player, (newVal, oldVal) => {
            console.log('watch監聽復雜對象中的newVal:', newVal);
            console.log('watch監聽復雜對象中的oldVal:', oldVal);
        }, {
            deep: true,
            // immediate: true
        })
        // 同時監聽多個值
        // watch([() => state1.count, state2.value], ([newVal1, newVal2], [oldVal1, oldVal2]) => {
        //     console.log('watch監聽中的newVal:', newVal1, newVal2);
        //     console.log('watch監聽oldVal:', oldVal1, oldVal2);
        // })
        function handleWatchSingle1() {
            state1.count++
        }
        function handleWatchSingle2() {
            state2.value++
        }
        function handleWatchSingle3() {
            state3.player = {
                name: 'Wade',
                achievement: ['3次NBA總冠軍', '曾經的熱火三巨頭之一', '1次NBA總決賽mvp']
            }
        }
        return {
            state1,
            state2,
            state3,
            handleWatchSingle1,
            handleWatchSingle2,
            handleWatchSingle3
        }
    },
}
</script>

computed(計算屬性)

  • 接受一個 getter 函數,並根據getter 的返回值返回一個不可變的響應式 ref 對象。
  • 接受一個具有 getset 函數的對象,用來創建可寫的 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的區別不大,我們就拿常用的props跟emit來講解一下。

props

  • 父級組件向子組件傳遞數據

emit

  • 子組件想父組件傳遞數據
  • 需要通過emits選項來定義組件可觸發的事件

父組件

<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過來的數據:', 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 寫法,我們就拿作用域插槽來舉例

子組件

我們定一個可循環的插槽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(['認知覺醒', '認知驅動']);
</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中調用。下圖是vue3跟vu2聲明周期的區別

<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比起來寫法上的區別

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進行路由跳轉

<template>
  <div class="mg30">
    <el-button @click="handleJump" type="primary">關於我們</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>關於我們</div>
</template>

<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()
console.log('id>>>', route.query.id);
</script>

路由守衛

全局守衛

/router/index.js

詳情頁面meta中添加登錄標識needLogin

let routes = [
    {
        path: '/detail',
        name: 'detail',
        component: () => import('@/views/detail.vue'),
        meta: {
            needLogin: true
        }
    }
]

main.js

添加守衛

import router from './router'

// 全局路由守衛
router.beforeEach((to, from) => {
    if (to.meta.needLogin) {
        return {
            name: 'login'
        }
    }
})

路由獨享守衛

/router/index.js

let routes = [
    {
        path: '/category/:id',
        name: 'category',
        component: () => import('@/views/category.vue'),
        beforeEnter: (to, from) => {
            // 如果不是正確的分類,跳轉到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,
              };
            }
        }
    }
]

組件內部守衛

<template>
    <div>關於我們</div>
</template>

<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'


// 頁面內部的路由守衛
onBeforeRouteLeave((to, from) => {
    const answer = window.confirm('是否確認離開') 
    if (answer) { 
        console.log('不離開'); 
        return false 
    }
})

// 對於一個帶有動態參數的路徑 /category/:catId,在 /category/1 和 /category/2 之間跳轉的時候, 會觸發onBeforeRouteUpdate的路由鉤子函數,在鉤子函數中可以進行數據的更新。
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 內部

// 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基礎&項目中如何使用Typescript

對於TS,筆者認為小項目中也不必集成TS,反倒會提升項目的編譯成本。那如果是大型項目的話,有必要嘗試接入TS,一方面可以減少不必要的類型判斷及文檔註釋,同時可以及早的發現錯誤,做靜態類型檢查時就可以及時的發現問題。另一方面,類、接口的使用更易於構建和維護組件;那麼,對於初學者我們有必要對TS的一些基本用法做一下普及。

基本的數據類型

/**
 * @description: 基本的數據類型
 * @return {*} boolean(佈爾值)number(數值) Array<number> (泛型數組)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);

// 泛型數組
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);

枚舉

// 數字類型枚舉與數字類型
enum CardSuit {
    Clubs,
    Diamonds,
    Hearts,
    Spades
}

console.log('CardSuit', CardSuit.Clubs); // 0
let col = CardSuit.Clubs;
col = 0 // 安全有效的
console.log('col', col); // 0

// 數字類型枚舉與字符串類型
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 {*} 允許數組各元素的類型不必相同
 */
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 {*} 表示任意類型, 通常用於不確定內容的類型,比如用戶的輸入或者是第三方庫代碼;實際項目中,此類型建議少用
 */
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相反,通常用於函數,表示沒有返回值
 */
const voidFunc = (): void => {
    console.log('這個函數沒有返回任何值');
    // return msg; // 不能return
}
voidFunc()

interface

/**
 * @description: 接口 interface
 * @return {*} 類型契約,跟我們平時與服務端接口要先定義字段是一個道理
 */
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應該是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字段是隻讀類型,不能修改

函數參數類型與返回值類型

/**
 * @description: 函數參數類型與返回值類型
 * @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 {*} 泛型的意義在於函數的重用性,設計原則希望組件不僅能夠支持當前的數據類型,同時也支持未來的數據類型
 * 語法:<T>(arg: T): T
 */

// 比如我們最初設計函數identity 入參為String
function identity(arg: String) {
    return arg;
}
console.log(identity('hello')); // hello

// 後來隨著業務的迭代我們又需要支持 Number
function identity2(arg: String) {
    return arg;
}
console.log(identity(2)); // Argument of type 'number' is not assignable to parameter of type 'String'

// 那我們為什麼不用any呢?使用any會導致丟失掉一些信息,我們無法確定要返回值到底是屬於什麼數據類型
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);

聯合類型

/**
 * @description: 聯合類型
 * @return {*} 表示一個值可以為幾種數據類型之一
 */
type arg = string | number | boolean

const foo = (arg: arg): any => {
    console.log('arg', arg);
}
foo(1)
foo('1')
foo(true)

函數重載

/**
 * @description: 函數重載
 * @return {*} 1個函數可以執行多項任務的能力
 */

// add函數,它可以接收string類型的參數進行拼接,也可以接收number類型的參數進行相加
function add <T, U>(arg1: T, arg2: U) {
  // 在實現上我們要註意嚴格判斷兩個參數的類型是否相等,而不能簡單的寫一個 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相關配置集成到項目中去。
  • 當然,你也可以手動去配置TS

安裝TS

npm i typescript

項目根目錄新建tsconfig.json文件,用於TS的編譯基礎文件

{
  "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('我是傳入的數據')" type="primary">設置數據</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 進階學習加油",
        // setNamechange(){
        //     this.name = "我是新設置的"
        // }
        // });
        // // 第二種方式
        // const book2 = reactive<Book>({
        // name: "vue3--typeScript",
        // desc: "學習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: {
        // 入參為String 返回空值
        setTitle(arg: String): void {
            this.book.title = arg;
            this.book.year = 2022
            this.book.author = '尤雨溪'
        }
    }
})
</script>

狀態管理Pinia

由於本文章篇幅較大,會在之後的文章中單獨來講解。

到此這篇關於vue3基礎知識剖析的文章就介紹到這瞭,更多相關vue3基礎知識內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: