如何使用Vue 2制作带有本地存储的CRUD应用程序

114 阅读4分钟

如何使用Vue 2制作带有本地存储的CRUD应用

Vue.js是一个渐进的、多功能的前端JavaScript框架。由于它的简单性和小捆绑规模,它的可采用性很高。本地存储是现代浏览器中的一个Web存储API,它允许我们在用户的浏览器上将数据存储为键值对的字符串。

有了这个,我们就可以在不与后端应用程序通信的情况下处理数据,这就增强了数据的持久性。与使用cookies时不同,它在客户端最多存储4kb的数据。它们在HTTP请求时被发送到服务器上,并且可以被服务器修改。

前提条件

要跟上本教程,读者将需要。

  • Node.js 6.x或更新版本
  • Npm 5.10或更新版本
  • Vue CLI
  • 对JavaScript、CSS和HTML有一定了解

用Vue CLI创建项目

要创建一个Vue.js项目,首先要检查Vue CLI是否已在你的电脑中全局安装。

使用终端运行。

 $ vue –version 

如果没有安装,运行下面的命令来安装它。

 $ npm install -g @vue/cli 

进入你的工作区文件夹,运行下面的命令,创建一个新的Vue.js应用程序。

$ vue create books-app

使用方向键选择。

❯ Default ([Vue 2] babel, eslint) 

然后点击回车。创建后,导航到创建的文件夹books-app ,并通过运行命令为该应用程序提供服务。

$ cd books-app
$ npm run serve

然后,在浏览器中打开URL http://localhost:8080,查看该应用程序。

安装Vue.js DevTools

这是一个用于调试Vue.js应用程序的浏览器扩展。它可以检查组件、道具、路由、vuex等。

打开你的浏览器,并安装MozillaChrome的Vue.js DevTools扩展。

要打开浏览器的DevTools,在Windows/Linux上按SHIFT + CTRL + J ,在MacOS上按Command + Option + j

创建书籍组件

这个应用程序将管理一个要阅读的书籍列表。用你选择的代码编辑器打开这个应用程序。在我们浏览器上显示的组件是位于src/components 文件夹中的HelloWorld 组件。

我们将删除它和它在App.vue 中的引用,包括图像标识。现在在components 文件夹中创建一个Books.vue 文件。将下面的代码添加到这个文件中。

<template>
    <div>
        <h2>My Books List</h2>
    </div>
</template>

<script>
    export default {
        name: "Books"
    }
</script>

<style scoped>

</style>

注意,在Vue 2中,组件模板应该只包含一个根元素。否则,它将抛出一个错误。然后我们可以将Books 组件导入到根组件中。App.vue 组件现在应该如下图所示。

data函数返回一个空的书籍数组,这些书籍我们以后会添加到数组中。

<template>
  <div id="app">
    <Books/>
  </div>
</template>

<script>
import Books from "./components/Books";

export default {
  name: 'App',
  components: {
    Books
  }, 
  data () {
    return {
      books: []
    }
  }
}
</script>

<style>
#app {
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

v-bind 是一个Vue指令,用于将数据附加到一个Vue组件。这将帮助我们绑定传递给 组件的数据。Books

App.vue 进行修改,如下图所示。

<Books v-bind:books="books"/>

props 是用来将数据从父组件传递给子组件的。在这种情况下, 是父组件,而 是子组件。App.vue Books.vue

为了使用道具,编辑子组件的<script> ,如下图所示。

<script>
    export default {
        name: "Books",
        props: ["books"]
    }
</script>

创建 "BookItem "组件

在组件文件夹中创建一个文件BookItem.vue 。这个组件将代表一本书。BookItem.vue 的代码应该像下面显示的片段。

<template>
    <div>
        <p></p>
    </div>
</template>

<script>
    export default {
        name: "BookItem"
    }
</script>

现在,将BookItem 作为一个子组件导入到Books 组件中,并在组件对象中声明它。在这里,我们将循环浏览数据,并使用Vue指令v-for ,向用户显示BookItems

Books.vue 的代码现在应该如下所示。

<template>
    <div>
        <h2>My Books List</h2>
        <div v-bind:key="book.id" v-for="book in books">
            <BookItem  v-bind:book="book"></BookItem>
        </div>
    </div>
</template>

<script>
    import BookItem from "./BookItem";
    export default {
        name: "Books",
        props: ["books"],
        components: {
            BookItem
        }
    }
</script>

