Vue: [功能] 能够禁用 Vue 观察

创建于 2016-04-07  ·  50评论  ·  资料来源: vuejs/vue

更新:
如果有人最终需要此功能,我已将其发布为vue-nonreactive并带有适当的警告和所有内容。


我们有一些非普通模型需要禁用 Vue 的观察和行走。 一个例子是一个资源模型,它可以访问缓存,以便它可以查找相关资源。 这会导致缓存中的所有对象都被监视(可能效率低下),以及与其他代码的一些额外交互。 目前,我们通过在缓存上设置一个虚拟观察者来解决这个问题。 类似的东西...

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('a', 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()不会在你的情况下工作吗? 从 v1.0.18 开始支持

  1. 如果您需要跳过对data的对象/数组的观察,请在其上使用Object.freeze()
  2. 您无需将对象放入data即可在this上访问它。 如果您只是将它附加到this created()挂钩中的
  • Object.freeze在这里不起作用。 缓存随时间更新。
  • 主要资源_is_reactive。 我最感兴趣的是使嵌套的缓存对象非反应性。

那么也许是时候重新考虑您的模型设计了。 为什么要把这些东西嵌套在要观察的东西下面?

因为缓存是用来动态查找相关资源的。

例如,我们可以有AuthorPost模型。 作者模型定义了一个称为posts到 post 模型的多对多关系。 此缓存包含关系数据以及相关集合。

调用 author.posts 从缓存中获取帖子。

根据设计,Vue 不鼓励将具有自己的状态变异机制的复杂对象放入 Vue 实例的data 。 您应该只将纯状态作为观察数据放入 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 负责在布局中放置可重用组件并将数据获取/绑定到相关组件。 子组件不会获取自己的数据,因为数据在不同的上下文中是不同的。 例如,_user's_ 评论列表与_post's_ 评论列表。

该模型相当“愚蠢”,除了相关对象没有嵌套( {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)数据的 1 级深度?

我的情况是,我希望 Vue 在data.curObj更改时得到通知,
但不要使用curObj.positioncurObj.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可以查找其authorcomments 。 当我调用article.author ,这是对该关系缓存的动态属性查找,而不是简单的属性访问。 需要考虑的几点:

  • 缓存不是纯状态,所以我不希望它被Vue观察到,因为它浪费资源。
  • 缓存不能被丢弃,因为我仍然需要一个引用来执行这些动态查找/更新。
  • 缓存实际上是一个单例,可以从外部更新。 应用程序相应更新。

回应一些建议:

  • 使用 JSON stringify/parse 或对象解构是不够的,因为这既复制了对象和缓存,也破坏了对原始缓存的引用。 更新原始缓存引用将不再更新应用程序的实例。 如果没有这些动态查找和更新,缓存基本上是没有意义的,最好在原始模型上使相关对象成为简单的属性。 同样值得注意的是,这些建议破坏了这些对象的实例方法。
  • 如果您控制类型创建, @Mechazawa的建议是有意义的,并且这些类型是为与 Vue 一起使用而构建的。 就我而言,这是一个与 Vue 无关的外部库,我不想麻烦地更改应用程序中的类型。 对于 vue 层来说,简单地将某些已知属性标记为不可观察的要简单得多。

我唯一的批评:

Vue-nonreactive 将 Vue 作为依赖项,这在做一些直接的事情时是过度的。

我不确定为什么这会是一件坏事? 您已经在您的应用程序中使用了 Vue,并且该插件是特定于 Vue 的。 如果我没记错的话,大多数构建工具都足够智能,不会创建具有重复依赖项的包。 无论如何,这是不正确的。 存在开发依赖项,但没有运行时依赖项。


无论如何,我很高兴看到这篇文章引起了一些人的兴趣,而且我确信这些其他解决方案中的一些将适用于其他各种情况。 我只想强调我最初评论中的要求,以及为什么这些建议不适用于这种情况。

所以,我最近遇到了这个,并发现有一种更简单的方法来短路 Vue 的观察逻辑:_Define a property as non-configurable._

背景

在我的应用程序中,我必须使用第 3 方库 (OpenLayers),它创建保存数据的类实例,并且不支持任何反应系统。 让我告诉你,试图硬塞进去已经引起了很多头痛。 因此,对于使用这个库的大型应用程序来说,唯一可行的解​​决方案是让 OpenLayers 拥有它想要的东西,而对我来说,让 Vue 更好地处理这些可怕的嵌套、超级厄运对象。 在发现这个问题之前,我的应用程序使用了大约 3 gig 的 ram(在我们最大的数据集上),所有这些都是由 Vue 使这些对象具有反应性造成的。 此外,加载时真的很慢。 我尝试了 Vue-nonreactive,它有帮助,但只是让我们降到了大约 1 个演出。 在使用 Vue 之前,应用程序大约有 350megs。

解决方案

任何你不想反应的东西,只需标记为configurable: false 。 这很简单:

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

(这会阻止nested属性及其所有属性被观察到。)

而已! 没有 Vue 依赖,甚至可以说是不正确的。 有了这个,我的应用程序使用我们最大的数据集减少到 200megs。 它简单易行,只需要在 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 的问题非常相似的问题:我的项目从 JSON 对象构建了一系列 Vue Virtual DOM(或称为 vnode)。 我将使用这个 JSON 对象来构建一个 android 应用程序。 不管怎样,这个 JSON 对象可以很大,当我在 Vue 中使用这个 JSON 时,它会被 Vue 观察到。 我尝试了 rpkilby 和 Morgul 的方式,但它不起作用。(顺便说一句,我在一个团队中,而有些人可能对 Vue 不太熟悉,他们可能会导致观察到 JSON,而我的 Vue 版本是 2.5.16 )。 我想知道我们是否可以在 Vue traverse 中做到这一点:
函数_traverse(val,看到){
var i,键;
var isA = Array.isArray(val);
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode
|| (val && val['__vueNonReactive__'])) {
返回
}
...
如您所见,我添加了“val && val['__vueNonReactive__']”。 然后我将我的 JSON 对象修改为具有 JSON 根节点的“__vueNonReactive__ = true”,这解决了我的问题。
我想知道这是否会导致任何问题? 并且这会不会被认为是 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的一个map实例,然后奇怪的事情发生了,map变轻了。 但是map实例需要在vue实例之间传递。 添加map._isVue = true ,问题解决了。

+1 正式支持。 我在组件中使用不需要反应性的大对象,禁用未使用的反应性将内存对象大小从 800MB 减少到 43MB
我正在使用@magicdawn解决方案解决兼容性问题,但我认为@Mechazawa是最好的解决方案。
对于将__ob__configurable为 false 的解决方案,但在尝试设置真实的__ob__时会使 Vue 崩溃。

我已经构建了一个 Vue 插件,它可以使 Vue 变量无反应(它使用 beforeCreate 钩子)。

这比vue-nonreactive - @rpkilby更干净,请查看此评论- 您的解决方案不适用于下一版本的 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 gigs...)

我同意,如果您使用另一种模式,例如导入模块并使用计算属性,则不需要Vue-Static 。 我只需要一种模式,我可以教给我的员工,让他们易于理解和使用。 import-module-and-use-computed-properties 模式在 IMO 中并不是那么清楚。


有点过时:我很确定 ES6 代理是一个不错的选择,但我对浏览器兼容性有一些担忧(IE11 及以下不支持)。 我很感兴趣是否会有兼容层/某种类型的 polyfill,以便我们可以将 Vue 用于具有更严格浏览器要求的项目。

如何告诉 Vue 只观察(defineProperty)数据的 1 级深度?

+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的一个map实例,然后奇怪的事情发生了,map变轻了。 但是map实例需要在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 个对象要通过这个数组,第一次通过这个数组时会搅动几秒钟左右; 这应该被视为检索这些数据的成本的一部分。 坦率地说,我建议使用一个类来使用这些对象(我使用一个从它的构造函数返回一个代理的类),然后通过一个唯一的 id 缓存这些对象,如果你在应用程序的生命周期中加载它们不止一次 -跨度。 但这确实是一个设计细节,与您的问题并不完全相关。

我在这里找到了解决方案:
https://medium.com/@deadbeef404/tell -vue-js-to-stop-wasting-time-and-render-faster-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__不可枚举,请使用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 美分和解决方案。

我在实施冻结概念和假观察者时也遇到了类似的问题。 我的数据来自服务器并且是一个递归的 TreeNode 场景,我的项目在这种情况下也使用了 vuex,它为所看到的问题添加了一个层。 由于 vues object.keys 循环,我不断得到Maximum call stack size exceeded 。 我曾尝试冻结并在假 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);
            });
        }
    }
}

