初学Vue(全家桶):todoList案例进阶版-组件自定义事件实现

初学Vue

todoList案例进阶版-组件自定义事件实现组件间通信

简介

其他相关文章

使用组件自定义事件实现组件间通信时并不需要使用props接收数据,通过$emit可以直接操作父组件中组件标签绑定的自定义事件并实现数据传递,从而实现子组件数据传递给父组件。

有如下几个组件,父组件App,其下有子组件MyHeader.vue,MyList.vue,MyItem.vue,MyFooter.vue

页面效果

请添加图片描述

具体代码

  • App.vue
<template>
  <div id="app">
    <div class="todo-container">
      <div class="todo-wrap">
        <!-- 输入框部分 -->
        <!-- 给组件标签绑定自定义事件-->
        <MyHeader @addTodo="addTodo"></MyHeader>
        <!-- /输入框部分 -->

        <!-- 列表部分 -->
        <MyList 
          :todos="todos" 
          :checkTodo="checkTodo" 
          :deleteTodo="deleteTodo"
        ></MyList>
        <!-- /列表部分 -->

        <!-- 底部部分 -->
        <MyFooter
          :todos="todos"
          @checkAllTodo="checkAllTodo"
          @clearAllTodo="clearAllTodo"
        ></MyFooter>
        <!-- /底部部分 -->
      </div>
    </div>
  </div>
</template>

<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";

export default {
      
      
  name: "App",
  components: {
      
      
    MyHeader,
    MyFooter,
    MyList,
  },
  data() {
      
      
    return {
      
      
      todos:JSON.parse(localStorage.getItem("todos")) || [],
    };
  },
  methods:{
      
      
    // 添加一个列表事项
    addTodo(e){
      
      
      // console.log("我是App组件,我收到了数据",x)
      this.todos.unshift(e)
    },
    // 勾选or取消勾选todo事项
    checkTodo(id){
      
      
      // 拿到todos中的每一项,找到对应id的那一项
      this.todos.forEach((todo)=>{
      
      
        if(todo.id === id)
          todo.done = !todo.done
      })
    },
    // 点击删除一个todo
    deleteTodo(id){
      
      
      this.todos = this.todos.filter((todo)=>{
      
      
        return todo.id !== id
      })
    },
    // 控制全选或全不选
    checkAllTodo(done){
      
      
      this.todos.forEach((todo)=>{
      
      
        todo.done = done;
      })
    },
    // 清除已经完成的事项
    clearAllTodo(){
      
      
      this.todos = this.todos.filter((todo)=>{
      
      
        return !todo.done
      })
    }
  },
  watch:{
      
      
    // 监视todos,当todos发生改变时,触发
    todos:{
      
      
      handler(value){
      
      
        localStorage.setItem("todos",JSON.stringify(value))
      }
    }
  }
}
</script>

<style>
/* 整体样式 */
body {
      
      
  background: rgba(230, 241, 245, 0.816);
}

.btn {
      
      
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}

.btn-danger {
      
      
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}

.btn-danger:hover {
      
      
  color: #fff;
  background-color: #bd362f;
}

.btn:focus {
      
      
  outline: none;
}

