第四天
scoped样式冲突
默认情况: 写在组件中的样式会全局生效 →因此很容易造成多个组件之间的样式冲突
- 全局样式:默认组件中的样式会作用到全局
- 局部样式:可以给组件加上scoped属性,可以让样式只作用于当前组件
scoped原理
- 当前组件内的表圈都被添加"data-v-hash值"的属性
- css选择器都被添加[data-v-hash值]的属性选择器
data是一个函数
一个组件的data选项必须是一个函数→保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会新执行一次data函数,得到一个新对象
data () {
return {
count:100
}
},
组件通信
- 两种组件关系分类 和 对应的组件通信方案
父子关系 → props & $emit
非父子关系 → provide & inject 或 eventbys
通用方案 → vuex - 父子通信方案的核心流程
- 父传子props:
①父中给子添加属性值 ②子props接受 ③使用
- 子传父$emit:
①子 $emit发送消息②父中给子添加消息监听③父中实现处理函数
props详解
什么是prop
prop定义:组件上注册的一些自定义属性
prop作用:向子组件传递数据
特点:
- 可以传递任意数量的prop
- 可以传递任意类型的prop
props校验
作用: 为prop指定验证要求,不符合要求,控制台就会有错误提示→帮助开发者,快速发现错误
语法:
- 类型校验(最常用)
props:{
校验的属性名:类型 //Number String...
},
- 非空校验
- 默认值
- 自定义校验
props:{
校验的属性名:{
type:类型,// Number String
required:true,//是否必填
default:默认值,//默认值
validator(value){
//自定义校验逻辑
return 是否通过校验
}
}
}
prop & data 、单向数据流
共同点:都可以给组件提供数据
区别:
- data的数据是自己的 → 随便改
- prop的数据是外部的 → 不能直接改,要遵循单向数据流
单向数据流:父级prop的数据更新,会向下流动,影响子组件。这个数据流动是单向的。
实践案例:小黑记事本
效果图:
核心步骤:
- 拆分基础组件
新建组件 → 拆分存放结构 → 导入注册使用 - 渲染待办任务
提供数据(公共父组件)→ 父传子传递list → v-for 渲染 - 添加任务
收集数据 v-model →监听事件 → 子传父传递任务 → 父组件unshift - 删除任务
监听删除携带id → 子传父传递id → 父组件filter删除 - 底部合计和清空功能
底部合计:父传子传递list → 合计展示
清空功能:监听点击 → 子传父通知父组件 → 父组件清空 - 持久化存储
watch监视数据变化,持久化到本地
核心代码如下:
//App.vue
<template>
<!-- 主体区域 -->
<section id="app">
<TodoHeader @add="handleAdd"></TodoHeader>
<TodoMain :list="list" @del="handelDel"></TodoMain>
<TodoFooter :list="list" @clear="handelClear"></TodoFooter>
</section>
</template>
<script>
import TodoHeader from './components/TodoHeader.vue';
import TodoMain from './components/TodoMain.vue';
import TodoFooter from './components/TodoFooter.vue';
export default {
data() {
return {
list:JSON.parse(localStorage.getItem('list')) || [
{id:1,name:'打篮球'},
{id:2,name:'看电影'},
{id:3,name:'逛街'},
]
};
},
methods:{
handleAdd (todoName) {
this.list.unshift({
id:+new Date(),
name: todoName
})
},
handelDel (id) {
this.list=this.list.filter(item => item.id !== id)
},
handelClear(){
this.list=[]
}
},
watch:{
list:{
deep:true,
handler(newValue) {
localStorage.setItem('list',JSON.stringify(newValue))
}
}
},
components: {
TodoHeader,
TodoMain,
TodoFooter
},
}
</script>
<style>
</style>
//TodoHeader.vue
<template>
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input
@keyup.enter="handleAdd"
v-model="todoName" placeholder="请输入任务" class="new-todo" />
<button @click="handleAdd" class="add">添加任务</button>
</header>
</template>
<script>
export default {
data () {
return {
todoName: ''
}
},
methods:{
handleAdd() {
if(this.todoName.trim() === ''){
alert('任务名不能为空')
return
}
this.$emit('add',this.todoName)
this.todoName = ''
}
}
}
</script>
<style>
</style>
//TodoMain.vue
<template>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list" :key="item.id">
<div class="view">
<span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label>
<button @click="handleDel(item.id)" class="destroy"></button>
</div>
</li>
</ul>
</section>
</template>
<script>
export default {
props:{
list:Array
},
methods:{
handleDel (id) {
this.$emit('del',id)
}
}
}
</script>
<style>
</style>
//TodoFooter.vue
<template>
<!-- 统计和清空 -->
<footer class="footer">
<!-- 统计 -->
<span class="todo-count">合 计:<strong> {{ list.length }} </strong></span>
<!-- 清空 -->
<button @click="clear" class="clear-completed">
清空任务
</button>
</footer>
</template>
<script>
export default {
props:{
list:Array
},
methods:{
clear(){
this.$emit('clear')
}
}
}
</script>
<style>
</style>
非父子通信(拓展) - event bus 事件总线
作用:飞父子组件之间,进行简易消息传递(复杂场景 → Vuex)
- 创建一个都能访问到的事件总线(空 Vue 实例) → utils/EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
- A组件(接收方),监听Bus实例的事件
created () {
Bus.$on('sendMsg',(msg) => {
this.msg = msg
})
}
- B组件(发送方),出发Bus实例的事件
Bus.$emit('sendMsg','这是一个消息')
非父子通信(拓展) - provide & inject
provide & inject 作用:跨层级共享数据
- 父组件 provide 提供数据
export default{
provide () {
return {
//普通类型【非响应式】
color: this.color,
//复杂类型【响应式】(推荐)
userInfo: this.userInfo,
}
}
}
- 子/孙组件 inject 取值使用
export default {
inject: ['color','userInfo'],
created () {
console.log(this.color,this.userInfo)
}
}
v-model 原理
原理:v-model本质上是一个语法糖。例如应用在输入框上,就是value属性和input属性的合写。
作用:提供数据的双向绑定
①数据变,视图跟这边 :value
②视图变,数据跟着变 @input
注意:¥event用于在模版中,获取事件的形参
<template>
<div class="app">
<input v-model="msg1" type="text" />
<input :value="msg2" @input="msg2 = $event.target.value" type="text" >
</div>
</template>
表单类组件封装
表单类组件封装 → 实现子组件和父组件数据的双向绑定
①父传子:数据应该是父组件props传递过来的,v-model拆解绑定数据
②子传父:监听输入,子传父传值给父组件修改
本质:实现了子组件和父组件数据的双向绑定
<!--父组件-->
<BaseSelect :cityId="selectId" @事件名="selectId = $event"></BaseSelect>
<!--子组件-->
<select :value="cityId" @change="handleChange">...</select>
//子组件
props: {
cityId:String
},
methods: {
handleChange (e) {
this.$emit('事件名', e.target.value)
}
}
v-model简化代码
父组件v-model简化代码,实现子组件和父组件数据双向绑定
①子组件中:props通过value接受,事件触发input
②父组件中:v-model给组件直接绑数据
<!--父组件-->
<BaseSelect v-model="selectId"></BaseSelect>
<!--子组件-->
<select :value="value" @change="handleChange">...</select>
//子组件
props: {
value:String
},
methods: {
handleChange (e) {
this.$emit('input', e.target.value)
}
}
.sync修饰符
作用:可以实现子组件和父组件数据的双向绑定,简化代码
特点:prop属性名,可以自定义,非固定为value
场景:封装弹框类的基础组件,visible属性 true显示 false隐藏
本质:就是 :属性名 和 @update:属性名 合写
<!--父组件-->
<BaseDialog :visible.sync="isShow"></BaseDialog>
//子组件
props:{
visible:Boolean
},
methods:{
close () {
this.$emit('update:visible', false)
}
}
ref 和 $refs
作用:利用 ref 和 $refs 可以用于 获取 dom 元素,或 组件实例
特点:查找范围 → 当前组件内(更精确稳定)
①获取dom:
- 目标标签 - 添加 ref 属性
<div ref="chartRef">我是渲染图标的容器</div>
- 恰当实际,通过 this.$refs.xxx,获取目标标签
mounted() {
console.log(this.$refs.chartRef)
},
②获取组件:
- 目标组件 - 添加ref属性
<BaseForm ref="baseForm"></BaseForm>
2.恰当实际,通过this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法
this.$refs.baseForm.组件方法()
Vue异步更新、$nextTick
- Vue是异步更新DOM的
- 想要在DOM更新完成之后做某件事,可以使用$nextTick
this.$nextTick(() => {
//业务逻辑
})