Перейти к содержимому

Как обновить computed vue js

  • автор:

Вычисляемые свойства и слежение

Встраиваемые в шаблоны выражения удобны, но могут предназначаться только для простых операций. При усложнении логики их труднее поддерживать:

div id="example"> 
{{ message.split('').reverse().join('') }}
div>

Такой шаблон уже не выглядит простым и декларативным. С первого взгляда и не скажешь, что он всего лишь отображает message задом наперёд. Ситуация станет ещё хуже, если эту логику потребуется использовать в нескольких местах шаблона.

На помощь здесь приходят вычисляемые свойства.

Простой пример

div id="example"> 
p>Изначальное сообщение: «{{ message }}» p>
p>Сообщение задом наперёд: «{{ reversedMessage }}» p>
div>
var vm = new Vue({ 
el: '#example',
data: {
message: 'Привет'
},
computed: {
// геттер вычисляемого значения
reversedMessage: function ( ) {
// `this` указывает на экземпляр vm
return this.message.split('').reverse().join('')
}
}
})

Мы определили вычисляемое свойство reversedMessage . Написанная нами функция будет использоваться как геттер свойства vm.reversedMessage :

console.log(vm.reversedMessage) // => 'тевирП'
vm.message = 'Пока'
console.log(vm.reversedMessage) // => 'акоП'

Вы можете открыть консоль и поиграть с примером самостоятельно. Значение vm.reversedMessage всегда зависит от значения vm.message .

В шаблонах можно обращаться к вычисляемым свойствам как и к обычным. Vue знает, что vm.reversedMessage зависит от vm.message , поэтому при обновлении vm.message обновятся и все зависящие от него элементы, в нашем случае обновится vm.reversedMessage . Самое важное — эту зависимость теперь мы указали декларативно: геттер вычисляемого свойства не имеет побочных эффектов, что упрощает понимание кода и его тестирование.

Кэширование вычисляемых свойств

Можно заметить, что такого же результата можно достичь и с помощью метода:

p>Сообщение задом наперёд: «{{ reverseMessage() }}» p> 
// в компоненте
methods: {
reverseMessage: function ( ) {
return this.message.split('').reverse().join('')
}
}

Вместо вычисляемого свойства, можно использовать ту же самую функцию в качестве метода. С точки зрения конечного результата, оба подхода делают одно и то же. Но есть важное отличие: вычисляемые свойства кэшируются, основываясь на своих реактивных зависимостях. Вычисляемое свойство пересчитывается лишь тогда, когда изменится одна из его реактивных зависимостей. Поэтому, пока message остаётся неизменным, многократное обращение к reversedMessage будет каждый раз возвращать единожды вычисленное значение, не запуская функцию вновь.

Обратите внимание, что следующее вычисляемое свойство никогда не обновится, поскольку Date.now() не является реактивной зависимостью:

computed: { 
now: function ( ) {
return Date.now()
}
}

Использование метода, напротив, будет запускать функцию всегда, при каждом обращении к нему.

Зачем нужно кэширование? Представьте, есть «дорогое» вычисляемое свойство A, требующее цикла по огромному массиву и выполняющее множество вычислений. И пусть ещё будут другие вычисляемые свойства, в свою очередь, зависящие от A. Без кэширования геттер A будет запускаться куда чаще необходимого! В тех же случаях, когда кэширования нужно избежать — используйте методы.

Вычисляемые свойства и слежение

Vue предоставляет и более общий способ наблюдения и реагирования на изменения данных в экземпляре: слежение за свойствами. Когда есть данные, которые необходимо обновлять при изменении других данных, возникает соблазн избыточно использовать только этот подход, особенно если привыкли к Angular. Но, как правило, лучше использовать вычисляемые свойства, а не императивный коллбэк в watch . Рассмотрим пример:

div id="demo">{{ fullName }} div> 
var vm = new Vue({ 
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})

Код выше — императивный и избыточный. Сравните с версией с использованием вычисляемого свойства:

var vm = new Vue({ 
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function ( ) {
return this.firstName + ' ' + this.lastName
}
}
})

Так гораздо лучше, не правда ли?

Сеттеры вычисляемых свойств

По умолчанию вычисляемые свойства работают только на чтение, но при необходимости можно также указать и сеттер:

// . 
computed: {
fullName: {
// геттер:
get: function ( ) {
return this.firstName + ' ' + this.lastName
},
// сеттер:
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// .

Теперь запись vm.fullName = ‘Иван Иванов’ вызовет сеттер, и vm.firstName и vm.lastName будут соответствующим образом обновлены.

Методы-наблюдатели

Хотя в большинстве случаев лучше использовать вычисляемые свойства, иногда необходимы пользовательские методы-наблюдатели. Поэтому Vue предоставляет более общий способ реагирования на изменения в данных через опцию watch . Эта возможность полезна для «дорогих» или асинхронных операций, выполняемых в ответ на изменение данных.

div id="watch-example"> 
p>
Задайте вопрос, на который можно ответить «да» или «нет»:
input v-model="question">
p>
p>{{ answer }} p>
div>
 



script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js">
script
>

script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js">
script>
script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'Пока вы не зададите вопрос, я не могу ответить!'
},
watch: {
// эта функция запускается при любом изменении вопроса
question: function (newQuestion, oldQuestion) {
this.answer = 'Ожидаю, когда вы закончите печатать. '
this.debouncedGetAnswer()
}
},
created: function ( ) {
// _.debounce — это функция lodash, позволяющая ограничить то,
// насколько часто может выполняться определённая операция.
// В данном случае мы ограничиваем частоту обращений к yesno.wtf/api,
// дожидаясь завершения печати вопроса перед отправкой ajax-запроса.
// Узнать больше о функции _.debounce (и её родственнице _.throttle),
// можно в документации: https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function ( ) {
if (this.question.indexOf('?') === -1) {
this.answer = 'Вопросы обычно заканчиваются вопросительным знаком. ;-)'
return
}
this.answer = 'Думаю. '
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Ошибка! Не могу связаться с API. ' + error
})
}
}
})
script>

Задайте вопрос, на который можно ответить «да» или «нет»:

В данном случае использование опции watch позволило выполнять асинхронную операцию (обращение к API), ограничивать частоту выполнения этой операции и устанавливать промежуточные состояния до получения ответа от сервера. Ничего из этого не удалось бы достичь с помощью вычисляемых свойств.

Кроме опции watch можно также использовать vm.$watch в императивном стиле.

Обнаружили ошибку или хотите добавить что-то своё в документацию? Измените эту страницу на GitHub! Опубликовано на Netlify .

Как обновить computed property в VUE при изменении элемента массива Array?

По клику на чекбокс изменяется элемент в группе(group), сам массив filterResultSelectionIds остается неизменным.
Как сделать так чтобы computed prop все равно обновлялось?

  • Вопрос задан более трёх лет назад
  • 3112 просмотров

5 комментариев

Средний 5 комментариев

А чему привязаны значения чекбоксов? Изменение значений как-то обрабатывается? Можете показать пример вывода чекбоксов?

Насколько я понимаю, this.filterResultSelection это часть зависимостей этого вычисляемого свойства. Когда массив изменяется, то и компьютед должен пересчитываться.
Если компьютед не реактивный, рекомендуют добавить реактивности. Завести какую- нибудь переменную, которая будет выполнять роль флага, например: isSelectionChanged = false. Типо на инпут повесите @change=»isSelectionChanged = true». Внутри компьютеда делаете проверку if (isSelectionChanged) и в тело if весь код поместить. В конце флаг опять надо на false менять.

alexbuki

Александр Буки @alexbuki Автор вопроса

nextGen, да, думал сделать так, но в итоге компьютед св-ва перенес методы и вызывал их при проверке на наличие элементов массива. Но вам спасибо.

Vue updating computed object, or updating the original?

I’m using the computed() method to add some data to a ref() array of objects. Using this computed array of objects works for reading data, for example using v-for, but it is ignored (nothing happens) when I’m trying to update the data, the example below shows the working vs not working code. In the useCart composable (see code below), I created a computedCartItems which maps the cart and adds the totalPrice for each item. Now in my index.vue file, I try to increase the amount for a cartItem, this works if I loop over the cart using

but it is ignored when using the computed object

useCart.js

const useCart = () => < const cart = ref([]) const computedCartItems = computed(() => < return cart.value.map(cartItem => < return < . cartItem, totalPrice: cartItem.amount * cartItem.price >>) >) return < cart, computedCartItems, >> export default useCart 

index.vue (not working, using computed ‘computedCartItems’ object)

 
>

index.vue (working, using original ‘cart’ object)

 
>

asked Jan 4, 2023 at 14:07
Miguel Stevens Miguel Stevens
8,894 19 19 gold badges 69 69 silver badges 128 128 bronze badges

Computeds are for creating derived data. You cannot modify a basic computed (you can create a computed with a getter and setter). You must modify the source data. I can try write this up into a proper answer — based on the docs — if you want?

Jan 4, 2023 at 15:08

2 Answers 2

TLDR; you’re updating values on a copy of your original object. They are not linked so the original object doesn’t receive the updated value.

Detailed anwser

Computeds are readonly. They are derivated data and should not be updated.
Because this is javascript, you can update the object attributes by reference, but you really shouldn’t, this is a bad practise leading to unclear side effects.

See the typescript type of computed:

export declare interface ComputedRef extends WritableComputedRef

So myComputed.value is readonly and cannot be assigned another value. You can still do myComputed.value.myProperty = ‘foo’ but, as mentioned, this is a bad practise.

More information on this on the official documentation

A possible solution

Create the totalPrice composable for each item, not for the entire cart, and assign the computed inside your item object.

const useItem = (reactiveItem) => < const totalPrice = computed(() =>reactiveItem.amount * reactiveItem.price) // Assign a new property in your item, which is the derived totalPrice reactiveItem.totalPrice = totalPrice return reactiveItem > const useCart = () => < const cart = ref([]) // Export a custom function to include the item and make it reactive + use composable (saves the final client from doing it) const addItem = (item) => < cart.value.push(useItem(reactive(item))) >return < cart, addItem >> const < cart, addItem >= useCart() function createItem() < addItem(< amount: 5, price: 10 >) > 

Check this online playground with a working example.

I’m sure there are other ways of doing it, this is only one. You could use watch to react for your cart changes for example.

answered Jan 4, 2023 at 15:27
6,630 2 2 gold badges 18 18 silver badges 45 45 bronze badges

The Core Issue

A computed ref is derived data: it represents your data in some way; you do not update it directly, you update its sources.

There is a section about this in the docs which explains the issue quite succinctly:

Avoid mutating computed value
The returned value from a computed property is derived state. Think of it as a temporary snapshot — every time the source state changes, a new snapshot is created. It does not make sense to mutate a snapshot, so a computed return value should be treated as read-only and never be mutated — instead, update the source state it depends on to trigger new computations.

In your non-working example, you are not trying to update the actual computed ref (which is not even possible; see the doc references at the end of the answer); you are updating properties of the ref’s value, which you can — but shouldn’t — do. However, aside from all the other problems, the computed will not update, as the total price is based on the original item in cart , not the one in the computed, meaning an update is never triggered (as cart is not changed).

If you instead modify the source ref ( cart ), the computed ref will update and the example will work:

  
>

A (Possibly) Better Way

While this works, it is quite possibly not the ideal way to go about solving your particular case. Every time an item is updated, the whole computed array and every item in it is recreated, which is very inefficient.

Instead, you can make the useCart composable only return the single cart ref along with some methods to manipulate the cart. You could do something like this:

