一、初识Vue3
Vue3
是一款流行的JavaScript
框架Vue
的最新版本,它于2020年9月正式发布。Vue3
主要着重于性能的提升、更好的开发体验以及更好的代码组织方式。
Vue3
的主要特性和改进:
- 更快的性能:
Vue3
对虚拟DOM
进行了重构,通过优化渲染和更新算法,可以显著提高性能。 - 更小的包大小:
Vue3
对代码进行了精简和优化,使其比Vue2
更小。 Composition API
:Vue3
引入了Composition API
,这是一种新的API
风格,提供了更好的代码组织方式,使得开发人员可以更好地管理代码复杂度。- 改进的
TypeScript
支持:Vue3
通过TypeScript
提供了更好的类型检查支持,这有助于减少代码错误和提高开发效率。 Teleport
:Vue3
引入了Teleport
,这是一种新的组件传送方式,可以方便地将组件渲染到DOM
中的任何位置。- 其他改进:
Vue3
还提供了许多其他改进,包括更好的错误处理、更好的调试支持、更好的跨平台支持等。
总体来说,Vue3
是一个更强大、更易于使用和更快的框架,它为开发人员提供了更好的工具来构建现代Web
应用程序。
二、Vue3新语法糖setup
setup
是Vue3
引入的新选项,用于定义组件的状态和行为。在Vue2
中,我们使用data
、computed
、methods
等选项来定义组件的状态和行为,但在Vue 3中,setup
选项被引入,以提供更清晰的代码组织和更灵活的API
。
setup
函数接收两个参数:props
和context
。props
是组件的属性对象,可以在函数内部使用,context
是一个对象,包含了一些属性和方法,如attrs
、slots
、emit
等,用于访问组件的属性、插槽和自定义事件。
在setup
函数内部,我们可以使用ref
、reactive
、computed
等函数来定义组件的响应式数据和计算属性,还可以使用普通的JavaScript语法来定义方法和计算逻辑。例如:
import { ref, reactive, computed } from 'vue'
export default {
props: {
message: String
},
setup(props, context) {
const count = ref(0)
const state = reactive({
name: 'Tom',
age: 18
})
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return {
count,
state,
doubleCount,
increment
}
}
}
在模板中,我们可以直接访问setup
函数返回的响应式数据、计算属性和方法。例如:
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<p>Name: {{ state.name }}, Age: {{ state.age }}</p>
<button @click="increment">Increment</button>
</div>
</template>
setup
是Vue3
中定义组件状态和行为的主要方式,它提供了更灵活、更清晰的API
,可以帮助我们更好地组织代码。
三、响应式数据函数
3.1 ref函数
ref
函数用于创建一个包装器对象,使得基本类型的值也可以响应式地更新。它接受一个初始值作为参数,并返回一个包装器对象,可以通过.value
来访问包装的值,如下所示:
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++ // 更新值
console.log(count.value) // 1
在模板中使用ref
时,可以直接访问.value
属性,如下所示:
<template>
<div>
Count: {{ count.value }}
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
</script>
3.2 reactive函数
reactive
函数用于创建一个响应式的对象。它接受一个普通的JavaScript
对象作为参数,并返回一个响应式代理对象,可以通过访问代理对象的属性来触发更新。例如:
import { reactive } from 'vue'
const state = reactive({
count: 0,
message: 'Hello, Vue 3!'
})
console.log(state.count) // 0
state.count++ // 更新对象属性
console.log(state.count) // 1
在模板中使用响应式对象时,可以直接访问对象的属性,如下所示:
<template>
<div>
Count: {{ state.count }}
Message: {{ state.message }}
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
message: 'Hello, Vue 3!'
})
return {
state
}
}
}
</script>
3.3 ref和reactive函数的异同
ref
和reactive
是Vue3
中用于创建响应式数据的两个函数,它们有一些相同点和不同点。
相同点:
- 都可以创建响应式数据,使得数据状态的更新更加自然和高效。
- 在使用上都可以像普通对象一样访问和修改属性。
- 都可以在
setup
函数内部使用。
不同点:
ref
函数用于创建一个包装器对象,使得基本类型的值也可以响应式地更新,而reactive
函数用于创建一个响应式的对象。- 使用
ref
函数创建的响应式数据在访问时需要通过.value
来访问包装的值,而使用reactive
函数创建的响应式数据直接访问对象属性即可。 ref
函数只能包装一个值,而reactive
函数可以包装一个对象,可以包含多个属性。ref
函数返回的是一个包装器对象,而reactive
函数返回的是一个响应式代理对象。ref
函数还有一些其他的API,如.unref()
、.isRef()
等,用于对包装器对象进行操作和判断,而reactive
函数则没有。
ref
和reactive
函数是Vue 3中用于创建响应式数据的两种常用方式,它们使得数据状态的更新更加自然和高效。使用哪种方式取决于需要创建的数据类型,如果是基本类型,可以使用ref
,如果是对象类型,可以使用reactive
。
四、Vue3的响应式原理
Vue3
的响应式原理与Vue2
有所不同。Vue2
使用了Object.defineProperty
来实现响应式,而Vue3
则使用了ES6
的Proxy
对象。
在Vue3
中,当我们创建一个响应式的数据对象时,Vue3
会使用一个叫做reactive
的函数将该对象转换成响应式对象。reactive
函数会使用Proxy
对象对原始数据对象进行代理,拦截对该对象的所有访问和修改操作,并且在必要的时候触发响应式更新。
当我们对响应式对象进行属性的访问或修改时,Proxy
对象会拦截这些操作,并通知Vue3
进行响应式更新。这个过程中,Vue3
会使用一个叫做effect
的函数来收集依赖,然后在属性发生变化时触发依赖的更新。
effect
函数是Vue3
中实现副作用的核心函数,我们可以通过调用它来创建响应式的副作用函数。当我们创建一个副作用函数时,effect
函数会自动执行该函数,并且在副作用函数中访问到的所有响应式数据都会被自动收集为依赖。当依赖发生变化时,副作用函数会自动被重新执行。
总的来说,Vue3
的响应式原理与Vue2
有所不同,但是它仍然是基于数据劫持的原理来实现响应式的。Vue3
使用Proxy
对象和effect
函数来实现数据劫持和响应式更新,使得代码更加简洁、高效,并且可以更好地支持TypeScript
等现代JavaScript
特性。
五、语法更新
5.1 Vue3使用computed计算属性
在Vue3
中,可以使用computed
函数来定义计算属性。computed
函数接受一个函数作为参数,这个函数返回的值就是计算属性的值。
下面是一个示例:
import { computed } from 'vue'
export default {
setup() {
const count = ref(0)
const doubledCount = computed(() => count.value * 2)
return {
count,
doubledCount
}
}
}
在这个示例中,我们使用computed
函数定义了一个计算属性doubledCount
,它的值是count
的两倍。count
是一个响应式变量,使用ref
函数创建。
在computed
函数的参数中,我们定义了一个箭头函数,它返回了count.value * 2
。由于count
是一个响应式变量,所以当count
的值改变时,doubledCount
的值也会自动更新。
在setup
函数中,我们将count
和doubledCount
返回给模板使用。在模板中,我们可以通过{{ count }}
和{{ doubledCount }}
来获取它们的值。
5.2 Vue3使用watch监视属性的注意点
5.2.1 监视ref对象
在Vue3
中,ref
对象是一个包装器,它包含一个value
属性和一些辅助方法。当我们创建一个ref
对象时,我们实际上是在创建一个带有value
属性的JavaScript
对象。因此,在监视ref
对象时,我们需要监视的是ref
对象的value
属性而不是ref
对象本身。这就意味着,当我们调用watch
函数时,需要传入ref
对象的value
属性而不是ref
对象本身。例如:
import { ref, watch } from 'vue';
const count = ref(0);
watch(count.value, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`);
});
在上面的例子中,我们监视了count.value
的变化,而不是count
对象本身的变化。
5.2.2 监视reactive对象
在Vue3
中,reactive
对象是一个响应式对象,它可以包含多个属性。当我们创建一个reactive
对象时,我们实际上是在创建一个带有多个属性的JavaScript
对象。因此,在监视reactive
对象时,我们需要监视整个对象而不是它的某个属性。例如:
import { reactive, watch } from 'vue';
const state = reactive({
count: 0,
message: 'Hello World!'
});
watch(state, (newValue, oldValue) => {
console.log('state changed', newValue, oldValue);
});
在上面的例子中,我们监视了整个state对象的变化,而不是state.count
或state.message
的变化。
5.2.3 监视嵌套属性
当监视reactive
对象的嵌套属性时,需要在watch
函数的选项对象中指定deep
选项为true
,否则只会监听属性的赋值操作,而不会监听属性内部的变化。例如:
import { reactive, watch } from 'vue';
const state = reactive({
nested: {
count: 0
}
});
watch(
() => state.nested,
(newValue, oldValue) => {
console.log('nested state changed', newValue, oldValue);
},
{ deep: true }
);
5.2.4 避免循环引用:
如果reactive
对象中包含循环引用,那么watch
函数会陷入无限递归,导致程序崩溃。因此,在使用watch
函数时需要注意避免循环引用的问题。例如:
import { reactive, watch } from 'vue';
const state = reactive({
a: 1,
b: {
c: 2
}
});
// 此处会陷入无限递归,导致程序崩溃
state.b.d = state;
watch(
state,
(newValue, oldValue) => {
console.log('state changed', newValue, oldValue);
},
{ deep: true }
);
5.3 watchEffect函数
在Vue3
中,watchEffect
函数可以用来监视响应式变量的变化并执行副作用函数。当响应式变量发生变化时,副作用函数会被重新执行。
下面是一个示例:
import { reactive, watchEffect } from 'vue'
export default {
setup() {
const state = reactive({
count: 0
})
watchEffect(() => {
console.log('Count changed to:', state.count)
})
function increment() {
state.count++
}
return {
state,
increment
}
}
}
在这个示例中,我们使用reactive
函数创建了一个响应式对象state
,其中包含一个属性count
。然后我们使用watchEffect
函数监视了state.count
的变化,并在控制台中打印了它的值。
在setup
函数中,我们将state
和increment
函数返回给模板使用。在模板中,我们可以通过{{ state.count }}
来获取count
的值,并通过increment
函数来增加count
的值。
当我们在模板中点击增加按钮时,state.count
的值会发生变化,watchEffect
函数会被重新执行并在控制台中打印新的值。这个过程是自动触发的,不需要显式地定义一个watcher
函数。
六、Vue3的生命周期
6.1 生命周期钩子函数
beforeCreate
: 在实例被创建之初,组件的属性和方法还没有初始化。
在这个阶段,
Vue
实例已经被创建,但是data
和methods
还没有被初始化。此时,我们可以做一些初始化工作,例如配置一些全局变量等等。
const app = Vue.createApp({
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created");
},
});
app.mount("#app");
created
: 在实例创建之后,组件的属性和方法已经初始化完成,但是DOM
元素还没有被挂载。
在这个阶段,
Vue
实例已经被创建,并且data
和methods
已经被初始化。此时,我们可以做一些异步操作,例如通过ajax
获取数据。
const app = Vue.createApp({
created() {
console.log("created");
axios.get("/api/data").then((response) => {
this.data = response.data;
});
},
});
app.mount("#app");
beforeMount
: 在DOM
元素被挂载到页面之前,该钩子函数会被触发。
在这个阶段,
Vue
实例已经被创建,并且data
和methods
已经被初始化,但是DOM
元素还没有被挂载。此时,我们可以做一些DOM
操作。
const app = Vue.createApp({
data() {
return {
message: "Hello, world!",
};
},
beforeMount() {
console.log("beforeMount");
const title = document.createElement("h1");
title.textContent = this.message;
document.body.appendChild(title);
},
});
app.mount("#app");
mounted
: 在DOM
元素被挂载到页面之后,该钩子函数会被触发。
在这个阶段,
Vue
实例已经被创建,并且data
和methods
已经被初始化,DOM
元素也已经被挂载到页面上。此时,我们可以做一些DOM
操作,例如绑定事件监听器等等。
const app = Vue.createApp({
data() {
return {
count: 0,
};
},
mounted() {
console.log("mounted");
this.$refs.button.addEventListener("click", () => {
this.count++;
});
},
});
app.mount("#app");
beforeUpdate
: 在组件更新之前,该钩子函数会被触发。
在这个阶段,
Vue
实例的data
发生了改变,但是这些改变还没有被更新到DOM
上。此时,我们可以做一些DOM
操作。
const app = Vue.createApp({
data() {
return {
message: "Hello, world!",
};
},
beforeUpdate() {
console.log("beforeUpdate");
const title = document.querySelector("h1");
title.textContent = this.message.toUpperCase();
},
});
app.mount("#app");
setInterval(() => {
app.message = "Hello, Vue.js 3!";
}, 1000);
updated
: 在组件更新之后,该钩子函数会被触发。
在这个阶段,
Vue
实例的data
发生了改变,且这些改变已经被更新到DOM
上。此时,我们可以做一些DOM
操作。
const app = Vue.createApp({
data() {
return {
message: "Hello, world!",
};
},
updated() {
console.log("updated");
},
});
app.mount("#app");
setInterval(() => {
app.message = "Hello, Vue!";
}, 1000);
beforeUnmount
: 在实例被销毁之前,该钩子函数会被触发。
在这个阶段,
Vue
实例即将被销毁,但是DOM
元素还存在。此时,我们可以做一些清理工作,例如取消事件监听器、清除定时器等等。
const app = Vue.createApp({
data() {
return {
count: 0,
};
},
mounted() {
this.timer = setInterval(() => {
this.count++;
}, 1000);
},
beforeUnmount() {
clearInterval(this.timer);
},
});
app.mount("#app");
unmounted
: 在实例被销毁之后,该钩子函数会被触发。
在这个阶段,
Vue
实例已经被销毁,DOM
元素也已经被移除。此时,我们不能再访问Vue
实例的属性和方法。
const app = Vue.createApp({
data() {
return {
message: "Hello, world!",
};
},
beforeUnmount() {
console.log("beforeUnmount");
},
unmounted() {
console.log("unmounted");
},
});
app.mount("#app");
setTimeout(() => {
app.unmount();
}, 5000);
6.2 生命周期的变化
Vue3
相较于Vue2
的生命周期函数一些变化和改进:
beforeCreate
和created
被合并为beforeCreate
,beforeMount
和mounted
被合并为onMounted
,beforeUpdate
和updated
被合并为onUpdated
,beforeDestroy
和destroyed
被合并为onBeforeUnmount
和onUnmounted
。这种变化使得生命周期函数名称更加简洁,并且每个阶段只有一个函数,更容易理解和使用。- 新增了两个生命周期函数:
onBeforeMount
和onBeforeUpdate
,分别在组件渲染之前被调用。 - 新增了一个生命周期函数:
onRenderTracked
和onRenderTriggered
,用于调试组件渲染性能问题。这两个函数需要与Vue3
的新特性Reactivity
结合使用。
Vue3
的生命周期函数相比Vue2
更加简洁、清晰,并且新增了一些实用的函数。
七、Vue3新增
7.1 toRef与toRefs
toRef
和toRefs
是Vue3
中的两个新函数,用于创建响应式对象。
toRef
接收两个参数,第一个参数是响应式对象,第二个参数是该对象的属性名。它会返回一个新的ref
对象,该对象指向原始对象的属性,并且任何对该属性的修改都会被响应式地更新到原始对象上。
例如,假设有一个包含name
属性的对象person
:
import { ref, toRef } from 'vue';
const person = {
name: 'Alice'
};
const nameRef = toRef(person, 'name');
console.log(nameRef.value); // 'Alice'
nameRef.value = 'Bob';
console.log(person.name); // 'Bob'
在上面的例子中,nameRef
是一个指向person.name
的ref对象。通过修改nameRef.value
,person.name
的值也被修改了。
toRefs
则接收一个响应式对象,并将其转换为一个包含相同属性名的对象,每个属性都是ref
对象。这样做的好处是可以在模板中方便地使用对象的属性而不需要使用.value
。
例如:
import { reactive, toRefs } from 'vue';
const person = reactive({
name: 'Alice',
age: 30
});
const personRefs = toRefs(person);
console.log(personRefs.name.value); // 'Alice'
personRefs.age.value++;
console.log(person.age); // 31
在上面的例子中,personRefs
是一个包含name
和age
属性的对象,每个属性都是一个ref对象。可以像访问普通对象一样访问personRefs
的属性,而不需要使用.value
。
7.2 shallowReactive与shallowRef
shallowReactive
和shallowRef
是Vue3
中的两个新函数,用于创建浅层响应式对象。
shallowReactive
函数可以将一个普通对象转换成一个响应式对象。与reactive
不同的是,shallowReactive
只会将对象的第一层属性转换成响应式对象,而不会递归转换嵌套对象的属性。这个函数的作用是可以在需要响应式对象的时候避免不必要的性能开销。
例如,假设有一个包含嵌套对象的普通对象:
import { shallowReactive } from 'vue';
const person = {
name: 'Alice',
age: 30,
address: {
city: 'Beijing',
country: 'China'
}
};
const shallowPerson = shallowReactive(person);
console.log(shallowPerson.address); // {city: "Beijing", country: "China"}
console.log(shallowPerson.address.city); // "Beijing"
在上面的例子中,shallowPerson
是一个响应式对象,但是address
属性并没有被递归地转换成响应式对象。
shallowRef
函数可以将一个普通的值转换成一个ref
对象。与ref
不同的是,shallowRef
只会将值转换成一个ref
对象,而不会递归地将值的属性转换成响应式对象。
例如:
import { shallowRef } from 'vue';
const person = {
name: 'Alice',
age: 30
};
const shallowPersonRef = shallowRef(person);
console.log(shallowPersonRef.value); // {name: "Alice", age: 30}
console.log(shallowPersonRef.value.name); // "Alice"
在上面的例子中,shallowPersonRef
是一个ref
对象,但是它的值person
并没有被递归地转换成响应式对象。
7.3 readonly与shallowReadonly
readonly
和shallowReadonly
是Vue3
中的两个新函数,用于创建只读对象。
readonly
函数可以将一个普通对象转换成一个只读对象。与reactive
不同的是,readonly
会将对象的所有属性都转换成只读属性,这样就不能修改对象的属性值了。
例如,假设有一个包含可修改属性的对象:
import { readonly } from 'vue';
const person = {
name: 'Alice',
age: 30
};
const readonlyPerson = readonly(person);
readonlyPerson.name = 'Bob'; // Error: Cannot assign to read only property 'name' of object '#<Object>'
在上面的例子中,readonlyPerson
是一个只读对象,尝试修改它的属性值会抛出错误。
shallowReadonly
函数可以将一个普通对象转换成一个浅层只读对象。与shallowReactive
类似,shallowReadonly
只会将对象的第一层属性转换成只读属性,而不会递归转换嵌套对象的属性。
例如:
import { shallowReadonly } from 'vue';
const person = {
name: 'Alice',
age: 30,
address: {
city: 'Beijing',
country: 'China'
}
};
const shallowReadonlyPerson = shallowReadonly(person);
console.log(shallowReadonlyPerson.address); // {city: "Beijing", country: "China"}
shallowReadonlyPerson.address.city = 'Shanghai'; // OK
shallowReadonlyPerson.address = {city: 'Shanghai', country: 'China'}; // Error: Cannot assign to read only property 'address' of object '#<Object>'
在上面的例子中,shallowReadonlyPerson
是一个只读对象,但是address
属性的属性值是可以修改的,因为它并没有被转换成只读对象。但是尝试修改shallowReadonlyPerson
的address
属性本身的值会抛出错误。
7.4 toRaw与markRaw
toRaw
和markRaw
是Vue3
中的两个函数,用于在响应式对象和普通对象之间转换,并标记一个对象为“原始”对象,即不会被转换成响应式对象。
toRaw
函数可以将一个响应式对象转换成一个普通对象,返回的对象是这个响应式对象的原始对象,不再是响应式对象。
例如:
import { reactive, toRaw } from 'vue';
const person = reactive({
name: 'Alice',
age: 30
});
const rawPerson = toRaw(person);
console.log(rawPerson === person); // false
console.log(rawPerson.name); // 'Alice'
console.log(rawPerson.age); // 30
在上面的例子中,rawPerson
是一个普通对象,它的值与person
相同,但是它已经不是响应式对象了。
markRaw
函数可以标记一个对象为“原始”对象,这个对象不会被转换成响应式对象。
例如:
import { reactive, markRaw } from 'vue';
const person = reactive({
name: 'Alice',
age: 30,
address: markRaw({
city: 'Beijing',
country: 'China'
})
});
console.log(person.address.city); // 'Beijing'
person.address.city = 'Shanghai'; // OK
person.address = {city: 'Shanghai', country: 'China'}; // OK
person.address.city = 'Beijing'; // OK
在上面的例子中,address
属性被标记为原始对象,所以它不会被转换成响应式对象。在这个例子中,我们可以修改person.address.city
的值,也可以修改person.address
的值,但是这个对象本身不会成为响应式对象,也就不会触发视图的更新。
7.5 customRef
customRef
是Vue3
新增的一个函数,用于创建自定义的ref
。它的用法类似于ref
函数,但是可以自定义ref
的读写行为。
customRef
接受一个工厂函数作为参数,这个函数必须返回一个对象,这个对象包含get
和set
两个函数。get
函数用于获取ref的值,set
函数用于设置ref的值。这两个函数可以是同步的也可以是异步的,只要它们返回的值是正确的就可以。
例如,下面的例子是一个使用customRef
实现的计数器,每次设置值时会在控制台输出一条消息:
import { customRef } from 'vue';
function createCounter() {
let count = 0;
return customRef((track, trigger) => ({
get() {
track();
return count;
},
set(value) {
count = value;
console.log(`count is now ${count}`);
trigger();
}
}));
}
const counter = createCounter();
console.log(counter.value); // 0
counter.value = 1; // count is now 1
console.log(counter.value); // 1
在上面的例子中,createCounter
函数返回一个使用customRef
创建的ref对象,它的读写行为由传递给customRef
的函数决定。在这个例子中,我们定义了一个计数器,每次设置值时会在控制台输出一条消息,并且会调用trigger
函数来通知Vue更新视图。
需要注意的是,customRef
返回的是一个包含value
属性的对象,与ref
不同,它不直接返回值。所以在使用customRef
时需要使用counter.value
来获取和设置值。
customRef
还有一个可选的第二个参数,它是一个布尔值,用于指示是否需要进行深度代理。默认值为false
,表示不进行深度代理。如果将这个值设置为true
,则可以对复杂的对象进行深度代理。
例如:
import { customRef } from 'vue';
function createDeepRef(obj) {
return customRef((track, trigger) => ({
get() {
track();
return obj;
},
set(value) {
obj = value;
trigger();
}
}), true);
}
const obj = { name: 'John', age: 20 };
const deepRef = createDeepRef(obj);
console.log(deepRef.value); // { name: 'John', age: 20 }
deepRef.value.name = 'Mike';
console.log(deepRef.value); // { name: 'Mike', age: 20 }
在上面的例子中,使用customRef
创建了一个深度代理的ref
对象,可以对其内部的对象进行修改。注意,修改内部的对象不会触发trigger
函数,因为Vue
无法监测到对象内部的属性的变化。
7.6 provide与inject
provide
和inject
是Vue3
新增的两个API
,用于在父子组件之间传递数据,相比于props
和事件,它们可以更方便地在组件树中的任意层次之间进行数据传递,而不需要一级一级地手动传递。
provide
函数用于在父组件中提供数据,接受一个key
和value
作为参数,key
必须是一个Symbol
类型的值,用于标识这个提供的数据,value
可以是任何类型的值,可以是基本类型、对象、函数等等。
例如:
import { provide } from 'vue';
export default {
setup() {
provide(Symbol('message'), 'Hello, World!');
// or provide('message', 'Hello, World!');
// It is recommended to use a Symbol as the key to avoid naming conflicts
}
}
在上面的例子中,我们使用provide
函数在父组件中提供了一个值为Hello, World!
的数据,key是一个Symbol类型的值。
inject
函数用于在子组件中注入数据,接受一个key
作为参数,这个key
必须是在父组件中通过provide
函数提供的key
。如果父组件中没有提供这个key
,则inject
函数会返回undefined
。
例如:
import { inject } from 'vue';
export default {
setup() {
const message = inject(Symbol('message'));
// or const message = inject('message');
console.log(message); // 'Hello, World!'
}
}
在上面的例子中,我们使用inject
函数在子组件中注入了父组件提供的值为Hello, World!
的数据,key
是一个Symbol
类型的值。
需要注意的是,provide
和inject
并不是响应式的,它们只是提供了一个方便的方式在组件之间传递数据。如果需要在子组件中监听父组件提供的数据变化,可以使用响应式数据来实现。
7.7 Teleport
Teleport
是Vue3
中新增的组件,它可以让你在组件树中的任何位置渲染元素,而不需要受到父组件的限制。这个组件通常用于实现弹出层或者模态框等组件,可以将它的内容渲染到指定的DOM
节点上。
Teleport
的用法类似于普通的组件,只需要将需要渲染的内容放在 <Teleport>
标签中即可。其中,to
属性用于指定要渲染到的目标节点,可以是DOM
节点或者Vue
组件。例如:
<template>
<button @click="showModal = true">Show Modal</button>
<Teleport to="#modal">
<div v-if="showModal" class="modal">
<h2>Hello World</h2>
<button @click="showModal = false">Close</button>
</div>
</Teleport>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const showModal = ref(false);
return {
showModal,
};
},
};
</script>
在上面的例子中,我们使用 Teleport
组件将弹出层渲染到页面上的一个 DOM 节点上,而不是作为父组件的子元素。具体来说,我们给 <Teleport>
标签添加了一个 to
属性,值为 #modal
,这个值对应着一个 id 为 modal
的 DOM 节点。然后在 <Teleport>
中我们渲染了一个包含弹出层内容的 div
元素。当用户点击 "Show Modal"
按钮时,showModal
变量会被设置为 true
,这个弹出层就会被渲染到 #modal
节点上。
需要注意的是,Teleport
组件在渲染时并不会改变组件的父子关系,所以在渲染时并不会受到父组件的CSS
样式的影响,这样可以更灵活地控制样式。此外,由于 Teleport
是异步渲染的,所以它的内容可能不会立即出现,需要等待 Vue
完成异步操作后才能正确地渲染。
7.8 Suspense
Suspense
是Vue3
中的一个新特性,它可以让你在异步加载组件时,显示一些占位符或者loading
状态,直到组件加载完成后再显示真正的内容。类似于React
中的 Suspense
组件。
Suspense
可以用于异步加载组件或者异步加载数据,当组件或者数据还没有准备好时,可以显示一个loading
状态或者占位符,等待异步操作完成后再显示真正的内容。这个特性在提高用户体验方面非常有用,可以让用户感觉应用更加流畅。
在Vue3
中,Suspense
组件通常和async setup()
函数一起使用,可以将异步操作放在async setup()
函数中,等待异步操作完成后再显示真正的组件内容。下面是一个例子:
<template>
<Suspense>
<template #default>
<HelloWorld />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script>
import { defineAsyncComponent } from 'vue';
const HelloWorld = defineAsyncComponent(() =>
import('./components/HelloWorld.vue')
);
export default {
components: {
HelloWorld,
},
};
</script>
在上面的例子中,我们使用了 Suspense
组件包裹了一个异步加载的 HelloWorld
组件。当 HelloWorld
组件还没有加载完成时,会显示一个loading
状态,等到 HelloWorld
组件加载完成后再显示真正的内容。在 Suspense
中,我们可以使用 default
和 fallback
两个插槽来分别指定真正的组件内容和loading
状态或者占位符。
需要注意的是,Suspense
组件需要配合异步加载的组件或者数据一起使用才能发挥作用,如果没有异步加载的需求,使用 Suspense
可能会带来额外的开销。此外,在使用 Suspense
组件时,需要确保被包裹的异步组件能够正常返回一个组件,否则会导致显示异常。