基础进阶使用 Vue 组件拆分 实现经典例子:TodoMV

本文通过实例演示如何使用Vue.js来实现经典的TodoMVC项目,详细讲解了Vue组件的拆分、生命周期、数据绑定以及父子组件间的交互。包括创建Todo组件、引入数据、实现增删改查功能,以及使用axios导入后端数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

TodoMVC是一个示例项目,它使用目前流行的不同JavaScript框架的来实现同一个Demo,来帮助你熟悉和选择最合适的前端框架。学习框架最直接有效的方式就是上手练习,接下来我们将用Vue.js来完成TodoMVC的示例。
基础进阶:使用生命周期实现
官网地址:https://todomvc.com/


在这里插入图片描述

一、准备

在 Vscode 软件在打开集成终端

  • 输入 vue cerate 项目名 创建项目
  • 输入cd 项目名 进入创建好的项目内
  • 输入 npm run serve启动项目
  • 创建 TodoDemo.vueTodoContent.vueTodoFoot.vueTodoHeader.vueTodoItem.vue
  • TodoDemo.vue引入 App.vue
  • TodoItem.vue引入 TodoContent.vue
  • TodoContent.vueTodoFoot.vueTodoHeader.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 节点了
      • 在组件被挂载之后调用。
  • 组件页面更新
    • beforeUpdate
      • 开始修改
    • updated
      • 修改完毕
      • 获取更新之后的 data 已经更新之后的 真实 dom
  • 组件销毁
    • beforeDestroy
      • 开始销毁
    • destroyed
      • 销毁完毕

四、功能实现

实现功能前需要:

  • 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"
<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>
  • 技巧
    • templatescript 内使用 组件内的
      • data
      • props
      • methods
      • coumputed
      • 的时候,区别是前者不加 this ,后者需要加 this
  • 组件间的交互
    • props 父子交互
    • 自定义事件 父子交互
    • 共享数据 兄弟交互
  • sync 修饰符的做用是:
    • 1.将 type 传递下去 名字也加 type
    • 2.定义了一个赋值修改 type 的方法叫 update:type当作自定义事件传递下去
    • 写法: :type=”type“ @update:type="(newType) => this.type = newType"
  • 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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fvcvv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值