Vue3.x 实现 todoList
1、前言
如果你对 vue3 的基础知识还很陌生,推荐先去学习一下 vue 基础
内容 | 参考链接 |
Vue2.x全家桶 | Vue2.x全家桶参考链接 |
Vue3.x的基本使用 | Vue3.x基本使用参考链接 |
- 如果你 刚学完 vue3 基础知识,想检查一下自己的学习成果
- 如果你 已学完 vue3 基础知识,想快速回顾复习
- 如果你 已精通 vue3 基础知识,想做个小案例
- 那不妨看完这篇文章,我保证你一定会有收获的!
2、项目演示(一睹为快)
Vue3.x_任务清单
3、涉及知识点
麻雀虽小,五脏俱全,接下来开始我们的项目之旅吧~~
- Vue3.x基础:插值语法,常用指令,键盘事件,列表渲染,计算属性,生命周期
- Vue3.x进阶:props(父传子),自定义事件(任意组件间通信)
- Vuex4.x:状态管理库的使用
- Vue-router4.x:使用路由进行页面跳转
备注:
- 任意组件间的通信方式有很多种(全局事件总线,消息订阅预发布…),熟练掌握一种即可(推荐自定义事件,配置简单,容易理解)
- 本文是 vue 基础的练习项目,也涉及 vue 周边(Vuex,Vue-Router)
4、项目详情
main.js
- 导入 store 和 router,并且使用
import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' createApp(App).use(store).use(router).mount('#app')
./router/index.js
- 配置路由
- 直接导入 VS 按需导入(节约性能)
- 使用了 history 路由模式
import { createRouter, createWebHistory } from 'vue-router' // 直接引入 import Start from '../views/Start.vue' const routes = [ { path: '/', name: 'Start', component: Start }, { path: '/home', name: 'Home', // 按需引入,节约性能 component: () => import('../views/Home.vue') } ] // 创建路由对象 const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
./store/index.js
- state 中定义初始化数据
- mutations 中定义方法
import { createStore } from 'vuex' export default createStore({ // 定义初始化状态 state: { list: [ { title: "吃饭", complete: false, }, { title: "睡觉", complete: false, }, { title: "敲代码", complete: true, }, ] }, // 同步修改 state 都是方法 // 第一个参数 state 第二个参数是需要修改的值 mutations: { // 添加任务 addTodo(state, payload) { state.list.push(payload) }, // 删除任务 delTodo(state, payload) { state.list.splice(payload, 1) }, // 清除已完成 clear(state, payload) { // 把过滤之后的数组传进来 state.list = payload } }, // 异步提交 mutation // 第一个参数是 store 第二个参数是修改的值 actions: { }, // 模块化 modules: {} })
App.vue 组件
- 做呈现的组件
- <router-view /> 呈现内容
<template> <router-view/> </template> <style lang="scss"> * { margin: 0; padding: 0; } </style>
Start.vue 组件
- 初始化页面
- 点击开启任务,跳转到任务页面
<template> <div class="title"> <h1>欢迎来到前端杂货铺</h1> <button @click="start">开始任务</button> </div> </template> <script> import { ref } from "vue"; import { useRouter } from "vue-router"; export default { name: "Start", setup() { // router 是全局路由对象 let router = useRouter(); let name = ref(10); // 点击进行路由跳转 let start = () => { router.push({ name: "Home", params: { name: name.value, }, }); }; return { start, }; }, }; </script> <style lang="scss" scoped> .title { color: orange; text-align: center; margin-top: 20%; } button { margin-top: 20px; width: 100px; height: 50px; background: skyblue; color: white; font-weight: bold; font-size: 15px; cursor: pointer; } button:hover { font-weight: bold; background: white; color: skyblue; cursor: pointer; } </style>
Home.vue 组件
- 其他组件 表演的舞台
- 传递数据
- 自定义事件,进行组件间通信
<template> <div class="container"> <nav-header @add="add"></nav-header> <nav-main :list="list" @del="del"></nav-main> <nav-footer :list="list" @clear="clear"></nav-footer> </div> </template> <script> import NavHeader from "@/components/navHeader/NavHeader"; import NavMain from "@/components/navMain/NavMain"; import NavFooter from "@/components/navFooter/NavFooter"; import { ref, computed } from "vue"; import { useStore } from "vuex"; export default { name: "Home", // 接收父组件的数据 props: {}, // 定义子组件 components: { NavHeader, NavMain, NavFooter, }, // 接收的数据,上下文 setup(props, ctx) { let store = useStore(); let list = computed(() => { return store.state.list; }); let value = ref(""); // 添加任务 let add = (val) => { value.value = val; // 任务存在 不能重复添加 let flag = true; list.value.map((item) => { if (item.title === value.value) { // 有重复任务 flag = false; alert("任务已存在"); } }); // 没有重复任务 if (flag == true) { // 调用 mutation store.commit("addTodo", { title: value.value, complete: false, }); } }; // 删除任务 let del = (val) => { // 调用删除的 mutation store.commit('delTodo', val) console.log(val); } // 清除已完成 let clear = (val) => { store.commit('clear', val) } return { add, list, del, clear }; }, }; </script>
NavHeader.vue 组件
- 头部组件(输入框)
- 输入任务按下回车进行任务的添加
- emit,使用分发的事务
<template> <div> <div class="container"> <input type="text" placeholder="请输入任务名称" v-model="value" @keyup.enter="enter" /> </div> </div> </template> <script> import { ref } from "vue"; export default { name: "navHeader", // 接收的数据,上下文 setup(props, ctx) { let value = ref(""); // 按回车键确认 let enter = () => { // 把输入框的内容传递给父组件 ctx.emit("add", value.value); // 清空输入框 value.value = ""; }; return { value, enter, }; }, }; </script> <style lang="scss" scoped> .container { text-align: center; margin-top: 220px; } .container input { height: 25px; width: 200px; margin-bottom: 10px; } </style>
NavMain.vue 组件
- props 接收 list 数据
- v-for 遍历出来内容
- 使用条件判断做呈现
<template> <div class="container"> <div v-if="list.length > 0"> <div v-for="(item, index) in list" :key="index"> <div class="item"> <input type="checkbox" v-model="item.complete" /> {{ item.title }} <button class="del" @click="del(item, index)">删除</button> </div> </div> </div> <div v-else> 暂无任务 </div> </div> </template> <script> export default { name: "navMain", props: { list: { type: Array, required: true, }, }, // 分发事件的属性名 emits: ["del"], setup(props, ctx) { // 删除任务 let del = (item, index) => { ctx.emit("del", index); console.log(index, item); }; return { del, }; }, }; </script> <style lang="scss" scoped> .container { margin: auto; border: 2px solid #ccc; width: 200px; margin-bottom: 20px; } .item { height: 35px; line-height: 35px; position: relative; width: 200px; button { cursor: pointer; position: absolute; right: 4px; top: 6px; display: none; z-index: 99; } &:hover { background: #ddd; button { display: block; } } } </style>
NavFooter.vue 组件
- 过滤出已完成的任务,获取到已完成任务的个数
- 过滤出未完成的任务,清除的时候保留未完成的任务
<template> <div class="container"> 已完成 {{ isCompelete }} / 全部 {{ list.length }} <span v-if="isCompelete" class="btn"> <button @click="clear">清除已完成</button> </span> </div> </template> <script> import { computed } from "vue"; export default { name: "navFooter", props: { list: { type: Array, required: true, }, }, setup(props, ctx) { let isCompelete = computed(() => { // 过滤已完成 let arr = props.list.filter((item) => { return item.complete; }); return arr.length; }); // 清除已完成 let clear = () => { // 过滤未完成的 let arr = props.list.filter((item) => { return item.complete === false; }); ctx.emit("clear", arr); console.log("clear"); }; return { isCompelete, clear, }; }, }; </script> <style lang="scss" scoped> .container { text-align: center; } </style>
至此,此项目就实现了,如果什么问题,欢迎评论区或私信留言,看到定会第一时间解决!
5、写在最后的话
如果你是 看完全篇 阅读到了这里,我相信你一定是有收获的!
那么下面不妨打开自己的电脑,启动自己的编译器,来跟着做 / 自己做一遍吧!