安装
npm init vue@latest
这一指令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具。
前提:node版本需要在15以上,选择Yes和no时候 按空格切换选择
创建实例
<div id="app">
<button @click="count++">{{ count }}</button>
</div>
import { createApp } from 'vue'
const app = createApp({
data() {
return {
count: 0
}
}
})
app.mount('#app')
模板语法
原始html ,使用v-html
指令(请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容,避免出现xss漏洞)
布尔类型:<button :disabled="isButtonDisabled">Button</button>
当 isButtonDisabled 为真值或一个空字符串 (即 <button disabled="">) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略。(这里经过尝试 设置为null或者undefined 则disabed为false,不会显示)
缩写(常用)
<!-- 完整语法 -->
<a v-on:click="doSomething"> ... </a>
<!-- 缩写 -->
<a @click="doSomething"> ... </a>
<!-- 完整语法 -->
<a v-bind:href="url"> ... </a>
<!-- 缩写 -->
<a :href="url"> ... </a>
调用函数
可以在绑定的表达式中使用一个组件暴露的方法
<span :title="toTitleDate(date)">
{{ formatDate(date) }}
</span>
注意:绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用,比如改变数据或触发异步操作。
JavaScript 表达式
模板表达式都被放在沙盒中,只能访问一个受限的全局变量列表,如 Math 和 Date。你不应该在模板表达式中试图访问用户定义的全局变量。
其中受限的全局变量列表如下:
const GLOBALS_WHITE_LISTED = 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' + 'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' + 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt'
没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window 上的属性。然而,你也可以自行在 app.config.globalProperties 上显式地添加它们,供所有的 Vue 表达式使用。
如:app.config.globalProperties.msg = 'hello'
动态参数 (New)
<a v-on:[eventName]="doSomething"> ... </a>
在这个示例中,当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus
动态参数表达式约定:要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写。
注意事项:
动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。
如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,切记不用字符串拼接形式
修饰符
修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault():
<form @submit.prevent="onSubmit">...</form>
响应式基础
响应式对象其实是 JavaScript Proxy,其行为表现与一般对象相似。不同之处在于 Vue 能够跟踪对响应式对象属性的访问与更改操作。
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 以下都会按照期望工作
obj.nested.count++
obj.arr.push('baz')
}
响应式 reactive()
reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的
对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身 (代理对象本身)
reactive对象中属性值为引用类型的,当访问该属性时,得到也是一个Proxy代理对象(深层次响应性)
限制:仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效
当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性
如:
const state = reactive({
count: 0,
person: {
name: 'zhang san',
age: 20
}
})
// n 是一个局部变量,同 state.count 失去响应性连接
let n = state.count
// 不影响原始的 state
n++
// count 也和 state.count 失去了响应性连接
//person 指向 state中person属性的代理对象
let { count, person } = state
// 不会影响原始的 state
count++
//person 为引用 所以会改变state中person对象的age值
person.age = 100
//{"count":0,"person":{"name":"zhang san","age":100}}
console.log("now state==>",JSON.stringify(state))
//如果直接赋值给person新的值, 则引用丢失,但并不会影响state
用 ref() 定义响应式变量
ref() 方法来允许我们创建可以使用任何值类型的响应式 ref
ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象
ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用 reactive() 自动转换它的 .value。
ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性(经常用于将逻辑提取到组合函数中)
const obj = {
foo: ref(1),
bar: ref(2)
}
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)
// 仍然是响应式的
const { foo, bar } = obj
ref 在模板中的解包
当 ref 在模板中作为顶层属性被访问时,它们会被自动解包,所以不需要使用 .value(适用于顶层属性)
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- 无需 .value -->
</button>
</template>
ref在响应式对象中的解包
当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
数组和集合类型的 ref 解包
当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
最佳实践
使用 Vue 的响应式系统的最佳实践是仅使用你声明对象的代理版本
响应式函数API工具
isRef()
:检查某个值是否为 ref
unref()
:如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。
toRef()
:基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。(结构解析响应不会丢失)
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
//更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2 ref会自动解包
toRefs()
:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
const stateAsRefs = toRefs(state)
// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2
isProxy()
:检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
isReactive()
:检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
响应性语法糖(实验性功能
)
$ref()
方法是一个编译时的宏命令
<script setup>
let count = $ref(0)
console.log(count)
function increment() {
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
代码会编译成
import { ref } from 'vue'
let count = ref(0)
console.log(count.value)
function increment() {
count.value++
}
每一个会返回 ref 的响应式 API 都有一个相对应的、以 $ 为前缀的宏函数。包括以下这些 API:
ref -> $ref
computed -> $computed
shallowRef -> $shallowRef
customRef -> $customRef
toRef -> $toRef
响应性语法糖目前默认是关闭状态,需要你显式选择启用。此外,以下列出的所有配置都需要 vue@^3.2.25。
vite脚手架启用
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
或组件中显式引用
import { $ref } from 'vue/macros'
let count = $ref(0)
DOM 更新时机
Vue 将缓冲它们直到更新周期的 “下个时机” 以确保无论你进行了多少次声明更改,每个组件都只需要更新一次。
若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API
computed 计算属性
其返回值为一个计算属性 ref
如:
<script setup>
import { ref, computed } from 'vue'
const numbers = ref([1, 2, 3, 4, 5])
//常用 获取数组中的偶数
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
计算属性和方法
计算属性将基于它们的响应依赖关系缓存。计算属性只会在相关响应式依赖发生改变时重新求值。
如果你不希望有缓存,请用 method 来替代。
最佳实践
不要在计算函数中做异步请求或者更改 DOM
避免直接修改计算属性值(值是只读的)
侦听器(watch)
watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组。
需要注意的地方:watch ref 基本类型 其数值发生变化 watch能侦测到 且newValue, oldValue不同,若是数组、对象等引用数据类型,只有将整个替换才会侦测到,如果替换里面的属性等部分内容,需要加deep:true强制转成深层侦听器,这样才能检测到。
举例两种最常用的用法:
// 单个 ref
const x = ref(0)
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
//侦测响应式对象
const obj = reactive({ count: 0 })
// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
// 这种写法错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
//特殊情况举例:
const obj = ref({ count: 0 })
watch(obj, (newValue, oldValue) => {
})
setTimeout(() => {
obj.value.count++ //这种情况检测不到,需要将整个对象替换才行 如:obj.value = { count: obj.value.count + 1 } 或者 设置deep:true就能检测到
//如果将定义改成 const obj = reactive({ count: 0 }) obj.count++ 也是能检测到的,这里需要注意下
}, 5000);
深层次侦测器
直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
})
一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调,或显式地加上 deep 选项,强制转成深层侦听器
watch(
() => state.someObject,
(newValue, oldValue) => {
// 注意:`newValue` 此处和 `oldValue` 是相等的
// *除非* state.someObject 被整个替换了
},
{ deep: true }
)
注意事项:深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能。
watchEffect
会立即执行一遍回调,然后在相关状态更改时重新执行回调,如:
const url = ref('https://...')
const data = ref(null)
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
注意:watchEffect 仅会在其同步执行期间,才追踪依赖
侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
}, 100)
Class和Style绑定
绑定 HTML class
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }" //对象形式 最常用
></div>
const isActive = ref(true)
const hasError = ref(false)
<div :class="classObject"></div> <!---绑定的是对象-->
const classObject = reactive({
active: true,
'text-danger': false
})
使用计算属性 (也很常用)
const isActive = ref(true)
const error = ref(null)
const classObject = computed(() => ({
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal'
}))
使用数组
<div :class="[isActive ? activeClass : '', errorClass]"></div>
const activeClass = ref('active')
const errorClass = ref('text-danger')
在数组中使用对象语法
<div :class="[{ active: isActive }, errorClass]"></div>
在组件中使用
对于只有一个根元素的组件,当你使用了 class attribute 时,这些 class 会被添加到根元素上,并与该元素上已有的 class 合并。
如果你的组件有多个根元素,你将需要指定哪个根元素来接收这个 class。你可以通过组件的 $attrs
属性来实现指定:如
<MyComponent class="baz" />
<!-- MyComponent 模板使用 $attrs 时 -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
内联样式
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
或者绑定成对象
<div :style="styleObject"></div>
const styleObject = reactive({
color: 'red',
fontSize: '13px'
})
使用数组绑定多个内联样式(包含多个样式对象的数组)
<div :style="[baseStyles, overridingStyles]"></div>
条件渲染和列表渲染
v-if、v-else、v-else-if(常用)
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
在template中使用v-if,是不可见的包装器元素,最后渲染的结果并不会包含这个 <template> 元素
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
</template>
v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。
v-if VS v-show
v-if 是“真正”的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
v-for 中遍历对象
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它在不同 JavaScript 引擎下的结果都一致。
注意事项:不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。
在 v-for 里使用值范围
v-for 可以直接接受一个整数值。在这种用例中,会将该模板基于 1...n 的取值范围重复多次。
<span v-for="n in 10">{{ n }}</span>(注意此处 n 的初值是从 1 开始而非 0)
与模板上的 v-if 类似,你也可以在 <template> 标签上使用 v-for 来渲染一个包含多个元素的块。
在组件中使用v-for
<ul>
<todo-item
v-for="(todo, index) in todos"
:key="todo.id"
:title="todo.title"
@remove="todos.splice(index, 1)"
></todo-item>
</ul>
<!--子组件 使用$emit触发父组件的自定义事件-->
app.component('todo-item', {
template: `
<li>
{{ title }}
<button @click="$emit('remove')">Remove</button>
</li>
`,
props: ['title'],
emits: ['remove']
})
v-if 与 v-for 一起使用
不推荐同时使用 v-if 和 v-for。当 v-if 与 v-for 一起使用时,v-if 具有比 v-for 更高的优先级
当它们处于同一节点,v-if 的优先级比 v-for 更高,这意味着 v-if 将没有权限访问 v-for 里的变量
可以把 v-for 移动到 <template> 标签中来修正:
<template v-for="todo in todos" :key="todo.name">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
数组的更新检测
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()、pop()、shift()、unshift()、splice()、sort()、reverse() (最后两个会变更原数组)
替换数组(如:filter()、concat() 和 slice()它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组)
example1.items = example1.items.filter(item => item.message.match(/Foo/))
模板引用
可以使用 ref attribute 为子组件或 HTML 元素指定引用 ID
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
只可以在组件挂载后才能访问模板引用
如果你需要侦听一个模板引用 ref 的变化,确保考虑到其值为 null 的情况
watchEffect(() => {
if (input.value) {
input.value.focus()
} else {
// 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
}
})
在选项api中的使用
<base-input ref="usernameInput"></base-input>
访问时:this.$refs.usernameInput.focusInput()
//其中focusInput为mounted的方法
$refs
只会在组件渲染完成之后生效。这仅作为一个用于直接操作子元素的“逃生舱”——你应该避免在模板或计算属性中访问 $refs
。
v-for 中的模板引用(ref是数组)
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([
/* ... */
])
const itemRefs = ref([]) //得到的是一个数组,不能保证与源数组顺序一致
onMounted(() => console.log(itemRefs.value))
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
组件中ref
可以使用ref拿到子组件实例,但是默认在<script setup>定义的内容都是私有的,如需暴露给父组件,需要使用defineExpose 定义,不过大部分情况下,我们都使用emit和props完成组件交互操作