注意v-bind:key 。这很重要,因为它给了Vue一个提示来追踪每个节点的身份。v-bind:book 将数据绑定到Vue组件上。

为了显示一本书,编辑BookItem.vue ,如下图所示。

<template>
    <div>
        <p>{{book.title}}</p>
    </div>
</template>

<script>
    export default {
        name: "BookItem",
        props: ["book"]
    }
</script>

你可以在App.vue 中的书籍数组中添加你自己的数据,如下图所示,以显示数据到用户界面。

books: [
  {
    id:1,
    title: "1000 Leagues Under the Sea"
  },
  {
    id:2,
    title: "The Scorpion"
  },
]

创建 "AddBookItem "组件

现在你可以删除上面的JSON测试数据。在components 文件夹下,创建一个名为AddBook.vue 的文件。把它导入到App.vue ,并在脚本内的组件对象中声明它,如下图所示。

import Books from "./components/Books";
import AddBookItem from "./components/AddBookItem";
export default {
  name: 'App',
  components: {
     Books,
     AddBookItem
  },
}

现在在AddBookItem.vue 中添加以下代码。

<template>
    <div>
        <form @submit="addBook">
            <input type="text" name="title" v-model="title" placeholder="Add Book">
            <button type="submit">Add Book</button>
        </form>
    </div>
</template>

<script>
    export default {
        name: "AddBookItem",
        data () {
            return {
                title: ''
            }
        },
        methods: {
            addBook(e){
                e.preventDefault();
                const newBook = {
                    title: this.title,
                    id: Math.floor(Math.random() * 100)
                };
                if (newBook.title !== ''){
                    this.$emit('add-book-event', newBook);
                }
                this.title = ''
            }
        }

    }
</script>

这段代码有一个表单,你可以用它来添加一本书。它还有一个方法addBook() 和一个vue-directivev-model ,在用户输入和Vue.js组件之间建立一个双向的绑定。对输入值的任何改变都会改变绑定的数据,反之亦然。在这种情况下,title 。你会看到一个添加书籍的表单。

每本书都需要一个唯一的ID。我们将使用JavaScript的Math.random() 方法来生成唯一的id。

$emit() 方法会发出一个事件add-book-event ,用来根据用户的动作从子组件向父组件传递数据。当用户添加一本书并提交时,这个事件会被发射到父组件。

为了让父组件(App.vue)能够监听来自子组件(AddBookItem.vue)的add-book-event 事件,我们创建了一个方法addBook() ,并将其分配给发出的事件。

App.vue 进行修改,使其看起来像下面这个。让AddBookItem() 在模板中位于Book 组件的上方。

<template>
  <div id="app">
    <AddBookItem  v-on:add-book-event="addBook" />
    <Books v-bind:books="books"/>
  </div>
</template>

就在data() ,使用下面的代码添加一个方法addBook()

methods: {
  addBookItem(newBook){
    this.books = [...this.books, newBook]
  },
}

该方法在书籍数组中添加一个新的书籍,我们使用传播操作符,这将新的书籍添加到数组的末尾,而不创建一个新的数组。

将数据保存到本地存储

我们将使用Vue.js内置的方法watch()。这个方法会自动观察书籍数组中的变化,并将数据保存到本地存储。

watch() 方法有一个名为deep 的属性,它被设置为true,以通知Vue实例始终关注books数组中的变化。

watch() 在处理你的组件之外的数据时,如浏览器API或获取数据时,会用到这个属性。

App.vue ,添加以下代码到<script>

watch: {
  books: {
    handler() {
      localStorage.setItem('books',JSON.stringify(this.books))
    },
    deep: true
  }
}

本地存储使用setItem() 方法将数据保存为键值对,数据必须是一个字符串,所以我们将JSON转换成字符串,以便从使用JSON.stringify()方法保存。

从本地存储加载数据

我们需要将保存的数据,从本地存储中显示给用户。我们将使用一个名为mounted()的生命周期钩子,在Vue实例被创建后执行。

在这个循环钩子中,我们使用方法localStorage.getItem('key') ,从本地存储中检索数据。我们用来存储的键和我们用来检索数据的键是一样的。

App.vue 中的watch() 方法之后添加下面的代码。

mounted() {
  if (localStorage.getItem("books")){
    this.books = JSON.parse(localStorage.getItem("books"))
  }
}

JSON.parse() 方法将一个字符串转换为一个JavaScript对象,因为数据在本地存储中只以字符串形式存储。

然后,该数据被设置为显示给用户的书籍数组。

