转载

Vuex + Firebase 构建 Notes App

前几天翻译了基于 这篇博客 的文章: 用 Vuex 构建一个笔记应用 。在此基础上我对它做了一些更新:

  • 把数据同步到 Firebase 上,不会每次关掉浏览器就丢失数据。

  • 加了笔记检索功能

  • 为保证代码整洁,加上了 eslint

你可以从 Github Repo 下载源码,和 Firebase 的同步效果看下面这个 gif:

Vuex + Firebase 构建 Notes App

一、把数据同步到 Firebase

可能你也知道 Vue.js 和 Firebase 合作搞出了一个 Vuefire , 但是在这里并不能用它,因为用 Vuex 管理数据的结果就是组件内部只承担基本的View层的职责,而数据基本上都在 store 里面。所以我们只能把数据的存取放在 store 里面。

1.1 Firebase 概述

如果熟悉 Firebase 的使用,可以放心地跳过这一段。

如果你还没有 Firebase 的账号,可以去注册一个,注册号之后会自动生成一个"MY FIRST APP",这个初始应用给的地址就是用来存数据的地方。

Firebase 存的数据都是 JSON 对象。我们向 JSON 树里面加数据的时候,这条数据就变成了 JSON 树里的一个键。比方说,在 /user/mchen 下面加上 widgets 属性之后,数据就变成了这个样子:

{   "users": {     "mchen": {       "friends": { "brinchen": true },       "name": "Mary Chen",       "widgets": { "one": true, "three": true }     },     "brinchen": { ... },     "hmadi": { ... }   } }

创建数据引用

要读写数据库里的数据,首先要创建一个指向数据的引用,每个引用对应一条 URL。要获取其子元素,可以用 child API, 也可以直接把子路径加到 URL 上:

// referene  new Firebase(https://docs-examples.firebaseio.com/web/data)  // 子路径加到 URL 上 new Firebase("https://docs-examples.firebaseio.com/web/data/users/mchen/name")  // child API rootRef.child('users/mchen/name')

Firebase 数据库中的数组

Firebase 数据库不能原生支持数组。如果你存了一个数组,实际上是把它存储为一个用数组作为键的对象:

// we send this ['hello', 'world'] // firebase database store this {0: 'hello', 1: 'world'}

存储数据

set()

set() 方法把新数据放到指定的引用的路径下,代替那个路径下原有的数据。它可以接收各种数据类型,如果参数是 null 的话就意味着删掉这个路径下的数据。

举个例子:

// 新建一个博客的引用 var ref = new Firebase('https://docs-examples.firebaseio.com/web/saving-data/fireblog')  var usersRef = ref.child('users')  usersRef.set({   alanisawesome: {   date_of_birth: "June 23, 1912",   full_name: "Alan Turing"   },   gracehop: {     date_of_birth: "December 9, 1906",     full_name: "Grace Hopper"   } })

当然,也可以直接在子路径下存储数据:

usersRef.child("alanisawesome").set({   date_of_birth: "June 23, 1912",   full_name: "Alan Turing" })  usersRef.child("gracehop").set({   date_of_birth: "December 9, 1906",   full_name: "Grace Hopper" })

不同之处在于,由于分成了两次操作,这种方式会触发两个事件。另外,如果 usersRef 下本来有数据的话,那么第一种方式就会覆盖掉之前的数据。

update()

上面的 set() 对数据具有"破坏性",如果我们并不想改动原来的数据的话,可能 update() 是更合适的选择:

var hopperRef = userRef.child('gracehop') hopperRef.update({   'nickname': 'Amazing Grace' })

这段代码会在 Grace 的资料下面加上 nickname 这一项,如果我们用的是 set() 的话,那么 full_namedate_of_birth 就会被删掉。

另外,我们还可以在多个路径下同时做 update 操作:

usersRef.update({   "alanisawesome/nickname": "Alan The Machine",   "gracehop/nickname": "Amazing Grace" })

push()

前面已经提到了,由于数组索引不具有独特性,Firebase 不提供对数组的支持,我们因此不得不转而用对象来处理。

在 Firebase 里面, push 方法会为每一个子元素根据时间戳生成一个唯一的 ID,这样就能保证每个子元素的独特性:

var postsRef = ref.child('posts')  // push 进去的这个元素有了自己的路径 var newPostRef = postsRef.push()  // 获取 ID var uniqueID = newPostRef.key()  // 为这个元素赋值 newPostRef.set({   author: 'gracehop',   title: 'Announcing COBOL, a New Programming language' })  // 也可以把这两个动作合并 postsRef.push().set({   author: 'alanisawesome',   title: 'The Turing Machine' })

最后生成的数据就是这样的:

{   "posts": {     "-JRHTHaIs-jNPLXOQivY": {       "author": "gracehop",       "title": "Announcing COBOL, a New Programming Language"     },     "-JRHTHaKuITFIhnj02kE": {       "author": "alanisawesome",       "title": "The Turing Machine"     }   } }

这篇博客 聊到了这个 ID 是怎么回事以及怎么生成的。

获取数据

获取 Firebase 数据库里的数据是通过对数据引用添加一个异步的监听器来完成的。在数据初始化和每次数据变化的时候监听器就会触发。 value 事件用来读取在此时数据库内容的快照,在初始时触发一次,然后每次变化的时候也会触发:

// Get a database reference to our posts var ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts")  // Attach an asynchronous callback to read the data at our posts reference ref.on("value", function(snapshot) {   console.log(snapshot.val()); }, function (errorObject) {   console.log("The read failed: " + errorObject.code); });

简单起见,我们只用了 value 事件,其他的事件就不介绍了。

1.2 Firebase 的数据处理方式对代码的影响

