Vue ④-组件通信 || 进阶语法
组件三大部分
template:只有能一个根元素
style:全局样式(默认):影响所有组件。局部样式:scoped
下样式,只作用于当前组件
script:el 根实例独有,data 是一个函数,其他配置项一致。
组件的样式冲突 scoped
默认情况:写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。
- 全局样式:默认组件中的样式会作用到全局
- 局部样式:可以给组件加上
scoped
属性,可以让样式只作用于当前组件。
scoped 原理
- 给当前组件模板的所有元素,都会添加上一个自定义属性
data-v-hash值
->data-v-5f6a9d56
用于区分开不通的组件。 - css选择器后面,被自动处理,添加上了属性选择器:
div[data-v-5f6a9d56]
div /*(div[data-v-5f6a9d56])*/ {border: 3px solid blue;margin: 30px;
}
data 是一个函数
一个组件的 data 选项必须是一个函数。→ 保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象。
// data() {
// console.log('函数执行了')
// return {
// count: 100,
// }
// },
data: function () {return {count: 100,}
}
组件通信
组件通信, 就是指 组件与组件 之间的数据传递。
- 组件的数据是独立的,无法直接访问其他组件的数据。
- 想用其他组件的数据 → 组件通信。
父子关系使用:props
和 $emit
非父子关系使用: provide
& inject
或 eventBus
通用的方式:Vuex(适合复杂业务场景)。
父子组件通信
- 父组件通过
props
将数据传递给子组件 - 子组件利用
$emit
通知父组件修改更新
父 -> 子
① 父中给子添加属性传值 ② 子 props
接收 ③ 子组件使用
父组件:
<template><div class="app">我是父组件<!-- 1.给组件标签,添加属性方式 赋值 --><Son :title="myTitle"></Son></div>
</template><script>
import Son from "./components/Son.vue"
export default {name: "App",components: {Son,},data() {return {myTitle: "学前端,就来程序员",}},
}
</script><style>
</style>
子组件:
<template><div class="son"><!-- 3.直接使用 props 的值 -->我是Son组件{{ title }}</div>
</template><script>
export default {name: 'Son-Child',// 2.通过props来接受props: ['title'],
}
</script><style>
</style>
子 -> 父
① 子 $emit
发送消息 ② 父中给子添加消息监听 ③ 父中实现处理函数
父组件:
<template><div class="app">我是APP组件<!-- 2.父组件,对消息进行监听 --><Son :title="myTitle" @changeTitle="handleChange"></Son></div>
</template><script>
import Son from "./components/Son.vue"
export default {name: "App",components: {Son,},data() {return {myTitle: "学前端,就来程序员",}},methods: {handleChange(newTitle) {this.myTitle = newTitle}}
}
</script><style>
</style>
子组件:
<template><div class="son">我是Son组件{{ title }}<button @click="changeFn">修改titile</button></div>
</template><script>
export default {name: 'Son-Child',props: ['title'],methods: {changeFn() {// 1.通过 $emit,向父组件发送消息通知this.$emit('changeTitle', '传智教育')}}
}
</script><style>
</style>
prop
Prop 定义:组件上 注册的一些 自定义属性
Prop 作用:向子组件传递数据
特点:
- 可以 传递 任意数量 的prop
- 可以 传递 任意类型 的prop
父组件:
<template><div class="app"><UserInfo:username="username":age="age":isSingle="isSingle":car="car":hobby="hobby"></UserInfo></div>
</template><script>
import UserInfo from './components/UserInfo.vue'
export default {data() {return {username: '小帅',age: 28,isSingle: true,car: {brand: '宝马',},hobby: ['篮球', '足球', '羽毛球'],}},components: {UserInfo,},
}
</script><style>
</style>
子组件:
<template><div class="userinfo"><h3>我是个人信息组件</h3><div>姓名:{{ username }} </div><div>年龄:{{ age }} </div><div>是否单身:{{ isSingle ? '是' : '否' }} </div><div>座驾:{{ car.brand }} </div><div>兴趣爱好 {{ hobby.join(', ') }} </div></div>
</template><script>
export default {props: ['username', 'age', 'isSingle', 'car', 'hobby']
}
</script><style>
</style>
props 校验
组件的 prop 可以乱传么?
为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误。
语法:
- 类型校验
- 非空校验
- 默认值
- 自定义校验
props: {校验的属性名: 类型 // Number String Boolean ...
},props: {校验的属性名: {type: 类型, // Number String Boolean ...required: true, // 是否必填default: 默认值, // 默认值validator (value) {// 自定义校验逻辑return 是否通过校验}}
},
父组件:
<template><div class="app"><BaseProgress :w="width"></BaseProgress></div>
</template><script>
import BaseProgress from './components/BaseProgress.vue'
export default {data() {return {width: 50,}},components: {BaseProgress,},
}
</script><style>
</style>
子组件:
<template><div class="base-progress"><div class="inner" :style="{ width: w + '%' }"><span>{{ w }}%</span></div></div>
</template><script>
export default {// props: ["w"],// 1.基础写法(类型校验)// props: {// w: Number// }// 2.完整写法(类型、是否必填、默认值、自定义校验)props: {w: {type: Number,// 是在父类使用组件时,看其是否用自定义属性绑定了require: true,default: 80,validator(value) {if(value >= 0 && value <= 100) {return true} else {console.error("错误")return false}}}}
}
</script><style>
</style>
prop & data、单向数据流
三者共同点:都可以给组件提供数据。
区别:
- data 的数据是自己的 → 随便改
- prop 的数据是外部的 → 不能直接改,要遵循 单向数据流
单向数据流:父级 prop 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。
谁的数据谁负责
非父子通信 (拓展) - event bus 事件总线
作用:非父子组件之间,进行简易消息传递。(复杂场景 → Vuex)
- 创建一个都能访问到的事件总线 (空 Vue 实例) → utils/EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
- B 组件(发送方),触发 Bus 实例的事件
sendMsgFn () {Bus.$emit('sendMsg', '今天天气晴朗')
}
- A 组件(接收方),监听 Bus 实例的事件
created () {Bus.$on('sendMsg', (msg) => {this.msg = msg})
}
EveentBus.js
// 1. 创建一个都能访问到的事件总线(空的 Vue实例)
import Vue from 'vue'
const Bus = new Vue()
export default Bus
BaseA.vue:
<template><div class="base-a">我是A组件(接受方)<p>{{ msg }}</p> </div>
</template><script>
import Bus from '../utils/EventBus'
export default {created () {// 2. 在 A组件(接收方),进行监听 Bus 的事件(订阅事件)Bus.$on('sendMsg', (msg) => {this.msg = msg})}, data () {return {msg: ''}}
}
</script><style>
</style>
BaseB.vue:
<template><div class="base-b"><div>我是B组件(发布方)</div><button @click="sendMsgFn">发送消息</button></div>
</template><script>
import Bus from '../utils/EventBus'
export default {methods: {sendMsgFn () {// 3. 在 B组件(发送方)触发事件的方式传递参数(发布消息)Bus.$emit('sendMsg', '今天天气晴朗')}}
}
</script><style>
</style>
BaseC.vue:
<template><div class="base-c">我是C组件(接受方)<p>{{ msg }}</p> </div>
</template><script>
import Bus from '../utils/EventBus'
export default {created () {// 2. 在 A组件(接收方),进行监听 Bus的事件(订阅事件)Bus.$on('sendMsg', (msg) => {this.msg = msg})}, data () {return {msg: ''}}
}
</script><style>
</style>
非父子通信 (拓展) - provide & inject
provide & inject 作用:跨层级共享数据。
- 父组件 provide 提供数据
export default {provide () {return {color: this.color,userInfo: this.userInfo}},
}
- 子/孙组件 inject 取值使用
export default {inject: ['color', 'userInfo']
}
父组件:
<template><div class="app">我是APP组件<button @click="change">修改数据</button><SonA></SonA></div>
</template><script>
import SonA from './components/SonA.vue'
export default {provide () {return {color: this.color,userInfo: this.userInfo}},data() {return {color: 'pink', // 简单类型(非响应式)userInfo: { // 复杂类型(响应式)name: 'zs',age: 18,},}},methods: {change () {this.name = 'green',this.userInfo.name = 'ls',this.userInfo.age = 20}},components: {SonA,},
}
</script><style>
</style>
SonA.vue:
<template><div class="SonA">我是SonA组件<GrandSon></GrandSon></div>
</template><script>
import GrandSon from '../components/GrandSon.vue'
export default {components:{GrandSon}
}
</script><style>
</style>
GrandSon.vue:
<template><div class="GrandSon">我是GrandSon组件<div>颜色:{{ color }}</div><div>用户名:{{ userInfo.name }}</div><div>用户年龄:{{ userInfo.age }}</div></div>
</template><script>
import { provide, inject } from 'vue'
export default {inject: ['color', 'userInfo'],
}
</script><style>
</style>
进阶语法
v-model 原理
原理:v-model
本质上是一个语法糖。例如应用在输入框上,就是 value属性 和 input事件 的合写。
作用:提供数据的双向绑定
- 数据变,视图跟着变
:value
- 视图变,数据跟着变
@input
注意:$event
用于在模板中,获取事件的形参。
<template><div class="app"><input v-model="msg1" type="text" /><br /><!-- 模板中获取事件的形参 -> $event获取 --><input :value="msg2" @input="msg2 = $event.target.value" type="text" ></div>
</template><script>
export default {data() {return {msg1: '',msg2: ''}},
}
</script><style>
</style>
表单类组件封装
表单类组件 封装 -> 实现 子组件 和 父组件数据 的双向绑定。
- 父传子:数据 应该是父组件 props 传递 过来的,拆解 v-model 绑定数据
- 子传父:监听输入,子传父传值给父组件修改。
App.vue
<template><div class="app"><!-- 此处传递过来的是 e.target.value --><BaseSelect @changeId="selectId = $event" :cityId="selectId"></BaseSelect></div>
</template><script>
import BaseSelect from './components/BaseSelect.vue'
export default {data() {return {selectId: '104',}},components: {BaseSelect,},methods: {handleChange(e) {console.log(e);this.selectId = e}}
}
</script><style>
</style>
BaseSelect.vue
<template><div><select :value="cityId" @change="handleChange"><option value="101">北京</option><option value="102">上海</option></select></div>
</template><script>
export default {props: {cityId: String,},methods: {handleChange (e) {this.$emit('changeId', e.target.value)}}
}
</script><style>
</style>
可以直接使用 v-model
简化代码
App.vue
<template><div class="app"><!-- v-model => :value + @input --><BaseSelect v-model="selectId"></BaseSelect></div>
</template><script>
import BaseSelect from './components/BaseSelect.vue'
export default {data() {return {selectId: '103',}},components: {BaseSelect,},
}
</script><style>
</style>
BaseSelect.vue
<template><div><select :value="value" @change="handleChange"><option value="101">北京</option><option value="102">上海</option></select></div>
</template><script>
export default {props: {value: String,},methods: {handleChange (e) {this.$emit('input', e.target.value)}}
}
</script><style>
</style>
.sync 修饰符
作用: 可以实现 子组件 与 父组件数据 的 双向绑定,简化代码
特点: prop
属性名,可以自定义,非固定为 value
场景: 封装弹框类的基础组件, visible属性 true显示 false隐藏
本质: 就是 :属性名
和 @update:属性名
合写
<BaseDialog :visible.sync="isShow" />
--------------------------------------
<BaseDialog:visible="isShow"@update:visible="isShow = $event"
/>
props: {visible: Boolean
}close () {this.$emit('update:visible', false)
}
ref 和 $refs
作用: 利用 ref
和 $refs
可以用于 获取 dom 元素, 或 组件实例。
特点:查找范围 → 当前组件内 (更精确稳定)
获取 dom:
- 目标标签 - 添加 ref 属性
<div ref="dom"></div>
- 通过 this.$refs.xxx, 获取目标 dom 元素
mounted () {console.log(this.$refs.dom)
},
<div ref="mychart" class="box">子组件</div>
// 基于准备好的dom,初始化 echarts 实例
// const myChart = echarts.init(document.querySelector('.box'))
const myChart = echarts.init(this.$refs.mychart)
获取组件:
- 目标组件 – 添加 ref 属性
<BaseForm ref="baseForm"></BaseForm>
- 恰当时机, 通过 this.$refs.xxx, 获取目标组件,就可以调用组件对象里面的方法
this.$refs.baseForm.组件方法()
Vue异步更新、$nextTick
需求: 编辑标题, 编辑框自动聚焦
-
点击编辑,显示编辑框
-
让编辑框,立刻获取焦点
this.isShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点
问题:“显示之后”,立刻获取焦点是不能成功的!
原因: Vue 是 异步更新 DOM (提升性能)
解决方案:
$nextTick
:等 DOM 更新后, 才会触发执行此方法里的函数体
语法: this.$nextTick(函数体)
this.$nextTick(() => {this.$refs.inp.focus()
})
<template><div class="app"><div v-if="isShowEdit"><input type="text" v-model="editValue" ref="inp" /><button>确认</button></div><div v-else><span>{{ title }}</span><button @click="handleEdit">编辑</button></div></div>
</template><script>
export default {data() {return {title: '大标题',isShowEdit: false,editValue: '',}},methods: {handleEdit () {this.isShowEdit = true// 等 Dom更新后,才会触发执行里面的方法体this.$nextTick(() => {this.$refs.inp.focus()})}},
}
</script><style>
</style>
ShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点
问题:“显示之后”,立刻获取焦点是不能成功的!
原因: Vue 是 异步更新 DOM (提升性能)
解决方案:
$nextTick
:等 DOM 更新后, 才会触发执行此方法里的函数体
语法: this.$nextTick(函数体)
this.$nextTick(() => {this.$refs.inp.focus()
})
<template><div class="app"><div v-if="isShowEdit"><input type="text" v-model="editValue" ref="inp" /><button>确认</button></div><div v-else><span>{{ title }}</span><button @click="handleEdit">编辑</button></div></div>
</template><script>
export default {data() {return {title: '大标题',isShowEdit: false,editValue: '',}},methods: {handleEdit () {this.isShowEdit = true// 等 Dom更新后,才会触发执行里面的方法体this.$nextTick(() => {this.$refs.inp.focus()})}},
}
</script><style>
</style>