如何在Vue中使localStorage具有响应式(思想实验)


Posted in Javascript onJuly 14, 2020

响应式是Vue.js的最大特色之一。如果你不知道幕后情况,它也是最神秘的地方之一。例如,为什么它不能用于对象和数组,而不能用于诸如 localStorage 之类的其他东西?

如何在Vue中使localStorage具有响应式(思想实验)

让我们回答这个问题,在解决这个问题时,让Vue响应式与 localStorage 一起使用。

如果运行以下代码,则会看到计数器显示为静态值,并且不会像我们期望的那样发生变化,这是因为setInterval在 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.js实例中的 counter 属性是响应式的,但它不会因为我们更改了它在 localStorage 中的来源而更改。

有多种解决方案,最好的也许是使用Vuex,并保持存储值与 localStorage 同步。但如果我们需要像本例中那样简单的东西呢?我们要深入了解一下Vue.js的响应式系统是如何工作的。

Vue 中的响应式

当Vue初始化组件实例时,它将观察data选项。这意味着它将遍历数据中的所有属性,并使用 Object.defineProperty 将它们转换为getter/setter。通过为每个属性设置自定义设置器,Vue可以知道属性何时发生更改,并且可以通知需要对更改做出反应的依赖者。它如何知道哪些依赖者依赖于一个属性?通过接入getters,它可以在计算的属性、观察者函数或渲染函数访问数据属性时进行注册。

// 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 < keys.length; i++) { 
   defineReactive(obj, keys[i]) 
  } 
 } 
} 
 
export function defineReactive (obj, key, ...) { 
 const dep = new Dep() 
 // ... 
 Object.defineProperty(obj, key, { 
  // ... 
  get() { 
   // ... 
   dep.depend() 
   // ... 
  }, 
  set(newVal) { 
   // ... 
   dep.notify() 
  } 
 }) 
}

所以,为什么 localStorage 不响应?因为它不是具有属性的对象。

但是等一下,我们也不能用数组定义getter和setter,但Vue中的数组仍然是反应式的。这是因为数组在Vue中是一种特殊情况。为了拥有响应式的数组,Vue在后台重写了数组方法,并与Vue的响应式系统进行了修补。

我们可以对 localStorage 做类似的事情吗?

覆盖localStorage函数

首先尝试通过覆盖localStorage方法来修复最初的示例,以跟踪哪些组件实例请求了localStorage项目。

// LocalStorage项目键与依赖它的Vue实例列表之间的映射。 
const storeItemSubscribers = {}; 
 
const getItem = window.localStorage.getItem; 
localStorage.getItem = (key, target) => { 
 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); 
};
new Vue({ 
 el: "#counter", 
 data: function() { 
  return { 
   counter: localStorage.getItem("counter", this) // 我们现在需要传递“this” 
  } 
 }, 
 computed: { 
  even() { 
   return this.counter % 2 == 0; 
  } 
 }, 
 template: `<div> 
  <div>Counter: {{ counter }}</div> 
  <div>Counter is {{ even ? 'even' : 'odd' }}</div> 
 </div>` 
});
setInterval(() => { 
 const counter = localStorage.getItem("counter"); 
 localStorage.setItem("counter", +counter + 1); 
}, 1000);

在这个例子中,我们重新定义了 getItem 和 setItem,以便收集和通知依赖 localStorage 项目的组件。在新的 getItem 中,我们注意到哪个组件请求了哪个项目,在 setItems 中,我们联系所有请求该项目的组件,并重写它们的数据属性。

为了使上面的代码工作,我们必须向 getItem 传递一个对组件实例的引用,这就改变了它的函数签名。我们也不能再使用箭头函数了,因为否则我们就不会有正确的 this 值。

如果我们想做得更好,就必须更深入地挖掘。例如,我们如何在不显式传递依赖者的情况下跟踪它们?

