前言
TodoMVC是一个示例项目,它使用目前流行的不同JavaScript框架的来实现同一个Demo,来帮助你熟悉和选择最合适的前端框架。学习框架最直接有效的方式就是上手练习,接下来我们将用Vue.js来完成TodoMVC的示例。
基础进阶:使用生命周期实现
官网地址:https://todomvc.com/
一、准备
在 Vscode 软件在打开集成终端
- 输入
vue cerate 项目名
创建项目 - 输入
cd 项目名
进入创建好的项目内 - 输入
npm run serve
启动项目 - 创建
TodoDemo.vue
、TodoContent.vue
、TodoFoot.vue
、TodoHeader.vue
、TodoItem.vue
- 将
TodoDemo.vue
引入App.vue
- 将
TodoItem.vue
引入TodoContent.vue
- 将
TodoContent.vue
、TodoFoot.vue
、TodoHeader.vue
引入TodoDemo.vue
二、创建
- TodoDemo.vue :
<template>
<div>
<h1>Todos</h1>
<!-- 将`TodoContent.vue`、`TodoFoot.vue`、`TodoHeader.vue`引入`TodoDemo.vue` -->
<TodoHeader />
<TodoContent />
<TodoFoot />
</div>
</template>
<script>
import TodoContent from './TodoContent.vue'
import TodoFoot from './TodoFoot.vue'
import TodoHeader from './TodoHeader.vue'
export default {
components: { TodoHeader, TodoContent, TodoFoot },
}
</script>
<style>
</style>
- TodoHeader.vue :
<template>
<div>
<input type="checkbox">
<input type="text">
<button>添加</button>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
- TodoFoot.vue:
<template>
<div>
<span>1 item left</span>
<div class="type-btns">
<button>all</button>
<button>active</button>
<button>completed</button>
</div>
<button>clear completed</button>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
- TodoItem.vue
<template>
<li>
<template v-if="true">
<input type="checkbox">
<span></span>
<span>×</span>
</template>
<input v-else type="text">
</li>
</template>
<script>
export default {
}
</script>
<style>
</style>
- TodoContent.vue
<template>
<div>
<ul>
<!--`TodoItem.vue`引入 `TodoContent.vue`-->
<TodoItem />
</ul>
</div>
</template>
<script>
import TodoItem from './TodoItem.vue'
export default {
components: { TodoItem },
}
</script>
<style>
</style>
- 在 vue 项目中创建一个 后端数据:
- 1)文件夹:data
- 2)文件名:data.json
- 3)使用:
json-server --watch data.json -p 3008
启动后端数据
{
"todos":[
{
"id": "yht67h9",
"text": "律师函警告",
"done": false
},
{
"id": "yh87sdy",
"text": "喜欢唱,跳,rep,篮球",
"done": true
},
{
"id": "thy52dq",
"text": "你干嘛,哎呀",
"done": false
}
]
}
三、Vue 生命周期介绍
Vue 组件生命周期:vue组件的生命周期(组件的一生:开始、经历、结束):vue 组件提供的一些钩子函数,这写函数会在组件的不同阶段自动执行
- 组件初始渲染
- beforeCreate
- 在组件实例初始化完成之后立即调用。
- 组件的数据 事件开始关联
- created
- 数据 事件 … 关联完成
- 就可以修改 data了
- 在组件实例处理完所有与状态相关的选项后调用
- beforeMount
- 开始渲染
- 在组件被挂载之前调用。
- mounted
- 完成渲染 可以获取到真实 dom 节点了
- 在组件被挂载之后调用。
- beforeCreate
- 组件页面更新
- beforeUpdate
- 开始修改
- updated
- 修改完毕
- 获取更新之后的 data 已经更新之后的 真实 dom
- beforeUpdate
- 组件销毁
- beforeDestroy
- 开始销毁
- destroyed
- 销毁完毕
- beforeDestroy
四、功能实现
实现功能前需要:
- 1)首先 在项目中:导入 axios
npm i axios
- 2)在TodoContent.vue 导入 axios
功能一:实现后端数据的导入
- 在 TodoContent.vue 导入数据
- 使用
v-for
进行数据的传递
<template>
<div>
<ul v-if="todos.length">
<!-- `TodoItem.vue`引入 `TodoContent.vue` -->
<!-- 组件的循环也可以使用 v-for -->
<!-- :text="todo.text" 传递 text -->
<TodoItem v-for="todo in todos" :key="todo.id" :text="todo.text"/>
</ul>
</div>
</template>
<script>
import TodoItem from './TodoItem.vue'
import axios from 'axios' //
export default {
data() {
return {
todos: []
}
},
components: { TodoItem },
async created (){
// 数据 事件 ... 关联完成
// 就可以修改 data了
// 在组件实例处理完所有与状态相关的选项后调用
// 获取数据库 方法一:
// axios.get('http://localhost:3008/todos').then( res => {
// console.log(res.data)
// })
// 方法二
const res = await axios.get('http://localhost:3008/todos')
this.todos = res.data
},}
</script>
<style>
</style>
- 将 TodoContent.vue 获取的 text 传递给 TodoItem.vue
<template>
<li>
<template v-if="true">
<input type="checkbox">
<span>{{text}}</span>
<span>×</span>
</template>
<input v-else type="text">
</li>
</template>
<script>
export default {
// 接输 text
props: ['text']
}
</script>
<style>
</style>
功能二:实现数据的删除
- 1)父组件(TodoContent.vue)定义好删除的功能,给子组件(TodoItem.vue)去使用
methods: {
// 写的都是函数,不一定都是事件,也可能写功能
// 删除功能
del(id){
this.todos = this.todos.filter(todo => todo.id !== id)
}
}
- 2)父组件(TodoContent.vue)将定义好的删除功能传递给 给子组件(TodoItem.vue)
- :delTodo=“del” :id=“todo.id”
TodoContent.vue
<ul v-if="todos.length">
<!-- `TodoItem.vue`引入 `TodoContent.vue` -->
<!-- 组件的循环也可以使用 v-for -->
<!-- :text="todo.text" 传递 text -->
<TodoItem v-for="todo in todos" :key="todo.id" :text="todo.text" :delTodo="del" :id="todo.id"/>
</ul>
TodoItem.vue
export default {
// 接输 text
props: ['text','delTodo','id'],
methods: {
// 删除父组件里面的 todos 里面的某一个数据
// 做法在父组件定义好删除的操作
// 然后传递给子 组件去执行
del(){
this.delTodo(this.id)
}
}
}
注意:当父组件向子组件传递函数的时候 vue 建议使用 自定义事件来传递
在父组件内@事件名='函数'
父组件给子组件传递了自定义事件的话
那么子组件就可以使用this.$emit('事件名',参数1,参数2,....)
接收并且调用
<TodoItem v-for="todo in todos" :key="todo.id" :text="todo.text" @delTodo="del" :id="todo.id"/></ul>
del(){
// 父组件给子组件传递了自定义事件的话
// 那么子组件就可以使用 this.$emit('事件名',参数1,参数2) 接收并且调用
this.$emit('delTodo',this.id)
}
删除功能完整代码:
TodoContent.vue
<template>
<div>
<ul v-if="todos.length">
<!-- `TodoItem.vue`引入 `TodoContent.vue` -->
<!-- 组件的循环也可以使用 v-for -->
<!-- :text="todo.text" 传递 text -->
<!-- 当父组件向子组件传递函数的时候 vue 建议使用 自定义事件来传递 -->
<!-- 在父组件内 @事件名='函数' -->
<TodoItem v-for="todo in todos" :key="todo.id" :text="todo.text" @delTodo="del" :id="todo.id"/>
</ul>
</div>
</template>
<script>
import TodoItem from './TodoItem.vue'
// 导入axios
import axios from 'axios'
export default {
data() {
return {
todos: [] //创建一个空的用来存放数据
}
},
components: { TodoItem },
// 获取后端数据
async created (){
// 数据 事件 ... 关联完成
// 就可以修改 data了
// 在组件实例处理完所有与状态相关的选项后调用
// 获取数据库 方法一:
// axios.get('http://localhost:3008/todos').then( res => {
// console.log(res.data)
// })
// 方法二
const res = await axios.get('http://localhost:3008/todos')
this.todos = res.data
},
methods: {
// 写的都是函数,不一定都是事件,也可能写功能
// 删除功能
del(id){
this.todos = this.todos.filter(todo => todo.id !== id)
}
}
}
</script>
<style>
</style>
TodoItem.vue
<template>
<li>
<template v-if="true">
<input type="checkbox">
<span>{{text}}</span>
<span @click="del">×</span> <!--删除功能-->
</template>
<input v-else type="text">
</li>
</template>
<script>
export default {
// 接输 text
props: ['text','id'],
methods: {
// 删除父组件里面的 todos 里面的某一个数据
// 做法在父组件定义好删除的操作
// 然后传递给子 组件去执行
del(){
// 父组件给子组件传递了自定义事件的话
// 那么子组件就可以使用 this.$emit('事件名',参数1,参数2) 接收并且调用
this.$emit('delTodo',this.id)
}
}
}
</script>
<style>
</style>
功能三:实现数据的添加
-
1)因为TodoHeader.vue 与 TodoContent.vue 是兄弟关系
-
2)这时我们需要将处理的 data 写到他们的 父组(TodoDemo.vue)组件内,然后向下传递
-
3)向下传递的时候需要注意:
- 组件的
data props computed methods $emit
等 - 在组件的 js 内可以直接使用
this.xx
服务 - 在组件的
template
内,可以直接访问 - 父组件传递自定义事件
delTodo
子组件想要继续向下传递,不能直接使用$emit()
向下传递,因为$emit
作用是接收并调用
- 组件的
TodoDemo.vue
<template>
<div>
<h1>Todos</h1>
<!-- 将`TodoContent.vue`、`TodoFoot.vue`、`TodoHeader.vue`引入`TodoDemo.vue` -->
<!-- TodoHeader 组件想要修改 TodoContent 组件内的 data -->
<!-- 因为它们两个是兄弟关系 -->
<!-- 可以将需要处理的 data 写到他们的 父组(TodoDemo.vue)组件内,然后向下传递 -->
<TodoHeader />
<TodoContent :todos="todos" @delTodo="del"/>
<TodoFoot />
</div>
</template>
<script>
import TodoContent from './TodoContent.vue'
import TodoFoot from './TodoFoot.vue'
import TodoHeader from './TodoHeader.vue'
// 导入axios
import axios from 'axios'
export default {
components: { TodoHeader, TodoContent, TodoFoot },
data() {
return {
todos: [], //创建一个空的用来存放数据
}
},
async created(){
// 数据 事件 ... 关联完成
// 就可以修改 data了
// 在组件实例处理完所有与状态相关的选项后调用
// 获取数据库 方法一:
// axios.get('http://localhost:3008/todos').then( res => {
// console.log(res.data)
// })
// 方法二
const res = await axios.get('http://localhost:3008/todos')
this.todos = res.data
},
methods: {
// 写的都是函数,不一定都是事件,也可能写功能
// 删除功能
del(id){
this.todos = this.todos.filter(todo => todo.id !== id)
}
}
}
</script>
<style>
</style>
TodoContent.veu
<template>
<div>
<ul v-if="todos.length">
<!-- `TodoItem.vue`引入 `TodoContent.vue` -->
<!-- 组件的循环也可以使用 v-for -->
<!-- :text="todo.text" 传递 text -->
<!-- 当父组件向子组件传递函数的时候 vue 建议使用 自定义事件来传递 -->
<!-- 在父组件内 @事件名='函数' -->
<!-- -->
<TodoItem v-for="todo in todos" :key="todo.id" :text="todo.text" :id="todo.id" @delTodo="() => {$emit('delTodo',todo.id)}"/>
<!-- 组件的 data props computed methods $emit等 -->
<!-- 在组件的 js 内可以直接使用 this.xx 服务 -->
<!-- 在组件的 template 内,可以直接访问 -->
<!-- 父组件传递自定义事件 delTodo 子组件想要继续向下传递,不能直接使用 $emit() 向下传递,因为 $emit 作用是接收并调用 -->
</ul>
</div>
</template>
<script>
import TodoItem from './TodoItem.vue'
export default {
props: ['todos'],
components: { TodoItem },
}
</script>
<style>
</style>
- 4)功能的实现
TodoDemo.vue
<template>
<div>
<h1>Todos</h1>
...
<TodoHeader @addTodo="addTodo"/>
...
</div>
</template>
<script>
...
export default {...},
async created(){...},
methods: {
...
// 添加功能
addTodo(text){
this.todos.push({
id: new Date().getTime(),
done: false,
text
})
}
}
}
</script>
<style>
</style>
TodoHeader.vue
<template>
<div>
<input type="checkbox">
<input type="text" v-model.trim="text">
<button @click="add">添加</button>
</div>
</template>
<script>
export default {
data(){
return {
text:''
}
},
methods:{
add(){
const {text} = this
if (text) {
// Date().getTime() 确保添加完成后的 id 不同
this.$emit('addTodo',text)
this.text = ""; // 添加完成后清空输入框
}
}
}
}
</script>
<style>
</style>
功能四:实现完成的样式
- 1)首先需要获取到 id ,来对 done 的值进行分析显示
- 2)在 TodoDemo.vue中定义 方法
<!--获取方法-->
<TodoContent ... @completTodo="completTodo"/>
completTodo(id){
const currentTodo = this.todos.find(todo => todo.id === id)
currentTodo.done = !currentTodo.done
}
- 3)将在 TodoDemo.vue中获取的方法以及值传递给 TodoContent,vue
<TodoItem ... @completTodo="() => {$emit('completTodo',todo.id)}"/>
- 3)将在 TodoContent,vue中获取传递给 Todoltem,vue
<input type="checkbox" ... @change="change">
change() {
this.$emit('completTodo')
}
- 4)设置样式
...
<span :class="{done: done}">{{text}}</span>
...
<style>
.done{
color: #ccc;
text-decoration: line-through;
}
</style>
功能五:实现击修改
- 1)在TodoDemo.vue中定义一个 空的 id
...
data() {
return {
todos: [], //创建一个空的用来存放数据
editid: ''
}
},
...
- 2)将TodoDemo.vue中的 editid 传递到 Todoltem.vue
TodoDemo.vue 中的:
<TodoContent ...:editTodo="editid" ..."/>
TodoContent.vue 中的:
<TodoItem ... :editid='editid' .../>
....
export default {
props: ['todos','editid'],// 获取 父级传递过来的 todos editid
...
Todoltem.vue 中的:
...
<template v-if="editid !== id">
...
...
export default {
// 接输 text
props: ['text','id','done','editid'],
...
- 3)在TodoDemo.vue中定义方法
<TodoContent ... @changeEditId="changeEditId"/>
methods: {
changeEditId(id){
this.editid =id
}
- 4)将 TodoDemo.vue中定义方法传递到 Todoltem.vue
TodoContent.vue 中的:
<TodoItem ...@changeEditId="(id) => {$emit('changeEditId',id)}" />
Todoltem.vue中的
<template>
...
<span @dblclick="handleDblclick" :class="{done: done}">{{text}}</span>
...
</template>
...
export default {
...
handleDblclick (){
this.$emit('changeEditId',this.id)
})
- 5)双击后获取原内容,并且聚交
Todoltem.vue
<input v-else type="text" :value="text"> <!--获取原内容-->
export default {
...
handleDblclick (){
this.$emit('changeEditId',this.id)
// 获取交点
this.$nextTick(() => {
this.$refs.myInp.focus()
})
- 6)失去交点的时候进行修改
- 在TodoDemo.vue中定义方法
<TodoContent ... @editTodo="editTodo"/>
methods: {
...
// 双击修改
editTodo(id, text){
const currentTodo = this.todos.find(todo => todo.id === id)
currentTodo.text = text
}
- 将TodoDemo.vue中定义方法传递到 Todoltem.vue
TodoContent.vue 中的:
<TodoItem ... @editTodo="(text) => {$emit('editTodo',todo.id,text)}" />
Todoltem.vue中的
<template>
...
<input ... @blur="editTodo">
...
</template>
...
export default {
...
editTodo(event) {
const text = event.target.value.trim()
if(text){
this.$emit('editTodo', text)
this.$emit('changeEditId')
}
}
功能六:展示未完成任务
- 1)创建计算属性,并且进行传递
<TodoFoot :activeTodoNum="activeTodoNums"/>
export default {
...
// 统计未完成个数
computed:{
activeTodoNums(){
return this.todos.filter(todo => !todo.done).length
}
},
- 2)TodoFoot.vue 进行接收
...
<span>{{activeTodoNums}} item left</span>
...
<script>
export default {
props:['activeTodoNums']
}
</script>
功能七:实现左上角的进行全选功能
- 1)创建计算属性,并且进行传递
<TodoHeader .... :isAllTodoDone="isAllTodoDone"/>
computed:{
...
isAllTodoDone(){
return this.todos.every(todo => todo.done)
}
- 2)TodoHeade.vue 进行接收
<template>
<div>
<input type="checkbox" :checked="isAllTodoDone">
...
</div>
</template>
<script>
export default {
data(){...
props: ['isAllTodoDone'],
</script>
- 3)在TodoDemo.vue 编写方法,并且传递给 TodoHeader.vue
<TodoHeader ...@changAllTodoStatus="changAllTodoStatus"
/>
export default {
...
async created() {
...
// 修改状态
changAllTodoStatus(newStatus) {
this.todos.forEach((todo) => {
todo.done = newStatus;
});
- 4)TodoHeader.vue 进行接收并且使用
<template>
<div>
<input ... @change="change"> <!--使用 TodoDemo.vue 传递过来的方法 -->
</div>
</template>
<script>
...
methods:{
...
// 接收方法
change(event){
this.$emit('changAllTodoStatus',event.target.checked)
}
}
}
</script>
功能八:对 all、 active、completed 的功能进行实现
- 1)自定义一个空的
type:''
,使用计算属性 对其进行筛选,并将筛选的结果传递给:todos
<TodoContent
:todos="showTodos"
...
/>
export default {
components: { TodoHeader, TodoContent, TodoFoot },
data() {
...
type: 'all',// 定义 all、 active、completed 的类型
};
},
...
showTodos(){
const {type} = this
return this.todos.filter((todo) =>
type === "all" ? true : type === "active" ? !todo.done : todo.done
);
- 2)子组件(TodoFoot)希望修改并且使用 父组件的 type 时,我们可以使用
- 常规做法是 利用 props 传值 利用自定义事件进行传递修改方法
- 我们也可以使用 v-bind 的
sync
修饰符来代替上面的方法 - sync 修饰符的做用是:
- 1.将
type
传递下去 名字也加type
- 2.定义了一个赋值修改 type 的方法叫
update:type
当作自定义事件传递下去 - 写法:
:type=”type“
@update:type="(newType) => this.type = newType"
- 1.将
<TodoFoot :activeTodoNums="activeTodoNums" v-bind.sync="type"/>
- 3)将 type 传递给 TodoFoot.vue
<template>
<div>
<div class="type-btns">
<button
:class="{ 'btn-active': type === 'all' }" >
all
</button>
<button
:class="{ 'btn-active': type === 'active' }" >
active
</button>
<button
:class="{ 'btn-active': type === 'completde' }">
completde
</button>
</div>
</div>
</template>
<script>
export default {
props: [... "type"],
};
</script>
<style>
.btn-active {
color: red;
}
</style>
- 4)获取 TodoDemo.vue传递过来的方法
<template>
<div>
<span>{{ activeTodoNums }} item left</span>
<div class="type-btns">
<button
...
@click="$emit('update:type', 'all')"
>
all
</button>
<button
...
@click="$emit('update:type', 'active')"
>
active
</button>
<button
...
@click="$emit('update:type', 'completde')"
>
completde
</button>
</div>
</div>
</template>
功能八:实现clear completed 功能
- 1)在TodoDemo.vue 编写方法,并且传递
<TodoFoot ... @delAllDoneTodo="delAllDoneTodo"/>
...
// clear completed 功能
delAllDoneTodo(){
this.todos = this.todos.filter(todo => !todo.done)
}
- 2)在TodoFoot.vue 获取方法
<template>
<div>
...
<button @click="$emit('delAllDoneTodo')">clear completed</button>
</div>
</template>
- 提升
- 们也可以使用 组件上的 v-model 来代替上面的做法
- v-model=“type”
- 相当于 :value=“type” 传递了 props 叫 balue
- 还有 @input=“(newType) => this.type = newType” 传递修改的自定义事件
- 当父组件 v-model 的时候默认的 props叫 value 自定义事件叫 input
- 我们已在组件中使用 model 修改这两名字
总结
-
组件生命周期分为
-
初始渲染
-
更新
-
卸载
-
自定义事件:父组件传递给子组件函数的时候可以使用自定义事件进行传递
-
自定义事件多级传递的话不能一直使用
emit
传递,因为emit
是接收并调用父组件 -
父组件:
调用 addTodo 方法
<TodoHeader @addTodo="addTodo" />
自定义 addTodo 方法
editTodo(id, text){
const currentTodo = this.todos.find(todo => todo.id === id)
currentTodo.text = text
=}
- 子组件:
this.$emit('delTodo',参数1,....)
<button @click="$emit('delTodo',参数1,...)">删除</button>
- 技巧
template
和script
内使用 组件内的- data
- props
- methods
- coumputed
- 的时候,区别是前者不加
this
,后者需要加this
- 组件间的交互
- props 父子交互
- 自定义事件 父子交互
- 共享数据 兄弟交互
- sync 修饰符的做用是:
- 1.将
type
传递下去 名字也加type
- 2.定义了一个赋值修改 type 的方法叫
update:type
当作自定义事件传递下去 - 写法:
:type=”type“
@update:type="(newType) => this.type = newType"
- 1.将
- V-model
- v-model=“type”
- 相当于 :value=“type” 传递了 props 叫 balue
- 还有 @input=“(newType) => this.type = newType” 传递修改的自定义事件
完整代码
TodoDemo.vue
<template>
<div>
<h1>Todos</h1>
<!-- 将`TodoContent.vue`、`TodoFoot.vue`、`TodoHeader.vue`引入`TodoDemo.vue` -->
<!-- TodoHeader 组件想要修改 TodoContent 组件内的 data -->
<!-- 因为它们两个是兄弟关系 -->
<!-- 可以将需要处理的 data 写到他们的 父组(TodoDemo.vue)组件内,然后向下传递 -->
<TodoHeader
@addTodo="addTodo"
:isAllTodoDone="isAllTodoDone"
@changAllTodoStatus="changAllTodoStatus"
/>
<TodoContent
:todos="showTodos"
:editid="editid"
@delTodo="del"
@completTodo="completTodo"
@changeEditId="changeEditId"
@editTodo="editTodo"
/>
<!-- 子组件(TodoFoot)希望修改并且使用 父组件的 type 时,我们可以使用
- 常规做法是 利用 props 传值 利用自定义事件进行传递修改方法
- 我们也可以使用 v-bind 的 sync 修饰符来代替上面的方法 -->
<!-- - sync 修饰符的做用是:
- 1.将 `type` 传递下去 名字也加 `type`
- 2.定义了一个赋值修改 type 的方法叫 `update:type`当作自定义事件传递下去
- 写法: `:type=”type“` `@update:type="(newType) => this.type = newType"` -->
<!-- <TodoFoot :activeTodoNums="activeTodoNums" :type.sync="type" @delAllDoneTodo="delAllDoneTodo"/> -->
<!-- 我们也可以使用 组件上的 v-model 来代替上面的做法 -->
<!-- v-model="type" -->
<!-- 相当于 :value="type" 传递了 props 叫 balue -->
<!-- 还有 @input="(newType) => this.type = newType" 传递修改的自定义事件 -->
<TodoFoot :activeTodoNums="activeTodoNums" v-model="type" @delAllDoneTodo="delAllDoneTodo"/>
</div>
</template>
<script>
import TodoContent from "./TodoContent.vue";
import TodoFoot from "./TodoFoot.vue";
import TodoHeader from "./TodoHeader.vue";
// 导入axios
import axios from "axios";
export default {
components: { TodoHeader, TodoContent, TodoFoot },
data() {
return {
todos: [], //创建一个空的用来存放数据
editid: "",
type: 'all',// 定义 all、 active、completed 的类型
};
},
// 计算属
computed: {
// 统计未完成个数
activeTodoNums() {
return this.todos.filter((todo) => !todo.done).length;
},
//
isAllTodoDone() {
return this.todos.every((todo) => todo.done);
},
showTodos(){
const {type} = this
return this.todos.filter((todo) =>
type === "all" ? true : type === "active" ? !todo.done : todo.done
);
}
},
async created() {
// 数据 事件 ... 关联完成
// 就可以修改 data了
// 在组件实例处理完所有与状态相关的选项后调用
// 获取数据库 方法一:
// axios.get('http://localhost:3008/todos').then( res => {
// console.log(res.data)
// })
// 方法二
const res = await axios.get("http://localhost:3008/todos");
this.todos = res.data;
},
methods: {
// 写的都是函数,不一定都是事件,也可能写功能
// 删除功能
del(id) {
this.todos = this.todos.filter((todo) => todo.id !== id);
},
// 添加功能
addTodo(text) {
this.todos.push({
id: new Date().getTime().toString(),
done: false,
text,
});
},
completTodo(id) {
const currentTodo = this.todos.find((todo) => todo.id === id);
currentTodo.done = !currentTodo.done;
},
changeEditId(id) {
this.editid = id;
},
// 修改
editTodo(id, text) {
const currentTodo = this.todos.find((todo) => todo.id === id);
currentTodo.text = text;
},
// 修改状态
changAllTodoStatus(newStatus) {
this.todos.forEach((todo) => {
todo.done = newStatus;
});
},
// clear completed 功能
delAllDoneTodo(){
this.todos = this.todos.filter(todo => !todo.done)
}
},
};
</script>
<style>
</style>
TodoHeader.vue
<template>
<div>
<input type="checkbox" :checked="isAllTodoDone" @change="change">
<input type="text" v-model.trim="text">
<button @click="add">添加</button>
</div>
</template>
<script>
export default {
data(){
return {
text:''
}
},
props: ['isAllTodoDone'],
methods:{
add(){
const {text} = this
if (text) {
// Date().getTime() 确保添加完成后的 id 不同
this.$emit('addTodo',text)
this.text = ""; // 添加完成后清空输入框
}
},
change(event){
this.$emit('changAllTodoStatus',event.target.checked)
}
}
}
</script>
<style>
</style>
data.json
{
"todos":[
{
"id": "yht67h9",
"text": "律师函警告",
"done": false
},
{
"id": "yh87sdy",
"text": "喜欢唱,跳,rep,篮球",
"done": true
},
{
"id": "thy52dq",
"text": "你干嘛,哎呀",
"done": false
}
]
}
Todoltem.vue
<template>
<li>
<template v-if="editid !== id">
<input type="checkbox" :checked="done" @change="change">
<span @dblclick="handleDblclick" :class="{done: done}">{{text}}</span>
<span @click="del">×</span>
</template>
<input v-else type="text" :value="text" ref="myInp" @blur="editTodo">
</li>
</template>
<script>
export default {
// 接输 text
props: ['text','id','done','editid'],
// props 是不允许直接修改的,props 是不能绑定在 v-model 上的
// 当 props 值是对象的时候 可以修改
methods: {
// 删除父组件里面的 todos 里面的某一个数据
// 做法在父组件定义好删除的操作
// 然后传递给子 组件去执行
del(){
// 父组件给子组件传递了自定义事件的话
// 那么子组件就可以使用 this.$emit('事件名',参数1,参数2) 接收并且调用
this.$emit('delTodo')
},
change() {
this.$emit('completTodo')
},
handleDblclick (){
this.$emit('changeEditId',this.id)
// 获取交点
this.$nextTick(() => {
this.$refs.myInp.focus()
})
},
// 修改
editTodo(event) {
const text = event.target.value.trim()
if(text){
this.$emit('editTodo', text)
this.$emit('changeEditId')
}
}
}
}
</script>
<style>
.done{
color: #ccc;
text-decoration: line-through;
}
</style>
TodoContent.vue
<template>
<div>
<ul v-if="todos.length">
<!-- `TodoItem.vue`引入 `TodoContent.vue` -->
<!-- 组件的循环也可以使用 v-for -->
<!-- :text="todo.text" 传递 text -->
<!-- 当父组件向子组件传递函数的时候 vue 建议使用 自定义事件来传递 -->
<!-- 在父组件内 @事件名='函数' -->
<!-- -->
<TodoItem
v-for="todo in todos"
:key="todo.id"
:editid="editid"
:text="todo.text"
:id="todo.id"
:done="todo.done"
@delTodo="() => {
$emit('delTodo', todo.id);
}
"
@completTodo="() => {
$emit('completTodo', todo.id);
}
"
@changeEditId="(id) => {
$emit('changeEditId',id)
}"
@editTodo="(text) => {
$emit('editTodo',todo.id,text)
}"
/>
<!-- 组件的 data props computed methods $emit等 -->
<!-- 在组件的 js 内可以直接使用 this.xx 服务 -->
<!-- 在组件的 template 内,可以直接访问 -->
<!-- 父组件传递自定义事件 delTodo 子组件想要继续向下传递,不能直接使用 $emit() 向下传递,因为 $emit 作用是接收并调用 -->
</ul>
</div>
</template>
<script>
import TodoItem from "./TodoItem.vue";
export default {
props: ["todos", "editid"], // 获取 父级传递过来的 todos
components: { TodoItem },
// vue组件的生命周期(组件的一生:开始、经历、结束)
// vue 组件提供的一些钩子函数,这写函数会在组件的不同阶段自动执行 都同步函数
//组件初始渲染
// beforeCreate
// created
// beforeMount
// mounted
beforeCreate() {
// 在组件实例初始化完成之后立即调用。
// 组件的数据 事件开始关联
},
// 获取后端数据
created() {},
beforeMount() {
// 开始渲染
// 在组件被挂载之前调用。
},
mounted() {
// 完成渲染 可以获取到真实 dom 节点了
// 在组件被挂载之后调用。
},
//组件页面更新
// beforeUpdate
// updated
beforeUpdate() {
// 开始修改
},
updated() {
// 修改完毕
// 可以获取更新之后的 data 已经更新之后的 真实 dom
},
//组件销毁
// beforeDestroy
// destroyed
beforeDestroy() {
// 开始销毁
},
destroyed() {
// 销毁完成
},
};
</script>
<style>
</style>