Vue3.x 新特性總結


Vue3.x 官網


Composition API 簡介

Vue2 時的方式在代碼很少的時候,邏輯結構還是蠻清晰的,但是隨着組件功能越來越多,代碼量越來越大,整體內容全部放在其中肯定會顯得臃腫。因為每個功能模塊的代碼會散落分布在各個位置,讓整個項目的內容難以閱讀和維護。如下圖:

在這里插入圖片描述

而到了 Vue3,它會根據邏輯功能來進行組織,把同一個功能的不同代碼都放在一起,或者把它們單獨拿出來放在一個函數中,所以 Composition API 又被稱為基於函數組合的API

在這里插入圖片描述


setup 函數

setup 函數是 Vue3 中新增的函數,它是我們在編寫組件時,使用 Composition API 的入口。
同時它也是 Vue3 中新增的一個生命周期函數,會在 beforeCreate 之前調用。因為此時組件的 datamethods 還沒有初始化,因此在 setup 中是不能使用 this。所以 Vue 為了避免我們錯誤的使用,它直接將 setup 函數中的 this 修改成了undefined。並且,我們只能同步使用setup函數,不能用async將其設為異步。

setup 函數接收兩個參數 propscontext, 語法為:setup(props,context){}

props

props 里面包含父組件傳遞給子組件的所有數據。在子組件中使用 props 進行接收。

props 是響應式的, 當傳入新的 props 時,會及時被更新。
由於是響應式的, 所以不可以使用 ES6 解構,解構會消除它的響應式。

父組件:

<template>
	<!-- 父組件向子組件傳遞數據 -->
	<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;  // 不能直接解構
		let { name, age } = toRefs(props);
		console.log(name.value, age.value); // 張三 20
	}
}
</script>

context

context 里面包含 attrs, slots, emit 等數據方法:

  • attrs:獲取組件上的屬性
  • slots:獲取 slot 插槽的節點
  • emit :emit 方法(子組件向父組件傳遞數據

父組件:

<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); // 接收子組件傳遞過來的數據
		}

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

子組件(Sub.vue):

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

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

	},
	methods: {
		// vue2.x emit方法(子組件向父組件傳遞數據)
		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插槽的節點
		console.log(slots.default());

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

		return { handleClick }
	}
}
</script>

生命周期

setup 函數是 Vue3 中新增的一個生命周期函數

  • setup 函數會在 beforeCreate 之前調用,因為此時組件的 datamethods 還沒有初始化,因此在 setup 中是不能使用 this
  • 所以 Vue 為了避免我們錯誤的使用,它直接將 setup 函數中的 this 修改成了undefined
  • setup函數,只能是同步的不能是異步
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');
		
		// 生命周期鈎子(沒有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鈎子  生產環境中會被忽略
		onRenderTracked(() => { console.log('onRenderTracked'); }) // 每次渲染后重新收集響應式依賴,在onMounted前觸發,頁面更新后也會觸發
		onRenderTriggered(() => { console.log('onRenderTriggered'); }) // 每次觸發頁面重新渲染時的自動執行,在onBeforeUpdate之前觸發
	},
	beforeCreate () {
		console.log('beforeCreate');
	},
	created () {
		console.log('created');
	}
}
</script>

返回值

setup 函數中返回一個對象,可以在模板中直接訪問該對象中的屬性和方法。

<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>

ref 與 reactive

創建一個響應式數據

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

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

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


		let nameObj2 = { name: '張三' };
		let name2 = ref(nameObj2); // ref為復雜數據類型添加響應式狀態(不建議)
		setTimeout(() => {
			// 設置值需要加.value
			name2.value.name = 'zhangsan';

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


		let nameObj3 = { name: '張三' };
		const name3 = reactive(nameObj3);// reactive只能為復雜數據類型添加響應式狀態
		setTimeout(() => {
			name3.name = 'zhangsan';

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


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

toRef 與 toRefs

也可以創建一個響應式數據

  • toRef:用來給抽離響應式對象中的某一個屬性,並把該屬性包裹成 ref 對象,使其和原對象產生鏈接。
    toRef 的本質是引用,修改響應式數據會影響原始數據。
  • toRefs:用來把響應式對象轉換成普通對象,把對象中的每一個屬性,包裹成 ref 對象。
    toRefs 就是 toRef 的升級版,只是toRefs 是把響應式對象進行轉換,其余的特性和 toRef 無二
<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不存在也能正常響應式賦值
		}, 1000)


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


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

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

readonly 只讀屬性

表示響應式對象不可修改

<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); // 對nameObj響應式對象設置成只讀,不可修改

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

		return { nameObj }
	}
}
</script>

