如何在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中关于执行环境的杂谈
Aug 14 Javascript
Javascript数组与字典用法分析
Dec 13 Javascript
jquery中filter方法用法实例分析
Feb 06 Javascript
基于BootStrap栅格栏系统完成网站底部版权信息区
Dec 23 Javascript
JS操作input标签属性checkbox全选的实现代码
Mar 02 Javascript
vue登录注册及token验证实现代码
Dec 14 Javascript
react native基于FlatList下拉刷新上拉加载实现代码示例
Sep 30 Javascript
js中的reduce()函数讲解
Jan 18 Javascript
BootStrap表单验证中的非Submit类型按钮点击时触发验证的坑
Sep 05 Javascript
如何实现一个简易版的vuex持久化工具
Sep 11 Javascript
vue-resource:jsonp请求百度搜索的接口示例
Nov 09 Javascript
Angular+Ionic使用queryParams实现跳转页传值的方法
Sep 05 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
ajax php 实现写入数据库
2009/09/02 PHP
php 中英文语言转换类代码
2011/08/11 PHP
使用新浪微博API的OAuth认证发布微博实例
2015/03/27 PHP
微信access_token的获取开发示例
2015/04/16 PHP
PHP实现QQ登录实例代码
2016/01/14 PHP
php投票系统之增加与删除投票(管理员篇)
2016/07/01 PHP
PHP中in_array函数使用的问题与解决办法
2016/09/11 PHP
Laravel解决nesting level错误和隐藏index.php的问题
2019/10/12 PHP
ExtJS Window 最小化的一种方法
2009/11/18 Javascript
jQuery实现textarea自动增长宽高的方法
2015/12/18 Javascript
jQuery获取及设置表单input各种类型值的方法小结
2016/05/24 Javascript
AngularJS之页面跳转Route实例代码
2017/03/10 Javascript
vue高德地图之玩转周边
2017/06/16 Javascript
Vue内容分发slot(全面解析)
2017/08/19 Javascript
anime.js 实现带有描边动画效果的复选框(推荐)
2017/12/24 Javascript
对angular4子路由&amp;辅助路由详解
2018/10/09 Javascript
JavaScript中的函数式编程详解
2020/08/22 Javascript
Python open读写文件实现脚本
2008/09/06 Python
Python Tkinter实现简易计算器功能
2018/01/30 Python
通过python爬虫赚钱的方法
2019/01/29 Python
Django之路由层的实现
2019/09/09 Python
Python使用Selenium实现淘宝抢单的流程分析
2020/06/23 Python
Python约瑟夫生者死者小游戏实例讲解
2021/01/04 Python
Merchant 1948澳大利亚:新西兰领先的鞋类和靴子供应商
2018/03/24 全球购物
中东地区最大的奢侈品市场:The Luxury Closet
2019/04/09 全球购物
影视动画专业个人的自我评价
2013/12/31 职场文书
高中综合实践活动总结
2014/07/07 职场文书
建筑结构施工求职信
2014/07/11 职场文书
公司行政专员岗位职责
2014/08/24 职场文书
房地产经营管理专业自荐信
2014/09/02 职场文书
校园环保广播稿(3篇)
2014/09/15 职场文书
事业单位个人查摆问题及整改措施
2014/10/28 职场文书
2014年应急管理工作总结
2014/11/26 职场文书
2015年医院药剂科工作总结
2015/05/04 职场文书
大学文艺委员竞选稿
2015/11/19 职场文书
学校体育节班级口号
2015/12/25 职场文书