现在你就可以看到添加后的书籍列表了。

从本地存储中删除数据

用以下代码更新BookItem.vue

<div class="float-left">
    <span class="float-right">
        {{book.title}}
        <button>
          <i class="glyphicon glyphicon-trash" @click="$emit('del-book-item', book.id)">delete</i>
       </button>
    </span>
</div>

我在index.html 文件中添加了一个bootstrap CSS CDN链接。你可以把你的样式设计得更好看。

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

当你点击删除按钮时,会发出一个事件,将书的ID传递给父级(Books.vue)。$emit() 是Vue将数据从子组件传递给父组件的方式。

Books.vue 更新<BookItem/> ,如下所示。

<BookItem v-bind:book="book" v-on:del-book-item="delBookMethod" />

将来自子组件的事件分配给一个叫做delBookMethod() 的方法。把它添加到方法对象中,并向它的父组件(App.vue)发出一个事件,同时传递书的ID。

methods: {
    delBookMethod(id){
        //send to parent
        this.$emit('del-book-event', id);
    },
 }

在父组件(App.vue)中,让我们做一些改变。

<Books v-bind:books="books" v-on:del-book-event="deleteBookItem" />

儿童事件被捕获并分配给一个方法deleteBookItem() 。这个方法将帮助我们删除被点击的图书项目。还记得我们从BookItem.vueBooks.vue 再到App.vue 所传递的ID吗?

它将被用来删除这本书,使用JavaScriptfilter() 方法来创建书籍数组,其中不包括传递ID的书籍。我们将使用ES6的箭头函数,如下图所示,这将返回所有的书,除了传递ID的那本。

AppVue ,将这个方法添加到methods

deleteBookItem(id){
  this.books = this.books.filter(book => book.id !== id);
}

编辑数据

就像我们在删除数据时做的那样,对BookItem.vue ,添加一个编辑按钮,代码应该是这样的。

<template>
    <div class="float-left">
        <span class="float-right">
            {{book.title}}
            <button>
              <i class="glyphicon glyphicon-pencil" @click="$emit('edit-book-item', book.id)">edit</i>
            </button>
            <button>
              <i class="glyphicon glyphicon-trash" @click="$emit('del-book-item', book.id)">delete</i>
            </button>
        </span>
    </div>
</template>

发出一个名为edit-book-item 的事件,并将书的ID传递给它的父类(Books.vue)。在Books.vue 中监听该事件并将其分配给一个名为editBookMethod() 的方法,如下所示。

<BookItem v-bind:book="book" v-on:del-book-item="delBookMethod" v-on:edit-book-item="editBookMethod" />

使用该方法,向它的父类(App.vue)发送一个事件,并将书的id与它一起传递。

将这个方法添加到Books.vue 中的methods

editBookMethod(id){
    //send to parent (App.vue)
    this.$emit('edit-book-event', id)
}

在父类中,进行修改以从Books 组件中捕获事件edit-book-event ,并将其分配给一个方法editBookItem() 。在data 中创建一个新的对象editBook ,它将保存被编辑的数据。

该对象应该有一个标题和一个ID。

两者都应该是空字符串。

data () {
  return {
    books: [],
    editBook: {
      title: '',
      id: ''
    }
  }
}

现在在editBookItem 方法中,我们需要找到该对象的id的索引。我们使用JavaScript的findIndex() 方法来做这件事。我们通过书籍数组来找到与子组件传递的ID相匹配的书籍对象,并将其分配给一个变量objIndex

这个变量帮助我们从书籍数组中获取书的标题,并将其与ID一起分配给editBook 对象中的标题,如下图所示。

editBookItem(id){
  //find the index of the book's id
  var objIndex = this.books.findIndex(obj=> obj.id === id);
  this.editBook.title = this.books[objIndex].title;
  this.editBook.id = id;
},

我们仍然不能编辑一本书。我们在AddBookItem 组件中捕获先前的事件edit-book-event ,并将其分配给一个方法editBookItemEvent()

然后我们用v-bind 指令将editBook 属性绑定到组件上,并将其作为一个道具传递给子组件(AddBookItem ),如下图所示。

<AddBookItem v-model="editBook.title" v-on:add-book-event="addBookItem"  v-bind:editBook="editBook"/>

让我们打开AddBookItem.vue 。我们从父节点接收editBook 数据对象作为道具。然后在数据函数中添加id 为空字符串,edit 为假。

