vue3实现 TodoList 运用到的点 组件传值props, ref, onMounted, reactive, toRefs, watch

vue3 实现简单的todolist,
运用到的知识点: 组件传值props, ref, onMounted, reactive, toRefs, watch
实现功能如下图。
在这里插入图片描述
在追加一个全部删除按钮
在这里插入图片描述
在这里插入图片描述
在app里添加

// 全部删除
   const delAll = (id) => {
      state.todos = state.todos.filter((val) => {
        console.log(val.id, "val");
        val.id !== id;
      });
    };
  记得传值过去就行

介绍下,我这里进行了封装和父子组件传值。

在这里插入图片描述
App文件(父组件)

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <Herder :addTodo="addtodo" />
      <List :todos="todos" :deleteTodo="deleteTodo" :updataTodo="updataTodo" />
      <Footer :todos="todos" :checkAll="checkAll" :deleteAll="deleteAll" :delAll="delAll" />
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, onMounted, reactive, toRefs, watch } from "vue";
import Herder from "./components/com/headTo.vue";
import List from "./components/com/listTo.vue";
import Footer from "./components/com/foterTo.vue";
// 定义接口,约束state数据类型
import Todo from "./components/type/type";
import { readTods, saveTodos } from "./components/utils/index";
export default defineComponent({
  name: "App",
  components: { Herder, List, Footer },
  setup() {
    // 初始化数据是一个数组形式,数组中的每个数据都是对象
    const state = reactive<{ todos: Todo[] }>({
      todos: [],// 初始化为空数组
    });
    // 加载完成后在读取数据
    onMounted(() => {
      setTimeout(() => {
        state.todos = readTods(); // 此处进行了本地存储的封装,
      }, 1000);
    });
    // 添加数据事件,是在输入框中执行,就在head 头部结构中进行
    const addtodo = (todo: Todo) => {
      state.todos.unshift(todo); //头部增加
      // state.todos.push(todo);  尾部增加
    };
    // 删除数据, list 表格中进行传值
    const deleteTodo = (index: number) => {
      state.todos.splice(index, 1);
    };
    // 修改todo的isComputed属性的状态 , list 表格中进行传值
    const updataTodo = (todo: Todo, isCompleted: boolean) => {
      todo.isCompleted = isCompleted;
      console.log(todo, "todo");
    };
    // 全选,全不选。 在底部的footer中执行
    const checkAll = (isCompleted: boolean) => {
      state.todos.forEach((todo) => {
        todo.isCompleted = isCompleted;
      });
    };
    // 删除所有选中,  在底部的footer中执行
    const deleteAll = () => {
      state.todos = state.todos.filter((todo) => !todo.isCompleted);
    };
     // 全部删除
   const delAll = (id) => {
      state.todos = state.todos.filter((val) => {
        console.log(val.id, "val");
        val.id !== id;
      });
    };
    // 监听操作,若todos数组变化直接存储到缓存中
    // watch(
    //   () => state.todos,
    //   (value) => {
    //     localStorage.setItem("todos——key", JSON.stringify(value));
    //   },
    //   { deep: true }
    // );
    
    watch(() => state.todos, saveTodos, { deep: true });  // 设置缓存

    return {
      ...toRefs(state),
      addtodo,
      deleteTodo,
      updataTodo,
      checkAll,
      deleteAll,
      delAll 
    };
  },
});
</script>
<style scoped>
	.todo-container {
	  width: 600px;
	  margin: 0 auto;
	}
	.todo-container .todo-wrap {
	  padding: 10px;
	  border: 1px, solid #000;
	  border-radius: 5px;
	}
</style>

type封装就一点不在上代码,看图就行
在这里插入图片描述
utils 存储封装也很少
在这里插入图片描述
head 头部代码

	<template>
	  <div class="todo-header">
	    <input
	      type="text"
	      placeholder="输入,回车结束"
	      v-model="title"
	      @keyup.enter="add"
	    />
	    <!-- add事件,是在输入框中进行的,
	    就要推送给父组件 app ,
	    我没有运用context上下文去emit 推送(context.emit),
	    直接用了 setup(props,context)中的第一个参数props 操作的 -->
	  </div>
	</template>
	<script>
	import { defineComponent, ref } from "vue";
	export default defineComponent({
	  name: "headTo",
	  props: { 
	  // 接收父组件传递的参数
	    addTodo: { 
	      type: Function,
	      required: true,
	    },
	  },
	  setup(props) {
	    const title = ref("");
	    const add = () => {
	      // 当获取的值为非空在添加
	      const text = title.value;
	      if (!text.trim()) return; // 没有数据
	      // 有数据,就添加信息值
	      const todo = {
	        id: Date.now(),
	        title: text,
	        isCompleted: false, // 新数据是默认不选中的
	      };
	      // 调用addTodo方法
	      props.addTodo(todo);
	      // 清空文本
	      title.value = "";
	    };
	    return {
	      title,
	      add,
	    };
	  },
	});
	</script>
	<style scoped>
	.todo-header input {
	  width: 560px;
	  height: 28px;
	  font-size: 14px;
	  border: 1px salmon #ccc;
	  border-radius: 4px;
	  padding: 4px 7px;
	}
	.todo-header input:focus {
	  outline: none;
	  border-color: rgba(82, 168, 236, 0.8);
	  box-shadow: rgba(82, 168, 236, 0.6);
	}
	</style>
	此时头部操作结束。

