使用Vue.js 2创建To-Do App

Vue是一个简而小的渐进式JavaScript框架,可用于增量地构建强大的Web应用程序。

Vue是其他JavaScript框架(如AngularJS)的轻量级替代品。通过对HTML、CSS和JS的理解,你应该准备好与Vue一起运行。

To Do App

在本文中,我们将使用Vue构建一个To-Do的应用程序,同时突出显示它能提供的优秀特能和功能。

那我们开始吧。

先决条件

一开始我们需要使用Vue CLI。CLI提供了一种快速搭建单个页面应用程序的方法,而且很快你就能得到一个可运行应用程序:热加载(Hot-reload)、在线保存(Lint-on-save)和 发布上线。

Vue CLI提供了一个不需要配置的开发工具来启动你的Vue应用程序和组件。

在你的应用程序如何扩展的问题上,你需要考虑很多问题。Vue CLI提供了一系列模板,这些模板提供了一个自给自足、现成的可用包。当前可用的模板是:

  • webpack:一个功能齐全的Webpack + Vue-loader安装程序,带有热加载、代码检测、测试和CSS提取等功能
  • webpack-simple:一个简单的Webpack + Vue-loader安装程序
  • browserify: 一个功能齐全的Browserify + vueify安装程序,带有热加载、代码检测和单元测试功能
  • browserify-simple:一个简单的Browserify + vueify安装程序
  • simple:提供一个HTML文件,文件中包含最简单的Vue设置

简单地说,Vue CLI是让应用程序启动和运行的最快方法。

# install vue-cli
$ npm install --global vue-cli

在这篇文章中,我们将重点关注单个文件组件的使用,而不是实例。我们还将介绍如何使用它们之间的父元素和子组件和数据交换。当你使用单个文件组件时,Vue的学习曲线尤为温和。此外,它们允许你将组件放置在任何一个位置。当你开始在大型应用程序上工作时,编写可重用组件的能力将显得尤为重要。

创建一个Vue2的应用程序

接下来,我们将使用CLI设置我们的Vue应用程序。

# 使用 "webpack" 模板创建一个新的vue项目
$ vue init webpack todo-app

你将被提示输入项目名称、项目描述、项目作者和Vue构建。我们不会为我们的App安装vue-router。你将需要安装代码检测和测试。一旦我们对App进行了初始化,我们就需要安装所需的依赖项。

译者注:在我本地练习的项目中安装了vue-router,但并没有安装ESLint、Karma + Mocha的单元测试和e2e的测试。

To Do App

安装项目需要的依赖关系:

$ cd my-project
$ npm install

很多时候使用npm i会有问题,建议改用cnpm,不过我这里采用的是tnpm来安装依赖关系。

To Do App

接着运行程序

$ npm run dev

运行上面的命令之后,浏览器会自动访问http://localhost:8080,该页面显示如下:

To Do App

为了给我们的应用程序添加样式风格,我们将使用语义化。添加较少的JavaScript和CSS,并且放到index.html文件中。

组件结构

每个Vue应用程序都需要一个一组的组件,作为整个应用程序的框架。在我们的应用程序中,我们将有一个主组件并嵌套在其中,这将是一个TodoList组件。在这里面将有todo子组件。

主应用程序组件

让我们开始构建我们的应用程序。首先,我们将从主要的顶层组件开始。Vue CLI已经生成了src/App.vue中可以找到的主要组件。我们将建立其他必要的组成部分。

创建一个组件

Vue CLI在src/components/Hello.vue中创建了一个Hello组件。我们将在这个目录下创建我们自己需要的组件TodoList.vue

TodoList.vue文件中添加下面的代码:

<template>
    <div>
        <ul>
            <li>Todo A</li>
            <li>Todo B</li>
            <li>Todo C</li>
        </ul>
    </div>
</template>

<script type="text/javascript">
    export default {

    }
</script>

<style>

</style>

组件文件有三部分组成:模板、组件类和样式部分。模板区域是组件的可视部分。模板的行为、事件和数据存储都是由类来处理。样式部分用于进一步改进模板的外观。

引入组件

要使用我们刚刚创建的组件,我们需要将其导入到主组件中。在src/App.vue中的<script>区域中引入上面创建的组件:

// 添加这一行
import TodoList from './components/TodoList'  

// 删除这一行
import Hello from './components/Hello'  

我们还需要引用组件属性中的TodoList组件,并删除前面提到的Hello组件。在更改之后,我们的脚本代码应该是这样的:

<script>
    import TodoList from './components/TodoList';

    export default {
        components: {
            // 把TodoList组件添加到组件列表中
            TodoList,
        },
    };
</script>

要渲染组件,我们就要像HTML元素一样调用它。组件名称以下划线分隔,而不是采用陀峰的写法:

<template>
    <div>
        // Render the TodoList component
        // TodoList becomes
        <todo-list></todo-list>
    </div>
</template>

App.vue<template>中添加上面的代码。

当我们保存修改后的文件时,在浏览器看到的效果是这样的:

To Do App

添加组件数据

我们将需要给todos列表的主组件中提供数据。我们的todos将有三个属性:标题、项目和完成(表明todo是否完成)。组件使用数据函数对其各自的模板使用数据。该函数返回一个对象,该对象具有用于该模板的属性。让我们向组件添加一些数据。

export default {
    name: 'app',
    components: {
        TodoList,
    },
    // data function avails data to the template
    data() {
        return {
        todos: [{
            title: 'Todo A',
            project: 'Project A',
            done: false,
        }, {
            title: 'Todo B',
            project: 'Project B',
            done: true,
        }, {
            title: 'Todo C',
            project: 'Project C',
            done: false,
        }, {
            title: 'Todo D',
            project: 'Project D',
            done: false,
        }],
        };
    },
};

App.vue文件的App.uve文件中添加上述代码。

我们需要将数据从主要组件中传递到TodoList组件。为此,我们将使用v-bind指令。该指令采用指令名后由冒号指示的参数。我们的参数是todos,它告诉v-bind指令将元素的todos属必绑定到表达式todos的值。

<template>
    <div id="app">
        <div>
            <todo-list v-bind:todos="todos"></todo-list>  
        </div>
    </div>
</template>

todos现在可以在TodoList组件中使用todos。我们将不得不修改TodoList组件来访问这些数据。TodoList组件必须声明它在使用时将接受的属性。我们通过向组件类添加一个属性来实现这一点。

循环和渲染数据

在我们的TodoList模板中,让我们在Todos列表上添加循环,并显示完成和未完成任务的数据。为了渲染列表项目,我们使用v-for指令。这样做的语法被表示为v-for="item in items",其中items是我们的数据的数组,而item是被迭代的数组元素。

// 在TodoList.vue中添加下面的代码
<template>
    <div>
        // JavaScript expressions in Vue are enclosed in double curly brackets.
        <p>Completed Tasks: {{todos.filter(todo => {return todo.done === true}).length}}</p>
        <p>Pending Tasks: {{todos.filter(todo => {return todo.done === false}).length}}</p>
        <div class='ui centered card' v-for="todo in todos">
        <div class='content'>
            <div class='header'>
            {{ todo.title }}
            </div>
            <div class='meta'>
            {{ todo.project }}
            </div>
            <div class='extra content'>
            <span class='right floated edit icon'>
                <i class='edit icon'></i>
            </span>
            </div>
        </div>
        <div class='ui bottom attached green basic button' v-show="todo.done">
            Completed
        </div>
        <div class='ui bottom attached red basic button' v-show="!todo.done">
            Complete
        </div>
    </div>
</template>

<script type = "text/javascript" >

    export default {
        props: ['todos'],
    };
</script>

编辑Todo

让我们将todo模板提取到它自己的组件中,让代码变得更清晰些。在src/components中创建一个新的组件文件Todo.vue。并把组件代码放到这个文件中,他看起来应该像这样:

<template>
    <div class="ui centered card">
        <div class="content">
            <div class="header">{{ todo.title }}</div>
            <div class="meta">{{ todo.project }}</div>
            <div class="extra content">
                <span class="right floated edit icon">
                    <i class="edit icon"></i>
                </span>
            </div>
        </div>

        <div class='ui bottom attached green basic button' v-show="todo.done">Completed</div>
        <div class='ui bottom attached red basic button' v-show="!todo.done">Complete</div>
    </div>
</template>

<script type="text/javascript">
    export default {
        props: ['todo']
    }
</script>

TodoList组件中重构代码以渲染Todo组件。我们还需要改变Todo组件的传递方式。我们可以在我们创建的任何组件上使用v-for属性,就像在任何其他元素中一样。它的语法看起来像这样:<my-component v-for="item in items" :key="item.id"></my-component>。请注意,在2.0及以上版本中,使用v-for创建组件需要的一个key。需要的注意的一个重要事项是,这不会自动将数据传递给组件,因为组件有自己的隔离范围。为了传递数据,我们必须使用props

