如何在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 原型链学习总结
Oct 29 Javascript
汉化英文版的Dreamweaver CS5并自动提示jquery
Nov 25 Javascript
javascript 进阶篇3 Ajax 、JSON、 Prototype介绍
Mar 14 Javascript
jQuery 淡入淡出 png图在ie8下有黑色边框的解决方法
Mar 05 Javascript
利用浏览器全屏api实现js全屏
Jan 16 Javascript
JavaScript中实现单体模式分享
Jan 29 Javascript
jquery 全选、全不选、反选效果的实现代码【推荐】
May 05 Javascript
jQuery.Form实现Ajax上传文件同时设置headers的方法
Jun 26 jQuery
从零开始最小实现react服务器渲染详解
Jan 26 Javascript
vue 组件高级用法实例详解
Apr 11 Javascript
node.js使用http模块创建服务器和客户端完整示例
Feb 10 Javascript
前端学习——JavaScript原生实现购物车案例
Mar 31 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实现多级树型菜单
2006/10/09 PHP
php生成SessionID和图片校验码的思路和实现代码
2009/03/10 PHP
PHP 小心urldecode引发的SQL注入漏洞
2011/10/27 PHP
解析VS2010利用VS.PHP插件调试PHP的方法
2013/07/19 PHP
浅析application/x-www-form-urlencoded和multipart/form-data的区别
2014/06/22 PHP
使用PHP处理数据库数据如何将数据返回客户端并显示当前状态
2016/02/16 PHP
详解PHP实现异步调用的4种方法
2016/03/14 PHP
PHP学习记录之数组函数
2018/06/01 PHP
javascript textContent与innerText的异同分析
2010/10/22 Javascript
javascript结合html5 canvas实现(可调画笔颜色/粗细/橡皮)的涂鸦板
2013/04/27 Javascript
JavaScript获取页面中超链接数量的方法
2015/11/09 Javascript
js判断上传文件后缀名是否合法
2016/01/28 Javascript
Bootstarp基本模版学习教程
2017/02/01 Javascript
js canvas实现适用于移动端的百分比仪表盘dashboard
2017/07/18 Javascript
最实用的JS数组函数整理
2017/12/05 Javascript
Angular路由ui-router配置详解
2018/08/01 Javascript
详解一个基于套接字实现长连接的express
2019/03/28 Javascript
利用Bootstrap Multiselect实现下拉框多选功能
2019/04/08 Javascript
基于iview-admin实现动态路由的示例代码
2019/10/02 Javascript
python打开url并按指定块读取网页内容的方法
2015/04/29 Python
理解Python垃圾回收机制
2016/02/12 Python
Python数据结构与算法之图结构(Graph)实例分析
2017/09/05 Python
解决Django删除migrations文件夹中的文件后出现的异常问题
2019/08/31 Python
Python面向对象原理与基础语法详解
2020/01/02 Python
Keras load_model 导入错误的解决方式
2020/06/09 Python
完美解决TensorFlow和Keras大数据量内存溢出的问题
2020/07/03 Python
Python colormap库的安装和使用详情
2020/10/06 Python
Grid 宫格常用布局的实现
2020/01/10 HTML / CSS
html5声频audio和视频video等新特性详细说明
2012/12/26 HTML / CSS
Shopty西班牙:缝纫机在线销售
2018/01/26 全球购物
德国内衣、泳装和睡衣网上商店:Bigsize Dessous
2018/07/09 全球购物
小学生环保演讲稿
2014/04/25 职场文书
党员四风问题对照检查材料
2014/09/27 职场文书
青岛导游词
2015/02/12 职场文书
护士年终个人总结
2015/02/13 职场文书
预备党员入党感想
2015/08/10 职场文书