Vue3 引入了一个全新的响应式系统,它是基于ES6的Proxy特性构建的。这个系统使得 Vue 能够更加高效地追踪数据的变化,并在数据发生变化时自动更新DOM。响应式系统的核心是"可观察",当数据变化时,视图会响应这些变化并重新渲染。
一、使用响应式助手声明基本类型
1、使用reactive代替Object,Array,Map,Set
reactive 用于定义一个响应式的对象。它接受一个普通对象并返回该对象的响应式代理。这使得整个对象的属性都是响应式的。
2、使用ref代替String,Number,Boolean
ref 用于定义响应式的引用类型。它接受一个参数值,并返回一个响应式且可变的 ref 对象。这个对象有一个 value 属性,指向传入的参数值。
ref 的使用场景:
- 当你需要包装一个基本类型值(如字符串、数字、布尔值)使其称为响应式时
- ref 也可以包装数组或对象,但通常情况下我们会使用 reactive 来处理这些复杂类型。
3、解构失去响应式值
return返回响应式对象的属性时不能用解构。在 vue3 中,如果你使用解构来访问一个由 reactive 创建的响应式对象的属性,这些属性会失去其响应式特性。这是因为解构操作实际上是在创建对象属性的本地副本,而这些副本不再是响应式的。
javascript
import { reactive } from 'vue';
const state = reactive({
count: 0,
message: 'Hello'
});
// 解构 state 对象
const { count, message } = state;
state.count++;
// count 和 message 在这里是非响应式的
console.log(count); // 输出 0
console.log(message); // 输出 'Hello'
为了保持响应式,你应该直接使用原始的响应式对象,或者使用 vue 提供的 toRefs 函数将 reactive 对象的每个属性都转换为 ref 对象,这样每个属性都保持响应式。
javascript
import { reactive, toRefs } from 'vue';
const state = reactive({
count: 0,
message: 'Hello'
});
// 使用 toRefs 保持响应式
const { count, message } = toRefs(state);
// 现在 count 和 message 仍然是响应式的
count.value++; // state.count 也会更新
console.log(state.count); // 输出 1
二、模板语法和基本指令
在 Vue3 中,模板语法和基本指令是构建动态用户界面的核心。
1、模板语法
Vue.js 使用基于HTML的模板语法,允许你声明式地将DOM绑定到底层组件实例的数据。所有 Vue.js 模板都是有效的HTML,可以被遵循规范的浏览器和HTML解析器解析。
在模板中你可以使用双大括号 {{ }} 进行文本插值
html
<span>{{message}</span>
2、v-bind
v-bind 指令用于动态地绑定一个或多个属性,或一个组件prop到表达式。在渲染的DOM上,你会看到属性值被设置为指令的表达式计算的结果。
html
<img v-bind:src="imageSrc"/>
<!-- 缩写 -->
<img :src="imageSrc"/>
3、v-model
v-model指令在 <input>、<textarea>和<select>元素上创建双向数据绑定。根据控件类型自动选择正确的方法来更新元素。
html
<!-- 在文本输入框中使用 v-model -->
<input v-model="message">
<!-- 在单选按钮中使用 v-model -->
<input type="radio" v-model="picked" value="One">
<!-- 在选择框中使用 v-model -->
<select v-model="selected">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
4、v-for
v-for 指令基于一个数组来渲染一个列表。该指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 是数组元素迭代的别名。
html
<ul>
<li v-for="item in items" :key="item.id">{{item.text}}</li>
</ul>
使用 :key 是为了给 Vue 一个提示,以便它可以跟踪每个节点的身份,从而重用和重新排序现有元素。
5、v-if、v-else-if、v-else
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true 的时候渲染。
html
<span v-if="awesome">Vue is awesome</span>
<span v-else-if="ok">Vue is ok</span>
<span v-else>Vue is not ok</span>
6、vue属性
在 Vue3 中,当你使用 ref 函数创建响应式引用时,需要通过 .value 属性来访问或修改数据,因为 ref 返回的是一个包含 value 属性的响应式引用对象。
javascript
import { ref } from 'vue';
const count = ref(0);
// 修改 count 的值
count.value = 1;
// 读取 count 的值
console.log(count.value); // 输出 1
对于对象或数组,通常会使用 reactive 函数,它不需要 value 属性来访问或修改属性,因为 reactive 返回的是一个响应式代理,直接操作对象的属性即可。
javascript
import { reactive } from 'vue';
const state = reactive({
count: 0,
message: 'Hello'
});
// 直接读取和修改 state 对象的属性
state.count = 1;
console.log(state.count); // 输出 1
在模板中使用 ref 创建的响应式引用时,不需要使用 value 来访问它们。Vue 的模板编译器会自动处理这些引用,使其在模板中直接可用。
html
<template>
<div>{{coount}}</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
return { count };
}
}
</script>
解包只在顶层属性上有效:
html
<template>
Counter:{{ count.foo +1 }} //展示 Counter:[object Object]1
Counter: {{ count.foo.value + 1 }} // 显示 1
</template>
<script setup>
import { ref } from "vue";
const count = { foo: ref(0) };
</script>
三、组件基础
在 Vue3 中,组件是构建应用的基本单元。组件可以扩展HTML元素,封装可重用的代码。在较高的层面,Vue 组件是预定义选项的一个实例,这些选项是用来定义一个新的自定义元素。
1、创建组件
组件可以通过两种方式创建:使用 Vue.extend ,或者使用单文件组件(.vue 文件)(vue3中推荐)
html
<!-- MyComponent.vue -->
<template>
<div class="my-component">
{{ message }}
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue!'
};
}
};
</script>
<style scoped>
.my-component {
/* 组件样式 */
}
</style>
2、组件的数据
组件的数据必须是一个函数,这样每个实例可以维护一份被返回对象的独立拷贝。
javascript
export default {
data() {
return {
count: 0
};
}
};
3、组件的 Props
Props是组件的自定义属性,用于从父组件接收数据。子组件需要显式地用 props 选项声明它期望获得的数据。
javascript
export default {
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // 或者任何其他构造函数
}
};
4、组件的方法
组件的方法应该定义在 methods 对象中,可以在模板中或组件实例中调用这些方法。
javascript
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
};
5、组件的生命周期
组件实例有其生命周期,你可以通过生命周期钩子在不同阶段执行代码。
javascript
export default {
mounted() {
console.log('组件已挂载!');
},
updated() {
console.log('组件已更新!');
},
unmounted() {
console.log('组件已卸载!');
}
};
6、组件的模板和样式
组件的模板定义了组件的html结构。样式可以是全局的,也可以通过 scoped 属性来限制在当前组件内部。
7、组件的注册
组件可以是全局注册的,也可以是局部注册的。局部注册的组件只能在注册它们的组件中使用。
全局注册
javascript
import Vue from 'vue';
import MyComponent from './MyComponent.vue';
Vue.component('MyComponent', MyComponent);
局部注册
javascript
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
}
};
8、组件的使用
注册组件后,你可以在父组件的模板中像使用普通html元素一样使用它。
html
<my-component></my-component>
9、组件通信
Props down:父组件通过 props 向子组件传递数据。
Events up:子组件通过事件向父组件发送消息。
10、单文件组件的<script setup>
Vue 3.2 引入了<script setup>,这是在单文件组件中使用Composition API的语法糖。
javascript
<script setup>
import { ref } from 'vue';
const count = ref(0);
function increment() {
count.value++;
}
</script>
四、defineProps、defineEmits的使用
在 Vue3 中,<script setup>是一种新的组件编写方式,它使得使用Composition API更加简洁。在<script setup>中,defineProps 和 defineEmits 是两个编译时的宏,它们用于在组件中声明 props 和自定义事件。
1、defineProps
defineProps 用于声明组件接收的props。它可以接受一个类型定义对象或TypeScript接口。
javascript
<script setup>
const props = defineProps({
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function
});
</script>
TypeScript
<script setup lang="ts">
interface Props {
title: string;
likes: number;
isPublished: boolean;
commentIds: number[];
author: object;
callback: Function;
}
const props = defineProps<Props>();
</script>
2、defineEmits
defineEmits 用于声明组件可以发出的自定义事件。它可以接收一个事件名称数组或一个 TypeScript 类型定义。
TypeScript
<script setup>
const emit = defineEmits(['update:title', 'delete:post']);
</script>
TypeScript
<script setup lang="ts">
const emit = defineEmits<{
(e: 'update:title', title: string): void;
(e: 'delete:post', postId: number): void;
}>();
</script>
在组件内部,你可以使用 emit 函数来发出事件:
TypeScript
function updateTitle(newTitle) {
emit('update:title', newTitle);
}
function deletePost(postId) {
emit('delete:post', postId);
}
在父组件中,你可以监听这些事件并做出相应的处理:
TypeScript
<template>
<ChildComponent
@update:title="handleTitleUpdate"
@delete:post="handlePostDelete"
/>
</template>
defineProps 和 defineEmits 提供了一种类型安全的方式来声明和使用 props 和自定义事件,特别是在使用TypeScript时。它们只能在<script setup>中使用,并且由于它们是编译时的宏,它们在运行时不会被包含在最终的JavaScript代码中。
请注意,defineProps 和 defineEmits 只能在<script setup>中使用一次,并且不能与其他 JavaScript 逻辑混合使用。如果你需要执行其他逻辑,应该在它们之外进行。
无论是 defineEmits 还是 defineProps ,都不需要导入。当使用 script setup. 时,它们会自动可用。
五、声明额外选项
有一些Options API方法的属性在<script setup>中不受支持
- name
- inheritAttrs
- 插件或库需要的自定义选项
解决方法是在同一组件中定义两个不同的脚本,如脚本设置RFC所定义的那样
javascript
<script>
export default {
name:'CustomName',
inheritAttrs:false,
customOptions:{}
}
</script>
<script setup>
// script setup logic
</script>
六、定义异步组件
defineAsyncComponent是 Vue3 中用于定义异步组件的函数。异步组件是指那些不会在初始渲染中同步加载的组件,而是在需要时才加载,这有助于减少应用程序的初始包大小,实现更快的加载时间。以下是一个使用 defineAsyncComponent 的例子:
1、基本用法
javascript
// 在你的组件文件中
import { defineAsyncComponent } from 'vue';
// 使用 defineAsyncComponent 定义一个异步组件
const AsyncComponent = defineAsyncComponent(() =>
import('./components/MyAsyncComponent.vue')
);
export default {
components: {
AsyncComponent
}
};
在这个例子中,MyAsyncComponent.vue是一个异步加载的组件。当AsyncComponent被用作模板中的标签时,Vue会在组件需要渲染时自动加载它。
2、在模板中使用
html
<template>
<div>
<!-- 使用异步组件 -->
<AsyncComponent />
</div>
</template>
3、高级用法
defineAsyncComponent 还接受一个对象作为参数,允许你指定加载状态时的行为,例如显示加载指示器、错误处理或设置超时时间。
javascript
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent({
// 加载异步组件的函数
loader: () => import('./components/MyAsyncComponent.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent, // LoadingComponent 是一个组件,用于在加载异步组件时显示
// 如果加载失败,使用的组件
errorComponent: ErrorComponent, // ErrorComponent 是一个组件,用于在加载失败时显示
// 在显示错误组件之前的延迟时间(毫秒)
errorDelay: 200,
// 在显示加载组件之前的延迟时间(毫秒)
delay: 200,
// 最长等待时间,在此时间之后如果组件还没有加载成功,则显示错误组件
timeout: 3000
});
export default {
components: {
AsyncComponent
}
};
在这个高级用法中,你可以定义loader函数来加载组件,以及loadingComponent和errorComponent来分别在加载和错误状态下显示。你还可以设置延迟和超时时间来优化用户体验。异步组件在大型应用程序中非常有用,特别是当你有一些只在特定情况下才需要的组件时,使用异步组件可以有效地分割代码,按需加载,从而加快应用程序的初始加载速度。
八、Vue3 生命周期钩子函数
在 Vue3 中,生命周期钩子函数是一组特殊的函数,它们提供了在组件的不同阶段执行代码的能力。这些生命周期函数钩子对应于组件的创建、更新、销毁等关键时刻。
1、挂载阶段
- beforeCreate
在实例初始化之后,数据观测(data observer)和 event/watcher 事件配置之前被调用。 - created
在实例创建完成后被立即调用。在这一步,实例已完成数据观测、属性和方法的运算,watch/event 事件回调已设置,但 $el 属性目前尚不可用。 - beforeMount
在挂载开始之前被调用,相关的render函数首次被调用。 - mounted
在实例被挂载后调用。此时你可以访问到DOM元素和组件实例。
2、更新阶段
- beforeUpdate
在数据变化之后,DOM 被更新之前调用,可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。 - updated
在由于数据变化导致的虚拟 DOM 重新渲染和打补丁之后调用。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
3、卸载阶段
- beforeUnmount
在卸载组件实例之前调用。在这一步,实例仍然完全可用。 - unmounted
在组件实例被卸载之后调用。当这个钩子被调用时,组件实例已经销毁,你不能再访问组件实例。
4、错误处理阶段
- errorCaptured
当捕获一个来自子孙组件的错误时被调用。此钩子在捕获错误时会收到三个参数:错误对象、发生错误的组件实例和一个包含错误来源信息的字符串。
5、组合式API中的生命周期钩子
Vue3 引入了组合式API,它提供了与上述生命周期钩子对应的函数,可以在setup函数中使用:
- onBeforeAmount
- onMounted
- onBeforeUpdate
- updated
- onBeforeUnmount
- onUnmounted
- onErrorCaptured
这些函数接受一个回调函数作为参数,该回调函数将在相应的生命周期钩子被触发时执行。
javascript
import { onMounted, onUpdated } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('组件已挂载!');
});
onUpdated(() => {
console.log('组件已更新!');
});
// ...其他逻辑
}
};
使用组合式API的生命周期函数可以让你在使用setup函数时更加灵活地组织代码,同时也使得逻辑复用变得更加容易。
九、Composition API
Vue3 引入了Composition API,这是一组基于函数的API,它们允许你更灵活地组织和重用代码,特别是在构建大型或复杂组件时。Composition API提供了一种新的方式来组合和管理组件的逻辑。
1、基本概念
Composition API的核心是setup函数。这个函数是组件的入口点,它在组件创建之前执行,允许你定义响应式状态、计算属性、方法和生命周期钩子。
javascript
import { ref, onMounted } from 'vue';
export default {
setup() {
// 定义响应式状态
const count = ref(0);
// 生命周期钩子
onMounted(() => {
console.log('组件已挂载');
});
// 返回值将暴露给模板
return {
count
};
}
};
2、响应式引用:ref
ref 用于定义一个响应式的引用类型。它接受一个参数值,并返回一个响应式且可变的 ref 对象。这个对象有一个value属性,指向传入的参数值。
javascript
import { ref } from 'vue';
const count = ref(0);
3、响应式状态:reactive
reactive 用于定义一个响应式的对象,它接受一个普通对象并返回该对象的响应式代理。
javascript
import { reactive } from 'vue';
const state = reactive({
count: 0,
message: 'Hello'
});
4、计算属性:computed
computed用于创建一个计算属性,它接受一个getter函数,并根据其返回值返回一个不可变的响应式引用。
javascript
import { computed } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
5、侦听器: watch 和 watchEffect
watch 和 watchEffect 用于侦听响应式引用或响应式状态的变化,并执行副作用。
javascript
import { ref, watch, watchEffect } from 'vue';
const count = ref(0);
// watch 侦听器
watch(count, (newValue, oldValue) => {
console.log(`count 变化了:${oldValue} -> ${newValue}`);
});
// watchEffect 会立即执行一次,并在依赖变化时重新执行
watchEffect(() => {
console.log(`count 的当前值是:${count.value}`);
});
6、生命周期钩子
Composition API提供了一系列的生命周期钩子函数,如onMounted、onUpdated和onUnmounted。
javascript
import { onMounted } from 'vue';
onMounted(() => {
console.log('组件已挂载');
});
7、<script setup>
Vue 3.2 引入了 <script setup>
,这是一个编译时的语法糖,它使得使用 Composition API 更加简洁。
javascript
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
8、提取和重用逻辑
Composition API的一个主要优势是能够更容易地提取和重用组件逻辑。你可以将相关的响应式状态、计算属性和函数封装到一个函数中,然后在多个组件之间共享这个函数。
javascript
// useCounter.js
import { ref } from 'vue';
export function useCounter() {
const count = ref(0);
const increment = () => {
count.value++;
};
return { count, increment };
}
然后在组件中使用这个函数:
javascript
import { useCounter } from './useCounter';
export default {
setup() {
const { count, increment } = useCounter();
return { count, increment };
}
};
Composition API提供了更多的灵活性和组织代码的能力,特别是对于那些需要处理复杂逻辑的组件,通过使用Composition API,你可以更容易地跟踪和管理组件的状态和逻辑,使得代码更加清晰和可维护。