import < ref, reactive, computed, readonly >from 'vue' const useCart = () => < const cart = ref([]) /** Add a new item to the cart. Makes the item reactive (so that there is a reactive source for computed properties), adds the `totalPrice` computed property, and appends it to the cart array. */ const addItem = (item) => < const reactiveItem = reactive(item) reactiveItem.totalPrice = computed(() =>reactiveItem.amount * reactiveItem.price) cart.value.push(reactiveItem) > /** Increase the amount of an item. You could add all kinds of methods like these. */ const increaseAmount = (id) => < const index = cart.value.findIndex((item) =>item.id === id) cart.value[index].amount += 1 > return < cart: readonly(cart), // So that the cart cannot be modified directly by the consumer. addItem, increaseAmount >> const < cart, addItem, increaseAmount >= useCart() addItem(< id: "1", amount: 5, price: 10 >) console.log(cart.value[0].totalPrice) // 50 

Now the handling of the cart is done by the useCart composable, making things easier for the consumer by abstracting away internals. In addition to the gains mentioned above, this also means that the composable remains in control of its data, as the cart ref cannot just be modified. «Separation of concerns», etc.

Documentation References and Such

Vue Docs

The whole point of computed refs is that they update automatically based on their sources. You do not modify them directly, you modify their sources.

A computed property automatically tracks its reactive dependencies. Vue is aware that the computation of publishedBooksMessage depends on author.books , so it will update any bindings that depend on publishedBooksMessage when author.books changes.

You cannot assign a value to a regular computed ref.

Computed properties are by default getter-only. If you attempt to assign a new value to a computed property, you will receive a runtime warning. In the rare cases where you need a «writable» computed property, you can create one by providing both a getter and a setter.

I highly recommend reading the «Reactivity Fundamentals» section of the Vue Guide. See especially «Ref Unwrapping in Reactive Objects» for some insight on how the nesting of the computed ref inside the reactive works.

I also suggest going through the entire «Reactivity in Depth» page when you’re ready. It gives you a grip on how the reactivity system actually works.

Other Links

VueUse is a great resource, both for many handy composables and for learning.

Как обновить данные в ‘newlevels’ computed?

Функция favorite меняет значение на сервере с true на false. Это работает на сервере, но не обновляется свойство x.favorite в моём `newlevels` computed.

import axios from 'axios' const state = < level: <>, levels: [], question: <>, questions: [], userlevels: [] > const actions = < // это я получаю когда логинюсь в моё app, к этому притензий нет LEVELS: () => < axios.get('/ef/levels/') .then(response =>< commit('LEVELS', response.data) >) .catch(error => < console.log(error, 'something went wront') >) >, // это я диспатчу в компоненте USERLEVELS: () => < axios.get('/ef/userlevel/') .then(response =>< console.log('I am from USERLEVELS', response.data) commit('USERLEVELS', response.data) >) .catch(error => < console.log(error, 'something went wrong') >) >, // сдесь я отправляю на сервер true или false // оно меняется на сервере, но не меняется в vue.js USERLEVELS_PATCH: (, payload) => < axios.patch('/ef/userlevel/' + payload.id + '/', payload.data) .then(response =>< console.log('I am from USERLEVELS_PATCH', response.data) >) > > const mutations = < LEVELS: (state, data) =>< state.levels = data >, USERLEVELS: (state, data) => < state.userlevels = data >, > const getters = < levels: state =>state.levels, userlevels: state => state.userlevels > export default

В компоненте в computed я соединяю levels и userlevels вместе. Оно работает, но по клику на иконку `star` я не могу обновить свойство favorite на true или false(тернарка). Вот мой computed и methods:

computed: < . mapGetters('ef', ["levels", "userlevels"]), newlevels: function () < this.levels.forEach(a => < this.userlevels.forEach(b => < if (a.id === b.level) < a['favorite'] = b.favorite a['complete'] = b.complete a['progress'] = b.progress a['userlevel_id'] = b.id >else < a['favorite'] = a.favorite || false a['complete'] = b.complete a['progress'] = b.progress || 0; >>) >) return this.levels > >, methods: < favorite(level) < let let data = this.$store.dispatch('ef/USERLEVELS_PATCH', ) >, created() < this.$store.dispatch("ef/USERLEVELS") >,

И также мой templete

    > star >  

Нужно как то обновить свойство `favorite` в `newlevels` в computed после клика на иконку.

  • Вопрос задан более трёх лет назад
  • 90 просмотров

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *