原稿:语雀 · 【Demo】Todo App · 原目录:Vue › 【Demo】Todo App
通过一个todo app的开发迭代,熟悉vue中各种基本语法的使用以及组件化的思想 |
v1.0 朴素的 todo
实现一个待办清单应用,首先只使用到vue的声明式渲染,即不与html交互,而是将数据存入vue实例,由实例接管选中的DOM,对其进行响应式渲染
<div id="app">
{{message}}
<ul>
<li>{{todolist[0].time}} {{todolist[0].todo}} {{todolist[0].done}}</li>
<li>{{todolist[1].time}} {{todolist[1].todo}} {{todolist[1].done}}</li>
<li>{{todolist[2].time}} {{todolist[2].todo}} {{todolist[2].done}}</li>
</ul>
</div>
<script>
class TodoItem {
constructor(time, todo, done = "未完成") {
this.time = time;
this.todo = todo;
this.done = done;
}
}
var app = new Vue({
el: "#app",
data: {
message: "待办事项:",
todolist: [
new TodoItem("7:00", "起床", "已完成"),
new TodoItem("8:00", "吃饭"),
new TodoItem("9:00", "打豆豆"),
],
},
});
</script>
得益于vue.js的模板语法,使用双大括号{{}}和js表达式绑定的数据都能被直接渲染到视图中

v1.1 列表渲染:v-for
如果每一条待办都要手动使用<li>罗列,非常没有必要,vue提供列表渲染,允许使用 <v-for> 指令把一个数组对应为一组元素。该指令还支持第二个参数index,可以获取到当前元素的索引
<div id="app">
{{message}}
<ul>
<li v-for="item in todolist">
{{item.time}} {{item.todo}} {{item.done}}
</li>
</ul>
</div>
渲染效果不变
v1.2 条件渲染:v-if
上面的todo app中,是否完成任务的视觉区分并不明显,如果实现划线删除效果会更好:7:00 起床,借助条件渲染的 v-if/v-else/v-else-if 指令,可以根据不同情况渲染出不同效果
<div id="app">
{{message}}
<ul>
<li v-for="item in todolist">
<span v-if="item.done"><del>{{item.time}} {{item.todo}}</del></span>
<span v-else>{{item.time}} {{item.todo}}</span>
</li>
</ul>
</div>
var app = new Vue({
el: "#app",
data: {
message: "待办事项:",
todolist: [
new TodoItem("7:00", "起床", true),
new TodoItem("8:00", "吃饭"),
new TodoItem("9:00", "打豆豆"),
],
}
});
通过条件渲染,将done的truthy值作为渲染的判断条件,已完成的项目被包裹在<del>标签中

v1.3 事件处理:v-on
现在把这个待办app变得更灵活,使用者可以自行勾选是否完成任务
使用vue.js的事件处理机制,为<input>元素添加监听事件,监听事件需要声明在Vue实例的 methods 对象中,在元素上使用 v-on 指令添加监听,v-on 可以被简写为 @
<ul> <li v-for="item, index in todolist"> <input type="checkbox" @change="doIt(index)"> <span v-if="item.done"><del>{{item.time}} {{item.todo}}</del></span> <span v-else>{{item.time}} {{item.todo}}</span> </li></ul>
var app = new Vue({ el: "#app", data: { message: "待办事项:", todolist: [ new TodoItem("7:00", "起床"), new TodoItem("8:00", "吃饭"), new TodoItem("9:00", "打豆豆"), ], }, methods: { doIt(index) { this.todolist[index].done = !this.todolist[index].done; }, },});
通过 @change=“doIt(index)” 每次选中checkbox时,index会被传入doIt(),当前item的done属性值会取非,从而改换选中状态,done的变化又会立刻响应到渲染结果上,在 v-if 和 v-else 中变换,这是一种双向的信息传递

