如何使用 Vue Composition API/Vue 3 观察道具变化? [英] How to Watch Props Change with Vue Composition API / Vue 3?
问题描述
虽然 Vue Composition API RFC 参考站点有许多先进的使用watch
模块的场景,没有如何观看组件props的例子?
Vue Composition API RFC 主页或 Github 中的 vuejs/composition-api.
我创建了一个 Codesandbox 详细说明这个问题.
<div id="应用程序"><img width="25%" src="./assets/logo.png"><br><p>使用 v-model 选择输入的 Prop 观看演示:</p><PropWatchDemo :selected="testValue"/>
</模板><脚本>从@vue/composition-api"导入 { createComponent, onMounted, ref };从./components/PropWatchDemo.vue"导入 PropWatchDemo;导出默认 createComponent({名称:应用程序",成分: {PropWatchDemo},设置:(道具,上下文)=>{const testValue = ref("initial");onMounted(() => {setTimeout(() => {console.log("延迟 3 秒后更改输入属性值");testValue.value = "改变";//这个值改变不会触发观察者?}, 3000);});返回 {测试值};}});
<select v-model="selected"><option value="null">null value</option><选项值>空值</option></选择></模板><脚本>import { createComponent, watch } from "@vue/composition-api";导出默认 createComponent({名称:我的输入",道具: {选择:{类型:[字符串,数字],要求:真实}},设置(道具){console.log("设置道具:", props);手表((第一,第二)=> {console.log("使用参数调用的监视函数:", first, second);//第一个arg函数registerCleanup,第二个未定义});//watch(props, (first, second) => {//console.log("Watch props 函数调用了 args:", first, second);////记录错误:////观看路径失败:[object Object]" Watcher 只接受简单的////点分隔的路径.要完全控制,请改用函数.//})watch(props.selected, (first, second) => {控制台.日志("观看带有 args 调用的 props.selected 函数:",第一的,第二);//两个 props 都未定义,所以它只是一个要运行的裸回调函数});返回 {};}});
编辑:虽然我的问题和代码示例最初是使用 JavaScript 的,但我实际上使用的是 TypeScript.托尼汤姆的第一个答案虽然有效,但会导致类型错误.Michal Levý的回答解决了这个问题.所以我后来用 typescript
标记了这个问题.
EDIT2:这是我针对此自定义选择组件的反应布线的完善但准系统版本,位于
之上,来自 bootstrap-vue
(否则是不可知的实现,但这个底层组件确实发出@input 和@change 事件,这取决于更改是以编程方式还是通过用户交互进行).
<b-形式-选择v-model="选择":options="{}"@input="handleSelection('input', $event)"@change="handleSelection('change', $event)"/></模板><script lang="ts">进口 {createComponent、SetupContext、Ref、ref、观察、计算、来自'@vue/composition-api';界面道具{值?:字符串 |数量 |布尔值;}导出默认 createComponent({name: '自定义选择',道具: {价值: {类型:[字符串,数字,布尔值],required: false,//也接受 null 和 undefined},},设置(道具:道具,上下文:SetupContext){//从 prop 创建一个 Ref,因为只有使用同步修饰符才允许双向绑定,//在父级中传递 prop 并在子级上显式发出更新事件://参考:https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier//参考:https://medium.com/@jithilmt/vue-js-2-two-way-data-binding-in-parent-and-child-components-1cd271c501baconst 选择:Ref= ref(props.value);const handleSelection = function emitUpdate(type: 'input' | 'change', value: Props['value']) {//对于同步修饰符,其中 'value' 是道具名称context.emit('update:value', value);//对于@input 和/或@change 事件传播//@input 在值改变时<以编程方式> 由选择组件发出//@change 和 @input 都在<用户交互>上发出上下文.发射(类型,值);};//观察 prop 值的变化并分配给值 'selected' Refwatch(() => props.value, (newValue: Props['value']) => {selected.value = newValue;});返回 {选择,句柄选择,};},});
如果你看看 watch
输入 这里 很明显 watch
的第一个参数可以是数组、函数或 Ref<T>
setup
函数的 props
是反应性对象(可能由 reactive()
),它的属性是 getter.所以你所做的是将 getter 的值作为 watch
的第一个参数传递 - 字符串initial";在这种情况下.因为 Vue 2 $watch
API 在幕后使用(和相同的功能 exists 在 Vue 3 中),您正在有效地尝试监视名称为initial"的不存在的属性;在您的组件实例上.
您的回调只会被调用一次,再也不会被调用.它至少被调用一次的原因是因为新的 watch
API 表现得像当前的 $watch
和 immediate
选项(UPDATE 03/03/2021 - 后来改变了,在 Vue 3 的发布版本中,watch
和 Vue 2 一样懒惰)
所以你偶然地做了托尼汤姆建议的同样的事情,但价值错误.在这两种情况下,如果您使用的是 TypeScript,则它不是有效代码
您可以改为这样做:
watch(() => props.selected, (first, second) => {控制台.日志("Watch props.selected 使用 args 调用的函数:",第一的,第二);});
这里第一个函数由 Vue 立即执行以收集依赖项(以了解应该触发回调的内容),第二个函数是回调本身.
其他方法是使用 toRefs
转换 props 对象,因此它的属性类型为 Ref
并且您可以将它们作为 的第一个参数传递>观看
While Vue Composition API RFC Reference site has many advanced use scenarios with the watch
module, there is no examples on how to watch component props?
Neither is it mentioned in Vue Composition API RFC's main page or vuejs/composition-api in Github.
I've created a Codesandbox to elaborate this issue.
<template>
<div id="app">
<img width="25%" src="./assets/logo.png">
<br>
<p>Prop watch demo with select input using v-model:</p>
<PropWatchDemo :selected="testValue"/>
</div>
</template>
<script>
import { createComponent, onMounted, ref } from "@vue/composition-api";
import PropWatchDemo from "./components/PropWatchDemo.vue";
export default createComponent({
name: "App",
components: {
PropWatchDemo
},
setup: (props, context) => {
const testValue = ref("initial");
onMounted(() => {
setTimeout(() => {
console.log("Changing input prop value after 3s delay");
testValue.value = "changed";
// This value change does not trigger watchers?
}, 3000);
});
return {
testValue
};
}
});
</script>
<template>
<select v-model="selected">
<option value="null">null value</option>
<option value>Empty value</option>
</select>
</template>
<script>
import { createComponent, watch } from "@vue/composition-api";
export default createComponent({
name: "MyInput",
props: {
selected: {
type: [String, Number],
required: true
}
},
setup(props) {
console.log("Setup props:", props);
watch((first, second) => {
console.log("Watch function called with args:", first, second);
// First arg function registerCleanup, second is undefined
});
// watch(props, (first, second) => {
// console.log("Watch props function called with args:", first, second);
// // Logs error:
// // Failed watching path: "[object Object]" Watcher only accepts simple
// // dot-delimited paths. For full control, use a function instead.
// })
watch(props.selected, (first, second) => {
console.log(
"Watch props.selected function called with args:",
first,
second
);
// Both props are undefined so its just a bare callback func to be run
});
return {};
}
});
</script>
EDIT: Although my question and code example was initially with JavaScript, I'm actually using TypeScript. Tony Tom's first answer although working, lead to a type error. Which was solved by Michal Levý's answer. So I've tagged this question with typescript
afterwards.
EDIT2: Here is my polished yet barebones version of the reactive wirings for this custom select component, on top of <b-form-select>
from bootstrap-vue
(otherwise agnostic-implementation but this underlying component does emit @input and @change events both, based on whether change was made programmatically or by user interaction).
<template>
<b-form-select
v-model="selected"
:options="{}"
@input="handleSelection('input', $event)"
@change="handleSelection('change', $event)"
/>
</template>
<script lang="ts">
import {
createComponent, SetupContext, Ref, ref, watch, computed,
} from '@vue/composition-api';
interface Props {
value?: string | number | boolean;
}
export default createComponent({
name: 'CustomSelect',
props: {
value: {
type: [String, Number, Boolean],
required: false, // Accepts null and undefined as well
},
},
setup(props: Props, context: SetupContext) {
// Create a Ref from prop, as two-way binding is allowed only with sync -modifier,
// with passing prop in parent and explicitly emitting update event on child:
// Ref: https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
// Ref: https://medium.com/@jithilmt/vue-js-2-two-way-data-binding-in-parent-and-child-components-1cd271c501ba
const selected: Ref<Props['value']> = ref(props.value);
const handleSelection = function emitUpdate(type: 'input' | 'change', value: Props['value']) {
// For sync -modifier where 'value' is the prop name
context.emit('update:value', value);
// For @input and/or @change event propagation
// @input emitted by the select component when value changed <programmatically>
// @change AND @input both emitted on <user interaction>
context.emit(type, value);
};
// Watch prop value change and assign to value 'selected' Ref
watch(() => props.value, (newValue: Props['value']) => {
selected.value = newValue;
});
return {
selected,
handleSelection,
};
},
});
</script>
If you take a look at watch
typing here it's clear the first argument of watch
can be array, function or Ref<T>
props
passed to setup
function is reactive object (made probably by reactive()
), it's properties are getters. So what you doing is passing the value of the getter as the 1st argument of watch
- string "initial" in this case. Because Vue 2 $watch
API is used under the hood (and same function exists in Vue 3), you are effectively trying to watch non-existent property with name "initial" on your component instance.
Your callback is called only once and never again. Reason it is called at least once is because new watch
API is behaving like current $watch
with immediate
option (UPDATE 03/03/2021 - this was later changed and in release version of Vue 3, watch
is lazy same way as it was in Vue 2)
So by accident you doing the same thing Tony Tom suggested but with wrong value. In both cases it's not valid code if you are using TypeScript
You can do this instead:
watch(() => props.selected, (first, second) => {
console.log(
"Watch props.selected function called with args:",
first,
second
);
});
Here the 1st function is executed immediately by Vue to collect dependencies (to know what should trigger the callback) and 2nd function is the callback itself.
Other way would be to convert props object using toRefs
so it's properties would be of type Ref<T>
and you can pass them as a 1st argument of watch
这篇关于如何使用 Vue Composition API/Vue 3 观察道具变化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!