Vue: [функция] Возможность отключить наблюдение за Vue

Созданный на 7 апр. 2016  ·  50Комментарии  ·  Источник: vuejs/vue

Обновлять:
Если кому-то понадобится эта функциональность, я выпустил ее как vue-nonreactive с соответствующими предупреждениями и всем остальным.


У нас есть несколько непростых моделей, в которых нам нужно отключить наблюдение и ходьбу Vue. Примером является модель ресурсов, у которой есть доступ к кешу, чтобы она могла искать связанные ресурсы. Это приводит к тому, что все объекты в кэше становятся отслеживаемыми (возможно, неэффективными), а также некоторым дополнительным взаимодействиям с другим кодом. В настоящее время мы обходим это, устанавливая фиктивный Observer в кеш. Что-то похожее...

import get from 'http';
import Resource from 'resource';


new Vue({
    data: { instance: {}, },
    ready() { this.fetch(); },

    methods: {
        fetch() {
            const Observer = Object.getPrototypeOf(this.instance.__ob__).constructor;

            get('/api/frobs')
            .then(function(data) {
                // initialize Resource w/ JSON document
                const resource = new Resource(data);

                // Protect cache with dummy observer
                resource.cache.__ob__ = new Observer({});

                this.instance = resource;
            });
        },
    },
});

Это работает, но

  • полагается на внутренности vue
  • требуется уже наблюдаемый объект, поскольку мы не можем напрямую импортировать класс Observer .

Предложение:
Добавьте официальный метод для явного отключения наблюдения / ходьбы во Vue. например, что-то вроде ...

const someThing = {
  nestedThing: {},
};

// make entire object non-reactive
Vue.nonreactive(someThing);

// make nested object non-reactive
Vue.nonreactive(someThing.nestedThing);
vm.$set('key.path', someThing);

Соображения:

  • Что должно произойти, если пользователь установит путь реактивного ключа к нереактивному объекту? Должен ли vue предупреждать пользователя? например,

