todoList组件化编码流程
所需依赖项:nanoid 生成唯一id,使用npm下载
1.拆分页面结构
├─todoList --------------------------------- // 页面文件夹
│ ├─components ---------------------- // 组件文件夹
│ ├ ├─j-head --------------------- // 头部
│ ├ ├─j-list ----------------------- // 列表
│ ├ ├─j-item --------------------- // 子项
│ ├ ├─j-footer ------------------- // 底部
│ ├─index.vue ------------------------ // 父组件页面
2.页面代码
index.vue
<template>
  <div>
    <div class="todo-list-box">
      <j-head @addTodo="addTodo"></j-head>
      <j-list :todoList="todoList" @delTodo="delTodo" @selectTodo="selectTodo"></j-list>
      <j-footer :todoList="todoList" @delComplete="delComplete" @isAllCheck="isAllCheck"></j-footer>
    </div>
  </div>
</template>
<script>
import jHead from './j-head';
import jList from './j-list';
import jFooter from './j-footer';
import { nanoid } from 'nanoid';
export default {
  components: { jHead, jList, jFooter },
  data() {
    return {
      todoList: [
        {
          id: nanoid(),
          title: '睡觉',
          select: true
        },
        {
          id: nanoid(),
          title: '吃饭',
          select: false
        },
        {
          id: nanoid(),
          title: '学习',
          select: false
        }
      ]
    };
  },
  methods: {
    /** 添加事件 */
    addTodo(obj) {
      this.todoList.unshift(obj);
    },
    /** 删除事件 */
    delTodo(id) {
      if (!confirm('确定删除吗?')) return;
      this.todoList = this.todoList.filter((item) => item.id != id);
    },
    /** 勾选事件 */
    selectTodo(obj) {
      this.todoList.filter((item) => item.id === obj.id)[0].select = !obj.select;
    },
    /** 删除已完成事件 */
    delComplete() {
      this.todoList = this.todoList.filter((item) => !item.select);
    },
    /** 是否全选 */
    isAllCheck(is) {
      this.todoList.forEach((item) => (item.select = is));
    }
  }
};
</script>
<style lang="less" scoped>
.todo-list-box {
  width: 95vw;
  padding: 10px;
  margin: 15px auto;
  border: 1px solid #eee;
  border-radius: 5px;
}
</style>
j-head.vue
<template>
  <div class="j-head-box">
    <input type="text" @keyup.enter="add" />
  </div>
</template>
<script>
import { nanoid } from 'nanoid';
export default {
  methods: {
    /** 添加待办事项 */
    add(e) {
      if (!e.target.value.trim()) return alert('请输入事件名');
      const todoObj = { id: nanoid(), title: e.target.value, select: false };
      this.$emit('addTodo', todoObj);
      e.target.value = '';
    }
  }
};
</script>
<style lang="less" scoped>
.j-head-box {
  width: 100%;
  height: 30px;
  input {
    width: 100%;
    height: 100%;
    border-radius: 5px;
    border: 1px solid #eee;
  }
}
</style>
j-list.vue
<template>
  <div class="j-list-box">
    <template v-for="item in todoList">
      <j-item :key="item.id" :item="item" v-on="$listeners"></j-item>
    </template>
  </div>
</template>
<script>
import jItem from './j-item.vue';
export default {
  components: { jItem },
  props: {
    todoList: Array
  }
};
</script>
<style lnag="less" scoped>
.j-list-box {
  border: 1px solid #eee;
  margin: 10px 0;
  border-radius: 3px;
  border-top: none;
}
</style>
j-item.vue
<template>
  <div class="j-item-box">
    <label>
      <input type="checkbox" :checked="item.select" @change="selectTodo(item)" />
      <span>{{ item.title }}</span>
    </label>
    <button @click="delTodo(item.id)">删除</button>
  </div>
</template>
<script>
export default {
  props: {
    item: Object
  },
  methods: {
    /** 勾选事件 */
    selectTodo(item) {
      this.$emit('selectTodo', item);
    },
    /** 删除事件 */
    delTodo(id) {
      this.$emit('delTodo', id);
    }
  }
};
</script>
<style lang="less" scoped>
.j-item-box {
  display: flex;
  align-items: center;
  font-size: 14px;
  padding: 10px 5px;
  border-top: 1px solid #eee;
  label {
    flex: 1;
    display: flex;
    align-items: center;
  }
  span {
    flex: 1;
    margin-left: 5px;
  }
  button {
    color: white;
    font-size: 12px;
    margin-left: auto;
    background-color: red;
    border: none;
    border-radius: 3px;
    padding: 0 4px;
  }
}
</style>
j-footer.vue
<template>
  <div class="j-footer-box">
    <input type="checkbox" v-model="isAll" />
    <span>已完成{{ completeCount }}/全部{{ todoList.length }}</span>
    <button @click="delComplete">清除全部已完成任务</button>
  </div>
</template>
<script>
export default {
  props: {
    todoList: Array
  },
  computed: {
    /** 计算已完成事件个数 */
    completeCount() {
      return this.todoList.reduce((pre, item) => (item.select ? pre + 1 : pre), 0);
    },
    /** 是否全选 */
    isAll: {
      get() {
        return this.todoList.length === this.completeCount && this.todoList.length > 0;
      },
      set(value) {
        this.$emit('isAllCheck', value);
      }
    }
  },
  methods: {
    /** 清除已完成事项 */
    delComplete() {
      this.$emit('delComplete');
    }
  }
};
</script>
<style lang="less" scoped>
.j-footer-box {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 5px;
  span {
    flex: 1;
    margin-left: 30px;
  }
  button {
    padding: 2px 5px;
    border: none;
    color: #fff;
    border-radius: 3px;
    background-color: red;
  }
}
</style>
3.涉及知识点
computed:vue中计算计算函数
var vm = new Vue({
        data: { a: 1, b: 1 },
        computed: {
           /** 
           * 仅读取 
           * 一般使用此方法
           * */
          sumB() {
            return this.b + 7;
          },
          /**
           * 读取和设置
           * 特殊情况使用例:v-model使用计算函数绑定时
           * */
          sumA: {
            /** 读取 */
            get() {
              return this.a + 1;
            },
            /** 设置 */
            set(v) {
              this.a = v - 1;
            },
          },
        },
      });
vm.sumB; // sumB = 8
vm.Suma; // 调用get方法结果为 Suma = 2
vm.sumA = 3; // 调用set方法 v = 3, a = 2, sumA = 3this.$emit(@event,$event):绑定一个自定义事件,当这行代码执行时将参数传给父组件。
参数说明
| 参数名 | 说明 | 类型 | 
| @evnet | 父组件接收的方法名 | String | 
| $event | 父组件接收的参数 | / | 
$listeners:是一个对象,内容为组件上的所有监听器










