如何使局部存储在VUE中反应性
Vue.js 的响应式系统是其核心优势之一,但对于不了解其底层机制的人来说,可能会感到神秘。例如,为什么它能与对象和数组一起工作,却不能与其他东西(如 localStorage)一起工作?本文将解答这个问题,并演示如何让 Vue 的响应式系统与 localStorage 协同工作。
如果运行以下代码,你会发现计数器显示为静态值,不会像预期的那样因间隔改变 localStorage 中的值而变化:
new Vue({ el: "#counter", data: () => ({ counter: localStorage.getItem("counter") }), computed: { even() { return this.counter % 2 == 0; } }, template: `<div> <div>Counter: {{ counter }}</div> <div>Counter is {{ even ? 'even' : 'odd' }}</div> </div>` });
// some-other-file.js setInterval(() => { const counter = localStorage.getItem("counter"); localStorage.setItem("counter", counter 1); }, 1000);
虽然 Vue 实例内的 counter
属性是响应式的,但仅仅因为我们在 localStorage 中更改了它的来源,它并不会改变。
解决这个问题的方法有很多,最好的方法可能是使用 Vuex 并保持存储值与 localStorage 同步。但如果我们只需要像这个例子中一样简单的解决方案呢?我们必须深入了解 Vue 的响应式系统的工作原理。
Vue 中的响应式
当 Vue 初始化组件实例时,它会观察 data
选项。这意味着它会遍历 data
中的所有属性,并使用 Object.defineProperty
将它们转换为 getter/setter。通过为每个属性设置自定义 setter,Vue 就能知道属性何时发生变化,并可以通知需要对变化做出反应的依赖项。它如何知道哪些依赖项依赖于某个属性?通过利用 getter,它可以在计算属性、观察者函数或渲染函数访问数据属性时进行注册。
// core/instance/state.js function initData () { // ... observe(data) }
// core/observer/index.js export function observe (value) { // ... new Observer(value) // ... } export class Observer { // ... constructor (value) { // ... this.walk(value) } walk (obj) { const keys = Object.keys(obj) for (let i = 0; i <p>那么,为什么 localStorage 不是响应式的呢?<strong>因为它不是具有属性的对象。</strong></p> <p>但是等等。我们也不能用数组定义 getter 和 setter,但在 Vue 中数组仍然是响应式的。这是因为数组在 Vue 中是一个特例。为了拥有响应式数组,Vue 在幕后重写了数组方法,并将它们与 Vue 的响应式系统整合在一起。</p> <p>我们可以对 localStorage 做类似的事情吗?</p> <h3 id="重写-localStorage-函数">重写 localStorage 函数</h3> <p>首先,我们可以通过重写 localStorage 方法来跟踪哪些组件实例请求了 localStorage 项目,从而修复我们最初的示例。</p> <p>// localStorage 项目键与依赖它的 Vue 实例列表之间的映射 const storeItemSubscribers = {};</p> <p>const getItem = window.localStorage.getItem; localStorage.getItem = (key, target) => { console.info("Getting", key);</p> <p>// 收集依赖的 Vue 实例 if (!storeItemSubscribers[key]) storeItemSubscribers[key] = []; if (target) storeItemSubscribers[key].push(target);</p> <p>// 调用原始函数 return getItem.call(localStorage, key); };</p> <p>const setItem = window.localStorage.setItem; localStorage.setItem = (key, value) => { console.info("Setting", key, value);</p> <p>// 更新依赖 Vue 实例中的值 if (storeItemSubscribers[key]) { storeItemSubscribers[key].forEach((dep) => { if (dep.hasOwnProperty(key)) dep[key] = value; }); }</p> <p>// 调用原始函数 setItem.call(localStorage, key, value); };</p> <p>// ... (其余代码与原文相同)</p> <p>在这个例子中,我们重新定义了 <code>getItem</code> 和 <code>setItem</code>,以便收集和通知依赖 localStorage 项目的组件。在新的 <code>getItem</code> 中,我们记录了哪个组件请求了哪个项目,在 <code>setItem</code> 中,我们联系所有请求该项目并重写其数据属性的组件。</p> <p>为了使上面的代码工作,我们必须将对组件实例的引用传递给 <code>getItem</code>,这会改变它的函数签名。我们也不能再使用箭头函数,否则我们将不会有正确的 <code>this</code> 值。</p> <p>如果我们想做得更好,我们必须更深入地挖掘。例如,我们如何在不显式地传递它们的情况下跟踪依赖项?</p> <h3 id="Vue-如何收集依赖项">Vue 如何收集依赖项</h3> <p>为了获得灵感,我们可以回到 Vue 的响应式系统。我们之前看到,当访问数据属性时,数据属性的 getter 会将调用者订阅到该属性的进一步更改。但它如何知道是谁进行了调用?当我们获取数据属性时,它的 getter 函数没有任何关于调用者是谁的输入。Getter 函数没有输入。它如何知道将谁注册为依赖项?</p> <p>每个数据属性都维护一个其依赖项列表,这些依赖项需要在一个 <code>Dep</code> 类中做出反应。如果我们更深入地研究这个类,我们可以看到,每当注册依赖项时,依赖项本身就已经在一个静态目标变量中定义了。这个目标是由一个迄今为止神秘的 <code>Watcher</code> 类设置的。事实上,当数据属性发生变化时,这些观察者将被实际通知,它们将启动组件的重新渲染或计算属性的重新计算。</p> <p>但是,再说一次,它们是谁?</p> <p>当 Vue 使数据选项可观察时,它还会为每个计算属性函数以及所有观察函数(不应与 <code>Watcher</code> 类混淆)和每个组件实例的渲染函数创建观察者。观察者就像这些函数的伴侣。它们主要做两件事:</p> <ol> <li> <strong>它们在创建时评估函数。</strong>这会触发依赖项的收集。</li> <li> <strong>当它们被通知到它们依赖的值已更改时,它们会重新运行其函数。</strong>这最终将重新计算计算属性或重新渲染整个组件。</li> </ol> <p>在观察者调用它们负责的函数之前,会发生一个重要的步骤:<strong>它们在 <code>Dep</code> 类中的静态变量中将自身设置为目标。</strong>这确保了当访问响应式数据属性时,它们会被注册为依赖项。</p> <h3 id="跟踪谁调用了-localStorage">跟踪谁调用了 localStorage</h3> <p>我们不能完全做到这一点,因为我们无法访问 Vue 的内部机制。但是,我们可以使用 Vue 的思想,让观察者在调用它负责的函数之前,在一个静态属性中设置目标。我们可以在调用 localStorage 之前设置对组件实例的引用吗?</p> <p>如果我们假设在设置数据选项时调用了 localStorage,那么我们可以挂接到 <code>beforeCreate</code> 和 <code>created</code>。这两个钩子在初始化数据选项之前和之后触发,因此我们可以设置,然后清除一个具有对当前组件实例的引用的目标变量(我们可以在生命周期钩子中访问它)。然后,在我们的自定义 getter 中,我们可以将此目标注册为依赖项。</p> <p>我们必须做的最后一件事是使这些生命周期钩子成为我们所有组件的一部分。我们可以为整个项目使用全局 mixin 来做到这一点。</p> <p>// ... (其余代码与原文相同)</p> <p>现在,当我们运行初始示例时,我们将得到一个每秒增加数字的计数器。</p> <p>// ... (其余代码与原文相同)</p> <h3 id="我们的思想实验的结束">我们的思想实验的结束</h3> <p>虽然我们解决了最初的问题,但请记住,这主要是一个思想实验。它缺少一些功能,例如处理已删除的项目和已卸载的组件实例。它也有一些限制,例如组件实例的属性名称需要与存储在 localStorage 中的项目名称相同。也就是说,主要目标是更好地了解 Vue 响应式系统在幕后是如何工作的,并充分利用它,所以我希望这就是你从这一切中获得的东西。</p>
以上是如何使局部存储在VUE中反应性的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

您是否曾经在项目上需要一个倒计时计时器?对于这样的东西,可以自然访问插件,但实际上更多

格子呢是一块图案布,通常与苏格兰有关,尤其是他们时尚的苏格兰语。在Tartanify.com上,我们收集了5,000多个格子呢
