状态是您与网站互动时由您生成的某种东西,例如,通过单击按钮或在文本字段中键入一些文本。 此状态位于浏览器的RAM中,由JavaScript对象(例如数组,字符串和对象)组成。
在本文中,我们将探讨采用这些数组和对象并将其持久化的模式。 为此,我们需要一些聪明的方法来将临时的“垃圾”与我们不能丢失的重要内容分开。
入门
假设我们有一个对象数组,每个对象中都有“临时”键。 在将对象数组发送到持久性存储( localStorage
或Ajax)中时,我们不希望这些键具有持久性。
在示例中,我将向您展示,我们将使用AngularJS应用程序。 在其中,我们将不需要持久化的东西放入每个对象中。 对于保持持久性不重要的所有内容都以下划线开头。
这是我们开始的HTML代码:
<div ng-repeat="thing in things track by thing.id"
ng-click="thing._expanded=!thing._expanded">
<div ng-if="thing._expanded">
EXPANDED VIEW
</div>
<div ng-if="!thing._expanded">
collapsed view
</div>
</div>
这是为它提供动力的小型JavaScript代码:
angular
.module('app', [])
.controller('Ctrl', ($scope) => {
$scope.things = [
{id: 1, key: 'Value'},
{id: 2, key: 'Value2'},
{id: 3, key: 'Value3'},
]
});
如果您想现场观看此示例, 请观看此演示 。
请参阅CodePen上的SitePoint( @SitePoint )提供的Pen XXNdab 。
很可爱吧? 真的很实用。
关于持久存储的思考
现在假设我们要保留$scope.things
。 关闭标签页后,可能要返回到Todo列表。 当我们回来时,很多这些浅层属性(例如_expanded
或_dateAsString
)都是我们不想在持久性存储中混乱的东西。 我们之所以这样做,是因为可以轻松地重新生成或重置它们,而又不会失去有价值的东西。 也许我们担心内存,其中一些临时状态项很重。
假设我们要将其保留在localStorage
。 我们需要做的如下所示:
localStorage.setItem('mystuff', JSON.stringify($scope.things));
但是,这将在我们不想记住的对象中保存很多其他内容(再次,因为它们可以重新生成,或者因为它们根本不值得保存)。
因此,我们需要做的第一件事就是克隆它们。 要克隆数组,您必须编写这样的JavaScript代码(我在这里使用ECMAScript6 ):
let copy = Array.from(myArray)
但是,如果数组是一个充满对象(即字典)的数组,那么我们必须做更多的魔术:
let copy = Array.from(
myArray,
(item) => Object.assign({}, item)
);
这样,我们得到了一个深层的副本 。
这是我们修改该副本的机会,以便为持久保存做好准备。 事不宜迟,下面是执行此操作的代码:
let copy = Array.from(myArray, (item) => {
let obj = Object.assign({}, item)
for (let key of Object.keys(obj)) {
if (key.startsWith('_') || key === '$$hashKey') {
delete obj[key]
}
}
return obj
});
如果您想现场观看此示例, 请观看此演示 。
$$hashKey
东西是什么?
AngularJS将一个名为$$hashKey
的键放入正在渲染的每个对象中(如果它认为需$$hashKey
)。 通过这种方式,它可以内部知道发生了什么变化,没有变化。 AngularJS中有一个内置实用程序,可用来剥离这些实用程序:
angular.toJson(myObject);
如果我们像ng-repeat="thing in things"
Things ng-repeat="thing in things"
这样重复,AngularJS将把这些键放入。但是要避免这种情况,我们可以使用跟踪键,例如:
ng-repeat="thing in things track by thing.id"
而且不会放入。
无论哪种方式,由于我们可能正在构建一个可用于各种AngularJS结构的实用工具,因此剥离那些$$hashKey
东西可能是一个非常好的主意。
把它放在一起
我们的第一个演示应用程序相当愚蠢,因为您永远不会将任何内容“输入”到状态中,因此保存它没有任何意义。 首先,让我们更改演示应用程序,以便它实际获取值得保存的输入。 该应用程序将成为“星期日志”,您可以在其中输入一周中每天的工作。
让我们先构建它,以使骨骼起作用。 以下是我们需要的HTML代码:
<div ng-app="app">
<div ng-controller="Ctrl">
<div ng-repeat="week in weeks track by week.date">
<h3 ng-click="week._expanded=!week._expanded">
Week of {{ week._date | date: 'EEEE MMM d' }} - {{ week._end | date: 'EEEE d, yyyy' }}
<span ng-if="week._expanded">(click to close)</span>
<span ng-if="!week._expanded">(click to edit)</span>
</h3>
<div ng-if="week._expanded" class="expanded">
<table>
<tr ng-repeat="day in week._days">
<td>{{ day.name }}</td>
<td><input type="text" ng-model="day.text" ng-blur="saveWeeks()"></td>
</tr>
</table>
</div>
<div ng-if="!week._expanded" class="collapsed">
<table>
<tr ng-repeat="day in week._days">
<td><b>{{ day.name }}</b></td>
<td>{{ day.text }}</td>
</tr>
</table>
</div>
</div>
<hr>
<p>
Click to edit the week entries. After reloading the page you
get back what you typed last.
</p>
<p>
A useful extension of this would be to be able to add new weeks. And make it infinitely prettier.
</p>
</div>
</div>
这是JavaScript代码:
angular.module('app', [])
.controller('Ctrl', ($scope) => {
const weekdays = [
[0, 'Monday'],
[1, 'Tuesday'],
[2, 'Wednesday'],
[3, 'Thursday'],
[4, 'Friday'],
[5, 'Saturday'],
[6, 'Sunday'],
]
let dressUp = (weeks) => {
// add internally needed things
weeks.forEach((week) => {
week._date = Date.create(week.date)
week._end = week._date.clone()
week._end.addDays(6)
week._days = [];
weekdays.forEach((pair) => {
week._days.push({
index: pair[0],
name: pair[1],
text: week.days[pair[0]] || ''
})
})
})
}
let dressDown = (weeks) => {
// week.days is an object, turn it into an array
weeks.forEach((week) => {
week._days.forEach((day) => {
week.days[day.index] = day.text || ''
})
})
}
// try to retrieve from persistent storage
$scope.weeks = JSON.parse(
localStorage.getItem('weeks') || '{"weeks":[]}'
).weeks
if (!$scope.weeks.length) {
// add a first default
let monday = Date.create().beginningOfISOWeek().format('{yyyy}-{MM}-{dd}')
$scope.weeks.push({date: monday, days: {}})
}
// when retrieved it doesn't have the internal
// stuff we need for rendering, so dress it up
dressUp($scope.weeks)
$scope.saveWeeks = () => {
// copy from _days to days
dressDown($scope.weeks)
// make a deep copy clone
let copy = Array.from($scope.weeks, (item) => {
let obj = Object.assign({}, item)
for (let key of Object.keys(obj)) {
if (key.startsWith('_') || key === '$$hashKey') {
delete obj[key]
}
}
return obj
})
// actually save it persistently
localStorage.setItem('weeks', JSON.stringify({weeks: copy}))
}
});
请参阅CodePen上SitePoint ( @SitePoint )的“笔每周”日志 。
那里发生了很多事。
该演示应用程序使用localStorage
来保持持久性。 加载该应用程序后,它会检索过去保存的内容,如果那里没有任何内容,它将创建第一个示例。 然后,它将显示星期,您可以编辑条目。 一旦您模糊了任何输入字段,它就会开始保存过程。 首先,它修改了状态(从列表week._days
复制到对象week.days
),其次,它创建了一个深层副本克隆,当这样做时,它删除了我们认为不需要保留的所有键。 最后,它将其存储在localStorage
。
尝试打开演示应用程序,键入内容,在输入字段中模糊显示,然后刷新整个页面,您应该会看到输入的数据仍然在那里。
毅力与超越
有人可能会嘲笑浏览器中的localStorage
是持久性的概念。 它仅存储在您的设备中,如果您丢失了设备或完全擦除了个人资料,则表明存储空间已消失。 更加持久的解决方案是在云中建立适当的数据库。 一个备份和复制的东西,没什么。 但是,这超出了本文的范围,并且做到这一点的扩展确实很简单。 因为我们将JSON存储在localStorage
,所以这意味着Ajax仅发送我们要保存的内容将变得非常容易,而您不必担心列或类型。
显然,如果您沿Ajax路线走下去,那么在输入字段更改时保存整个大事可能会过多。 但是,由于每个ng-blur
知道您编辑了星期几和星期几,因此Ajax可以只发送该特定日期的更改。 读者,请多多练习。
结论
这是现实的吗? 是的! 可以缩放吗? 是的,它确实。
我们正在进入一个时代,前端变得越来越聪明,后端变得越来越笨拙。 这意味着您将许多业务逻辑放在前端代码中,并要求后端仅执行真正的基本任务(例如“仅存储此blob!”)。 如果要构建那些花哨的离线优先应用程序之一,则需要开始考虑将所有状态保留在webapp中,并且不能依赖于服务器始终可用。 最终必须如此。
现在,您需要将后端视为同步服务器。 这并不意味着你必须发送一切来回,但你想要做的是在前端和后端都和复杂的业务逻辑的最后一件事情的整个斑点,如果你想脱机先应用体验,您必须向前端倾斜以了解发生魔术的地方。
另外,将业务逻辑放入前端并将后端视为“哑巴”,这意味着您可以对后端技术变得更加不可知。 这些后端技术正忙于提供,备份和快速。 上面提到的演示应用程序使用localStorage
但是很容易用Kinto , PouchDB或Firebase替换。 这些都是非常可扩展的平台。 因此,这种模式确实可以扩展。
From: https://www.sitepoint.com/persisting-state-in-angularjs/