this.$nextTick(十分常用的功能)
语法:this.$nextTick(回调函数)
作用:在下一次 DOM 更新结束后执行其指定的回调
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
案例:使用 $nextTick 优化 TodoList案例,在UserItem中添加一个编辑按钮,在点击时将<span>标签变化为<input>标签,并自动获取焦点,离开焦点时,完成数据更新以及再变化为<span>标签
(可以给todo对象新增一个isEdit属性,判断是否编辑)
src/components/UserHeader.vue(无变动)
<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add" /> </div> </template> <script> // ID 生成器库 import { nanoid } from "nanoid"; export default { name: "UserHeader", data() { return { title: "", }; }, // 接收来自App的addTodo方法 // props: ["addTodo"], // 注意,这里不需要通过 props 传递数据,因此也就不需要 props 在子组件接收数据了 methods: { add() { // 校验数据 if (!this.title.trim()) return alert("输入不能为空"); // 将用户的输入包装成一个对象 const todoObj = { id: nanoid(), title: this.title, done: false }; console.log(todoObj) // 调用来自App的addTodo方法,通知App组件去添加一个对象 // this.addTodo(todoObj); // 触发UserHeader组件实例vc上的自定义事件addTodo,传递一个todoObj对象 this.$emit("addTodo", todoObj) // 清空输入 this.title = ""; }, }, }; </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>
src/components/UserList.vue(无变动)
<template> <ul class="todo-main"> <!--根据todos数据,进行遍历--> <!--<UserItem--> <!-- v-for="todoObj in todos"--> <!-- :key="todoObj.id"--> <!-- :todo="todoObj"--> <!-- :checkTodo="checkTodo"--> <!-- :deleteTodo="deleteTodo"--> <!--></UserItem>--> <!--这里通过事件总线传递数据,因此就不通过props绑定"checkTodo"和"deleteTodo"--> <UserItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" ></UserItem> </ul> </template> <script> import UserItem from "./UserItem.vue"; export default { name: "UserList", components: { UserItem, }, // 接收来自App的数据 // props: ["todos", "checkTodo",'deleteTodo'], // 通过事件总线传递数据,因此也就不用 props 接收 "checkTodo"和"deleteTodo" props: ["todos"], }; </script> <style scoped> /*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
src/components/UserItem.vue
(新增编辑功能,todo对象新增"isEdit"属性,为true是将<span>转换为<input>标签,为false再转换为<span>标签;使用$nextTick实现在下一次 DOM 更新结束后获取input框焦点)
<template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> <!--如下代码也能实现功能,但不推荐,违反修改props的原则--> <!--<input type="checkbox" v-model="todo.done" @change="handleCheck(todo.id)"/>--> <!--<span>{{todo.title}}</span>--> <span v-show="!todo.isEdit">{{todo.title}}</span> <!--编辑时的input框--> <!--input框失去焦点时,触发@blur事件--> <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputTitle" > </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> <button class="btn btn-edit" @click="handleEdit(todo)">编辑</button> </li> </template> <script> // 引入 pubsub-js库 import pubsub from "pubsub-js" export default { name: "UserItem", //声明接收对象 // props: ["todo", "checkTodo", "deleteTodo"], // 通过事件总线传递数据,因此也就不用 props 接收 "checkTodo"和"deleteTodo" props: ["todo"], // mounted() { // console.log(this.todo) // } methods: { // 勾选or取消勾选 handleCheck(id) { console.log("勾选或取消勾选,id:",id) // this.checkTodo(id); // 提供数据,触发事件 this.$bus.$emit("checkTodo", id) }, // 删除 handleDelete(id) { if (confirm("确定删除吗")) { console.log("删除,id:",id) // this.deleteTodo(id); // 提供数据,触发事件 // this.$bus.$emit("deleteTodo", id) // 提供消息(发布消息) pubsub.publish("deleteTodo", id) } }, // 编辑 handleEdit(todo){ // 首先判断,todo对象身上有没有"isEdit"属性 // hasOwnProperty(propertyName)方法是用来检测属性是否为对象的自有属性,如果是,返回true,否者false; // eslint-disable-next-line no-prototype-builtins if(todo.hasOwnProperty("isEdit")){ // 当存在"isEdit"属性时 console.log("存在\"isEdit\"属性") todo.isEdit = true }else{ // 当不存在"isEdit"属性时,这里增加一个"isEdit"属性,默认为true console.log("不存在\"isEdit\"属性") this.$set(todo,"isEdit",true) } // this.$nextTick 所指定的回调函数,会在DOM节点更新完之后再执行(重点,经常用 // ) this.$nextTick(function (){ // 获取输入框的焦点(不能直接获取焦点,这是因为当前DOM还没更新) this.$refs.inputTitle.focus() }) }, // input失去焦点时,调用handleBlur方法(这里也执行修改逻辑) handleBlur(todo,e){ // this.$set(todo,"isEdit",false) todo.isEdit = false // 如果 input框 的值为空,则返回一个警告 // console.log(!e.target.value.trim()) if(e.target.value.trim() === "") return alert("输入内容不能为空") // 通过触发一个自定义事件,进行数据更新 console.log("updateTodo",todo.id,e.target.value) this.$bus.$emit("updateTodo",todo.id,e.target.value) } }, }; </script> <style> /*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 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; } li:hover { background-color: #ddd; } li:hover button { display: block; } </style>
src/components/UserFooter.vue(无变动)
<template> <!-- v-show="total" 当total为0时,返回false,页面不显示,当total为非0时,返回true,页面显示--> <div class="todo-footer" v-show="total"> <label> <!--<input type="checkbox" :checked="isAll" @change="checkAll" />--> <!--为什么可以用v-model,这是因为绑定的是计算属性isAll,没有修改props的值--> <input type="checkbox" v-model="isAll"/> </label> <span> <span>已完成{{ doneTotal }}</span> / 全部{{ total }} </span> <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> </div> </template> <script> // import { computed } from "vue"; export default { name: "UserFooter", // props: ["todos", "checkAllTodo", "clearAllTodo"], // 注意,这里不需要通过 props 传递"checkAllTodo", "clearAllTodo"数据,因此也就不需要 props 在子组件接收数据了 props: ["todos"], methods: { // checkAll(e) { // console.log("@checkAll",e.target.checked) // this.checkAllTodo(e.target.checked); // }, clearAll() { // this.clearAllTodo(); // 触发UserFooter组件实例vc上的自定义事件clearAllTodo this.$emit("checkAllTodo") }, }, computed: { total() { return this.todos.length; }, doneTotal() { // 1.遍历列表的方法 // let i = 0 // this.todos.forEach((todo)=>{ // if(todo.done === true) i++ // }); // return i // 2.reduce()方法,对数组中的每个元素按序执行一个提供的 reducer 函数,将其结果汇总为单个返回值 // const x = this.todos.reduce((pre, current) => { // console.log("@",pre) // return pre + (current.done ? 1 : 0); // }, 0); // return x; return this.todos.reduce( (pre, current) => pre + (current.done ? 1 : 0), 0 ); }, // isAll() { // // 判断已完成 / 全部 前面的复选框够不够,取决于已完成的个数是否等于全部的个数 // return this.doneTotal === this.total && this.total > 0; // }, // 计算属性 isAll 的完整写法 isAll:{ get(){ console.log("@isAll 的 get",this.doneTotal === this.total && this.total > 0) return this.doneTotal === this.total && this.total > 0; }, set(value){ console.log("@isAll 的 set",value) // this.checkAllTodo(value) // 触发UserFooter组件实例vc上的自定义事件checkAllTodo,传递一个value数据 this.$emit("checkAllTodo", value) } } }, }; </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>
src/App.vue(无变动)
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!--动态绑定函数,在父组件定义一个函数,子组件调用该函数,父组件就可以收到子组件传过来的参数 :addTodo="addTodo"--> <!--<UserHeader :addTodo="addTodo"></UserHeader>--> <!--使用自定义事件优化,通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用v-on 或 @)--> <UserHeader @addTodo="addTodo"></UserHeader> <!--<UserList--> <!-- :todos="todos"--> <!-- :checkTodo="checkTodo"--> <!-- :deleteTodo="deleteTodo"--> <!--></UserList>--> <!--这里通过事件总线传递数据,因此就不通过props绑定"checkTodo"和"deleteTodo"--> <UserList :todos="todos"></UserList> <!--<UserFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></UserFooter>--> <!--使用自定义事件优化,通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用v-on 或 @)--> <!--这里注意,:todos="todos" 动态绑定的是一个数组数据,而不是 子组件 ===> 父组件 所用的函数--> <UserFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"></UserFooter> </div> </div> </div> </template> <script> // 引入 pubsub-js库 import pubsub from "pubsub-js" import UserHeader from "./components/UserHeader.vue"; import UserList from "./components/UserList.vue"; import UserFooter from "./components/UserFooter.vue"; export default { name: "App", components: { UserHeader, UserList, UserFooter, }, data() { return { // todos: [ // { id: "001", title: "起床", done: true }, // { id: "002", title: "洗漱", done: false }, // { id: "003", title: "睡觉", done: true }, // ], // 这里,将 todos 数据读取 localStorage 本地存储;如果初始化数据为空返回null时,则赋值一个空数组 // JSON.parse() 方法用来解析 JSON 字符串 // 在json中,||逻辑运算符 // 1.只要 || 前面为 false,不管 || 后面是 true 还是 false,都返回 || 后面的值。 // 2.只要 || 前面为 true,不管 || 后面是 true 还是 false,都返回 || 前面的值。 todos:JSON.parse(localStorage.getItem("todos")) || [] }; }, methods: { // 添加一个todo addTodo(todoObj) { console.log("我是APP组件,我收到了数据:",todoObj) // unshift() 在数组头部添加元素 this.todos.unshift(todoObj); }, // 勾选or取消勾选一个todo checkTodo(id) { // forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数 this.todos.forEach((todo) => { // 将tudo的done属性进行取反 if (todo.id === id) todo.done = !todo.done; }); }, // 编辑的数据更新 updateTodo(id,title){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.title = title }) }, // 删除一个 // deleteTodo(id) { // // 数组.filter() 实现数组的过滤,创建一个新数组, 其包含通过所提供函数实现的测试的所有元素 // // 过滤出,todo.id 不是 id 的数据 // this.todos = this.todos.filter((todo) => todo.id !== id); // }, deleteTodo(_, id) { // 使用 _占位符代替msgName变量 // 数组.filter() 实现数组的过滤,创建一个新数组, 其包含通过所提供函数实现的测试的所有元素 // 过滤出,todo.id 不是 id 的数据 this.todos = this.todos.filter((todo) => todo.id !== id); }, // 全选or全不选 checkAllTodo(done) { // forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数 this.todos.forEach((todo) => { todo.done = done; }); }, //清除所有已经完成的任务 clearAllTodo() { if(confirm("确认删除所有已完成的任务吗")){ // 数组.filter() 实现数组的过滤,创建一个新数组, 其包含通过所提供函数实现的测试的所有元素 // 过滤出,todo.done 还没完成 的数据 // this.todos = this.todos.filter((todo) => todo.done == false); this.todos = this.todos.filter((todo) => !todo.done); } }, }, // 使用监视属性,通过监视todos属性,实现 localStorage 本地存储 todos 数据 watch:{ todos:{ // 深度监视,监视对象内部值的改变 deep:true, handler(newValue){ // JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串 console.log("@@监视属性",JSON.stringify(newValue)) localStorage.setItem("todos",JSON.stringify(newValue)) } } }, // 使用生命周期,实现在组件中传递数据(给$bus绑定和解绑事件) mounted() { // 接收数据,在组件中给$bus绑定自定义事件 this.$bus.$on("checkTodo", this.checkTodo) this.$bus.$on("updateTodo", this.updateTodo) // this.$bus.$on("deleteTodo", this.deleteTodo) // 接收数据(消息订阅) this.pubId = pubsub.subscribe("deleteTodo", this.deleteTodo) }, beforeDestroy() { // 解绑事件 this.$bus.$off("checkTodo") this.$bus.$off("updateTodo") // this.$bus.$off("deleteTodo") // 解除订阅 pubsub.unsubscribe(this.pubId) } }; </script> <style> /*base*/ body { background: #fff; } .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-edit { color: #fff; background-color: skyblue; border: 1px solid #3f7e9a; margin-right: 5px; } .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>
src/main.js(无变动)
import Vue from "vue" import App from "./App.vue" // 阻止 vue 在启动时生成生产提示 Vue.config.productionTip = false new Vue({ el:"#app", render:h => h(App), // 安装全局事件总线 beforeCreate() { Vue.prototype.$bus = this } })
标签:nextTick,Vue,TodoList,border,deleteTodo,done,todo,id,todos From: https://www.cnblogs.com/REN-Murphy/p/17816570.html