如何在Vue中使localStorage具有响应式(思想实验)
响应式是Vue.js的最大特色之一。如果你不知道幕后情况,它也是最神秘的地方之一。例如,为什么它不能用于对象和数组,而不能用于诸如 localStorage 之类的其他东西?
Vue如何收集依赖关系
为了获得启发,我们可以回到Vue的响应式系统。我们之前曾看到,访问数据属性时,数据属性的 getter 将使调用者订阅该属性的进一步更改。但是它怎么知道是谁做的调用呢?当我们得到一个数据属性时,它的 getter 函数没有任何关于调用者是谁的输入。Getter函数没有输入,它怎么知道谁要注册为依赖者呢?
每个数据属性维护一个需要在Dep类中进行响应的依赖项列表。如果我们在此类中进行更深入的研究,可以看到只要在注册依赖项时就已经在静态目标变量中定义了依赖项。这个目标是由一个非常神秘的Watche类确定的。实际上,当数据属性更改时,将实际通知这些观察程序,并且它们将启动组件的重新渲染或计算属性的重新计算。
但是,他们又是谁?
当Vue使 data 选项可观察时,它还会为每个计算出的属性函数以及所有watch函数(不应与Watcher类混为一谈)以及每个组件实例的render函数创建watcher。观察者就像这些函数的伴侣。他们主要做两件事:
- 当它们被创建时,它们会评估函数。这将触发依赖关系的集合。
- 当他们被通知他们所依赖的一个值发生变化时,他们会重新运行他们的函数。这将最终重新计算一个计算出的属性或重新渲染整个组件。
在观察者调用其负责的函数之前,有一个重要的步骤发生了:他们将自己设置为Dep类中静态变量的目标。这样可以确保在访问响应式数据属性时将它们注册为从属。
追踪谁调用了localStorage
我们无法完全做到这一点,因为我们无法使用Vue的内部机制。但是,我们可以使用Vue的想法,即观察者可以在调用其负责的函数之前,将目标设置为静态属性。我们能否在调用 localStorage 之前设置对组件实例的引用?
如果我们假设在设置 data 选项时调用了 localStorage,则可以将其插入 beforeCreate 和 created 中。这两个挂钩在初始化data选项之前和之后都会被触发,因此我们可以设置一个目标变量,然后清除该变量,并引用当前组件实例(我们可以在生命周期挂钩中访问该实例)。然后,在我们的自定义获取器中,我们可以将该目标注册为依赖项。
我们要做的最后一点是使这些生命周期挂钩成为我们所有组件的一部分,我们可以通过整个项目的全局混合来做到这一点。
// LocalStorage项目键与依赖它的Vue实例列表之间的映射 const storeItemSubscribers = {}; // 当前正在初始化的Vue实例 let target = undefined; const getItem = window.localStorage.getItem; localStorage.getItem = (key) => { console.info("Getting", key); // 收集依赖的Vue实例 if (!storeItemSubscribers[key]) storeItemSubscribers[key] = []; if (target) storeItemSubscribers[key].push(target); // 调用原始函数 return getItem.call(localStorage, key); }; const setItem = window.localStorage.setItem; localStorage.setItem = (key, value) => { console.info("Setting", key, value); // 更新相关Vue实例中的值 if (storeItemSubscribers[key]) { storeItemSubscribers[key].forEach((dep) => { if (dep.hasOwnProperty(key)) dep[key] = value; }); } // 调用原始函数 setItem.call(localStorage, key, value); }; Vue.mixin({ beforeCreate() { console.log("beforeCreate", this._uid); target = this; }, created() { console.log("created", this._uid); target = undefined; } });
现在,当我们运行第一个示例时,我们将获得一个计数器,该计数器每秒增加一个数字。
new Vue({ el: "#counter", data: () => ({ counter: localStorage.getItem("counter") }), computed: { even() { return this.counter % 2 == 0; } }, template: `<div class="component"> <div>Counter: {{ counter }}</div> <div>Counter is {{ even ? 'even' : 'odd' }}</div> </div>` });
setInterval(() => { const counter = localStorage.getItem("counter"); localStorage.setItem("counter", +counter + 1); }, 1000);
我们的思想实验结束
当我们解决了最初的问题时,请记住这主要是一个思想实验。它缺少一些功能,例如处理已删除的项目和未安装的组件实例。它还具有一些限制,例如组件实例的属性名称需要与存储在 localStorage 中的项目相同的名称。就是说,主要目标是更好地了解Vue响应式在幕后的工作方式并充分利用这一点,因此,我希望你能从所有这些事情中受益。
到此这篇关于如何在Vue中使localStorage具有响应式的文章就介绍到这了,更多相关Vue localStorage响应式内容请搜索来客网以前的文章或继续浏览下面的相关文章希望大家以后多多支持来客网!