Vue中组件、动态组件、插槽、数据处理、生命周期、自定义指令
一、组件
1.props属性
1)props属性
组件可以嵌套使用,叫做父子组件。
那么父组件经常要给子组件传递数据这叫做父子组件通信。
父子组件的关系可以总结为 props 向下传递,事件向上传递。
父组件通过 props 给子组件下发数据,子组件通过事件给父组件发送消息。<div id="app"> <!-- 使用子组件 --> <!-- 通过绑定属性,将父组件的数据设置到子组件标签中 --> <!-- msg2 代表的是一个自定义属性名,可任意 msg 代表的是父组件中data属性中的msg数据 --> <child :msg2="msg" :b="greetTxt"></child> </div> </body> <script type="text/javascript"> /* 定义子组件 */ Vue.component('child',{ /* 子组件中想用父组件传递的数据,需要通过props属性声明 */ props:["msg2",'b'], template:"<h3>Hello -- {{msg2}} --- {{b}}</h3>" }); /* 父组件 */ new Vue({ el:"#app", data:{ msg:"来自父组件的消息", greetTxt:"你好啊~Child" } });
2)props校验
子组件在接收父组件传入数据时, 可以进行props校验,来确保数据的格式和是否必传。可以指定一下属性:
1) type: 指定数据类型 String Number Object …注意不能使用字符串数组,只能是对象大写形式
2) required: 指定是否必输
3) default: 给默认值或者自定义函数返回默认值
4) validator: 自定义函数校验两种形式:
数组(不支持数据校验)
props:[‘参数1’,‘参数2’…]
对象(支持数据校验)
props:{
参数1:
参数2:
}<div id="app"> <child :a="msg" :b="greetTxt" :d="num" ></child> </div> </body> <script type="text/javascript"> /* 定义子组件 */ Vue.component('child',{ /* props校验 */ props:{ // 要求数据是数值型 // a:String, // 可以是数值型 和 字符串类型 b:[Number, String], // 必须设置属性 /* c:{ required:true }, */ // 设置默认值;如果数据未被传递,则显示默认结果 c:{ type:Number, default:100 }, // 数组/对象的默认值应当由一个工厂函数返回 e:{ default:function(){ return 1; } }, // 自定义验证函数 d:{ validator:function(value){ return value > 1; } }, // 自定义验证函数 a:{ validator:function(value){ return value.length > 10; } } }, template:"<h3>Hello -- {{a}} -- {{b}} -- {{c}} -- {{e}} -- {{d}}</h3>" }); /* 父组件 */ new Vue({ el:"#app", data:{ msg:"来自父组件的消息", greetTxt:"你好啊~Child", num:10 } }); </script>
2.非props属性
引用子组件时,非定义的props属性,自动合并到子组件上**,class和style也会自动合并。**
<div id="app"> <child style="font-size: 20px;" class="active" ></child> </div> </body> <script type="text/javascript"> /* 定义子组件 */ Vue.component('child',{ template:"<div style='color:red' class='bg'>Hello</div>" }); /* 父组件 */ new Vue({ el:"#app", }); </script>
3.自定义事件
自定义事件
父组件给子组件传值使用props属性, 那么需要子组件更新父组件时,要使用自定义事件emit:
emit主动触发: $emit(事件名,传入参数)
实现步骤: 1.定义父组件
定义父组件中的数据和方法 2.定义子组件
定义子组件的模板和方法 3.在使用子组件的标签时,绑定自定义事件
<child @update-count=“changeCount” >
说明:
update-count 代表的是 自定义事件
changeCount 代表的是自定义事件被触发时,调用的函数 4.在子组件的方法中,手动触发自定义事件
$emit(事件名,传递的参数);主动挂载
自定义事件不仅可以绑定在子组件,也可以直接挂载到父组件,使用emit触发。<div id="app"> 数量:{{count}} <hr > <child @update-count="changeCount" ></child> </div> <hr > <div id="root"> 数量:{{count}} </div> </body> <script type="text/javascript"> /* 定义子组件 */ Vue.component('child',{ template:"<button @click='update'>子组件Child</button>", methods:{ update:function(){ console.log("子组件的方法..."); // 触发自定义事件 this.$emit("update-count","Hello"); } } }); /* 父组件 */ new Vue({ el:"#app", data:{ count:0 }, methods:{ changeCount:function(a){ console.log("父组件的方法..."); console.log("子组件中传递的数据:" + a); // 更新data属性中的数据 this.count++; } } }); var root = new Vue({ el:"#root", data:{ count:0 } }); // 主动挂载 root.$on("update-count",function(){ console.log("主动挂载..."); // 更新父组件的数据 this.count++; }); // 手动触发 root.$emit("update-count","HAHAH"); </script>
二、插槽分发
1.slot插槽
父子组件使用时,有时需要将父元素的模板跟子元素模板进行混合,这时就要用到slot插槽进行内容分发,简单理解就是在子模板中先占个位置等待父组件调用时进行模板插入。
在子组件模板中使用**标签定义插槽位置**,标签中可以填写内容,当父组件不传入内容时显示此内容。<div id="app"> <child> <h3>我是父组件</h3> <h3>我是父组件2</h3> </child> </div> <hr > <!-- 组件模板 --> <template id="child-template"> <div> <slot><div>我是默认内容,父组件不传入时我显示</div></slot> <div>我是子组件</div> <div>Hello</div> <!-- 定义slot插槽进行占位 --> <!-- <slot>我是默认内容,父组件不传入时我显示</slot> --> <slot><div>我是默认内容,父组件不传入时我显示</div></slot> </div> </template> </body> <script type="text/javascript"> /* 定义子组件 */ Vue.component('child',{ template:"#child-template", }); /* 父组件 */ new Vue({ el:"#app", data:{ count:0 } }); </script>
2.具名插槽
具名插槽slot, 就是给插槽起个名字。
在子组件定时可以定定义多个插槽,同时通过name属性指定一个名字
如:,父组件引用时使用< slot=‘header’>进行插槽选择。<div id="app"> <child> <h3 slot="zhangsan">我是张三</h3> <h3 slot="lisi">我是李四</h3> </child> </div> <hr > <!-- 组件模板 --> <template id="child-template"> <div> <slot name="zhangsan"></slot> <div>我是子组件</div> <div>Hello</div> <slot name="lisi"></slot> </div> </template> </body> <script type="text/javascript"> /* 定义子组件 */ Vue.component('child',{ template:"#child-template", }); /* 父组件 */ new Vue({ el:"#app", data:{ count:0 } }); </script>
3.slot-scope
作用域插槽slot-scope,父组件通过插槽混入父组件的内容, 子组件也可以通过slot作用域向插槽slot内部传入数据
使用方式:,父组件通过进行引用。<div id="app"> <child> <!-- 使用template标签将插槽对应的数据包裹起来,并设置slot-scope的属性值,属性值是一个变量名,自定义 --> <!-- <template slot-scope="props"> <h3 >我是张三 -- {{props.msg}}</h3> </template> --> <!-- 在2.5+之后,可以不局限于<template>, 任何元素都可以,同时可以使用解构赋值的方式进行数据解析。 --> <!-- <h3 slot-scope="props">我是张三 -- {{props.msg}}</h3> --> <!-- 解构赋值 --> <h3 slot-scope="{msg,txt}">我是张三 -- {{msg}}-- {{txt}}</h3> </child> </div> <hr > <!-- 组件模板 --> <template id="child-template"> <div> <div>我是子组件</div> <div>Hello</div> <!-- 定义插槽时,传递数据 --> <slot msg="你好啊张三" txt="哈哈"></slot> </div> </template> </body> <script type="text/javascript"> /* 定义子组件 */ Vue.component('child',{ template:"#child-template", }); /* 父组件 */ new Vue({ el:"#app", data:{ count:0 } }); </script>
三、动态组件
1.动态组件
使用标签的is属性,动态绑定多个组件到一个挂载点**,通过改变is绑定值,切换组件。**
实现步骤: 1. 定义父组件,设置data数据 2. 定义多个子组件 3. 定义导航(a标签),并为导航绑定点击事件,修改data属性中数据的值 4. 使用is属性绑定父组件的数据 <component :is="page"></component> page 代表的是父组件中data属性定义的数据 <div id="app"> / <a href="#" @click.prevent="page='index'">首页</a> / <a href="#" @click.prevent="page='news'">新闻页</a> / <a href="#" @click.prevent="page='login'">登录页</a> <hr > <component :is="page"></component> </div> </body> <script type="text/javascript"> Vue.component('index', { template:'<h5>首页</h5>' }); Vue.component('news', { template:'<h5>新闻页</h5>' }); Vue.component('login', { template:'<h5>登陆页</h5>' }); /* 父组件 */ new Vue({ el:"#app", data:{ page:"login" } }); </script>
2.keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令。
用生命周期中的mounted(挂载)钩子函数进行组件渲染监听,当组件第一次被渲染后就保存在内存中,下次切换不会被重新渲染。<div id="app"> / <a href="#" @click.prevent="page='index'">首页</a> / <a href="#" @click.prevent="page='news'">新闻页</a> / <a href="#" @click.prevent="page='login'">登录页</a> <hr > <!-- 把切换出去的组件保留在内存中 --> <keep-alive> <component :is="page"></component> </keep-alive> </div> </body> <script type="text/javascript"> Vue.component('index', { template:'<h5>首页</h5>', mounted: function () { console.log('挂载...首页'); } }); Vue.component('news', { template:'<h5>新闻页</h5>', mounted: function () { console.log('挂载...新闻页'); } }); Vue.component('login', { template:'<h5>登陆页</h5>', mounted: function () { console.log('挂载...登录页'); } }); /* 父组件 */ new Vue({ el:"#app", data:{ page:"login" } }); </script>
3.refs属性
使用ref 给每个组件起一个固定的名字,方便后续直接引用操作,在父组件中使用$refs访问子组件。
<div id="app"> <child ref="ref1"></child> <hr> <child ref="ref2"></child> </div> </body> <script type="text/javascript"> Vue.component('child', { template:'<button @click="count++">{{count}}</button>', data:function(){ return {count:0}; } }); /* 父组件 */ var app = new Vue({ el:"#app", }); </script>
四、数据处理
1.watch属性
在Vue组件中,使用watch属性来监听数据的变化,同时可以指定监听那个属性。
watch:{**
监听的数据:function(newValue,oldValue){
}
}**<div id="app"> {{uname}} -- {{upwd}} <hr > <p> firstName: <input type="text" v-model="firstName"> </p> <p> lastName: <input type="text" :value="lastName" @input="changeLastName"> </p> <h4>{{fullName}}</h4> </div> </body> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ uname:"zhangsan", upwd:"123456", firstName:"Hello", lastName:"Kitty", fullName:"Hello Kitty" }, // watch属性 监听数据变化,可以得知数据变化前的值与变化后的结果 watch:{ uname:function(v1,v2){ console.log(v1+","+v2); }, upwd:function(newValue,oldValue){ console.log("改变后的值:" + newValue + ",改变后的值:" + oldValue); }, // 监听值的变化 firstName:function(newValue,oldVlue){ this.fullName = newValue + " " + this.lastName; }, lastName:function(newValue,oldVlue){ this.fullName = this.firstName + " " + newValue; } }, methods:{ changeLastName:function(e){ // 给数赋值 this.lastName = e.target.value } } }); </script>
2.$watch
除了在组件内部使用watch也可以使用内部命令watch第一个参数是需要监听的属性,第二个是回调函数用法和watch一样。
需要取消监听只需拿到监听对象的引用,这个引用是返回一个函数对象,执行该对象就可以取消监听。
同时监听多个属性,可以不指定属性<div id="app"> {{uname}} </div> </body> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ uname:"zhangsan", upwd:'123' }, }); // 使用$watch监听指定数据 /* var unwacth = app.$watch("uname",function(v1,v2){ console.log(v1+","+v2) }); */ // 取消监听 (返回一个函数对象,执行该对象就可以取消监听) // unwacth(); // 监听所有数据 app.$watch(function(){ return this.uname +"-" + this.upwd; },function(newValue, oldValue) { console.log('newValue:【'+newValue+'】 oldValue:【'+oldValue+'】'); }); </script>
3.computed属性
computed计算属性用于定义比较复杂的属性计算
注:如果是computed属性计算得到的结果,使用时不需要加括号,与定义在data中的属性使用方法一致
computed和methods区别:
计算属性使用computed定义, 方法使用methods定义
计算属性使用时不加括号执行符
计算属性是基于它们的依赖进行缓存的,计算属性只有在它的相关依赖发生改变时才会重新求值。否则返回之前计算好的值,性能更高!<div id="app"> <p> firstName: <input type="text" v-model="firstName"> </p> <p> lastName: <input type="text" v-model="lastName"> </p> <!-- 如果是computed属性计算得到的结果,使用时不需要加括号,与定义在data中的属性使用方法一致 --> <h4>{{fullName2}}</h4> <!-- 如果是methods中定义的方法,使用时需要添加括号调用该方法 --> <h4>{{fullName()}}</h4> </div> </body> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ firstName:"Hello", lastName:"Kitty" }, // 计算数据 computed:{ fullName2:function(){ return this.firstName + " " + this.lastName; } }, methods:{ fullName:function(){ return this.firstName + " " + this.lastName; } } }); </script>
4.getter和setter
<div id="app"> <p> fullName: <input type="text" v-model="fullName" /> </p> <h4>firstName: {{firstName}}</h4> <h4>lastName: {{lastName}}</h4> </div> </body> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ firstName:"Hello", lastName:"Kitty" }, // 计算数据 computed:{ fullName:{ // get 取值 get:function(){ return this.firstName + " " + this.lastName; }, // set 赋值 set:function(value){ console.log(value); var news = value.split(" "); this.firstName = news[0]; this.lastName = news[1]; } } } }); </script>
五、生命周期
1.生命周期
常用的生命周期钩子函数有:
1) created: 实例创建完成后被立即调用
2) mounted: 实例挂载完成后被立即调用
3) beforeUpdate: 实例需要渲染之前调用
4) updated: 实例更新后调用
5) destroyed: Vue 实例销毁后调用<div id="app"> {{uname}} </div> </body> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ uname:"admin" }, created: function () { console.log('实例创建...'); }, mounted:function () { console.log('实例挂载...'); }, beforeUpdate:function () { console.log('实例将要更新...'); }, updated:function () { console.log('实例已更新...'); }, destroyed:function(){ console.log('实例卸载...'); } }); // 销毁实例 // app.$destroy(); </script>
2.钩子函数
el: 指令所绑定的元素,可以用来直接操作 DOM 。
binding: 一个对象,包含以下属性:
- name: 指令名,不包括
v-
前缀。- value: 指令的绑定值, 例如:
v-my-directive="1 + 1"
, value 的值是2
。- oldValue: 指令绑定的前一个值,仅在
update
和componentUpdated
钩子中可用。无论值是否改变都可用。- expression: 绑定值的表达式或变量名。 例如
v-my-directive="1 + 1"
, expression 的值是"1 + 1"
。- arg: 传给指令的参数。例如
v-my-directive:foo
, arg 的值是"foo"
。- modifiers: 一个包含修饰符的对象。 例如:
v-my-directive.foo.bar
, 修饰符对象 modifiers 的值是{ foo: true, bar: true }
。vnode: Vue 编译生成的虚拟节点。
oldVnode: 上一个虚拟节点,仅在
update
和componentUpdated
钩子中可用。<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue</title> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> </head> <body> <div id="app" v-runoob:hello.a.b="message"> </div> <script> Vue.directive('runoob', { bind: function (el, binding, vnode) { var s = JSON.stringify el.innerHTML = 'name: ' + s(binding.name) + '<br>' + 'value: ' + s(binding.value) + '<br>' + 'expression: ' + s(binding.expression) + '<br>' + 'argument: ' + s(binding.arg) + '<br>' + 'modifiers: ' + s(binding.modifiers) + '<br>' + 'vnode keys: ' + Object.keys(vnode).join(', ') } }) new Vue({ el: '#app', data: { message: '哈士奇' } }) </script> </body> </html>
六、自定义指令
1.自定义指令
除了默认设置的核心指令 (v-model 和 v-show),Vue 也允许注册自定义指令。
两种定义方式:全局指令和局部指令1.定义指令
全局指令
Vue.directive(“指令名”,{
//inserted 钩子函数,当绑定元素插入到DOM调用
inserted:function(el){
// el 代表的是当前绑定指令的元素对象
}
});
局部指定
- 使用指令
v-指令名 注:使用directive定义,第一个参数为指令名,使用时加上v-前缀才能生效。inserted属性指当绑定元素插入到DOM时调用。
<div id="app"> <input type="text" v-focus v-aa > <input type="text" v-demo:ab.a.b="1+1"> </div> </body> <script type="text/javascript"> /* 定义全局指令 */ Vue.directive("focus",{ //当绑定元素插入到DOM调用 inserted:function(el){ // el 代表的是当前绑定指令的元素对象 el.focus(); // 聚焦 } }); Vue.directive('demo',{ bind: function (el,binding) { console.log(el); console.log(binding); } }); var app = new Vue({ el:"#app", data:{ uname:"admin" }, /* 局部指令 */ directives:{ aa:function(el){ console.log(el); el.value="Hello" } } }); // 销毁实例 // app.$destroy(); </script>
2.实现图片懒加载
谷歌图片的加载做得非常优雅,在图片未完成加载前,用随机的背景色占位,图片加载完成后才直接渲染出来,用自定义指令可以非常方便的实现这个功能。
<div id="app"> <div class="item" v-for="item in imgs" v-img="item.url"></div> </div> </body> <script type="text/javascript"> /* 定义全局自定义指令v-img */ Vue.directive("img",{ bind:function(el,binding){ console.log(el); console.log(binding); //生成随机颜色 var color=parseInt(Math.random()*0xFFFFFF).toString(16); //设置当前元素的背景,提前进行占位等待图片加载 el.style.background="#"+color; //setTimeout模拟图片加载的延时情况 setTimeout(function() { //得到图片的路径 var url=binding.value; //得到图片对象 var img=new Image(); //设置img对象的人src属性 img.src=url; //将img对象设置到div(el对象)中 el.appendChild(img); },Math.random()*100+500);//随机延时 } }); var app=new Vue({ el:"#app", data:{ //定义模拟数据 imgs:[ {url:'img/1.jpg'}, {url:'img/2.jpg'}, {url:'img/3.jpg'}, {url:'img/4.jpg'} ] } }); </script>
3.过滤器
Vue允许自定义过滤器,可被用作一些常见的文本格式化。
过滤器可以用在两个地方:mustache 插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。
过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示:
{{数据 | 过滤器}}
多个过滤器可以串联
{{数据 | 过滤器 | 过滤器2}} 定义过滤器
全局过滤器
Vue.filter(“uppercase”,function(value){
return value.toUpperCase();
});
局部过滤器
filters:{
length:function(value){
return value.length;
}
}<div id="app"> <!-- 使用过滤器 --> {{msg}} --{{msg | uppercase}}--{{msg | length}}<br> {{msg | uppercase | test}}<br> {{msg | test01("-->","<--")}} </div> </body> <script type="text/javascript"> /* 定义全局过滤器 */ Vue.filter("uppercase",function(value){ return value.toUpperCase(); }); var app=new Vue({ el:"#app", data:{ msg:"Hello" }, /* 定义局部过滤器 */ filters:{ length:function(value){ return value.length; }, test:function(value){ return value+":"+value.length; }, test01:function(value,begin,end){ return begin + value + end; } } }); </script>