Vue-4
文章目录
- Vue-4
- 01-scoped组件的样式冲突
- scoped原理
- 02-data是一个函数
- 代码
- 03-组件通信
- 组件关系分类:
- 父子关系组件的使用
- 04-props详解
- 01-prop
- 作用
- 特点
- 02-props 校验
- 03-prop & data 单向数据流
- 05-小黑记事本(组件版)
- 核心步骤
- 添加功能
- 删除功能
- 底部合计
- 清空功能
- 持久化存储
- 06-非父子通信-(事件总线)
- 07-非父子通信-provide & inject
- 代码示例
- 08-v-model原理
- 01-表单类组件封装(下拉菜单)
- 02-父组件 v-model简化代码
- 09-sync修饰符
- 10-ref 和 $refs
- 1.获取dom元素:
- 2.获取组件实例
- 11- Vue 异步更新 、$nextTick
01-scoped组件的样式冲突
默认情况下:写在组件的样式会 全局生效 -> 因此很容易造成多个组件之间的样式冲突问题。
- 全局样式:默认组件中的样式会作用到全局
- 局部样式:可以给组件加上 scoped属性,可以让样式只作用于当前组件。
<!-- 默认的style样式,会作用于全局样式 加上了 scoped属性样式,只会作用于当前组件样式
-->
<style scoped>
div {border: 2px solid blue;margin: 30px;
}
</style>
推荐加上scoped属性,让组件拥有独立的样式。
scoped原理
- 给当前的组件模板所有元素,都会被添加上自定义属性
data-v-hash 利用hash的值[data-v-5f6a9d56]来区分不同组件
- css选择器后面被自动处理添加上了属性选择器。
div[data-v-5f6a9d56]
最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到。
02-data是一个函数
- 一个组件的data选项必须是一个函数。保证每个组件实例,维护独立的一份数据对象。
- 每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象。
data () {
return {
count: 100
}
}
代码
<template><div class="base-count"><button @click="count--">-</button><span>{{ count }}</span><button @click="count++">+</button></div>
</template><script>
export default {data () {return {count: 999}}
}
</script>
每次初始化组件实例的时候,就会调用一次data函数,得到一个独立的对象,不会印象到别人的数据。
03-组件通信
什么是组件通讯呢?
- 组件通信,就是指 组件与组件之间的数据传递。
- 组件的数据是独立的,无法直接访问其他组件的数据。
- 想用其他组件的数据 -> 就得使用 组件通信
组件关系分类:
-
父子关系
父传子props 和 子传父$emit
-
非父子关系
- provide & inject
- eventbus
- 通用(终极)解决方案:Vuex(适合复杂业务场景)
父子关系组件的使用
- 父传子props
// 准备好数据data () {return {myTitle: 'Veu'}},<!-- 1. 给组件标签,添加属性的方式,传值 -->
<SonDemo :title="myTitle"></SonDemo>// 2、通过props进行接收传递过来的属性
props: ['title']//使用
{{ title }}
- 子传父 $emit
<button @click="changeFn">修改title</button>
methods: {changeFn() {// 1、通过$emit,向父组件发送消息通知this.$emit('changeTitle','子组件')}
<!-- 2、父组件:对消息进行监听 -->
<SonDemo :title="myTitle" @changeTitle="handleChange"></SonDemo>
methods: {handleChange (newTitle) {// 3、提供处理函数,提供逻辑this.myTitle = newTitle}},
04-props详解
01-prop
答:prop是组件上注册的一些 自定义属性
作用
- 作用: 向子组件传递数据
特点
- 可以传递任意数量的 prop
- 可以传递 任意类型的 prop
1.导入注册并准备数据在App.vue中
import UserInfo from './components/UserInfo.vue'
export default {// 数据由父组件data函数提供data () {return {username: '小帅',age: 28,isSingle: true,car: {brand: '宝马',},hobby: ['篮球','足球','羽毛球']}},components: {UserInfo}
}
2.给组件注册自定义属性<!-- 组件上注册自定义属性 --><UserInfo:username="username":age="age":isSingle="true":car="car":hobby="hobby"></UserInfo>
3.利用props父传子通信数据props: ['username','age','isSingle','car','hobby']4.子组件使用模板语法进行使用<div class="userinfo"><h3>我是个人信息组件</h3><div>姓名:{{ username }}</div><div>年纪:{{ age }}</div><div>是否单身:{{ isSingle? '是' : '否'}}</div><div>座驾:{{ car.brand}}</div><div>兴趣爱好:{{ hobby.join('、') }}</div></div>
02-props 校验
作用:为组件的prop指定验证要求,不符合要求的,控制台会有错误显示, 帮助开发者,快速发现错误。
语法
- 类型校验
- 非空校验
- 默认值
- 自定义校验
写法:需要将原来的写法(数组),改为对象的写法。
props: {
}
- 类型校验(基础写法)
// props: ['w']props: {w: Number //Number String Boolean Array Object}
}
- 完整写法
类型、非空、默认值、自定义
// 完整写法props: {w: {// 1、类型判断type: Number,// 2、非空判断required: true,// 3、默认值(没有值的时候)default: 0,// 4、自定义判断validator (value) {// console.log(value)if (value >= 0 && value <= 100) {return true} else {console.error('传入的porp w 必须是0~100之间的数字')return false}}}}
03-prop & data 单向数据流
共同点: 都可以给组件提供数据
区别:
- data 的数据是自己的 -> 随便修改
- prop 的数据是外部的 -> 不能直接改,要遵循 单向数据流
1.自己的数据自己改(谁的数据谁负责)
2.prop传过来的数据(外部的数据),不能直接改
App.vue
<script>
import BaseCount from './components/BaseCount.vue'
export default {data () {return {count: 666}},methods: {// 监听事件的处理函数handleChange (newCount) {// console.log(newCount)this.count = newCount}},
// 注册
components: {BaseCount
}
}==========================分割线=========BaseCount// 不能直接改props: {count: Number},methods: {handleAdd () {this.$emit('changeCount' ,this.count + 1)},handleDel () {this.$emit('changeCount',this.count - 1)}}
}
</script>
单向数据流:父组件的prop更新;会单项的向下流动,影响到子组件数据
口诀:谁的数据谁负责
05-小黑记事本(组件版)
核心步骤
-
渲染功能
-
提供数据 → 提供在公共的父组件 App.vue中
-
通过父传子,将数据传递给 TodoMain
-
利用 v-for 渲染
添加功能
-
收集表单数据 → v-model
-
监听事件 (回车 + 点击 都要进行添加)
-
子传父 ,将任务名称传递给父组件 App.vue
-
进行添加, unshift (自己的数据自己负责)
删除功能
-
监听事件 (监听点击事件) 携带 id
-
子传父, 将删除的 id 传递给父组件 App.vue
-
进行删除 filter (自己的数据自己负责)
底部合计
- 父传子传list → 渲染
清空功能
-
注册监听点击事件
-
子传父 → $emit → list = []
持久化存储
- watch深度监视list变化 → 往本地存储 → 进入页面优先往本地存储
-
##### **渲染功能**
-
提供数据 → 提供在公共的父组件 App.vue中
-
通过父传子,将数据传递给 TodoMain
-
利用 v-for 渲染
1.提供数据 → 提供在公共的父组件 App.vue中list: JSON.parse(localStorage.getItem('list')) || [{ id: 1, name: "打篮球" },{ id: 2, name: "陪女朋友过七夕" },{ id: 3, name: "看电影逛街" },],
2.通过父传子,将数据传递给 TodoMain// 利用组件通信 父传子传递数据props: {list: Array},
3.利用v-for渲染<li v-for="(item,index) in list" :key="item.id" class="todo"><div class="view"><span class="index"> {{index + 1}}.</span> <label>{{ item.name }}</label><button @click="todoDel(item.id)" class="destroy"></button></div></li>
添加功能
-
收集表单数据 → v-model
-
监听事件 (回车 + 点击 都要进行添加)
-
子传父 ,将任务名称传递给父组件 App.vue
-
进行添加, unshift (自己的数据自己负责)
1.收集表单数据 → v-model<input@keyup.enter="handleAdd"v-model="todoTask"placeholder="请输入任务"class="new-todo"/>2.监听事件 (回车 + 点击 都要进行添加)<button @click="handleAdd" class="add">添加任务</button>3.子传父 ,将任务名称传递给父组件 App.vuemethods: {handleAdd() {if (this.todoTask.trim() === '') {alert('输入内容不能为空')return}// 数据是App.vue的,需要通知他,让他该this.$emit("addTask", this.todoTask);// 清空数据框内容this.todoTask = "";},},4.进行添加, unshift (自己的数据自己负责)<TodoHeader @addTask="handleAdd"></TodoHeader>handleAdd(newValue) {// 对list数据添加(list是App.vue的)this.list.unshift({ id: +new Date(), name: newValue });},
删除功能
-
监听事件 (监听点击事件) 携带 id
-
子传父, 将删除的 id 传递给父组件 App.vue
-
进行删除 filter (自己的数据自己负责)
1.监听事件 (监听点击事件) 携带 id<button @click="todoDel(item.id)" class="destroy"></button>2.子传父, 将删除的 id 传递给父组件 App.vuemethods: {todoDel (id) {// 2. 子传父, 将删除的 id 传递给父组件 App.vuethis.$emit('changeDel', id)}}3.进行删除 filter (自己的数据自己负责)delTask (id) {// 3. 进行删除 filter (自己的数据自己负责)this.list = this.list.filter(item => item.id !== id) //与子组件传递过来的id进行匹配过滤},
底部合计
- 父传子传list → 渲染
1.父传子传list → 渲染<!-- 底部 --><TodoFooter @removeTask="removeList" :list="list"></TodoFooter><span class="todo-count">合 计:<strong> {{ list.length }} </strong></span>
清空功能
-
注册监听点击事件
-
子传父 → $emit → list = []
1.注册监听点击事件 <button @click="removeTask" class="clear-completed">清空任务</button>2.子传父 → $emit → list = []methods: {removeTask () {// 2. 子传父 → $emit → list = []this.$emit('removeTask')}}
3.父组件中处理removeList () {// 监听子组件的通知后,执行删除功能this.list = []}
持久化存储
- watch深度监视list变化 → 往本地存储 → 进入页面优先往本地存储
watch: {list: {deep: true,handler (newValue) {// 将监视的list的数据存入到本地localStorage.setItem('list', JSON.stringify(newValue))}}}// 逻辑或终端,本地有 → 用本地的 没有 → 用list数组默认数据list: JSON.parse(localStorage.getItem('list')) || [{ id: 1, name: "打篮球" },{ id: 2, name: "陪女朋友过七夕" },{ id: 3, name: "看电影逛街" },],
06-非父子通信-(事件总线)
作用;非父子组件之间,进行简易消息传递,(复杂场景 → Vuex)
一对多的关系(一个组件发送,只要监听了的都可以访问到)
使用方法:
- 创建一个都能访问到的事件总线 (空Vue实例) → utils /EventBus.js
- 接收方,监听Bus实例的事件
- 发送方,触发 Bus实例的事件
1.创建一个都能访问到的事件总线 (空Vue实例) → utils /EventBus.js
// 1.创建爱一个都能访问到的事件总线(空的 Vue 实例)
import Vue from 'vue'const Bus = new Vue()export default Bus
2.接收方,监听Bus实例的事件
<script>
import Bus from '../utils/EventBus'
export default {created () {// 2.在 A组件(接收方,进行监听Bus的事件 (订阅消息)Bus.$on('sendMsg',(msg) =>{this.msg = msg})},data () {return {msg: ''}}}
</script>
3.发送方,触发 Bus实例的事件
<script>import Bus from '../utils/EventBus'export default {methods: {clickSend () {//3.B组件(发送方) 触发事件的方式传递参数(发布消息)Bus.$emit('sendMsg','今天是七夕节,准备怎么过呢?可惜一个人过!')}}}</script>
07-非父子通信-provide & inject
作用是跨层级共享数据
provide共享的数据
- 简单数据类型:非响应式的
- 复杂数据类型:是响应式的
通常使用就是使用包对象的形式(复杂数据类型)共享
代码示例
共享 1.父组件 provide 提供数据
<script>
export default {provide () {return {普通类型(非响应式的)color: this.color,复杂类型(响应式的)
}
}
}使用 2. 子孙组件 inject 取值使用export default {inject: ['color',userInfo],created () {console.log(this.color,this.userInfo)}}
</script>
08-v-model原理
原理:v-model本质上就是一个语法糖。例如应用在输入框上,就是value属性 和 input事件的合写。
子组件不能直接是有个v-model访问父组件的数据的,因为没有权限,需要通过组件通信 + value 和 事件来解决。
**作用:**实现数据双向绑定
- 数据变,视图跟着变:value
- 视图变,数据跟着变:@input
注意:$event 用于模板中,获取事件的形参
<!-- v-model原理:等价于 :value和 @input的合体 --><input v-model="msg1" type="text"><br><br><!-- 模板中获取时间的形参,使用$event → 获取 --><input :value="msg2" @input="msg2 = $event.target.value" type="text"><br><br>
01-表单类组件封装(下拉菜单)
- 父传子:数据 应该是父组件props传递过来的,v-model拆解绑定数据
- 子传父: 监听输入,子传父传值给父组件修改
父组件
<BaseSelect:cityId="selectId"//监听子组件的事件,实现值的修改@changeId="selectId = $event"></BaseSelect>子组件<select :value="cityId" @change="handleChaneg" name="" id=""><option value="101">北京</option><option value="102">上海</option><option value="103">武汉</option><option value="104">深圳</option><option value="105">广州</option></select>
<script>
props: {cityId: String
},
methods: {handleChaneg (e) {this.$emit('changeId',e.target.value)}
}
</script>
这里使用了 :value 和 @change事件 实现子组件和父组件双向绑定
02-父组件 v-model简化代码
父组件 v-model 简化代码,实现 子组件 和 父组件 数据双向绑定
- 子组件中:props通过value接受,事件触发input
- 父组件中: v-model 给组件直接绑定数据
前提条件:value接收 input事件触发
父传子 使用 value 传值
props: {value: String
},子传父 使用 input 事件触发
methods: {handleChaneg (e) {this.$emit('input',e.target.value)}
}这样父组件中才能使用 v-model<BaseSelectv-model="selectId"></BaseSelect>
09-sync修饰符
**作用:**可以实现 子组件 与 父组件数据的 双向绑定,简化代码
特点: prop属性名 ,可以自定义。非固定的value
场景:封装弹框类的基础组件,visible属性 true显示,false隐藏。
本质:就是**:属性名** 和 @ updata:属性名的 合写
父组件
<BaseDialog :visible.sync="isShow">
</BaseDialog>子组件
props: {//visible就是父组件传过来的属性名visible: Boolean
}methods: {close () {//通知父组件控制显示和隐藏this.$emit('updata:visible',fasle)这里的事件名在父组件中使用了sync修饰符,就相当于省略了事件监听。
}
}
10-ref 和 $refs
作用:利用 ref 和 refs可以用于获取dom元素,或组件实例
特点:查找范围 → 当前组件内(更精确稳定)
1.获取dom元素:
① 目标标签 - 添加 ref属性
<div ref="chartRef">我是渲染图标的容器</div>
②恰当时机,通过this.$refs.chartRef,获取目标标签
mounted () {
console.log(this.$refs.chartRef)
}
① 目标标签 - 添加 ref属性
<div ref="mychart" class="base-chart-box"></div>②恰当时机,通过this.$refs.chartRef,获取目标标签const myChart = echarts.init(this.$refs.mychart);
使用ref 和 $refs,哪怕获取到相同的类名元素,我依旧值局限于当前组件我添加了ref属性的元素,其他的我都不管。
querySelector 查找的范围 → 整个页面(很容易出错)
2.获取组件实例
利用res 和 $refs 获取组件实例
①目标组件 - 添加 ref 属性
②恰当时机,通过this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法
父组件 App.vue代码①目标组件 - 添加 ref 属性<BaseForm ref="baseForm"></BaseForm>②恰当时机,通过this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法
<script>
// 导入组件
import BaseForm from './components/BaseForm.vue'
export default {
// 注册组件
components: {BaseForm
},
methods: {// 获取数据的执行函数handleGet () {// 获取组件,然后直接调用子组件封装好的方法console.log(this.$refs.baseForm.getValues())},// 重置数据的执行函数handleReset () {this.$refs.baseForm.resetValues()}
}子组件 BaseForm代码<template><form action="">账号:<input v-model="username" type="text"><br><br><hr>密码:<input v-model="password" type="password"><br></form>
</template><script>
export default {data () {return {username: '',password: ''}},methods: {// 方法1:收集表单数据,返回一个对象getValues () {return {username: this.username,password: this.password}},// 方法2:重置表单resetValues () {this.username = ''this.password = ''}}
通过 res 和 $refs 获取组件,然后使用获取到的组件里面的方法。例如上面代码中,父组件中是获取数据和重置数据使用的方法。就是从子组件中封装好的方法。
11- Vue 异步更新 、$nextTick
需求:编辑标题,编辑框自动将聚焦
-
点击编辑,显示编辑框
-
让编辑框,立刻获取焦点(focus)
<script> this.isShowEdit = true //显示输入框 this.$refs.inp.focus() //获取焦点 </script>
问题:在Vue中,这样写代码 “显示之后”,立刻获取焦点是不能成功的!
<script>methods: {handleEdit () {// 1.显示输入框this.isShowEdit = true// 2.让 输入框 获取焦点 (Veu 是异步获取dom元素的)this.$refs.inp.focus() //执行到这里的时候,其实dom还没有获取到。所以会出现问题}} </script>
原因:Vue是 异步更新 DOM (提升性能)
解决方发: $nextTick: 等DOM更新后,才会触发执行此方法里面的函数体。
一但DOM加载完毕,立即执行$nextTick方法里面的函数方法
<script>methods: {handleEdit () {// 1.显示输入框this.isShowEdit = true// 2.让 输入框 获取焦点 (Veu 是异步获取dom元素的)this.$nextTick(() => {this.$refs.inp.focus()})}}</script>