本节目标
- 相关概念
- 父子通信
- 非父子通信
- v-model
- .sync修饰符
- ref / $refs
- $nextTick
相关概念
组件通信就是指组件与组件之间的数据传递
组件的数据是独立的, 无法直接访问其他组件的数据,
想要其他组件的数据, 就要使用组件通信技术
组件关系
总体来说, 组件分为父子关系 和 非父子关系
通信方案
父与子关系: props & $emit (自定义属性/自定义事件)
非父子关系: provide & inject 或者 eventbus (事件总线)
全局方案: Vuex
父子通信
步骤
- 通过自定义属性/自定义事件完成父子通信
- 父组件给子组件添加自定义属性并传值
- 子组件通过props接收数据
- 子组件通过$emit触发父组件的自定义事件
- 父组件通过事件参数接收数据
prop详解
prop就是组件上注册的一些自定义属性, 作用就是向子组件传递数据, 组件可以传递任意类型和数量的prop
不同的prop写法有不同的特点, 数组写法最简单, 基础写法可以限制数据类型, 完整写法可以有更多限制规则
1,组组写法:
2,基础写法:
3,完整写法
说明:
- 自定校验规则是一个函数, 返回true则通过校验, false则未通过
- 未通过教研室, 建议通过console.err() 返回原因, 便于排查
单向数据流
- 父级prop的数据更新, 会向下流动, 影响子组件, 这个数据流动是单向的
- data的数据是自己的, 可以随便改
- pop的数据是外部的, 不能直接改, 要遵循单向数据流
- 口诀: 谁的数据谁负责
非父子通信
1>兄弟组件通信
eventbus方案可以在非父子组件之间, 进行消息传递, 核心是利用Vue实例的事件机制
步骤
- 创建事件总线 (空的Vue实例) (utils/EventBus.js)
- A组件(接收方), 监听Bus实例的事件
- B组件(发送方), 触发Bus实例的事件
2>子孙组件通信
provide & inject 可以实现跨层级的数据共享
步骤
- 父组件通过peovide属性提供数据
- 子孙组件通过inject属性获取数据
v-model
v-model本质上是一个语法糖, 例如应用在输入框上, 就是value属性 和 input事件的合写
除了进行表单的双向数据绑定, 也可以实现父子组件通信 (简化表单组件的封装)
1>v-model原理
- 实现表单数据双向绑定,
- 数据变, 视图跟着变 :value
- 视图变, 数据跟着变 @input
- 在模版中获取事件对象的形参, 使用$evnet
<template>
<div class="app">
<!-- 使用v-model实现表单数据双向绑定 -->
<input v-model="msg1" type="text">
<!-- 拆解v-model手动实现数据双向绑定 -->
<input :value="msg2" @input="msg2 = $event.target.value" type="text">
</div>
</template>
<script>
export default {
data() {
return {
msg1: '',
msg2: '',
}
},
}
</script>
2>封装表单组件
表单类组件封装, 最直接的方式就是使用父子组件通信技术
- 父传子: 数据应该是父组件 props 传递过来的
- 子传父: 监听输入, 子组件传值给父组件修改数据
- 最终实现了父子组件的数据双向绑定
- 右侧为效果图 ->_->
示例
<template>
<div class="app">
<!--@change可以写行内, 便于理解这里不再简化 -->
<BaseSelect :cityId="selectId" @change="handleChange"></BaseSelect>
</div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data() {
return {
selectId: '102',
}
},
components: {
BaseSelect,
},
methods: {
handleChange(e) {
console.log('父组件拿到子组件选中的值', e);
this.selectId = e
}
}
}
</script>
<template>
<div>
<!--这里不能使用v-model, 因为prop是只读的 -->
<!--@change可以写行内, 便于理解这里不再简化 -->
<select :value="cityId" @change="hander">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">武汉</option>
<option value="104">广州</option>
<option value="105">深圳</option>
</select>
</div>
</template>
<script>
export default {
props: {
cityId: String
},
methods: {
hander(e) {
console.log('子组件选中的值', e.target.value);
this.$emit('change', e.target.value)
}
}
}
</script>
3>简化表单组件
使用v-model简化表单组件的封装
- 改造子组件: props必须使用value接收, 事件触发必须使用input
- 简化父组件: 可以使用 v-model 给组件直接绑定数据
- 组件的封装步骤不变, 只是子组件的参数命名有了限制, 但是简化了组件的使用, 非常推荐
<template>
<div>
<!-- 必须通过input触发事件 -->
<select :value="value" @change="$emit('input', $event.target.value)">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">武汉</option>
<option value="104">广州</option>
<option value="105">深圳</option>
</select>
</div>
</template>
<script>
export default {
props: {
// 必须通过value接收数据
value: String
},
}
</script>
<template>
<div class="app">
<!-- 极大的简化了组件的使用 -->
<BaseSelect v-model="selectId"></BaseSelect>
</div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data() {
return {
selectId: '102',
}
},
components: {
BaseSelect,
},
}
</script>
.sync修饰符
使用 .sync 实现父子组件的双向绑定, 简化代码
- 特点: prop属性名, 可以自定义, 非固定value
- 场景: 封装弹框类的基础组件, visible属性 true显示 false隐藏
- 本质: 就是 :属性名 和 @update:属性名 合写
- 对比: 相比如v-model简化代码, .sync简化代码时属性名更灵活
- 表单类的组件使用v-model简化, 弹窗类的组件使用.sync简化
<template>
<div class="app">
<!-- .sync简写 -->
<!-- .sync ==> :属性名 + @update:属性名 -->
<BaseDialog :visible.sync="isShow"></BaseDialog>
<!-- 完整写法 -->
<BaseDialog :visible="isShow" @update:visible="isShow = $event"></BaseDialog>
</div>
</template>
<script>
export default {
data() {
return {
isShow: true,
}
},
}
</script>
<template>
<!-- 2,使用数据 -->
<div class="base-dialog-wrap" v-show="visible">
<div class="base-dialog" >
<div class="title">
<h3>温馨提示:</h3>
<button @click="close" class="close">x</button>
</div>
<div class="content">
<p>你确认要退出本系统么?</p>
</div>
<div class="footer">
<button @click="close">确认</button>
<button>取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
// 1,接收数据
props: {
visible: Boolean
},
methods: {
close() {
// 3, 通知父组件修改数据
// 使用.sync后,这里的触发格式为: update:属性名
this.$emit('update:visible', false)
}
}
}
</script>
ref / $refs
利用ref 和 $refs 可以用于获取DOM元素 或 组件实例
- 使用 document.querySelector() 等方法获取DOM, 是在整个HTML页面查找
- 使用 $refs 获取DOM, 只在当前组件内查找, 更加精准和稳定
- 获取DOM: <div ref="boxRef">我是容器</div>
- 获取DOM: this.$refs.BoxRef
- 获取组件实例: <sum ref="sumRef"><sum>
- 调用组件方法: this.$refs.sumRef.组件方法()
- 通过组件实例获取组件数据, 组件的方法要将数据return返回
$nextTick
Vue采用异步更新DOM的方案 (提升性能), 如果要保证本轮DOM更新后, 操作最新的DOM, 使用$nextTick
语法: this.$nextTick(函数体)
相比延时器更加准确可靠