<my-component v-for="(item, index) in items" v-bind:item="item"  v-bind:index="index">
</my-component>

我们重构我们的TodoList组件模板:

<template>
    <div>
        <p>Completed Tasks: {{todos.filter(todo => {return todo.done == true}).length}}</p>
        <p>Pending Tasks: {{todos.filter(todo => {return todo.done === false}).length}}</p>
        <!-- 现在,我们将数据传递给todo组件,以呈现todo列表 -->
        <todo v-for="todo in todos" v-bind:todo="todo"></todo>
    </div>
</template>

<script type="text/javascript">
    import Todo from './Todo'

    export default {
        props: ['todos'],
        components: {
            Todo
        }
    }
</script>

我们将属性添加到Todo组件类,称之为isEditing。这将被用来判断Todo是否处理编辑模式。我们将在模板的span上编辑,用来处理一个事件。这将在单击时触发showForm方法。这将isEditing属性设置为true。在我们看这个之前,我们将添加一个表单和设置条件,以显示todo或编辑表单,这取决于isEditing属性是true还是false。我们的模板现在是这样的。

<template>
    <div class="ui centered card">
        <!-- 当我们不在编辑模式时显示的todo列表 -->
        <div class="content" v-show="!isEditing">
            <div class="header">{{ todo.title }}</div>
            <div class="meta">{{ todo.project }}</div>
            <div class="extra content">
                <span class="right floated edit icon" v-on:click="showForm">
                    <i class="edit icon"></i>
                </span>
            </div>
        </div>
        <!-- 当我们在编辑模式时,表单是可见的 -->
        <div class="content" v-show="isEditing">
            <div class="ui form">
                <div class="field">
                    <label>Ttitle</label>
                    <input type="text" v-model="todo.title" />
                </div>
                <div class="field">
                    <label>Project</label>
                    <input type="text" v-model="todo.project" />
                </div>
                <div class='ui two button attached buttons'>
                    <button class='ui basic blue button' v-on:click="hideForm">Close X</button>
                </div>
            </div>
        </div>
        <div class='ui bottom attached green basic button' v-show="!isEditing && todo.done" disabled>Completed</div>
        <div class='ui bottom attached red basic button' v-show="!isEditing && !todo.done">Complete</div>
    </div>
</template>

除了showForm方法之外,我们还需要添加一个hideForm方法,以便在单击cancel按钮时关闭表单。让我们看看脚本现在是什么样子。

<script type="text/javascript">
    export default {
        props: ['todo'],
        data() {
            return {
                isEditing: false
            }
        },
        methods: {
            showForm() {
                this.isEditing = true;
            },
            hideForm() {
                this.isEditing = false;
            }
        }
    }
</script>

由于我们已经将表单值绑定到todo值,因此编辑值将立即编辑todo。一旦完成,我们将点击关闭按钮查看更新的todo

删除一个Todo

让我们开始给编辑的图标上添加删除一个Todo的事件。

<template>
    <div class="ui centered card">
        <!-- 当我们不在编辑模式时显示的todo列表 -->
        <div class="content" v-show="!isEditing">
            <div class="header">{{ todo.title }}</div>
            <div class="meta">{{ todo.project }}</div>
            <div class="extra content">
                <span class="right floated edit icon" v-on:click="showForm">
                    <i class="edit icon"></i>
                </span>
                <span class="right floated trash icon" v-on:click="deleteTodo(todo)">
                    <i class="trash icon"></i>
                </span>
            </div>
        </div>
        <!-- 当我们在编辑模式时,表单是可见的 -->
        <div class="content" v-show="isEditing">
            <div class="ui form">
                <div class="field">
                    <label>Ttitle</label>
                    <input type="text" v-model="todo.title" />
                </div>
                <div class="field">
                    <label>Project</label>
                    <input type="text" v-model="todo.project" />
                </div>
                <div class='ui two button attached buttons'>
                    <button class='ui basic blue button' v-on:click="hideForm">Close X</button>
                </div>
            </div>
        </div>
        <div class='ui bottom attached green basic button' v-show="!isEditing && todo.done" disabled>Completed</div>
        <div class='ui bottom attached red basic button' v-show="!isEditing && !todo.done">Pending</div>
    </div>