开始写代码之前,我想搞清楚两个问题:

  • Firebase 是怎么管理数据的,它对组件的 View 有什么影响

  • 用户交互过程中是怎么和 Firebase 同步数据的

先看第一个问题,这是我在 Firebase 上保存的 JSON 数据:

{   "notes" : {     "-KGXQN4JVdopZO9SWDBw" : {       "favorite" : true,       "text" : "change"     },     "-KGXQN6oWiXcBe0a54cT" : {       "favorite" : false,       "text" : "a"     },     "-KGZgZBoJJQ-hl1i78aa" : {       "favorite" : true,       "text" : "little"     },     "-KGZhcfS2RD4W1eKuhAY" : {       "favorite" : true,       "text" : "bit"     }   } }

这个乱码一样的东西是 Firebase 为了保证数据的独特性而加上的。我们发现一个问题,在此之前 notes 实际上是一个包含对象的数组:

[   {     favorite: true,     text: 'change'   },   {     favorite: false,     text: 'a'   },     {     favorite: true,     text: 'little'   },     {     favorite: true,     text: 'bit'   }, ]

显然,对数据的处理方式的变化使得渲染 notes 列表的组件,也就是 NotesList.vue 需要大幅修改。修改的逻辑简单来说就是在思路上要完成从数组到对象的转换。

举个例子,之前 filteredNotes 是这么写的:

filteredNotes () {   if (this.show === 'all'){     return this.notes   } else if (this.show === 'favorites') {     return this.notes.filter(note => note.favorite)   } }

现在的问题就是,notes 不再是一个数组,而是一个对象,而对象是没有 filter 方法的:

filteredNotes () {   var favoriteNotes = {}   if (this.show === 'all') {     return this.notes   } else if (this.show === 'favorites') {     for (var note in this.notes) {       if (this.notes[note]['favorite']) {         favoriteNotes[note] = this.notes[note]       }     }     return favoriteNotes   } }

另外由于每个对象都对应一个自己的 ID,所以我也在 state 里面加了一个 activeKey 用来表示当前笔记的 ID,实际上现在我们在 TOGGLE_FAVORITE , SET_ACTIVE 这些方法里面都需要对相应的 activeKey 赋值。

再看第二个问题,要怎么和 Firebase 交互:

// store.js let notesRef = new Firebase('https://crackling-inferno-296.firebaseio.com/notes')  const state = {   notes: {},   activeNote: {},   activeKey: '' }  // 初始化数据,并且此后数据的变化都会反映到 View notesRef.on('value', snapshot => {   state.notes = snapshot.val() })  // 每一个操作都需要同步到 Firebase const mutations = {    ADD_NOTE (state) {     const newNote = {       text: 'New note',       favorite: false     }     var addRef = notesRef.push()     state.activeKey = addRef.key()     addRef.set(newNote)     state.activeNote = newNote   },      EDIT_NOTE (state, text) {     notesRef.child(state.activeKey).update({       'text': text     })   },    DELETE_NOTE (state) {     notesRef.child(state.activeKey).set(null)   },    TOGGLE_FAVORITE (state) {     state.activeNote.favorite = !state.activeNote.favorite     notesRef.child(state.activeKey).update({       'favorite': state.activeNote.favorite     })   },    SET_ACTIVE_NOTE (state, key, note) {     state.activeNote = note     state.activeKey = key   } }

二、笔记检索功能

效果图:

Vuex + Firebase 构建 Notes App

这个功能比较常见,思路就是列表渲染 + 过滤器:

// NoteList.vue  <!-- filter --> <div class="input">   <input v-model="query" placeholder="Filter your notes..."> </div>  <!-- render notes in a list --> <div class="container">   <div class="list-group">     <a v-for="note in filteredNotes | byTitle query"       class="list-group-item" href="#"       :class="{active: activeKey === $key}"       @click="updateActiveNote($key, note)">       <h4 class="list-group-item-heading">         {{note.text.substring(0, 30)}}       </h4>     </a>   </div> </div>
// NoteList.vue  filters: {   byTitle (notesToFilter, filterValue) {     var filteredNotes = {}     for (let note in notesToFilter) {       if (notesToFilter[note]['text'].indexOf(filterValue) > -1) {         filteredNotes[note] = notesToFilter[note]       }     }     return filteredNotes   } }

三、在项目中用 eslint

如果你是个 Vue 重度用户,你应该已经用上 eslint-standard 了吧。

"eslint": "^2.0.0", "eslint-config-standard": "^5.1.0", "eslint-friendly-formatter": "^1.2.2", "eslint-loader": "^1.3.0", "eslint-plugin-html": "^1.3.0", "eslint-plugin-promise": "^1.0.8", "eslint-plugin-standard": "^1.3.2"

把以上各条添加到 devDependencies 里面。如果用了 vue-cli 的话, 那就不需要手动配置 eslint 了。

// webpack.config.js module: {   preLoaders: [     {       test: //.vue$/,       loader: 'eslint'     },     {       test: //.js$/,       loader: 'eslint'     }   ],   loaders: [ ... ],   eslint: {     formatter: require('eslint-friendly-formatter')   } }

如果需要自定义规则的话,就在根目录下新建 .eslintrc ,这是我的配置:

module.exports = {   root: true,   // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style   extends: 'standard',   // required to lint *.vue files   plugins: [     'html'   ],   // add your custom rules here   'rules': {     // allow paren-less arrow functions     'arrow-parens': 0,     'no-undef': 0,     'one-var': 0,     // allow debugger during development     'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0   } }

四、结语

讲得比较粗糙,具体可以拿 源码 跑一下。如果有什么问题,欢迎评论。

原文  https://segmentfault.com/a/1190000005038509
正文到此结束
Loading...