在本教程中,我们将使用 Vue 制作一个待办事项列表应用程序。这是我关于创建您的第一个 vue 应用程序的教程的后续内容。如果您需要入门帮助,请遵循该教程。由于最好的学习方式是尝试自己制作一些东西,因此本指南应该为您提供一个了解 Vue 工作原理的良好起点。
最终,我们的待办事项列表应用程序将如下所示:
制作一个 Vue 待办事项列表应用程序#
如果你已经按照我们的其他教程制作你的第一个 vue 应用程序,你应该有一个基本的 vue 文件结构。任何项目的第一步都是考虑你想要它做什么。对于我们的待办事项应用程序,我认为以下功能将是一个很好的起点:
- 存档页面– 这将包含我们已删除的所有待办事项列表项。
- 待办事项列表页面——这将是我们的主要待办事项列表页面,我们可以在其中添加和删除待办事项列表项。
- 持久列表– 如果我离开页面或刷新它,我希望列表存在。它不应该消失——所以我们需要存储。
- 一个关于页面– 一个简单的关于页面显示关于我们的一切以及我们的使命是什么。
在我们开始之前,让我们设置我们的文件结构。如果您已经学习了我们的其他教程,那么您应该对 Vue 应用程序的结构有一个基本的了解。对于这个项目,将您的文件设置为如下所示:
项目文件结构
public |- index.html <-- 这是我们的应用程序将存在的文件 src |- components <-- 存放组件的文件夹 |-- TodoList.vue <-- 今天我们只需要一个组件,我们的“TodoList”组件 |- router |-- index.js <-- 我们的路线信息(页面的另一个词) |- views |-- About.vue <-- 关于页面 |-- Archive.vue <-- 存档页面 |-- Home.vue <-- 主页 | App.vue <-- 我们的主要应用代码 | main.js <-- 我们的 main.js,它将包含一些核心 Javascript
路由器Routers
注意:如果您没有路由器文件夹,您可以通过vue add router
在您的 vue 文件夹中运行来添加它。
设置我们的路由器#
由于我们的 Vue 应用程序中有多个页面,因此我们需要在路由器index.js 文件中进行配置。在 router 文件夹中打开index.js,并将其更改为如下所示:
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
},
{
path: '/archive',
name: 'Archive',
component: () => import('../views/Archive.vue')
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
我们在之前的教程中已经介绍了这一点,但本质上这将创建 3 个不同的页面 –/archive
和/
/about – 并为它们启用历史 API。我们import()
用来导入我们之前在文件结构中创建的页面 – 那些存在Archive.vue
和.Home.vue
About.vue
使用 Vuex 在 Vue 中存储数据#
现在我们有了应用程序的“结构”,让我们讨论如何在应用程序中存储数据。Vue 有一个非常有用的插件叫做Vuex,它是一个状态管理工具。这意味着我们可以从 Vue 获取所有数据,将其存储在 Vuex 存储中,我们将能够轻松管理所有数据。要安装 vuex,只需在 vue 文件夹中运行以下命令:
npm i vuex
将 Vuex 添加到我们的应用程序
由于我们已经安装了 Vuex,我们可以开始在我们的应用程序中配置它。让我们专注于我们将如何操作和存储我们的数据。我们将 Vuex Store 直接添加到src文件夹中的 main.js文件中。将该文件更改为以下内容,以便我们可以启动存储:
import { createApp } from 'vue'
import { createStore } from 'vuex'
import App from './App.vue'
import router from './router'
const app = createApp(App);
// Create a store for our to do list items
const store = createStore({
state() {
},
getters: {
},
mutations: {
}
});
app.use(router).use(store).mount('#app')
Vuex 允许我们为我们的数据创建一个存储。我们会将整个待办事项列表存储在 Vuex 商店中。在 Vuex 中,我们将利用 3 个主要功能:
- state() – 这是我们存储数据的地方。我们所有的待办事项列表数据都会放在这里。
- getters – 这完全符合您的想法 – 它让我们可以从我们的商店获取数据。
- 突变——这些是我们将用来更新我们的状态数据的函数——所以这些函数将更新我们的待办事项列表——例如,将一个项目标记为完成。
Vuex 中的State和Getters#
我们将在我们的商店中看到的两个最简单的功能将是我们的state()和getters。让我们考虑一下如何将待办事项列表项存储在state()
. 我们的待办事项列表项有几个不同的属性——它们将有一个名称,并且可能有一个唯一的id。我们需要标记它们所在的页面(主页或存档),并且我们需要一个选项来设置它们是否完成。
因为getters
,当我们想要获取我们的待办事项列表时,我们实际上只需要一种方法——获取我们所有的待办事项列表项。下面,我配置了一个默认的 todo 列表项,以及一个简单地获取我们所有的 todo 列表的 getter:
const store = createStore({
state () {
return {
todos: [
// I've added one default todo below which will show when you first access the page.
// You can remove this if you want!
// id String] can be any unique ID
// name String] is the name of our item
// completed [Boolean] is set to true when done, false when not
// location<['home', 'archive']> is set to home or archive depending on which page we want to show it on
{ id: 'first-element', name: 'My First To Do Item', completed: false, location: 'home' }
]
}
},
getters: {
todos (state) {
// Returns every todo list (state stores our data,
// so state.todos refers to our entire todo list)
return state.todos;
}
}
mutations: {
}
}
在我们的代码中,我们稍后将能够调用getters.todo
来检索我们所有的待办事项列表项。现在我们有了存储数据的存储,以及获取数据的方法。接下来让我们看看我们将如何改变我们的数据。
用 Vuex 改变我们的数据#
现在让我们考虑一下我们的数据可能会如何变化。我们的数据有几种变化方式:
- 我们可以将待办事项列表项标记为已完成。
- 我们可以添加一个新的待办事项列表项。
- 我们可以删除一个待办事项列表项。
- 我们可以归档一个待办事项列表项。
因此,我们将制作4 个变异函数。让我们从第一个 – 开始updateTodo
。
mutations: {
updateTodo (state, todoItem) {
// the state argument holds all of our data
// the todoItem argument holds the data about a particular todo list item
// Let's get all the data from the todoItem
let id = todoItem.id;
let completed = todoItem.completed;
let name = todoItem.name;
// Let's find the item in our state we are trying to change, by checking for its ID
let findEl = state.todos.find((x) => x.id == id);
if(findEl !== null) {
// If we find it, then we'll update complete or name if those properties exist
if(completed !== undefined) {
findEl.completed = completed;
}
if(name !== undefined) {
findEl.name = name;
}
}
else {
// Otherwise lets console log that the item can't be found for some reason
console.log(`To Do List Item ${id} couldn't be found`);
}
}
}
在上面的代码中,state
将保存我们的待办事项列表数据,同时todoItems
将保存正在更改的项目。你可能想知道,我们怎么知道哪个项目是变化的?当我们创建我们的Home.vue
页面时,我们将能够将数据传递给我们的突变,让函数知道哪个项目正在改变。在设计这个时,我们可以考虑我们可能需要哪些数据来改变我们的状态,然后在我们构建前端时将这些数据传递给存储。
我们将需要的其他 3 个变异函数如下所示,但它们都遵循与updateTodo
. mutation:{}
在您的列表中添加这些。
addTodo (state, todoItem) {
// Check we have all the right properties to make an element
if(todoItem.id !== undefined && typeof todoItem.name == 'string' && typeof todoItem.completed == 'boolean') {
// Push our new element to our store!
state.todos.push({
id: todoItem.id,
name: todoItem.name,
completed: todoItem.completed,
location: 'home'
})
}
},
deleteTodo (state, todoItem) {
// Check for the id of the element we want to delete
let id = todoItem.id;
let removedEl = state.todos.findIndex((x) => x.id == id);
if(removedEl !== null) {
// If it exists, delete it!
state.todos.splice(removedEl, 1);
}
},
moveTodoItem (state, todoItem) {
// Check for the id and location information
let id = todoItem.id;
let location = todoItem.location;
let findEl = state.todos.find((x) => x.id == id);
// If the item exists, update its location
if(findEl !== null) {
findEl.location = location;
}
else {
// Otherwise console log a message
console.log(`To Do List Item ${id} couldn't be found`);
}
}
如何将 Vuex 数据保存到本地存储#
现在我们已经设置了整个数据存储。我们可以根据需要操纵和更改我们的商店。最后一个难题是我们需要一种方法来保存更改。Vuex 不坚持。如果刷新页面,数据就会消失,这不是我们想要的。因此,我们需要再添加一个函数,它会在发生突变时触发。这种方法称为subscribe
. 将它添加到你的底部main.js
,就在之前app.use(router).use(store).mount('#app')
:
store.subscribe((mutation, state) => {
// The code inside the curly brackets fires any time a mutation occurs.
// When a mutation occurs, we'll stringify our entire state object - which
// contains our todo list. We'll put it in the users localStorage, so that
// their data will persist even if they refresh the page.
localStorage.setItem('store', JSON.stringify(state));
})
现在,将某些内容保存在 localStorage 中是一回事 – 将其展示给用户是另一回事。因此,我们需要在页面加载时更新整个 Vuex 状态。首先要做的是进行一个新的突变,我们称之为loadStore
. 所有这一切都会打开localStorage
,检索我们的数据,并将state
数据存储的值设置为找到的值。
mutations: {
loadStore() {
if(localStorage.getItem('store')) {
try {
this.replaceState(JSON.parse(localStorage.getItem('store')));
}
catch(e) {
console.log('Could not initialize store', e);
}
}
}
// ... other mutations
}
我们希望在应用程序加载时运行它,这样我们就可以将本地存储同步到我们的 Vuex 存储 – 所以我们需要将它添加到我们的App.vue
文件中。更改您的脚本以导入我们的 store ( useStore()
),然后我们可以loadStore
使用commit()
. 这是连接所有内容的最后一步。
<script>
import { useStore } from 'vuex'
export default {
beforeCreate() {
// Get our store
const store = useStore()
// use store.commit to run any mutation. Below we are running the loadStore mutation
store.commit('loadStore');
}
}
</script>
这就是我们数据所需的一切。让我们回顾一下我们在这里所做的事情:
- 我们创建了一个新的 Vuex 商店。这样我们就可以存储我们的待办事项列表数据。
- 我们创建了一个 getter 方法来从我们的 Vuex 存储中加载任何待办事项列表数据。
- 我们创建了许多突变来操纵我们的 Vuex 存储数据。
- 我们创建了一个函数来将我们的 Vuex 存储放入本地存储。然后我们将它也放入我们的 App.vue 文件中,以确保我们的本地存储和 Vuex 存储保持同步。
实现我们的待办事项列表前端#
困难的部分已经结束,我们终于可以开始创建我们的前端了。我们将为我们的待办事项列表应用程序制作一个组件 – TodoList.vue
,我们将把它放在src/components
文件夹中。我们的组件将有一个属性 – location
,它可以让我们区分我们是在存档页面上还是在主页上。
让我们从组件的基本 Javascript 开始。首先,让我们导入我们的 Vuex 存储,并将其全部放在我们组件的data()
函数中。让我们也 import uuid
,让我们为待办事项列表项提供 ID。您可以通过运行以下代码来安装 uuid:
npm i uuid
我还将包含一个名为 的数据元素newTodoItem
,我们将在添加新的待办事项列表项时使用它。现在,我们的 Javascript 将如下所示:
<script>
import { useStore } from 'vuex'
import { v4 as uuidv4 } from 'uuid'
export default {
name: "TodoList",
data() {
return {
// Used for adding new todo list items.
newTodoItem: ''
}
},
props: {
location: String
},
setup() {
// Open our Vuex store
const store = useStore()
// And use our getter to get the data.
// When we use return {} here, it will
// pass our todos list data straight to
// our data() function above.
return {
todos: store.getters.todos
}
}
}
</script>
现在我们存储的所有待办事项列表数据都将在我们的data()
函数中。您可能还记得我们的待办事项列表项看起来有点像这样:
[{ id: 'first-element', name: 'My First To Do Item', completed: false, location: 'home' }]
鉴于我们知道待办事项列表项的结构,我们可以开始在我们的应用程序中显示它们。将以下模板添加到您TodoList.vue
的脚本标记上方:
<template>
<div id="todo-list">
<div class="list-item" v-for="n in todos" :key="n.id">
<div class="list-item-holder" v-if="n.location == location" :data-status="n.completed">
<input type="checkbox" :data-id="n.id" :id="n.id" @click="updateTodo" :checked="n.completed"> <label :data-id="n.id" :for="n.id"></label>
<div class="delete-item" @click="deleteItem" :data-id="n.id">Delete</div>
<div class="archive-item" v-if="n.location !== 'archive'" @click="archiveItem" :data-id="n.id">Archive</div>
</div>
</div>
<div id="new-todo-list-item">
<input type="text" id="new-todo-list-item-input" @keyup="updateItemText">
<input type="submit" id="new-todo-list-item-submit" @click="newItem" value="Add To Do List Item">
</div>
</div>
</template>
这只是普通的 HTML。在底部,我们有一些输入,我们将使用它们来添加新的待办事项列表项。在顶部,我们正在使用v-for
Vue 附带的功能。使用v-for
,我们可以遍历我们的待办事项数组,并以反应方式显示它们。我们将使用我们的待办事项列表 ID 作为每个的键,这由以下行显示:
<div class="list-item-holder" v-if="n.location == location" :data-status="n.completed">
还记得我们说过我们的组件会有一个名为 location 的属性吗?好吧,我们只想显示待办事项列表项位置与属性匹配的待办事项列表项。如果我们在主页上,我们只想显示“主页”待办事项。所以下一行就是这样做的,使用v-if
. 如果待办事项列表位置n.location
与属性相同location
,则会显示。如果不是,它不会。
<div class="list-item-holder" v-if="n.location == location" :data-status="n.completed">
接下来的几行简单地从待办事项列表项中提取名称和 ID 信息以在我们的应用程序中显示它。我们还有两个按钮,一个用于删除,一个用于归档我们的待办事项列表项。您会注意到 Vue 中的事件显示为@click
、 或@keyup
。每当用户在该元素上单击或按键时,它们就会触发。其中的文本是我们将调用的函数,但我们还没有定义它们。因此,让我们开始定义我们的函数,以便我们可以将数据发送回我们的 Vuex 存储。
待办事项列表前端方法#
正如我们所说,我们有许多“事件”,只要用户单击或将待办事项列表项标记为完成,就会触发这些“事件”。例如,当他们单击复选框时,我们运行updateTodo
. 不过,我们需要定义这些函数,所以现在就开始吧。我们所有的函数(也称为方法)都将存储在我们的export default {}
Javascript 中,在methods: {}
.
由于我们已经初始化了我们的数据存储,我们可以通过this.$store
. 还记得我们在 store 中定义了一堆突变事件吗?我们现在将针对这些并触发信息以实时更新我们的商店。让我们看一个例子,updateTodo
。在这里,我们想要将待办事项的状态更改为已完成或未完成。所以我们将首先获得新的状态,并将其发送到我们的 Vuex 商店。
要在 Vuex 商店上触发突变,我们使用store.commit
. 第一个参数是我们想要触发的突变,第二个是我们想要发送的数据。因此,我们的方法如下所示updateTodo
:
methods: {
updateTodo: function(e) {
// Get the new status of our todo list item
let newStatus = e.currentTarget.parentElement.getAttribute('data-status') == "true" ? false : true;
// Send this to our store, and fire the mutation on our
// Vuex store called "updateTodo". Take the ID from the
// todo list, and send it along with the current status
this.$store.commit('updateTodo', {
id: e.currentTarget.getAttribute('data-id'),
completed: newStatus
})
}
}
我们的其余方法遵循相同的模式。获取待办事项列表的 ID – 并将其与新数据一起发送到我们的商店。我们存储中的突变事件然后更新 Vuex 存储,并且由于我们实现了该subscribe
方法,它会在我们的本地存储中自动更新。以下是我们所有的方法,包括添加新项目的方法:
methods: {
// As a user types in the input in our template
// We will update this.newTodoItem. This will then
// have the full name of the todo item for us to use
updateItemText: function(e) {
this.newTodoItem = e.currentTarget.value;
if(e.keyCode === 13) {
this.newItem();
}
return false;
},
updateTodo: function(e) {
// Get the new status of our todo list item
let newStatus = e.currentTarget.parentElement.getAttribute('data-status') == "true" ? false : true;
// Send this to our store, and fire the mutation on our
// Vuex store called "updateTodo". Take the ID from the
// todo list, and send it along with the current status
this.$store.commit('updateTodo', {
id: e.currentTarget.getAttribute('data-id'),
completed: newStatus
})
},
deleteItem: function(e) {
// This will fire our "deleteTodo" mutation, and delete
// this todo item according to their ID
this.$store.commit('deleteTodo', {
id: e.currentTarget.getAttribute('data-id')
})
},
newItem: function() {
// If this.newTodoItem has been typed into
// We will create a new todo item using our
// "addTodo" mutation
if(this.newTodoItem !== '') {
this.$store.commit('addTodo', {
id: uuidv4(),
name: this.newTodoItem,
completed: false
})
}
},
archiveItem: function(e) {
// Finally, we can change or archive an item
// using our "moveTodoItem" mutation
this.$store.commit('moveTodoItem', {
id: e.currentTarget.getAttribute('data-id'),
location: 'archive'
})
}
}
最后,我添加了一些基本样式来划掉标记为完成的项目。</script>
在你的最终标签之后添加这个:
<style scoped>
.list-item-holder {
display: flex;
}
[data-status="true"] label {
text-decoration: line-through;
}
</style>
把它们拉到一起#
我们现在有一个可靠的 Vuex 存储和一个TodoList.vue
组件。最后一步是将它集成到我们的 Home.vue 页面中——这很容易。只需导入组件,然后将其添加到您的Home.vue
模板中:
<template>
<h1>To do List:</h1>
<TodoList location="home" />
</template>
<script>
import TodoList from '../components/TodoList.vue';
export default {
name: "HomePage",
components: {
TodoList
}
}
</script>
在我们的存档页面上,我们将拥有相同的内容,只是我们的TodoList
位置将设置为“存档”。
<template>
<TodoList location="archive" />
</template>
设计我们的待办事项应用程序#
现在我们完成了,我们可以通过运行以下命令来测试我们的待办事项列表,这将让我们在http://localhost:8080上查看它:
npm run serve
我们应该有一个看起来像这样的待办事项列表:
我将把页面的整体设计留给您,但我已经对其进行了一些更新,使其看起来更现代一些。以下所有样式都将在最终代码仓库中提供。经过一番努力,我找到了这个设计:
演示#
我已经在 Github Pages 上设置了最终应用程序外观的演示。你可以在这里找到演示。如果您想了解我们将构建的内容,请查看它。
结论#
我希望你喜欢这份关于制作待办事项列表应用程序的指南。当您开始更多地了解 Vue 时,尝试自己的应用程序想法很重要,以便更多地了解它的实际工作原理。通过这个例子,我们已经涵盖了很多新的想法:
- 在 Vue 中配置你的路由器。
- 使用 Vuex 的数据存储 – 以及它们是如何工作的。
- 与数据存储交互,并使 Vuex 数据存储持久保存在本地存储中。
- 使用 .创建与 Vuex 数据存储交互的组件
store.commit
。 - 使用自定义道具将这些组件实现到主页中
与往常一样,您可以在下面找到一些有用的链接: