资讯专栏INFORMATION COLUMN

Vuex + Firebase 构建 Notes App

Invoker / 1770人阅读

摘要:前几天翻译了基于这篇博客的文章用构建一个笔记应用。概述如果熟悉的使用,可以放心地跳过这一段。存的数据都是对象。修改的逻辑简单来说就是在思路上要完成从数组到对象的转换。把以上各条添加到里面。

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

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

加了笔记检索功能

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

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

一、把数据同步到 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
  }
}
二、笔记检索功能

效果图:

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

// NoteList.vue


// 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://www.ucloud.cn/yun/79360.html

相关文章

  • 学习实践 - 收藏集 - 掘金

    摘要:官网地址聊天机器人插件开发实例教程一创建插件在系统技巧使你的更加专业前端掘金一个帮你提升技巧的收藏集。我会简单基于的简洁视频播放器组件前端掘金使用和实现购物车场景前端掘金本文是上篇文章的序章,一直想有机会再次实践下。 2道面试题:输入URL按回车&HTTP2 - 掘金通过几轮面试,我发现真正那种问答的技术面,写一堆项目真不如去刷技术文章作用大,因此刷了一段时间的博客和掘金,整理下曾经被...

    mikyou 评论0 收藏0
  • 使用 Vuex + Vue.js 构建单页应用

    摘要:鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇下的使用方法,传送门使用构建单页应用新篇华丽的分割线原文地址前言在最近学习的时候,看到国外一篇讲述了如何使用和来构建一个简单笔记的单页应用的文章。 鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用【新篇】 ---------...

    tomorrowwu 评论0 收藏0
  • 使用 Vuex + Vue.js 构建单页应用

    摘要:鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇下的使用方法,传送门使用构建单页应用新篇华丽的分割线原文地址前言在最近学习的时候,看到国外一篇讲述了如何使用和来构建一个简单笔记的单页应用的文章。 鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用【新篇】 ---------...

    cnsworder 评论0 收藏0
  • 使用 Vuex + Vue.js 构建单页应用

    摘要:鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇下的使用方法,传送门使用构建单页应用新篇华丽的分割线原文地址前言在最近学习的时候,看到国外一篇讲述了如何使用和来构建一个简单笔记的单页应用的文章。 鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用【新篇】 ---------...

    levius 评论0 收藏0
  • 使用 Vuex + Vue.js 构建单页应用

    摘要:鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇下的使用方法,传送门使用构建单页应用新篇华丽的分割线原文地址前言在最近学习的时候,看到国外一篇讲述了如何使用和来构建一个简单笔记的单页应用的文章。 鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用【新篇】 ---------...

    UsherChen 评论0 收藏0

发表评论

0条评论

Invoker

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<