.todo-container {
      
      
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
      
      
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>
  • Header.vue
<template>
  <div>
    <div class="todo-header">
      <input type="text" placeholder="请输入你的任务名称,回车键确认" 
        @keyup.enter="add"
      />
    </div>
  </div>
</template>

<script>
import {
      
      nanoid} from "nanoid"
export default {
      
      
  name: "MyHeader",
  methods:{
      
      
    add(event){
      
      
      // 如果输入为空,则结束方法,不添加任何数据
      if(!event.target.value){
      
      
        return alert("输入不能为空")
      }
      // 使用能生成唯一标识的库nanoid或者uuid生成id,这里用nanoid(轻量级的uuid)
      // 将用户的输入包装成一个todo对象
      const todoObj = {
      
      id:nanoid(),title:event.target.value,done:false}
      // 通过$emit操作父组件组件标签上绑定的事件,并传入一个参数todoObj
      this.$emit("addTodo",todoObj)
      event.target.value='' // 回车后清空输入框中的内容
    }
  },
};
</script>

<style scoped>
/*header*/
.todo-header input {
      
      
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header input:focus {
      
      
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
    0 0 8px rgba(82, 168, 236, 0.6);
}
</style>
  • MyList.vue
<template>
  <div>
    <ul ul="todo-main">
        <!-- 显示todos数组中所有的事项,用id作为key -->
      <MyItem 
      v-for="todoObj in todos" 
      :key="todoObj.id"
      :todo="todoObj"
      :checkTodo="checkTodo" 
      :deleteTodo="deleteTodo"
      ></MyItem>
    </ul>
  </div>
</template>

<script>
import MyItem from "./MyItem.vue";

export default {
      
      
  name: "MyList",
  components: {
      
       MyItem },
  props:['todos','checkTodo','deleteTodo']
};
</script>

<style scoped>
/*main*/
.todo-main {
      
      
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}
</style>
  • MyItem.vue
<template>
  <div>
    <li>
      <label>
        <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
        <span>{
   
   {todo.title}}</span>
      </label>
      <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    </li>
  </div>
</template>

<script>
export default {
      
      
  name: "MyItem",
  props:['todo','checkTodo','deleteTodo'], // 这里todo是个对象,由MyList传过来的,存储了id、title、done属性
  methods:{
      
      
    handleCheck(id){
      
      
      // 通知App组件将对应todo对象的done值取反
      this.checkTodo(id)
    },
    // 删除事项
    handleDelete(id){
      
      
      if(confirm("确定删除吗?")){
      
      
        // 通知App删除数据
        this.deleteTodo(id)
      }
    }
  }
};
</script>

<style scoped>
/*item*/
li {
      
      
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
      
      
  float: left;
  cursor: pointer;
}

li:hover {
      
      
  background-color: rgb(226, 219, 219);
  cursor: pointer;
}

li:hover button{
      
      
  display:block
}

li label li input {
      
      
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
      
      
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
      
      
  content: initial;
}

li:last-child {
      
      
  border-bottom: none;
}
</style>
  • MyFooter.vue
<template>
  <div>
      <!--当列表中没有事项时,v-show用于隐藏Footer部分-->
    <div class="todo-footer" v-show="total"> 
      <label>
        <input type="checkbox" :checked="isAll" @change="checkAll"/>
      </label>
      <span> <span>已完成{
   
   {doneTotal}}</span>/ 全部{
   
   {total}}</span>
      <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
  </div>
</template>

<script>
export default {
      
      
  name: "MyFooter",
  props:['todos'],
  computed:{
      
      
    total(){
      
      
      return this.todos.length
    },
    doneTotal(){
      
      
      // 高端一点的方法
      // return this.todos.reduce((pre,current)=>{
      
      
      //   return pre + (current.done ? 1 : 0)
      // },0)

      // 普通的方法
      let i=0
      this.todos.forEach((todo)=>{
      
      
        if(todo.done) i++
      })
      return i
    },
    isAll:{
      
      
      // 看全选框是否勾选
      get(){
      
      
        return this.doneTotal === this.total && this.total>0
      },
      // isAll被修改时set被调用
      set(value){
      
      
        // this.checkAllTodo(value)
        this.$emit("checkAllTodo",value);
      }
    }
  },
  methods:{
      
      
    // 全选或取消全选
    checkAll(event){
      
      
      // this.checkAllTodo(event.target.checked)
      this.$emit("checkAllTodo",event.target.checked)
    },
    // 清除已完成事项
    // clearAll(){
      
      
    //   this.clearAllTodo()
    // }
    clearAll(){
      
      
      this.$emit('clearAllTodo')
    }
  }
}
</script>

<style scoped>
/*footer*/
.todo-footer {
      
      
  height: 40px;
  line-height: 40px;
  padding-left: 6px;
  margin-top: 5px;
}

.todo-footer label {
      
      
  display: inline-block;
  margin-right: 20px;
  cursor: pointer;
}

.todo-footer label input {
      
      
  position: relative;
  top: -1px;
  vertical-align: middle;
  margin-right: 5px;
}

.todo-footer button {
      
      
  float: right;
  margin-top: 5px;
}
</style>

说明:App.vue和myList.vue、MyItem.vue组件间的关系是“爷父孙”的关系,使用自定义事件实现组件间通信是不可能的,因为myItem.vue中的数据要通过myList间接传递给App.vue,中间必须借助props实现。

猜你喜欢

转载自blog.csdn.net/lalala_dxf/article/details/125162840