使用 Vue 构建 RSS 阅读器:第 2 部分

Avatar of Raymond Camden
Raymond Camden

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

欢迎来到本关于使用 Vue 构建 RSS 阅读器的迷你系列的第 2 部分。在 上一篇文章 中,我介绍了如何使用 Vue.js 和 Vuetify 构建前端演示,以及使用 Webtask 构建后端。当我构建初始版本时,我就知道它只是一个“初始”版本。我花了一些时间进行了一些更新,虽然我不敢称之为“完美”版本,但我认为我做了一些改进,我想与大家分享。

文章系列

  1. 设置和第一次迭代
  2. 改进和最终版本 (本篇文章)

在开始之前,这里提供了已完成的演示和源代码的链接。

请随意 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 的一点是,我知道我可以安全地执行此操作而不会干扰应用程序的其余部分。


文章系列

  1. 设置和第一次迭代
  2. 改进和最终版本 (本篇文章)