Vue3.x 新特性總結(jié)

image

Vue3.x 官網(wǎng)


一、Composition API 簡(jiǎn)介

Vue2 時(shí)的方式在代碼很少的時(shí)候,邏輯結(jié)構(gòu)還是蠻清晰的,但是隨著組件功能越來(lái)越多,代碼量越來(lái)越大,整體內(nèi)容全部放在其中肯定會(huì)顯得臃腫。因?yàn)槊總€(gè)功能模塊的代碼會(huì)散落分布在各個(gè)位置,讓整個(gè)項(xiàng)目的內(nèi)容難以閱讀和維護(hù)。如下圖:

image

而到了 Vue3,它會(huì)根據(jù)邏輯功能來(lái)進(jìn)行組織,把同一個(gè)功能的不同代碼都放在一起,或者把它們單獨(dú)拿出來(lái)放在一個(gè)函數(shù)中,所以 Composition API 又被稱為基于函數(shù)組合的API

image

1. setup 函數(shù)

setup 函數(shù)是 Vue3 中新增的函數(shù),它是我們?cè)诰帉懡M件時(shí),使用 Composition API 的入口。
同時(shí)它也是 Vue3 中新增的一個(gè)生命周期函數(shù),會(huì)在 beforeCreate 之前調(diào)用。因?yàn)?strong>此時(shí)組件的 datamethods 還沒(méi)有初始化,因此在 setup 中是不能使用 this 的。所以 Vue 為了避免我們錯(cuò)誤的使用,它直接將 setup 函數(shù)中的 this 修改成了undefined。并且,我們只能同步使用setup函數(shù),不能用async將其設(shè)為異步。

setup 函數(shù)接收兩個(gè)參數(shù) propscontext, 語(yǔ)法為:setup(props,context){}

props

props 里面包含父組件傳遞給子組件的所有數(shù)據(jù)。在子組件中使用 props 進(jìn)行接收。

props 是響應(yīng)式的, 當(dāng)傳入新的 props 時(shí),會(huì)及時(shí)被更新。
由于是響應(yīng)式的, 所以不可以使用 ES6 解構(gòu),解構(gòu)會(huì)消除它的響應(yīng)式。

父組件:

<template>
    <!-- 父組件向子組件傳遞數(shù)據(jù) -->
    <Sub :name="name" :age="age" />
</template>

<script>
import { ref } from 'vue'
import Sub from './Sub.vue'
export default {
    setup () {
        const name = ref('張三');
        const age = ref(20)

        return { name, age }
    },
    components: { Sub },
}
</script>

子組件(Sub.vue):

<template>
    <div>{{name}}{{age}}</div>
</template>

<script>
export default {
    props: {
        name: String,
        age: Number
    },
    mounted () {
        // vue2.x 的寫法
        console.log(this.name); // 張三
        console.log(this.age); // 20
    },
    setup (props) {
        // vue3.x 的寫法
        console.log(props.name); // 張三
        console.log(props.age); // 20
        
        // let { name ,age } = props;  // 不能直接解構(gòu)
        let { name, age } = toRefs(props);
        console.log(name.value, age.value); // 張三 20
    }
}
</script>

context