Computed 在我的情况下不起作用,因为我仍然需要完整的对象,而不是处理程序向我发送对计算的更改。 至少如果我理解深度观察者如何针对计算出的子集结果工作。

我认为将这个提升到一个新的水平是创建一个注入的非递归助手或装饰器来加速这个过程。 任何有用的反馈都会很棒。

好像已经有人解决了这个问题

希望你在https://github.com/vuejs/vue/issues/4384查看所有细节

嗨,我创建了 #10265,特别是因为我不知道这个以前的问题。
我只是想知道什么是建议的解决方案,以适应未来的发展并在 Vue 使用 Proxy 时与 Vue 保持兼容。
Object.definePropertyconfigurable: false效果很好(但它不会阻止具有现有 setter/getter 的属性变得被动)。
这种技术在 Vue 3 中仍然可用吗?
谢谢

@colin-guyon configurable: false可能_not_ 工作,特别是因为似乎有https://github.com/vuejs/vue-next/blob/d9c6ff372c10dde8b496ee32f2b9a246edf66a35/packages/reactivity/src/reactive.ts# . 如果它最终出现在 Vue 3.x 中,将会有一个官方 API 来标记一个对象是非响应式的。

请注意,就像新提议的Vue.observable ,当在反应式对象上设置属性时,该 _new_ 值不会被污染,只会保持原样。 相反,_getter_ 会为其返回一个响应式代理,如果缓存中尚不存在,则创建一个。

一个幸运的消息是,只要你在 _that_ 反应式代理上做的不多,你应该没问题。 如果内存占用或互操作性是一个问题,那么你当然不需要担心它,因为无论对象是什么 - 一个巨大的数据集合或来自库的一些外来对象,如果对其应用反应性,则行为是不可预测的,你说- 毕竟,_没有_关于它被感动。 从这个意义上说,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的一个map实例,然后奇怪的事情发生了,map变轻了。 但是map实例需要在vue实例之间传递。 添加map._isVue = true ,问题解决了。

我一直在使用这种方法,直到我被 Vue 开发工具的漏洞咬了,因为它看到_isVue是真的,它认为该对象是一个 Vue 组件实例,但它不是。

我见过的唯一没有严重副作用的 hack 似乎是 OP 使用vue-nonreactive库的方法。

V3 中是否有其他解决方案?

@HunderlineK浅参考、浅反应、标记

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

robertleeplummerjr picture robertleeplummerjr  ·  3评论

hiendv picture hiendv  ·  3评论

franciscolourenco picture franciscolourenco  ·  3评论

loki0609 picture loki0609  ·  3评论

6pm picture 6pm  ·  3评论