Day 83 VUE——组件、插槽、生命周期
VUE——组件
局部组件
创建子组件、挂载子组件、调用子组件
注意:在组件中,这个 data 必须是一个函数,返回一个对象。
<div id="app"> <!-- 3、使用组件 --> <app></app> </div> <script src="./vue.js"></script> <script> // 组件 // 创建子组件、挂载子组件、调用子组件 ///1、创建组件 const app={ data(){ return { msg:'这是第一个局部组件' } }, template: ` <div> <h2>{{ msg }}</h2> <button @click='handClick'>改变</button> </div> `, methods:{ handClick(){ this.msg = '修改局部组件' } } } new Vue({ el:'app', data:{ }, // 2、挂载组件 components:{ app } }) </script>
全局组件
// 全局组件 // 每个全局组件都需要定义一次 Vue.component // 全局变量的使用:可以在任意 template 中进行引入。 // 引入方式:<Vheader></Vheader> or <Vconter /> // 使用手法:在任意 template 中引入 // 第一个参数:组件名(Vheader) // 第二个参数:配置 Vue.component( 'Vheader',{ template:` <div> 我是导航组件 </div>` } ) Vue.component( 'Vsider',{ template:` <div> 我是侧边栏组件 </div>` } )
组件通信
父传子
父传子:通过 props 来进行通信
1.在子组件中声明 props 接收在父组件挂载的属性
2.可以在子组件的 template 中任意使用
3.在父组件绑定自定义的属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <parent></parent> </div> <script src="./vue.js"></script> <script> // 父传子:通过 props 来进行通信 // 1.在子组件中声明 props 接收在父组件挂载的属性 // 2.可以在子组件的 template 中任意使用 // 3.在父组件绑定自定义的属性 // 全局组件为子组件 Vue.component( 'children',{ template:` <div> <h3>这是子组件</h3> <h3>{{ childData }}</h3> </div> `, props:['childData'] } ) // 局部组件为父组件 const parent = { data(){ return { msg:'这是父组件' } }, template:` <div> <children :childData='msg'></children> </div> `, } new Vue({ el:'#app', data:{ }, components:{ parent } }) </script> </body> </html>
子传父
在父组件中,子组件上绑定自定义事件
在子组件中,触发原生的事件,在事件函数通过 this.$emit 触发自定义事件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <parent></parent> </div> <script src="./vue.js"></script> <script> // 子传父 // 在父组件中,子组件上绑定自定义事件 // 在子组件中,触发原生的事件,在事件函数通过 this.$emit 触发自定义事件 // 全局组件为子组件 Vue.component( 'child',{ template:` <div> <h3>这是子组件</h3> <input type="text" @input = 'handleInput'> </div> `, methods:{ handleInput(e){ const val = e.target.value this.$emit('inputHand',val) } } } ) // 局部组件为父组件 const parent = { data(){ return { msg:'这是父组件', newVal:'' } }, methods:{ input(newVal){ this.newVal = newVal } }, template:` <div> <h3>{{ msg }}</h3> <p>显示:{{ newVal }}</p> <child @inputHand='input'></child> </div> ` } new Vue({ el:'#app', data:{ }, components:{ parent } }) </script> </body> </html>
平行组件通信
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <!-- <app2></app2> <app1></app1> --> <App></App> </div> <script src="./vue.js"></script> <script> // 平行组件 const bus = new Vue(); Vue.component('app1',{ data(){ return{ msg:0 } }, template:` <div> <h3>msg:{{ msg }}</h3> </div> `, created(){ // $on 绑定事件 bus.$on('add',(n)=>{ this.msg += n }) } }) Vue.component('app2',{ data(){ return{ } }, template:` <div> <button @click='handClick'>+1</button> </div> `, methods:{ handClick(){ bus.$emit('add',1); } } }) const App = { data(){ return{ } }, template:` <div> <app2></app2> <app1></app1> </div> `, } new Vue({ el:'#app', data:{ }, components:{ App } }) </script> </body> </html>
组件通信其它方式(provide和inject)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <App></App> </div> <script src="./vue.js"></script> <script> // provide 提供变量 // inject 注入变量 // provide 在父组件中提供变量,然后在子组件中使用 inject 注入变量,无论组件嵌套有多深 Vue.component('app1',{ data(){ return{ } }, inject:['msg'], template:` <div> <h3>{{ msg }}</h3> </div> `, created(){ console.log(this.msg) // 还可以通过 $pareent $chilred 的方式来获取父组件、子组件的值 console.log(this.$parent.$parent) console.log(this.$children) } }) const App = { data(){ return{ } }, template:` <div> <app1></app1> </div> `, provide(){ return{ msg:'父组件中的数据' } } } new Vue({ el:'#app', data:{ }, components:{ App } }) </script> </body> </html>
组件异步加载
html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <App></App> </div> <script src="./vue.js"></script> <script type="module"> const App = { data(){ return{ isShow:false } }, methods:{ handClick(){ this.isShow = !this.isShow } }, components:{ Test:()=>import('./Test.js') }, template:` <div> <Test v-if="isShow"></Test> <button @click='handClick'>异步加载</button> </div> ` } new Vue({ el:'#app', data:{ }, components:{ App } }) </script> </body> </html>
js文件
export default { data(){ return{ msg:'亦双弓' } }, template:` <div> <h3>{{msg}}</h3> </div> ` }
VUE——插槽
匿名插槽
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <App></App> </div> <script src="./vue.js"></script> <script> Vue.component('MBtn',{ template:` <button> <slot></slot> </button> ` }) const App = { data(){ return{ title:'父组件' } }, template:` <div> <m-btn>登录</m-btn> <m-btn><a href='#'>注册</a></m-btn> </div> ` } new Vue({ el:'#app', data:{ }, components:{ App } }) </script> </body> </html>
注意:匿名插槽 <slot></slot> 只能出现一次,如果出现多次,则会出造成以下情况。
<button>
<slot></slot>
<slot></slot>
<slot></slot>
</button>
具名插槽
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <App></App> </div> <script src="./vue.js"></script> <script> Vue.component('MBtn',{ // 插入组件的顺序和命名时的顺序无关 template:` <button> <slot name = 'login'></slot> <slot name = 'register'></slot> <slot name = 'submit'></slot> </button> ` }) const App = { data(){ return{ title:'父组件' } }, template:` <div> <m-btn> <template slot = 'login'> 登录 </template> </m-btn> <m-btn> <template slot = 'submit'> 提交 </template> </m-btn> <m-btn> <template slot = 'register'> 注册 </template> </m-btn> </div> ` } new Vue({ el:'#app', data:{ }, components:{ App } }) </script> </body> </html>
注意:插入组件的顺序和命名时的顺序无关
作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id='app'> <App></App> </div> <script src="./vue.js"></script> <script> // 要求: // 1.之前数据格式和应用接口不变,正常显示 // 2.新功能模块:增加对勾 const todoList = { data(){ return{ } }, props:{ todos: Array, defaultValue:[] }, template:` <ul> <li v-for='item in todos' :key='item.id'> <slot :itemValue = 'item'> </slot> {{ item.title }} </li> </ul> ` } const App = { data(){ return{ todoList:[ { title:'你的玫瑰', isComplate:true, id:1 }, { title:'独善其身', isComplate:false, id:2 }, { title:'失忆的金鱼', isComplate:false, id:3 }, { title:'浪费', isComplate:true, id:4 }, ] } }, components:{ todoList }, template:` <todoList :todos="todoList"> <template v-slot='data'> <input type="checkbox" v-model="data.itemValue.isComplate" /> </template> </todoList> `, } new Vue({ el:'#app', data:{ }, components:{ App } }) </script> </body> </html>
生命周期
生命周期钩子
beforeCreate
created 重要
beforeMount
mounted 重要
beforeUpdate
updated 重要
activated 组件激活 配合 keep-alive 需要掌握
deactivated 组件停用 配合 keep-alive 需要掌握
beforeDestroy 销毁之前 需要掌握
destroyed 销毁完成 需要掌握
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .active{ color: red; } </style> </head> <body> <div id="app"> <App></App> </div> <script src="./vue.js"></script> <script> /* beforeCreate created 重要 beforeMount mounted 重要 beforeUpdate updated 重要 activated 组件激活 配合 keep-alive 需要掌握 deactivated 组件停用 配合 keep-alive 需要掌握 beforeDestroy 销毁之前 需要掌握 destroyed 销毁完成 需要掌握 */ Vue.component('Test',{ data(){ return{ msg:'亦双弓', isRed:false } }, methods:{ handClick(){ this.msg = 'ysg'; this.isRed = true; } }, template:` <div> <button @click='handClick'>改变</button> <h3 :class="{active:isRed}">{{msg}}</h3> </div> `, beforeCreate(){ console.log('组件创建之前',this.$data) }, created(){ // 这时可以做一件非常重要的事情:发送 ajax 请求后端数据 console.log('组件创建完成',this.$data) }, beforeMount(){ // 即将挂载 console.log('DOM挂载之前', document.getElementById('app')) }, mounted(){ console.log('DOM挂载完成', document.getElementById('app')) }, beforeUpdate(){ // 获取更新之前 原始的DOM console.log('更新之前的DOM',document.getElementById('app').innerHTML) }, updated(){ // 获取更新之后 最新的DOM console.log('更新之后的DOM',document.getElementById('app').innerHTML) }, beforeDestroy(){ // 销毁之前 console.log('销毁之前') }, destroyed(){ // 销毁完成 console.log('销毁完成') }, activated(){ console.log('组件激活') }, deactivated(){ console.log('组件停用') }, }) const App = { data(){ return{ isShow:true } }, components:{ }, methods:{ handClick(){ this.isShow = !this.isShow } }, template:` <div> <keep-alive> <Test v-if="isShow"></Test> </keep-alive> <button @click='handClick'>销毁与否</button> </div> ` } new Vue({ el:'#app', data:{ }, components:{ App } }) </script> </body> </html>
refs的使用
1.如果给标签添加 ref,获取的是真实的 DOM 节点
2.如果给子组件添加 ref,获取的是当前子组件对象
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <App></App> </div> <script src="./vue.js"></script> <script type="module"> Vue.component('Test',{ data(){ return{ msg:'亦双弓' } }, template:` <h3>{{msg}}</h3> ` }) const App = { data(){ return{ } }, mounted(){ // 1.如果给标签添加 ref,获取的是真实的 DOM 节点 // 2.如果给子组件添加 ref,获取的是当前子组件对象 console.log(this.$refs.btn); console.log(this.$refs.test); // 加载界面 自动获取焦点 this.$refs.input.focus(); }, template:` <div> <Test ref='test'></Test> <input type="text" ref="input"></input> <button ref='btn'>改变</button> </div> ` } new Vue({ el:'#app', data:{ }, components:{ App } }) </script> </body> </html>
nextTick
nextTick的使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <h3>{{ msg }}</h3> </div> <script src="./vue.js"></script> <script type="module"> const vm = new Vue({ el:'#app', data:{ msg:'亦双弓' }, }) vm.msg = 'new msg' console.log(vm.$el.textContent) // 获取的还是没变化前的 msg Vue.nextTick(()=>{ // 为了数据变化之后等待 vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick 在当前的 // 回调函数中可以获取最新的 DOM console.log(vm.$el.textContent) // 获取的是变化后的 msg }) </script> </body> </html>
nextTick的应用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <App></App> </div> <script src="./vue.js"></script> <script> /* 要求: 在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的, 然后在接口已返回数据就展示了这个浮层组件, 并在展示的同时,上报一些数据给后台(这些数据是父组件从接口拿的)。 这个时候,神奇的事情发送了,虽然拿到了数据,但是浮层展现的时候,这些数据还未更新到组件上去,上报时报 */ const Pop = { data(){ return{ isShow:false } }, props:{ name:{ type:String, default:'' } }, template:` <div v-if='isShow'> {{ name }} </div> `, methods:{ show(){ this.isShow = true // 弹窗组件展示 console.log(this.name) } }, } const App = { data(){ return{ name:'' } }, created(){ // 模拟异步请求 setTimeout(()=>{ this.name = 'ysg' this.$nextTick(()=>{ this.$refs.pop.show() }) },1000) }, components:{ Pop }, template:` <pop ref='pop' :name="name"></pop> ` } new Vue({ el:'#app', components:{ App } }) </script> </body> </html>
mixin
mixin混入偷懒技术
mixin 来分发 Vue 中可复用的功能
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <h3> {{ msg }} </h3> </div> <script src="./vue.js"></script> <script> const myMixin={ data(){ return{ msg:123 // 界面上就会显示 123 } }, created(){ this.sayHello() }, methods:{ sayHello(){ console.log('Hello 123') // console 中就会显示 Hello world } } } new Vue({ el:'#app', data:{ title:'亦双弓', msg:'ysging' // 假如这里也有 msg 就会优先显示这里的 }, created(){ console.log('111111') // created() 会同时显示,假如这里存在 this.sayHello(),则会调用两次 这里的 sayHello() }, mixins:[myMixin] // 数组,可放入多个混入对象 }) </script> </body> </html>
mixin混入技术应用
创建全局的 mixin 要格外的小心,因为每个组件实例创建时,都会被调用
使用比较少
Vue.mixin({
})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> </div> <script src="./vue.js"></script> <script> /* 创建全局的 mixin 要格外的小心,因为每个组件实例创建时,都会被调用 使用比较少 Vue.mixin({ }) */ const Temp = { data(){ return{ isShow:false } }, methods:{ toggleShow(){ this.isShow = !this.isShow } } } const app2 = { template :` <div v-if='isShow'> App2 组件 </div> `, // 局部的 mixin mixins: [Temp] } const app1 = { template :` <div v-if='isShow'> App1 组件 </div> `, mixins: [Temp] } new Vue({ el:'#app', data:{ }, components:{ app1, app2 }, template:` <div> <button @click='handleApp1'>app1</button> <button @click='handleApp2'>app2</button> <app1 ref='a1'></app1> <app2 ref='a2'></app2> </div> `, methods:{ handleApp1(){ this.$refs.a1.toggleShow() }, handleApp2(){ this.$refs.a2.toggleShow() }, } }) </script> </body> </html>
对象变更检测注意事项
Vue 不能检测对象属性的添加与删除
如果想要动态 添加与删除 属性,使用以下方法
// 方法一
this.$set(this.user,'age',20);
// 方法二 添加多个响应式属性
this.user = Object.assign({},this.user,{
age:20,
phone:123456789
})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"> <h3> {{ user.name }} {{ user.age }} {{ user.phone }} </h3> <button @click='handClick'>添加属性</button> </div> <script src="./vue.js"></script> <script> // Vue 不能检测对象属性的添加与删除 new Vue({ el:'#app', data:{ user:{} }, created(){ setTimeout(()=>{ this.user = { name:'张三', } },2000) }, methods:{ handClick(){ // this.user.age = 20 // 如果想要动态 添加与删除 属性,使用以下方法 // 方法一 this.$set(this.user,'age',20); // 方法二 添加多个响应式属性 this.user = Object.assign({},this.user,{ age:20, phone:123456789 }) } } }) </script> </body> </html>