简介
import { createApp } from 'vue'
createApp ({
data ( ) {
return {
count : 0
}
}
}).mount ('#app' )
无需构建步骤,渐进式增强静态的 HTML
注释:petite-vue,用于渐进式增强静态的 HTML,可以把 html 中部分片段作为 vue 组件
在任何页面中作为 Web Components 嵌入
注释:Vue 提供了一个 defineCustomElement 方法,用来将 Vue 组件转换成一个继承自 HTMLElement 的自定义元素构造器。
import { defineCustomElement } from "vue" ;
const MyVueElement = defineCustomElement ({
props : {},
emits : {},
template : `...` ,
styles : [`/* inlined css */` ],
});
window .customElements .define ("my-vue-element" , MyVueElement );
Jamstack / 静态站点生成 (SSG)
注释:Jamstack / 静态站点生成 (SSG) 是一种构建快速网站的技术,它使用一系列配置、模板和数据,生成静态 HTML 文件及相关资源,并将它们部署到任何支持静态文件的 Web 服务器或 CDN 上
选项式 API 是在组合式 API 的基础上实现的
快速上手
可以使用导入映射表 (Import Maps) 来告诉浏览器如何定位到导入的 vue
如果你更喜欢那些还不支持导入映射表的浏览器,你可以使用 es-module-shims 来进行 polyfill
<script type ="importmap" >
{
"imports" : {
"vue" : "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script >
<div id ="app" > {{ message }}</div >
<script type ="module" >
import { createApp } from 'vue'
createApp ({
data ( ) {
return {
message : 'Hello Vue!'
}
}
}).mount ('#app' )
</script >
如果直接在浏览器中打开了上面的 index.html,你会发现它抛出了一个错误,因为 ES 模块不能通过 file:// 协议工作。为了使其工作,你需要使用本地 HTTP 服务器通过 http:// 协议提供 index.html
如果 组件模板是内联的 JavaScript 字符串。如果你正在使用 VSCode,你可以安装 es6-string-html 扩展,然后在字符串前加上一个前缀注释 /html / 以高亮语法。
创建一个 Vue 应用
.mount() 方法应该始终在整个应用配置和资源注册完成后被调用。同时请注意,不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。
当根组件没有设置 template 选项时,Vue 将自动使用容器的 innerHTML 作为模板。
DOM 内模板通常用于无构建步骤的 Vue 应用程序。它们也可以与服务器端框架一起使用,其中根模板可能是由服务器动态生成的。
应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项,例如定义一个应用级的错误处理器,用来捕获所有子组件上的错误:
app.config .errorHandler = (err ) => {
}
模板语法
模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表 。
没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用
在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:
动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告
动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的
当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写
<a v-bind: [attributeName ]="url" > ... </a >
<a : [attributeName ]="url" > ... </a >
响应式基础
Vue 在组件实例上暴露的内置 API 使用 $ 作为前缀。它同时也为内部属性保留 _ 前缀。因此,你应该避免在顶层 data 上使用任何以这些字符作前缀的属性
在 Vue 3 中,数据是基于 JavaScript Proxy(代理) 实现响应式的
注释:所以一个对象赋值给响应的属性后,这两个值就不是同一个值了
注释:reactive 只是利用 Proxy 拦截了对对象的操作,并返回一个包装过后的对象。在内部对包装后对象的属性操作依然会对原对象进行修改。
在某些情况下,我们可能需要动态地创建一个方法函数,比如创建一个预置防抖的事件处理器:
import { debounce } from 'lodash-es'
export default {
methods : {
click : debounce (function ( ) {
}, 500 )
}
}
不过这种方法对于被重用的组件来说是有问题的,因为这个预置防抖的函数是 有状态的:它在运行时维护着一个内部状态。如果多个组件实例都共享这同一个预置防抖的函数,那么它们之间将会互相影响。
要保持每个组件实例的防抖函数都彼此独立,我们可以改为在 created 生命周期钩子中创建这个预置防抖的函数:
export default {
created ( ) {
this .debouncedClick = _.debounce (this .click , 500 )
},
unmounted ( ) {
this .debouncedClick .cancel ()
},
methods : {
click ( ) {
}
}
}
可以使用 reactive() 函数创建一个响应式对象或数组:
import { reactive } from 'vue'
const state = reactive ({ count : 0 })
为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身
依靠深层响应性,响应式对象内的嵌套对象依然是代理
注释:Proxy 包装的 get 或 set 方法只对浅层有效,vue 递归包装了所有深层,每一层都是一个 Proxy 对象,都具备响应性。
reactive() API 有两条限制:
仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效
因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失
同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性
const state = reactive({ count: 0 })
let n = state.count
n++
let { count } = state
count++
callSomeFunction(state.count)
注释: ref 函数内部会使用 reactive 函数来创建一个响应式的代理对象,这个代理对象会拦截对原始对象和其深层属性的访问和修改,并触发依赖更新
仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 例如, object 是顶层属性,但 object.foo 不是
const object = { foo: ref (1 ) }
{{ object .foo + 1 }}
需要注意的是,如果一个 ref 是文本插值(即一个 {{ }} 符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1
{{ object .foo }}
当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样:
注释:这是在 js 中
注释:reactive 包装的普通对象({}定义的)在通过 get 获取属性时会对属性指向指的类型进行判断,如果值是一个 ref 对象,会返回 ref.value 的值。同理赋值的时候也是对 ref.value 进行赋值
注释:reactive 包装其他对象类型不会有这个特性
注释:在模板中绑定 state.count 时等同 reactive 包装的普通对象,不需要写为 state.count.value
只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包
注释:浅层响应对象是指使用 shallowReactive () 或 shallowReadonly () 创建的响应式对象
const count = ref (0 )
const state = reactive ({
count
})
console.log (state.count)
state.count = 1
console.log (count.value)
跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。
const books = reactive([ref ('Vue 3 Guide' )])
console.log(books[0 ].value )
const map = reactive(new Map([['count' , ref (0 )]]))
console.log(map.get ('count' ).value )
计算属性
computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value 访问计算结果
类与样式绑定
<div :class="[{ active: isActive }, errorClass]" ></div>
如果你的组件有多个根元素,你将需要指定哪个根元素来接收这个 class。你可以通过组件的 $attrs 属性来实现指定
<p :class="$attrs.class" >Hi!</p>
我们还可以给 :style 绑定一个包含多个样式对象的数组。这些对象会被合并后渲染到同一元素上:
<div :style="[baseStyles, overridingStyles]" ></div>
可以对一个样式属性提供多个 (不同前缀的) 值,举例来说
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }" ></div>
条件渲染
当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行
列表渲染
Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
默认模式是高效的,但只适用于列表渲染输出的结果不依赖子组件状态或者临时 DOM 状态 (例如表单输入值) 的情况
为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute
推荐在任何可行的时候为 v-for 提供一个 key attribute,除非所迭代的 DOM 内容非常简单 (例如:不包含组件或有状态的 DOM 元素),或者你想有意采用默认行为来提高性能。
key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key
在计算属性中使用 reverse() 和 sort() 的时候务必小心!这两个方法将变更原始数组,计算函数中不应该这么做。
事件处理
有时我们需要在内联事件处理器中访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的 $event 变量,或者使用内联箭头函数:
注释:内联事件会被包装成如下形式warn('Form cannot be submitted yet.', $event)
=>(event)=>this.warn('Form cannot be submitted yet.', event)
注释:函数名事件感觉也会被包装
<button @click ="warn('Form cannot be submitted yet.', $event)" >
Submit
</button >
<button @click ="(event) => warn('Form cannot be submitted yet.', event)" >
Submit
</button >
事件修饰符
.capture 添加事件监听器时,使用 capture
捕获模式
.passive 告诉浏览器你不想阻止事件的默认行为,从而提高性能和用户体验
注释:onScroll 滚动事件会在每一帧滚动动画结束后被触发,且在一个滚动过程中会被多次触发,当 onScroll 有事件时会阻碍下一帧动画导致不顺畅
可以直接使用 KeyboardEvent.key 暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。
<input @keyup .page-down="onPageDown" />
系统按键修饰符和常规按键不同。与 keyup 事件一起使用时,该按键必须在事件发出时处于按下状态。换句话说,keyup.ctrl 只会在你仍然按住 ctrl 但松开了另一个键时被触发。若你单独松开 ctrl 键将不会触发。
.exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。
<button @click.ctrl ="onClick" > A</button >
<button @click.ctrl.exact ="onCtrlClick" > A</button >
<button @click.exact ="onClick" > A</button >
表单输入绑定
v-model 还可以用于各种不同类型的输入,<textarea>、<select> 元素。它会根据所使用的元素自动使用对应的 DOM 属性和事件组合
v-model 会忽略任何表单元素上初始的 value、checked 或 selected attribute。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源
注释:v-model是语法糖,在编译时根据标签类型编译成不同的语法
对于需要使用 IME 的语言 (中文,日文和韩文等),你会发现 v-model 不会在 IME 输入还在拼字阶段时触发更新。如果你的确想在拼字阶段也触发更新,请直接使用自己的 input 事件监听器和 value 绑定而不要使用 v-model
单一的复选框,绑定布尔类型值
// checked是布尔值
<input type ="checkbox" id ="checkbox" v-model="checked" />
可以将多个复选框绑定到同一个数组或集合的值
注释:复选框有两种使用方式,绑定布尔值(是否选中)或绑定一个数组(value是否包含在数组中)
// const checkedNames = ref([])
<div > Checked names: {{ checkedNames }}</div >
<input type ="checkbox" id ="jack" value ="Jack" v-model ="checkedNames" >
<label for ="jack" > Jack</label >
<input type ="checkbox" id ="john" value ="John" v-model ="checkedNames" >
<label for ="john" > John</label >
<input type ="checkbox" id ="mike" value ="Mike" v-model ="checkedNames" >
<label for ="mike" > Mike</label >
如果 v-model 表达式的初始值不匹配任何一个选择项,<select> 元素会渲染成一个“未选择”的状态。在 iOS 上,这将导致用户无法选择第一项,因为 iOS 在这种情况下不会触发一个 change 事件。因此,我们建议提供一个空值的禁用选项,如上面的例子所示。
注释:当 option 没有 value 属性时,选中值为 option 的 htmlText
<select v-model ="selected" >
<option disabled value ="" > Please select one</option >
<option > A</option >
<option > B</option >
<option > C</option >
</select >
true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。这里 toggle 属性的值会在选中时被设为 'yes',取消选择时设为 'no'。
注释:复选框在没有 value 属性时,绑定值默认为布尔值
<input
type ="checkbox"
v-model="toggle"
true -value="yes"
false -value="no" />
true-value 和 false-value attributes 不会影响 value attribute,因为浏览器在表单提交时,并不会包含未选择的复选框。为了保证这两个值 (例如:“yes”和“no”) 的其中之一被表单提交,请使用单选按钮作为替代。
注释:浏览器表单提交时会提交复选框的 value 属性,未选中时不会提交这个复选框项,当选中时复选框的 value 会被设置为 true-value 或 false-value 绑定值,符合浏览器表单提交的默认行为
pick 会在第一个按钮选中时被设为 first,在第二个按钮选中时被设为 second
<input type ="radio" v-model="pick" :value="first" />
<input type ="radio" v-model="pick" :value="second" />
v-model 同样也支持非字符串类型的值绑定!在上面这个例子中,当某个选项被选中,selected 会被设为该对象字面量值
注释:value 的绑定值和 selected 的选中值是同一个内存对象
<select v-model ="selected" >
<option :value ="{ number: 123 }" > 123</option >
</select >
默认情况下,v-model 会在每次 input 事件后更新数据 (IME 拼字阶段的状态例外)。你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据
<input v-model.lazy ="msg" />
如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入:
如果该值无法被 parseFloat() 处理,那么将返回原始值。
number 修饰符会在输入框有 type="number" 时自动启用。
<input v-model.number="age" />
如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符:
<input v-model.trim="msg" />
生命周期
注释:
new Vue 创建应用实例
触发 beforeCreate 生命周期
应用实例.mount 创建根组件实例,调用 _init 进行初始化
合并选项
初始化生命周期,设置父子组件关系
初始化事件
初始化渲染,创建一个空得 VNode,赋值给 vm._vnode
初始化状态
创建 Watcher 实例,用于订阅数据变化
触发 created 生命周期
调用 render 函数生成虚拟 dom
发现虚拟 dom 中有组件,塞入实例化队列
实例树包含所有组件实例
虚拟dom树包括用于生成浏览器标准dom的虚拟节点
render函数应该是先创建了对应的虚拟dom,然后把这个虚拟dom传入构造函数创建实例,再把实例塞入实例队列等待初始化,当实例初始化结束调用 render 时,用 render 返回结果代替传入的虚拟 dom
检查实例化队列,为空时,调用 patch 函数更新到真实 dom
setTimeout (() => {
onMounted (() => {
})
}, 100 )
侦听器
watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
watch (
() => x.value + y.value ,
(sum ) => {
console .log (`sum of x + y is: ${sum} ` )
}
)
watch ([x, () => y.value ], ([newX, newY] ) => {
console .log (`x is ${newX} and y is ${newY} ` )
})
不能直接侦听响应式对象的属性值,例如:
注释:这个属性的值不能是个基础类型,因为基础类型不具有响应性。如果值是个对象的,在 reactive 包装时这个值也被递归包装了,具有响应性
const obj = reactive ({ count : 0 })
watch (obj.count , (count ) => {
console .log (`count is: ${count} ` )
})
watch (
() => obj.count ,
(count ) => {
console .log (`count is: ${count} ` )
}
)
直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:
const obj = reactive ({ count : 0 })
watch (obj, (newValue, oldValue ) => {
})
obj.count ++
相比之下,一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调:
注释:下例中计算属性的返回===state.someObject,即具有响应性
注释:在 watch 计算属性时应该是做了特殊处理,不会监听内部的改变
watch (
() => state.someObject ,
() => {
}
)
也可以给上面这个例子显式地加上 deep 选项,强制转成深层侦听器:
watch (
() => state.someObject ,
(newValue, oldValue ) => {
},
{ deep : true }
)
watchEffect() 允许我们自动跟踪回调的响应式依赖。上面的侦听器可以重写为:
这个例子中,回调会立即执行,不需要指定 immediate: true。在执行期间,它会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。有了 watchEffect(),我们不再需要明确传递 todoId 作为源值。
watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪
watchEffect (async () => {
const response = await fetch (
`https://jsonplaceholder.typicode.com/todos/${todoId.value} `
)
data.value = await response.json ()
})
watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
注释:watch 监听的对象被 set 相同的值时不会触发,但是 watchEffect 会
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项:
后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect()
注释:optionAPI也支持
watch(source , callback, {
flush: 'post'
})
watchEffect(callback , {
flush: 'post'
})
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。
要手动停止一个侦听器,请调用 watch 或 watchEffect 返回的函数:
const unwatch = watchEffect (() => {})
unwatch ()
optionAPI
watch 默认是浅层的:被侦听的属性,仅在被赋新值时,才会触发回调函数——而嵌套属性的变化不会触发。如果想侦听所有嵌套的变更,你需要深层侦听器:
回调函数的初次执行就发生在 created 钩子之前。Vue 此时已经处理了 data、computed 和 methods 选项,所以这些属性在第一次调用时就是可用的。
模板引用
应该注意的是,ref 数组并不保证与源数组相同的顺序。
注释:因为 ref 数组是根据组件的挂载顺序来更新的,而不是根据源数组的顺序
除了使用字符串值作名字,ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数:
注意我们这里需要使用动态的 :ref 绑定才能够传入一个函数
当绑定的元素被卸载时,函数也会被调用一次,此时的 el 参数会是 null
注释:optionAPI也支持
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }" >
使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:
<script setup >
import { ref } from 'vue'
const a = 1
const b = ref (2 )
defineExpose ({
a,
b
})
</script >
optionAPI
expose 选项可以用于限制对子组件实例的访问:
export default {
expose : ['publicData' , 'publicMethod' ],
data ( ) {
return {
publicData : 'foo' ,
privateData : 'bar'
}
},
methods : {
publicMethod ( ) {
},
privateMethod ( ) {
}
}
}
组件基础
在单文件组件中,推荐为子组件使用 PascalCase 的标签名,以此来和原生的 HTML 元素作区分。虽然原生 HTML 标签名是不区分大小写的,但 Vue 单文件组件是可以在编译中区分大小写的
defineProps 会返回一个对象,其中包含了可以传递给组件的所有 props:
const props = defineProps (['title' ])
console .log (props.title )
defineEmits 仅可用于 <script setup> 之中,并且不需要导入,它返回一个等同于 $emit 方法的 emit 函数。
<script setup >
const emit = defineEmits (['enlarge-text' ])
emit ('enlarge-text' )
</script >
如果你没有在使用 <script setup>,你可以通过 emits 选项定义组件会抛出的事件
可以从 setup() 函数的第二个参数,即 setup 上下文对象上访问到 emit 函数
例子中,被传给 :is 的值可以是以下几种:
<component :is ="tabs[currentTab]" > </component >
在 DOM 模板中,我们必须显式地写出关闭标签
这是由于 HTML 只允许一小部分特殊的元素省略其关闭标签
<my-component > </my-component >
某些 HTML 元素对于放在其中的元素类型有限制
这将导致在使用带有此类限制元素的组件时出现问题。例如:
<table >
<blog-post-row > </blog-post-row >
</table >
可以使用特殊的 is attribute 作为一种解决方案:
当使用在原生 HTML 元素上时,is 的值必须加上前缀 vue: 才可以被解析为一个 Vue 组件。这一点是必要的,为了避免和原生的自定义内置元素相混淆。
<table >
<tr is ="vue:blog-post-row" > </tr >
</table >
注册
可以使用 Vue 应用实例的 app.component() 方法,让组件在当前 Vue 应用中全局可用
使用 PascalCase 作为组件名的注册格式
PascalCase 是合法的 JavaScript 标识符。这使得在 JavaScript 中导入和注册组件都很容易,同时 IDE 也能提供较好的自动补全。
<PascalCase /> 在模板中更明显地表明了这是一个 Vue 组件,而不是原生 HTML 元素。同时也能够将 Vue 组件和自定义元素 (web components) 区分开来。
为了方便,Vue 支持将模板中使用 kebab-case 的标签解析为使用 PascalCase 注册的组件
Props
注意传递给 defineProps() 的参数和提供给 props 选项的值是相同的,两种声明方式背后其实使用的都是 prop 选项。
如果一个 prop 的名字很长,应使用 camelCase 形式,因为它们是合法的 JavaScript 标识符,可以直接在模板的表达式中使用,也可以避免在作为属性 key 名时必须加上引号
虽然理论上你也可以在向子组件传递 props 时使用 camelCase 形式 (使用 DOM 模板时例外),但实际上为了和 HTML attribute 对齐,我们通常会将其写为 kebab-case 形式:
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter' ])
const counter = ref (props.initialCounter)
当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。这是因为 JavaScript 的对象和数组是按引用传递,而对 Vue 来说,禁止这样的改动,虽然可能生效,但有很大的性能损耗,比较得不偿失。
这种更改的主要缺陷是它允许了子组件以某种不明显的方式影响父组件的状态,可能会使数据流在将来变得更难以理解。在最佳实践中,你应该尽可能避免这样的更改,除非父子组件在设计上本来就需要紧密耦合
注释:性能损耗似乎差不多
type 也可以是自定义的类或构造函数,Vue 将会通过 instanceof 来检查类型是否匹配。例如下面这个类:
defineProps ({
author: Person
})
为了更贴近原生 boolean attributes 的行为,声明为 Boolean 类型的 props 有特别的类型转换规则。
当一个 prop 被声明为允许多种类型时,无论声明类型的顺序如何,Boolean 类型的特殊转换规则都会被应用。
<MyComponent disabled />
<MyComponent />
事件
emits 选项还支持对象语法,它允许我们对触发事件的参数进行验证
<script setup >
const emit = defineEmits ({
submit (payload ) {
}
})
</script >
事件声明能让 Vue 更好地将事件和透传 attribute 作出区分,从而避免一些由第三方代码触发的自定义 DOM 事件所导致的边界情况
如果一个原生事件的名字 (例如 click) 被定义在 emits 选项中,则监听器只会监听组件触发的 click 事件而不会再响应原生的 click 事件
组件 v-model
当使用在一个组件上时,v-model 会被展开为如下的形式:
<CustomInput
:modelValue="searchText"
@update :modelValue="newValue => searchText = newValue"
/>
可以通过给 v-model 指定一个参数来更改这些名字
<MyComponent v-model:title ="bookTitle" />
组件的 v-model 上所添加的修饰符,可以通过 modelModifiers prop 在组件内访问到
在下面的组件中,我们声明了 modelModifiers 这个 prop,它的默认值是一个空对象
对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + "Modifiers"
<script setup >
const props = defineProps ({
modelValue : String ,
modelModifiers : { default : () => ({}) }
})
defineEmits (['update:modelValue' ])
console .log (props.modelModifiers )
</script >
<template >
<input
type ="text"
:value ="modelValue"
@input ="$emit('update:modelValue', $event.target.value)"
/>
</template >
透传 Attributes
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false
如果你使用了 <script setup>,你需要一个额外的 <script> 块来书写这个选项声明
<script >
// 使用普通的 <script > 来声明选项
export default {
inheritAttrs : false
}
</script >
<script setup >
</script >
从 3.3 开始你也可以直接在 <script setup> 中使用 defineOptions
<script setup >
defineOptions ({
inheritAttrs : false
})
</script >
$attrs 对象包含了除组件所声明的 props 和 emits 之外的所有其他 attribute,例如 class,style,v-on 监听器等等。
和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像 foo-bar 这样的一个 attribute 需要通过 $attrs['foo-bar'] 来访问
像 @click 这样的一个 v-on 事件监听器将在此对象下被暴露为一个函数 $attrs.onClick
注释:v-bind 也可以用于绑定事件只是要以 onClick 的方式
<div class ="btn-wrapper" >
<button class ="btn" v-bind ="$attrs" > click me</button >
</div >
可以在 <script setup> 中使用 useAttrs() API 来访问一个组件的所有透传 attribute:
<script setup >
import { useAttrs } from 'vue'
const attrs = useAttrs ()
</script >
如果没有使用 <script setup>,attrs 会作为 setup() 上下文对象的一个属性暴露
export default {
setup (props, ctx ) {
console .log (ctx.attrs )
}
}
虽然这里的 attrs 对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。你不能通过侦听器去监听它的变化。
如果你需要响应性,可以使用 prop。或者你也可以使用 onUpdated() 使得在每次更新时结合最新的 attrs 执行副作用
插槽
Vue 组件的插槽机制是受原生 Web Component <slot> 元素的启发而诞生
要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 <template> 元素,并将目标插槽的名字传给该指令:
<BaseLayout >
<template v-slot:header >
</template >
</BaseLayout >
v-slot 有对应的简写 #,因此 <template v-slot:header> 可以简写为 <template #header>
动态指令参数在 v-slot 上也是有效的,即可以定义下面这样的动态插槽名
<base-layout >
<template v-slot: [dynamicSlotName ]>
...
</template >
<template #[dynamicSlotName ]>
...
</template >
</base-layout >
默认插槽如何接受 props,通过子组件标签上的 v-slot 指令,直接接收到了一个插槽 props 对象:
<MyComponent v-slot ="slotProps" >
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent >
如果你同时使用了具名插槽与默认插槽,则需要为默认插槽使用显式的 <template> 标签。尝试直接为组件添加 v-slot 指令将导致编译错误。这是为了避免因默认插槽的 props 的作用域而困惑
一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。我们将这种类型的组件称为无渲染组件。
但大部分能用无渲染组件实现的功能都可以通过组合式 API 以另一种更高效的方式实现,并且还不会带来额外组件嵌套的开销
依赖注入
如果不使用 <script setup>,请确保 provide() 是在 setup() 同步调用的:
import { provide } from 'vue'
export default {
setup ( ) {
provide ( 'message' , 'hello!' )
}
}
除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖:
import { createApp } from 'vue'
const app = createApp ({})
app.provide ( 'message' , 'hello!' )
在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值:
注释:默认值 () => new ExpensiveClass() 在当能够查找到对应 key 的 provide 时,是不会运行的
注释:第三个参数用以声明 inject 的值是否函数
const value = inject ('key' , () => new ExpensiveClass ())
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
有的时候,我们可能需要在注入方组件中更改数据。在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数
最后,如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。
<script setup>
import { ref , provide, readonly } from 'vue'
const count = ref (0 )
provide('read-only-count' , readonly (count))
</script>
异步组件
提供了 defineAsyncComponent 方法来实现此功能
最后得到的 AsyncComp 是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数。
它会将接收到的 props 和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝地替换原始组件,同时实现延迟加载。
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent (() => {
return new Promise ((resolve, reject ) => {
resolve ()
})
})
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent (() =>
import ('./components/MyComponent.vue' )
)
异步操作不可避免地会涉及到加载和错误状态,因此 defineAsyncComponent() 也支持在高级选项中处理这些状态
在加载组件显示之前有一个默认的 200ms 延迟——这是因为在网络状况较好时,加载完成得很快,加载组件和最终组件之间的替换太快可能产生闪烁,反而影响用户感受。
const AsyncComp = defineAsyncComponent({
loader: () => import ('./Foo.vue' ),
loadingComponent: LoadingComponent,
delay: 200 ,
errorComponent: ErrorComponent,
timeout: 3000
})
异步组件可以搭配内置的 <Suspense> 组件一起使用
组合式函数
toValue() is an API added in 3.3. It is designed to normalize refs or getters into values. If the argument is a ref, it returns the ref's value; if the argument is a function, it will call the function and return its return value. Otherwise, it returns the argument as-is. It works similarly to unref(), but with special treatment for functions.
toValue()是一个在3.3版本中添加的API。它的目的是将refs或者函数标准化为值。如果参数是一个ref,它就返回ref的值;如果参数是一个函数,它就调用函数并返回其返回值。否则,它就原样返回参数。它的工作方式类似于unref(),但是对函数有特殊的处理。
import { ref, watchEffect, toValue } from 'vue'
export function useFetch (url ) {
const data = ref (null )
const error = ref (null )
watchEffect (() => {
data.value = null
error.value = null
fetch (toValue (url))
.then ((res ) => res.json ())
.then ((json ) => (data.value = json))
.catch ((err ) => (error.value = err))
})
return { data, error }
}
组合式函数约定用驼峰命名法命名,并以“use”作为开头
我们推荐的约定是组合式函数始终返回一个包含多个 ref 的普通的非响应式对象,这样该对象在组件中被解构为 ref 之后仍可以保持响应性:
在组合式函数中的确可以执行副作用 (例如:添加 DOM 事件监听器或者请求数据),但请注意以下规则:
确保在 onUnmounted() 时清理副作用。举例来说,如果一个组合式函数设置了一个事件监听器,它就应该在 onUnmounted() 中被移除
Access to an active component instance is necessary so that:组件实例化时
将计算属性和监听器注册到该组件实例上,以便在该组件被卸载时停止监听,避免内存泄漏
<script setup> 是唯一在调用 await 之后仍可调用组合式函数的地方。编译器会在异步操作之后自动为你恢复当前的组件实例。
注释:等同于 async setup() 该组件在使用时必须由 Suspense 组件包裹
VueUse :一个日益增长的 Vue 组合式函数集合。源代码本身就是一份不错的学习资料。
自定义指令
自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数
在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令
<script setup >
const vFocus = {
mounted : (el ) => el.focus ()
}
</script >
<template >
<input v-focus />
</template >
将一个自定义指令全局注册到应用层级也是一种常见的做法
const app = createApp({})
app.directive('focus' , {
})
const myDirective = {
created (el, binding, vnode, prevVnode ) {
},
beforeMount (el, binding, vnode, prevVnode ) {},
mounted (el, binding, vnode, prevVnode ) {},
beforeUpdate (el, binding, vnode, prevVnode ) {},
updated (el, binding, vnode, prevVnode ) {},
beforeUnmount (el, binding, vnode, prevVnode ) {},
unmounted (el, binding, vnode, prevVnode ) {}
}
binding:一个对象,包含以下属性
value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2。
oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"。
modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
instance:使用该指令的组件实例。
dir:指令的定义对象
和内置指令类似,自定义指令的参数也可以是动态的
除了 el 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现
仅仅需要在 mounted 和 updated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令
app.directive ('color' , (el, binding ) => {
el.style .color = binding.value
})
和 attribute 不同,指令不能通过 v-bind="$attrs" 来传递给一个不同的元素。总的来说,不推荐在组件上使用自定义指令。
插件
插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。下面是如何安装一个插件的示例
import { createApp } from 'vue'
const app = createApp ({})
app.use (myPlugin, {
})
一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装它的应用实例和传递给 app.use() 的额外选项作为参数
const myPlugin = {
install (app, options ) {
}
}
插件发挥作用的常见场景主要包括以下几种:
通过 app.component() 和 app.directive() 注册一到多个全局组件或自定义指令。
通过 app.provide() 使一个资源可被注入进整个应用。
向 app.config.globalProperties 中添加一些全局实例属性或方法
Transition
可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上
进入或离开可以由以下的条件之一触发:
由 v-if 所触发的切换
由 v-show 所触发的切换
由特殊元素 <component> 切换的动态组件
改变特殊的 key 属性
注释:插入的组件在多个中进行切换,可以直接改变插入根元素的key属性来触发动画
<Transition> 仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。
当一个 <Transition> 组件中的元素被插入或移除时,会发生下面这些事情:
Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是,则一些 CSS 过渡 class 会在适当的时机被添加和移除。
注释:如果没有过渡或动画 class 不会被添加
注释:Vue 是通过监听目标元素的 transitionend 或 animationend 事件来检测目标元素是否应用了 CSS 过渡或动画的
注释:所以只是一些 CSS 过渡 class 会在适当的时机被添加和移除,其他一些 class 只要是插入 Transition 组件就会被添加
如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用。
如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。
一共有 6 个应用于进入与离开过渡效果的 CSS class
v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
注释:Vue 的 nextFrame 方法是基于浏览器的 requestAnimationFrame API 实现的,这个 API 可以让你在浏览器的下一个动画帧执行一些操作
v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
v-enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
v-leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。
可以给 <Transition> 组件传一个 name prop 来声明一个过渡效果名:
<Transition> 的 props (比如 name) 也可以是动态的
原生 CSS 动画和 CSS transition 的应用方式基本上是相同的,只有一点不同,那就是 *-enter-from 不是在元素插入后立即移除,而是在一个 animationend 事件触发时被移除
可以向 <Transition> 传递以下的 props 来指定自定义的过渡 class
enter-from-class
enter-active-class
enter-to-class
leave-from-class
leave-active-class
leave-to-class
Vue 需要附加事件监听器,以便知道过渡何时结束。可以是 transitionend 或 animationend,这取决于你所应用的 CSS 规则。如果你仅仅使用二者的其中之一,Vue 可以自动探测到正确的类型。
此时你需要显式地传入 type prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 animation 或 transition
可以通过向 <Transition> 组件传入 duration prop 来显式指定过渡的持续时间 (以毫秒为单位)。总持续时间应该匹配延迟加上内部元素的过渡持续时间
注释:vue是通过嵌套根元素的 transitionend 或 animationend 事件来检测动画是否结束的,当动画不在根元素时可以通过传递过渡持续时间来控制
可以用对象的形式传入,分开指定进入和离开所需的时间
<Transition :duration ="{ enter: 500, leave: 800 }" > ...</Transition >
可以通过监听 <Transition> 组件事件的方式在过渡过程中挂上钩子函数
<Transition
@before-enter ="onBeforeEnter"
@enter ="onEnter"
@after-enter ="onAfterEnter"
@enter-cancelled ="onEnterCancelled"
@before-leave ="onBeforeLeave"
@leave ="onLeave"
@after-leave ="onAfterLeave"
@leave-cancelled ="onLeaveCancelled"
>
</Transition >
function onBeforeEnter (el) {}
function onEnter (el, done) {
done ()
}
function onAfterEnter (el) {}
function onEnterCancelled (el) {}
function onBeforeLeave (el) {}
function onLeave (el, done) {
done ()
}
function onAfterLeave (el) {}
function onLeaveCancelled (el) {}
在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css="false" prop
如果你想在某个节点初次渲染时应用一个过渡效果,你可以添加 appear prop
很多情况下这可能并不符合需求。我们可能想要先执行离开动画,然后在其完成之后再执行元素的进入动画。手动编排这样的动画是非常复杂的,好在我们可以通过向 <Transition> 传入一个 mode prop 来实现这个行为
也支持 mode="in-out",虽然这并不常用。
<Transition mode ="out-in" >
...
</Transition >
TransitionGroup
默认情况下,它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。
过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换
注释:过渡模式是指 model 这个 attribute 实现的先离开动画后进入动画的功能
列表中的每个元素都必须有一个独一无二的 key attribute
注释:通过 key 去判断哪个元素被删除、增加和移动的
当某一项被插入或移除时,它周围的元素会立即发生“跳跃”而不是平稳地移动。我们可以通过添加一些额外的 CSS 规则来解决这个问题:
.list-move ,
.list-enter-active ,
.list-leave-active {
transition : all 0.5s ease;
}
.list-enter-from ,
.list-leave-to {
opacity : 0 ;
transform : translateX (30px );
}
.list-leave-active {
position : absolute;
}
通过在 JavaScript 钩子中读取元素的 data attribute,我们可以实现带渐进延迟的列表动画
注释:通过元素的 data attribute 绑定每个元素特定的数据,然后在过渡方法中通过 el 获取,来为每个元素创建独立的效果
注释:
<TransitionGroup
tag="ul"
:css="false"
@before -enter="onBeforeEnter"
@enter ="onEnter"
@leave ="onLeave"
>
<li
v-for ="(item, index) in computedList"
:key ="item.msg"
:data-index ="index"
>
{{ item.msg }}
</li >
</TransitionGroup >
function onEnter (el, done ) {
gsap.to (el, {
opacity : 1 ,
height : '1.6em' ,
delay : el.dataset .index * 0.15 ,
onComplete : done
})
}
KeepAlive
注释:KeepAlive 保留了原生 dom 结构
<KeepAlive> 默认会缓存内部的所有组件实例,但我们可以通过 include 和 exclude prop 来定制该行为
这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组:
<KeepAlive include ="a,b" >
<component :is ="view" />
</KeepAlive >
<KeepAlive :include ="/a|b/" >
<component :is ="view" />
</KeepAlive >
<KeepAlive :include ="['a', 'b']" >
<component :is ="view" />
</KeepAlive >
在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,无需再手动声明。
onActivated 在组件挂载时也会调用,并且 onDeactivated 在组件卸载时也会调用
这两个钩子不仅适用于 <KeepAlive> 缓存的根组件,也适用于缓存树中的后代组件。
Teleport
将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去
<Teleport> 接收一个 to prop 来指定传送的目标。to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body 标签下”
<button @click ="open = true" > Open Modal</button >
<Teleport to ="body" >
<div v-if ="open" class ="modal" >
<p > Hello from the modal!</p >
<button @click ="open = false" > Close</button >
</div >
</Teleport >
<Teleport> 挂载时,传送的 to 目标必须已经存在于 DOM 中。
注释:
挂载时,Teleport组件会先创建一个占位符节点,用来标记传送内容的原始位置。
然后,Teleport组件会根据to属性找到目标节点,并将传送内容插入到目标节点的最后。
如果disabled属性为true,那么传送内容就不会插入到目标节点,而是保留在占位符节点的后面。
在某些场景下可能需要视情况禁用 <Teleport>。举例来说,我们想要在桌面端将一个组件当做浮层来渲染,但在移动端则当作行内组件。我们可以通过对 <Teleport> 动态地传入一个 disabled prop 来处理这两种不同情况。
这里的 isMobile 状态可以根据 CSS media query 的不同结果动态地更新
<Teleport :disabled ="isMobile" >
...
</Teleport >
Suspense
注释:setup 内允许使用 await,但在父组件中使用时必须由 Suspense 包裹
<Suspense >
└─ <Dashboard >
├─ <Profile >
│ └─ <FriendStatus > (组件有异步的 setup())
└─ <Content >
├─ <ActivityFeed > (异步组件)
└─ <Stats > (异步组件)
有了 <Suspense> 组件后,我们就可以在等待整个多层级组件树中的各个异步依赖获取结果时,在顶层展示出加载中或加载失败的状态
可以等待的异步依赖有两种
带有异步 setup() 钩子的组件。这也包含了使用 <script setup> 时有顶层 await 表达式的组件
异步组件
在这种情况下,加载状态是由 <Suspense> 控制,而该组件自己的加载、报错、延时和超时等选项都将被忽略
<Suspense> 组件有两个插槽:#default 和 #fallback。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点
注释:当异步依赖都完成时就会显示默认槽中的节点,否则就会显示后备槽 #fallback 中的节点
<Suspense >
<Dashboard />
<template #fallback >
Loading...
</template >
</Suspense >
在初始渲染时,<Suspense> 将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后,<Suspense> 会进入完成状态,并将展示出默认插槽的内容。
进入完成状态后,只有当默认插槽的根节点被替换时,<Suspense> 才会回到挂起状态。
发生回退时,后备内容不会立即展示出来。相反,<Suspense> 在等待新内容和异步依赖完成时,会展示之前 #default 插槽的内容。这个行为可以通过一个 timeout prop 进行配置:在等待渲染新内容耗时超过 timeout 之后,<Suspense> 将会切换为展示后备内容。若 timeout 值为 0 将导致在替换默认内容时立即显示后备内容。
组件会触发三个事件:pending、resolve 和 fallback。pending 事件是在进入挂起状态时触发。resolve 事件是在 default 插槽完成获取新内容时触发。fallback 事件则是在 fallback 插槽的内容显示时触发。
组件自身目前还不提供错误处理,不过你可以使用 errorCaptured 选项或者 onErrorCaptured() 钩子,在使用到 <Suspense> 的父组件中捕获和处理异步错误
注释:errorCaptured 选项是 Vue 2.5.0 新增的一个生命周期钩子,它可以让你在组件中捕获来自子孙组件的错误,并进行相应的处理
注释:当前组件 template 中的组件是当前组件的子组件,可以通过当前组件的 errorCaptured 选项捕获这些子组件的错误
RouterView v-slot作用域插槽拿到的是被渲染的组件
常常会将 <Suspense> 和 <Transition>、<KeepAlive> 等组件结合
注释:KeepAlive组件允许并列插入多个动态组件,它们都会被缓存
<RouterView v-slot ="{ Component }" >
<template v-if ="Component" >
<Transition mode ="out-in" >
<KeepAlive >
<Suspense >
<component :is ="Component" > </component >
<template #fallback >
正在加载...
</template >
</Suspense >
</KeepAlive >
</Transition >
</template >
</RouterView >
Vue Router 使用动态导入对懒加载组件进行了内置支持。这些与异步组件不同,目前他们不会触发 <Suspense>。但是,它们仍然可以有异步组件作为后代,这些组件可以照常触发 <Suspense>。
注释:全局路由守卫 router.beforeEach 和 router.beforeResolve
单文件组件
Vue SFC 是一个框架指定的文件格式,因此必须交由 @vue/compiler-sfc 编译为标准的 JavaScript 和 CSS,一个编译后的 SFC 是一个标准的 JavaScript(ES) 模块
工具链
若要了解如何为一个 Vite 项目配置 Vue 相关的特殊行为,比如向 Vue 编译器传递相关选项,请查看 @vitejs/plugin-vue 的文档。
从 Vue CLI 迁移到 Vite 的资源
前缀为 vue.runtime.* 的文件是只包含运行时的版本:不包含编译器,当使用这个版本时,所有的模板都必须由构建步骤预先编译。
如果因为某些原因,在有构建步骤时,你仍需要浏览器内的模板编译,你可以更改构建工具配置,将 vue 改为相应的版本 vue/dist/vue.esm-bundler.js
推荐使用的 IDE 是 VSCode,配合 Vue 语言特性 (Volar) 插件。该插件提供了语法高亮、TypeScript 支持,以及模板内表达式与组件 props 的智能提示
Vue 的浏览器开发者插件使我们可以浏览一个 Vue 应用的组件树,查看各个组件的状态,追踪状态管理的事件,还可以进行组件性能分析。
使用 vue-tsc 可以在命令行中执行相同的类型检查,通常用来生成单文件组件的 d.ts 文件。
Cypress 推荐用于 E2E 测试。也可以通过 Cypress 组件测试运行器 来给 Vue SFC 作单文件组件测试。
Vitest 是一个追求更快运行速度的测试运行器,由 Vue / Vite 团队成员开发。主要针对基于 Vite 的应用设计,可以为组件提供即时响应的测试反馈。
注释:即时响应的测试反馈是指,当你修改了组件的代码或者测试用例时,Vitest 可以立即重新运行相关的测试,并显示测试结果和覆盖率。这样,你就可以快速地检查你的代码是否符合预期,而不需要等待传统的测试运行器重新编译和执行整个测试套件。
Jest 可以通过 vite-jest 配合 Vite 使用。不过只推荐在你已经有一套基于 Jest 的测试集、且想要迁移到基于 Vite 的开发配置时使用,因为 Vitest 也能够提供类似的功能,且后者与 Vite 的集成更方便高效。
启用类似 lint-staged 一类的工具在 git commit 提交时自动执行规范检查。
Prettier 也提供了内置的 Vue SFC 格式化支持
自定义块被编译成导入到同一 Vue 文件的不同请求查询。这取决于底层构建工具如何处理这类导入请求。
@vue/compiler-sfc
@vitejs/plugin-vue
vue-loader
其他在线演练场
状态管理
为了确保改变状态的逻辑像状态本身一样集中,建议在 store 上定义方法,方法的名称应该要能表达出行动的意图:
import { reactive } from 'vue'
export const store = reactive ({
count : 0 ,
increment ( ) {
this .count ++
}
})
请注意这里点击的处理函数使用了 store.increment(),带上了圆括号作为内联表达式调用,因为它并不是组件的方法,并且必须要以正确的 this 上下文来调用。
<template >
<button @click ="store.increment()" >
From B: {{ store.count }}
</button >
</template >
Pinia
在大规模的生产应用中还有很多其他事项需要考虑:
与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试
注释:时间轴:这是一个显示你的应用程序中发生的各种事件和活动的图形界面,你可以在时间轴上查看和过滤不同类型的事件,如组件更新、用户交互、路由变化、性能指标等。你也可以在时间轴上选择某个事件,查看它发生时的应用程序状态和快照
注释:组件内部审查,就是查看组件树和属性
注释:时间旅行调试:这是一个让你在时间上前进或后退,重现或回溯你的应用程序状态的工具,你可以使用暂停、播放、快进、快退等按钮,控制应用程序的执行流程。你也可以使用滑块或输入框,跳转到某个特定的时间点
模块热更新 (HMR)
服务端渲染支持
相比于 Vuex,Pinia 提供了更简洁直接的 API,并提供了组合式风格的 API,最重要的是,在使用 TypeScript 时它提供了更完善的类型推导
测试
当设计你的 Vue 应用的测试策略时,你应该利用以下几种测试类型:
单元测试:检查给定函数、类或组合式函数的输入是否产生预期的输出或副作用。
组件测试:检查你的组件是否正常挂载和渲染、是否可以与之互动,以及表现是否符合预期。这些测试比单元测试导入了更多的代码,更复杂,需要更多时间来执行。
端到端测试:检查跨越多个页面的功能,并对生产构建的 Vue 应用进行实际的网络请求。这些测试通常涉及到建立一个数据库或其他后端。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现