如何在Vue中使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响应式内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
Javascript 阻止javascript事件冒泡,获取控件ID值
Jun 27 Javascript
鼠标滚轮控制网页横向移动实现思路
Mar 22 Javascript
JS获取下拉列表所选中的TEXT和Value的实现代码
Jan 11 Javascript
详解vue-cli + webpack 多页面实例应用
Apr 25 Javascript
微信小程序 wx.request方法的异步封装实例详解
May 18 Javascript
angular.js中解决跨域问题的三种方式
Jul 12 Javascript
快速掌握jquery分页插件jqPaginator的使用方法
Aug 09 jQuery
浅谈webpack下的AOP式无侵入注入
Nov 12 Javascript
jQuery 实现批量提交表格多行数据的方法
Aug 09 jQuery
Vue组件通信的几种实现方法
Apr 25 Javascript
vue3修改link标签默认icon无效问题详解
Oct 09 Javascript
vue 通过绑定事件获取当前行的id操作
Jul 27 Javascript
Jquery使用each函数实现遍历及数组处理
Jul 14 #jQuery
javaScript实现一个队列的方法
Jul 14 #Javascript
0基础学习前端开发的一些建议
Jul 14 #Javascript
jQuery 实现DOM元素拖拽交换位置的实例代码
Jul 14 #jQuery
Vue切换div显示隐藏,多选,单选代码解析
Jul 14 #Javascript
JS数组reduce()方法原理及使用技巧解析
Jul 14 #Javascript
微信小程序连接服务器展示MQTT数据信息的实现
Jul 14 #Javascript
You might like
如何在PHP中使用Oracle数据库(2)
2006/10/09 PHP
一个PHP模板,主要想体现一下思路
2006/12/25 PHP
php加水印的代码(支持半透明透明打水印,支持png透明背景)
2013/01/17 PHP
php中base_convert()进制数字转换函数实例
2014/11/20 PHP
PHP编程中尝试程序并发的几种方式总结
2016/03/21 PHP
Javascript 判断函数类型完美解决方案
2009/09/02 Javascript
js截取固定长度的中英文字符的简单实例
2013/11/22 Javascript
js中的preventDefault与stopPropagation详解
2014/01/29 Javascript
jQuery .tmpl() 用法示例介绍
2014/08/21 Javascript
jquery获取当前日期的方法
2015/01/14 Javascript
JS正则表达式验证账号、手机号、电话和邮箱是否合法
2017/03/08 Javascript
详解使用Typescript开发node.js项目(简单的环境配置)
2017/10/09 Javascript
Angular利用内容投射向组件输入ngForOf模板的方法
2018/03/05 Javascript
JQuery常见节点操作实例分析
2019/05/15 jQuery
vue filter 完美时间日期格式的代码
2019/08/14 Javascript
[51:15]2014 DOTA2国际邀请赛中国区预选赛 Orenda VS LGD-GAMING
2014/05/22 DOTA
python 随机数生成的代码的详细分析
2011/05/15 Python
理解python多线程(python多线程简明教程)
2014/06/09 Python
Python中动态创建类实例的方法
2017/03/24 Python
python实现对excel进行数据剔除操作实例
2017/12/07 Python
Django中的CBV和FBV示例介绍
2018/02/25 Python
替换python字典中的key值方法
2018/07/06 Python
浅谈python3发送post请求参数为空的情况
2018/12/28 Python
python获取txt文件词向量过程详解
2019/07/05 Python
python requests使用socks5的例子
2019/07/25 Python
Django 后台带有字典的列表数据与页面js交互实例
2020/04/03 Python
python matplotlib:plt.scatter() 大小和颜色参数详解
2020/04/14 Python
Canvas制作旋转的太极的示例
2018/03/09 HTML / CSS
应届毕业生如何写求职信
2014/02/16 职场文书
美丽乡村建设实施方案
2014/03/23 职场文书
大四毕业生自荐书
2014/07/05 职场文书
司机工作自我鉴定
2014/09/19 职场文书
停车位租赁协议书
2014/09/24 职场文书
出国留学自荐信模板
2015/03/06 职场文书
2016年教师党员创先争优承诺书
2016/03/24 职场文书
详解JS数组方法
2021/11/20 Javascript