使用Vue.js 2创建To-Do App
Vue是一个简而小的渐进式JavaScript框架,可用于增量地构建强大的Web应用程序。
Vue是其他JavaScript框架(如AngularJS)的轻量级替代品。通过对HTML、CSS和JS的理解,你应该准备好与Vue一起运行。
在本文中,我们将使用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的测试。
安装项目需要的依赖关系:
$ cd my-project
$ npm install
很多时候使用
npm i
会有问题,建议改用cnpm
,不过我这里采用的是tnpm
来安装依赖关系。
接着运行程序
$ npm run dev
运行上面的命令之后,浏览器会自动访问http://localhost:8080,该页面显示如下:
为了给我们的应用程序添加样式风格,我们将使用语义化。添加较少的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>
中添加上面的代码。
当我们保存修改后的文件时,在浏览器看到的效果是这样的:
添加组件数据
我们将需要给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。
如需转载,烦请注明出处:https://www.fedev.cn/vue/build-a-to-do-app-with-vue-js-2.htmlNike Roshe Two Release Date