简体   繁体   English

如何使用 Vue 组合 API / Vue 3 观察道具变化?

[英]How to Watch Props Change with Vue Composition API / Vue 3?

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 ?虽然Vue Composition API RFC Reference 站点有很多高级的watch模块使用场景,但没有关于如何观看组件 props的示例?

Neither is it mentioned in Vue Composition API RFC's main page or vuejs/composition-api in Github . Vue Composition API RFC 的主页Github 中的 vuejs/composition-api 中也没有提到它。

I've created a Codesandbox to elaborate this issue.我创建了一个Codesandbox来详细说明这个问题。

<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.编辑:虽然我的问题和代码示例最初是使用 JavaScript,但我实际上使用的是 TypeScript。 Tony Tom's first answer although working, lead to a type error.托尼汤姆的第一个答案虽然有效,但会导致类型错误。 Which was solved by Michal Levý's answer. Michal Levý的回答解决了这个问题。 So I've tagged this question with typescript afterwards.所以我后来用typescript标记了这个问题。

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) . EDIT2 :这是我在bootstrap-vue<b-form-select>之上的自定义 select 组件的反应布线的抛光但准系统版本(否则不可知论实现,但这个底层组件确实发出 @input 和 @change 事件两者都基于更改是通过编程方式还是通过用户交互进行的)

<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://v2.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>如果你看一下watch typing here ,很明显watch的第一个参数可以是数组、函数或Ref<T>

props passed to setup function is reactive object (made probably by reactive() ), it's properties are getters.传递给setup函数的props是响应式对象(可能由reactive()制作),它的属性是 getter。 So what you doing is passing the value of the getter as the 1st argument of watch - string "initial" in this case.所以你所做的是将getter的值作为watch的第一个参数传递——在这种情况下是字符串“initial”。 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.因为 Vue 2 $watch API是在后台使用的(并且相同的函数存在于 Vue 3 中),所以您实际上是在尝试在组件实例上查看名称为“initial”的不存在的属性。

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)它至少被调用一次的原因是因为新的watch API 的行为类似于当前的$watch带有immediate选项(更新 03/03/2021 - 后来更改了,在 Vue 3 的发布版本中, watch的惰性与它在视图 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在这两种情况下,如果您使用的是 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.这里第一个函数由 Vue 立即执行以收集依赖项(以了解应该触发回调的内容),第二个函数是回调本身。

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另一种方法是使用toRefs转换 props 对象,因此它的属性将是Ref<T>类型,您可以将它们作为watch的第一个参数传递

I just wanted to add some more details to the answer above.我只是想在上面的答案中添加更多细节。 As Michal mentioned, the props coming is an object and is reactive as a whole.正如 Michal 所提到的,即将到来的props是一个对象,并且作为一个整体是反应性的。 But, each key in the props object is not reactive on its own.但是,props 对象中的每个键本身都不是响应式的。

We need to adjust the watch signature for a value in the reactive object compared to a ref valueref值相比,我们需要调整reactive对象中的值的watch签名

// watching value of a reactive object (watching a getter)

watch(() => props.selected, (selection, prevSelection) => { 
   /* ... */ 
})
// directly watching a ref

const selected = ref(props.selected)

watch(selected, (selection, prevSelection) => { 
   /* ... */ 
})

Just some more info even though it's not the mentioned case in the question: If we want to watch on multiple properties, one can pass an array instead of a single reference只是一些更多的信息,即使它不是问题中提到的情况:如果我们想观察多个属性,可以传递一个数组而不是单个引用

// Watching Multiple Sources

watch([ref1, ref2, ...], ([refVal1, refVal2, ...],[prevRef1, prevRef2, ...]) => { 
   /* ... */ 
})

This does not address the question of how to "watch" properties.这并没有解决如何“观察”属性的问题。 But if you want to know how to make props responsive with Vue's Composition API, then read on.但是如果你想知道如何使用 Vue 的 Composition API 使 props 响应,那么请继续阅读。 In most cases you shouldn't have to write a bunch of code to "watch" things (unless you're creating side effects after changes).在大多数情况下,您不必编写一堆代码来“观察”事物(除非您在更改后创建副作用)。

The secret is this: Component props IS reactive.秘诀是:组件props是反应式的。 As soon as you access a particular prop, it is NOT reactive.一旦您访问特定的道具,它就不是被动的。 This process of dividing out or accessing a part of an object is referred to as "destructuring".这种划分或访问对象的一部分的过程称为“解构”。 In the new Composition API you need to get used to thinking about this all the time--it's a key part of the decision to use reactive() vs ref() .在新的 Composition API 中,您需要习惯于一直考虑这一点——这是决定使用reactive()还是ref()的关键部分。

So what I'm suggesting (code below) is that you take the property you need and make it a ref if you want to preserve reactivity:所以我的建议(下面的代码)是,如果你想保持反应性,你可以获取你需要的属性并将其设为ref

export default defineComponent({
  name: 'MyAwesomestComponent',
  props: {
    title: {
      type: String,
      required: true,
    },
    todos: {
      type: Array as PropType<Todo[]>,
      default: () => [],
    },
    ...
  },
  setup(props){ // this is important--pass the root props object in!!!
    ...
    // Now I need a reactive reference to my "todos" array...
    var todoRef = toRefs(props).todos
    ...
    // I can pass todoRef anywhere, with reactivity intact--changes from parents will flow automatically.
    // To access the "raw" value again:
    todoRef.value
    // Soon we'll have "unref" or "toRaw" or some official way to unwrap a ref object
    // But for now you can just access the magical ".value" attribute
  }
}

I sure hope the Vue wizards can figure out how to make this easier... but as far as I know this is the type of code we'll have to write with the Composition API.我当然希望 Vue 向导能够弄清楚如何使这更容易……但据我所知,这是我们必须使用 Composition API 编写的代码类型。

Here is a link to the official documentation , where they caution you directly against destructuring props.这是官方文档的链接,他们直接警告您不要破坏道具。

Change your watch method like below.更改您的观看方法,如下所示。

 watch("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
    });

In my case I solved it using key就我而言,我使用key解决了它

<MessageEdit :key="message" :message="message" />

Maybe on your case would look something like this也许在你的情况下看起来像这样

<PropWatchDemo :key="testValue" :selected="testValue"/>

But I don't have any idea of its pros and cons versus watch但我不知道它与watch的优缺点

None of the options above worked for me but I think I found a simple way that seems to works very well to keep vue2 coding style in composition api上面的选项都不适合我,但我认为我找到了一种简单的方法,它似乎可以很好地在组合 api 中保持 vue2 编码风格

Simply create a ref alias to the prop like:只需为 prop 创建一个ref别名,例如:

myPropAlias = ref(props.myProp)

and you do everything from the alias你用别名做所有事情

works like a charm for me and minimal对我来说就像一个魅力和最小的工作

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM