1. Calculate attributes

Computed attributes (computed) mainly calculate another kind of data based on existing data. If the data on which the calculated property depends changes, the calculation is recalculated. Computed properties cannot be modified, and asynchronous operations cannot be performed in computed properties.

Example 1.

Let’s start with a set of examples where 100 integrals are equal to 1 dollar, and now implement entering the number of integrals, calculating the equivalent amount, and leaving two decimal places.

<script setup lang="ts"> import { ref, computed } from 'vue'; const point = ref<string | number>(''); const amount = computed(() => { return (+point.value / 100).toFixed(2); }); </script> <template> <div> <span> </span> <input type="number" V-model ="point" placeholder=" placeholder" {{ amount }}</span> </div> </template>Copy the code

Look at the results:

As you can see, when we change the value of the variable point, the value of amount is automatically computed and the view is refreshed.

Let’s do a slightly more complex example to help you understand computed properties. When the user enters the ID number, the date of birth is automatically calculated:

<script setup lang="ts"> import { ref, computed } from 'vue'; const idNo = ref(''); const birth = computed(() => { if (idNo.value.length ! == 18 || isNaN(Number(idNo.value))) { return ''; } let t = idNo.value; let year = t.slice(6, 10); let month = t.slice(10, 12); let day = t.slice(12, 14); return `${year}-${month}-${day}`; }); </script> <template> <div> <span> </span> <div> <span> <span> </span> <span>{{ birth }}</span> </div> </template>Copy the code

View the effect:

2. set & get

Computed properties only have getters by default, although you can provide a setter if needed

<script setup lang="ts"> const count = ref(1); const plusOne = computed({ set: (v) => { console.log(v); count.value = v - 1; }, get: () => { return count.value + 1; }}); plusOne.value = 10; </script>Copy the code

3. Calculate properties vs. methods

Methods can be used to evaluate properties. In the end, the two implementations are exactly the same. However, the difference is that computed attributes will be cached based on their response dependencies. Evaluated properties are reevaluated only when the associated reactive dependencies change. For example, in example 1, as long as the count has not changed, multiple visits to the amount property will immediately return the previous calculation without having to execute the function again. By contrast, the calling method will always execute the function again whenever a rerender is triggered. In terms of performance, computed properties are better than methods.

Why do we need caching? Let’s say we have a list of calculated attributes that are expensive in performance and need to iterate through a huge array and do a lot of calculations. And then we might have other computed properties that depend on the list. Without caching, we would inevitably execute the getter for list multiple times! If you don’t want caching, use method instead.

2. Listeners

While computing properties is more appropriate in most cases, sometimes a custom listener is required. That’s why Vue provides a more generic way to respond to changes in data with the Watch option. This approach is most useful when asynchronous or expensive operations need to be performed when data changes.

1. watch

Example:

<script setup lang="ts"> import { ref, reactive, watch } from 'vue'; // refs const name = ref('Muzili'); const age = ref(18); const tel = ref('15999999999'); Const otherName = reactive({firstName: 'reactive ',' reactive ',}); // methods const fullName = () => otherName.firstName + otherName.lastName; / / watchs / / 1. Listen to specify attributes watch (name, (v, o = > {the console. The log (` new value: ${n}, the old value: ${o} `); }); Watch (fullName, (v) => {console.log(' I call ${v}. '); }); / / 3. Listening watch multiple attributes change ([age, tel], [[v1, v2], [o1, o2]) = > {the console. The log (` age - > new value: ${v1} old value: ${o1} `); Console. log(' tel -> new value: ${v2} Old value: ${O2} '); }); SetTimeout (() => {name. Value = 'name.value '; Othername. firstName = 'zhang '; age.value = 28; tel.value = '15888888888'; }, 1000); </script> <template></template>Copy the code

Output:

Age -> New value: 28 Old value: 18 tel -> New value: 15888888888 Old value: 15999999999Copy the code

2. watchEffect

To automatically apply and reapply side effects based on reactive state, we can use the watchEffect function. It executes a function passed in immediately, tracing its dependencies responsively, and rerunking the function when its dependencies change.

// refs
const page = ref(1);
const pageSize = ref(10);
// effects
watchEffect(() = > {
  console.log('Request Data -> Page:${page.value}, number of items per page:${pageSize.value}`);
});
// simulate data modification
setTimeout(() = > {
  page.value = 2;
}, 1000);
Copy the code

2.1. Stop listening

When watchEffect is called on a component’s setup() function or lifecycle hook, the listener is linked to the component’s lifecycle and stops automatically when the component is uninstalled.

In some cases, it is also possible to explicitly call the return value to stop listening:

const stop = watchEffect(() = > {
  / *... * /
})

// later
stop()
Copy the code

2.2. Removal of side effects

Sometimes side effects functions perform asynchronous side effects, and these responses need to be cleared when they fail (that is, the state has changed before completion). So a function that listens for incoming side effects can take an onInvalidate function as an input parameter to register a callback in the event of a cleanup failure. This invalidation callback is triggered when:

  • When the side effect is about to be re-executed
  • The listener is stopped (if insetup()Or when watchEffect is used in the lifecycle hook function)
watchEffect(onInvalidate= > {
  const token = performAsyncOperation(id.value)
  onInvalidate(() = > {
    // id has changed or watcher is stopped.
    // invalidate previously pending async operation
    token.cancel()
  })
})
Copy the code

The reason we register the invalidation callback by passing in a function rather than returning it from the callback is because the return value is important for asynchronous error handling.

When performing a data request, the side effect function is usually an asynchronous function:

const data = ref(null)
watchEffect(async onInvalidate => {
  onInvalidate(() = > {
    / *... * /
  }) // We registered the cleanup function before the Promise resolution
  data.value = await fetchData(props.id)
})
Copy the code

We know that asynchronous functions implicitly return a Promise, but the cleanup function must be registered before the Promise can be resolved. In addition, Vue relies on this returned Promise to automatically handle potential errors in the Promise chain.

2.3. Refresh timing

Vue’s responsive system caches side effects functions and refreshes them asynchronously to avoid unnecessary repeated calls due to multiple state changes in the same “tick.” In the concrete implementation of the core, the component’s update function is also a monitored side effect. When a user-defined side effect function is queued, by default it is executed before all component updates:

<script setup lang="ts">
import { ref, watchEffect } from 'vue';
const count = ref(0);
watchEffect(() => {
  console.log(count.value);
});

</script>

<template>
  <div>{{ count }}</div>
</template>
Copy the code

In this example:

  • countWill be printed synchronously at initial run time
  • To change thecountWill be in the componentBefore the updateExecutive side effects.

If you need to re-run the listener side effect after a component update (e.g., when with a template reference), you can pass the additional options object with flush option (default ‘pre’) :

// Emitted after the component is updated so you can access the updated DOM.
// Note: This will also delay the initial running of side effects until the first rendering of the component is complete.
watchEffect(
  () = > {
    / *... * /
  },
  {
    flush: 'post'})Copy the code

The Flush option also accepts sync, which forces the effect to always fire synchronously. However, this is inefficient and should rarely be needed.

Starting with Vue 3.2.0, the watchPostEffect and watchSyncEffect aliases can also be used to make code intent more obvious.

3. Listener vs. computed properties

Vue provides a more general way to observe and respond to changes in data on a currently active instance: listening properties. Watch is easy to abuse when you have some data that needs to change with other data — especially if you’ve used AngularJS before. However, it is often better to use computed properties rather than imperative Watch callbacks.