事件处理
当我们需要访问内联处理程序中的原始 DOM 事件 可以用特殊变量将其传递给方法$event 或者使用内联箭头函数
1 | <button @click="warn('Form cannot be submitted yet.', $event)"> |
表单上输入绑定
<input>
文本类型和<textarea>
元素使用value
属性和input
事件;<input type="checkbox">``<input type="radio">
使用checked
和事件change
;<select>
用作value
和change
事件。
v-model将忽略在任何表单元素上找到的初始或属性
value。它将始终将当前绑定的 JavaScript 状态视为事实来源。您应该使用反应性 API在 JavaScript 端声明初始值。
checked``selected
如果您的表达式的初始值v-model
与任何选项都不匹配,则该<select>
元素将呈现为“未选择”状态。在 iOS 上,这将导致用户无法选择第一项,因为在这种情况下 iOS 不会触发更改事件。因此,建议提供一个带空值的禁用选项
默认情况下 v-model 在每个事件之后将输入与数据同步 可以添加修饰符lazy
修改为在事件后同步
1 | <!-- synced after "change" instead of "input" --> |
如果希望用户输入的自动转化为数字 可以添加.number
如果希望自动修剪用户输入中的空白 可以加入.trim
生命周期钩子
Watcher
1 | <script setup> |
当你在 watch()直接调用一个响应式对象时 它会隐式创建一个深度观察者模式-回调将在所有嵌套突变上触发
1 | const obj = reactive({ count: 0}); |
**tips:**深度监视需要遍历监控对象中的所有嵌套属性 并且在用于大型数据结构时可能会开销昂贵 所以仅在必要时使用它并注意性能影响
watchEffect()
watch 是惰性的 在观察源发生变化之前不会立即调用 但是如果我们想要获取一些初始数据 然后在相关状态发生变化时重新获取数据
1 | const url = ref('https://...') |
watchEffect 允许我们在自动追踪效果的反应依赖的同时立即执行副作用函数
1 | watchEffect(async () => { |
1 | const API_URL = `https://api.github.com/repos/vuejs/core/commits?per_page=3&sha=` |
tips:watch 仅在其同步执行期间跟踪依赖项 当它与异步回调一起使用时 只会跟踪 await 跟踪第一个 tick 之前访问的属性
- watch 仅跟踪明确监视的源 将依赖跟踪与副作用分开
- 将依赖跟踪与副作用结合到一个阶段 会自动跟踪在同步执行期间访问的每一个反应属性 会使代码更简洁 但是依赖关系不明确
回调刷新时间
vue 默认在组件更新之前调用用户创建的观察者回调 所以如果尝试在观察者回调中访问 dom 则 dom 将处于 Vue 应用任何更新之前的状态 所以如果想要在 vue 更新后在 watcher 回调中访问 dom 需要指定 flush:’post’
1 | watch(source, callback, { |
停止观察者
Watchers 在内部同步声明setup()
或<script setup>
绑定到所有者组件实例,并在所有者组件卸载时自动停止。如果观察者是异步创建的 那么不会绑定到所有者组件 必须手动停止以避免内存泄漏
1 | const unwatch = watchEffect(() => {}) |
应该军垦首选同步创建 如果需要等待一些异步数据 可以使用逻辑控制异步操作
1 | watchEffect(() => { |
Props
如果要将对象的所有属性作为 props 传递 则可以使用 v-bind 不带参数使用
1 | <BlogPost v-bind="post" /> |
prop 用于传入一个初始值:子组件之后希望将其用作本地数据属性 则定义一个使用 prop 作为其初始值的本地数据属性
```
const props = defineProps([‘initialCounter’])// counter only uses props.initialCounter as the initial value;
// it is disconnected from future prop updates.
const counter = ref(props.initialCounter)1
2
3
4
5
6
7
8
- prop 作为需要转换的原始值传入 最好使用 prop 的值定义计算属性
- ```
const props = defineProps(['size'])
// computed property that auto-updates when the prop changes
const normalizedSize = computed(() => props.size.trim().toLowerCase())
组件事件
tips:组件的事件不会冒泡 父组件只能接收直接子组件发出的事件
可以通过 defineEmits 显示声明
1 | <script setup> |
如果在选项中定义了本地事件如 click 则侦听器现在仅侦听组件发出的 click 而不再响应本地的 click 事件
如果使用对象语法则可以添加验证 为了添加验证 事件被分配了一个函数 改函数将接收传递给调用的参数并返回一个有效的布尔值以只是事件是否
1 | <script setup> |
自定义事件还能和 v-model 相结合
1 | <!-- CustomInput.vue --> |
v-model 还可以使用 computed 属性和 getter 以及 setter 该 get 方法应该返回 modelValue 属性并且该 set 方法应该发出相应的事件
1 | <!-- CustomInput.vue --> |
使用多个 v-model
1 | <UserName |
处理 v-model 修饰符:自定义修饰符
eg:自定义一个将字符串第一个字母大写
1 | <MyComponent v-model.capitalize="myText" /> |
对于v-model
同时具有参数和修饰符 生成的道具名称将为arg + 'Modifiers'
1 | <MyComponent v-model:title.capitalize="myText"> |
Fallthrough 属性
v-on
传递给组件的属性或事件监听器 但是未在组件的 props 或 emits 中显示声明 如class
、style
、id
当组件渲染单个根元素时 将自动添加到根元素的属性中
1 | <!-- template of <MyButton> --> |
如果不希望组件自动继承属性 可以在inheriAttrs: false
在组件的选项中进行设置
如果使用<script setup>
,您将需要使用单独的普通<script>
块声明此选项:
1 | <script> |
常见场景:当属性需要引用于根节点以为的其他元素时 可以将inheritAttrs
选项设置为false
,您可以完全控制在何处应用 fallthrough 属性
可以通过$attrs
来访问
- 与 props 不同,fallthrough 属性在 JavaScript 中保留了它们的原始大小写,因此
foo-bar
需要将属性 like 访问为$attrs['foo-bar']
. - 像这样的
v-on
事件侦听@click
器将作为$attrs.onClick
.
我们希望所有诸如class
和v-on
监听器之类的贯穿属性都应用于内部<button>
,而不是外部<div>
。可以使用 inheritAttrs: false 和
v-bind="$attrs"
来使用
1 | <div class="btn-wrapper"> |
如果 v-bind 没有参数 则将对象的所有属性绑定为目标元素的属性
多个根节点的继承
具有多个根节点的组件没有自动属性失效的行为 需要明确绑定$attrs
1 | <CustomLayout id="custom-layout" @click="changeValue" /> |
1 | <script setup> |
插槽
插槽的作用
- 使用插槽 子组件负责样式的渲染 而内容由父组件提供
- 通过使用插槽 我们可以使
<FancyButton>
更加灵活和重用 我们可以在不同的地方使用它 具有不同的内部内容 但是都具有同样花哨的样式
渲染范围
- 插槽可以访问父组件的数据范围 因为它是在父组件中定义的
- 插槽无权访问子组件的数据 因为父模板中的所有内容都在父范围内编译 子模板中的所有内容都在子范围内编译
命名插槽
可以通过使用 name 用来为不同的插槽分配一个唯一的 ID 这样可以控制在哪里呈现内容
1 | <div class="container"> |
当组件同时接受默认槽和命名槽时,所有顶级非<template>
节点都被隐式视为默认槽的内容。
动态插槽名称
动态指令参数也适用于 v-slot 允许定义动态插槽的名称
1 | <base-layout> |
命名插槽
可以通过命名插槽访问子组件的数据
1 | <div> |
命名范围插槽
1 | <MyComponent> |
name 不会包含在 prop 中 所以 headerProps 是{ message: 'hello' }
.
作用域插槽的使用场景
如果需要一个
1 | <FancyList :api-url="url" :per-page="10"> |
在内部,我们可以使用不同的项目数据多次<FancyList>
渲染相同的内容(注意我们使用对象作为插槽道具传递):<slot>``v-bind
1 | App.vue |
Provide/Inject
1 | script setup> |
提供响应式值允许使用提供的值的后代组件建立到提供程序组件的响应式连接。
如果提供的值是 ref 它是按原样注入 不会自动解构 这允许注入器组件保留与提供者组件的反应性连接
注入默认值
如果想要让注入的属性和可选提供者一起工作 那么就需要声明一个默认值 类似于 props
1 | const value = inject("message", "default value"); |
在某些情况下,可能需要通过调用函数或实例化新类来创建默认值。为了避免在不使用可选值的情况下产生不必要的计算或副作用,我们可以使用工厂函数来创建默认值:
1 | const value = inject("key", () => new ExpensiveClas()); |
使用响应式数据
尽可能将任何对响应式状态的突变保留在提供程序内部 这确保了提供的状态以及可能的突变位于同一组件 从而更容易在将来维护
有时我们可能从租入器组件更新数据 这种情况我们一般提供一个负责改变状态的函数
1 | <script setup> |
最后 我们想确保 provide 的数据不会被改变 可以使用 readonly
1 | provide("read-only-count", readonly(count)); |
最好使用符号注入密钥来避免潜在的冲突 可以将符号到处到专有文件中
1 | // keys.js |
异步组件
1 | import { defineAsyncComponent } from "vue"; |
结果AsyncComp
是一个包装器组件,它仅在实际呈现在页面上时才调用加载器函数。此外,它会将任何 props 传递给内部组件,因此您可以使用异步包装器无缝替换原始组件,同时实现延迟加载。
加载和错误状态
1 | const AsyncComp = defineAsyncComponent({ |
如果提供了加载组件,它将在加载内部组件时首先显示。在显示加载组件之前有一个默认的 200 毫秒延迟 - 这是因为在快速网络上,即时加载状态可能会被替换得太快并最终看起来像闪烁。
如果提供了错误组件,当 loader 函数返回的 Promise 被拒绝时会显示。您还可以指定超时以在请求时间过长时显示错误组件。
Composables
Composables 是一种可以利用 Vue 组合 API 封装和重用有状态逻辑的函数
鼠标跟踪器示例
```js
// mouse.js
import { ref, onMounted, onUnmounted } from ‘vue’// by convention, composable function names start with “use”
export function useMouse() {
// state encapsulated and managed by the composable
const x = ref(0)
const y = ref(0)// a composable can update its managed state over time.
function update(event) {x.value = event.pageX y.value = event.pageY
}
// a composable can also hook into its owner component’s
// lifecycle to setup and teardown side effects.
onMounted(() => window.addEventListener(‘mousemove’, update))
onUnmounted(() => window.removeEventListener(‘mousemove’, update))// expose managed state as return value
return { x, y }
}
//组件中的使用方式Mouse position is at: ,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
COmposables 还可以嵌套使用:一个可组合函数可以调用另一个或多个其他可组合函数 这是的我们能够使用小的、隔离的单元来组合复杂的逻辑 了类似于使用组件来整合整个应用程序的方式
实例 2:我们可以将添加和清理 DOM 事件监听器的逻辑提取到它自己的可组合中
```js
// event.js
import { onMounted, onUnmounted } from "vue";
export function useEventListener(target, event, callback) {
// if you want, you can also make this
// support selector strings as target
onMounted(() => target.addEventListener(event, callback));
onUnmounted(() => target.removeEventListener(event, callback));
}
//现在我们的useMouse()可以简化为
// 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 };
}**tips:**每个组件实例调用
useMouse()
都将创建自己的副本x
和y
状态,因此它们不会相互干扰。如果想管理组件之间的共享状态 可以使用 vuex异步状态示例
在进行异步数据获取时 我们经常需要处理不同的状态:加载、成功和错误 我们可以将其提取到可组合中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 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>;如果我们希望在 URL 更改时重新获取数据 可以使用响应式数据作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// fetch.js
import { ref, isRef, unref, watchEffect } from "vue";
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
function doFetch() {
// reset state before fetching..
data.value = null;
error.value = null;
// unref() unwraps potential refs
fetch(unref(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err));
}
if (isRef(url)) {
// setup reactive re-fetch if input URL is a ref
watchEffect(doFetch);
} else {
// otherwise, just fetch once
// and avoid the overhead of a watcher
doFetch();
}
return { data, error };
}useFetch()接收静态 URL 以及 URL 字符串的引用 当检测到 URL 是一个动态引用即 isRef()时 会设置一个响应式效果 WatchEffect()效果将立即运行 并在这个过程中将 URL ref 作为依赖项进行跟踪 每当 URL 更改时 数据将被重置并再次获取
Conventions and Best Practices
以 use 开头 如 useFetch();
如果正在编写一个可能被其他开发人员使用的组合 在处理输入参数时 refs 而不是原始值时是一个好主意 可以使用 unref
1
2
3
4
5
6
7import { unref } from "vue";
function useFeature(maybeRef) {
// if maybeRef is indeed a ref, its .value will be returned
// otherwise, maybeRef is returned as-is
const value = unref(maybeRef);
}如果在输入是
ref
是创建响应性 请确保使用watch()
来跟踪ref
数据 或者使用unref
并在内部使用watchEffect()
我们推荐返回一个 refs 对象 以便在组件中对其进行解构并保持响应性 如果更喜欢返回对象属性 可以使用
reactive()
以便解构引用
1 | const mouse = reactive(useMouse()); |
副作用
- 如果正在开发服务端渲染(SSR)的原因程序 确保在安装后生命周期执行特定于 DOM 的副作用 如在 onMounted()确保可以访问 DOM
- 确保清除 onUnmounted 在设置事件监听器后记得清理
使用限制
只能在钩子中同步使用 可以在<script setup>setup()onMounted()
中调用
- 生命周期钩子可以注册到它
- 计算属性和观察者可以追踪并在组件卸载时进行处理
- 你只能在
<script setup>
中使用 await 编译器会在执行异步操作后自动恢复活动实例上下文
为代码组织提取可组合项
可组合项不仅可以提取用于重用 还可以用于代码组织
1 | <script setup> |
自定义指令
自定义指令主要用于重用涉及对普通元素进行低级 DOM 访问的逻辑
自定义指令被定义为包含类似于组件的生命周期钩子的对象 钩子接收指令绑定的元素
1 | <script setup> |