Pet*_*len 14 vue.js vue-component vuejs3
我在使用组合 API 在 vue 3 中双向绑定反应式组件时遇到问题。
设置:
父调用代码是:
<template>
<h1>{{ message.test }}</h1>
<Message v-model="message" />
</template>
<script>
import Message from '@/components/Message.vue';
import { reactive } from 'vue';
export default {
name: 'Home',
components: { Message },
setup() {
const message = reactive({ test: '123' });
return {
message
};
}
};
</script>
Run Code Online (Sandbox Code Playgroud)
子组件代码为:
<template>
<label>
<input v-model="message" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
messageObj: {
type: Object,
default: () => {},
},
},
emits: ['update:messageObj'],
setup(props, { emit }) {
const message = computed({
get: () => props.messageObj.test,
set: (value) => emit('update:messageObj', value),
});
return {
message,
};
},
};
</script>
Run Code Online (Sandbox Code Playgroud)
问题:
加载组件后,对象的默认值将显示在输入字段中。这是应该的,但是,当我更新输入框中的值时,父视图中的 H1 不会使用新的输入框值进行更新。
我已经通过 stackoverflow board 和 google 进行了搜索,但没有找到任何关于需要做什么才能使对象具有反应性的提示。
我通读了反应性文档,但仍然没有找到解决我的问题的任何方法。
为了进行测试,我已将消息更改为引用,并使用此单个引用值,数据保持反应状态,并且一切都按预期工作。
关于反应式对象不更新可能出现什么问题的任何指示?
Mat*_*att 13
<div id="app">
<h1>{{ message.test }}</h1>
<child v-model="message"></child>
</div>
Run Code Online (Sandbox Code Playgroud)
const { createApp, reactive, computed } = Vue;
// -------------------------------------------------------------- child
const child = {
template: `<input v-model="message.test" type="text" />`,
props: {
modelValue: {
type: Object,
default: () => ({}),
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
return { message };
}
};
// ------------------------------------------------------------- parent
createApp({
components: { child },
setup() {
const message = reactive({ test: 'Karamazov' });
return { message };
}
}).mount('#app');
Run Code Online (Sandbox Code Playgroud)
解决方案和观察结果:
在调用组件的父视图中,如果您只需要传递对象中的值之一,则可以使用 v-model 并向该 v-model 添加参数。
<template>
<h1>{{ message.test }}</h1>
<!-- <h1>{{ message }}</h1> -->
<Message v-model:test="message" />
</template>
<script>
import Message from '@/components/Message.vue';
import { reactive } from 'vue';
export default {
name: 'Home',
components: { Message },
setup() {
const message = reactive({ test: '123' });
return {
message
};
}
};
</script>
Run Code Online (Sandbox Code Playgroud)
然后,在接收组件中,您将在 props 中传递的对象参数注册为对象。
<template>
<label>
<input v-model="message.test" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
test: {
type: Object,
default: () => {}
},
},
emits: ['update:test'],
setup(props, { emit }) {
const message = computed({
get: () => props.test,
set: (value) => emit('update:test', value),
});
return {
message,
};
},
};
</script>
Run Code Online (Sandbox Code Playgroud)
如果需要传递整个对象,则需要在组件中使用名称 modelValue 作为道具。
与之前的代码相比,父级发生了变化:
<template>
<h1>{{ message.test }}</h1>
<!-- <h1>{{ message }}</h1> -->
<Message v-model="message" />
</template>
Run Code Online (Sandbox Code Playgroud)
组件代码:
<template>
<label>
<input v-model="message.test" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
modelValue: {
type: Object,
default: () => {}
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
return {
message,
};
},
};
</script>
Run Code Online (Sandbox Code Playgroud)
你最初的问题很简单。在 Vue 3 中v-model,默认有一个名为 的 prop modelValue,并且发出来自update:modelValue。这里的其他答案在他们的解决方案中假设了这一点,但没有直接解决它。
您可以重命名messageObjprop 以使用默认 prop,或者使用 Vue 3 中的多模型功能:
<Message v-model:messageObj="message" />
Run Code Online (Sandbox Code Playgroud)
然而我们的问题还更深层次。
所有(当前)答案都有效,但并不完全正确。它们都不符合惯用的“单向数据流”规则。
考虑这个 JSFiddle,修改自这个答案。
const child = {
template: `<input v-model="message.test" type="text" />`,
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
// No set() ?
});
return { message };
}
}
Run Code Online (Sandbox Code Playgroud)
在此示例中,子组件永远不会“发出” - 但父组件中的数据仍在更新。这违反了“单向”规则。数据必须仅使用发出而不是通过 prop 代理从子组件传播。
这里的问题是props.modelValue当到达子组件时它是反应性的。可以向isReactive()助手验证这一点。当它通过时,computed()它会保留这种反应性,并将继续通过自身将更新代理到父组件中。
const { createApp, ref, computed } = Vue;
const child = {
template: `<input v-model="message" type="text" />`,
props: {
modelValue: {
type: Object,
default: () => ({}),
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue.test,
set: (test) => emit('update:modelValue', ({...props.modelValue, test })),
});
return { message };
}
};
createApp({
components: { child },
setup() {
const message = ref({ test: 'Karamazov' });
return { message };
}
}).mount('#app');
Run Code Online (Sandbox Code Playgroud)
解决方案分为三部分:
计算的 getter 不得从父组件返回代理对象。一旦发生这种情况,您就有违反“单向”规则的危险[注 1]。在这个例子中props.modelValue.test是一个字符串,所以我们是安全的。
计算设置器必须发出整个对象,但同样它不能是反应类型。因此,我们克隆modelValue使用传播并包含在更新的test字段中。Object.assign({}, props.modelValue, {test})这也可以通过[注2]来实现。
message父组件中的变量不能是areactive()并且必须是 a ref()。当v-model接收到新发出的对象时message,变量被破坏并且不再具有反应性[注3]。即使有引用,props.modelValue当它到达子组件时,它仍然会完全反应,因此克隆步骤仍然很重要。
我还应该提到的是,来自的值computed()并不具有深度反应性。如图所示,在计算对象上设置值不会触发计算设置器。
将整个对象传递到模板的替代解决方案:
setup(props, { emit }) {
const message = reactive({...props.modelValue});
watch(message, message => emit('update:modelValue', ({...message})));
return { message };
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,每当字段更新时,整个message对象就会发出。.test例如<input v-model="message.test" />。这仍然遵循“单向”数据规则,因为发出是向父组件提供数据的唯一方式。
“单向”数据流很重要[4]。考虑一下:
<child :modelValue="message"></child>
Run Code Online (Sandbox Code Playgroud)
乍一看(也是明智的),这似乎将数据传递到“child”中,但没有从“child”中传递出去。但是,如果子级未正确处理反应性对象,这会将更改发送到我自己的组件中。
观察这段代码,我并不期望出现这种行为,因此子组件正确执行非常重要。
[1]:测试是否违反“单向”规则非常简单。删除任何emit内容,如果父级收到更新 - 你就破坏了它。或者替换v-model为v-bind也可以。
[2]:Object.assign()和{...}传播确实不同。但应该不影响我们在这里的使用。
[3]:我还没有找到任何关于reactive()和的这种行为的明确文档v-model。如果有人愿意插话,那就太好了。
[4]:Vue 文档强调了单向绑定的重要性。Evan 本人(Vue 的创建者)甚至提供了有关如何使用对象的示例v-model(在 Vue 2 中,但原则仍然适用)。
我觉得稍后在同一个线程中值得注意的是,Evan 建议嵌套超过 1 层的对象被视为滥用v-model.
| 归档时间: |
|
| 查看次数: |
32641 次 |
| 最近记录: |