name: "AddBookItem",
props: ['editBook'],
data () {
    return {
        title: '',
        id: '',
        edit: false
    }
}

我们将使用这个edit 属性来决定是否编辑或添加一个新书。我们首先检查用户是否没有编辑。我们保存数据,否则我们将编辑数据。

如果是编辑,我们发出一个edit-book-event ,并将保存编辑过的数据的变量bookItem ,连同事件一起传递给父级。

我们还清除输入字段。现在更新addBookItem() 方法,使其看起来如下所示。

addBook(e){
    e.preventDefault();
    if (this.edit === false){
        // add new book
        const newBook = {
            title: this.title,
            id: Math.floor(Math.random() * 100)
        };
        if (newBook.title !== ''){
            this.$emit('add-book-event', newBook);
        }
        this.title = ''
    }else{
        //edit book
        const bookItem = {
            title: this.title,
            id: this.id
        }
        //send to parent (App.vue)
        this.$emit('edit-book-event', bookItem)
        // clear input field
        this.title = '';
        this.edit = false;
    }
}

现在你可以点击编辑按钮,输入字段将被填充为书名。watch() 方法再次派上用场,以帮助我们观察 editBook 数据的任何变化。

我们设置deep:true 属性,让Vue实例持续观察变化。因此,在编辑一本书时,edit 属性将始终为真。

它还观察标题属性,如果它是空的,它就把edit 属性设置为假。

这里我们不需要deep 属性。

watch: {
    editBook: {
        handler() {
            this.title = this.editBook.title;
            this.id = this.editBook.id;
            this.edit = true
        },
        deep: true
    },
    title: {
        handler() {
            if (this.title === ''){
                this.edit = false;
            }
        }
    }
}

回到App.vue ,在编辑完一个标题后,一个事件edit-book-event 被发送到App.vue 。我们将该事件分配给一个方法,以便将变化保存到本地存储。更新你的代码,使其看起来像下面所示。

<AddBookItem v-model="editBook.title" v-on:add-book-event="addBookItem"  v-bind:editBook="editBook" v-on:edit-book-event="editBookItemEvent" />

现在我们创建一个editBookItemEvent() 方法来处理数据的保存。在这个方法中,我们找到id的对象的索引。

这个索引将被用来重新分配正在编辑的书的标题。如果你已经走到这一步,你就可以编辑一个书名了。

App.vue ,添加下面的代码到methods

editBookItemEvent(bookItem){
    //find the index of this id's object
     let objIndex = this.books.findIndex(obj => obj.id === bookItem.id)
     //update the item
     this.books[objIndex].title = bookItem.title;
}

现在,你的App.vue 应该看起来像下面的代码。

<template>
  <div id="app">
    <AddBookItem v-on:add-book-event="addBookItem" v-on:edit-book-event="editBookItemEvent" v-bind:editBook="editBook"/>
    <div>
      <Books v-bind:books="books" v-on:del-book-event="deleteBookItem" v-on:edit-book-event="editBookItem" />
    </div>
  </div>
</template>

<script>
import Books from "./components/Books";
import AddBookItem from "./components/AddBookItem";

export default {
  name: 'App',
  components: {
    Books,
    AddBookItem
  },
  data () {
    return {
      books: [],
      editBook: {
        title: '',
        id: ''
      }
    }
  },
  methods: {
    addBookItem(newBook){
      // console.log('newbook', newBook.title);
        this.books = [...this.books, newBook];
      // this.books.unshift(newBook)
    },
    deleteBookItem(id){
      this.books = this.books.filter(book => book.id !== id);
    },
    editBookItem(id){
      //find the index of the book's id
      let objIndex = this.books.findIndex(obj=> obj.id === id);
      this.editBook.title = this.books[objIndex].title;
      this.editBook.id = id;
    },
    editBookItemEvent(bookItem){
      //find the index of this id's object
      let objIndex = this.books.findIndex(obj => obj.id === bookItem.id)
      //update the item
      this.books[objIndex].title = bookItem.title;
    }
  },
  watch: {
    books: {
      handler() {
        localStorage.setItem('books',JSON.stringify(this.books))
      },
      deep: true
    }
  },
  mounted() {
    if (localStorage.getItem("books")){
      this.books = JSON.parse(localStorage.getItem("books"))
    }
  }
}
</script>

<style>
#app {
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

结论

我们刚刚完成了一个带有本地存储的CRUD Vue2应用程序的创建。你可以使用 materialize 组件或其他 UI 设计材料来改进你的应用程序的用户界面。