v3介绍
v3分为options api(选项式 API)和composition api(组合式 API)
options api还是和v2一样的写法,有着data、methods、mounted等,还是用this指向当前组件实例
<script>
export default {
// data() 返回的属性将会成为响应式的状态
// 并且暴露在 `this` 上
data() {
return {
count: 0
}
},
// methods 是一些用来更改状态与触发更新的函数
// 它们可以在模板中作为事件处理器绑定
methods: {
increment() {
this.count++
}
},
// 生命周期钩子会在组件生命周期的各个不同阶段被调用
// 例如这个函数就会在组件挂载完成后被调用
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
composition api使用导入api的方式,通常与<script setup>搭配使用
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
创建vue3应用
请确保你安装了 18.3 或更高版本的 Node.js
推荐使用 nvs 来切换node版本,个人使用感觉比nvm好用
npm create vue@latest
同名简写
<!-- 与 :id="id" 相同 -->
<div :id></div>
动态参数
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>
声明响应式状态
ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:
import { ref } from 'vue'
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
template中ref定义的值不需要.value使用,script中的使用需要.value
Q:为什么ref包裹的需要使用.value?
A:在标准的 JavaScript 中,检测普通变量的访问或修改是行不通的。然而,我们可以通过 getter 和 setter 方法来拦截对象属性的 get 和 set 操作。所以需要将一个值变量转变为对象的形式,才能去监听它的变化
reactive()
import { reactive } from 'vue'
const state = reactive({ count: 0 })
<button @click="state.count++">
{{ state.count }}
</button>
值得注意的是,reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的
const raw = {}
const proxy = reactive(raw)
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
事件处理
有时我们需要在内联事件处理器中访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的 $event 变量,或者使用内联箭头函数
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>
function warn(message, event) {
// 这里可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
监听watch
watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
const x = ref(0)
const y = ref(0)
const computedX = computed(()=>x.value)
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// computed监听
watch(computedX, (newX) => {
console.log(`computedx is ${newX}`)
})
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
- 你不能直接监听响应式对象的属性值,比如
const obj = reactive({ count: 0 })
// 错误,因为 watch() 得到的参数是一个 number ref定义的复杂类型也一样
watch(obj.count, (count) => {
console.log(`Count is: ${count}`)
})
-你可以用computed或者getter函数的方式来监听对象中的属性值变化,getter函数如果返回的是一个对象,那对象内部的属性变化会监听不到,除非整个对象被替换了,这时候可以加上{deep:true}来深层监听
可以配置immediate:true来让watch立即执行一遍
watch(
source,
(newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
},
{ immediate: true }
)
watchEffect会立即执行,不需要传入监听的变量,直接监听回调中的所有变量
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
监听的回调在父组件更新之后,所属组件的dom更新之前,所以想要拿到所属组件的dom属性,拿到的是更新前的数值
如果想在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM,你需要指明 flush: 'post' 选项,或者直接用watchPostEffect,类似于watchEffect,但它在vue更新后执行
模板引用
ref来获取元素,变量与ref必须同名
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
a,
b
})
</script>
props与emit
在template中可以直接使用,在script中需要声明,然后使用
const props = defineProps(['title','open'])
defineProps({
title: String,
likes: Number
})
console.log(props.title)
<script setup>
const emit = defineEmits(['enlarge-text','update:open'])
emit('enlarge-text')
//如果传值是v-model:open='open'这种形式,可以直接upate:open的emit方式进行更新值,在子组件中
emit('update:open', false);
</script>
v-model
父组件使用v-model时,子组件可以用defineModel()接受,它和父组件双向绑定同步
<!-- Parent.vue -->
<Child v-model="countModel" />
<!-- Child.vue -->
<script setup>
const model = defineModel()
function update() {
model.value++
}
</script>
<template>
<div>Parent bound v-model is: {{ model }}</div>
</template>
还可以传参
<MyComponent v-model:title="bookTitle" />
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
处理 v-model 修饰符
在某些场景下,你可能想要一个自定义组件的 v-model 支持自定义的修饰符
<MyComponent v-model.capitalize="myText" />
<script setup>
const [model, modifiers] = defineModel()
console.log(modifiers) // { capitalize: true }
</script>
<template>
<input type="text" v-model="model" />
</template>
为了能够基于修饰符选择性地调节值的读取和写入方式,我们可以给 defineModel() 传入 get 和 set 这两个选项。这两个选项在从模型引用中读取或设置值时会接收到当前的值,并且它们都应该返回一个经过处理的新值。下面是一个例子,展示了如何利用 set 选项来应用 capitalize (首字母大写) 修饰符
<script setup>
const [model, modifiers] = defineModel({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<template>
<input type="text" v-model="model" />
</template>
透传 Attributes
父组件
<!-- <MyButton> 的模板 -->
<button class="btn">Click Me</button>
子组件
// 这个 `$attrs` 对象包含了除组件所声明的 `props` 和 `emits` 之外的所有其他 attribute,例如 `class`,`style`,`v-on` 监听器等等。
<span>Fallthrough attribute: {{ $attrs }}</span>
没有参数的 v-bind 会将一个对象的所有属性都作为 attribute 应用到目标元素上。
// 子组件
<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>
注意,attrs对象内容都是最新的,但是不能通过watch监听它的变化,如果需要相应,可以用prop,也可以在onUpated中获取attr
异步组件
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
不管是全局注册时还是父组件调用子组件时都可以这么用
全局注册
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
父组件调用子组件
<script setup>
import { defineAsyncComponent } from 'vue'
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
<template>
<AdminPage />
</template>
组合式函数,以后可以回去再好好品味
可以在函数中使用vue的生命周期和定义响应式变量,返回响应式变量
自定义指令
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。
在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。
在没有使用 <script setup> 的情况下,自定义指令需要通过 directives 选项注册
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
将一个自定义指令全局注册到应用层级也是一种常见的做法:
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})
一个指令的定义对象可以提供几种钩子函数 (都是可选的):
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
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) {}
}
指令的钩子会传递以下几种参数:
-
el:指令绑定到的元素。这可以用于直接操作 DOM。 -
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:指令的定义对象。
-
vnode:代表绑定元素的底层 VNode。 -
prevVnode:代表之前的渲染中指令所绑定元素的 VNode。仅在beforeUpdate和updated钩子中可用。
对于自定义指令来说,一个很常见的情况是仅仅需要在 mounted 和 updated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令,如下所示:
<div v-color="color"></div>
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
插件
一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装它的应用实例和传递给 app.use() 的额外选项作为参数:
const myPlugin = {
install(app, options) {
// 配置此应用
}
}
// main.js
import { createApp } from 'vue'
const app = createApp({})
app.use(myPlugin, {
/* 可选的选项 */
})
示例:
// plugins/i18n.js
export default {
install: (app, options) => {
// 注入一个全局可用的 $translate() 方法
app.config.globalProperties.$translate = (key) => {
// 获取 `options` 对象的深层属性
// 使用 `key` 作为索引
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
}
}
KeepAlive 缓存
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
<KeepAlive> 默认会缓存内部的所有组件实例,但我们可以通过 include 和 exclude prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组,也可以传入max来限制最大缓存数,超过时将最久远的缓存组件销毁
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b" :max="10">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
一个持续存在的组件可以通过 onActivated() 和 onDeactivated() 注册相应的两个状态的生命周期钩子:
<script setup>
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 调用时机为首次挂载
// 以及每次从缓存中被重新插入时
})
onDeactivated(() => {
// 在从 DOM 上移除、进入缓存
// 以及组件卸载时调用
})
</script>
- 这两个钩子不仅适用于
<KeepAlive>缓存的根组件,也适用于缓存树中的后代组件。
Teleport
<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 prop 来指定传送的目标。to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body 标签下”。