在官网中查阅到了 defineModel
这个宏 API,于是就自己测试了一下,但是遇到一个很诡异的地方无法理解。
下面是测试代码:
这个是父组件,结构很简单,引入了一个子组件
<script lang="ts" setup>import { ref, onBeforeUpdate, onUpdated } from "vue";import Context from "../Context.vue";const count = ref(100);</script><template> <Context v-model="count" /></template>
子组件如下
<script lang="ts" setup>import { ref, nextTick } from "vue";const n = ref(0);const count = defineModel({ type: Number, default: 13 });count.value += 1;count.value += 2;count.value += 3;count.value += 4; console.log(count.value); // 这里为什么打印是 100?nextTick(() => { console.log(count.value); //这里结果为什么是 104??});</script><template><div> {{count}}</div></templage>
问题:子组件的输出是不是有点问题呢?
众所周知,v-model
仅仅是语法糖,父组件中的 <Context v-model="count" />
将被编译为:
// 父组件<Context :modelValue="count" @update:modelValue="$event => (count = $event)"/>
那么子组件中的 defineModel
又是什么呢?从上面的代码中可以看出,它应该为子组件定义了一个包含 modelValue
的 props
和一个自定义事件 update:modelValue
。类似于:
// 子组件const props = defineProps<{ modelValue: number }>(), emit = defineEmits<{ 'update:modelValue': [value: number] }>()
同时它还有一个返回值,类型为 ModelRef
。它实际上也只是让我们使用更方便而包装的一层代理而已。当我们读取它的值时,它实际上是在访问 props.modelValue
;当我们设置它的值时,实际上也只是触发了 update:modelValue
事件而已:
const modelRef = { get value() { return props.modelValue }, set value(newValue) { emit('update:modelValue', newValue) }}
此时我们的应用中有两个 count
:
// 父组件// Ref<number>const count = ref(100)// 子组件// ModelRef<number>const count = defineModel(/** code */)
一个是 Ref
,另一个是 ModelRef
。看上去似乎相同,实际上没有任何关系。他们之间实际上是通过父子组件间的 props
相联系起来的。
让我们回到问题,当子组件中执行 count.value += 1
时,发生了什么:
count.value += 1==>count.value = count.value + 1==>emit('update:modelValue', count.value + 1)==>emit('update:modelValue', props.modelValue + 1)
当执行完这一语句时,父组件中的 Ref
确实变成了 101,但是子组件的 props
的更新还要等到下一个 tick
,所以子组件中的 ModelRef
还是 100。
所以接下来 count.value += 2
、count.value += 3
、count.value += 4
将会分别把父组件的 Ref
设置为 102,103,104。
在 Vue 3.4+ 中,defineModel
是一个宏 API,用于定义组件的模型(model),它使得组件可以方便地通过 v-model
绑定到父组件。然而,在你提供的代码中,存在一些误解和错误使用 defineModel
的情况。
首先,defineModel
宏 API 的作用是定义组件的 props 和 emit 事件,以支持 v-model
的语法糖。它并不会自动处理 prop 的默认值或类型校验。对于默认值,你应该在 props
中直接设置,而对于类型校验,Vue 3 提供了 defineProps
和 defineEmits
宏 API 来分别定义 props 和 emit 事件。
在你的子组件中,你尝试使用 defineModel
来定义一个 count
属性,并且给它指定了类型和默认值。但是,你实际上并没有在 props
中定义 count
,而且 defineModel
并不会设置默认值。因此,count
的值将由父组件通过 v-model
绑定传入。
在父组件中,你通过 v-model="count"
将 count
绑定到子组件。这意味着子组件的 count
prop 的值将会与父组件的 count
ref 同步。因此,当父组件的 count
发生变化时,子组件的 count
prop 也会相应地变化。
关于你提到的打印结果:
count
进行一系列加法操作,但此时 count
的值实际上还没有从父组件传入,所以这些操作是没有意义的。console.log(count.value);
打印的 100
是因为此时 count
的值已经被父组件的 count
ref 同步了。在父组件中,你初始化了 count
为 100
,所以子组件中的 count
prop 也是 100
。nextTick(() => { console.log(count.value); })
打印的 104
是因为你在 nextTick
的回调函数中打印 count.value
。nextTick
会在 DOM 更新后执行回调函数,此时子组件的 count
prop 已经被父组件的 count
ref 同步,并且由于你在子组件中尝试对 count
进行加法操作(尽管这些操作没有意义),所以 count.value
的值变成了 104
。综上所述,你遇到的输出问题主要是由于对 defineModel
的错误使用以及对 Vue 组件 props 和 v-model
的工作原理理解不足导致的。你应该直接使用 props
和 emit
来处理 v-model
的绑定,而不是使用 defineModel
。
以下是一个修正后的子组件示例:
<script lang="ts" setup>import { defineProps, defineEmits, nextTick } from "vue";const props = defineProps({ modelValue: { type: Number, default: 13 }});const emit = defineEmits(['update:modelValue']);const count = computed({ get() { return props.modelValue; }, set(value) { emit('update:modelValue', value); }});nextTick(() => { console.log(count.value); // 这里将会打印从父组件传入的 modelValue 的值});</script><template> <div>{{ count }}</div></template>
在这个修正后的示例中,我们使用了 defineProps
来定义 modelValue
prop,并使用 defineEmits
来定义 update:modelValue
emit 事件。我们还使用了一个计算属性 count
来封装对 modelValue
的访问和更新,以便能够正确地触发 v-model
的更新逻辑。这样,当父组件的 count
发生变化时,子组件的 count
prop 也会相应地变化,并且子组件可以通过更新 count
来通知父组件进行更新。
作为一名前端开发者,有时候会遇到一些跨域问题,之前我参考一些教程网址的理解是: 客户端浏览器其实已经将请求发送出去了,服务器端也接收到了,但是服务器返回的数据在回来的时候被浏览器拦截了。 但是今天在和后端同事讨论的时候,他说我之前理解错了。下面是他的解释: 跨域的本质是保护服务器的数据,就好像你不能直接进我家来捣乱,你需要我给你钥匙(需要后端的 Access-Control-Allow-Origi
问题内容: 我正在学习一些节点,并一直尝试使用猫鼬。目前,我的目标是学习如何使用populate。 我有一个定义,并要求: 然后我在某个时候执行此操作: 如何填充里程碑? 这是来自mongo 的数据: 这是基本上与项目相关的一个: 另外,这是里程碑模式: 问题答案: 您需要获得定义查询选项然后执行的顺序,并且可链接的API(例如mongoose Query)不知道在查询触发后您可能会调用哪些其他方
问题内容: 我正在使用Selenium IDE为我的站点编写测试,但是我无法让selenium使用上一个同级按钮单击按钮 我自己的路 问题答案: 您无需升级并使用,因为所有按钮都处于同一级别:
本文向大家介绍Array.filter中如何正确使用Async,包括了Array.filter中如何正确使用Async的使用技巧和注意事项,需要的朋友参考一下 1. 如何仅保留满足异步条件的元素 在第一篇文章中,我们介绍了 async / await 如何帮助处理异步事件,但在异步处理集合时却无济于事。在本文中,我们将研究该filter函数,它可能是支持异步函数的最直观的方法。 2. Array.
问题内容: 我只想检索UserAccount类中的某些列,所以我有以下代码: 我得到了空值作为回报。但是,如果我注释掉setProjections,我将获得具有所有属性的用户。在这种情况下,如何正确使用setProjection? 问题答案: 它返回一个Object数组,因此代码应为:
问题内容: 我不知道我在哪里错了:/。当我运行这段代码时,我得到的只是一个空白元素。我似乎无法让insertRule方法执行任何操作(甚至不会产生错误)。我想念什么吗? 问题答案: 这有点令人困惑,但是您的代码确实可以工作,只是您看不到返回的XML树中插入的规则。 为了验证您的代码是否有效,您可以执行两个测试: 运行上面的代码片段,您可以看到CSS规则确实适用。并且属性也在控制台中更改。 当浏览器