v1.4 表单输入绑定:v-model
|
修改<input>,删除监听,直接使用v-model将输入绑定到item.done上,这样checkbox是否选中会直接更新到vue实例的data中
<input type="checkbox" v-model="item.done">
<!-- 等价于 -->
<input type="checkbox" v-bind:checked="item.done" @change="item.done=$event.target.checked">
v-model 原理:v-model 实际上是一个语法糖:
|
根据v-model可以逆向更新表单元素渲染状态的特性,设置一个重置按键,在绑定的方法中将每一个item的done置为false,点击后所有checkbox的选中状态也被取消

<div id="app">
{{message}}
<ul>
<li v-for="item, index in todolist">
<input type="checkbox" v-model="item.done">
<span v-if="item.done"><del>{{item.time}} {{item.todo}}</del></span>
<span v-else>{{item.time}} {{item.todo}}</span>
</li>
</ul>
<button @click="reset">重置</button>
</div>
var app = new Vue({
el: "#app",
data: {
message: "待办事项:",
todolist: [
new TodoItem("7:00", "起床"),
new TodoItem("8:00", "吃饭"),
new TodoItem("9:00", "打豆豆"),
],
},
methods: {
reset(){
this.todolist.forEach((item)=>item.done=false);
}
},
});
v1.5 组件化
组件是可复用的vue实例,如果我们想复用 todo app 又不想重写冗余代码,可以提炼为一个组件。作为一个 vue 实例,其中的属性和正常的vue基本一样,主要有这几个特点:
|
v1.5.1 朴素的组件
通过以上信息,可以得到一个简单 todo app 组件,使用 Vue.component() ,将 name 为“my-todo-list”的组件进行了全局注册,即之后在任何使用 new Vue 创建的实例中都可以使用这个组件
Vue.component("my-todo-list", {
props: ["message", "todolist"],
data() {
return {
message: "待办事项:",
todolist: [
new TodoItem("7:00", "起床"),
new TodoItem("8:00", "吃饭"),
new TodoItem("9:00", "打豆豆"),
],
};
},
methods: {
reset() {
this.todolist.forEach((item) => (item.done = false));
},
},
template: `<div class="todolist">
{{message}}
<ul>
<li v-for="item, index in todoList">
<input type="checkbox" v-model="item.done" />
<span v-if="item.done">
<del>{{item.time}} {{item.todo}}</del></span>
<span v-else>{{item.time}} {{item.todo}}</span>
</li>
</ul>
<button @click="reset">重置</button>
</div>`,
});
使用时在 html 中用一对标签即可,为了美观,简单用 flex 布局分三列
<div id="app">
<my-todo-list></my-todo-list>
<my-todo-list></my-todo-list>
<my-todo-list></my-todo-list>
</div>
<style>
#app {
display: flex;
}
.todolist {
border: gray 0.5px solid;
width: 200px;
}
.todolist button {
display: block;
margin: 5px auto;
}
</style>
最终效果

v1.5.2 向组件传递数据:props
把一个 todo app 抽离为组件后,每次使用时必须传入不同的 todolist 才有意义,否则每个组件都是一模一样的。vue.js 为组件准备了 props 这个特殊的属性,它是一个数组,可以接收不定数量的字符串,每一个串都代表一个由父实例传递给组件的 attribute
|
下面在父实例 data 对周一到周三创建三个list,传递给组件
<div id="app">
<my-todo-list :todolist="monList" message="星期一"></my-todo-list>
<my-todo-list :todolist="tueList" message="星期二"></my-todo-list>
<my-todo-list :todolist="wedList" message="星期三"></my-todo-list>
</div>

v1.5.3 再探 props:单向数据流
props 可以组件上创建自定义 attribute 即 prop,用以接收父实例数据。
如果把 prop 理解为指针,那么组件使用 prop 时对父组件的数据只有读取的权限,没有修改的权限 |
比如修改 reset(),在其中更改 props
props: ["message", "todolist"],
reset() {
this.message = "工作日";
this.todolist = [];
}
当点击重置后,虽然修改成功,但浏览器会给出 warn。无论 props 是原始类型值还是引用值,修改都会报错,但是对于引用值来说,通过引用修改对象不会报错(比如增删数组元素)

如果一定要将父组件传递的数据作为子组件自身的 prop,需要在子组件的 data() 中对来自 prop 的数据进行拷贝