监听器 watch
用于监听对应的数据变化来进行相关的处理
三个监听属性
- handler :默认写函数就是handler
- deep :深度监听默认为false,对象里面的数据更改不会被监听到,如果为true就能够被监听到
- immediate :是否编译的时候就监听
<div id="app">
{{number}}
<button @click="number=2">改变number的值</button>
<button @click="msg='不好'">msg的值</button>
<button @click="user.username='tom'">改变username的值</button>
<button @click="user.age='20'">改变age的值</button>
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
number: 1,
msg: '你好',
user: {
username: "tony",
age: "19"
}
},
watch: {
//名字和监听的属性名要一致
//默认使用的handler,deep和immediate默认为false
//handler内部有两个参数,为newValue, oldValue
number(newValue, oldValue) {
//watch监听的是data中的数据,但是内部存储的为_data的数据,所以不会递归
this.number = 10 //里面的代码只执行一次
},
//监听对象
msg: {
handler(newValue, oldValue) {
console.log(newValue, oldValue);
},
deep: true,
// immediate: true//立即执行
},
//监听对象中的属性,需要打引号
"user.username": {
handler(newValue, oldValue) {
console.log(newValue, oldValue);
},
// deep: false
},
//监听对象,返回的新旧对象为同一个对象,所以返回的值也一值
"user": {
handler(newValue, oldValue) {
console.log(newValue.age, oldValue.age);
},
deep: true
}
}
})
</script>
computed 和 watch 的区别
- computed:不支持异步,有缓存,内置get、set属性
- watch:支持异步,没有缓存,内置 handler、deep、immediate属性
动画
基础动画 tansition的内置样式:
- v-enter :从隐藏到显示的样式
- v-enter-to :从隐藏到显示到达的样式
- v-enter-active :从隐藏到显示激活的样式
- v-leave :从显示到隐藏的样式
- v-leave-to :从显示到隐藏到达的样式
- v-leave-active :从显示到隐藏激活的样式
如果需要更改对应的样式名前缀v,需要在对应的transition中添加一个name属性这个name的值就是对应的前缀
<style>
.box {
background-color: palevioletred;
width: 100px;
height: 100px;
}
/* 显示状态 */
/* .v-enter,
.v-leave-to {
opacity: 0;
transform: translate(100px, 100px);
} */
/* 激活状态 */
/* .v-enter-active,
.v-leave-active {
transition: all 3s;
} */
/* 内置样式 */
/* v-enter 从隐藏到显示的样式
v-enter-to 从隐藏到显示到达的样式
v-enter-active 从隐藏到显示激活的样式
v-leave 从显示到隐藏的样式
v-leave-to 从显示到隐藏到达的样式
v-leave-active 从显示到隐藏激活的样式
*/
/* 更改样式名前缀,就是将v改为fade,需要在动画添加的元素上再加一个name属性赋值fade属性值 */
/* 显示状态 */
.fade-enter,
.fade-leave-to {
opacity: 0;
transform: translate(100px, 100px);
}
/* 激活状态 */
.fade-enter-active,
.fade-leave-active {
transition: all 3s;
}
</style>
</head>
<body>
<div id="app">
<button @click="isShow=!isShow">显示隐藏</button>
<!-- vue内置的transition标签 -->
<transition name="fade">
<div v-show="isShow" class="box"></div>
</transition>
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
isShow: false
}
})
</script>
</body>
第三方css库(animate.css)引入
- enter-active-class="animated fadeInRight":从隐藏到显示激活的样式
- enter-class="animated fadeInRight”:从隐藏到显示的样式
- enter-to-class="animated fadeInRight":从隐藏到显示到达的样式
- leave-active-class="animated fadeOutRightBig":从显示到隐藏激活的样式
- leave-to-class="animated fadeOutRightBig":从显示到隐藏到达的样式
- leave-class="animated fadeOutRightBig":从显示到隐藏的样式
- :duration="{ enter: 1000, leave: 1000 }" //绑定的对应的时间
导入
<!-- 第三方CSS库链入 -->
<link rel="stylesheet" href="./lib/animate.min.css">
<!-- 第三方CSS库链入 会覆盖第一个链入的-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
<!-- 第三方CSS库链入 -->
<link rel="stylesheet" href="./lib/animate.min.css">
<!-- 第三方CSS库链入 会覆盖第一个链入的-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
<style>
.box1 {
width: 100px;
height: 100px;
background-color: peru;
}
.box2 {
width: 100px;
height: 100px;
background-color: peru;
animation-duration: 5s;
}
</style>
</head>
<body>
<div id="app">
<button @click="isShow=!isShow">显示隐藏</button>
<!-- <transition enter-to-class="animated fadeInRight" leave-to-class="animated fadeInLeft" duration="2000">
<div class="box1" v-show="isShow"></div>
</transition> -->
<transition enter-to-class=" animate__fadeInTopLeft " leave-to-class=" animate__fadeInTopRight ">
<div class="box2" v-show="isShow"></div>
</transition>
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
isShow: false
}
})
</script>
使用js事件制作动画
- beforeEnter:进入之前
- enter:进入
- afterEnter:进入之后
<style>
.box {
width: 100px;
height: 100px;
background-color: peru;
}
</style>
</head>
<body>
<div id="app">
<button @click="isShow=!isShow">显示隐藏</button>
<transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
<div class="box" v-show="isShow"></div>
</transition>
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
isShow: false
},
methods: {
beforeEnter(el) {
el.style.opacity = 1;
el.style.transform = 'translate(100px,100px)'
el.style.transition = 'all 3s'
},
enter(el, done) {
//简单的重绘offsetLeft\offsetTop
el.offsetLeft
//给el添加相关的样式
el.style.width = "200px"
el.style.opacity = 0
el.style.transform = 'translate(0px)'
//已经完成
done()
},
afterEnter(el) {}
}
})
</script>
</body>
动画组:transition-group
- 跟transition的使用一致 (可以包含多个内容 里面遍历的内容必须具备key值)
- appear 设置初始渲染
<transition-group appear enter-to-class =" animated fadeInRight ">
<li v-for =" v in arr " :key = "v" > {{ v }} </li>
</transition-group>
<div id="app">
<input type="text" @keydown.enter="add" v-model="value">
<transition-group appear enter-to-class="animated fadeInRight">
<li v-for="v in arr" :key="v">{{v}}</li>
</transition-group>
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
arr: [1, 2, 3, 4, 5],
value: ''
},
methods: {
add() {
this.arr.push(this.value)
}
},
})
</script>
组件
组件是抽取的模板,它是模块化的体现。它抽取了html+css+JavaScript组成的一个对象。组件的设计是为了复用。
内置组件
- transition
- transition-group
- component
组件分类
- 全局组件:挂载到vue的对应的构造之上,声明在vue实例之前
- 局部组件:先定义,挂载到对应的vue实例内
组件使用
- 组件使用,标签只能全小写,-分割大写
- 双标签写法,里面如果需要嵌套内容使用 :<first-component> </first-component>
- 单标签写法,不要嵌套内容使用: <first-component />
<div id="app">
<!-- 组件使用 标签只能全小写 -分割大写 -->
<!-- 双标签写法 里面如果需要嵌套内容使用 -->
<first-component></first-component>
<!-- 单标签写法 不要嵌套内容使用-->
<!-- <first-component /> -->
<second-component></second-component>
<second-component />
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
// 全局组件声明
let firstComponent = Vue.component('firstComponent', {
//全局组件 声明必须在new vue实例之前
//包含了vue的一些属性
//组件内容的data是一个函数
//为什么他是个函数 组件允许互相嵌套(保证数据的唯一性和隔离性) 保证地址不能被修改
data() {
return {
msg: '我是全局组件 '
}
},
//template里面包含对应的html代码 最外层只能有一个标签 所有的内容必须包含在这个标签里面
template: `
<div>
{{msg}}
<button @click="handlerClick">点击</button>
</div>`,
methods: {
handlerClick() {
console.log(this.msg);
}
}
});
//局部组件
//先定义 抽取对象 组件相关的内容
//在对应的vue实例中的components注册
let secondComponent = {
//包含了vue的一些属性
data() {
return {
msg: '我是局部组件 '
}
},
//template里面包含对应的html代码,最外层只能有一个标签,所有的内容必须包含在这个标签里面
template: '<div>{{msg}}<button @click="handlerClick">点击</button></div>',
methods: {
handlerClick() {
console.log(this.msg);
}
}
}
new Vue({
el: "#app",
data: {
},
components: {
//组件的组件名:对应的组件内容对象
// second:secondComponent
secondComponent
}
})
</script>
【组件的注意事项】
- 组件内部的属性和vue实例的属性是一致的 (Vue实例是最大的组件)
- 组件内部data属性是一个函数,通过返回对象的形势来返回数据 (对象会覆盖 造成全局污染)
- 组件内的显示利用template属性,template属性的根节点只能有一个,不然会报错
- 组件的使用直接把组件名当作标签名(不能使用驼峰命名法,使用-来分割驼峰)
- 组件的使用可以是单标签也可以是双标签
面试题:组件内部的data为什么是一个函数
- 因为抽取组件是复用,如果复用多个组件,里面的data如果被设计成一个对象,那么对应的组件的地址就会共享。这个时候对应的数据就会被污染。
- 所以设计为一个函数,以函数返回对象的形式可以保证每个返回的对象的地址都是独立不会相互的影响。
- 保证了数据的唯一性和隔离性。
组件切换
- 组件的切换 :is传入对应的组件名
<component :is="msg"></component>
div id="app">
<button @click="msg=(msg=='one'?'two':'one')">切换</button>
<!-- 组件的切换 :is传入对应的组件名-->
<component :is="msg"></component>
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
let one = {
template: `<div>组件1</div>`
}
let two = {
template: `<div>组件2</div>`
}
new Vue({
el: "#app",
data: {
msg: "one"
},
components: {
one,
two
}
})
</script>
组件间的关系
- 父子关系
- 兄弟关系
- 没有关系
父子通信(属性传值)
父传子:
父组件准备(属性绑定)
//准备父组件
let father = {
template: `<div>我是父组件<child :message="msg"/><child /></div>`,
data() {
return {
msg: "父组件数据"
}
},
components: {
child
}
}
子组件接收 (props来接收)
//准备子组件
let child = {
template:`<div>我是子组件{{message}}</div>`,
data(){
return {
msg:
}
},
// 子组件使用props来接收
// props:['message'] //第一种写法 里面为接收的属性名(这个属性名它可以直接使用(当成data中的数据))
props:{ //第二种写法 推荐
//接收的属性名:{}
message:{
default:'吃饭', //指定默认值
type:String //类型验证
}
}
}
子传父:
子组件准备 通过$emit来派发对应的事件
//准备子组件
let child = {
template: `<div>我是子组件 <button @click="childHandler">父组件获取子组件数据</button></div>`,
data() {
return {
msg: "子组件数据"
}
},
methods: {
childHandler() {
//执行,内有两个参数,一个为事件名,另一个为参数
this.$emit('send', this.msg);
}
},
}
父组件中监听对应的事件执行并接收数据
//准备父组件
let father = {
//@send="fatherHandler" 进行一个事件执行监听
template: `<div>我是父组件<child @send="fatherHandler"/>{{result}}</div>`,
data() {
return {
msg: "父组件数据",
result: ''
}
},
components: {
child
},
methods: {
//emit中的参数可以被处理函数调用
fatherHandler(arg) {
console.log(arg); //子组件数据
this.result = arg
}
}
}
总结
- 父传子是利用属性进行传值,子组件利用props来接收
- 子传父是利用观察者模式进行传值,子组件通过$emit来派发对应的事件,父组件通过事件监听的处理函数来接收数据
- 父传子中props中的default,如果是一个对象类型那么最好通过工厂函数来返回新的对象
- 如果在传值过程中你不希望接收方的组件来更改你的数据,那么传递的数据最好进行深拷贝
兄弟组件通信
- 先进行子传父,再进行父传子,必须要有同父组件 (支持所有组件但不建议的,除非本身俩个元素就有一个父组件)
- 通过bus传值
bus传值:
- 通过一个公共的bus对象来传递的值
- 通过一个创建新的vue实例对象 ,来帮助传值(观察者传值 $emit 派发事件 $on 监听事件)
- $emit派发的事件的携带数据,会被 $on监听的事件的处理函数接收
<div id="app">
<first-brother></first-brother>
<last-brother></last-brother>
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
//创建一个bus
let bus = new Vue()
let firstBrother = {
template: `<div>大哥的元素<button @click="handler">获取大哥的数据</button></div>`,
data() {
return {
msg: "大哥的数据"
}
},
methods: {
handler() {
bus.$emit('send', this.msg)
}
},
}
let lastBrother = {
template: `<div>小弟的元素{{message}}</div>`,
data() {
return {
message: ""
}
},
created() {
bus.$on('send', (arg) => {
console.log(arg);
return this.message = arg
})
}
}
new Vue({
el: "#app",
data: {},
components: {
firstBrother,
lastBrother
}
})
</script>
provide和inject传值
它主要是为高阶组件/组件库提供用例的,它可以是用于跨组件传值(不建议使用),它要基于父子关系,可以跨层级传值。
- provide 主要是用于传递数据
- provide 是一个函数,通过 return 对象的形式将数据进行传递
- provide 和 inject 默认不是传递响应式数据
- inject 主要用于接收 provide 传递的数据 (可以字符串也可以是对象)
<div id="app">
{{value}} {{msg}}
<button @click="msg='hi'">更改msg的值</button>
<parent></parent>
</div>
<script src="./lib/vue.js"></script>
<script>
let child = {
template: `<div>子组件{{dataCopy.msg}}<button @click="send">给爷爷组件传值</button></div > `,
// template: ` <div> 子组件 {{msg}} {{changeValue}} <button @click = "send"> 给爷爷组件传值 </button></div> `,
//通过inject接收provide传递的数据
// inject:['msg',"changeValue"], //类似props 直接使用
inject: ["dataCopy", "changeValue"], //第一种写法
//接收的第二种写法
// inject: {
// msg: {
// default: "哈哈",
// type: String
// },
// changeValue: {
// default: "哈哈"
// }
// },
data() {
return {
arg: "传递的数据 "
}
},
methods: {
send() {
this.changeValue(this.arg)
}
},
updated() {
console.log(this.dataCopy)
},
}
let parent = {
template: ` <div>父组件<child /></div>`,
components: {
child
}
}
new Vue({
el: "#app",
data: {
msg: "hello ",
value: "" //用于接收对应的子组件传递的数据
},
components: {
parent
},
methods: {
changeValue(v) {
this.value = v
}
},
//provide是一个函数 通过return 对象的形式将数据进行传递
//provide和inject默认不是传递响应式数据
//如果需要响应式 只要将provide里面的传递的变成响应式就ok
provide() {
// return {
// msg: this.msg,
// //子组件传值给对应的爷爷组件 需要传递一个函数过去通过函数调用来更改对应的数据
// changeValue: this.changeValue,
// }
//通过传递响应式数据来达到响应式 (默认不是响应式)
return {
changeValue: this.changeValue,
dataCopy: this.$data
}
}
})
</script>
ref传值
ref 传值主要传递是当前的组件,通过 ref 标记,通过 $refs 获取
【注意事项】
- ref 如果是用于普通的 dom 元素,那么接收的就是一个 dom 元素对象。
- 如果用于组件那么接收到就是一个组件对象。
- 给对应的标签或者组件设置 ref 属性,如:<com ref="com"></com>
- 通过 $refs 获取,如:this.$refs.com
- ref 需要在dom已经渲染完成后才会有,在使用的时候确保dom已经渲染完成。在生命周期 mounted(){} 钩子中调用,或者在 this.$nextTick(()=>{}) 中调用。
<div id="app">
{{message}}
<input ref="inp" type="text">
<button @click="handler">获取input的value值</button>
<com ref="com"></com>
</div>
<script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
<script>
let com = {
template: `<div>com组件</div>`,
data() {
return {
msg: "com组件的数据"
}
},
methods: {
sayHello() {
console.log("com的方法");
}
}
}
new Vue({
el: "#app",
data: {
message: ''
},
// 需要获取dom元素
mounted() {
this.message = this.$refs.com.msg
// 获取com的方法
this.$refs.com.sayHello()
},
components: {
com
},
methods: {
handler() {
console.log(this.$refs.inp.value);
}
},
})
</script>