Vue3新特性API
一、vue3介绍
vue3.0是在2.0的基础上重大优化调整后的升级版本,其响应式原理已经在vue2框架基础中介绍过,此文章重点介绍Vue 3 中一些新功能API及其使用,文章内容来源于Vue3API。包括:
- 组合式 API
- Emits 组件选项
- 单文件组件组合式 API 语法糖 (
<script setup>
) - 单文件组件状态驱动的 CSS 变量 (
<style>
中的v-bind
) - SFC
<style scoped>
现在可以包含全局规则或只针对插槽内容的规则
二、组合式 API
通过创建 Vue 组件,我们可以将界面中重复的部分连同其功能一起提取为可重用的代码段,可以大大提高应用可维护性和灵活性。然而经验证明,光靠这一点可能并不够,处理大型应用时,共享和重用代码变得尤为重要。假设我们的应用中有一个显示某个列表的视图。此外,我们还希望有搜索和筛选功能。在vue2中使用 (data
、computed
、methods
、watch
) 组件选项来组织逻辑通常都很有效,但是当我们的组件开始变得更大时,同一Vue页面的逻辑关注点也会增多,对于没有编写这些组件的人来说,这会导致组件难以阅读和理解。如果能够将同一个逻辑关注点相关代码收集在一起将能使开发人员更容易阅读和理解,这就是组合式 API 的设计思想。
如何使用组合式API? 这里需要使用vue3的一个组件选项setup,setup
选项是一个接收 props
和 context
的函数,setup() 函数在组件创建 created() 之前执行。setup
返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。我们将在vue2中data
、computed
、methods
、watch组件选项中书写的代码全部集中到setup函数中去,然后把需要给外部使用的变量或函数通过返回值方式返回,这就是vue3的组合api思想。例如如下:
<template> <div class="home"> {{ count }} --- {{ double }}---- {{ number }} </div> </template> <script lang="ts"> import { defineComponent, ref, computed, reactive, toRefs, onMounted, onUpdated, watch } from "vue" export default defineComponent({ name: "HomeView", components: {}, /* 第一个参数 props,它是响应式的,当传入新的 prop 时,它将被更新。 第二个参数 context 是一个普通的 JavaScript 对象,它是一个上下文对象,暴露了其它可能在 setup 中有用的值。 在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。 */ /* 替代vue2+的data属性、methods属性,可以在该对象中创建data、method方法 */ setup(props,context) { /* 可以将生命周期钩子函数定义到setup中,例如如下,vue3的钩子函数与vue2稍有区别,请自行百度 */ onMounted(() => { console.log("....") }) onUpdated(() => { console.log("....") }) /* 创建一个响应式的对象 响应式对象和普通对象相比,他在值改变后会同步更新模板 */ const count = ref(1280) /* 计算属性 */ const double = computed(() => { return count.value * 10000 }) /* 监听器,第一个参数可以是数组,用于监听多个对象 */ const str = ref("") watch(str, (nval, oval) => { console.log("countchange....") }) /* 创建一个方法 */ const add = () => { /* 在需要.value的方式获取属性的值 */ count.value++ } /* reactive用于把对象进行包裹,形成一个对象,导出后外部可以直接通过data.属性名的方式调用 data也可以使用...data的方式导出(会让数据失去响应式的活力变成普通对象),这样就不需要data.属性名的方式使用 */ const data = reactive({ number: 100, /* 在reactive内部使用属性时,不再需要.value的方式获取 */ plus: () => { return data.number * 1000 } }) /* 让数据失去响应式的对象转换成响应式对象 */ const refData = toRefs(data) /* 被外部使用的属性需要导出,否则无法使用 */ return { count, add, double, ...data, ...refData } } }) </script>
三、应用配置
每个 Vue 应用都会暴露一个包含其配置项的 config
对象,在挂载应用之前,你可以修改下列 property,代码示例如下:
const app = createApp({}) console.log(app.config) /* 指定一个处理函数,来处理组件渲染函数和侦听器执行期间抛出的未捕获错误。这个处理函数被调用时,可获取错误信息和相应的应用实例。 */ app.config.errorHandler = (err, vm, info) => { // 处理错误 } /* 为 Vue 的运行时警告指定一个自定义处理函数。注意这只会在开发环境下生效,在生产环境下它会被忽略。 */ app.config.warnHandler = function(msg, vm, trace) { // `trace` 是组件的继承关系追踪 } /* 添加一个可以在应用的任何组件实例中访问的全局 property。组件的 property 在命名冲突时具有优先权。这可以代替 Vue 2.x 的 Vue.prototype 扩展 */ app.config.globalProperties.foo = 'bar' /* 为自定义选项定义合并策略。合并策略选项分别接收在父实例和子实例上定义的选项的值作为第一个和第二个参数。 */ app.config.optionMergeStrategies.hello = (parent, child) => { return `Hello, ${child}` } /* 指定一个方法来识别 Vue 以外 (例如通过 Web Components API) 定义的自定义元素。如果一个组件匹配了这个条件,它就不需要在本地或全局注册,Vue 也不会抛出 Unknown custom element 的警告。 */ app.config.compilerOptions.isCustomElement = tag => tag.startsWith('ion-') /* 默认情况下,Vue 会移除/压缩模板元素之间的空格以产生更高效的编译结果 1、元素内的多个开头/结尾空格会被压缩成一个空格 2、元素之间的包括折行在内的多个空格会被移除 3、文本结点之间可被压缩的空格都会被压缩成为一个空格 将值设置为 'preserve' 可以禁用 2 和 3。 */ app.config.compilerOptions.whitespace = 'preserve' /* 这个选项一般会用于避免和同样使用双大括号语法的服务端框架发生冲突,用于配置模板内文本插值的分隔符,将分隔符设置为 ES6 模板字符串风格 */ app.config.compilerOptions.delimiters = ['${', '}'] /* 默认情况下,Vue 会在生产环境下移除模板内的 HTML 注释。将这个选项设置为 true 可以强制 Vue 在生产环境下保留注释。而在开发环境下注释是始终被保留的。 */ app.config.compilerOptions.comments = true /* 设置为 true 以在浏览器开发工具的 performance/timeline 面板中启用对组件初始化、编译、渲染和更新的性能追踪。只适用于开发模式和支持 performance.mark API 的浏览器。 */ app.config.performance=true
四、应用API
在 Vue 3 中,全局改变 Vue 行为的 API 现在被移动到了由新的 createApp
方法所创建的应用实例上,现在它们的影响仅限于该特定应用实例。调用 createApp
返回一个应用实例。该实例提供了一个应用上下文。应用实例挂载的整个组件树共享相同的上下文,该上下文提供了之前在 Vue 2.x 中的“全局”配置。由于 createApp
方法返回应用实例本身,因此可以在其后链式调用其它方法,这些方法如下:
import { createApp } from 'vue' const app = createApp({}) // 注册一个选项对象 app.component('my-component', { /* ... */ }) // 检索注册的组件 const MyComponent = app.component('my-component') //配置一个包含应用配置的对象。 app.config = {...} // 注册全局指令。 app.directive('my-directive', { // 指令具有一组生命周期钩子: // 在绑定元素的 attribute 或事件监听器被应用之前调用 created() {}, // 在绑定元素的父组件挂载之前调用 beforeMount() {}, // 在绑定元素的父组件挂载之后调用 mounted() {}, // 在包含组件的 VNode 更新之前调用 beforeUpdate() {}, // 在包含组件的 VNode 及其子组件的 VNode 更新之后调用 updated() {}, // 在绑定元素的父组件卸载之前调用 beforeUnmount() {}, // 在绑定元素的父组件卸载之后调用 unmounted() {} }) // 注册 (函数指令) app.directive('my-directive', () => { // 这将被作为 `mounted` 和 `updated` 调用 }) // 检索全局指令, 如果已注册,则返回指令定义 const myDirective = app.directive('my-directive') //将一个 mixin 应用在整个应用范围内。一旦注册,它们就可以在当前的应用中任何组件模板内使用它。插件作者可以使用此方法将自定义行为注入组件。不建议在应用代码中使用,它将影响每一个之后创建的组件。 // 为自定义的选项 'myOption' 注入一个处理器。 app.mixin({ created() { const myOption = this.$options.myOption if (myOption) { console.log(myOption) } } }) //所提供 DOM 元素的 innerHTML 将被替换为应用根组件的模板渲染结果。 app.mount('#my-app') //设置一个可以被注入到应用范围内所有组件中的值。组件应该使用 inject 来接收 provide 的值。从 provide/inject 的角度来看,可以将应用程序视为根级别的祖先,而根组件是其唯一的子级。 app.provide('user', 'administrator') // 卸载应用实例的根组件,挂载 5 秒后,应用将被卸载 setTimeout(() => app.unmount(), 5000) //安装 Vue.js 插件。如果插件是一个对象,则它必须暴露一个 install 方法。如果插件本身是一个函数,则它将被视为 install 方法。 //该 install 方法将以应用实例作为第一个参数被调用。 //传给 use 的其他 options 参数将作为后续参数传入该安装方法。 //当在同一个插件上多次调用此方法时,该插件将仅安装一次。 app.use(MyPlugin)
五、全局API
如果你使用的是 CDN 构建版本,那么全局 API 可以通过全局对象 Vue
来访问,使用方式举例如下:
import { createApp, h, nextTick ....... } from 'vue' //按需引入需要的API名称 //调用API,你可以在 createApp 之后链式调用其它方法,这些方法可以在应用 API 中找到 const app = createApp({}) render() { return h('h1', {}, 'Some title') //返回一个”虚拟节点“,通常缩写为 VNode:一个普通对象,其中包含向 Vue 描述它应在页面上渲染哪种节点的信息,包括所有子节点的描述。它的目的是用于手动编写的渲染函数 } //defineComponent 只返回传递给它的对象。但是,就类型而言,返回的值有一个合成类型的构造函数,用于手动渲染函数、TSX 和 IDE 工具支持 const MyComponent = defineComponent({ data() { return { count: 1 } }, methods: { increment() { this.count++ } } }) //创建一个只有在需要时才会加载的异步组件。 const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) //该方法接受和 defineComponent 相同的参数,但是返回一个原生的自定义元素,该元素可以用于任意框架或不基于框架使用。 const MyVueElement = defineCustomElement({ // 这里是普通的 Vue 组件选项 props: {}, emits: {}, template: `...`, // 只用于 defineCustomElement:注入到 shadow root 中的 CSS styles: [`/* inlined css */`] }) //resolveComponent 只能在 render 或 setup 函数中使用。返回一个 Component。如果没有找到,则返回接收的参数 name。 render() { const MyComponent = resolveComponent('MyComponent') } //resolveDirective 只能在 render 或 setup 函数中使用。返回一个 Directive。如果没有找到,则返回 undefined。 render () { const highlightDirective = resolveDirective('highlight') } //createRenderer 函数接受两个泛型参数: HostNode 和 HostElement,对应于宿主环境中的 Node 和 Element 类型。 const { render, createApp } = createRenderer<Node, Element>({ patchProp, ...nodeOps }) // nextTick将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。 const app = createApp({ setup() { const message = ref('Hello!') const changeMessage = async newMessage => { message.value = newMessage await nextTick() console.log('Now DOM is updated') } } }) .......
六、选项式 API
//该函数返回组件实例的 data 对象。在 data 中,我们不建议观察具有自身状态行为的对象,如浏览器 API 对象和原型 property。 data() { return { a: 1 } } //一个用于从父组件接收数据的数组或对象。它可以是基于数组的简单语法,也可以是基于对象的支持诸如类型检测、自定义验证和设置默认值等高阶配置的语法。 props: ['size', 'myMessage'] props: { // 类型检查 height: Number, // 类型检查 + 其他验证 age: { type: Number, default: 0, required: true, validator: value => { return value >= 0 } } } //计算属性将被混入到组件实例中。所有 getter 和 setter 的 this 上下文自动地绑定为组件实例。如果你为一个计算属性使用了箭头函数,则 this 不会指向这个组件的实例,不过你仍然可以通过该函数的第一个参数来访问实例: computed: { aDouble: vm => vm.a * 2 } //methods 将被混入到组件实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this 自动绑定为组件实例。 methods: { plus() { this.a++ } } /* data() { return { a: 1, b: 2, c: { d: 4 }, e: 5, f: 6 } }, 监听器,一个对象,键是要侦听的响应式 property——包含了 data 或 computed property,而值是对应的回调函数。值也可以是方法名,或者包含额外选项的对象。组件实例将会在实例化时调用 $watch() */ watch: { // 侦听顶级 property a(val, oldVal) { console.log(`new: ${val}, old: ${oldVal}`) }, // 字符串方法名 b: 'someMethod', // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深 c: { handler(val, oldVal) { console.log('c changed') }, deep: true }, // 侦听单个嵌套 property 'c.d': function (val, oldVal) { // do something }, // 该回调将会在侦听开始之后被立即调用 e: { handler(val, oldVal) { console.log('e changed') }, immediate: true }, // 你可以传入回调数组,它们会被逐一调用 f: [ 'handle1', function handle2(val, oldVal) { console.log('handle2 triggered') }, { handler: function handle3(val, oldVal) { console.log('handle3 triggered') } /* ... */ } ] }, //emits 可以是数组或对象,从组件触发自定义事件,emits 可以是简单的数组,也可以是对象,后者允许配置事件验证。 // 数组语法 app.component('todo-item', { emits: ['check'], created() { this.$emit('check') } }) // 对象语法 app.component('reply-form', { emits: { // 没有验证函数 click: null, // 带有验证函数 submit: payload => { if (payload.email && payload.password) { return true } else { console.warn(`Invalid submit event payload!`) return false } } } }) //一个将暴露在公共组件实例上的 property 列表。默认情况下,通过 $refs、$parent 或 $root 访问到的公共实例与模板使用的组件内部实例是一样的。expose 选项将限制公共实例可以访问的 property。 //由 Vue 自身定义的 property,比如 $el 和 $parent,将始终可以被公共实例访问,并不需要列出。 export default { // increment 将被暴露, // 但 count 只能被内部访问 expose: ['increment'], data() { return { count: 0 } }, methods: { increment() { this.count++ } } }
七、生命周期钩子
所有生命周期钩子的 this
上下文将自动绑定至实例中,因此你可以访问 data、computed 和 methods。这意味着你不应该使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos()
)。因为箭头函数绑定了父级上下文,所以 this
不会指向预期的组件实例,并且this.fetchTodos
将会是 undefined。
//在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。 updated() { this.$nextTick(function () { // 仅在整个视图都被重新渲染完毕之后才会运行的代码 }) } //在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。 errorCaptured() { } //跟踪虚拟 DOM 重新渲染时调用。钩子接收 debugger event 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。 renderTracked() { } //当虚拟 DOM 重新渲染被触发时调用。和 renderTracked 类似,接收 debugger event 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。 renderTriggered() { }
八、单文件组件
*.vue
文件是使用类 HTML 语法来描述 Vue 组件的一种自定义文件格式。每一个 *.vue
文件都由三种类型的顶层语法块所组成:<template>
、<script>
、<style>
以及可选的附加自定义块。常用规范说明如下://每个 *.vue 文件最多可同时包含一个顶层 <template> 块。 <template> <div class="example">{{ msg }}</div> </template> //为了满足任何项目特定的需求,*.vue 文件中还可以包含额外的自定义块,例如 <page-query> 块。 <page-query> query { posts: allWordPressPost { edges { node { id title } } } } </page-query> //每一个 *.vue 文件最多可同时包含一个 <script> 块 (不包括<script setup>)。 //其默认导出的内容应该是 Vue 组件选项对象,它要么是一个普通的对象,要么是 defineComponent 的返回值。 <script> export default { data() { return { msg: 'Hello world!' } } } </script> //每个 *.vue 文件最多可同时包含一个 <script setup> 块 (不包括常规的 <script>) //该脚本会被预处理并作为组件的 setup() 函数使用,也就是说它会在每个组件实例中执行。<script setup> 的顶层绑定会自动暴露给模板。<script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。 <script setup> //....会被预处理并作为组件的 setup() 函数使用 </script> //一个 *.vue 文件可以包含多个 <style> 标签。当<style>
标签带有scoped
attribute 的时候,它的 CSS 只会应用到当前组件的元素上。 <style> .example { color: red; } </style> //可选的附加自定义块 <custom1> //这里可以是,例如:组件的文档 </custom1> //如果你倾向于将 *.vue 组件拆分为多个文件,可以使用 src attribute 来引入外部的文件作为语言块 <template src="./template.html"></template> <style src="./style.css"></style> <script src="./script.js"></script> //在每个块中,注释应该使用相应语言 (HTML, CSS, JavaScript, Pug, 等等) 的语法。对于顶层的注释而言,使用 HTML 注释语法:<!-- 这里是注释内容 -->。 //块可以使用 lang attribute 声明预处理语言。最常见的场景就是在 <script> 块中使用 TypeScript <script lang="ts"> // 使用 TypeScript </script> //自动 name 推断:SFC 在下列情况会依据它的文件名来自动推断组件名称,名为 FooBar 的文件可以在模板中用 <FooBar/> 引用它自己。这种方式比明确注册或引入的组件的优先级要低。
九、响应性 API
响应性 API 包含以下部分,使用方式此处不在叙述,可以查阅vue.API接口文档。
- 响应性基础 API:包含reactive、readonly、isproxy、isreactive、isreadonly、toraw、markraw、shallowreactive、shallowreadonly。
- Refs:ref、unref、toRef、toRefs、isRef、customRef、shallowRef。
- Computed 与 watch:computed、watcheffect、watchposteffect、watchsynceffect、watch。
- Effect 作用域 API:effectscope、getcurrentscope、onscopedispose。
十、单文件组件 <script setup>语法糖
<script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。里面的代码会被编译成组件 setup()
函数的内容。这意味着与普通的 <script>
只在组件被首次引入的时候执行一次不同,<script setup>
中的代码会在每次组件实例被创建的时候执行。任何在 <script setup>
声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用。相比于普通的 <script>
语法,它具有更多优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和抛出事件。
- 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。