1. 前言

最近在修 Bug 时遇到一个很奇怪的问题,简单来说就是:在使用 watch 监听 props 的一个类型为数组的数据时,props 传值没有变,但却触发了 watch 回调。

2. 复现 Bug

假设我们实现一个选择器组件:

Select.vue

<template>
    <div>
        <div>{{ modelValue ?? "-" }}</div>
        <div v-for="option in options" :key="option.value" @click="$emit('update:modelValue', option.value)">
            {{ option.label }}
        </div>
    </div>
</template>

<script setup>
import { watch } from "vue";

const props = defineProps({
    modelValue: String,
    options: Array
});

const emit = defineEmits(["update:modelValue"]);

watch(
    () => props.options,
    () => {
        console.log("options changed");
    },
    {
        onTrack(e) {
            console.log(e);
        },
        onTrigger(e) {
            console.log(e);
        }
    }
);
</script>

引用该组件:

Demo.vue

<script setup>
import { ref } from "vue";
import Select from "./Select.vue";

const valueRef = ref(null);
function createOptions() {
    return [
        { label: "A", value: "A" },
        { label: "B", value: "B" },
        { label: "C", value: "C" }
    ];
}
</script>

<template>
    <Select v-model="valueRef" :options="createOptions()" />
</template>

效果如下:

图片标题

通过 onTrack() 调试信息发现,() => props.optionsget 方式监听 options

当我们点击 ABC 中某一个选项,那么就短横线就应该显示为那一项的值,我们点击 A:

图片标题

A 显示出来了,但控制台竟然打印了 options changed

具体原因目前不详,但可以使用以下两种方式避免该问题。

3. 解决方案

3.1 props 直接传数组

引用组件时,props 传值直接传数组:

Demo.vue

.
.
.
<template>
    <Select v-model="valueRef" :options='[
        { label: "A", value: "A" },
        { label: "B", value: "B" },
        { label: "C", value: "C" }
    ]' />
</template>

3.2 如果值由函数生成,先赋值给变量

.
.
.
const options = createOptions();
</script>

<template>
    <Select v-model="valueRef" :options="options" />
</template>