文章目录
Vue学习笔记05
父组件给子组件传值
props用于让组件接受外部传过来的值
//父组件传值 age传的是字符串"18"
<Student name="李四" sex="女" age="18"/>
//vbind动态绑定,绑定是引号里的js表达式的执行的结果,18
<Student name="李四" sex="女" :age="18"/>
//写法1:子组件接受值,简单接受
props:['name','sex','age']
//写法2:子组件接受值,设置希望类型
props:{
name:String,
sex:String,
age:Number
}
//写法3: 子组件接受值,完整写法
props:{
name:{
type:String,
required:true //是否必须
},
age:{
type:Number,//类型
default:99 //默认值
}
}
props是只读属性,子组件不可以修改传入的值。
如果想要修改可以使用中间变量
{{myAge}} //模板中使用
data(){
myAge:this.age //中间变量
},
props:['age'] //优先被设置在vc组件上
注意点
如果子组件不使用prop,子组件的$attrs里会存储传过来的属性,子组件的vc实例上不会存储
如果子组件使用prop,子组件的vc实例上会直接存储传过来的属性,子组件的$attr里不会存储
子组件给父组件传值
- 将自定义事件绑定在子组件的实例vc上,回调函数是在父组件中
1.1 在父组件中自定义事件@自定义事件=’事件回调'
1.2 在父组件中采用ref
获取到子组件的实例,采用$on(‘事件名’,回调函数)
绑定自定义事件 - 子组件触发自定义事件
this.$emit(‘事件名’,数据)
,子组件解绑自定义事件this.$off()
父组件接受子组件的传值 通过函数
v-model绑定的值不能是props传过来的值,因为props的值是不可以修改的
父组件给子组件传递函数,子组件调用传参,父组件获得子组件的参数值
//父组件
<MyHeader :addTodo="addTodo"/>
export default {
name:'App',
components:{MyHeader},
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
}
}
//子组件
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
export default {
name:'MyHeader',
props:['addTodo'],//接收从App传递过来的addTodo
methods: {
add(){
const todoObj ={id:nanoid(),title:this.title,done:false}
//通知App组件去添加一个todo对象
this.addTodo(todoObj)
}
}
}
组件的自定义事件
内置指令->HTML元素使用
自定义指令-> 组件使用
事件绑定的第一种写法 @或v-on
@或v-on 绑定在子组件的vc上
绑定事件:使用@或v-on:将回调函数绑定在子组件的vc上
触发事件:在子组件中触发,$emit(‘事件名’,参数1…,参数2…)
<!-- 第一种写法,使用@或v-on 绑定在子组件的vc上 -->
<Student @getName="getStudentName" @demo="m1"/>
<!-- 一次性事件 -->
<Student @getName.once="getStudentName" @demo="m1"/>
methods: {
getSchoolName(name){
console.log('App收到了学校名:',name)
}
<!--Studetn子组件传值-->
<button @click="sendStudentlName">把学生名给App</button>
methods:{
sendStudentlName(){
//触发Student组件实例身上的getName事件
this.$emit('getName',this.name)
}
}
事件绑定的第二种写法:使用ref + $on
this.$refs.xxx
获取到组件的vc实例
更灵活,可以控制绑定时间
使用$on绑定在子组件上
$on('事件名',回调函数)
给组件绑定自定义事件
<!-- 第二种写法,使用ref -->
<Student ref="student"/>
//父组件挂载完毕时
mounted(){
this.$refs.student.$on('atguigu',this.getStudentName)
//this.$refs.student.$once('atguigu',this.getStudentName)一次性事件
}
需要注意回调函数的定义位置,定义在methods中,或者使用箭头函数,不然this的指向可以出问题。
事件触发解绑 $emit $off
触发事件:在绑定事件的组件中触发,$emit('事件名',参数1..,参数2..)
解绑一个事件: 在绑定事件的组件中触发,off('事件名')
解绑多个事件: 在绑定事件的组件中触发,$off(['事件名1','事件名2'...])
解绑所有事件: 在绑定事件的组件中触发,off()
组件绑定原生事件
自定义指令-> 组件使用,所以如果直接写原生事件名会被认为时自定义事件
//需要Student组件$emit触发click事件之后,show回调才会被调用
<Student ref="student" @click="show"/>
修饰符native
表明该事件是内置事件
//需要Student组件$emit触发click事件之后,show回调才会被调用
<Student ref="student" @click.native="show"/>
全局事件总线 任意组件间通讯
全局事件总线的特点
1.所有组件都可以看见
2.需要有$on
、$off
、$emit
VueComponent.prototype.prototype.proto === Vue.prototype
作用:让组件实例对象vc可以访问到Vue原型上的属性和方法
思路
1.事件总线需要在Vue显式原型上,这样所有的组件都可以看见
2.事件总线需要使用Vue显式原型里的$on
、$off
、$emit
函数,所以事件总线需要能看见Vue显式原型,事件总线可以设置成Vue的实例
错误写法
import Vue from 'vue'
const vm = new Vue({
el:'#app',
render:h=>h(App) //①
})
//报错
Vue.prototype.$bus= vm;
上述写法报错的原因
①执行完后,App整个组件已经渲染到了页面上,此时组件中的$on绑定事件已经执行完毕,此时已经晚了。
正确写法
可以写在befoteCreate
生命周期钩子里,befoteCreate的this指向Vue实例(new Vue())
import Vue from 'vue'
const vm = new Vue({
el:'#app',
render:h=>h(App),
beforeCreate(){
Vue.prototype.$bus= this;//安装全局事件总线
}
})
组件中通过this.$bus.$on
this.$bus.$off
`this.
b
u
s
.
bus.
bus.emit 使用
全局事件总线所有组件都可以看见,所以要小心事件名重复。
最好在beforeDestroy钩子
中,使用$off
去解绑当前组件所用到的事件
消息订阅与发布 任意组件间通讯
消息订阅与发布
1.订阅消息:消息名 谁需要数据谁订阅
2.发布消息:消息内容 提供数据的发布消息
依赖消息订阅与发布库,这里选择pubsub-js
库
//谁需要谁引用
import pubsub from 'pubsub.js'
//订阅消息,每次订阅返回一个新的消息号,
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
console.log("订阅hello消息的回调函数,当hello消息被发布时调用")
}
//发布消息
pubsub.publish('hello',666)
//最好在beforeDestroy钩子中取消消息
pubsub.unsubscribe(消息号);
事件总线区别
对于同名的消息,消息订阅和发布可以取消特定消息号的一个消息,但是事件总线取消同名的一类消息
$nextTick(callback)
$nextTick指定的回调函数callback会在DOM节点更新完毕之后再执行
使用场景
当改变数据后,要基于更新后的新DOM进行操作时,要在nextTick所指定的回调函数中执行。
动画效果 transition
作用:在插入、更新或移除DOM时,在合适的时候给元素添加样式名
<transition></transition>
包裹需要动画的代码块,Vue解析时并不会生成真实的标签
通过CSS特定的选择器名字设置动画的执行时机
当有多个动画有不同的效果时,可以给transition标签添加name属性,那么对应的.v-enter-active
等应该变成.hh-enter-active
<!--开始的时候isShow为真,但是开始并没有进入的动画,添加appear后初次渲染就有效果-->
<transition name="hh" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
<style>
/*进入时激活的样式*/
.v-enter-active{
animation: ranan 0.5s linear;
}
/*离开时激活的样式*/
.v-leave-active{
animation: ranan 0.5s linear reverse;
}
@keyframes ranan {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
可以不采用定义动画的方式实现上述效果,可以使用过渡实现
/*进入的过程中,离开的过程中*/
.v-enter-active,.v-leave-active{
transition:0.5s linear;
}
/*进入的起点样式*/
.v-enter{
transform: translateX(-100%);
}
/*进入的终点样式*/
.v-enter-to{
transform: translateX(0);
}
/*离开的起点样式*/
.v-leave{
transform: translateX(0);
}
/*进入的终点样式*/
.v-leave-to{
transform: translateX(-100%);
}
transition-group
<transition></transition>
里面只能有一个元素
<transition-group></transition-group>
里面可以放多个元素,但是每个元素必须要绑定key属性
第三方动画库Animate.css
,引入直接import 'animate.css'
解决ajax跨域问题 配置代理
同源原则:协议名、主机名、端口号都一样
真正意义上的跨域cors
后端使用cors解决跨域,添加特殊的响应头,浏览器看见特殊的响应头,虽然跨域但是服务器同意把数据给客户端,浏览器就把数据给客户端
代理服务器
代理服务器和我们客户端同源,利用服务器与服务器之间没有同源策略的方法实现。(因为服务器之间不适用ajax通信)
利用脚手架配置代理服务器
方式1 不常用
修改vue.config.js
vue-cli配置文件
module.exports = {
devServer:{
proxy:'http:localhost:5000' //代理服务的端口会根据我们客服端的端口自动的配置,这里写需要代理的服务器的地址
}
}
说明
1.发送请求的时候,直接向代理服务器要数据
2.public目录就是代理服务器的根目录
3.代理服务器先会在自己的文件中查找,如果没有再转发给服务器
缺点
1.只能配置一个代理
2.不可以灵活转发,没办法控制走不走代理
方式2 请求前缀
请求前缀在端口号的后面
如:http:localhost:8080/student -> http:localhost:8080/xxx/student
如果这样直接给服务器,服务器收到的是http:localhost:8080/xxx/student,我们需要把/xxx去掉,需要重写路径
module.exports = {
devServer:{
proxy:{
'/xxx':{
//遇见xxx前缀的请求,走代理去服务器端获取
target:'http:localhost:5000',
pathRewrite:{'^/xxx':'']//重写path把开头的/xxx前缀删除
//ws:true, 用于支持websocket
//changeOrigin:true,是否告诉服务器请求源的真实信息,比如这是一台代理服务器端口号是8080。用于控制请求头中的host值
},
'/yyy':{
target:'http:localhost:5001',
pathRewrite:{'^/yyy':'']
}
}
}
}
插槽
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件->子组件
父组件传的html结构会以虚拟节点的形式存储在子组件vc实例的$slots属性里
默认插槽
<!--解析到img标签后,img应该放在Category组件里的哪个位置呢?-->
<Category title="美食" >
<img src="" alt="">
</Category>
<!--Category组件里使用slot告诉img放在这里-->
<div>
<h1>Category组件</h1>
<slot>这段文字是默认值,当使用者没有传递具体结构时出现<slot>
</div>
具名插槽
使用<slot name="xxx">
给插槽命名,使用 slot="xxx"
选择放入哪个插槽
h4和div都想放在footer插槽里面,但是不想再加一个div结构,就可以选择使用<template >
标签
<template >
标签有个独特的具名插槽写法<template v-slot:footer>
,Vue3只支持这种具名插槽写法
<Category title="电影">
<video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
<template v-slot:footer>
<!--<template slot="footer">-->
<div class="foot">
<a href="http://www.atguigu.com">经典</a>
<a href="http://www.atguigu.com">热门</a>
<a href="http://www.atguigu.com">推荐</a>
</div>
<h4>欢迎前来观影</h4>
</template>
</Category>
<!--Category组件-->
<div>
<h1>Category组件</h1>
<slot name="center">这段文字是默认值,当使用者没有传递具体结构时出现<slot>
<slot name="footer">这段文字是默认值,当使用者没有传递具体结构时出现<slot>
</div>
作用域插槽
使用场景:数据在插槽位置,但是根据数据生成的结构需要父组件来决定
数据在子组件(作用域),结构由父组件传。
数据: 子 -> 父 结构:父 -> 子
1.<slot :xxxx="数据">
将数据传给父组件
2.父组件(给插槽传结构的代码)外侧包裹<template scope="yyyy">
标签,也可以写成<template slot-scope="yyyy">
,yyyy
接收到的是插槽传过来的{xxxx:数据}
3.父组件接收的数据是一个对象,对象包含传过来的值。也就是说传过来的值外层包裹了一层对象,所以起名的时候yyy可以和xxx不一样。
<Category title="游戏">
<template scope="ranan">
<ul>
{{ranan}} <!--接收到{game:['红色警戒','穿越火线','劲舞团','超级玛丽'],mag:"hello"}-->
</ul>
</template>
</Category>
<!--category组件-->
<template>
<div class="category">
<slot :game="games">我是默认的一些内容</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
}
},
}
</script>