Skip to content

Vue3 重点理解部分

TIP

过了一遍 Vue3 官网,这里的内容持续更新中

响应式

Reactive

  • 对原始类型无效:仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、numberboolean 这样的 原始类型 无效。
  • 赋值或结构会丢失响应性:因为 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 无论初始条件为何,始终渲染,只是动态切换 CSSdisplay 属性;
  • 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...

Released under the MIT License.