Vue 3是 Vue.js 框架的最新主要版本,它带来了许多新特性、优化和改进。以下是 Vue 3 的一些主要特点和亮点:
Composition API:
ref
和 reactive
来创建响应式数据。setup
函数作为组件的入口点,这是使用 Composition API 的地方。性能改进:
更小的体积:
更好的 TypeScript 支持:
多个根元素:
Fragments:
Suspense 和异步组件:
Suspense
组件,用于处理异步组件的加载状态。自定义渲染器 API:
更多的内置指令:
v-model
的更新,允许在一个组件上使用多个 v-model
。v-is
指令用于动态组件。更强大的内部结构:
Vue 3 的内部结构进行了重大改进,使得功能如 Composition API、tree-shaking 和自定义渲染器成为可能。
更好的安全性:
Vue 3 引入了更多的安全特性,以防止潜在的安全威胁。
新的生命周期钩子:
与 Composition API 一起使用的新的生命周期钩子,如 onMounted
、onUpdated
等。
总的来说,Vue 3 是一个更快、更小、更易于维护的版本,它引入了许多新特性和改进,使得开发者能够更加高效地构建和优化应用。
npm install vue
npm install vite @vitejs/plugin-vue --save-dev
vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
});
导入必要的模块:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { defineConfig } from 'vite'
: 从 Vite 包中导入 defineConfig
函数。这个函数用于定义 Vite 的配置,并提供更好的类型提示(如果你在使用 TypeScript)。import vue from '@vitejs/plugin-vue'
: 导入 Vite 的 Vue 插件。这个插件允许 Vite 处理和编译 Vue 单文件组件(SFCs)。导出配置:
export default defineConfig({
plugins: [vue()],
});
defineConfig(...)
: 使用前面导入的 defineConfig
函数定义 Vite 的配置。plugins: [vue()]
: 在配置对象中,我们指定了一个 plugins
数组,并将 Vue 插件添加到其中。这告诉 Vite 我们要使用该插件来支持 Vue 单文件组件的处理。总结:
这段代码配置 Vite 以支持 Vue 3 项目,特别是处理 .vue
文件。在一个典型的 Vue 3 + Vite 项目中,你会在项目的根目录下找到这样的配置文件,通常命名为 vite.config.js
或 vite.config.ts
(如果使用 TypeScript)。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue3</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
src\main.js
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
导入必要的模块:
import { createApp } from "vue";
import App from "./App.vue";
import { createApp } from 'vue'
: 从 Vue 3 的库中导入 createApp
函数。这是 Vue 3 中创建应用实例的新方法。import App from './App.vue'
: 导入 App.vue
文件。这通常是你的主组件,它可能包含其他子组件和应用的主要布局。创建并挂载应用:
createApp(App).mount("#app");
createApp(App)
: 使用 createApp
函数并传入 App
组件来创建一个新的 Vue 应用实例。这与 Vue 2 中的 new Vue()
方法不同。.mount('#app')
: 这是挂载应用的方法。它告诉 Vue 将应用挂载到 DOM 中的哪个元素上。在这种情况下,它会寻找一个具有 id="app"
的元素并将 Vue 应用挂载到那里。这通常在你的 HTML 文件中是这样的:<div id="app"></div>
总结:
这段代码的主要目的是初始化并启动一个 Vue 3 应用。它首先导入必要的模块和主组件,然后创建一个新的 Vue 应用实例,并将其挂载到页面上的特定元素上。
src\App.vue
<template>
<h1>App</h1>
</template>
package.json
{
"scripts": {
"dev": "vite"
}
}
在 Vue 3 的 Composition API 中,ref
是用于创建一个响应式引用值的函数。它特别适用于跟踪基本类型值(如 number
, string
)的变化。
首先,让我们了解一下 ref
的基本概念:
ref
创建一个响应式引用值。ref
的值,你需要使用 .value
属性。.value
属性来访问 ref
的值。Vue 会自动为你解包它。src\App.vue
<template>
<div>
<button @click="increment">Click me</button>
<p>You have clicked the button {{ count }} times.</p>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
function increment() {
count.value++;
}
return {
count,
increment
};
}
}
</script>
在上面的 App.vue
文件中,我们使用 ref
创建了一个响应式的 count
值,初始值为 0
。我们还定义了一个 increment
方法,用于增加 count
的值。在模板中,我们创建了一个按钮和一个段落,显示点击按钮的次数。
在 Vue 3 的 Composition API 中,reactive
是用于创建响应式对象的函数。与 ref
不同,reactive
用于使整个对象响应式,而不仅仅是单个值。
首先,让我们了解一下 reactive
的基本概念:
reactive
创建一个响应式对象。reactive
对象的属性,无需使用 .value
。reactive
对象的属性。src\App.vue
<template>
<div>
<button @click="increment">Click me</button>
+ <p>You have clicked the button {{ state.count }} times.</p>
</div>
</template>
<script>
+import { reactive } from 'vue';
export default {
setup() {
+ const state = reactive({
+ count: 0
+ });
function increment() {
state.count++;
}
return {
+ state,
increment
};
}
}
</script>
在上面的 App.vue
文件中,我们使用 reactive
创建了一个响应式的 state
对象,其中包含一个 count
属性,初始值为 0
。我们还定义了一个 increment
方法,用于增加 state.count
的值。在模板中,我们创建了一个按钮和一个段落,显示点击按钮的次数。
在 Vue 3 的 Composition API 中,computed
是用于创建计算属性的函数。计算属性是基于响应式依赖关系进行缓存的,只有当其依赖的数据发生变化时,它们才会重新计算。
以下是关于 computed
的一些关键点:
基本使用:
使用 computed
创建一个计算属性,该属性基于其他响应式数据进行计算。
自动跟踪依赖关系:
computed
会自动跟踪其计算函数中使用的任何响应式数据,当这些数据发生变化时,计算属性会重新计算。
只读:
默认情况下,通过 computed
创建的计算属性是只读的,但你可以提供一个 setter 来使其可写。
src\components\Computed.vue
<template>
<div>
<input v-model="baseValue" type="number" />
<p>Base Value: {{ baseValue }}</p>
<p>Double Value: {{ doubleValue }}</p>
<button @click="setDoubleValue">Set Double Value to 100</button>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const baseValue = ref(0);
const doubleValue = computed({
get: () => baseValue.value * 2,
set: (newValue) => {
baseValue.value = newValue / 2;
}
});
const setDoubleValue = () => {
doubleValue.value = 100;
};
return {
baseValue,
doubleValue,
setDoubleValue
};
}
}
</script>
在上面的文件中,我们使用 ref
创建了一个响应式的 baseValue
,并使用 computed
创建了一个计算属性 doubleValue
,该属性是 baseValue
的两倍。在模板中,我们有一个输入框,允许用户修改 baseValue
,并显示 baseValue
和 doubleValue
。
我们还为 doubleValue
提供了一个 setter。当 doubleValue
被设置时,它会更新 baseValue
。我们还添加了一个按钮,当点击时,它会使用 setter 将 doubleValue
设置为 100
,这会导致 baseValue
被设置为 50
。
在 Vue 3 中,watch
是一个非常有用的 API,允许我们观察和响应 Vue 实例上的数据变化。它可以观察单个数据源或多个数据源,并在其更改时执行回调函数。
以下是关于 watch
的基本使用和示例代码:
1. 基本使用
在 setup
函数中,我们可以使用 watch
来观察响应式数据的变化。
import { ref, watch } from "vue";
export default {
setup() {
const count = ref(0);
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
return {
count,
};
},
};
2. 观察多个数据源
使用数组作为第一个参数,我们可以同时观察多个数据源。
import { ref, watch } from "vue";
export default {
setup() {
const count1 = ref(0);
const count2 = ref(0);
watch(
[count1, count2],
([newCount1, newCount2], [oldCount1, oldCount2]) => {
console.log(`Count1 changed from ${oldCount1} to ${newCount1}`);
console.log(`Count2 changed from ${oldCount2} to ${newCount2}`);
}
);
return {
count1,
count2,
};
},
};
src\components\Watch.vue
<template>
<div>
<button @click="incrementCount1">Increment Count1</button>
<p>Current count1: {{ count1 }}</p>
<hr/>
<button @click="incrementCount2">Increment Count2</button>
<p>Current Count2: {{ count2 }}</p>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
setup() {
const count1 = ref(0);
const count2 = ref(0);
watch(count1, (newValue, oldValue) => {
console.log(`Count1 changed from ${oldValue} to ${newValue}`);
});
watch(count2, (newValue, oldValue) => {
console.log(`Count2 changed from ${oldValue} to ${newValue}`);
});
const incrementCount1 = () => {
count1.value++;
};
const incrementCount2 = () => {
count2.value++;
};
return {
count1,
count2,
incrementCount1,
incrementCount2
};
}
}
</script>
在这个示例中,我们有一个按钮和一个显示计数的段落。每次点击按钮时,计数会增加,并且我们使用 watch
来监听这个变化,并在控制台中打印出来。
watchEffect
是 Vue 3 中的另一个有用的 API,它允许我们自动跟踪响应式依赖并在它们更改时运行一个函数。与 watch
不同,watchEffect
不需要明确指定要观察的依赖项,它会自动跟踪在其函数体中使用的所有响应式引用。
1. 基本使用
在 setup
函数中,我们可以使用 watchEffect
来自动跟踪并响应数据变化。
import { ref, watchEffect } from "vue";
export default {
setup() {
const count = ref(0);
watchEffect(() => {
console.log(`Count is now: ${count.value}`);
});
return {
count,
};
},
};
每当 count
的值发生变化时,上述 watchEffect
都会执行。
src\components\WatchEffect.vue
<template>
<div>
<button @click="incrementCount">Increment Count</button>
<p>Current count: {{ count }}</p>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const count = ref(0);
watchEffect(() => {
console.log(`Count is now: ${count.value}`);
});
const incrementCount = () => {
count.value++;
};
return {
count,
incrementCount
};
}
}
</script>
<style>
</style>
与之前的 watch
示例相似,我们有一个按钮和一个显示计数的段落。每次点击按钮时,计数会增加。但这次,我们使用 watchEffect
来自动跟踪 count
的变化,并在控制台中打印出来。
总之,watchEffect
是一个非常强大的工具,尤其是当你想要自动跟踪多个响应式依赖项而不必明确指定它们时。
在 Vue 3 中,toRef
是一个用于从响应式对象中创建一个响应式引用的函数。当你有一个响应式对象,并且你想要从中提取一个属性作为一个独立的响应式引用时,这非常有用。
1. 基本使用
假设你有一个响应式对象,并且你想要跟踪该对象的某个属性的变化,但你不想整个对象都是响应式的。这时,你可以使用 toRef
。
import { reactive, toRef } from "vue";
export default {
setup() {
const state = reactive({
count: 0,
message: "Hello",
});
const countRef = toRef(state, "count");
return {
countRef,
};
},
};
在上述代码中,countRef
是 state.count
的响应式引用。当 state.count
更改时,countRef
也会更新,反之亦然。
src\components\ToRef.vue
<template>
<div>
<button @click="incrementCount">Increment Count</button>
<p>Current count: {{ countRef }}</p>
</div>
</template>
<script>
import { reactive, toRef } from "vue";
export default {
setup() {
const state = reactive({
count: 0,
message: "Hello",
});
const countRef = toRef(state, "count");
const incrementCount = () => {
countRef.value++;
};
return {
countRef,
incrementCount,
};
},
};
</script>
在这个示例中,我们有一个按钮和一个显示计数的段落。每次点击按钮时,计数会增加。我们使用 toRef
从响应式对象 state
中提取 count
属性,并将其作为一个独立的响应式引用 countRef
。这样,我们可以直接操作 countRef
,而 state.count
也会相应地更新。
总之,toRef
是一个非常有用的工具,尤其是当你想要从响应式对象中提取某个属性,并将其作为一个独立的响应式引用时。
在 Vue 3 中,toRefs
是一个用于将响应式对象的每个属性都转换为响应式引用的函数。这在 setup
函数中与模板或其他组件共享响应式对象时特别有用,因为它允许我们在不失去响应性的情况下解构对象。
1. 基本使用
当你有一个响应式对象,并且你想要将其所有属性都转换为独立的响应式引用时,你可以使用 toRefs
。
import { reactive, toRefs } from "vue";
export default {
setup() {
const state = reactive({
count: 0,
message: "Hello",
});
const { count, message } = toRefs(state);
return {
count,
message,
};
},
};
在上述代码中,count
和 message
都是响应式引用,它们分别对应于 state.count
和 state.message
。当 state
的属性更改时,这些响应式引用也会更新,反之亦然。
src\components\ToRefs.vue
<template>
<div>
<button @click="incrementCount">Increment Count</button>
<p>Current count: {{ count }}</p>
<p>Message: {{ message }}</p>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
setup() {
const state = reactive({
count: 0,
message: 'Hello from Vue 3!'
});
const { count, message } = toRefs(state);
const incrementCount = () => {
count.value++;
};
return {
count,
message,
incrementCount
};
}
}
</script>
在这个示例中,我们有一个按钮、一个显示计数的段落和一个显示消息的段落。每次点击按钮时,计数会增加。我们使用 toRefs
从响应式对象 state
中提取所有属性,并将它们作为独立的响应式引用。这样,我们可以直接操作这些引用,而 state
的相应属性也会相应地更新。
总之,toRefs
是一个非常有用的工具,尤其是当你想要从响应式对象中提取所有属性,并将它们都作为独立的响应式引用时。
在 Vue 3 中,shallowReactive
是一个创建浅响应式对象的函数。当你使用 shallowReactive
,对象的顶层属性会变得响应式,但对象内部的嵌套属性不会。这与 reactive
不同,reactive
会使对象的所有嵌套属性都变得响应式。
1. 基本使用
当你有一个对象,并且你只想让其顶层属性变得响应式,而不关心其嵌套属性的响应性时,你可以使用 shallowReactive
。
import { shallowReactive } from "vue";
export default {
setup() {
const state = shallowReactive({
count: 0,
nested: {
message: "Hello",
},
});
// state.count 是响应式的
// state.nested 不是响应式的,但 state.nested.message 也不是响应式的
return {
state,
};
},
};
src\components\ShallowReactive.vue
<template>
<div>
<button @click="incrementCount">Increment Count</button>
<p>Current count: {{ state.count }}</p>
<button @click="changeMessage">Change Message</button>
<p>Message: {{ state.nested.message }}</p>
</div>
</template>
<script>
import { shallowReactive } from 'vue';
export default {
setup() {
const state = shallowReactive({
count: 0,
nested: {
message: 'Hello from Vue 3!'
}
});
const incrementCount = () => {
state.count++;
};
const changeMessage = () => {
// 这里的更改不会触发视图的更新,因为 state.nested.message 不是响应式的
state.nested.message = 'New Message!';
};
return {
state,
incrementCount,
changeMessage
};
}
}
</script>
在这个示例中,我们有两个按钮:一个用于增加计数,另一个用于更改消息。尽管我们可以增加计数并在视图中看到更新,但当我们尝试更改 state.nested.message
时,视图不会更新,因为它不是响应式的。
总之,shallowReactive
是一个非常有用的工具,尤其是当你只关心对象的顶层属性的响应性,而不关心其嵌套属性时。
shallowRef是 ref() 的浅层作用形式
和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
src\components\ShallowRef.vue
<template>
<div>
<button @click="incrementCount">Increment Count</button>
<p>Current count: {{ state.count }}</p>
</div>
</template>
<script>
import { shallowRef } from 'vue';
export default {
setup() {
const state = shallowRef({
count: 0
});
const incrementCount = () => {
//state.value.count++;
state.value = {count:state.value.count+1}
};
return {
state,
incrementCount
};
}
}
</script>
<style>
/* Add your styles here if needed */
</style>
在 Vue 3 中,readonly
是一个用于创建只读响应式对象或引用的函数。当你使用 readonly
,你将获得一个不可变的响应式版本的原始对象或引用,这意味着你不能更改其属性,但仍然可以观察它们的变化。
1. 基本使用
当你想要确保某个响应式对象或引用不被意外修改,同时仍然能够观察其变化时,你可以使用 readonly
。
import { reactive, readonly } from "vue";
export default {
setup() {
const originalState = reactive({
count: 0,
message: "Hello",
});
const readonlyState = readonly(originalState);
// readonlyState.count 是只读的,尝试修改它会在开发模式下抛出警告
return {
readonlyState,
};
},
};
<template>
<div>
<p>originalState count: {{ originalState.count }}</p>
<button @click="incrementOriginalCount">incrementOriginalCount</button>
<hr/>
<p>readonlyState count: {{ readonlyState.count }}</p>
<button @click="incrementReadonlyCount">incrementReadonlyCount</button>
</div>
</template>
<script>
import { reactive, readonly } from 'vue';
export default {
setup() {
const originalState = reactive({
count: 0
});
const incrementOriginalCount = () => {
originalState.count++;
};
const readonlyState = readonly(originalState);
const incrementReadonlyCount = () => {
readonlyState.count++;
};
return {
originalState,
readonlyState,
incrementOriginalCount,
incrementReadonlyCount
};
}
}
</script>
在这个示例中,我们有一个按钮用于增加原始状态的计数。尽管我们不能直接修改 readonlyState
,但当原始状态 originalState
发生变化时,我们仍然可以在视图中观察到 readonlyState
的变化。
总之,readonly
是一个非常有用的工具,尤其是当你想要确保某个响应式对象或引用不被修改,同时仍然能够观察其变化时。
在 Vue 3 中,shallowReadonly
是一个用于创建浅只读响应式对象的函数。与 readonly
不同,shallowReadonly
只会使其值的顶层变得只读和响应式,而不会深入到其嵌套属性。
1. 基本使用
当你有一个对象,并且你只想让其顶层属性变得只读和响应式,而不关心其嵌套属性的响应性或只读性时,你可以使用 shallowReadonly
。
import { shallowReadonly } from "vue";
export default {
setup() {
const state = shallowReadonly({
count: 0,
nested: {
message: "Hello",
},
});
// state.count 是只读的
// state.nested 不是只读的,但 state.nested.message 也不是只读的
return {
state,
};
},
};
src\components\ShallowReadonly.vue
<template>
<div>
<p>Current count: {{ readonlyState.count }}</p>
<button @click="incrementCount">incrementCount</button>
<p>Message: {{ readonlyState.nested.message }}</p>
<button @click="changeMessage">changeMessage</button>
</div>
</template>
<script>
import { reactive, shallowReadonly } from 'vue';
export default {
setup() {
const originalState = reactive({
count: 0,
nested: {
message: 'hello'
}
});
const readonlyState = shallowReadonly(originalState);
const incrementCount = () => {
readonlyState.count++;
};
const changeMessage = () => {
readonlyState.nested.message = "world"
};
return {
readonlyState,
incrementCount,
changeMessage
};
}
}
</script>
需要注意的是,尽管 state
的顶层属性是只读的,其嵌套属性(如 state.nested.message
)仍然可以被修改。
总之,shallowReadonly
是一个非常有用的工具,尤其是当你只关心对象的顶层属性的只读性和响应性,而不关心其嵌套属性时。
在 Vue 3 中,toRaw
是一个用于获取响应式对象的原始版本的函数。当你有一个响应式对象并需要访问其未经代理的原始数据时,这个函数非常有用。
1. 基本使用
当你想要从响应式对象中获取其原始版本,例如,当你需要对原始对象执行操作而不触发响应式系统时,你可以使用 toRaw
。
import { reactive, toRaw } from "vue";
export default {
setup() {
const state = reactive({
count: 0,
message: "Hello",
});
const rawState = toRaw(state);
// rawState 是 state 的原始版本,不是响应式的
return {
state,
rawState,
};
},
};
src\components\ToRaw.vue
<template>
<div>
<button @click="incrementCount">Increment Count</button>
<p>Current count: {{ state.count }}</p>
<button @click="changeRawMessage">Change Raw Message</button>
<p>Message: {{ state.message }}</p>
</div>
</template>
<script>
import { reactive, toRaw } from 'vue';
export default {
setup() {
const state = reactive({
count: 0,
message: 'Hello from Vue 3!'
});
const incrementCount = () => {
state.count++;
};
const changeRawMessage = () => {
const rawState = toRaw(state);
rawState.message = 'Changed via raw!';
// 注意:尽管我们更改了 rawState,但 state.message 也会更新,因为它们引用的是同一个对象
};
return {
state,
incrementCount,
changeRawMessage
};
}
}
</script>
在这个示例中,我们有两个按钮:一个用于增加计数,另一个用于更改消息。尽管我们通过 toRaw
获取的原始对象来更改 message
,但由于 state
和 rawState
引用的是同一个对象,所以 state.message
也会更新。
总之,toRaw
是一个非常有用的工具,尤其是当你需要访问响应式对象的原始数据或在其上执行非响应式操作时。
在 Vue 3 中,markRaw
是一个用于标记一个对象,使其永远不会变成响应式的函数。这对于确保某些对象不被 Vue 的响应式系统跟踪或代理非常有用。
1. 基本使用
当你有一个对象,并且你不希望它变成响应式的,即使你尝试将其传递给 reactive
或 ref
,你可以使用 markRaw
。
import { reactive, markRaw } from "vue";
export default {
setup() {
const nonReactiveState = {
count: 0,
message: "Hello",
};
markRaw(nonReactiveState);
const state = reactive(nonReactiveState);
// 尽管我们尝试将 nonReactiveState 传递给 reactive,但由于我们使用了 markRaw,state 仍然不是响应式的
return {
state,
};
},
};
src\components\MarkRaw.vue
<template>
<div>
<button @click="incrementCount">Increment Count</button>
<p>Current count: {{ state.count }}</p>
<button @click="changeMessage">Change Message</button>
<p>Message: {{ state.message }}</p>
</div>
</template>
<script>
import { reactive, markRaw } from 'vue';
export default {
setup() {
const nonReactiveState = {
count: 0,
message: 'Hello from Vue 3!'
};
markRaw(nonReactiveState);
const state = reactive(nonReactiveState);
const incrementCount = () => {
state.count++;
};
const changeMessage = () => {
state.message = 'Changed message!';
// 注意:尽管我们更改了 state,但视图不会更新,因为 state 不是响应式的
};
return {
state,
incrementCount,
changeMessage
};
}
}
</script>
在这个示例中,我们有两个按钮:一个用于增加计数,另一个用于更改消息。尽管我们尝试更改 state
的属性,但视图不会更新,因为由于 markRaw
的使用,state
不是响应式的。
总之,markRaw
是一个非常有用的工具,尤其是当你需要确保某个对象不被 Vue 的响应式系统跟踪或代理时。
在 Vue 3 中,customRef
是一个用于创建自定义响应式引用的函数。它允许你为引用的 get
和 set
操作提供自定义逻辑,这在某些高级用例中非常有用,例如当你需要在值改变之前或之后执行某些操作时。
1. 基本使用
customRef
接受一个函数,该函数提供两个参数:track
和 trigger
。这两个函数分别用于手动跟踪和触发响应式依赖。
import { customRef } from "vue";
export default {
setup() {
const count = customRef((track, trigger) => {
let value = 0;
return {
get() {
track();
return value;
},
set(newValue) {
value = newValue;
trigger();
},
};
});
return {
count,
};
},
};
src\components\CustomRef.vue
<template>
<div>
<button @click="incrementCount">Increment Count</button>
<p>Current count: {{ count }}</p>
</div>
</template>
<script>
import { customRef } from "vue";
export default {
setup() {
const count = customRef((track, trigger) => {
let value = 0;
return {
get() {
track();
console.log("Getting count:", value);
return value;
},
set(newValue) {
console.log("Setting count from", value, "to", newValue);
value = newValue;
trigger();
},
};
});
const incrementCount = () => {
count.value++;
};
return {
count,
incrementCount,
};
},
};
</script>
在这个示例中,我们有一个按钮用于增加计数。每次我们获取或设置 count
的值时,都会在控制台中打印消息,这是由于我们在 customRef
的 get
和 set
函数中添加的自定义逻辑。
总之,customRef
是一个非常有用的工具,尤其是当你需要为响应式引用的 get
和 set
操作提供自定义逻辑时。
src\components\NumericInput.vue
<template>
<div>
<input v-model="numericInput" placeholder="Only numbers allowed" />
</div>
</template>
<script>
import { customRef } from 'vue';
export default {
setup() {
const numericInput = customRef((track, trigger) => {
let value = '';
return {
get() {
track();
return value;
},
set(newValue) {
if (/^\d*$/.test(newValue)) {
value = newValue;
} else {
console.warn('Only numbers are allowed in the input!');
}
trigger();
}
};
});
return {
numericInput
};
}
}
</script>
在 Vue 3 中,isRef
、isReactive
、isReadonly
和 isProxy
是用于检查对象的响应式状态的辅助函数。
ref
创建的响应式引用。reactive
创建)。readonly
创建)。reactive
和 readonly
创建的对象)。src\components\Is.vue
<template>
<div>
<p>isRef(refData): {{ isRefCheck }}</p>
<p>isReactive(reactiveData): {{ isReactiveCheck }}</p>
<p>isReadonly(readonlyData): {{ isReadonlyCheck }}</p>
<p>isProxy(reactiveData): {{ isProxyReactiveCheck }}</p>
<p>isProxy(readonlyData): {{ isProxyReadonlyCheck }}</p>
</div>
</template>
<script>
import { ref, reactive, readonly, isRef, isReactive, isReadonly, isProxy } from 'vue';
export default {
setup() {
const refData = ref(0);
const reactiveData = reactive({ count: 0 });
const readonlyData = readonly({ message: 'Hello Vue 3!' });
const isRefCheck = isRef(refData);
const isReactiveCheck = isReactive(reactiveData);
const isReadonlyCheck = isReadonly(readonlyData);
const isProxyReactiveCheck = isProxy(reactiveData);
const isProxyReadonlyCheck = isProxy(readonlyData);
return {
isRefCheck,
isReactiveCheck,
isReadonlyCheck,
isProxyReactiveCheck,
isProxyReadonlyCheck
};
}
}
</script>
在这个示例中,我们创建了三种不同的响应式数据:ref
、reactive
和 readonly
。然后,我们使用辅助函数来检查这些数据的响应式状态,并在模板中显示结果。
这些辅助函数在开发过程中非常有用,尤其是当你需要根据数据的响应式状态来执行特定的逻辑或操作时。
在 Vue 3 的 Composition API 中,setup
函数是组件内部使用 Composition API 的入口点。setup
函数接受两个参数:props
和 context
。在这里,我们将专注于第一个参数:props
。
props
参数
props
参数提供了组件接收的所有 prop 的响应式引用。这意味着你可以直接在 setup
函数中访问这些 props,而不需要使用 this
关键字。但请注意,尽管你可以在 setup
函数中读取 props
的值,但你不应该尝试修改它们,因为 props 应该被视为只读的。
在下面的示例中,Child
组件接受一个 message
prop 并在模板中显示它。我们在 Child
组件的 setup
函数中直接访问 props.message
。
总之,props
参数在 setup
函数中提供了一个方便的方式来访问组件的 props,而不需要使用 this
关键字。
src\App.vue
<template>
<div>
<Child message="Message passed from App to Child!" />
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components: {
Child
},
setup() {
}
}
</script>
src\components\Child.vue
<template>
<div>
{{ message }}
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
default: 'Hello from Child component!'
}
},
setup(props) {
// 在 setup 函数中,你可以直接访问 props.message
console.log(props.message);
// 由于 props 是响应式的,我们不需要再返回它们,它们已经在模板中可用
return {};
}
}
</script>
在 Vue 3 的 Composition API 中,setup
函数的第二个参数是 context
,它是一个对象,包含了组件的几个属性:attrs
、slots
和 emit
。
attrs
属性
attrs是一个对象,包含了传递给组件但不是 prop 的属性。这通常用于高阶组件或库组件,这些组件可能不知道它们会接收哪些特定的属性。
在下面的示例中,Child
组件接受一个 message
prop 并在模板中显示它。我们还传递了一个额外的 class
属性,这不是 Child
组件的 prop,所以它会出现在 attrs
对象中。我们在 Child
组件的 setup
函数中直接访问 attrs
并在模板中使用它来设置 div 的类。
总之,attrs
在 setup
函数的 context
参数中提供了一个方便的方式来访问传递给组件但不是 prop 的属性。
在模板中,Vue 提供了一些特殊的前缀,如 $attrs
和 $slots
,以区分它们和用户定义的响应式数据。
src\App.vue
<template>
<div>
+ <Child message="Message passed from App to Child!" class="custom-class" />
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components: {
Child
},
setup() {
}
}
</script>
src\components\Child.vue
<template>
+ <div :class="$attrs.class">
{{ message }}
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
default: 'Hello from Child component!'
}
},
setup(props, { attrs }) {
// 在 setup 函数中,你可以直接访问 attrs
+ console.log(attrs);
// 由于 attrs 是响应式的,我们不需要再返回它们,它们已经在模板中可用
return {};
}
}
</script>
在 Vue 3 的 Composition API 中,setup
函数的第二个参数是 context
,它是一个对象,包含了组件的几个属性:attrs
、slots
和 emit
。
slots
属性
slots
是一个对象,包含了传递给组件的所有插槽。你可以使用 slots
来访问和渲染组件的插槽内容。
在下面示例中,Child
组件有两个插槽:一个默认插槽和一个名为 header
的命名插槽。我们在 Child
组件的 setup
函数中直接访问 slots
,并在模板中使用它们来渲染插槽内容。
src\App.vue
<template>
<div>
<Child>
<template #header>
This is the header slot content.
</template>
This is the default slot content.
</Child>
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components: {
Child
}
}
</script>
src\components\Child.vue
<template>
<div>
<div class="header">
<slot name="header"></slot>
</div>
<div class="body">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
setup(_, { slots }) {
// 在 setup 函数中,你可以直接访问 slots
console.log(slots);
// 由于 slots 是响应式的,我们不需要再返回它们,它们已经在模板中可用
return {};
}
}
</script>
在 Vue 3 的 Composition API 中,setup
函数的第二个参数是 context
,它是一个对象,包含了组件的几个属性:attrs
、slots
和 emit
。
emit
属性
emit
是一个函数,用于触发组件的自定义事件。这对于子组件与父组件之间的通信非常有用。
在这个示例中,当 Child
组件的按钮被点击时,它会触发一个名为 buttonClicked
的自定义事件。在 App
组件中,我们监听这个事件,并在事件触发时显示一个警告。
总之,emit
在 setup
函数的 context
参数中提供了一个方便的方式来触发组件的自定义事件,从而实现子组件与父组件之间的通信。
src\App.vue
<template>
<div>
<Child @buttonClicked="showAlert" />
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components: {
Child
},
setup() {
const showAlert = (message) => {
alert(message);
};
return {
showAlert
};
}
}
</script>
src\components\Child.vue
<template>
<button @click="handleClick">Click me!</button>
</template>
<script>
export default {
setup(_, { emit }) {
const handleClick = () => {
// 触发一个自定义事件
emit('buttonClicked', 'Button was clicked!');
};
return {
handleClick
};
}
}
</script>
在 Vue 3 中,provide
和 inject
是 Composition API 的一部分,用于实现依赖注入,从而允许祖先组件提供属性,这些属性可以被其后代组件注入,而不必通过 props 一层一层地传递。
这对于跨多个组件层级共享状态或提供主题等功能特别有用。
provide
函数
provide
函数允许你在当前组件中提供一个值,这个值可以被任何后代组件注入。
inject
函数
inject
函数用于在组件中注入一个由其祖先组件提供的值。
下面个示例中,App
组件提供了一个 themeColor
。虽然 Child
组件不使用这个颜色,但 GrandSon
组件注入并使用它来设置文本颜色。
总之,provide
和 inject
提供了一种跨多个组件层级共享状态的方法,而不必通过 props 一层一层地传递。
src\App.vue
<template>
<div>
<Child />
</div>
</template>
<script>
import { provide } from 'vue';
import Child from './components/Child.vue';
export default {
components: {
Child
},
setup() {
const themeColor = 'red';
// 提供 themeColor
provide('themeColor', themeColor);
}
}
</script>
src\components\Child.vue
<template>
<div>
<GrandSon />
</div>
</template>
<script>
import GrandSon from './GrandSon.vue';
export default {
components: {
GrandSon
},
setup() {
// Child 组件不需要注入 themeColor,所以这里没有其他逻辑
}
}
</script>
src\components\GrandSon.vue
<template>
<div :style="{ color: themeColor }">
This text should be in the provided color!
</div>
</template>
<script>
import { inject } from 'vue';
export default {
setup() {
// 从祖先组件中注入 themeColor
const themeColor = inject('themeColor');
return {
themeColor
};
}
}
</script>
在 Vue 3,生命周期钩子在 Composition API 中有了新的表示方式。这些新的钩子函数与 Vue 2 的选项式 API 中的生命周期钩子相对应,但它们的名称有所不同,并且是作为导入的函数使用的。
Vue 3 生命周期钩子与 Vue 2 的对比
Vue 2 钩子 | Vue 3 钩子 (Composition API) |
---|---|
beforeCreate | setup (部分) |
created | setup (部分) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
src\App.vue
<template>
<div>
<!-- 使用 keep-alive 缓存组件 -->
<keep-alive>
<component :is="currentComponent" :count="count"></component>
</keep-alive>
<!-- 切换按钮 -->
<button @click="toggleComponent">Toggle Component</button>
<button @click="increment">increment</button>
</div>
</template>
<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onActivated, onDeactivated } from 'vue';
import Child1 from './components/Child1.vue';
import Child2 from './components/Child2.vue';
export default {
components: {
Child1,
Child2
},
beforeCreate() {
console.log('App beforeCreate');
},
created() {
console.log('App created');
},
beforeMount() {
console.log('App beforeMount');
},
mounted() {
console.log('App mounted');
},
beforeUpdate() {
console.log('App beforeUpdate');
},
updated() {
console.log('App updated');
},
beforeDestroy() {
console.log('App beforeDestroy');
},
destroyed() {
console.log('App destroyed');
},
activated() {
console.log('App activated');
},
deactivated() {
console.log('App deactivated');
},
setup() {
// 初始显示 Child1
const currentComponent = ref('Child1');
const count = ref(0);
// 切换组件的方法
const toggleComponent = () => {
currentComponent.value = currentComponent.value === 'Child1' ? 'Child2' : 'Child1';
};
const increment = () => {
count.value++;
};
onBeforeMount(() => {
console.log('App onBeforeMount');
});
onMounted(() => {
console.log('App onMounted');
});
onBeforeUpdate(() => {
console.log('App onBeforeUpdate');
});
onUpdated(() => {
console.log('App onUpdated');
});
onBeforeUnmount(() => {
console.log('App onBeforeUnmount');
});
onUnmounted(() => {
console.log('App onUnmounted');
});
onActivated(() => {
console.log('App onActivated');
});
onDeactivated(() => {
console.log('App onDeactivated');
});
return {
currentComponent,
toggleComponent,
increment,
count
};
}
}
</script>
src\components\Child1.vue
<template>
<div>
Child1 {{count}}
</div>
</template>
<script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onActivated, onDeactivated } from 'vue';
export default {
props:{
count: Number
},
beforeCreate() {
console.log('Child1 beforeCreate');
},
created() {
console.log('Child1 created');
},
beforeMount() {
console.log('Child1 beforeMount');
},
mounted() {
console.log('Child1 mounted');
},
beforeUpdate() {
console.log('Child1 beforeUpdate');
},
updated() {
console.log('Child1 updated');
},
beforeDestroy() {
console.log('Child1 beforeDestroy');
},
destroyed() {
console.log('Child1 destroyed');
},
activated() {
console.log('Child1 activated');
},
deactivated() {
console.log('Child1 deactivated');
},
setup() {
onBeforeMount(() => {
console.log('Child1 onBeforeMount');
});
onMounted(() => {
console.log('Child1 onMounted');
});
onBeforeUpdate(() => {
console.log('Child1 onBeforeUpdate');
});
onUpdated(() => {
console.log('Child1 onUpdated');
});
onBeforeUnmount(() => {
console.log('Child1 onBeforeUnmount');
});
onUnmounted(() => {
console.log('Child1 onUnmounted');
});
onActivated(() => {
console.log('Child1 onActivated');
});
onDeactivated(() => {
console.log('Child1 onDeactivated');
});
}
}
</script>
src\components\Child1.vue
<template>
<div>
Child2 {{count}}
</div>
</template>
<script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onActivated, onDeactivated } from 'vue';
export default {
props:{
count: Number
},
beforeCreate() {
console.log('Child2 beforeCreate');
},
created() {
console.log('Child2 created');
},
beforeMount() {
console.log('Child2 beforeMount');
},
mounted() {
console.log('Child2 mounted');
},
beforeUpdate() {
console.log('Child2 beforeUpdate');
},
updated() {
console.log('Child2 updated');
},
beforeDestroy() {
console.log('Child2 beforeDestroy');
},
destroyed() {
console.log('Child2 destroyed');
},
activated() {
console.log('Child2 activated');
},
deactivated() {
console.log('Child2 deactivated');
},
setup() {
onBeforeMount(() => {
console.log('Child2 onBeforeMount');
});
onMounted(() => {
console.log('Child2 onMounted');
});
onBeforeUpdate(() => {
console.log('Child2 onBeforeUpdate');
});
onUpdated(() => {
console.log('Child2 onUpdated');
});
onBeforeUnmount(() => {
console.log('Child2 onBeforeUnmount');
});
onUnmounted(() => {
console.log('Child2 onUnmounted');
});
onActivated(() => {
console.log('Child2 onActivated');
});
onDeactivated(() => {
console.log('Child2 onDeactivated');
});
}
}
</script>
onErrorCaptured
是 Vue 3 中的一个生命周期钩子,它允许你捕获来自子组件的错误。当子组件抛出一个错误时,这个钩子会被触发。你可以在这个钩子中处理错误,例如记录错误或显示一个错误消息。
下面是一个简单的示例,其中 Child
组件会抛出一个错误,而 App
组件会使用 onErrorCaptured
钩子来捕获并处理这个错误。
在这个示例中,当你点击 Child
组件中的按钮时,它会抛出一个错误。这个错误会被 App
组件中的 onErrorCaptured
钩子捕获,并显示在页面上。同时,错误信息也会被打印到控制台。
src\App.vue
<template>
<div>
<Child />
<div v-if="error">{{ error.message }}</div>
</div>
</template>
<script>
import { ref } from 'vue';
import Child from './components/Child.vue';
export default {
components:{
Child
},
setup() {
const error = ref(null);
const handleError = (err, instance, info) => {
error.value = err;
console.error("Error captured:", err, "Info:", info);
};
return {
error,
onErrorCaptured: handleError
};
}
}
</script>
src\components\Child.vue
<template>
<button @click="throwError">Click to throw error</button>
</template>
<script>
export default {
setup() {
const throwError = () => {
throw new Error("This is an error from Child component");
};
return {
throwError
};
}
}
</script>
onRenderTracked
是 Vue 3 中的一个调试用的生命周期钩子。当响应式依赖被访问并被跟踪时,这个钩子会被触发。这对于调试渲染函数中的响应式依赖关系非常有用。
下面是一个简单的示例,其中 Child
组件有一个响应式的 count
属性。当这个属性在模板中被访问时,onRenderTracked
钩子会被触发,并记录被跟踪的依赖。
在这个示例中,当 Child
组件的模板被渲染时,count
属性会被访问,这会触发 onRenderTracked
钩子。你可以在控制台中看到被跟踪的依赖的详细信息。
请注意,onRenderTracked
主要用于调试,所以在生产环境中,你可能不需要使用它。
src\App.vue
<template>
<div>
<Child />
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components:{
Child
}
}
</script>
src\components\Child.vue
<template>
<div>
{{ count }}
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref, onRenderTracked } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
onRenderTracked(event => {
console.log('Tracked:', event);
});
return {
count,
increment
};
}
}
</script>
onRenderTriggered
是 Vue 3 中的一个调试用的生命周期钩子。当组件的渲染被一个响应式依赖的变化所触发时,这个钩子会被调用。这对于调试和了解哪个响应式依赖导致了组件的重新渲染非常有用。
下面是一个简单的示例,其中 Child
组件有一个响应式的 count
属性。当这个属性改变并导致组件重新渲染时,onRenderTriggered
钩子会被触发,并记录导致重新渲染的依赖。
在这个示例中,当你点击 Child
组件中的按钮并改变 count
属性的值时,这会导致组件重新渲染,从而触发 onRenderTriggered
钩子。你可以在控制台中看到导致重新渲染的依赖的详细信息。
与 onRenderTracked
类似,onRenderTriggered
主要用于调试,所以在生产环境中,你可能不需要使用它。
src\App.vue
<template>
<div>
<Child />
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components: {
Child
}
}
</script>
src\components\Child.vue
<template>
<div>
{{ count }}
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref, onRenderTriggered } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
onRenderTriggered(event => {
console.log('Render triggered by:', event);
});
return {
count,
increment
};
}
}
</script>
在 Vue 3 中,由于 Composition API 的引入,我们可以创建自定义的 hook 函数。这些函数可以封装和重用逻辑,从而使我们的代码更加模块化和可维护。这与 React 中的自定义 hook 非常相似。
什么是自定义 hook?
自定义 hook 是一个普通的 JavaScript 函数,但它可以利用 Vue 的响应式系统和其他 Composition API 函数。它的主要目的是提供一个封装特定逻辑的方法,这样你就可以在多个组件之间重用这些逻辑,而不必重复代码。
如何创建自定义 hook?
use
开头(这是一个约定,不是必须的)。ref
, reactive
, computed
, watch
, 等等。src\components\UseLocalStorage.vue
我们将使用 useLocalStorage
自定义 hook 来在 App.vue
中存储和读取一个用户的名字。当用户输入他们的名字并刷新页面时,他们的名字会被保存在本地存储中并在下次加载时显示出来。
<template>
<div>
<input v-model="name" placeholder="Enter your name" />
<p>Hello, {{ name }}!</p>
</div>
</template>
<script>
import { ref, watch } from 'vue';
function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key);
const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue);
watch(value, (newValue) => localStorage.setItem(key, JSON.stringify(newValue)));
return value;
}
export default {
setup() {
const name = useLocalStorage('username', 'Guest');
return {
name
};
}
}
</script>
src\components\UseFetch.vue
我们将使用 useFetch
自定义 hook 来在 App.vue
中从一个 URL 获取数据。这个 hook 会返回数据、加载状态和任何错误。
<template>
<div>
<!-- 显示加载状态 -->
<div v-if="loading">Loading...</div>
<!-- 显示数据 -->
<div v-else-if="data">
<pre>{{ data }}</pre>
</div>
<!-- 显示错误 -->
<div v-if="error">{{ error.message }}</div>
</div>
</template>
<script>
import { ref } from 'vue';
function useFetch(url) {
const data = ref(null);
const loading = ref(true);
const error = ref(null);
fetch(url)
.then(res => res.json())
.then(result => { data.value = result; })
.catch(e => { error.value = e; })
.finally(() => { loading.value = false; });
return { data, loading, error };
}
export default {
setup() {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1');
return {
data,
loading,
error
};
}
}
</script>
src\components\UseDebounce.vue
我们将使用 useDebounce
自定义 hook 来在 App.vue
中实现一个防抖动的输入框。这意味着,当用户在输入框中输入时,我们将等待他们停止输入一段时间(例如 300 毫秒)后,再获取他们的输入值。
<template>
<div>
<input v-model="inputValue" placeholder="Type something..." />
<p>Debounced value: {{ debouncedInputValue }}</p>
</div>
</template>
<script>
import { ref, watch } from 'vue';
function useDebounce(value, delay = 300) {
const debouncedValue = ref(value.value);
let timeout;
watch(value, (newValue) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
debouncedValue.value = newValue;
}, delay);
});
return debouncedValue;
}
export default {
setup() {
const inputValue = ref('');
const debouncedInputValue = useDebounce(inputValue);
return {
inputValue,
debouncedInputValue
};
}
}
</script>
src\components\UseThrottle.vue
我们将使用 useThrottle
自定义 hook 来在 App.vue
中实现一个节流的输入框。这意味着,当用户在输入框中输入时,我们将在指定的时间间隔内只获取他们的输入值一次,而忽略该时间段内的其他输入。
在这个示例中,我们有一个输入框,用户可以在其中输入文本。输入的实时值存储在 inputValue
中,而节流的值存储在 throttledInputValue
中。只有当用户的输入间隔超过约 300 毫秒时,throttledInputValue
才会更新为用户的最新输入值。
<template>
<div>
<input v-model="inputValue" placeholder="Type something..." />
<p>Throttled value: {{ throttledInputValue }}</p>
</div>
</template>
<script>
import { ref, watch } from 'vue';
function useThrottle(value, limit = 300) {
const throttledValue = ref(value.value);
let lastRan = Date.now();
watch(value, (newValue) => {
if (Date.now() - lastRan >= limit) {
throttledValue.value = newValue;
lastRan = Date.now();
}
});
return throttledValue;
}
export default {
setup() {
const inputValue = ref('');
const throttledInputValue = useThrottle(inputValue);
return {
inputValue,
throttledInputValue
};
}
}
</script>
在 Vue 3 中,Fragment 是一个新特性,允许组件模板有多个根节点。在 Vue 2 中,每个组件模板必须有一个单独的根元素。但在 Vue 3 中,这个限制被移除了。
什么是 Fragment?
Fragment,简单来说,就是没有根元素的模板。这意味着你可以在组件模板中直接返回多个元素,而不需要将它们包裹在一个父元素中。
如何使用 Fragment?
在 Vue 3 中,你只需正常编写模板即可。如果模板有多个根节点,Vue 会自动处理它们作为 Fragment。
实用价值
更自然的组件:在某些情况下,将多个元素组合在一个组件中是有意义的,但不希望为它们添加额外的包裹元素。Fragment 使这成为可能。
更好的样式和布局:避免不必要的包裹元素可以简化 CSS 样式和布局,特别是在使用 Flexbox 或 Grid 布局时。
总之,Fragment 是 Vue 3 中的一个小但非常有用的特性,它使组件模板更加灵活和直观。
src\App.vue
<template>
<h1>Welcome to Vue 3</h1>
<p>This is a fragment example.</p>
</template>
<script>
export default {
}
</script>
在 Vue 3 中,Teleport 是一个新特性,允许你将组件模板的一部分“传送”到 DOM 的其他位置,而不是放在它的实际位置。这对于模态框、通知、弹出窗口等 UI 元素特别有用,因为这些元素通常需要从 DOM 的深层次中“浮出”来,避免父元素的样式或布局影响。
什么是 Teleport?
Teleport 提供了一种将组件的子元素渲染到 DOM 树中的其他位置的方法,而不是它们在 Vue 组件树中的位置。
如何使用 Teleport?
使用 <teleport>
标签,并使用 to
属性指定目标元素的选择器。
实用价值
避免样式和布局问题:Teleport 可以确保 UI 元素(如模态框或弹出窗口)不受其父元素或祖先元素的样式和布局影响。
更好的可访问性:对于模态框等 UI 元素,将它们渲染到 body
的直接子元素中可以提高可访问性。
灵活性:Teleport 提供了一种灵活的方式,可以根据需要将 UI 元素渲染到 DOM 的任何位置。
假设我们要创建一个模态框。我们希望模态框始终渲染在 body
的直接子元素中,而不是嵌套在其他 DOM 元素中。这样,我们可以更容易地控制模态框的样式和位置。
src\App.vue
<template>
<div>
<button @click="showModal = !showModal">Toggle Modal</button>
<teleport to="#modal-container">
<div v-if="showModal" class="modal">
<h2>Modal Title</h2>
<p>This is a modal content.</p>
<button @click="showModal = false">Close</button>
</div>
</teleport>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const showModal = ref(false);
return {
showModal
};
}
}
</script>
<style>
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 500px;
padding: 20px;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
animation: fadeIn 0.3s;
}
.modal h2 {
margin-top: 0;
font-size: 24px;
color: #333;
}
.modal p {
font-size: 16px;
color: #666;
margin: 20px 0;
}
.modal button {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
color: #fff;
background-color: #007BFF;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.modal button:hover {
background-color: #0056b3;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translate(-50%, -60%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
</style>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue3</title>
</head>
<body>
<div id="app"></div>
+ <div id="modal-container"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
在 Vue 3 中,<suspense>
是一个新的内置组件,用于等待嵌套的异步依赖项或组件直到它们解析为止。它与 Vue 3 的异步组件和 Composition API 中的 async setup()
配合使用,为你提供了一种优雅的方式来处理异步操作和显示加载状态。
什么是 Suspense?
Suspense 允许你“等待”组件的异步逻辑,直到它完成,并在此期间显示一些备用内容(例如加载指示器)。
如何使用 Suspense?
你可以使用 <suspense>
的两个插槽:#default
和 #fallback
。#default
插槽包含你的主要内容,而 #fallback
插槽包含在等待异步组件或逻辑时要显示的内容。
实用价值
更好的用户体验:通过使用 Suspense,你可以为用户提供即时的反馈,告诉他们内容正在加载,从而提供更好的用户体验。
与 Composition API 配合:Suspense 不仅可以与异步组件配合使用,还可以与 Composition API 中的 async setup()
配合使用,使你的组件逻辑更加清晰和模块化。
假设我们有一个异步组件,该组件从 API 获取数据。我们希望在数据加载时显示一个加载指示器。
当 AsyncComponent
加载数据时,用户会看到 "Loading..."。一旦数据加载完成,AsyncComponent
的内容将替换 "Loading..."。
src\App.vue
<template>
<suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</suspense>
</template>
<script>
import AsyncComponent from './AsyncComponent.vue';
export default {
components: {
AsyncComponent
}
}
</script>
src\AsyncComponent.vue
<template>
<div>
{{ data }}
</div>
</template>
<script>
function fetchDataFromAPI() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Fetched data from API!");
}, 2000);
});
}
export default {
async setup() {
const data = await fetchDataFromAPI();
return {
data
};
}
}
</script>
createRenderer
API 来自 @vue/runtime-core
,它允许开发者创建自定义的渲染器。这是 Vue 3 的一个强大特性,它使得 Vue 可以在不同的平台上运行,例如 web、native mobile、或 even 3D engines。
createRenderer
提供了一种低级的方式来定义如何将虚拟 DOM 转化为实际的平台特定的视图。Vue 3 的默认 web 渲染器就是使用这个 API 创建的。
如何使用 createRenderer?
createRenderer
接受一个对象,该对象定义了一系列的方法,这些方法描述了如何处理虚拟节点的各个方面,例如创建、更新、删除等。
为什么使用 createRenderer?
跨平台渲染:你可以为不同的平台或渲染目标创建自定义的渲染器。例如,为原生移动应用、WebGL、Canvas 或 even terminal interfaces 创建渲染器。
优化特定用例:如果你有一个非常特定的用例,你可以创建一个高度优化的渲染器,只包含你需要的功能。
实验和学习:createRenderer
是一个很好的工具,用于深入了解 Vue 的内部工作原理,或进行与渲染相关的实验。
src\main.js
import { createRenderer, h } from "@vue/runtime-core";
const { render } = createRenderer({
createElement(element) {
return document.createElement(element);
},
setElementText(el, text) {
el.innerHTML = text;
},
insert(el, container) {
container.appendChild(el);
},
});
render(h("h1", "hello world"), document.getElementById("app"));
这段代码展示了如何使用 Vue 3 的 createRenderer
API 来创建一个简单的自定义渲染器,该渲染器将虚拟 DOM 节点渲染为真实的 DOM 节点。让我们逐步解析这段代码:
导入必要的函数:
import { createRenderer, h } from "@vue/runtime-core";
createRenderer
:用于创建自定义渲染器。h
:一个帮助函数,用于创建虚拟节点。创建自定义渲染器:
const { render } = createRenderer({
createElement(element) {
return document.createElement(element);
},
setElementText(el, text) {
el.innerHTML = text;
},
insert(el, container) {
container.appendChild(el);
},
});
createElement
:当渲染器需要创建一个新的 DOM 元素时,会调用此方法。setElementText
:当渲染器需要设置元素的文本内容时,会调用此方法。insert
:当渲染器需要将元素插入到容器中时,会调用此方法。使用渲染器:
render(h("h1", "hello world"), document.getElementById("app"));
h('h1', 'hello world')
:使用 h
函数创建一个虚拟节点,表示一个 <h1>
元素,其内容为 "hello world"。document.getElementById('app')
:选择一个真实的 DOM 容器,用于插入渲染的内容。render
:使用上面创建的自定义渲染器将虚拟节点渲染为真实的 DOM 节点,并插入到指定的容器中。结果:
这段代码将在页面上的 #app
元素中渲染一个 <h1>
标签,内容为 "hello world"。
单文件组件的 style 标签支持使用 v-bind CSS 函数将 CSS 的值链接到动态的组件状态
<template>
<div class="box">
box
<button @click="changeColor">changeColor</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const color = ref("red"); // 默认颜色
const bgColor = ref("green"); // 默认颜色
const changeColor = () => {
color.value = "green";
bgColor.value = "red";
};
return {
color,
bgColor,
changeColor,
};
},
};
</script>
<style>
.box {
padding: 20px;
color: v-bind(color);
background-color: v-bind(bgColor);
}
</style>
当 style 标签带有 scoped attribute 的时候,它的 CSS 只会影响当前组件的元素
src\App.vue
<template>
<div class="box">hello</div>
</template>
<script>
export default {
setup() {
return {};
}
}
</script>
<style scoped>
.box {
color: red;
}
</style>
处于 scoped 样式中的选择器如果想要做更“深度”的选择,也即:影响到子组件,可以使用 :deep() 这个伪类:
src\App.vue
<template>
<div class="box">
<Child></Child>
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components:{
Child
},
setup() {
return {};
}
}
</script>
<style scoped>
.box :deep(.child) {
color: red;
}
</style>
src\components\Child.vue
<template>
<div class="child">
Child
</div>
</template>
<script>
export default {
setup() {
return {
};
}
}
</script>
<style scoped>
</style>
默认情况下,作用域样式不会影响到 <slot/>
渲染出来的内容,因为它们被认为是父组件所持有并传递进来的。使用 :slotted
伪类以明确地将插槽内容作为选择器的目标:
src\App.vue
<template>
<div class="box">
<Child>
<p>Child</p>
</Child>
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components: {
Child
},
setup() {
return {};
}
}
</script>
<style scoped></style>
src\components\Child.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
setup() {
return {
};
}
}
</script>
<style scoped>
:slotted(p) {
color: red;
}
</style>
全局选择器如果想让其中一个样式规则应用到全局,比起另外创建一个 <style>
,可以使用 :global
伪类来实现:
src\App.vue
<template>
<div class="box">
<Child>
<p>Child</p>
</Child>
</div>
</template>
<script>
import Child from './components/Child.vue';
export default {
components: {
Child
},
setup() {
return {};
}
}
</script>
<style scoped>
:global(.global-class) {
color: red;
}
</style>
当 Vue 3 引入了新的应用 API,许多全局 API 和配置选项从 Vue 2 的全局范围转移到了 Vue 3 的应用实例。以下是 Vue 2 和 Vue 3 之间全局 API 转移的对比:
| Vue 2 API | Vue 3 API | 描述 |
| --------------------------------- | ----------------------------------- | ------------------------------------------------------------------ |
| `new Vue({ ... })` | `createApp({ ... })` | 创建一个新的 Vue 应用实例。 |
| `Vue.component(name, definition)` | `app.component(name, definition)` | 注册或获取全局组件。 |
| `Vue.directive(name, definition)` | `app.directive(name, definition)` | 注册或获取全局指令。 |
| `Vue.filter(name, function)` | Removed in Vue 3 | Vue 3 中已移除全局过滤器。 |
| `Vue.mixin(mixin)` | `app.mixin(mixin)` | 注册全局混入。 |
| `Vue.use(plugin)` | `app.use(plugin)` | 安装 Vue.js 插件。 |
| `Vue.prototype.customProperty` | `app.config.globalProperties` | 在 Vue 3 中,为所有组件实例定义全局属性。 |
| `Vue.config.key = value` | `app.config.key = value` | 在 Vue 3 中,应用级别的配置。 |
| `Vue.set(target, key, value)` | Use native JavaScript | Vue 3 推荐使用原生 JavaScript 代替,因为它现在有更好的响应性系统。 |
| `Vue.delete(target, key)` | Use native JavaScript | 同上。 |
| `Vue.observable(object)` | `ref(object)` or `reactive(object)` | 使一个对象变得响应式。 |
这个表格提供了 Vue 2 和 Vue 3 之间全局 API 转移的简单对比。这种转移的目的是为了使 Vue 更加模块化,并允许多个 Vue 应用共享同一个运行时,而不会相互干扰。
在 Vue 3 中,过渡类名的默认值发生了一些变化,以使其更加直观。以下是 Vue 2 和 Vue 3 之间过渡类名的对比:
Vue 2 Class Names | Vue 3 Class Names | 描述 |
---|---|---|
v-enter |
v-enter-from |
定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。 |
v-leave |
v-leave-from |
定义离开过渡的开始状态。在离开过渡被触发时生效,在动画/过渡开始之后的下一帧移除。 |
v-enter-active |
v-enter-active |
定义进入过渡的活动状态(例如,持续时间、延迟等)。在整个进入过渡期间生效。 |
v-leave-active |
v-leave-active |
定义离开过渡的活动状态。在整个离开过渡期间生效。 |
v-enter-to |
v-enter-to |
新引入于 Vue 2.1.8。定义进入过渡的结束状态。在元素被插入之后,下一帧开始生效。 |
v-leave-to |
v-leave-to |
新引入于 Vue 2.1.8。定义离开过渡的结束状态。在动画/过渡开始之后的下一帧开始生效。 |
从 Vue 2 到 Vue 3,一些事件修饰符和模式发生了变化。以下是关于 keyCode
和 .native
修饰符的变更:
1. keyCode
事件修饰符
在 Vue 2 中,你可以使用 keyCode
修饰符来监听特定键的键盘事件:
<input @keyup.13="submit" />
在上面的例子中,.13
是 Enter
键的键码。
变更:
在 Vue 3 中,keyCode
修饰符已被废弃,因为它不是一个推荐的实践(键码在不同的键盘布局和浏览器中可能会有所不同)。取而代之的是,你应该使用键盘事件的 key
属性:
<input @keyup.enter="submit" />
2. .native
修饰符
在 Vue 2 中,如果你想在一个组件上监听原生事件(而不是该组件触发的自定义事件),你可以使用 .native
修饰符:
<MyComponent @click.native="doSomething" />
变更:
在 Vue 3 中,.native
修饰符已被移除。如果你想在组件上监听原生事件,你应该在组件内部使用 v-on="$listeners"
或者使用新的 emits
选项来定义组件可以触发的事件。
但是,对于大多数常见的原生监听器(如 click
),你可以直接在 Vue 3 的组件上使用它们,而不需要 .native
修饰符:
<MyComponent @click="doSomething" />
src\components\TodoApp\TodoApp.vue
<template>
<div>
<todo-input @add="addTodo" />
<todo-list :todos="todos" @edit="editTodo" @remove="removeTodo" />
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import TodoInput from './TodoInput.vue';
import TodoList from './TodoList.vue';
const newTodo = ref('');
const todos = reactive([]);
function addTodo(text) {
todos.push({
id: Date.now(),
text: text,
completed: false,
});
}
function removeTodo(todo) {
const index = todos.indexOf(todo);
if (index !== -1) {
todos.splice(index, 1);
}
}
function editTodo(todo, newText) {
todo.text = newText;
}
</script>
src\components\TodoApp\TodoInput.vue
<template>
<input v-model="newTodo" @keyup.enter="add" placeholder="Add a new todo" />
</template>
<script setup>
import { ref, defineEmits } from 'vue';
const newTodo = ref('');
const emit = defineEmits();
const add = () => {
if (newTodo.value.trim()) {
emit('add', newTodo.value.trim());
newTodo.value = '';
}
};
</script>
src\components\TodoApp\TodoList.vue
<template>
<ul>
<todo-item v-for="todo in todos" :key="todo.id" :todo="todo" @edit="editTodo" @remove="removeTodo" />
</ul>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import TodoItem from './TodoItem.vue';
const props = defineProps({
todos: Array
});
const emit = defineEmits();
const editTodo = (todo, newText) => {
emit('edit', todo, newText);
};
const removeTodo = (todo) => {
emit('remove', todo);
};
</script>
src\components\TodoApp\TodoItem.vue
<template>
<li>
<input type="checkbox" v-model="todo.completed" />
<span @dblclick="edit" :class="{ completed: todo.completed }">{{ todo.text }}</span>
<button @click="remove">Delete</button>
</li>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
todo: Object
});
const emit = defineEmits();
const edit = () => {
const currentText = props.todo.text;
const newText = prompt('Edit todo', currentText);
if (newText !== null && newText.trim()) {
emit('edit', props.todo, newText);
}
};
const remove = () => {
emit('remove', props.todo);
};
</script>
<style scoped>
.completed {
text-decoration: line-through;
color: gray;
}
</style>
src\App.vue
<template>
<button @click="displayToast">Display Toast</button>
</template>
<script setup>
import { showToast } from './components/Toast';
function displayToast() {
showToast({
message: "This is a toast message",
duration: 3000
});
}
</script>
src\components\Toast\index.js
import Toast from "./Toast.vue";
import { render, h } from "vue";
export function showToast(options = {}) {
const { message = "", duration = 3000 } = options;
const container = document.createElement("div");
render(
h(Toast, {
message,
duration,
onDestroy: () => {
document.body.removeChild(container);
},
}),
container
);
document.body.appendChild(container);
}
src\components\Toast\Toast.vue
<template>
<transition name="fade" @after-leave="handleAfterLeave">
<div class="toast" v-if="isVisible">
<slot>{{ message }}</slot>
</div>
</transition>
</template>
<script setup>
import { ref, onMounted, onUnmounted, defineProps, defineEmits } from 'vue';
const { message, duration } = defineProps(['message', 'duration']);
const emit = defineEmits(['destroy']);
const isVisible = ref(false);
let timer;
onMounted(() => {
isVisible.value = true;
timer = setTimeout(() => {
isVisible.value = false;
}, duration);
});
onUnmounted(() => {
clearTimeout(timer);
})
function handleAfterLeave() {
emit('destroy');
}
</script>
<style scoped>
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
background: rgba(0, 0, 0, .7);
color: white;
border-radius: 5px;
text-align: center;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
SFC(Single File Components)是 Vue 中的一个核心概念,它允许你在一个 .vue
文件中定义模板、脚本和样式。在 Vue 3 中,SFC 仍然是一个核心特性,并且与 Vue 2 中的使用方式非常相似,但也有一些新的特性和改进。
以下是 Vue 3 中 SFC 的主要部分和特性:
模板 (Template)
<template>
标签定义。v-if
、v-for
、v-bind
等。脚本 (Script)
<script>
标签定义。export default
导出一个对象,该对象定义了组件的数据、方法、生命周期钩子等。样式 (Style)
<style>
标签定义。scoped
属性)。自定义块
<docs>
或 <i18n>
,用于文档或国际化等目的。新的 <script setup>
<script setup>
语法糖,它允许你在一个更简洁的环境中使用 Composition API。ref
、reactive
和其他 Composition API 的功能,而不需要一个 setup
函数。Vite 和 SFC
总的来说,SFC 提供了一个清晰、模块化的方式来组织和定义 Vue 组件。在 Vue 3 中,尽管 SFC 的基本概念保持不变,但新的特性和改进使得开发体验更加流畅和高效。
在 HTML 中,<script type="module">
是一个相对较新的特性,它允许你在浏览器中直接使用 ES6+ 的模块功能。这意味着你可以使用 import
和 export
语句,而不需要构建工具或模块加载器。
以下是关于 <script type="module">
的一些关键点:
默认延迟加载:
type="module"
的脚本默认具有 defer
的行为,这意味着它们会在文档解析完成后、DOMContentLoaded
事件之前执行,而不会阻塞 HTML 的解析。defer
属性,它已经是模块脚本的默认行为。静态导入:
import
语句来导入其他模块。动态导入:
import()
函数来动态地导入模块。这返回一个 promise,当模块加载和解析完成时,该 promise 会被解析。CORS 和 MIME 类型:
application/javascript
,否则浏览器不会执行模块。没有顶级变量污染:
let
、const
、class
和 function
)是局部的,不会污染全局命名空间。浏览器支持:
<script nomodule>
。不支持模块的浏览器会执行 nomodule
脚本,而支持模块的浏览器则会忽略它。使用示例:
<!-- 导入外部模块 -->
<script type="module" src="./my-module.js"></script>
<!-- 内联模块脚本 -->
<script type="module">
import { myFunction } from "./my-module.js";
myFunction();
</script>
总的来说,<script type="module">
提供了一种在浏览器中直接使用 ES6+ 模块的方式,无需依赖构建工具或模块加载器。这为前端开发带来了更大的灵活性和更现代的开发体验。
createApp
是 Vue 3 中的一个核心函数,用于创建一个新的 Vue 应用实例。与 Vue 2 的 new Vue()
初始化方式相比,createApp
提供了一个更清晰、更模块化的方法来创建和配置 Vue 应用。
以下是关于 createApp
的详细解释:
基本使用:
你可以使用 createApp
函数创建一个新的 Vue 应用实例,并传入一个根组件:
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
挂载:
创建应用实例后,你需要将其挂载到某个 DOM 元素上。这通常是一个具有特定 ID 的 <div>
元素:
app.mount("#app");
插件和全局配置:
使用 createApp
创建的应用实例提供了一系列方法,允许你在应用级别进行配置和扩展:
use
: 允许你安装 Vue 插件。mixin
: 允许你添加全局混入。component
和 directive
: 允许你注册全局组件和指令。示例:
import MyPlugin from "./plugins/MyPlugin";
app.use(MyPlugin);
app.component("MyGlobalComponent", MyGlobalComponent);
app.directive("my-global-directive", myGlobalDirective);
提供/注入:
createApp
提供了一个新的方法来设置全局的提供者(providers),这在 Vue 2 中通常是通过原型属性或混入来完成的:
app.provide("someKey", someValue);
与 Vue 2 的差异:
new Vue()
来创建和挂载应用。在 Vue 3 中,推荐使用 createApp
,因为它提供了更清晰的应用边界和更好的模块化。createApp
,每个应用实例都有其独立的配置,这意味着全局 API 和配置不再是真正的“全局”。这有助于避免不同应用之间或第三方插件与应用之间的潜在冲突。总的来说,createApp
是 Vue 3 中创建和配置 Vue 应用的主要方法。它提供了一种更模块化、更清晰的方式来初始化应用,使得应用的配置和扩展更加直观和灵活。
defineConfig
是 Vite 提供的一个辅助函数,用于定义 Vite 的配置。虽然它在技术上不是必需的(你可以直接导出一个普通的配置对象),但它提供了几个主要的好处,特别是在使用 TypeScript 时。
以下是关于 defineConfig
的一些关键点:
类型提示:
defineConfig
函数可以提供更好的类型推断和自动完成。这使得编写配置更加容易,因为你可以更清楚地知道哪些选项是可用的,以及它们的预期值。编辑器支持:
defineConfig
可以增强这种支持。清晰的意图:
defineConfig
明确表示这是一个 Vite 配置文件,这可以为阅读代码的人提供更多的上下文。使用示例:
import { defineConfig } from "vite";
export default defineConfig({
// ... your Vite configuration here ...
});
不使用 defineConfig
的示例:
如果你选择不使用 defineConfig
,你的配置可能会像这样:
export default {
// ... your Vite configuration here ...
};
总的来说,defineConfig
是一个为了提高开发者体验而存在的辅助函数。它不会改变配置的行为,但可以提供更好的类型支持和代码清晰度。如果你正在使用 TypeScript 或希望从智能提示中受益,那么使用 defineConfig
是一个好的选择。
@vitejs/plugin-vue
是一个 Vite 插件,专门用于支持 Vue 单文件组件(SFCs)的处理。当你使用 Vite 构建 Vue 项目时,这个插件是必不可少的,因为它允许 Vite 正确地解析和编译 .vue
文件。
以下是关于 @vitejs/plugin-vue
的一些关键点:
主要功能:
.vue
文件的三个部分:<template>
, <script>
, 和 <style>
。安装: 你可以使用 npm 或 yarn 安装这个插件:
npm install --save-dev @vitejs/plugin-vue
或
yarn add --dev @vitejs/plugin-vue
在 Vite 配置中使用: 一旦安装了插件,你需要在 Vite 的配置文件中导入并使用它:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
});
与 Vue 2 的兼容性:
如果你正在使用 Vue 2,你需要使用另一个插件:vite-plugin-vue2
。但请注意,随着 Vue 3 的普及,大多数新项目和库都在向 Vue 3 迁移。
扩展配置:
@vitejs/plugin-vue
本身还有一些配置选项,如自定义块的处理、模板编译选项等。你可以查阅插件的官方文档以获取更多详细信息。
总的来说,@vitejs/plugin-vue
是 Vite 在处理 Vue 项目时的关键插件。它确保 Vite 能够正确地解析、编译和优化 Vue 单文件组件,从而为开发者提供快速、高效的开发体验。
Vue Devtools 是一个为 Vue.js 开发的浏览器扩展,它允许开发者更轻松地检查和调试 Vue 应用。Vue Devtools 提供了一系列工具和功能,使得开发者可以深入了解其应用的状态、组件结构、性能等。
以下是 Vue Devtools 的一些主要特点和功能:
组件树检查:
状态管理:
实时编辑:
性能分析:
事件监听:
路由支持:
Pinpointed Logging:
暗黑模式:
支持 Vue 2 和 Vue 3:
安装:
Vue Devtools 可以作为 Chrome、Firefox 和其他浏览器的扩展进行安装。此外,还有一个独立的 Electron 版本,可以用于非 web 场景,如 NativeScript 或其他非浏览器环境。
开发模式:
请注意,Vue Devtools 仅在开发模式下工作。为了安全考虑,它在生产模式下是禁用的。
总的来说,Vue Devtools 是每个 Vue 开发者工具箱中的必备工具。它提供了深入了解和调试 Vue 应用的强大功能,从而大大提高了开发效率和应用质量。
Vue 3 引入了 Composition API,这是一个新的、可选的方式来组织和复用组件逻辑。与 Vue 2 的 Options API 相比,Composition API 提供了更加灵活的代码组织方式,特别是对于更复杂的组件。
以下是关于 Composition API 的一些关键点和详细解释:
为什么需要 Composition API:
data
, methods
, computed
)。这使得阅读和维护代码变得困难。基本使用:
setup
函数,这是组件内部的入口点。setup
函数中,你可以定义响应式数据、计算属性、方法和其他逻辑。响应式数据:
使用 ref
和 reactive
创建响应式数据。
import { ref, reactive } from 'vue';
setup() {
const count = ref(0);
const state = reactive({ name: 'Vue', version: 3 });
return { count, state };
}
计算属性和侦听器:
使用 computed
和 watch
。
import { computed, watch } from 'vue';
setup() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
return { count, doubled };
}
生命周期钩子:
Composition API 提供了与 Options API 中相对应的生命周期钩子,但它们是作为函数导入和使用的。
import { onMounted } from 'vue';
setup() {
onMounted(() => {
console.log('Component is mounted');
});
}
逻辑复用和抽象:
与 Options API 的共存:
模板中的使用:
setup
函数返回任何你想在模板中使用的值。总的来说,Composition API 是 Vue 3 的一个重要特性,它为开发者提供了一个更加灵活和模块化的方式来组织和复用组件逻辑。虽然它是可选的,但对于复杂的应用和组件,使用 Composition API 可以带来更好的开发体验。
在 Vue 3 中,setup
是 Composition API 的入口点,它是一个新的组件选项。setup
函数在组件内部的响应式数据、函数和生命周期钩子被初始化之前执行,这使得它成为使用 Composition API 的理想位置。
以下是关于 setup
的一些关键点和详细解释:
参数:
setup
函数可以接受两个参数:props
和 context
。props
: 是组件的当前 props。context
: 是一个对象,包含以下三个属性:attrs
: 一个包含未被 props
捕获的 attribute 的对象。slots
: 一个包含组件的 slots 的对象。emit
: 一个用于触发事件的函数。返回值:
setup
函数返回对象,这个对象的属性将会被合并到组件的模板上下文中。这意味着你可以在组件的模板中直接访问这些属性。响应式数据:
setup
内部,你可以使用 ref
和 reactive
来创建响应式数据。例如:
import { ref } from 'vue';
setup() {
const count = ref(0);
return { count };
}
方法和计算属性:
setup
内部定义方法、计算属性和侦听器。computed
和 watch
为计算属性和侦听器。生命周期钩子:
setup
内部,你可以使用新的生命周期钩子,如 onMounted
, onUpdated
等。与 Options API 的关系:
setup
函数在组件的 data
、methods
、computed
和其他选项之前执行。与模板的交互:
setup
返回任何你想在模板中使用的值。这包括响应式数据、方法、计算属性等。总的来说,setup
是 Vue 3 中引入的新特性,它为开发者提供了一个更加灵活和组合的方式来组织组件的逻辑。通过使用 setup
和其他 Composition API 的功能,你可以更容易地重用和分享代码,同时保持组件的清晰和可维护性。
@vue/runtime-core
是 Vue 3 的核心运行时包,它包含了 Vue 的核心逻辑,但不包括 DOM 特定的代码。这意味着它不包含用于更新真实 DOM 的代码,而是提供了一种机制,允许你为不同的平台(例如 web、native mobile 或 even 3D engines)创建自定义渲染器。
以下是 @vue/runtime-core
的主要内容和功能:
Virtual DOM:包括虚拟节点的创建、diffing 和 patching 逻辑。
Reactivity System:Vue 3 的响应式系统,包括 ref
, reactive
, computed
, watch
等。
Component Lifecycle:组件的生命周期钩子,如 onMounted
, onUpdated
等。
Component Logic:包括 setup
函数、provide/inject
依赖注入机制等。
Custom Renderer API:允许你创建自定义渲染器的 API,如 createRenderer
。
Utilities:各种内部工具函数和类型。
为什么需要 @vue/runtime-core
?
Platform Agnosticism:Vue 的核心逻辑与任何特定平台无关。这意味着你可以为不同的目标平台创建自定义渲染器,例如 web、native mobile、canvas、WebGL 或 even terminal interfaces。
Tree Shaking:由于 Vue 的核心功能被细分为多个模块,所以当你只使用其中的一部分功能时,打包工具可以有效地摇掉未使用的代码,从而减小最终的包大小。
Flexibility:高度模块化的结构使得 Vue 更加灵活。例如,如果你想创建一个自定义渲染器,或者只使用 Vue 的响应式系统而不使用其他功能,这都是可能的。
总结:
@vue/runtime-core
是 Vue 3 架构的核心,它包含了 Vue 的主要逻辑,但不依赖于任何特定平台。这使得 Vue 可以在各种不同的环境中运行,从浏览器到原生应用,甚至是游戏引擎或命令行界面。
在 Vue 3 中,组合式 API 提供了一种更加灵活和组合友好的方式来组织组件的逻辑。而 <script setup>
是组合式 API 的语法糖,它允许你在单文件组件 (SFC) 中以更简洁的方式使用组合式 API。
基本用法
使用 <script setup>
,你可以直接在 <script>
标签内部编写 setup
函数的内容,而无需明确定义 setup
函数。
例如,传统的组合式 API 的写法如下:
<script>
import { ref } from "vue";
export default {
setup() {
const count = ref(0);
const increment = () => count.value++;
return {
count,
increment,
};
},
};
</script>
使用 <script setup>
,上述代码可以简化为:
<script setup>
import {ref} from "vue"; const count = ref(0); const increment = () =>
count.value++;
</script>
特点和优势
setup
函数,代码更简洁。<script setup>
中,你不需要返回响应式引用或函数,它们会自动可用于模板中。setup
函数一样,<script setup>
也提供了很好的 TypeScript 支持。使用 defineProps
和 defineEmits
在 <script setup>
中,你不能直接使用 props
和 context.emit
,因为没有 setup
函数的签名。但你可以使用 defineProps
和 defineEmits
函数来定义它们。
例如:
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps(["message"]);
const emit = defineEmits();
const handleClick = () => {
emit("clicked", "Hello from child");
};
</script>
<template>
<button @click="handleClick">{{ message }}</button>
</template>
使用 <script setup>
与 <template>
的 ref
语法糖
在 <script setup>
中,你可以直接使用 ref
关键字在模板中创建一个响应式引用,而无需导入 ref
。
例如:
<script setup>
let count = ref(0);
const increment = () => count++;
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Vue 3 的响应式原理与 Vue 2 在本质上有所不同。Vue 2 主要依赖于 ES5 的 Object.defineProperty
来实现响应式,而 Vue 3 则使用了 ES6 的 Proxy
和 Reflect
。利用 Proxy 可以更为灵活和全面地拦截对象的操作,因此 Vue 3 的响应式更加强大。
下面是 Vue 3 响应式的核心原理简要概述:
reactive
函数,我们可以使一个对象变得响应式。const obj = reactive({ count: 0 });
Proxy:
obj.count
时,Proxy 允许我们捕捉到这个“get”操作。obj.count = 1
时,Proxy 也可以捕捉到这个“set”操作。依赖收集:
Effect 函数:
effect
函数是用来包裹那些应该随着响应式数据变化而重新执行的逻辑。effect
包裹的函数会被重新执行。Computed 属性:
computed
API 来创建响应式的计算属性。effect
,但与一般的 effect
不同,computed
会缓存其结果,并只在依赖的数据发生变化时重新计算。Ref API:
ref
API。.value
,例如 refCount.value
。ref
内部也是使用 Proxy 实现的。Watch 和 WatchEffect:
watch
和 watchEffect
两个 API 来观察响应式数据的变化。watch
需要明确指定观察哪个响应式源,而 watchEffect
会自动追踪它内部使用的所有响应式源。这只是 Vue 3 响应式系统的一个高度概括。真正的实现涉及到很多细节和优化策略。如果你想深入了解,建议查看 Vue 的源代码或相关的深度分析文章。
Reflect
是 ES6 中引入的一个内置对象,提供了很多用于拦截 JavaScript 操作的方法。Reflect
不是一个函数对象,所以它不可构造。其主要目的是在某些情况下统一 Object 操作行为,以及在 Proxy handler 中更简洁地表达操作。
以下是关于 Reflect.get
和 Reflect.set
的简要说明:
Reflect.get(target, propertyKey[, receiver])
这个方法读取 target
对象的 propertyKey
属性。
参数:
target
: 要读取其属性的目标对象。propertyKey
: 读取其值的属性的名字 (可以是 Symbol 或字符串)。receiver
: 如果使用了 getter,那么 receiver
就是 getter 的 this 值。返回值:返回属性的值。
示例:
const obj = {
foo: 123,
get bar() {
return "hello " + this.foo;
},
};
console.log(Reflect.get(obj, "foo")); // 123
console.log(Reflect.get(obj, "bar")); // "hello 123"
Reflect.set(target, propertyKey, value[, receiver])
这个方法设置 target
对象的 propertyKey
属性的值。
参数:
target
: 要设置其属性的目标对象。propertyKey
: 要设置其值的属性的名字 (可以是 Symbol 或字符串)。value
: 要给属性设置的新值。receiver
: 如果使用了 setter,那么 receiver
就是 setter 的 this 值。返回值:如果设置成功,返回 true
,否则返回 false
。
示例:
const obj = {
foo: 123,
set bar(value) {
this.foo = value;
},
};
console.log(obj.foo); // 123
Reflect.set(obj, "bar", 456);
console.log(obj.foo); // 456
在 Proxy handler 中,Reflect.get
和 Reflect.set
很有用,因为它们允许我们轻松地进行默认操作,同时还可以在需要时添加自定义逻辑。
Proxy
是 ES6(即 ECMAScript 2015)中引入的一个新的内置对象。它用于创建一个对象的代理,从而允许你在对象上的基础操作进行自定义的拦截和行为。
基本上,通过 Proxy
,你可以“控制”对象属性的读取、写入、删除等操作。
创建一个 Proxy
基本的 Proxy
创建语法如下:
const proxy = new Proxy(target, handler);
target
:要代理的目标对象。handler
:一个对象,其属性通常是当执行一个操作时定义的函数。一些基本的拦截操作(traps)
const handler = {
get(target, key) {
console.log(`Get on property "${key}"`);
return target[key];
},
};
const target = { foo: "bar" };
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // 输出: Get on property "foo"
// bar
const handler = {
set(target, key, value) {
console.log(`Set on property "${key}" to "${value}"`);
target[key] = value;
return true; // 表示属性设置成功
},
};
const target = {};
const proxy = new Proxy(target, handler);
proxy.foo = "bar"; // 输出: Set on property "foo" to "bar"
in
操作符。const handler = {
has(target, key) {
console.log(`Check if "${key}" exists`);
return key in target;
},
};
const target = { foo: "bar" };
const proxy = new Proxy(target, handler);
console.log("foo" in proxy); // 输出: Check if "foo" exists
// true
这只是 Proxy
的冰山一角。除了上述操作外,Proxy
还支持许多其他的拦截操作,如 deleteProperty
、ownKeys
、getOwnPropertyDescriptor
、defineProperty
、getPrototypeOf
、setPrototypeOf
等。
使用场景
注意点
虽然 Proxy
提供了强大的拦截能力,但也需要谨慎使用,因为增加了额外的抽象层可能会影响性能。此外,使用 Proxy
的代码可能难以理解和调试,特别是当存在多层代理时。
let activeEffect = null;
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach((effect) => effect());
}
}
function effect(fn) {
activeEffect = fn;
fn(); // 运行函数来收集依赖
activeEffect = null;
}
function ref(rawValue) {
const dep = new Dep();
let value = rawValue;
return {
get value() {
dep.depend();
return value;
},
set value(newVal) {
value = newVal;
dep.notify();
},
};
}
function reactive(target) {
const depsMap = new Map();
return new Proxy(target, {
get(target, key) {
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
dep.depend();
return target[key];
},
set(target, key, value) {
target[key] = value;
let dep = depsMap.get(key);
if (dep) {
dep.notify();
}
return true;
},
});
}
// 使用 ref
const count = ref(0);
effect(() => {
console.log(count.value);
});
count.value++; // 会打印 1
// 使用 reactive
const state = reactive({ count: 0 });
effect(() => {
console.log(state.count);
});
state.count++; // 会打印 1
统一性:Reflect
API 为大部分操作提供了对应的方法,这使得代码更加统一和简洁。当你看到 Reflect.get(...)
或 Reflect.set(...)
,你可以清晰地知道这是一个对对象属性的操作。
返回值:与直接的对象属性操作相比,Reflect
的方法返回更多有用的信息。例如,Reflect.set
不仅仅返回 true
或 false
来表示操作是否成功,它还可以为某些操作返回其他有意义的结果。
错误处理:当使用直接的属性操作时,例如设置一个不可写的属性,它会抛出错误。但是,使用 Reflect.set
进行设置时,它只会返回 false
,不会抛出错误。这使得错误处理更加灵活。
边界情况:在某些边界情况下,直接使用对象属性操作可能会出现问题,而 Reflect
的方法则更能确保操作的正确性。例如,当属性名称是一个 Symbol 时,直接使用 target[key]
可能会有问题,而 Reflect.get(target, key)
则可以正确处理。
未来的 JavaScript 特性兼容性:随着 ECMAScript 标准的进化,某些操作的语义可能会发生改变。使用 Reflect
可以确保 Vue 的响应式系统更好地适应未来的变化。
考虑下面的对象:
const obj = {
name: "Vue",
};
当你使用普通方式设置属性时:
obj.name = "React"; // 这会返回 "React"
现在,考虑一个不可写的属性:
Object.defineProperty(obj, "name", {
value: "Vue",
writable: false,
});
如果你尝试更改该属性:
obj.name = "React"; // 在严格模式下,这会抛出错误
但是,如果你使用 Reflect.set
:
const result = Reflect.set(obj, "name", "React");
console.log(result); // 这会输出 false,而不是抛出错误
Reflect.set
返回 false
来表示属性没有被成功设置,而不是抛出错误。
对于 Reflect.get
,考虑以下的代理:
const objWithDefault = new Proxy(
{},
{
get(target, property) {
return Reflect.get(target, property) || "default"; // 如果属性不存在,返回 "default"
},
}
);
console.log(objWithDefault.name); // 输出 "default"
在这里,我们使用 Reflect.get
来获取属性的值,并在属性不存在时提供默认值。这样的模式对于处理默认值或回退逻辑非常有用。
当谈到“边界情况”,我们实际上是指某些特定场景或条件下,直接的对象属性操作可能不如使用 Reflect
API 那么稳定或直观。
一个经典的边界情况是处理 Symbol
作为属性键。
考虑以下例子:
const sym = Symbol("key");
const obj = {
[sym]: "value",
};
如果你直接使用 obj[sym]
来获取值,当然也是可以的:
console.log(obj[sym]); // 输出 "value"
但考虑以下的代理:
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
if (typeof prop === "string" && prop.startsWith("symbol:")) {
const realKey = Symbol.for(prop.split(":")[1]);
return Reflect.get(target, realKey, receiver);
}
return Reflect.get(target, prop, receiver);
},
});
此代理的意图是允许我们使用字符串形式的 "symbol:key" 来访问使用 Symbol
作为键的属性。
在不使用 Reflect
的情况下,处理这种逻辑可能会更加复杂,因为我们必须处理可能的类型转换和符号查找。而使用 Reflect.get
可以简化这个过程,并确保操作的正确性。
这只是处理 Symbol
键时的一个例子。还有其他的边界情况,如处理原型链、处理非对象的目标等,其中 Reflect
API 可以提供更加稳定和一致的结果。
在使用 Proxy
进行对象拦截时,receiver
是 get
和 set
陷阱函数的第三个参数。它引用了原始的代理对象(或继承代理对象的另一个对象),而不是底层目标对象。receiver
的存在使得我们可以在代理对象上实现一些高级的行为,特别是与原型链和 this
值相关的行为。
以下是一个 receiver
的基本使用场景:
const target = {
message: "Hello, world!",
get greeting() {
return this.message;
},
};
const handler = {
get(target, prop, receiver) {
console.log("Accessed property:", prop);
return Reflect.get(target, prop, receiver);
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.greeting); // 输出 "Accessed property: greeting" 和 "Hello, world!"
在上面的例子中,当我们通过代理访问 greeting
属性时,get
陷阱会被触发。然后我们使用 Reflect.get
获取该属性的值,其中传递了 receiver
作为其第三个参数。
如果我们没有传递 receiver
,则 this
值在 greeting
getter 内部将引用底层的 target
对象,而不是 proxy
对象。因此,使用 receiver
确保了正确的 this
绑定。
另一个使用场景是与继承和原型链相关:
const target = {
value: 42,
};
const handler = {
get(target, prop, receiver) {
if (prop === "value") {
return Reflect.get(target, prop, receiver) * 2;
}
return Reflect.get(target, prop, receiver);
},
};
const proxy = new Proxy(target, handler);
const childObject = Object.create(proxy);
console.log(childObject.value); // 输出 84
在这里,childObject
继承自 proxy
。当我们通过 childObject
访问 value
属性时,它实际上在原型链上查找到了 proxy
,并触发了 get
陷阱。在这种情况下,receiver
引用的是 childObject
,而不是 proxy
。
通过传递 receiver
,我们可以确保即使属性查找来自原型链中的其他对象,代理的行为也是正确的。
总之,receiver
在处理代理和原型链交互、确保正确的 this
绑定等场景中非常有用。
Symbol
是 ES6(即 ECMAScript 2015)中引入的一个新的原始数据类型。与 number
、string
、boolean
等原始数据类型相比,Symbol
的独特之处在于,每次创建的 Symbol
都是唯一的。这使得 Symbol
成为了创建对象属性的理想选择,尤其是当你想避免与其他属性名冲突时。
以下是关于 Symbol
的一些关键点:
创建 Symbol: 通过 Symbol()
函数创建一个新的 Symbol
。你可以为其提供一个描述,但这只是为了调试的目的,并不影响 Symbol
的唯一性。
const symbol1 = Symbol();
const symbol2 = Symbol("description");
console.log(symbol1 === symbol2); // 输出 false
为对象添加 Symbol 属性: 你可以使用 Symbol
作为对象的属性键。
const obj = {};
const key = Symbol("key");
obj[key] = "value";
console.log(obj[key]); // 输出 "value"
Symbol 作为对象属性的私有性: 由于 Symbol
是唯一的,因此它们不会与其他属性名冲突。此外,常规的对象属性访问和迭代方法(如 Object.keys
或 for...in
循环)不会返回 Symbol
属性。
for (let prop in obj) {
console.log(prop); // 不会输出 Symbol 属性
}
console.log(Object.keys(obj)); // 输出 []
访问对象的 Symbol 属性: 使用 Object.getOwnPropertySymbols()
方法可以获取对象的所有 Symbol
属性。
console.log(Object.getOwnPropertySymbols(obj)); // 输出 [Symbol(key)]
预定义的 Symbol: ES6 提供了一些预定义的 Symbol
值,它们代表了语言内部的特定行为。例如,Symbol.iterator
代表了一个对象的默认迭代器。
const array = [1, 2, 3];
const iterator = array[Symbol.iterator]();
console.log(iterator.next()); // 输出 { value: 1, done: false }
Symbol.for() 和 Symbol.keyFor(): 这两个方法允许在全局范围内共享 Symbol
。Symbol.for()
会检查给定的描述是否已经存在一个相应的 Symbol
值,如果存在,则返回该 Symbol
;否则,它将创建一个新的 Symbol
。
const globalSymbol = Symbol.for("name");
const sameGlobalSymbol = Symbol.for("name");
console.log(globalSymbol === sameGlobalSymbol); // 输出 true
console.log(Symbol.keyFor(globalSymbol)); // 输出 "name"
总的来说,Symbol
提供了一种创建不可变、唯一属性键的方法,从而使得你可以为对象定义独特的、不容易与其他属性冲突的属性。在某些情况下,这可以增加代码的封装性和安全性。