Vue.js - Как правильно следить за вложенными данными
Я пытаюсь понять, как правильно наблюдать за некоторым изменением опоры. У меня есть родительский компонент (файлы.vue), который получает данные от вызова ajax, помещает данные в объект и использует его для визуализации некоторого дочернего компонента с помощью директивы v-for, что ниже упрощения моей реализации:
<template>
<div>
<player v-for="(item, key, index) in players"
:item="item"
:index="index"
:key="key"">
</player>
</div>
</template>
... тогда внутри <script>
тег:
data(){
return {
players: {}
},
created(){
let self = this;
this.$http.get('../serv/config/player.php').then((response) => {
let pls = response.body;
for (let p in pls) {
self.$set(self.players, p, pls[p]);
}
});
}
Объекты item выглядят так:
item:{
prop: value,
someOtherProp: {
nestedProp: nestedValue,
myArray: [{type: "a", num: 1},{type: "b" num: 6} ...]
},
}
Теперь внутри моего дочернего компонента "player" я пытаюсь следить за изменением свойств любого элемента и использую:
...
watch:{
'item.someOtherProp'(newVal){
//to work with changes in "myArray"
},
'item.prop'(newVal){
//to work with changes in prop
}
}
Это работает, но мне кажется, что это немного сложно, и мне было интересно, если это правильный способ сделать это. Моя цель - каждый раз совершать какие-то действия prop
изменения или myArray
получает новые элементы или некоторые изменения внутри существующих. Любое предложение будет оценено.
17 ответов
Вы можете использовать глубокий наблюдатель для этого:
watch: {
item: {
handler(val){
// do stuff
},
deep: true
}
}
Это теперь обнаружит любые изменения объектов в item
массив и дополнения к самому массиву (при использовании с Vue.set). Вот JSFiddle: http://jsfiddle.net/je2rw3rs/
РЕДАКТИРОВАТЬ
Если вы не хотите следить за каждым изменением объекта верхнего уровня и просто хотите использовать менее неудобный синтаксис для непосредственного просмотра вложенных объектов, вы можете просто посмотреть computed
вместо:
var vm = new Vue({
el: '#app',
computed: {
foo() {
return this.item.foo;
}
},
watch: {
foo() {
console.log('Foo Changed!');
}
},
data: {
item: {
foo: 'foo'
}
}
})
Вот JSFiddle: http://jsfiddle.net/oa07r5fw/
Еще один хороший и немного более элегантный подход заключается в следующем:
watch:{
'item.someOtherProp': function (newVal, oldVal){
//to work with changes in someOtherProp
},
'item.prop': function(newVal, oldVal){
//to work with changes in prop
}
}
(Я узнал этот подход от @peerbolte в комментарии здесь)
VueJs глубоко следит за дочерними объектами
new Vue({
el: "#myElement",
data: {
entity: {
properties: []
}
},
watch: {
'entity.properties': {
handler: function (after, before) {
// Changes detected. Do work...
},
deep: true
}
}
});
Лично я предпочитаю эту чистую реализацию:
watch: {
myVariable: {
handler(newVal, oldVal){ // here having access to the new and old value
// do stuff
},
deep: true,
immediate: true // Also very important the immediate in case you need it, the callback will be called immediately after the start of the observation
}
}
Как, если вы хотите какое-то время наблюдать за недвижимостью, а затем не смотреть ее?
Или посмотреть свойство дочернего компонента библиотеки?
Вы можете использовать "динамический наблюдатель":
this.$watch(
'object.property', //what you want to watch
(newVal, oldVal) => {
//execute your code here
}
)
$watch
возвращает функцию unwatch, которая прекращает просмотр, если она вызывается.
var unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()
Также вы можете использовать deep
опция:
this.$watch(
'someObject', () => {
//execute your code here
},
{ deep: true }
)
Пожалуйста, не забудьте взглянуть на документы
Еще один способ добавить, что я использовал "взломать" это решение, было сделать следующее: я настроил отдельный computed
значение, которое просто возвращает значение вложенного объекта.
data : function(){
return {
my_object : {
my_deep_object : {
my_value : "hello world";
}.
},
};
},
computed : {
helper_name : function(){
return this.my_object.my_deep_object.my_value;
},
},
watch : {
helper_name : function(newVal, oldVal){
// do this...
}
}
Отслеживание отдельных измененных элементов в списке
Если вы хотите просмотреть все элементы в списке и знать, какой элемент в списке был изменен, вы можете настроить специальные наблюдатели для каждого элемента отдельно, например:
var vm = new Vue({
data: {
list: [
{name: 'obj1 to watch'},
{name: 'obj2 to watch'},
],
},
methods: {
handleChange (newVal, oldVal) {
// Handle changes here!
// NOTE: For mutated objects, newVal and oldVal will be identical.
console.log(newVal);
},
},
created () {
this.list.forEach((val) => {
this.$watch(() => val, this.handleChange, {deep: true});
});
},
});
Если ваш список не заполняется сразу (как в исходном вопросе), вы можете убрать логику из created
туда, где это необходимо, например, внутри .then()
блок.
Наблюдая за изменяющимся списком
Если ваш список обновляется, и в него добавляются новые или удаленные элементы, я разработал полезный шаблон, который "поверхностно" наблюдает за самим списком и динамически отслеживает / отменяет отслеживание элементов по мере изменения списка:
// NOTE: This example uses Lodash (_.differenceBy and _.pull) to compare lists
// and remove list items. The same result could be achieved with lots of
// list.indexOf(...) if you need to avoid external libraries.
var vm = new Vue({
data: {
list: [
{name: 'obj1 to watch'},
{name: 'obj2 to watch'},
],
watchTracker: [],
},
methods: {
handleChange (newVal, oldVal) {
// Handle changes here!
console.log(newVal);
},
updateWatchers () {
// Helper function for comparing list items to the "watchTracker".
const getItem = (val) => val.item || val;
// Items that aren't already watched: watch and add to watched list.
_.differenceBy(this.list, this.watchTracker, getItem).forEach((item) => {
const unwatch = this.$watch(() => item, this.handleChange, {deep: true});
this.watchTracker.push({ item: item, unwatch: unwatch });
// Uncomment below if adding a new item to the list should count as a "change".
// this.handleChange(item);
});
// Items that no longer exist: unwatch and remove from the watched list.
_.differenceBy(this.watchTracker, this.list, getItem).forEach((watchObj) => {
watchObj.unwatch();
_.pull(this.watchTracker, watchObj);
// Optionally add any further cleanup in here for when items are removed.
});
},
},
watch: {
list () {
return this.updateWatchers();
},
},
created () {
return this.updateWatchers();
},
});
Я обнаружил, что это тоже работает:
watch: {
"details.position"(newValue, oldValue) {
console.log("changes here")
}
},
data() {
return {
details: {
position: ""
}
}
}
Не видя, что здесь упоминается, но также можно использовать vue-property-decorator
шаблон, если вы расширяете Vue
класс.
import { Watch, Vue } from 'vue-property-decorator';
export default class SomeClass extends Vue {
...
@Watch('item.someOtherProp')
someOtherPropChange(newVal, oldVal) {
// do something
}
...
}
Моя проблема с принятым ответом использования deep: true
в том, что при глубоком просмотре массива я не могу легко определить, какой элемент массива содержит изменение. Единственное ясное решение, которое я нашел, - это ответ, который объясняет, как создать компонент, чтобы вы могли наблюдать за каждым элементом массива индивидуально.
Я использовал deep:true, но обнаружил, что старое и новое значение в отслеживаемой функции всегда одинаковы. В качестве альтернативы предыдущим решениям я попробовал это, которое будет проверять любые изменения во всем объекте, преобразовывая его в строку:
created() {
this.$watch(
() => JSON.stringify(this.object),
(newValue, oldValue) => {
//do your stuff
}
);
},
Ни один из ответов для меня не работал. Фактически, если вы хотите наблюдать за вложенными данными с многократным вызовом компонентов. Поэтому они вызываются с разными реквизитами для их идентификации. Например<MyComponent chart="chart1"/> <MyComponent chart="chart2"/>
Мое обходное решение - создать дополнительную переменную состояния vuex, которую я вручную обновляю, чтобы указать на свойство, которое было обновлено последним.
Вот пример реализации Vuex.ts:
export default new Vuex.Store({
state: {
hovEpacTduList: {}, // a json of arrays to be shared by different components,
// for example hovEpacTduList["chart1"]=[2,6,9]
hovEpacTduListChangeForChart: "chart1" // to watch for latest update,
// here to access "chart1" update
},
mutations: {
setHovEpacTduList: (state, payload) => {
state.hovEpacTduListChangeForChart = payload.chart // we will watch hovEpacTduListChangeForChart
state.hovEpacTduList[payload.chart] = payload.list // instead of hovEpacTduList, which vuex cannot watch
},
}
Для любой функции компонента для обновления магазина:
const payload = {chart:"chart1", list: [4,6,3]}
this.$store.commit('setHovEpacTduList', payload);
Теперь для любого компонента, чтобы получить обновление:
computed: {
hovEpacTduListChangeForChart() {
return this.$store.state.hovEpacTduListChangeForChart;
}
},
watch: {
hovEpacTduListChangeForChart(chart) {
if (chart === this.chart) // the component was created with chart as a prop <MyComponent chart="chart1"/>
console.log("Update! for", chart, this.$store.state.hovEpacTduList[chart]);
},
},
Для тех, кто ищет Vue 3
import { watch } from 'vue';
...
...
watch(
() => yourNestedObject, // first param, your object
(currValue, prevValue) => { // second param, watcher callback
console.log(currValue, prevValue);
},
{ deep: true } // third param, for deep checking
);
Вы можете обратиться к документации здесь: https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watch
Еще один метод.
Не используйте функцию стрелки при этом. Вы не можете получить доступ к этому объекту.
watch:{
'item.someOtherProp': function(newValue, oldValue){
// Process
}
}
Вот способ написать наблюдателей для вложенных свойств:
new Vue({
...allYourOtherStuff,
watch: {
['foo.bar'](newValue, oldValue) {
// Do stuff here
}
}
});
Вы даже можете использовать этот синтаксис для асинхронных наблюдателей:
new Vue({
...allYourOtherStuff,
watch: {
async ['foo.bar'](newValue, oldValue) {
// Do stuff here
}
}
});
Теперь вы можете использовать это для API композиции часов vue 3:
watch(
() => item.someOtherProp,
someOtherProp => {
// Do some thing
}
);
Или используйте часы для вычисления значения:
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
https://vuejs.org/guide/essentials/watchers.html#deep-watchers
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// Note: `newValue` will be equal to `oldValue` here
// on nested mutations as long as the object itself
// hasn't been replaced.
},
deep: true
}
}
}