Vue3 重点理解部分
TIP
过了一遍 Vue3 官网,这里的内容持续更新中
响应式
Reactive
- 对原始类型无效:仅对对象类型有效(对象、数组和
Map、Set
这样的集合类型),而对string、number
和boolean
这样的 原始类型 无效。 - 赋值或结构会丢失响应性:因为
Vue
的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失。
js
const state = reactive({ count: 0 });
// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count;
// 不影响原始的 state
n++;
// count 也和 state.count 失去了响应性连接
let { count } = state;
// 不会影响原始的 state
count++;
// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count);
Ref
创建可以使用任何值类型的响应式 ref
和响应式对象的属性类似,
ref
的.value
属性也是响应式的。同时,当值为对象类型时,会用reactive()
自动转换它的.value
。很重要的一点:ref
被传递给函数或是从一般对象上被解构时,不会丢失响应性。(为实现「组合式函数」打下基础!)
js
const objectRef = ref({ count: 0 });
// 这是响应式的替换
objectRef.value = { count: 1 };
const obj = {
foo: ref(1),
bar: ref(2),
};
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo);
// 仍然是响应式的
const { foo, bar } = obj;
简言之,ref()
让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中。
渲染机制
计算属性缓存 vs 方法
首先,二者都可以实现相同功能。computed
方法期望接收一个 getter
函数,返回值为一个计算属性 ref
,且仅会在其响应式依赖更新时才重新计算。相比之下,方法调用
总是会在重渲染发生时再次执行函数。所以,computed
是可以起到性能优化作用的。
// 下面的计算属性永远不会更新,因为 Date.now() 并不是一个响应式依赖:
const now = computed(() => Date.now())
透传属性
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。
- 组件在被使用时,如果传入了
class
,那么这个class
的值会被添加(或合并)到组件内部的根结点的class
上; - 如果不想被默认添加到根节点上,需要在组件内部设置
inheritAttrs: false
,然后便可以完全控制透传进来的attribute
被如何使用。代码如下:
vue
<!-- JS -->
<script>
// 使用普通的 <script> 来声明选项
export default {
inheritAttrs: false,
};
</script>
<script setup>
// ...setup 部分逻辑
</script>
<!-- HTML -->
<template>
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">click me</button>
<button :id="$attrs.id">click me</button>
<button :class="$attrs.class">click me</button>
</div>
</template>
小提示:没有参数的
v-bind
会将一个对象的所有属性都作为attribute
应用到目标元素上。
- 多根节点的
Attributes
继承,需要显式的绑定$attrs
,否则会抛出警告,如下:
html
<!-- 使用组件 -->
<CustomLayout id="custom-layout" @click="changeValue" />
<!-- 组件 -->
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
- 在 JavaScript 可以通过 useAttrs() API,访问透传 attribute
js
<script setup>import {useAttrs} from 'vue' const attrs = useAttrs()</script>
v-show VS v-if
v-if
也是惰性的:如果在初次渲染时条件值为false
,则不会做任何事,只在条件为true
时,条件区块才会被渲染;v-show
无论初始条件为何,始终渲染,只是动态切换CSS
的display
属性;v-show
不支持在<template>
元素上使用,也不能和v-else
搭配使用;v-if
有更高的切换开销(切换条件都会销毁、重建);v-show
有更高的初始渲染开销(始终渲染);- 因此,如果需要频繁切换,则使用
v-show
较好;如果在运行时绑定条件很少改变,则v-if
会更合适。
同时使用 v-if 和 v-for 是不推荐的,当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行
事件修饰符
- .stop 停止传递事件
- .prevent 阻止默认事件
- .self 仅当 event.target 是元素本身时才会触发事件处理器
- .capture 捕获模式,指向内部元素的事件,在被内部元素处理前,先被外部处理
- .once 点击事件最多被触发一次
- .passive 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。
js
// 单击事件将停止传递
<a @click.stop="doThis"></a>
// 提交事件将不再重新加载页面
<form @submit.prevent="onSubmit"></form>
// 修饰语可以使用链式书写
<a @click.stop.prevent="doThat"></a>
// 也可以只有修饰符
<form @submit.prevent></form>
// 仅当 event.target 是元素本身时才会触发事件处理器
// 例如:事件处理器不来自子元素
<div @click.self="doThat">...</div>
// 添加事件监听器时,使用 `capture` 捕获模式
// 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理
<div @click.capture="doThis">...</div>
// 点击事件最多被触发一次
<a @click.once="doThis"></a>
// 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成
// 以防其中包含 `event.preventDefault()`
<div @scroll.passive="onScroll">...</div>
v-model
- 在原始标签上使用:
v-model
相当于:input='val'
加@input="val = $event.target.value"
;
js
<input v-model="searchText" />
// 等价于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
- 在组件上使用:
- 将内部原生
input
元素的value attribute
绑定到modelValue
prop
- 输入新的值时在
input
元素上触发update:modelValue
事件
- 将内部原生
vue
<CustomInput v-model="searchText" />
<!-- CustomInput.vue -->
<script setup>
defineProps(["modelValue"]);
defineEmits(["update:modelValue"]);
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<!-- 或者:使用一个可写的,同时具有 getter 和 setter 的计算属性 -->
<script setup>
import { computed } from "vue";
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
const value = computed({
get() {
return props.modelValue;
},
set(value) {
emit("update:modelValue", value);
},
});
</script>
<template>
<input v-model="value" />
</template>
- 默认情况下,
v-model
在组件上都是使用modelValue
作为prop
,并以update:modelValue
作为对应的事件。我们可以通过给v-model
指定一个参数来更改这些名字:
vue
<MyComponent v-model:title="bookTitle" />
<!-- MyComponent.vue -->
<script setup>
defineProps(["title"]);
defineEmits(["update:title"]);
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
- 多个
v-model
绑定,在单个指定了v-model
参数的基础上,增加一个属性的绑定即可。
组合式函数
在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
命名:组合式函数约定用驼峰命名法命名,并以“use”作为开头;
输入参数:通过 unref 工具函数,兼容可能是 ref 的参数;
js
import { unref } from 'vue'
function useFeature(maybeRef) {
// 若 maybeRef 确实是一个 ref,它的 .value 会被返回
// 否则,maybeRef 会被原样返回
const value = unref(maybeRef)
}
- 返回值:在组合式函数中使用 ref() 而不是 reactive()。我们推荐的约定是组合式函数始终返回一个包含多个 ref 的普通的非响应式对象,这样该对象在组件中被解构为 ref 之后仍可以 保持响应性。
js
// 使用组合式函数:得到 x 和 y 是两个 ref
const { x, y } = 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 }
}
// event.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
// 如果你想的话,
// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
something more...