使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

brownbird 发布于9月前 阅读188次
0 条评论

这篇文章将介绍使用最新版本的 Vue.jsVuex 构建应用程序的一些基本知识。

Vue.js(读音 /vjuː/ ,类似于 view ) 是一套构建用户界面的 渐进式框架 。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与 单文件组件Vue 生态系统支持的库 结合使用时,Vue 也完全能够为复杂的单页应用程序提供驱动。

新版本的Vue中已经添加了许多优秀的特性,比如virtual-DOM。

译者注:如果你和我一样,对Virtual-DOM不熟悉,建议移步阅读下面有关于这方面的文档:

Vuex也更新了。在这篇文章中,我们将讨论如何基于Vuex的最新版本来创建一个Todo应用程序,允许用户对Todo应用程序进行添加、编辑、完成和删除等任务。

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。它的灵感来自于 FluxRedux 。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

执行完上面的命令之后,你的命令终端可以看到像下图这样的信息:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

接下来需要安装项目所需的依赖关系:

npm i

在大天朝使用 npm i 经常会碰到一些资源被墙,你可以采用 cnpm i 来做,我这里采用的是 tnpm i 来替代 npm i 安装一些依赖关系:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

安装好项目的依赖关系之外,在命令终端执行

npm run dev

你的浏览器会自动启用 http://localhost:8080/#/ 地址,访问Todo应用程序。你在浏览器中将看到的效果如下:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

安装过程很简单,但是由于这个设置与Vuex没有关系,我们将不得不将它导入我们的项目中:

npm install vuex@next

执行上面的命令你将安装Vuex的最新版本。

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

译者注:最终换成了 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>

此时浏览器看到的是一个空白页,什么内容都没有:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

但不用紧张,随着后面的内容完善,我们的页面效果会出来的。

Vuex Store

让我们从创建我们的Vuex store(仓库)开始。在 src 目录下创建一个 store 的文件夹,并在这个文件夹中新建一个 store.js 文件。在这个文件中将包含我们所需要的store。

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

每一个 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的新手,那么有关于 statemutationsactions 这些术语可能会让人感到困惑。然而,背后的概念非常简单。我们创建一个 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 下。目录结构看上去像这样:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

为了让我们的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>

这个时候你在浏览器看到的效果像这样:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

到这一步,虽然在输入框中输入内容,点击 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 里,看上去像下图这样:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

现在在浏览器看到的效果包含了 GetTodo 和 CurrentTodos 两个组件:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

在 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 里显示。

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

现在来看一下整体的操作过程:

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

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 从列表中删除的选项。

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

看上去不太美,但样子和功能都出来了,对于样式的美化不再花时间去纠结了,感兴趣的同学,自己可以去修复。

总结

我们还可以添加很多其他功能来增强这个应用程序的功能,但我希望这个简短的介绍能帮助你更好地理解如何使用Vue和Vuex 2.0来编写应用程序。

我们可以在 这里 找到我们文章中演示的示例代码。

本文根据 @paadams 的《》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: https://medium.com/@paadams/build-a-simple-todo-app-with-vue-js-1778ae175514

使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。

查看原文: 使用Vue.js和Vuex 2.0构建一个简单的Todo应用程序

  • whitebear
  • redfish
  • lazylion
  • bluerabbit
  • brownpanda
  • whitemeercat
  • silverduck
  • orangemeercat
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。