</template>

接下来,我们将向组件类添加一个方法来处理图标的点击事件。该方法将向父组件TodoList发送一个delete-todo事件,用来删除一个Todo。我们将给删除图标添加一个事件监听器。

<span class='right floated trash icon' v-on:click="deleteTodo(todo)">

methods中添加一个deleteTodo()事件:

// Todo component
methods: {
    deleteTodo(todo) {
        this.$emit('delete-todo', todo);
    },
},

父组件TodoList将需要添加一个删除事件。我们是这样来定义它。

// TodoList component
methods: {
    deleteTodo(todo) {
        const todoIndex = this.todos.indexOf(todo);
        this.todos.splice(todoIndex, 1);
    },
},

deleteTodo方法将被传递到Todo组件,如下所示。

// TodoList template
<todo  v-on:delete-todo="deleteTodo" v-for="todo in todos" v-bind:todo="todo"></todo>

一旦我们点击删除图标,事件将被释放并传给父组件,然后将其删除。

添加一个新的Todo

为了创建一个新的todo,我们首先要在src/components中创建一个新的组件CreateTodo。这将显示一个带有加号的按钮,点击后会变成表单。看起来像这样:

<template>
    <div class='ui basic content center aligned segment'>
        <button class='ui basic button icon' v-on:click="openForm" v-show="!isCreating">
        <i class='plus icon'></i>
        </button>
        <div class='ui centered card' v-show="isCreating">
            <div class='content'>
                <div class='ui form'>
                    <div class='field'>
                        <label>Title</label>
                        <input v-model="titleText" type='text' ref='title' defaultValue="">
                    </div>
                    <div class='field'>
                        <label>Project</label>
                        <input type='text' ref='project' defaultValue="">
                    </div>
                    <div class='ui two button attached buttons'>
                        <button class='ui basic blue button' v-on:click="sendForm()">
                        Create
                        </button>
                        <button class='ui basic red button' v-on:click="closeForm">
                        Cancel
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
        titleText: '',
        projectText: '',
        isCreating: false,
        };
    },
    methods: {
        openForm() {
            this.isCreating = true;
        },
        closeForm() {
            this.isCreating = false;
        },
        sendForm() {
            if (this.titleText.length > 0 && this.projectText.length > 0) {
                const title = this.titleText;
                const project = this.projectText;
                this.$emit('create-todo', {
                    title,
                    project,
                    done: false,
                });
                this.newTodoText = '';
            }
            this.isCreating = false;
        }
    }
}
</script>

创建新组件后,我们导入它并将其添加到组件类中的组件属性中。

// Main Component App.vue
components: {
    TodoList,
    CreateTodo,
},

我们还将添加一个新方法用来创建新的Todos。

// App.vue
methods: {
    addTodo(title) {
        this.todos.push({
            title,
            done: false,
        });
    },
},

App.vue模板中添加CreateTodo组件:

<create-todo v-on:add-todo="addTodo">

完成一个Todo

最后,我们将在Todo组件中添加一个方法completeTodo,它会在单击Pending按钮时向父组件发送一个complete-todo事件,并将todo的状态设置为true

// Todo component
methods: {
    completeTodo(todo) {
        this.$emit('complete-todo', todo);
    },
}

把事件处理添加到TodoList组件中:

methods: {
    completeTodo(todo) {
        const todoIndex = this.todos.indexOf(todo);
        this.todos[todoIndex].done = true;
    },
},

TodoList方法传递到Todo组件,我们将把它添加到Todo组件调用。

<todo v-on:delete-todo="deleteTodo" v-on:complete-todo="completeTodo" v-for="todo in todos" :todo.sync="todo"></todo>

总结

我们已经学习了如何使用Vue CLI初始化一个Vue应用程序。此外,我们还了解了组件结构、向组件添加事件监听和事件处理程序和添加数据。我们看到了如何创建一个todo,编辑它并删除它。还有很多东西需要学习。我们在主要组件中使用静态数据。下一步是从服务器获取数据并相应的进行更新。我们现在准备创建交互式的Vue应用程序。你自己试试别的办法,看看结果如何。

本文根据@Jeremy Kithome的《Build a To-Do App with Vue.js 2》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://scotch.io/tutorials/build-a-to-do-app-with-vue-js-2

大漠

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

如需转载,烦请注明出处:https://www.w3cplus.com/vue/build-a-to-do-app-with-vue-js-2.html

返回顶部