computed 計算屬性

<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++
		}
		// 計算屬性
		const addCount = computed(() => {
			return num.value * 2;
		})
		return { add, addCount }
	}
}
</script>

watch 與 watchEffect 監聽屬性

  • watch 函數:
    用來偵聽特定的數據源,並在回調函數中執行副作用。
    默認情況是惰性的,也就是說僅在偵聽的源數據變更時才執行回調。
  • watchEffect 函數:
    1.立即執行、立即監聽(immediate)
    2.自動會感知代碼依賴(自動收集依賴),不需要傳遞監聽的內容(不需要像 watch 一樣手動傳入依賴)
    3.無法獲得變化前的值(oldVal)
<template>
	<div>
		<p>{{name}}</p>
		<p>{{nameObj.name}}</p>
	</div>
</template>

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

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



		// 監聽復雜類型
		const nameObj = reactive({ name: 'zhangsan' })
		setTimeout(() => {
			nameObj.name = 'lisi';
		}, 2000);
		
		// 復雜數據無法直接監聽、惰性
		watch(() => nameObj, (newVal, oldVal) => {
			console.log(newVal, oldVal); // 不會觸發
		})
		
		// 需要深度監聽、不惰性
		watch(() => nameObj, (newVal, oldVal) => {
			console.log(newVal, oldVal); // newVal、oldVal具有響應式
		}, { deep: true })
		
		// 也可以直接監聽對象的屬性
		watch(() => nameObj.name, (newVal, oldVal) => {
			console.log(newVal, oldVal);
		})



		// 同時監聽多個對象
		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秒后停止監聽
		setTimeout(()=>{  
			stop();   
		},5000)
		
		
		return { name, nameObj }
	}
}
</script>

獲取DOM節點

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

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

provide 與 inject

父組件向子組件傳遞數據與方法

父組件:

<template>
	<Sub />
</template>

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

		// provide(別名, 要傳遞的數據和方法)
		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 () {
		//調用 inject 方法,通過指定的別名,接收到父級共享的數據和方法
		const name = inject('myName');
		const handleClick = inject('handleClick');

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

Teleport 傳送門

Teleport 翻譯過來是傳送的意思,就像是哆啦 A 夢中的 「任意門」 ,任意門的作用就是可以將人瞬間傳送到另一個地方。

舉例:
我們希望 Dialog 渲染的 dom 和頂層組件是兄弟節點關系, 在 App.vue 文件中定義一個供掛載的元素:

<template>
	<router-view />
	<div id="model"></div> <!-- 掛載點 -->
</template>

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

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

DOM 渲染效果如下:

在這里插入圖片描述

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


Suspense

Suspense組件用於在等待某個異步組件解析時顯示后備內容

在 Vue2.x 中經常遇到這樣的場景:

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

在異步數據沒加載前,一般我們都會提供一個加載中( loading )的動畫,當數據返回時配合 v-if 來控制數據顯示。

現在 Vue3.x 新出的內置組件 Suspense , 它提供兩個template slot, 剛開始會渲染一個 fallback 狀態下的內容, 直到到達某個條件后才會渲染 default 狀態的正式內容, 通過使用 Suspense 組件進行展示異步渲染就更加的簡單。

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

Suspense 組件 只是一個帶插槽的組件,只是它的插槽指定了 defaultfallback 兩種狀態。


碎片化節點 Fragment

在 Vue2.x 中, template 中只允許有一個根節點:

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

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

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

插槽與作用域插槽

插槽

父組件:

<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>

作用域插槽

父組件:

<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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM