数据绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- mustache 可以做运算 取值 输出 三元 不能写js语法-->
<!-- 只要使用vue的数据就需要先声明 在使用 -->
<div id="app">
{{msg}} {{1+1}} {{info.a}} {{ {} }}
{{flag?1:2}} {{ (function(){return 100})() }}
{{msg+'123'}}
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
data:{
info:{},
msg:'hello',
flag:true
}
});
vm.$mount('#app')
// 1.vm.$el 指代的就是当前的元素
// 2.vm.$nextTick 延迟执行 dom操作时必备
// 3.vm.$watch 监控数据变化
// 4.vm.$data 当前数据对象
// 5.vm.$options所有的选项
// 6.vm.$set
// 7.vm.$mount挂载 单元测试 在内存中挂载vue实例 此时只能用$mount属性
vm.$watch('msg',function (newVale,oldValue) { // 可以观察某个数据 发生变化后触发此函数
console.log(newVale,oldValue)
});
vm.msg = 'zf';
vm.msg = 'xxx'; // dom更新是异步的
vm.$nextTick(function () {
console.log(vm.$el.innerHTML);
});
</script>
</body>
</html>
常用指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- v-for 指令:v-开头 有特定功能的 操作dom元素-->
<!-- 数据都需要循环来操作 {} ([] 5) string -->
<!-- for(let a of arr) -->
<!-- vue 2.5+ 版本要求循环的时候 必须增加key属性 为了做domdiff -->
<!-- v-bind 是动态绑定属性 所有的指令 中的值 都是我们的变量 如果要是字符串需要加双引号 -->
<!-- template vue自带的标签 无意义的标签 template上不能增加key属性 需要给真实的元素添加key -->
<!-- v-if/ v-else / v-show -->
<!-- v-on 绑定事件 可以简写成 @ 符号 并且事件参数是$event -->
<div id="app">
<template v-for="(a,index) in arr">
<!-- 多个元素 需要区分名称 而且可以使用模板字符串 -->
<li :key="index+'_1'" :a="index+'_1'">{{a}} {{index}}</li>
<li :key=`${index}_2` :a=`${index}_2`>{{a}} {{index}}</li>
</template>
<template v-for="(value,key) in {a:1}">
<!-- 多个元素 需要区分名称 而且可以使用模板字符串 -->
<li>{{value}} {{key}}</li>
</template>
<template v-for="(value,key) in 5">
<!-- 多个元素 需要区分名称 而且可以使用模板字符串 -->
<li>{{value}} {{key}}</li>
</template>
<!-- v-if v-show的区别 if处理dom是否增加到页面上 show style的操作 (show 不支持template写法) -->
<div v-show="false">
你好
</div>
<div v-show="true">
不好
</div>
{{this.flag}}
<button v-on:click="fn($event)">切换</button>
<!-- 尽量不要给动态的数据 不要用key来渲染 可能会导致浪费性能-->
<!-- [香蕉,苹果,橘子] 点击 翻转 -->
<!-- <li x>香蕉</li> <li j>橘子</li>
<li p>苹果</li> <li p>苹果</li>
<li j>橘子</li> <li x>香蕉</li> -->
<div v-if="flag">
<label >test1</label>
<input type="text" key="1">
</div>
<div v-else>
<label >test2</label>
<input type="text" key="2">
</div>
<button @click="fn">切换</button>
<!-- 只渲染一次 渲染后会产生缓存 下次更新时 会直接从缓存中获取 v-once 可以有效的防止重新渲染-->
<div v-once>{{flag}}</div>
<!-- innerHTML 会导致 xss攻击 防止xss攻击 就不要把用户输入的内容直接显示出来 -->
<input type="text" :value="element" @input="(e)=>{element=e.target.value}">
<!-- 语法糖 -->
<input type="text" v-model="element">
<div v-html="element"></div>
</div>
<script src="./node_modules/vue/dist/vue.js"> </script>
<script>
// 所有的数据 都会合并到vm的实例上 但是会被data覆盖掉 不要声明相同的名字
let vm = new Vue({
el:'#app',
data:{
flag:true,
arr:[1,2,3],
element:'<h1>hello</h1>'
},
methods:{ // 方法要放到methods中
fn(e){
console.log(this);
this.flag=!this.flag
},
}
});
</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>
<style>
.content {
width: 100px;
height: 100px;
background: pink;
}
</style>
<body>
<div id="app">
<!-- 如果是多次可复用的操作dom节点,可以使用自定义指令,如果是单次操作dom节点,更建议使用$ref -->
<!-- 指令的作用就是操作dom,有特定功能 .a是修饰符-->
<div v-direcitve_demo.a="'red'">xxx</div>
<!-- popup -->
<div v-click_outside='hide'>
<input type="text" @focus='show'>
<div class="content" v-if='isShow'>
content
<button>点我呀</button>
</div>
</div>
<!-- 一刷新页面就获取焦点 -->
<input type="text" v-focus='xx'>
<!-- 过滤器,| 竖线叫管道符 -->
<div :id="xx | abc"></div>
<div>{{xx | abc}}</div>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
/**
* @description: 全局指令,不需要每个组件都引用,全局引用即可
* @param {type} 指令名
* @param {type} 对象或函数
* @author: dlb
*/
Vue.directive('direcitve_demo', {
// bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
// inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
// update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
// componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
// unbind
bind: (el, binding, vnode, oldVnode) => {
console.log('el', el);
console.log('binding', binding);
console.log('vnode', vnode);
console.log('oldVnode', oldVnode);
el.style.color = `${binding.value}`
}
})
let vm = new Vue({
// 局部过滤器
filters: {
abc: (value) => {
return 666
}
},
// 局部指令,因为有可能是多个指令,所以要加个s
directives: {
// 如果是直接绑定的方法默认是bind和update(两个的合体)
click_outside(el, binding, vnode) {
// 绑定给document 捕获事件发生在谁的身上
document.addEventListener('click', (e) => {
if (!el.contains(e.target)) {
console.log(binding.expression, '失去焦点', vnode.context);
// 在vnode的上下文里可以拿到vue实例,从而调用方法
vnode.context[binding.expression]()
}
})
},
focus: {
// bind: (el, binding, vnode) => {
// // 保证vue渲染完成再次性就可以达到效果了
// Vue.nextTick(() => {
// el.focus()
// })
// },
// 被绑定元素插入父节点时调用
inserted: (el, binding, vnode) => {
el.focus()
}
}
},
el: '#app',
data: {
isShow: false,
xx: 'hello'
},
methods: {
show() {
this.isShow = true
},
hide() {
this.isShow = false
}
}
})
</script>
</body>
</html>
```'
## computed、methods、watch的区别
> methods中的方法,页面中用到的任意一个变量改变了,都会重新执行
```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">
<div id="example">
<!-- 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护 -->
{{ message.split('').reverse().join('') }} <br />
<!-- 复杂运算应该放入到computed上 -->
{{messageHandler}}<br />
<!-- computed和methods的区别 -->
{{firstName+lastName}}<br />
<!-- methods -->
{{getName()}}
</div>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data() {
return {
message: 'ABCDEF',
firstName: '懒',
lastName: '斌'
}
},
// 对于任何复杂逻辑,你都应当使用计算属性
computed: {
messageHandler() {
return this.message.split('').reverse().join('')
}
},
methods: {
// 使用methods的方法,只要页面中任意变量修改了,这个方法都会执行,对性能比较浪费
getName() {
console.log('getName');
return this.firstName + this.lastName
}
},
mounted() {}
})
</script>
</body>
</html>
如果是methods里面的方法
// computed是基于Object.defineProperty的,某个属性变了,会自动触发set/get方法,更新试图,
// 如果属性没变,就不会触发set/get方法
computed: { //computed会有缓存,如果依赖的数据不发生变化,不会重新执行方法
// 虽然getName是个方法,但是实际上是个属性
getName: {
// 计算属性有get和set方法,如果不配置的话默认是get方法
get() {
// 里面不支持定时器等异步方法
console.log('getName');
// 并且computed里的方法,只有当依赖的变量变了,才会从新执行
return this.firstName + this.lastName
},
// 给getName赋值的时候会触发这个方法
set(val) {
console.log('set', val);
//这里可以做一些逻辑操作
this.message = 'iiiii'
//不需要有返回值
}
},
// 对于任何复杂逻辑,你都应当使用计算属性
messageHandler() {
return this.message.split('').reverse().join('')
}
},
1.在控制台手动修改方法里的变量值,方法会执行,页面渲染也会改变 2.在控制台手动修改页面用到了但是方法里没用到的变量值,这个方法也会重新执行,这里就等于是浪费了渲染性能了 3.所以在这种情况下,使用methods里的方法就不是太合适
如果是computed里面的方法
1.在控制台手动修改方法里的变量值,方法会执行,页面渲染也会改变 2.在控制台手动修改页面用到了但是方法里没用到的变量值,这个方法不会重新执行,性能上会好一些 3.computed是基于Object.defineProperty的,某个属性变了会自动触发set/get方法并更新试图,如果没变就不会触发
methods和computed的区别
-
methods:里面定义的是方法,不会进行缓存
-
computed:里面定义的是值,会进行缓存
-
{{fn()}} 这种写法虽然从语法上不会报错,但是错误的写法,尽量不要这么写
watch监控
// 什么时候用watch,什么时候用computed
// watch:简单的事情,例入数据变化了就发送请求
// computed:用在只是计算结果
watch: {// 监控
firstName: {
handler(val, oldVal) {
// 在里面加定时器是可以正常生效的
setTimeout(()=>{
this.fullName = val + 999
},2000)
},
deep: true, // 深度监听
immediate: false, //立即执行
}
},
watch和computed在使用场景上的区别
- watch:简单的事情,例入数据变化了就发送请求
- computed:用在只是计算结果
动画animate的使用
触发动画的方式
1.v-if、v-show,会触发过渡效果
2.路由切换
实现动画的方式
1.css添加动画
- animation
- transition 2.js添加动画
- 使用自带的钩子
- 使用velocity动画库
动画分类
1.单个动画
2.多个动画
动画的阶段
实现原理是,每个对应的状态会加上对应的类名
-
v-enter进入之前/v-leave离开前
-
v-enter-active进入中/v-leave-active离开中
主要控制过渡时间和效果
-
v-enter-to进入之后/v-leave-to离开后
调试的实际效果是,-to的类名和-active的类名基本上同时出现同时消失,个人觉得没啥用,离开后的样式会恢复默认的
具体使用
1.使用transition/transition-group标签包裹
<transition>
<div class="box" v-if='show'>
content
</div>
</transition>
// 过渡动画样式
.v-enter {
opacity: 0;
transform: scale(0.1, 0.1);
}
.v-enter-active {
transition: all 2s linear;
}
.v-enter-to {
opacity: 1;
transform: scale(1,1);
}
.v-leave {
opacity: 1;
}
.v-leave-active {
transition: all 5s linear;
}
.v-leave-to {
opacity: 0;
}
2.如果给transition加name属性的,则样式也要以定义的name开头,在需要多组动画时可以有对应关系
<transition name='fade'></transition>
.fade-enter {
opacity: 0;
transform: scale(0.1, 0.1);
}
使用动画库anmate.css
安装和引入
yarn add animate.css -s
<link rel="stylesheet" href="./node_modules/animate.css/animate.css">
使用方法
1.需要先给需要动画的盒子加animated类名,并且transition的name也要用库里的名字
<!-- name='bounce'可以指定动画名字 -->
<transition name='bounce'>
<div class="box animated" v-if='show'>
content
</div>
</transition>
// 进入样式
.bounce-enter-active{
animation: bounceIn 1s ease-in;
}
// 离开样式
.bounce-leave-active{
animation: bounceOut 1s ease-in;
}
2.不写样式的方式,在transition上直接配置使用的类名,以animate__类名的方式
<!-- 指定进入和离开的动画类名 -->
<transition enter-active-class='animate__bounceIn' leave-active-class='animate__bounceOut'>
<!-- 设置过渡时间 -->
<div class="box animated" v-show='show' style="animation-duration: 50000ms">
content
</div>
</transition>
这两种方式都能使用animate.css达到了一个弹跳的动画效果
使用js动画
触发动画的钩子,在方法里用js控制动画效果
<transition @beforeEnter="beforeEnter"
@enter="enter"
@afterEnter="afterEnter"
@enterEancelled="enterCancelled"
@beforeLeave="beforeLeave"
@leave="leave"
@afterLeave="afterLeave"
@leaveCancelled="leaveCancelled">
<div class="box animated" v-if='show' style="animation-duration: 5000ms">
content
</div>
</transition>
methods: {
beforeEnter: function (el) {
console.log(el,'el');
el.style.background = 'red'
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter: function (el, done) {
setTimeout(() => {
el.style.background = 'yellow'
})
setTimeout(() => {
done()
})
},
afterEnter: function (el) {
el.style.background = 'pink'
},
enterCancelled: function (el) {},
beforeLeave: function (el) {},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave: function (el, done) {},
afterLeave: function (el) {},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {}
}
使用vue推荐的第三方js动画库Velocity.js
中文文档 1.安装、引入
cnpm install velocity-animate -s
<script src="./node_modules/_velocity-animate@1.5.2@velocity-animate/velocity.min.js"></script>
2.使用,在vue的动画钩子的回调里使用就可以了,html中也是那几个动画钩子
beforeEnter(el){
el.style.opacity = 0;
el.style.color="purple"
},
enter(el,done){
// 标签 动画属性 Velocity 动画配置项,参数是固定的
Velocity(el,{opacity:1},{duration:1000,complete:done});
},
afterEnter(el){
el.style.color="blue"
},
leave(el,done){
// 在离开的时候 还需要自己手动重置回去
Velocity(el,{opacity:0},{duration:1000,complete:done});
}
多个动画的实现
应用场景:用在列表循环,注意要用transition-group标签包裹,其他的都一样
<style>
.box {
width: 200px;
height: 30px;
border-bottom: 1px solid #000;
}
</style>
<link rel="stylesheet" href="./node_modules/animate.css/animate.css">
<body>
<div id="app">
<input type="text" v-model='filter'>
<transition-group enter-active-class='animate__bounceIn' leave-active-class='animate__bounceOut'>
<div class="box animated" v-for='item in list2' :key='item'>
{{item.title}}
</div>
</transition-group>
</div>
<script src="./node_modules/vue/dist/vue.min.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
filter: '',
list: [{
title: '标题1'
}, {
title: '标题2'
}, {
title: '标题3'
}, {
title: '标题4'
}]
},
computed: {
list2() {
// filter() 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
return this.list.filter((e) => {
// 查找字符串是否包含
return e.title.includes(this.filter)
})
}
}
})
</script>
</body>
</html>
使用css3的keyframes
其实就是通过控制类名的存在与否是否展示动画
<template>
<div class="statistical">
<template v-for="(item, i) in arr">
<div
:key="item"
class="item"
@touchstart="startAnimation(i)"
@touchmove="stopAnimation"
:class="{ animate__tada: showAnimate === i }"
>
<div class="name">{{ item.name }}</div>
<div class="bottom">{{ item.number }}</div>
<i :style="'background:' + item.color"></i>
</div>
</template>
</div>
</template>
<script>
export default {
data () {
return {
showAnimate: true,
}
},
methods: {
// 显示动画
startAnimation (i) {
this.long = true
this.showAnimate = i
setTimeout(() => {
if (this.showAnimate) this.showAnimate = false
}, 350)
setTimeout(() => {
this.$emit('toEcharts', i)
}, 500)
},
// 停止动画
stopAnimation () {
this.showAnimate = false
}
}
}
</script>
@keyframes tada {
from {
transform: scale3d(1, 1, 1);
}
10%,
20% {
transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
}
30%,
50%,
70%,
90% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}
40%,
60%,
80% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}
to {
transform: scale3d(1, 1, 1);
}
}
.animate__tada {
animation-duration: 0.7s;
animation-fill-mode: both;
animation-name: tada;
}
组件的应用
组件的初始渲染流程生命周期
每个生命周期,做什么事情(在特定的时间下执行)
beforeCreate 创建之前
1.new Vue或者new组件,这个钩子就执行了,有this,会初始化this的生命周期(children),可以拿到部分的事件方法以及原型方法emit
2.在new的时候,会最先调用,这个钩子一般不会做太多功能,底层中可以做一些链的操作
created 创建
1.响应式的数据变化、观察完成了
2.dom还没创建完毕,获取不到真实dom元素,this.$el拿不到,打印会是undefined
beforeMount 挂载之前,创建dom
1.用响应式的数据变化和模板结合渲染成一个真实的页面,替换掉原来的div
2.必须要挂载了元素才会走(如果Vue实例里没设置el是不会执行此钩子的)
3.会检测template属性(有没有模板),如果有template就不会去渲染el挂载的标签的内容了,而是用template中的内容去替换,template属性叫内部模板,el对应标签的内容叫外部模板,具体的话是将template渲染成一个render函数
render函数(渲染函数,不算是生命周期)
1.beforeMount走完之后走render
2.render的参数 createElement 创建一个虚拟dom,再渲染成真实dom
3.返回createElement('div',{attrs:{id:123}},'hello'),表示创建一个div,属性是id=123,内容是hello,就会在页面渲染一个div标签
4.同时写template和render会渲染render中的内容
5.template本质上也是通过render进行渲染
mounted 挂载之后
1.可以获取到真实的元素 this.$el
2.在操作dom时,建议增加this.$nextTick
nextTick原理,先用promise.then异步执行,如果还未完成继续setImmediate,如果还未完成继续MutationObserver,还不行setTimeout,最终要等到数据更新完,dom渲染完,才会执行里面的回调
3.一般在这个钩子做数据请求,因为这里可以获取dom元素
数据变化触发的生命周期钩子
beforeUpdate 数据更新之前
1.在更新之前再去做某件事(例如,在更新之前再做一次修改)
updated 数据更新之后
1.更新后不能再修改值了(会死循环),这个钩子不能更改数据
销毁时触发的钩子
beforeDestroy 销毁前
1.一般情况下,用来清除定时器和移除绑定的方法事件
2.触发方式:组件销毁的时候,路由切换的时候,手动销毁的时候(vm.$destroy,用得很少)
destroyed 销毁后
1.把属性、数据、响应式的变化都去掉,但不会影响当前页面渲染
2.用得少
缓存用到的生命周期钩子(被 keep-alive 缓存的组件激活时调用)
activated 缓存前
deactivated 缓存后
为什么要有组件
1.复用,方便维护,拆分功能,
2.每个组件间作用域是隔离的,组件间互不干扰
组件的定义
1.组件就是一个自定义标签,可以代表一些特定的功能
2.主要封装css html js
组件的类型
- 全局组件
- 全局组件是component,局部组件是components
- 在任何组件中可以直接使用,而且不需要引入,在组件的模板中用,组件的数据必须是函数类型
<div id="app">
<!-- 上面这种是不符合 w3c规范的 hr img br... -->
<my-button :info="xxx"></my-button>
</div>
<!-- 组件的类型 全局组件 局部组件 函数式组件 异步组件 -->
<!-- 为什么要有组件 :复用,方便维护。拆分方便 ,每个组件间作用域是隔离的,组件间互不干扰,组件间的数据传递,组件就是一个自定义的标签,可以代表一些特定的功能,主要封装 css html js .vue文件-->
<script>
// 数据来源 data props
// 全局组件 在任何组件中可以直接使用,而且不需要引入,在组件的模板中用
Vue.component('my-button',{ //每个组件都有自己的声明周期
props:['info'],
data(){ // 组件的数据必须是函数类型
return {msg:"点我啊"} // 保证数据不会互相影响 所以通过一个函数返回一个唯一的对象
},
template:`<button>{{msg}}-{{info}}</button>`
})
let vm = new Vue({ // 根实例必须数据是对象类型,但是自定义的组件必须是函数类型
el:'#app',
data(){
return { xxx:'试试'}
}
});
// 父组件传递给子组件通过属性传递
</script>
- 局部组件
- 需要在用到的组件引入
生命周期先走子组件,然后父组件,最后走根组件
可以使用attrs" 传入内部组件——在创建高级别的组件时非常有用。
可以通过inheritAttrs属性控制是否需要在dom上显示传递的属性
<div id="app">
<my-button :info="xxx" a="1" b="2" c="3"></my-button>
</div>
<script>
let vm = new Vue({
el:'#app',
data:{
xxx:'xxx'
},
components:{
'my-button':{
inheritAttrs:false, // 不在dom上显示传递的属性
props:['info'],
data(){
return {msg:"点我啊"}
},
// this.$attrs 表示所有没有被用的属性,批量传递属性
// mounted(){
// console.log(this.$attrs)
// },
template:`<button>{{msg}}-{{info}}-<my v-bind="$attrs"></my></button>`,
components:{
'my':{
props:['a','b','c'],
mounted(){
console.log(this.$attrs);
},
template:`<span>试试啊 {{a}} {{b}} {{c}}</span>`
}
}
}
}
});
</script>
-
函数式组件
-
异步组件
- 路由分割,点个按钮去加载个组件
组件间的通信 props events parent ref eventBus vuex
父传子
使用修饰符传递事件
-
直接给子组件加事件是不会触发的,需要加上.native修饰符
-
native修饰符相当于给子组件内最外层的标签加事件,下面只要点击到div的范围就会触发show
<div id="app">
<my-button @click.native='show'></my-button>
</div>
<script>
Vue.component('my-button', {
template: `<div><button>点我</button></div>`
})
let vm = new Vue({
el: '#app',
data: {
},
methods: {
show() {
alert('show')
}
}
})
</script>
使用$listeners传递事件
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
1.子组件内,所有外面传进来的属性,存在$attrs中
2.子组件内,所有外面传进来的方法,存在$listenters中
<div id="app">
<my-button @click='show' @mouseup='show2'></my-button>
</div>
<script>
Vue.component('my-button', {
// 一个一个传递方法
template: `<div><button @click='$listeners.click' @mouseup='$listeners.mouseup'>点我</button></div>`
})
let vm = new Vue({
el: '#app',
data: {
},
mounted(){
console.log(this.$listeners);
},
methods: {
show() {
alert('show')
},
show2() {
alert('show2')
}
}
})
</script>
3.如果一个个写$listeners下的方法很麻烦,可以用语法糖v-on
- 下面写法效果和上面完全一样的
template: `<div><button v-on='$listeners'>点我</button></div>` // 把事件整体传入
- 还有种写法,一样的可以触发在父组件中绑定的事件,也会执行show方法
<div id="app">
<!-- 其实给组件绑定事件就相当于 this.$on('click',show) 这里$on就相当于发布 -->
<my-button @click='show' @mouseup='show2'></my-button>
</div>
<script>
Vue.component('my-button', {
// template: `<div><button v-on='$listeners'>点我</button></div>`, // 把事件整体传入
// 这里$emit相当于订阅
template: `<div><button @click='$emit('click')'>点我</button></div>`, //触发自己身上的click事件
template: `<div><button @click='test'>点我</button></div>`,
methods: {
test() {
this.$emit('click')
}
}
})
let vm = new Vue({
el: '#app',
data: {
},
mounted() {
console.log(this.$listeners);
},
methods: {
show() {
alert('show')
},
show2() {
alert('show2')
}
}
})
props
1.Prop 是你可以在组件上注册的一些自定义属性。当一个值传递给一个 prop 属性的时候,它就变成了那个组件实例的一个 变量
<blog-post data="My journey with Vue"></blog-post>
props:{
data:{ // 数据名称,组件实例中访问这个值,就像访问 data 中的值一样。
type:String, // 校验类型
default:'', // 默认值
required:false, // 是否必填项
// 自定义验证函数会将该 prop 的值作为唯一的参数代入。该函数返回一个 falsy 的值
validator(){
}
}
}
2.使用 v-bind 绑定一个全是属性的对象,可以传递整个对象到子组件props,和一个个传一样的效果,但是使用是要用$attrs获取,用在给ui框架的组件设置属性是非常方便,直接渲染在标签上了
<div id="app">
<blog-post v-bind="posts"></blog-post>
</div>
<script>
Vue.component('blog-post', {
template: `
<div class="blog-post">
<h3>{{$attrs.id}}</h3>
<div>{{$attrs.title}}</div>
<div>{{$attrs.maxlength}}</div>
<div>{{$attrs.disabled}}</div>
<div>{{$attrs.style}}</div>
<div>{{$attrs.text}}</div>
</div>
`,
mounted() {
console.log(this.post);
}
})
let vm = new Vue({
el: '#app',
data: {
posts: {
id: 1,
title: 'My journey with Vue',
maxlength: 30,
disabled: false,
style: 'width:816px',
text: '123',
text2: '234',
}
}
})
</script>
3.在html中建议不要给标签的大写,属性名字建议单词连接用-代替驼峰
slot 插槽
1.父组件,在标签中间写内容。子组件,用slot标签接收
<div id="app">
<panel>
<span style="color:red">999</span>
</panel>
</div>
<script>
Vue.component('panel', {
template: `<div>123
slot有name,不写的话默认是default
<slot name='default'>name没匹配上就会展示标签中间的内容</slot>
</div>`
})
let vm = new Vue({
el: '#app',
data: {
}
})
</script>
2.具名插槽,slot有name属性,不写的话默认是default,如果修改了那么的话,父组件调用的地方要用template包裹并且定义v-slot:名字(可以简写#名字)
<div id="app">
<panel name='default2'>
//<template v-slot:default2>
<template #default2>
<span style="color:red">999</span>
</template>
</panel>
</div>
<script>
Vue.component('panel', {
template: `<div>123
<slot name='default2'>name没匹配上就会展示标签中间的内容</slot>
</div>`
})
let vm = new Vue({
el: '#app',
data: {}
})
</script>
3.作用域插槽,让插槽内容能够访问子组件中的数据
<div id="app">
<panel name='default2'>
<!-- 作用域插槽,让插槽内容能够访问子组件中的数据 -->
// <template v-slot:default2='data2'>
<template #default2='data2'>
<span style="color:red">999{{data2}}</span>
</template>
</panel>
</div>
<script>
Vue.component('panel', {
template: `<div>123
<slot v-bind:data2='demo2' name='default2'>name没匹配上就会展示标签中间的内容 </slot>
</div>`,
data() {
return {
demo2: '邓良斌'
}
}
})
let vm = new Vue({
el: '#app',
})
</script>
children
1.可以使用children直接获取参数和方法,官网不太建议直接这样使用
2.每个组件有一个唯一标识,_uid,它是组件自身的,是组件的id号(官网我都没找到文档)
mixins
1.混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
2.选项合并,当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
3.混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。推荐将其作为插件发布,以避免重复应用混入。
extend
使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
在 vue 项目中,我们有了初始化的根实例后,所有页面基本上都是通过 router 来管理,组件也是通过 import 来进行局部注册,所以组件的创建我们不需要去关注,相比 extend 要更省心一点点。但是这样做会有几个缺点: 1、组件模板都是事先定义好的,如果我要从接口动态渲染组件怎么办? 2、所有内容都是在 #app 下渲染,注册组件都是在当前位置渲染。如果我要实现一个类似于 window.alert() 提示组件要求像调用 JS 函数一样调用它,该怎么办? 这时候,Vue.extend + vm.$mount 组合就派上用场了。
我们照着官方文档来创建一个示例:
import Vue from 'vue'
const testComponent = Vue.extend({
template: '<div>{{ text }}</div>',
data: function () {
return {
text: 'extend test'
}
}
})
然后我们将它手动渲染:
// 这时候,我们就将组件渲染挂载到 body 节点上了。
const extendComponent = new testComponent().$mount()
// 我们可以通过 $el 属性来访问 extendComponent 组件实例:
document.body.appendChild(extendComponent.$el)
可以用extend做一个 alert 组件来实现类似于原生的全局调用。
inject
1.provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
2.这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
Vue.component("children", {
template: `<div>{{test}}子组件<demo2></demo2></div>`,
inject: ['test'],
mounted() {
console.log(this.test, '子组件可以拿到');
},
components: {
demo2: {
template: `<div>孙组件{{test}}</div>`,
inject: ['test'],
mounted() {
console.log(this.test, '孙组件可以拿到');
},
}
}
})
let vm = new Vue({
el: '#app',
// 允许一个祖先组件向其所有子孙后代注入一个依赖,
// 不论组件层次有多深,并在其上下游关系成立的时间里始终生效
provide: {
test: "1239999"
},
})
ref
1.可以直接获取组建的实例 2.ref 的三种情况 如果给dom 就是一个dom 如果给 v-for 出来的就是数组 如果给组件 那就是组件的实例
<div id="app">
<!-- ref 的三种情况 如果给dom 就是一个dom 如果给 v-for 出来的就是数组 如果给组件 那就是组件的实例 -->
<div ref="div" v-for="i in 3"></div>
<parent ref="parent"></parent>
</div>
<script>
Vue.component("parent",{
data(){
return {isShow:false}
},
methods:{
show(){
this.isShow = true;
},
hide(){
this.isShow = false;
}
},
template:`<div> <span v-if="isShow">parent</span> </div>`
});
let vm = new Vue({
el:'#app',
mounted() {
console.log(this.$refs.div)
this.$refs.parent.show();
setTimeout(()=>{
this.$refs.parent.hide();
},1000)
},
})
</script>
evenBus兄弟组件通信
<div id="app">
<!-- 这里接收的组件要先渲染,发送的组件要后渲染,不然不会触发 -->
<son2></son2>
<son1></son1>
</div>
<script>
// 平级组件通信 可以通过共同的父亲传参
// 一个全局的发布订阅的方式
// eventBus 比较适合 简单的数据流
// 通信的数据 (vuex中)
// new一个vue作为总线
Vue.prototype.$bus = new Vue();
Vue.component('son1', {
template: `<div>大哥</div>`,
mounted() {
console.log('发送');
// 使用$emit方法,触发当前实例上的事件,用以发送
this.$bus.$emit('busDemo', '二弟,饺子好吃')
}
})
Vue.component('son2', {
template: `<div>二弟</div>`,
mounted() {
console.log('接收');
// 使用$on方法,监听当前实例上的自定义事件,用以接收
this.$bus.$on('busDemo', (data) => {
console.log(data, '接收内容');
})
}
})
let vm = new Vue({
el: '#app'
})
</script>