这篇文章将介绍使用最新版本的Vue.js和Vuex构建应用程序的一些基本知识。
Vue.js(读音
/vjuː/
,类似于 view) 是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也完全能够为复杂的单页应用程序提供驱动。
新版本的Vue中已经添加了许多优秀的特性,比如virtual-DOM。
译者注:如果你和我一样,对Virtual-DOM不熟悉,建议移步阅读下面有关于这方面的文档:
Vuex也更新了。在这篇文章中,我们将讨论如何基于Vuex的最新版本来创建一个Todo应用程序,允许用户对Todo应用程序进行添加、编辑、完成和删除等任务。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。它的灵感来自于Flux和Redux。Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
开始
Vue已经简化了使用Vue-cli的过程。Vue社区的许多人也通过它创建项目,并为此做出很多贡献。在这篇文章中,我们将使用一个名为webpack-simeple-2.0
的模板来创建项目。
译者注:我在跟着教程做Todo应用程序的时候,采用的也是Vue-CLI来创建项目,但我采用的是
webpack
模板。操作非常简单。在这个模板中,我安装了vue-router
,但放弃了ESLint和单元测试等功能。
vue init webpack simple-todo
执行完上面的命令之后,你的命令终端可以看到像下图这样的信息:
Image may be NSFW.
Clik here to view.
接下来需要安装项目所需的依赖关系:
npm i
在大天朝使用npm i
经常会碰到一些资源被墙,你可以采用cnpm i
来做,我这里采用的是tnpm i
来替代npm i
安装一些依赖关系:
Image may be NSFW.
Clik here to view.
安装好项目的依赖关系之外,在命令终端执行
npm run dev
你的浏览器会自动启用http://localhost:8080/#/
地址,访问Todo应用程序。你在浏览器中将看到的效果如下:
Image may be NSFW.
Clik here to view.
安装过程很简单,但是由于这个设置与Vuex没有关系,我们将不得不将它导入我们的项目中:
npm install vuex@next
执行上面的命令你将安装Vuex的最新版本。
Image may be NSFW.
Clik here to view.
译者注:最终换成了
tnpm i vuex --save
安装的vuex。但这样安装的不是最新版本的vuex
在我们的项目中,我们已经有了一个组件和Vue
实例。让我们把里面的内容删除掉(删除src/components
中的Hello.vue
组件,清理src/App.vue
文件中的代码):
<!-- 修改App.vue文件 -->
<div id=”app”>
</div>
修改src/router/index.js
文件:
<!-- 修改router/index.js文件 -->
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [{
path: '/'
}]
})
同样把App.vue
中export
表达式的内容了清理干净,清理后的代码看上去像这样:
<!-- 修改App.vue文件 -->
<template>
<div id="app">
</div>
</template>
<script>
export default {
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
}
</style>
此时浏览器看到的是一个空白页,什么内容都没有:
Image may be NSFW.
Clik here to view.
但不用紧张,随着后面的内容完善,我们的页面效果会出来的。
Vuex Store
让我们从创建我们的Vuex store(仓库)开始。在src
目录下创建一个store
的文件夹,并在这个文件夹中新建一个store.js
文件。在这个文件中将包含我们所需要的store。
Image may be NSFW.
Clik here to view.
每一个 Vuex 应用的核心就是 store(仓库)。"store"基本上就是一个容器,它包含着你的应用中大部分的状态(state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交(commit) mutations。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
在早期的Vuex版本中,每一个操作需要在store
中创建单独的文件。Vuex的最新特点之一是能够在一个store
文件中定义actions
和getter
。这样做,使用一个模块加载更加便捷,更易组合。这是store
的基本结构:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
}
})
如果您和我一样是Vuex的新手,那么有关于state、mutations和actions这些术语可能会让人感到困惑。然而,背后的概念非常简单。我们创建一个state
对象,当应用程序启动时,它保持初始状态。接下来,我们创建一个对象来存储我们的mutations
。在Vuex中mutations
基本上是事件。最后,我们创建一个对象来存储我们的actions
。actions
是用来分派mutations
的函数。我们完成的store.js
看上去像这样:
// 修改src/store/store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
todos: [],
newTodo: ''
},
mutations: {
GET_TODO(state, todo) {
state.newTodo = todo
},
ADD_TODO(state) {
state.todos.push({
body: state.newTodo,
completed: false
})
},
EDIT_TODO(state, todo) {
var todos = state.todos
todos.splice(todos.indexOf(todo), 1)
state.todos = todos
state.newTodo = todo.body
},
REMOVE_TODO(state, todo) {
todo.completed = !todo.completed
},
CLEAR_TODO(state) {
state.newTodo = ''
}
},
actions: {
getTodo({ commit }, todo) {
commit('GET_TODO', todo)
},
addTodo({ commit }) {
commit('ADD_TODO')
},
editTodo({ commit }, todo) {
commit('EDIT_TODO', todo)
},
removeTodo({ commit }, todo) {
commit('REMOVE_TODO', todo)
},
completeTodo({ commit }, todo) {
commit('COMPLETE_TODO', todo)
},
clearTodo({ commit }) {
commit('CLEAR_TODO')
}
}
})
State
当我们的应用程序启动时,初始状态是todos
的空列表([]
),它将存储我们新添加的todos
和一个空的字符串,该字符串将保留添加到todos
。
Mutations
每个mutation
都有一个字符串的事件类型(type
)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受state
作为第一个参数。习惯上把mutation
的每一个回调函数的命名都用大写的字母,主要是方便它们与普通的函数区分开来。GET_STATE
接受用户输入的值并将其赋值给state.newTodo
。当触发ADD_TODO
时,添加了一个新的todo
到我们的todos
列表 —— 将body
设置为state.newTodo
和completed
开始被设置为false
。这些mutations
很简单,所以我们不会花太多时间来详细解释它们。
Actions
Vuex的actions
期望一个存储作为必需的参数,然后是一个可选的参数。如果使用参数遭到破坏,可以使用以下语法传递额外的参数:
addTodo({commit}){
commit(‘ADD_TODO’)
},
没有参数的结构:
addTodo = function(store){
var commit = store.commit
commit('ADD_TODO')
}
分发怎么开始?
如果你使用过Vuex以前的版本,你可能会被新的版本语法抛弃。在Vuex2中,分发用于触发actions
,而commit
用于触发mutations
。一个actions
的分发对应一个mutations
的commit
。由于我们的操作是在Vuex存储中定义的,所以可以在多个模块中使用单个调用来分发actions
:
<button @click=”$store.dispatch(‘addTodo’)”>Add Todo</button>
当addTodo
被分发时,它将触发名叫ADD_TODO
的mutation
,并将一个新的todo
推送(push()
)到todos
列表。其余的动作,当被触发时将以类似的方式表现,因此没有必要对每一个动作进行检查。
有关于Vuex2更多的资料可以阅读下面文章:
回到我们项的store中来。剩下的就是在main.js
的Vue
实例中注册store
和导入store
:
// 修改src/main.js
import Vue from 'vue'
import App from './App'
import store from './store/store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
创建组件
我们要做的Todo应用程序将由四个不同的组件组成:App
、GetTodo
、CurrentTodos
和CompletedTodos
。
组件是Vue.js最强大的特性之一。它能帮助你扩展基本的HTML元素,将其封装起来可重用。更高级的是,组件也是Vue.js自定义的元素,编译器可以附加指定行为。
对于一个简单的Todo应用程序,从单个组件构建整个应用程序是可以的。然而,我们这里想要演示的是Vuex是如何用来在各种不相关的组件之间进行通信的。
GetTodo
在创建组件之前,先在src
目录下创建一个components
子目录用来存储Todo应用程序所需要的组件(我这里创建Vue项目的时候,src
下就有components
文件夹)。而App.vue
还是依旧放在src
下。目录结构看上去像这样:
Image may be NSFW.
Clik here to view.
为了让我们的Todo应用程序好看一点,可以在index.html
引入BootStrap的样式文件。
截止到整理这篇文章的时候,BootStrap已经发布了Beta4版本,感兴趣的话,也可以将最新版本的样式引入到
index.html
中替换以前的版本。
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
接下来给GetTodo
组件添加代码:
<template>
<div id="get-todo" class="container">
<input class="form-control" :value="newTodo" @change="getTodo" placeholder="I need to ..." />
<button class="btn btn-primary" @click="addTodo">Todo</button>
</div>
</template>
<script>
export default {
methods: {
getTodo(e) {
this.$store.dispatch('getTodo', e.target.value)
},
addTodo() {
this.$store.dispatch('addTodo')
this.$store.dispatch('clearTodo')
}
},
computed: {
newTodo() {
return this.$store.getters.newTodo
}
}
}
</script>
如果你熟悉Vuex的话,你应该注意到了Vuex选项(option)的变化。在V2.0中,Vuex选项(option)已经被弃用,转而支持computed
属性和方法。作者认为,使用computed
的属性和方法可以减少在任何地方需要导入store
的需求。另一个你可能注意到的变是没有getters
。在V2.0中,引入了getters
:
store.getters
Vuex 允许我们在
store
中定义getters
(可以认为是store
的计算属性)。就像计算属性一样,getters
的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
在我们的store.js
文件中,在actions
对象下面添加一个getters
对象。
// 修改src/store/store.js
// 在actions对象下添加下面代码
getters: {
newTodo: state => state.newTodo,
todos: state => state.todos.filter((todo) => {
return !todo.completed
})
}
newTodo
返回用户输入的新的todo
值,这个值绑定到input
元素上。todos
返回一个包含所有已完成值的todos
的数组。由于我们还希望在CompletedTodos
组件内显示所有已完成的todos
,我们不仿现在添加:
// 修改src/store/store.js
getters: {
newTodo: state => state.newTodo,
todos: state => state.todos.filter((todo) => {
return !todo.completed
}),
completedTodos: state => state.todos.filter((todo) => {
return todo.completed
})
}
接下来,我们应该在App.vue
组件内部注册我们的组件。我们使用注册的组件作为自定义元素,看上去像这样:
<template>
<div id="app">
<GetTodo></GetTodo>
</div>
</template>
<script>
import GetTodo from './components/GetTodo.vue'
export default {
components: {
GetTodo
}
}
</script>
这个时候你在浏览器看到的效果像这样:
Image may be NSFW.
Clik here to view.
到这一步,虽然在输入框中输入内容,点击
Todo
按钮,在页面上看不到任何的变化,事实上,程序已经有相应的变化,如上图中所示。
CurrentTodos
CurrentTodos
组件主要用来负责显示所有完成值为false
的todos
。除了显示每个todo
的主体之外,该组件还提供用于编辑、完成和删除todo
列表中的todo
操作(有相应的操作按钮)。该组件还显示了正在进行的todos
的数量。CurrentTodos
的代码如下:
<template>
<div id="current-todos" class="container">
<h3 v-if="todos.length > 0">Current({{ todos.length }})</h3>
<ul class="list-group">
<li class="list-group-item" v-for="todo in todos">
{{ todo.body }}
<div class="btn-group">
<button type="button" @click="edit(todo)" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-edit"></span> Edit
</button>
<button type="button" @click="complete(todo)" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-ok-circle"></span> Complete
</button>
<button type="button" @click="remove(todo)" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-remove-circle"></span> Remove
</button>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
methods: {
edit(todo) {
this.$store.dispatch('editTodo', todo)
},
complete(todo) {
this.$store.dispatch('completeTodo', todo)
},
remove(todo) {
this.$store.dispatch('removeTodo', todo)
}
},
computed: {
todos() {
return this.$store.getters.todos
}
}
}
</script>
<style>
.btn-group{
float: right;
}
</style>
像前面的GetTodo
组件一样,把CurrentTodos
组件添加到App.vue
组件中:
<template>
<div id="app">
<CurrentTodos></CurrentTodos>
<GetTodo></GetTodo>
</div>
</template>
<script>
import GetTodo from './components/GetTodo.vue'
import CurrentTodos from './components/CurrentTodos.vue'
export default {
components: {
GetTodo,
CurrentTodos
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
}
</style>
这个时候你在input
输入框输入你需要的todo
,然后点击todo
按钮,就可以把你想要的todo
添加到todos
里,看上去像下图这样:
Image may be NSFW.
Clik here to view.
现在在浏览器看到的效果包含了GetTodo
和CurrentTodos
两个组件:
Image may be NSFW.
Clik here to view.
在CurrentTodos
组件中,我们有几个按钮(编辑、完成和删除)可以对todo
进行操作。当用户点击Edit按钮,对应的todo
将被删除,而且对应的todo
的值也将被删除,todo.body
的值同时出现在input
输入框中。然后用户可以编辑todo.body
,并将编辑过的文本添加到当前todo
的列表中。用户点击Remove按钮可以删除对应的todo
。点击Complete按钮,可以将对应的todo
的completed
的值改为true
。因为我们只是映射给todos
的completed
的值为false
,只有completed
的值为false
时对应的todo
才会显示在todos
里,反之为true
时,todo
不会在todos
里显示。
Image may be NSFW.
Clik here to view.
现在来看一下整体的操作过程:
Image may be NSFW.
Clik here to view.
CompletedTodos
看最后一个组件CompletedTodos
,代码如下:
<template>
<div id="completed-todos" class="container">
<h3 v-if="completed.length > 0">Completed({{ completed.length }})</h3>
<ul class="list-group">
<li class="list-group-item" v-for="todo in completed">
{{todo.body}}
<button type="button" @click="remove(todo)" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-remove-circle"></span> Remove
</button>
</li>
</ul>
</div>
</template>
<script>
export default{
methods: {
remove(todo){
this.$store.dispatch('removeTodo', todo)
}
},
computed: {
completed(){
return this.$store.getters.completedTodos
}
}
}
</script>
跟前面的组件一样的方法,把该组件添加到App.vue
组件中:
<template>
<div id="app">
<CompletedTodos></CompletedTodos>
<GetTodo></GetTodo>
<CurrentTodos></CurrentTodos>
</div>
</template>
<script>
import GetTodo from './components/GetTodo.vue'
import CurrentTodos from './components/CurrentTodos.vue'
import CompletedTodos from './components/CompletedTodos.vue'
export default {
components: {
GetTodo,
CurrentTodos,
CompletedTodos
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
}
</style>
CompletedTodos
组件主要用来显示用户已完成的当前todos
。在这个示例中只显示完成的todo
数量、todo.body
以及将已完成的todos
从列表中删除的选项。
Image may be NSFW.
Clik here to view.
看上去不太美,但样子和功能都出来了,对于样式的美化不再花时间去纠结了,感兴趣的同学,自己可以去修复。
总结
我们还可以添加很多其他功能来增强这个应用程序的功能,但我希望这个简短的介绍能帮助你更好地理解如何使用Vue和Vuex 2.0来编写应用程序。
我们可以在这里找到我们文章中演示的示例代码。
本文根据@paadams的《》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://medium.com/@paadams/build-a-simple-todo-app-with-vue-js-1778ae175514。
如需转载,烦请注明出处:https://www.w3cplus.com/vue/build-a-simple-todo-app-with-vue-js.html