1. Props 声明
- 声明类型
// 使用 <script setup>
defineProps({
title: String,
likes: Number
})
- 传递 prop 的细节
如果一个 prop 的名字很长,应使用 camelCase 形式, 传递时为了和 HTML attribute 对齐,我们通常会将其写为 kebab-case 形式
defineProps({
greetingMessage: String
})
<MyComponent greeting-message="hello" />
- 任何类型的值都可以作为 props 的值被传递。
- 使用一个对象绑定多个 prop
const post = {
id: 1,
title: 'My Journey with Vue'
}
<BlogPost v-bind="post" />
# 实际上等价于
<BlogPost :id="post.id" :title="post.title" />
- 单向数据流
不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告, - Boolean 类型转换
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />
<!-- 等同于传入 :disabled="false" -->
<MyComponent />
2. 事件
- 触发与监听事件, 直接使用 $emit 方法触发自定义事件
<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>
<MyComponent @some-event="callback" />
# 事件监听器也支持 .once 修饰符:
<MyComponent @some-event.once="callback" />
- 事件参数, 直接使用 $emit 方法触发自定义事件, 传递参数
<button @click="$emit('increaseBy', 1)">
Increase by 1
</button>
# 内联方法
<MyButton @increase-by="(n) => count += n" />
#或者调用独立的方法
<MyButton @increase-by="increaseCount" />
function increaseCount(n) {
count.value += n
}
- 事件校验
<script setup>
const emit = defineEmits({
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', { email, password })
}
</script>
3. 组件v-model
- 多个 v-model 双向绑定
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
<script setup>
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
-
v-model 修饰符
.trim,.number 和 .lazy,或者自定义的修饰符 -
自定义修饰符
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
# 绑定
<MyComponent v-model:title.capitalize="myText">
4. 透传 Attributes
- Attributes 继承
当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上。
<!-- <MyButton> 的模板 -->
<button>click me</button>
<MyButton class="large" />
# 最后渲染的结果是
<button class="large">click me</button>
- 对 class 和 style 的合并
如果一个子组件的根元素已经有了 class 或 style attribute,它会和从父组件上继承的值合并。 - v-on 监听器继承
<MyButton @click="onClick" />
button 被点击,会触发父组件的 onClick 方法。同样的,如果原生 button 元素自身也通过 v-on 绑定了一个事件监听器,则这个监听器和从父组件继承的监听器都会被触发。
4. 深层组件继承
一个组件会在根节点上渲染另一个组件, 接收的透传 attribute 会直接继续传给下一层子节点
5. 禁用 Attributes 继承
不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false。
6. 这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到。
- 在 JavaScript 中访问透传 Attributes
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
5. 插槽 Slots
- 基础使用
# 定义FancyButton
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
<FancyButton>
<span style="color:red">Click me!</span>
<AwesomeIcon name="plus" />
</FancyButton>
- 作用域
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。 - 具名插槽
# BaseLayout.vue
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<style>
footer {
border-top: 1px solid #ccc;
color: #666;
font-size: 0.8em;
}
</style>
# App.vue
<script setup>
import BaseLayout from './BaseLayout.vue'
</script>
<template>
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
</template>
- 动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
- 插槽作用域, 访问子组件数据
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
#定义组件
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const x = ref(0)
const y = ref(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>
<template>
<slot :x="x" :y="y"/>
</template>
# 渲染
<template>
<MouseTracker v-slot="{ x, y }">
Mouse is at: {{ x }}, {{ y }}
</MouseTracker>
</template>
6. 依赖注入
- 从父组件向子组件传递数据时,会使用 props, 为了解决多层组件传递数据链路较长的问题, 引入依赖注入
- Provide (提供)
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
# 我们还可以在整个应用层面提供依赖
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
- Inject (注入)
<script setup>
import { inject } from 'vue'
const message = inject('message', '这是默认值')
</script>
- 和响应式数据配合使用
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
如果你想确保提供的数据不能被注入方的组件更改,你可以使用 readonly() 来包装提供的值。
7. 异步组件
- 基本用法
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
- 加载与错误状态
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
8.组合式函数
- 实际上就是封装一个方法, 暴露给外部使用
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'
export function useMouse() {
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', (event) => {
x.value = event.pageX
y.value = event.pageY
})
return { x, y }
}
- 异步状态返回
// fetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
fetch(url)
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
return { data, error }
}
# 使用方
<script setup>
import { useFetch } from './fetch.js'
const { data, error } = useFetch('...')
</script>
9. 自定义指令
- 自定义指令由一个包含类似组件生命周期钩子的对象来定义。
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
- 常用指令钩子
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。
prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
- 对象字面量
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
10. 插件
- 插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码
import { createApp } from 'vue'
const app = createApp({})
app.use(myPlugin, {
/* 可选的选项 */
})
const myPlugin = {
install(app, options) {
// 配置此应用
}
}
- 插件没有严格定义的使用范围,但是插件发挥作用的常见场景主要包括以下几种:
- 通过 app.component() 和 app.directive() 注册一到多个全局组件或自定义指令。
- 通过 app.provide() 使一个资源可被注入进整个应用。
- 向 app.config.globalProperties 中添加一些全局实例属性或方法
- 一个可能上述三种都包含了的功能库 (例如 vue-router)。
- 编写一个插件
实在没看懂
11. 内置组件
- Transition / TransitionGroup 动画, 详见地址
- KeepAlive, 在被“切走”的时候保留它们的状态
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
- KeepAlive 默认会缓存内部的所有组件实例,但我们可以通过 include 和 exclude prop 来定制该行为
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
- 最大缓存实例数
<KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive>
- 缓存实例的生命周期