欢迎来到本关于使用 Vue 构建 RSS 阅读器的迷你系列的第 2 部分。在 上一篇文章 中,我介绍了如何使用 Vue.js 和 Vuetify 构建前端演示,以及使用 Webtask 构建后端。当我构建初始版本时,我就知道它只是一个“初始”版本。我花了一些时间进行了一些更新,虽然我不敢称之为“完美”版本,但我认为我做了一些改进,我想与大家分享。
文章系列
- 设置和第一次迭代
- 改进和最终版本 (本篇文章)
在开始之前,这里提供了已完成的演示和源代码的链接。
请随意 fork、提交 PR 和报告错误,直到您心满意足!
计划
当我在第 1 部分中分享初始版本时,我概述了一些改进 RSS 阅读器的想法,包括
- 迁移到 Vuex。
- 开始在布局中切换到组件。(好吧,我之前已经在使用 Vuetify 组件,但我的意思是应用程序的自定义组件。)
- 使用 IndexedDB 存储 Feed 项目以实现更快的访问和离线支持。
这就是计划,就像大多数计划一样,我未必能够在此更新中完成所有内容(我将在最后解释原因)。但希望您能将这些改进视为应用程序“朝着正确方向前进”的总体体现。言归正传,让我们开始吧!
实现 Vuex
我将首先讨论应用程序中最大的变化,即添加 Vuex。正如我在上一篇文章中所说,Vuex 在其 “什么是 Vuex” 页面上将自己描述为“状态管理模式 + 库”。对他们的文档没有冒犯之意,但从实践意义上来说,我很难理解这到底意味着什么。
在现在的一些小型项目中使用它之后,我开始领会到它提供的优势。对我而言,核心益处是为数据提供一个集中接口。如果我有一个使用值数组的基本 Vue 应用程序,我可能有多种不同的修改方法。当我开始需要在数据更改之前应用某些规则时会发生什么?举个简单的例子,假设一个 RSS Feed 数组。在添加新的 Feed 之前,我想确保它在列表中不存在。如果我有一种方法可以添加到 Feed 列表中,那不是问题,但如果我有更多方法,那么在不同的方法之间保持逻辑同步可能会变得很麻烦。我可以简单地构建一个实用程序来执行此操作,但如果我还涉及其他组件会怎么样呢?
虽然这不是**完全相同**的比较,但我感觉 Vuex 让我想起了 Angular 中 Provider 或 Service 的工作方式。如果我想对任何数据执行操作,我都会确保使用中央 Provider 来处理对该数据的所有访问。这就是我看待 Vuex 的方式。
因此,此应用程序中的重大更改是将所有与数据相关的内容迁移到存储区。我首先将库添加到我的 HTML 中
<script src="https://unpkg.com/vuex"></script>
太棒了!完成了一半!(好吧,也许没有。)
然后,我在我的 JavaScript 文件中创建了存储区的实例
const feedStore = new Vuex.Store({
// lots of stuff here
});
并将其包含在我的 Vue 应用程序中
let app = new Vue({
el: '#app',
store:feedStore,
// lots of stuff here too...
});
现在是有趣的部分。每当我的 Vue 应用程序需要数据时,主要包括 Feed 列表和这些 Feed 中的项目,它都会向存储区请求它们。因此,例如,我的 feeds
值现在是计算出来的
feeds() {
return feedStore.state.feeds;
},
这现在在我的存储区的 state
部分中定义
state: {
allItems: [],
feeds: [],
selectedFeed: null
},
请注意,feeds
默认值为一个空数组。我之前使用过 Vue 应用程序的 created
事件从 localStorage
中读取数据。现在,我请求存储区执行此操作
created() {
feedStore.dispatch('restoreFeeds');
},
回到存储区,逻辑基本上相同
restoreFeeds(context) {
let feedsRaw = window.localStorage.getItem('feeds');
if(feedsRaw) {
try {
let feeds = JSON.parse(feedsRaw);
context.state.feeds = feeds;
context.state.feeds.forEach(f => {
context.dispatch('loadFeed', f);
});
} catch(e) {
console.error('Error restoring feed json'+e);
// bad json or other issue, nuke it
window.localStorage.removeItem('feeds');
}
}
},
我说“基本上相同”,除了我现在正在对从 localStorage
读取的值进行一些错误检查。但关键点在于,我已经说过我在切换到 IndexedDB 方面失败了,但理论上,我可以使用更新的存储区构建此应用程序的第三个版本,而我的 Vue 应用程序不会知道有什么区别。这就是我开始真正感到兴奋的地方。我工作得越多,“愚蠢”的 Vue 应用程序就变得越少,并且它与任何特定存储实现的绑定也越少。现在让我们看看完整的 Vue 应用程序
let app = new Vue({
el: '#app',
store:feedStore,
data() {
return {
drawer:true,
addFeedDialog:false,
addURL:'',
urlError:false,
urlRules:[],
selectedFeed:null
}
},
computed: {
showIntro() {
return this.feeds.length == 0;
},
feeds() {
return feedStore.state.feeds;
},
items() {
return feedStore.getters.items;
}
},
created() {
feedStore.dispatch('restoreFeeds');
},
methods:{
addFeed() {
this.addFeedDialog = true;
},
allFeeds() {
feedStore.dispatch('filterFeed', null);
},
addFeedAction() {
this.urlError = false;
this.urlRules = [];
feedStore.dispatch('addFeed', {url:this.addURL})
.then(res => {
this.addURL = '';
this.addFeedDialog = false;
})
.catch(e =>{
console.log('err to add', e);
this.urlError = true;
this.urlRules = ["URL already exists."];
});
},
deleteFeed(feed) {
feedStore.dispatch('deleteFeed', feed);
},
filterFeed(feed) {
feedStore.dispatch('filterFeed', feed);
}
}
})
您会注意到,几乎所有实际逻辑都消失了,我在这里真正做的只是 UI 方面的事情。在这里打开一个模态,在那里添加一个错误,等等。
您可以查看完整的存储区 此处,尽管我为将所有内容都放在一个文件中而表示歉意。
添加组件
我提到的另一个更改是开始将视图层“组件化”。我最终只创建了一个组件,feed-item
。这稍微减少了 HTML 中的总行数
<v-flex xs12 v-for="item in items" :key="item.link">
<feed-item :title="item.title" :content="item.content" :link="item.link" :feedtitle="item.feedTitle" :color="item.feedColor" :posted="item.pubDate"></feed-item>
</v-flex>
这并不是一个巨大的改变,但当开始处理 Feed 显示时,它确实使事情变得更容易了一些。由于我还没有使用花哨的构建器,因此我像这样直接在 JavaScript 中定义了我的组件
Vue.component('feed-item', {
props:[
'color','title','content','link','feedtitle', 'posted'
],
template: `
<v-card :color="color">
<v-card-title primary-title>
<div class="headline">{{title}} ({{posted | dtFormat}})</div>
</v-card-title>
<v-card-text>
{{content | maxText }}
</v-card-text>
<v-card-actions>
<v-btn flat target="_new" :href="link">Read on {{feedtitle}}</v-btn>
</v-card-actions>
</v-card>
`
});
我在这里没有做任何花哨的事情——没有动态逻辑或事件或任何类似的东西,但我肯定可以在以后添加有意义的逻辑。我终于开始添加发布日期和时间了。如果您好奇我是如何构建为此使用的格式化程序的,请阅读我的文章 使用 Vue.js 和原生 Web 规范构建 i18n 过滤器。”
删除的力量!
哦,我终于添加了一种删除 Feed 的方法

这只是在 Vue 对象上触发一个方法,该方法又触发对存储区的调用,存储区负责从 UI 中删除 Feed 和项目,然后将其持久化。这是一件小事,但是,哇,在第一个版本中测试时我多么希望有它。最后,让我们看看所有内容

后续步骤……以及 IndexedDB 发生了什么?
正如我在开头所说,此版本仍然不完美,但我确实感觉好多了。我强烈建议您在下面的评论中或 GitHub 存储库中分享技巧、建议和错误报告。
那么 IndexedDB 支持发生了什么?我遇到的问题是如何正确初始化数据库。Vuex 存储区没有 created
过程的概念。我可以这样做
// dummy code for getting feeds
dispatch('getDB')
.then(() =>
// do stuff
);
其中 getDB
操作返回一个 Promise 并处理执行一次性 IndexedDB 打开并将值存储在状态中的操作。我以后可能会尝试一下,再说一遍,我喜欢 Vuex 的一点是,我知道我可以安全地执行此操作而不会干扰应用程序的其余部分。
文章系列
- 设置和第一次迭代
- 改进和最终版本 (本篇文章)