list文件

	<template>
	  <ul class="todo-main">
	    <Item
	      v-for="(todo, index) in todos"
	      :key="todo.id"
	      :todo="todo"
	      :deleteTodo="deleteTodo"
	      :index="index"
	      :updataTodo="updataTodo"
	    />
	  </ul>
	</template>
	<script>
		import { defineComponent } from "vue";
		import Item from "./itemTo.vue"; // 因为要循环遍历,就把每一项单独拉出去做成组件 item
		export default defineComponent({
		  name: "listTo",
		  components: { Item },
		  props: ["todos", "deleteTodo", "updataTodo"], // 接收父组件app传递来的数据,在通过 list传递给item
		  setup() {
		    return {};
		  },
		});
	</script>
	<style scoped>
		.todo-mian {
		  margin-left: 0px;
		  border: 1px solid #ddd;
		  border-radius: 2px;
		  padding: 0px;
		}
		.todo-empty {
		  height: 40px;
		  line-height: 40px;
		  border-radius: 2px;
		  border: 1px solid #ddd;
		  padding-left: 5px;
		  margin-top: 10px;
		}
	</style>

Item 文件

<template>
<!-- 鼠标移入移除时事件和颜色改变 -->
  <li
    @mouseenter="mouseHander(true)"
    @mouseleave="mouseHander(false)"
    :style="{ backgroundColor: bgColor, color: whColor }"
  >
    <label>
      <input type="checkbox" v-model="isCompleted" />
      <span>{{ todo.title }}</span>
    </label>
    <!-- 删除按钮操作 -->
    <button
      class="btn btn-danger"
      style="display: none"
      v-show="isShow"
      @click="delTodo"
    >
      删除
    </button>
  </li>
</template>
<script>
	import { computed, defineComponent, ref } from "vue";
	export default defineComponent({
	  name: "itemTo",
	  props: {
	  // 父组件list传递的
	    todo: {
	      type: Object,
	      required: true,
	    },
	    deleteTodo: {
	      type: Function,
	      required: true,
	    },
	    index: {
	      type: Number,
	      required: true,
	    },
	    updataTodo: {
	      type: Function,
	      required: true,
	    },
	  },
	  setup(props) {
	    const bgColor = ref("white");
	    const whColor = ref("black");
	    const isShow = ref(false);
	    // 鼠标进入和离开的函数,flag true/false
	    const mouseHander = (flag) => {
	      if (flag) {
	        bgColor.value = "pink";
	        whColor.value = "green";
	        isShow.value = true;
	      } else {
	        bgColor.value = "white";
	        whColor.value = "black";
	        isShow.value = false;
	      }
	    };
	    // 删除数据方法
	    const delTodo = () => {
	      if (window.confirm("删除吗")) {
	        props.deleteTodo(props.index);
	      }
	    };
	    // 计算属性的方式,操作选中状态
	    const isCompleted = computed({
	      get() {
	        // 查询到传递过来的todo对象里面的数据的状态
	        return props.todo.isCompleted;
	      },
	      set(val) {
	        console.log(val);
	        props.updataTodo(props.todo, val);
	      },
	    });
	    return {
	      mouseHander,
	      bgColor,
	      whColor,
	      isShow,
	      delTodo,
	      isCompleted,
	    };
	  },
	});
</script>
<style scoped>
	li label li input {
	  vertical-align: middle;
	  margin-right: 6px;
	  position: relative;
	  top: -1px;
	}
	li button {
	  float: right;
	}
	li:before {
	  content: initial;
	}
	li:last-child {
	  border-bottom: none;
	}
</style>

底部的 footer 文件

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox" v-model="isCkeck" />
    </label>
    <span>
      <span>已完成{{ count }}</span> / 全部{{ todos.length }}
    </span >
    <button class="btn btn-danger" @click="deleteAll">清除完成任务项</button>
  </div>
</template>
<script>
import { defineComponent, computed } from "vue";
export default defineComponent({
  name: "foterTo",
  props: {
    todos: {
      type: Array,
      required: true,
    },
    checkAll: {
      type: Boolean,
      required: true,
    },
    deleteAll: {
      type: Function,
      required: true,
    },
  },
  setup(props) {
    console.log(props);
    // 已完的成计算属性
    const count = computed(() => {
      return props.todos.reduce(
        (pre, todo, index) => pre + (todo.isCompleted ? 1 : 0),
        0
      );
    });
    // 全选、全不选计算属性
    const isCkeck = computed({
      get() {
        return count.value > 0 && props.todos.length === count.value;
      },
      set(val) {
        props.checkAll(val);
        return;
      },
    });

    return {
      count,
      isCkeck,
    };
  },
});
</script>
<style scoped>
	.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>

就这样结束了。
如果不想这样来回的组件封装。在一个文件中直接展示,可以看下个文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值