`` js
vm. $ set ('а', Vue.nonreactive ({});

// отличается от..
vm. $ set ('a', {
someKey: Vue.nonreactive ({}),
});
`` ''

  • Должен ли уже реактивный объект предупреждать пользователя, если его попытаются сделать нереактивным? например,

`` js
// ошибка
Vue.nonreactive (vm. $ Data.a)

// действительный
Vue.nonreactive (_. Clone (vm. $ Data.a));
`` ''

Самый полезный комментарий

  1. Если вам нужно пропустить наблюдение за объектом / массивом в data , используйте на нем Object.freeze() ;
  2. Вам не нужно помещать объект в data , чтобы получить к нему доступ в this . Если вы просто прикрепите его к this в хуке created() , это вообще не будет замечено.

Все 50 Комментарий

Разве Object.freeze() не сработает в вашем случае? Поддерживается с версии 1.0.18.

  1. Если вам нужно пропустить наблюдение за объектом / массивом в data , используйте на нем Object.freeze() ;
  2. Вам не нужно помещать объект в data , чтобы получить к нему доступ в this . Если вы просто прикрепите его к this в хуке created() , это вообще не будет замечено.
  • Object.freeze здесь не работает. Кеш обновляется со временем.
  • Главный ресурс _is_ реактивный. Меня больше всего интересует сделать объект вложенного кеша нереактивным.

Тогда, может быть, пришло время переосмыслить дизайн вашей модели. Зачем вкладывать эти вещи в то, что нужно наблюдать?

Потому что кеш используется для динамического поиска связанных ресурсов.

например, у нас могут быть модели Author и Post . Модель автора определяет отношение ко многим, называемое posts с моделью публикации. Этот кеш содержит данные о взаимосвязях, а также связанную коллекцию.

вызов author.posts получает сообщения из кеша.

По замыслу Vue не рекомендует помещать сложные объекты с их собственным механизмом изменения состояния в data экземпляра Vue. Вы должны помещать только чистое состояние в качестве наблюдаемых данных в экземпляры Vue. Вы можете манипулировать этим состоянием как хотите, но объекты, ответственные за такие манипуляции, не должны быть частью состояния экземпляра Vue.

Во-первых, уточняющий вопрос - что именно вы подразумеваете под чистым состоянием ? У нас есть два типа состояния:

  • состояние модели (постоянное, данные синхронизированы с магазином, например, задача)
  • состояние vue (временные, данные, которые контролируют поведение просмотра, например, свертывание / отображение списка задач)

Но все таки:
Это честно. Модель определенно «сложная», поэтому этот запрос идет вразрез с текущими передовыми практиками. Кроме того, мой первоначальный пример не очень хорош - это просто то, что сработало, чтобы отключить наблюдение. Это более характерно для нашей текущей настройки с возможным использованием:

<!-- layout -->
<post :post="post"></post>
<author :author="author" ><author>
<comments :comments="comments"></comments>
import post from 'components/post';
import author from 'components/author';
import comments from 'components/comments';
/* post = {
 *     template: '...'
 *     props: ['post'],
 *     data: () => {collapsed: false},
 *     ...
 * };  */

new Vue({
    el: 'body',
    data() { 
        instance = postStore.fetch({include: ['author', 'comments.author']})
        Vue.nonreactive(instance.cache)

        return {post: instance, },
    },
    components: {
        post,
        author,
        comments,
    },
    ...
});

По сути, у нас есть родительский vue, отвечающий за размещение повторно используемых компонентов в макете и выборку / привязку данных к связанным компонентам. Дочерние компоненты не получают свои собственные данные, потому что данные различаются в разных контекстах. например, список комментариев _пользователя_ по сравнению со списком комментариев _поста_.

Модель довольно «тупая» за исключением того, что связанные объекты не вложены ( {post: {author: {}, comments: []}} ), а вместо этого ищутся из кеша. например, post.comments[2].author может быть тем же самым объектом, что и post.author . Итак, вместо того, чтобы иметь несколько копий объекта-автора, у нас есть только одна, которая просматривается из кеша. В приведенном выше описании нет никаких изменений - все данные создаются при первоначальной выборке.

Кроме того, это не значит, что запрос больше актуален, но альтернативой может быть отказ от наблюдения за «частными» членами объекта. Это могут быть элементы с ведущим одинарным или, возможно, двойным подчеркиванием. Обратной стороной этого подхода является то, что он нарушит правила.

Если кому-то понадобится эта функциональность, я выпустил ее как vue-nonreactive с соответствующими предупреждениями и всем остальным.

@rpkilby спасибо, что поделились!

@rpkilby Один из способов я удаляю наблюдаемую / реактивность

var newObj = JSON.parse(JSON.stringify(obj))

Действительно полезно, так как я хочу сохранить массив «состояний» и реализовать объект истории состояний в vuex.

Изменить : это решение было специально для моего случая. У меня был объект, в котором мне требовалась только копия значений свойств в определенный момент времени. Меня не волновали ссылки, динамические обновления и т. Д.

Сейчас замораживание объекта - не долгосрочное решение. [Vue-nonreactive] имеет Vue в качестве зависимости, что является излишним, когда дело доходит до выполнения чего-то прямого. Разве не будет достаточно простой проверки кода, такого как instance.__ob__ !== false ? Это позволит библиотекам гарантировать, что такие вещи, как кеши, не будут замечены.

class Unobservable {
  construtor() {
    Object.defineProperty(this, '__ob__', {  
      enumerable: false,  configurable: false,
      writable: false, value: false,
    });
  }
}

В основном это проблема для библиотек, используемых в приложениях Vue (по крайней мере, для меня).

Как указать Vue, чтобы он смотрел (defineProperty) только на одноуровневую глубину данных?

В моем случае я хочу, чтобы Vue получал уведомление при изменении data.curObj ,
но не с curObj.position , curObj.rotation и т. д.

Раньше я использовал Object.freeze , но в этом случае это вызывало бы ошибку, когда three.js пыталась присвоить значения объекту.

Должен ли я делать следующее?
(на самом деле я делал это в другом подобном месте)

data () {
  return {
    wrapper: Object.freeze({
      actual: [bigData]
    })
  }
},
methods: {
  operation () {
    this.wrapper = Object.freeze({
      actual: [newBigData]
    })
  }
}

// core/observer/watch.js
function _traverse (val: any, seen: ISet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || !Object.isExtensible(val)) {
    return
  }
  // ...
// core/observer/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  // ...
> curObj PerspectiveCamera {uuid: "BD3C14DF-8C2B-4B96-9900-B3DD0EAC1163", name: "PerspectiveCamera", type: "PerspectiveCamera", parent: null, children: Array(0), …}

> Lodash.isPlainObject(curObj) false
> Vue.isPlainObject(curObj) true
  1. Можно ли добавить еще одно условие для отключения пользователем наблюдения, кроме как Object.isExtensible ( Object.freeze )?
  2. Можем ли мы улучшить обнаружение Vue.isPlainObject?

Вы можете использовать деструктуризацию

var newObj = { ...obj };

Это должно исправить. Это заставит метод isPlainObject вернуть false.

/**
 * Makes an object and it's children unobservable by frameworks like Vuejs
 */
class Unobservable {
  /**
   * Overrides the `Object.prototype.toString.call(obj)` result
   * <strong i="6">@returns</strong> {string} - type name
   * <strong i="7">@see</strong> {<strong i="8">@link</strong> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag}
   */
  get [Symbol.toStringTag]() {
    // Anything can go here really as long as it's not 'Object'
    return 'ObjectNoObserve';
  }
}
>> Object.prototype.toString.call(new Unobservable());
   "[object ObjectNoObserve]"

Привет всем, один момент, который был упущен в ответах, заключается в том, что данные в исходном комментарии не являются чистыми . В моем случае у меня есть экземпляр модели с частной ссылкой на кеш поиска отношений. Например, article может искать свои author или comments . Когда я вызываю article.author , это динамический поиск свойств в этом кэше отношений, а не простой доступ к атрибутам. Несколько вещей, которые следует учитывать:

  • Кеш - это не чистое состояние, поэтому я не хочу, чтобы Vue наблюдал за ним, поскольку это пустая трата ресурсов.
  • Кеш не может быть отброшен, так как мне все еще нужна ссылка для выполнения этих динамических поисков / обновлений.
  • Кэш фактически является одноэлементным и может обновляться извне. Приложение обновляется соответственно.

В ответ на некоторые предложения:

  • Использование JSON stringify / parse или деструктурирования объекта недостаточно, поскольку это одновременно дублирует объект и кеш, а также нарушает ссылку на исходный кеш. Обновление исходной ссылки на кеш больше не будет обновлять экземпляры приложения. Без этих динамических поисков и обновлений кэш в принципе бессмысленен, и было бы лучше сделать связанные объекты простыми атрибутами исходной модели. Также стоит отметить, что эти предложения нарушают методы экземпляров этих объектов.
  • Предложения @Mechazawa имеют смысл, если вы управляете созданием типов, а типы созданы для использования с Vue. В моем случае это внешняя библиотека, не привязанная к Vue, и я не хочу утруждать себя изменением типов в приложении. Слою vue гораздо проще просто пометить определенные известные свойства как ненаблюдаемые.

Моя единственная критика:

Vue-nonreactive имеет Vue в качестве зависимости, что является излишним, когда дело доходит до того, чтобы делать что-то прямолинейное.

Я не уверен, почему это было бы плохо? Вы уже используете Vue в своем приложении, а плагин специфичен для Vue. Если я не ошибаюсь, большинство инструментов сборки достаточно умны, чтобы не создавать пакеты с повторяющимися зависимостями. Тем не менее, это неверно . Есть зависимость от разработчика, но не от времени выполнения.


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

Итак, я недавно столкнулся с этим и обнаружил, что есть гораздо более простой способ сократить логику наблюдения Vue: _Определить свойство как ненастраиваемое.

Задний план

В моем приложении мне приходится работать со сторонней библиотекой (OpenLayers), которая создает экземпляры классов, хранящих данные, и не поддерживает какую-либо систему реактивности. Попытка втиснуть кого-то в нее вызвала столько головных болей, позвольте мне вам сказать. Итак, единственное жизнеспособное решение для крупномасштабного приложения, использующего эту библиотеку, - это позволить OpenLayers иметь вещи так, как они хотят, а для меня сделать Vue лучше с этими ужасно вложенными сверхобъектами гибели. До обнаружения этой проблемы мое приложение использовало около 3 гигабайт оперативной памяти (в нашем самом большом наборе данных), и все это было вызвано тем, что Vue делал эти объекты реактивными. Кроме того, загрузка была очень медленной. Я попробовал Vue-nonreactive, и это помогло, но только до 1 гига. До использования Vue приложение занимало около 350 мегабайт.

Решение

Все, что вы не хотите реагировать, просто отметьте как configurable: false . Это очень просто:

Object.defineProperty(target, 'nested', { configurable: false });

(Это останавливает наблюдение nested свойством

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

Интересно - определенно кажется жизнеспособной альтернативой.

Есть ли способ временно приостановить реакцию наблюдения и возобновить ее позже?

У меня есть наблюдатель, в котором я обновляю огромный объект, где я не хочу запускать обновление DOM только после завершения всей подготовки данных.

@intijk Не совсем так. Видите, это зависит от того, что вы пытаетесь сделать; Vue в конечном итоге должен применить ваше состояние, поэтому простая пауза во время вычисления не очень помогает. Если вместо этого вы пытаетесь пропустить промежуточные состояния и применить только конечное состояние, просто начните с нового объекта, а затем назначьте этот объект в конце.

Например (псевдокод):

doUpdate()
{
   const state = _.cloneDeep(this.myState);

  // Do intermediate state updates

  this.myState = state;
}

(Действуют обычные предостережения Vue о реактивности объектов.)

Я бы порекомендовал использовать описанный выше трюк configurable чтобы пропустить те разделы вашего большого объекта, которые не требуют реакции. Если все это _должно_ быть реактивным, я рекомендую использовать что-то вроде vuex .

@Morgul Я уже давно использовал этот трюк, но факт в том, что я больше не хочу использовать этот трюк.
В моем случае объект данных теперь довольно большой, диапазон от 2M до 100M, глубокое копирование такого объекта довольно болезненно.

@intijk Звучит невероятно сложно для чего-то, к чему можно привязать Vue. Какой здесь вариант использования?

@Morgul
Я не думаю, что это сложный случай, сам случай простой, просто данные довольно большие. Каждый раз сеть будет загружать какой-нибудь индексированный файл журнала визуализации, и у меня есть компонент визуализации для его отображения.

У кого-нибудь есть мысли об определении нереактивного поля в вычисляемом свойстве? Моя первая идея зависит от нереактивности присваивания массиву ...

template: '<div v-html="markdown.render(input, env)"></div>',
props: ['id', 'input'],
computed: {
  env:      function() { return { reactive:this.id, non_reactive:[] } },
  markdown: function() { return Markdown },
},

// within markdown.render():
  env.non_reactive[0] = internal_data;

Но это не совсем самодокументируется :-)

Привет, ребята. Я только что обнаружил эту проблему и обнаружил, что столкнулся с проблемой, которая очень похожа на проблему rpkilby: мой проект создает серию виртуальных DOM Vue (или называемых vnode) из объекта JSON. Я буду использовать этот объект JSON для создания приложения для Android. В любом случае, этот объект JSON может быть большого размера, и когда я использую этот JSON во Vue, Vue будет наблюдать за ним. Я попробовал методы rpkilby и Morgul, но это не сработало (кстати, я в команде, хотя некоторые ребята могут не очень хорошо знакомы с Vue, и они, вероятно, вызовут наблюдаемый JSON, а моя версия Vue - 2.5.16. ). Мне интересно, можем ли мы сделать это в Vue traverse:
function _traverse (val, замечено) {
var i, keys;
var isA = Array.isArray (val);
if ((! isA &&! isObject (val)) || Object.isFrozen (val) || val экземпляр VNode
|| (val && val ['__ vueNonReactive__'])) {
возвращение
}
...
Как видите, я добавил "val && val ['__ vueNonReactive__']". Затем я изменяю свой объект JSON, чтобы он имел «__vueNonReactive__ = true» с корневым узлом JSON, и это решило мою проблему.
Мне интересно, может ли это вызвать проблемы? И будет ли это рассматриваться как новая функция в Vue, которая позволит разработчику настроить объект, который будет отслеживаться Vue, или нет, настроив свойство объекта? (Object.freeze может изменить объект как неизменяемый объект, поэтому он не подходит для любой ситуации)

рассмотрите это https://github.com/vuejs/vue/blob/v2.5.16/src/core/observer/index.js#L121
set val._isVue = true может выйти из процедур наблюдения vue.

Сегодня я встретил случай, когда Vue наблюдал за экземпляром mapbox-gl на карте, затем происходили странные вещи, карта становилась светлее. Но экземпляр карты необходимо передавать между экземплярами vue. После добавления map._isVue = true проблема решена.

+1 в поддержку этого официально. Я использую большой объект, который не требует реактивности в компоненте, и отключение неиспользуемой реакции уменьшило размер объекта памяти с 800 МБ до 43 МБ.
Я использую решение @magicdawn для решения проблемы совместимости, но я думаю, что @Mechazawa - лучшее решение здесь.
Для решения установки __ob__ configurable на false работает, но вызывает сбой Vue, когда он пытается установить реальный __ob__ .

Я создал плагин Vue, который позволяет сделать переменные Vue нереактивными (он использует ловушку beforeCreate).

Это чище, чем vue- nonreactive - этот комментарий - ваше решение не будет работать для следующей версии Vue.


Пожалуйста, посмотрите Vue-Static, чтобы

<script>
export default {
    static() {
        return {
            map: null,
        };
    },
    mounted() {
        this.map = new mapboxgl.Map({...}); /* something heavy */
    },
};
</script>

Привет @samuelantonioli - похоже, vue-static делает что-то немного другое, отключая реактивность для всего объекта. Напротив, vue-nonreactive может отключить наблюдение для одного свойства , оставив остальную часть объекта реактивной.

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

например, в моем экземпляре модели есть ссылка на кеш объектов, который позволяет выполнять поиск связанных объектов. Я хочу наблюдать / визуализировать instance и связанный instance.author , но не instance._cache .

new Vue({
    data() {
        const instance = postStore.fetch({include: ['author', 'comments.author']})
        Vue.nonreactive(instance._cache)

        return {post: instance, },
    },
    ...
});

В любом случае, спасибо за внимание. Мне нужно будет посмотреть, как в следующей версии используются прокси, и посмотреть, есть ли способ обмануть создание наблюдателя / прокси.

@LinusBorg - экспериментальной ветки не вижу. Где идет разработка следующей версии?

Мы все еще на стадии концептуального / экспериментального проекта и еще не опубликовали ветку. Серьезная работа над этим не начнется, пока мы не выпустим обновление 2.6, которое само по себе потребует некоторой работы после того, как мы, надеюсь, очень скоро выпустим vue-cli 3.0.

Спасибо за обновления.

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

У меня проблема с Vue-Static том, что он кажется излишним. Я могу построить свой объект в модуле JS, импортировать его, а затем вернуть его значение из вычисленной функции; поскольку это не будет соблюдаться, не имеет значения, что значение вычисленной функции никогда не будет пересчитано. И это намного лучшее разделение проблем, поскольку я не использую вещи типа бизнес-логики в своих компонентах vue.

В любом случае мой трюк с установкой свойств как ненастраиваемых, по-прежнему остается наименее инвазивным и наименее зависимым от Vue способом решения проблемы. Также нет причин предполагать, что он сломается с прокси-серверами ES; вы по-прежнему, вероятно, не хотите наблюдать ненастраиваемые свойства. Я могу ошибаться, но мы _знаем_, что __ob__ уходит ... мы не знаем о проверке настраиваемого свойства.

Кроме того, он работает как чемпион в нашем производственном коде более 8 месяцев. ;) (У нас есть аналогичное проблемное пространство с @samuelantonioli; у нас есть карта OpenLayers, с которой нам нужно работать внутри Vue, без того, чтобы Vue увеличил нашу память до 2,4 гигабайт ...)

Я согласен, если вы используете другой шаблон, например, импортируете модули и используете вычисленные свойства, вам не понадобится Vue-Static . Мне просто нужен был шаблон, который я мог бы преподать своим сотрудникам, который им будет легко понять и использовать. Шаблон import-module-and-use-computed-properties не так понятен, IMO.


Немного ОТ: я почти уверен, что прокси-серверы ES6 - хороший вариант, но у меня есть некоторые опасения по поводу совместимости с браузером (IE11 и ниже не поддерживают его). Мне интересно, будет ли слой совместимости / какой-то тип полифилла, чтобы мы могли использовать Vue для проектов с более строгими требованиями к браузеру.

Как указать Vue, чтобы он смотрел (defineProperty) только на одноуровневую глубину данных?

+1 для этой идеи, будет проще структурировать данные для использования Vue с внешней графической библиотекой (которая обычно имеет многоуровневые вложенные большие объекты).

Как насчет того, чтобы указать только некоторые свойства, которые будут реактивными?

Я могу упустить что-то очевидное здесь, но использую this.propertyName = {/ * здесь что-то большое * /};
в хуке mount () не является решением при наличии ненаблюдаемых свойств?

Привет @vlahanas. См. Https://github.com/vuejs/vue/issues/2637#issuecomment -403630456.

set _isVue вызовет сбой vue-devtool, используйте вместо этого эту функцию

export default function setIsVue(val) {
    if (!val) return

    Object.defineProperty(val, '_isVue', {
        value: true,
        enumerable: false,
        configurable: true,
    })

    // vue-devtool
    // https://github.com/vuejs/vue-devtools/blob/c309065c57f6579b778341ea37042fdf51a9fc6c/src/backend/index.js#L616
    // 因为有 _isVue 属性
    if (process.env.NODE_ENV !== 'production') {
        if (!val.$options) {
            Object.defineProperty(val, '$options', {
                value: {},
                enumerable: false,
                configurable: true,
            })
        }

        if (!val._data) {
            Object.defineProperty(val, '_data', {
                value: {},
                enumerable: false,
                configurable: true,
            })
        }
    }

    return val
}

Он потерялся в шуме, но отметка свойства как ненастраиваемого действительно кажется исправлением.

Object.keys(scene).forEach((key)=>{
  Object.defineProperty(target, 'nested', { configurable: false });
});

Действительно приятно, когда мне нужно передать THREE.Scene но я не хочу, чтобы буквально весь граф сцены превратился в беспорядок наблюдаемых. И тогда я все еще могу передавать исходный объект, и он может реагировать на это. Идеально!

Все еще проблема.
У меня есть объекты, содержащие много вложенных уровней свойств, которые я хочу оставить безреактивными.
1

Даже если я использую

Он потерялся в шуме, но отметка свойства как ненастраиваемого действительно кажется исправлением.

Object.keys(scene).forEach((key)=>{
  Object.defineProperty(target, 'nested', { configurable: false });
});

Действительно приятно, когда мне нужно передать THREE.Scene но я не хочу, чтобы буквально весь граф сцены превратился в беспорядок наблюдаемых. И тогда я все еще могу передавать исходный объект, и он может реагировать на это. Идеально!

или

рассмотрите это https://github.com/vuejs/vue/blob/v2.5.16/src/core/observer/index.js#L121
set val._isVue = true может выйти из процедур наблюдения vue.

Сегодня я встретил случай, когда Vue наблюдал за экземпляром mapbox-gl на карте, затем происходили странные вещи, карта становилась светлее. Но экземпляр карты необходимо передавать между экземплярами vue. После добавления map._isVue = true проблема решена.

Свойства вложенных объектов становятся реактивными.

Я пытался сделать это рекурсивно, но Maximum call stack size exceeded , и это вызывает больше задержек.

@Mitroright Вам придется делать это рекурсивно, но я подозреваю, что ваш подход мог быть не совсем правильным.

Проблема здесь в том, как Vue обрабатывает массивы; просто пометить свойство geoJsonData неконфигурируемое, скорее всего, не сработает (у меня были с этим проблемы, но я никогда не разбирался в вопросе "почему").

Попробуйте что-то вроде этого:

function makeArrayNonConfigurable(objects)
{
    objects.forEach((obj) =>
    {
        Object.keys(obj).forEach((key) =>
        {
            Object.defineProperty(obj, key, { configurable: false });
        });
    });
}

Мы углубляемся только на один уровень, потому что это все, что нам нужно сделать; Vue не просматривает свойства вложенных объектов. Однако, похоже, он смотрит на объекты внутри массивов в свойствах, отмеченных как не настраиваемые, отсюда и проблема, с которой вы столкнулись.

Теперь я скажу вам, что с 10 000 объектов, которые должны пройти, это будет происходить в течение нескольких секунд или около того при первом прохождении через этот массив; это следует рассматривать как часть затрат на извлечение этих данных. Честно говоря, я рекомендую использовать эти объекты с классом (я использую класс, который возвращает прокси из его конструктора), а затем кэшировать эти объекты по уникальному идентификатору, если вы загружаете их более одного раза в течение жизни приложения - охватывать. Но на самом деле это деталь дизайна, которая не совсем связана с вашей проблемой.

Я нашел здесь решение:
https://medium.com/@deadbeef404/tell -vue-js-to-stop-wasting-time-and-render -fast-7c3f7d2acaab

Короче, сделайте служебную функцию:

import Vue from 'vue';

const Observer = (new Vue()).$data.__ob__.constructor;

export function makeNonreactive(obj) {
    obj.__ob__ = new Observer({});
}

Привет @Mitoright. Для справки, статья описывает внутренности vue-nonreactive . Разница в том, хотите ли вы использовать код как плагин (через vue-nonreactive ) или как вспомогательную функцию. Кроме того, vue-nonreactive упоминается в обновлении прямо вверху описания этой проблемы.

Поскольку vue-devtool снова получил обновление, https://github.com/vuejs/vue/issues/2637#issuecomment -434154442 снова вызовет сбой vue-devtool

Предлагаю vue-nonreactive подобные решения 😆

чтобы сделать __ob__ none перечисляемым, используйте defineProperty

vue-free.js

import Vue from 'vue'

const Observer = new Vue().$data.__ob__.constructor

function prevent(val) {
    if (val) {
        // Set dummy observer on value
        Object.defineProperty(val, '__ob__', {
            value: new Observer({}),
            enumerable: false,
            configurable: true,
        })
    }

    return val
}

// vue global
Vue.VUE_FREE = prevent

// window
global.VUE_FREE = prevent

// default export
export default prevent

На рисунке я бы отдал свои 2 цента и решение по этому поводу.

У меня также были похожие проблемы с реализацией концепции Freeze и поддельного Observer. Мои данные поступают с сервера и представляют собой рекурсивный сценарий TreeNode. В моем проекте также используется vuex, который добавил слой к обнаруженным проблемам. Постоянно получал Maximum call stack size exceeded из-за цикла vues object.keys. Я пробовал заморозить и установить данные внутри поддельного VNode, но, похоже, ни один из них не остановил проблемы с рекурсией.

Я наконец отступил и обернул свои «нереактивные» свойства, используя классический шаблон модуля выявления.

это класс (ES6 / typescript), но то же самое можно применить и в простом vue

import {  first, forEach } from 'lodash';

export class TreeNode {
    internalRefsInstance: () => { getParent: () => TreeNode; setParent: (parent: TreeNode) => void; getChildNodes: () => TreeNode[]; setChildNode: (childNode: TreeNode) => number; };

    get visitedDate(): string | undefined {
        return this._visitedDates.get(this.id) || undefined;
    }

    isSelectedTreeNode: boolean = false;
    showSubheader: boolean = false;
    showHelp: boolean = false;
    treeNodeIconName: string = 'empty';
    childTreeNodeCount: number = 0;

    constructor(public id: string,
        public componentName: string,
        private _visitedDates: Map<string, string>,
        public isActive: boolean = true,
        public nextFlow?: string,
        public prevFlow?: string,
        parent: TreeNode | undefined = undefined) {

        //invoke the internal refs module to create our static instance func to get the values from
        this.internalRefsInstance = this.nonReactiveModule();
        this.internalRefsInstance().setParent(parent);
    }
    nonReactiveModule = () => {
        let _parent: TreeNode | undefined = undefined;
        let _childNodes: TreeNode[] = [];
        const _getParent = (): TreeNode | undefined => {
            return _parent;
        };
        const _setParent = (parent: TreeNode | undefined): void => {
            _parent = parent;
        };
        const _getChildNodes = (): TreeNode[] => {
            return _childNodes || [];
        };
        const _setChildNode = (childNode: TreeNode): number => {
            if (!_childNodes) {
                _childNodes = [];
            }
            _childNodes.push(childNode);
            return _childNodes.length;
        };
        const returnObj = {
            getParent: _getParent,
            setParent: _setParent,
            getChildNodes: _getChildNodes,
            setChildNode: _setChildNode,
        };
        return () => { return returnObj; };
    }

    getParent(): TreeNode | undefined {
        return this.internalRefsInstance().getParent();
    }

    getChildNodes(): TreeNode[] {
        return this.internalRefsInstance().getChildNodes();
    }

    setChildNode(childFlow: TreeNode): void {
        this.childTreeNodeCount = this.internalRefsInstance().setChildNode(childFlow);
    }

    clone(parent: TreeNode | undefined = undefined): TreeNode {
        const newInstance = new TreeNode(this.id, this.componentName, this._visitedDates, this.isActive, this.nextFlow, this.prevFlow, parent);
        newInstance.showHelp = this.showHelp;
        newInstance.showSubheader = this.showSubheader;
        newInstance.isSelectedTreeNode = this.isSelectedTreeNode;
        forEach(this.getChildNodes(), (flow: TreeNode) => {
            newInstance.childTreeNodeCount = newInstance.internalRefsInstance().setChildNode(flow.clone(newInstance));
        });
        return newInstance;
    }

    setVisitedDates(visitedDates: Map<string, string>): void {
        this._visitedDates = visitedDates;
        forEach(this.getChildNodes(), (flow: TreeNode) => {
            flow.setVisitedDates(visitedDates);
        });
    }

    setAsSelected(setParent: boolean = true, setAllFirstChildren: boolean = true): void {
        this.isSelectedTreeNode = true;
        if (setAllFirstChildren) {
            const firstChildFlow = first(this.getChildNodes());
            if (firstChildFlow) {
                firstChildFlow.setAsSelected(false, true);
            }
        }

        if (setParent && this.getParent()) {
            this.getParent()!.setAsSelected(setParent);
        }
    }
    resetSelected(resetChildren: boolean = true): void {
        this.isSelectedTreeNode = false;
        if (resetChildren) {
            forEach(this.getChildNodes(), (flow: TreeNode) => {
                flow.resetSelected(resetChildren);
            });
        }
    }
}

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

Я думаю, что перейти на следующий уровень выше было бы созданием внедренного нерекурсивного помощника или декоратора для ускорения процесса. Любая полезная обратная связь была бы замечательной.

похоже, что кто-то уже решил эту проблему,

надеюсь, вы проверите все подробности на https://github.com/vuejs/vue/issues/4384

Привет, я создал # 10265 тем более, что не знал об этой предыдущей проблеме.
Мне просто интересно, какое решение было бы рекомендовано для будущего и сохранения совместимости с Vue, когда он будет использовать прокси.
Использование Object.defineProperty с configurable: false работает хорошо (но это не препятствует тому, чтобы свойство, имеющее существующий метод установки / получения, стало реактивным).
Будет ли эта техника по-прежнему использоваться с Vue 3?
Спасибо

@ colin-guyon configurable: false , вероятно, _не_ сработает, тем более что, похоже, https://github.com/vuejs/vue-next/blob/d9c6ff372c10dde8b496ee32f2b9a246edf66a35/packages/reactivity/src/reactive.ts#L . Если он попадает в Vue 3.x, будет официальный API, чтобы пометить объект как неактивный.

Обратите внимание, что, как и в случае с новым предложенным Vue.observable , когда свойство устанавливается для реактивного объекта, это _new_ значение не будет испорчено и останется как есть. Вместо этого _getter_ вернет для него реактивный прокси, создав его, если он еще не существует в кеше.

К счастью, если вы мало что делаете на этом реактивном прокси, с вами, вероятно, все будет в порядке. Если проблема связана с перегрузкой памяти или совместимостью, тогда вам, конечно, не нужно беспокоиться об этом, поскольку какой бы объект ни был - гигантский набор данных или какой-то посторонний объект из библиотеки, поведение которого непредсказуемо, если к нему будет применена реактивность, вы говорите - В конце концов, ничего в этом не тронут. В этом смысле Vue 3.x на самом деле решает множество критических ситуаций, когда эта функция в противном случае была бы полезна.

На данный момент код vue-next похоже, исключает реактивность символьных ключей, точно так же, как это делает текущая версия Vue.

рассмотрите это https://github.com/vuejs/vue/blob/v2.5.16/src/core/observer/index.js#L121
set val._isVue = true может выйти из процедур наблюдения vue.

Сегодня я встретил случай, когда Vue наблюдал за экземпляром mapbox-gl на карте, затем происходили странные вещи, карта становилась светлее. Но экземпляр карты необходимо передавать между экземплярами vue. После добавления map._isVue = true проблема решена.

Я использовал этот метод, пока меня не укусили сбои инструментов Vue dev, потому что он видит, что _isVue истинно, и думает, что объект является экземпляром компонента Vue, но это не так.

Единственный хак, который я видел без серьезных побочных эффектов, похоже, это подход OP с библиотекой vue-nonreactive .

Есть ли в V3 альтернативные решения для этого?

@HunderlineK shallowRef, shallowReactive, markRaw

Была ли эта страница полезной?
0 / 5 - 0 рейтинги