context 里面包含 attrs, slots, emit 等數(shù)據(jù)方法:

  • attrs:獲取組件上的屬性
  • slots:獲取 slot 插槽的節(jié)點(diǎn)
  • emit :emit 方法(子組件向父組件傳遞數(shù)據(jù)

父組件:

<template>
    <Sub subData="some other data" @subClick='subClick'>parent</Sub>
</template>

<script>
import Sub from './Sub.vue'
export default {
    setup () {

        function subClick (e) {
            console.log(e); // 接收子組件傳遞過(guò)來(lái)的數(shù)據(jù)
        }

        return { subClick }
    },
    components: { Sub },
}
</script>

子組件(Sub.vue):

<template>
    <!-- 父組件向子組件傳遞數(shù)據(jù) -->
    <div @click="handleClick">Child</div>
</template>

<script>
export default {
    mounted () {
        // vue2.x 獲取組件上的屬性
        console.log(this.$attrs.subData);  // 'some other data'
        
        // vue2.x 獲取slot插槽的節(jié)點(diǎn)
        console.log(this.$slots);

    },
    methods: {
        // vue2.x emit方法(子組件向父組件傳遞數(shù)據(jù))
        handleClick () {
            this.$emit('subClick', 'vue2.x - this is subData')
        },
    },
    setup (props, context) {
        let { attrs, slots, emit } = context;

        // vue3.x 獲取組件上的屬性
        console.log(attrs.subData);  // 'some other data'

        // vue3.x 獲取slot插槽的節(jié)點(diǎn)
        console.log(slots.default());

        // vue3.x emit方法(子組件向父組件傳遞數(shù)據(jù))
        function handleClick () {
            emit('subClick', 'vue3.x - this is subData');
        }

        return { handleClick }
    }
}
</script>

2. 生命周期

Vue3.x 生命周期

setup 函數(shù)是 Vue3 中新增的一個(gè)生命周期函數(shù)

  • setup 函數(shù)會(huì)在 beforeCreate 之前調(diào)用,因?yàn)?strong>此時(shí)組件的 datamethods 還沒(méi)有初始化,因此在 setup 中是不能使用 this 的。
  • 所以 Vue 為了避免我們錯(cuò)誤的使用,它直接將 setup 函數(shù)中的 this 修改成了undefined。
  • setup函數(shù),只能是同步的不能是異步
Vue2.x Vue3.x
beforeCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured
- - onRenderTracked
- - onRenderTriggered

初始化加載順序:

setup => beforeCreate => created => onBeforeMount => onMounted

<script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onRenderTracked, onRenderTriggered } from 'vue'
export default {
    setup () {
        console.log('setup');
        
        // 生命周期鉤子(沒(méi)有beforeCreate和created)
        onBeforeMount(() => { console.log('onBeforeMount'); })
        onMounted(() => { console.log('onMounted'); })
        onBeforeUpdate(() => { console.log('onBeforeUpdate'); })
        onUpdated(() => { console.log('onUpdated'); })
        onBeforeUnmount(() => { console.log('onBeforeUnmount'); })
        onUnmounted(() => { console.log('onUnmounted'); })

        // 新增的debug鉤子  生產(chǎn)環(huán)境中會(huì)被忽略
        onRenderTracked(() => { console.log('onRenderTracked'); }) // 每次渲染后重新收集響應(yīng)式依賴,在onMounted前觸發(fā),頁(yè)面更新后也會(huì)觸發(fā)
        onRenderTriggered(() => { console.log('onRenderTriggered'); }) // 每次觸發(fā)頁(yè)面重新渲染時(shí)的自動(dòng)執(zhí)行,在onBeforeUpdate之前觸發(fā)
    },
    beforeCreate () {
        console.log('beforeCreate');
    },
    created () {
        console.log('created');
    }
}
</script>

3. 返回值

setup 函數(shù)中返回一個(gè)對(duì)象,可以在模板中直接訪問(wèn)該對(duì)象中的屬性和方法。

<template>
    <div @click="handleClick">{{name1}} - {{name2}}</div>
</template>

<script>
import { ref, provide } from 'vue'
export default {
    setup () {
        const name1 = ref('張三');
        
        return {
            name1,
            name2: 'zhangsan',
            handleClick () {
                console.log(name1.value); // 張三
                console.log(this.name2); // zhangsan
            }
        }
    }
}
</script>

4. ref 與 reactive

創(chuàng)建一個(gè)響應(yīng)式數(shù)據(jù)

  • ref:任意類型(建議基本類型)數(shù)據(jù)的響應(yīng)式引用(設(shè)置、獲取值時(shí)需要加.value)。
    ref 的本質(zhì)是拷貝,修改數(shù)據(jù)是不會(huì)影響到原始數(shù)據(jù)。
  • reactive:只能是復(fù)雜類型數(shù)據(jù)的響應(yīng)式引用
<template>
    <ul>
        <li>ref 基本類型:{{name1}}</li>
        <li>ref 復(fù)雜類型:{{name2.name}}</li>
        <li>reactive 復(fù)雜類型:{{name3.name}}</li>
    </ul>
</template>

<script>
import { ref, reactive } from 'vue'
export default {
    setup () {
        let nameStr = '張三';
        let name1 = ref(nameStr); // ref為基本數(shù)據(jù)類型添加響應(yīng)式狀態(tài)
        setTimeout(() => {
            name1.value = 'zhangsan';

            console.log(name1.value); // 'zhangsan'
            console.log(nameStr); // '張三'  =>  不會(huì)影響到原始數(shù)據(jù)
        }, 1000)


        let nameObj2 = { name: '張三' };
        let name2 = ref(nameObj2); // ref為復(fù)雜數(shù)據(jù)類型添加響應(yīng)式狀態(tài)(不建議)
        setTimeout(() => {
            // 設(shè)置值需要加.value
            name2.value.name = 'zhangsan';

            console.log(name2.value.name); // 'zhangsan'  =>  獲取值需要加.value
            console.log(nameObj2); // {name: "zhangsan"}  =>  會(huì)影響到原始數(shù)據(jù)
        }, 2000)


        let nameObj3 = { name: '張三' };
        const name3 = reactive(nameObj3);// reactive只能為復(fù)雜數(shù)據(jù)類型添加響應(yīng)式狀態(tài)
        setTimeout(() => {
            name3.name = 'zhangsan';

            console.log(name3.name); // 'zhangsan'
            console.log(nameObj3); // {name: "zhangsan"}  =>  會(huì)影響到原始數(shù)據(jù)
        }, 3000)


        return { name1, name2, name3 }
    }
}
</script>

5. toRef 與 toRefs

也可以創(chuàng)建一個(gè)響應(yīng)式數(shù)據(jù)

  • toRef:用來(lái)給抽離響應(yīng)式對(duì)象中的某一個(gè)屬性,并把該屬性包裹成 ref 對(duì)象,使其和原對(duì)象產(chǎn)生鏈接。
    toRef 的本質(zhì)是引用,修改響應(yīng)式數(shù)據(jù)會(huì)影響原始數(shù)據(jù)。
  • toRefs:用來(lái)把響應(yīng)式對(duì)象轉(zhuǎn)換成普通對(duì)象,把對(duì)象中的每一個(gè)屬性,包裹成 ref 對(duì)象。
    toRefs 就是 toRef 的升級(jí)版,只是toRefs 是把響應(yīng)式對(duì)象進(jìn)行轉(zhuǎn)換,其余的特性和 toRef 無(wú)二
<template>
    <ul>
        <li>{{name1}}</li>
        <li>{{name2.name.value}}</li>
        <li>{{name}}</li>
    </ul>
</template>

<script>
import { toRef, toRefs, reactive } from 'vue'
export default {
    setup () {
        let nameObj1 = reactive({ name: '張三' });
        let name1 = toRef(nameObj1, 'name');
        let age1 = toRef(nameObj1, 'age '); // age不存在
        setTimeout(() => {
            name1.value = 'zhangsan';
            age1.value = 20; // 即便age不存在也能正常響應(yīng)式賦值
        }, 1000)


        let nameObj2 = reactive({ name: '張三' });
        let age2 = toRef(nameObj3, 'age '); // age不存在
        let name2 = toRefs(nameObj2);
        setTimeout(() => {
            name2.name.value = 'zhangsan';
            age2.value = 20; // age不存在,賦值無(wú)反應(yīng)
        }, 2000)


        let nameObj3 = reactive({ name: '張三' });
        setTimeout(() => {
            nameObj3.name = 'zhangsan';
        }, 3000)
        let { name } = toRefs(nameObj3); // 解構(gòu)后仍需保持響應(yīng)式

        return { name1, name2, name }
    }
}
</script>

6. readonly 只讀屬性

表示響應(yīng)式對(duì)象不可修改

<template>
    <ul>
        <li>{{nameObj.name}}</li>
    </ul>
</template>

<script>
import { reactive, readonly } from 'vue'
export default {
    setup () {
        let nameObj = reactive({ name: '張三' });
        let readonlyObj = readonly(nameObj); // 對(duì)nameObj響應(yīng)式對(duì)象設(shè)置成只讀,不可修改

        setTimeout(() => {
            readonlyObj.name = 'zhangsan'; // 無(wú)法設(shè)置屬性  =>  Set operation on key "name" failed: target is readonly. Proxy {name: "張三"}
        }, 1000)

        return { nameObj }
    }
}
</script>

7. computed 計(jì)算屬性

<template>
    <div>
        <button @click="add">+</button>
        <p>{{addCount}}</p>
    </div>
</template>

<script>
import { ref, computed } from 'vue'
export default {
    setup () {
        const num = ref(0);
        const add = () => {
            num.value++
        }
        // 計(jì)算屬性
        const addCount = computed(() => {
            return num.value * 2;
        })
        return { add, addCount }
    }
}
</script>

8. watch 與 watchEffect 監(jiān)聽(tīng)屬性

  • watch 函數(shù):
    用來(lái)偵聽(tīng)特定的數(shù)據(jù)源,并在回調(diào)函數(shù)中執(zhí)行副作用。
    默認(rèn)情況是惰性的,也就是說(shuō)僅在偵聽(tīng)的源數(shù)據(jù)變更時(shí)才執(zhí)行回調(diào)。
  • watchEffect 函數(shù):
    1.立即執(zhí)行、立即監(jiān)聽(tīng)(immediate)
    2.自動(dòng)會(huì)感知代碼依賴(自動(dòng)收集依賴),不需要傳遞監(jiān)聽(tīng)的內(nèi)容(不需要像 watch 一樣手動(dòng)傳入依賴)
    3.無(wú)法獲得變化前的值(oldVal)
<template>
    <div>
        <p>{{name}}</p>
        <p>{{nameObj.name}}</p>
    </div>
</template>

<script>
import { ref, reactive, watch, watchEffect } from 'vue'
export default {
    setup () {

        // 監(jiān)聽(tīng)基本類型
        const name = ref('張三');
        setTimeout(() => {
            name.value = '李四';
        }, 1000);
        
        watch(name, (newVal, oldVal) => {
            console.log(newVal, oldVal);
        }, { immediate: true }) // 立即執(zhí)行



        // 監(jiān)聽(tīng)復(fù)雜類型
        const nameObj = reactive({ name: 'zhangsan' })
        setTimeout(() => {
            nameObj.name = 'lisi';
        }, 2000);
        
        // 復(fù)雜數(shù)據(jù)無(wú)法直接監(jiān)聽(tīng)、惰性
        watch(() => nameObj, (newVal, oldVal) => {
            console.log(newVal, oldVal); // 不會(huì)觸發(fā)
        })
        
        // 需要深度監(jiān)聽(tīng)、不惰性
        watch(() => nameObj, (newVal, oldVal) => {
            console.log(newVal, oldVal); // newVal、oldVal具有響應(yīng)式
        }, { deep: true })
        
        // 也可以直接監(jiān)聽(tīng)對(duì)象的屬性
        watch(() => nameObj.name, (newVal, oldVal) => {
            console.log(newVal, oldVal);
        })



        // 同時(shí)監(jiān)聽(tīng)多個(gè)對(duì)象
        watch([() => nameObj.name, () => nameObj.lastName], ([newName, newLastName], [oldName, oldLastName]) => {
            console.log(newName, oldName, newLastName, oldLastName);
        })



        const stop = watchEffect(() => {
            console.log(name);
            console.log(nameObj.name);
        })

        // 5秒后停止監(jiān)聽(tīng)
        setTimeout(()=>{  
            stop();   
        },5000)
        
        
        return { name, nameObj }
    }
}
</script>

二、獲取DOM節(jié)點(diǎn)

<template>
    <input type="text" value="張三" ref="hello">
</template>

<script>
import { ref, onMounted } from 'vue'
export default {
    setup () {
        const hello = ref(null); // 獲取組件中ref="hello"的真實(shí)dom元素
        
        onMounted(() => {
            console.log(hello.value); // <input type="text">
            console.log(hello.value.value); // 張三
        })
        
        return { hello }
    }
}
</script>

三、provide 與 inject

父組件向子組件傳遞數(shù)據(jù)與方法

父組件:

<template>
    <Sub />
</template>

<script>
import { ref, provide } from 'vue'
import Sub from './Sub.vue'
export default {
    setup () {
        const name = ref('張三');

        // provide(別名, 要傳遞的數(shù)據(jù)和方法)
        provide('myName', name)
        provide('handleClick', () => {
            name.value = 'zhangsan';
        })

    },
    components: { Sub },
}
</script>

子組件(Sub.vue):

<template>
    <div @click="handleClick">{{name}}</div>
</template>

<script>
import { inject } from 'vue'
export default {
    setup () {
        //調(diào)用 inject 方法,通過(guò)指定的別名,接收到父級(jí)共享的數(shù)據(jù)和方法
        const name = inject('myName');
        const handleClick = inject('handleClick');

        return { name, handleClick }
    }
}
</script>

四、Teleport 傳送門

Teleport 翻譯過(guò)來(lái)是傳送的意思,就像是哆啦 A 夢(mèng)中的 「任意門」 ,任意門的作用就是可以將人瞬間傳送到另一個(gè)地方。

舉例:
我們希望 Dialog 渲染的 dom 和頂層組件是兄弟節(jié)點(diǎn)關(guān)系, 在 App.vue 文件中定義一個(gè)供掛載的元素:

<template>
    <router-view />
    <div id="model"></div> <!-- 掛載點(diǎn) -->
</template>

定義一個(gè) Dialog 組件 Dialog.vue , 留意 to 屬性, 與上面的 id選擇器 一致:

<template>
    <teleport to="#model"> 
        <!-- 掛載內(nèi)容 -->
        <div>title</div>
        <div>I'am a Dialog.</div>
    </teleport>
</template>

DOM 渲染效果如下:

image

我們使用 teleport 組件,通過(guò) to 屬性,指定該組件渲染的位置在 App.vue 中,但是 Dialog 又是完全由 Dialog.vue 組件控制。


五、Suspense

Suspense組件用于在等待某個(gè)異步組件解析時(shí)顯示后備內(nèi)容。

在 Vue2.x 中經(jīng)常遇到這樣的場(chǎng)景:

<template>
<div>
    <div v-if="!loading">
        <!-- 異步渲染 -->
        ...  
    </div>
    <div v-if="loading">
        加載中...
    </div>
</div>
</template>

在異步數(shù)據(jù)沒(méi)加載前,一般我們都會(huì)提供一個(gè)加載中( loading )的動(dòng)畫,當(dāng)數(shù)據(jù)返回時(shí)配合 v-if來(lái)控制數(shù)據(jù)顯示。

現(xiàn)在 Vue3.x 新出的內(nèi)置組件 Suspense , 它提供兩個(gè)template slot, 剛開(kāi)始會(huì)渲染一個(gè) fallback 狀態(tài)下的內(nèi)容, 直到到達(dá)某個(gè)條件后才會(huì)渲染 default 狀態(tài)的正式內(nèi)容, 通過(guò)使用 Suspense 組件進(jìn)行展示異步渲染就更加的簡(jiǎn)單。

<Suspense>
      <template #default>
            <!-- 異步渲染 -->
            ...  
      </template>
      <template #fallback>
          <div>
              Loading...
          </div>
      </template>
</Suspense>

Suspense 組件 只是一個(gè)帶插槽的組件,只是它的插槽指定了 defaultfallback 兩種狀態(tài)。


六、碎片化節(jié)點(diǎn) Fragment

在 Vue2.x 中, template 中只允許有一個(gè)根節(jié)點(diǎn):

<template>
    <div>
        <span></span>
        <span></span>
    </div>
</template>

但是在 Vue3.x 中,可以有多個(gè)根元素,也可以有把文本作為根元素

<template>
    <span></span>
    <span></span>
</template>

七、插槽與作用域插槽

Vue之slot插槽和作用域插槽

1. 插槽

父組件:

<template>
    <Child>
        <!-- Vue2.x寫法
        <div slot="parent">
            <div>父組件</div>
        </div>
         -->
        <template v-slot:parent>
            <div>父組件</div>
        </template>
    </Child>
</template>

子組件(Child.vue):

<template>
    <slot name='parent'>子組件</slot>
</template>

2. 作用域插槽

父組件:

<template>
    <Child>
        <!-- <template slot="content" slot-scope="scoped">  -->
        <template v-slot:content="scoped">
            <div>{{scoped.myName}}</div>
        </template>
    </Child>
</template>

子組件:

<template>
    <slot name="content" :myName="myName"></slot>
</template>

<script>

import { ref } from 'vue'
export default {
    setup () {

        let myName = ref("貓老板的豆")

        return { myName }
    },
}
</script>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容