三大组成部分
<template>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。
<template>
</template>
<script>
export default {
}
</script>
<style scoped>
</style>
通过上述的<style scoped>
就可以使
最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
一个组件的 data 选项必须是一个函数。目的是为了:保证每个组件实例,维护独立的一份数据对象。 每次创建新的组件实例,都会新执行一次data 函数,得到一个新对象。
<script>
export default {
data: function () {
return {
count: 100,
}
},
}
</script>
组件通信,就是指组件与组件之间的数据传递
父子关系 非父子关系
父组件通过 props 将数据传递给子组件 子组件利用 $emit 通知父组件修改更新
父组件通过props将数据传递给子组件 父组件App.vue
//:title="msg" 表示动态赋予属性
父向子传值步骤
子向父传值步骤
组件上 注册的一些 自定义属性, 我们可以使用props属性来向子组件传递数据 两个特点:
案例:
Main.vue
为我们自己定义的父组件, UserInfo
为自定义的子组件 , 通过props就可以实现组件之间的数据传递
我们使用组件的props属性, 但是数据不能乱传, 所以就需要使用props来校验数据 为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误
类型: 类型校验、非空校验、默认值、自定义校验
props: {
校验属性名:类型
}
props: {
校验的属性名: {
type: 类型, // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
},
代码实例:
<script>
export default {
// 完整写法(类型、默认值、非空、自定义校验)
props: {
w: {
type: Number,
//required: true,
default: 0,
// 校验逻辑
validator(val) {
// console.log(val)
if (val >= 100 || val <= 0) {
console.error('传入的范围必须是0-100之间')
return false
} else {
return true
}
},
},
},
}
</script>
都可以给组件提供数据
父级props 的数据更新,会向下流动,影响子组件。这个数据流动是单向的
实现案例:
代码地址: https://github.com/Ray2310/vue-demo 实现功能:
以组件TodoMain.vue
(子组件)和组件App.vue
(父组件) 为例 讲解父子数据传输的问题。
template
区域, 通过使用:list="list"
来实现可以在子组件中接受数据props
实现父亲组件传递内容的接收。 template
中使用list中的数据<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 class="destroy"></button>
</div>
</li>
</ul>
</section>
</template>
<script>
export default {
// 使用对象的写法 ,接收父组件中传入的内容
props: {
list: Array
}
}
</script>
<style></style>
App.vue
<template>
<!-- 主体区域 -->
<section id="app">
<TodoHeader></TodoHeader>
<!-- 通过在这里写list, 就可以实现在子组件中作接收 -->
<TodoMain :list="list"></TodoMain>
<TodoFooter></TodoFooter>
</section>
</template>
<script>
// 导入组件
import TodoHeader from './components/TodoHeader.vue'
import TodoMain from './components/TodoMain.vue'
import TodoFooter from './components/TodoFooter.vue'
export default {
// 注册组件
components: {
TodoHeader,
TodoMain,
TodoFooter
},
//TODO: 数据提供需要提供在父组件中, 这样如果我们想要使用, 直接使用props传递即可。
data () {
return {
list: [
{id: 1, name: 'eat'},
{id: 2, name: 'play'},
{id: 3, name: 'write'}
]
}
}
}
</script>
<style>
</style>
TodoHeader.vue
子组件向App.vue
父子 传输添加的数据
v-model
实现数据收集并通过点击事件或回车 进行数据发送this.$emit('addItem', this.name)
实现给父组件发送消息通知@addItem="add"
, 并且通过add()
函数接收数据<template>
<!-- 输入框 -->
<header class="header">
<h1>记事本</h1>
<input placeholder="请输入任务" @keyup.enter="addItem()" v-model="name" class="new-todo" />
<button class="add" @keyup.enter="addItem()" @click="addItem()">添加任务</button>
</header>
</template>
<!--
这里实现数据的 子传父
完成添加功能
1. 收集表单数据 v-model
2. 监听时间 (回车+点击 都要进行添加)
3. 子传父,将任务名称传递给父组件App.vue
4. 父组件接受到数据后 进行添加 unshift(自己的数据自己负责)
-->
<script>
export default {
data(){
return {
name:''
}
},
methods: {
addItem(){
// console.log(this.name);
//传递给父亲组件
if(this.name === "" ) {
return
}
this.$emit('addItem', this.name)
//清空表单
this.name = ''
}
}
}
</script>
<style>
</style>
<template>
<!-- 主体区域 -->
<section id="app">
<TodoHeader @addItem="add"></TodoHeader>
<!-- 通过在这里写list, 就可以实现在子组件中作接收 -->
<TodoMain @deleteItem="deleteItem" :list="list"></TodoMain>
<TodoFooter @clear="clear" :list="list"></TodoFooter>
</section>
</template>
<script>
// 导入组件
import TodoHeader from './components/TodoHeader.vue'
import TodoMain from './components/TodoMain.vue'
import TodoFooter from './components/TodoFooter.vue'
export default {
// 注册组件
components: {
TodoHeader,
TodoMain,
TodoFooter
},
//TODO: 数据提供需要提供在父组件中, 这样如果我们想要使用, 直接使用props传递即可。
data () {
return {
list: [
{id: 1, name: 'eat'},
{id: 2, name: 'play'},
{id: 3, name: 'write'}
]
}
},
methods: {
add(newName){
console.log(newName)
//使用unshift添加
this.list.unshift({
id: +new Date(), //通过时间戳来实现id的唯一性
name: newName
})
//还需要作的是清空表单
},
deleteItem(id) {
console.log(id)
//通过filter过滤器实现删除操作
this.list = this.list.filter(item => item.id !== id)
},
//清空任务栏
clear(){
this.list = []
}
}
}
</script>
<!--
TODO:
拆分基础组件
渲染待办任务
添加任务
删除任务
底部合计 和 清空功能
持久化存储
-->
<style>
</style>
非父子组件之间,进行简易消息传递。(复杂场景→ Vuex) 发送通知不是一个一对一的关系, 但凡有人接收, 那么就都可以接受发送的内容
在工具包utils
中
import Vue from 'vue'
const Bus = new Vue()
export default Bus
<script>
import Bus form './utils/EventBus'
export default {
created () {
Bus.$on('sendMsg', (msg) => {
this.msg = msg
})
},
data: {
return {
msg: ''
}
}
}
</script>
<template>
<button #click="clickSend"></button>
</template>
<script>
import Bus form './utils/EventBus'
export default {
methods: {
clickSend(){
Bus.$emit('sendMsg', '这是一个消息')
}
}
}
</script>
跨层级共享数据 从祖辈层到后辈层(例如:爷爷到孙子)
export default {
provide () {
return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型【响应式】
userInfo: this.userInfo,
}
}
}
export default {
inject: ['color','userInfo'],
created () {
console.log(this.color, this.userInfo)
}
}
v-model
实现表单类组件的封装**实现子组件和父组件数据的双向绑定 (实现App.vue中的selectId和子组件选中的数据进行双向绑定) **
v-model
本质上实现的是双向绑定,而:value
这中的是单向绑定, 但是我们子组件是不允许修改父组件的内容的, 所以如果直接使用v-model
就会报错, 需要修改。
v-model其实就是 :value和@input事件的简写
子组件
<select :value="value" @change="handleChange">...</select>
props: {
value: String
},
methods: {
handleChange (e) {
this.$emit('input', e.target.value)
}
}
父组件
<BaseSelect v-model="selectId"></BaseSelect>
:visible
就是控制显示隐藏的
update是固定的。
作用: 利用ref 和 $refs 可以用于 获取 dom 元素 或 组件实例
<div ref="chartRef">我是渲染图表的容器</div>
mounted () {
console.log(this.$refs.chartRef)
}
需求 编辑标题, 编辑框自动聚焦
“显示之后”,立刻获取焦点是不能成功的! 原因:Vue 是异步更新DOM (提升性能)
nextTick:等 DOM更新后,才会触发执行此方法里的函数体**语法: **this.nextTick(函数体)
this.$nextTick(() => {
this.$refs.inp.focus()
})
注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例
概念:自己定义的指令,可以封装一些DOM操作,扩展额外的功能
案例, 通过自定义指令, 可以封装一些dom操作, 扩展额外的功能, 实现项目中的所有获取dom的操作都可以使用我们的自定义指令来实现获取dom
//在main.js中
Vue.directive('指令名', {
"inserted" (el) {
// 可以对 el 标签,扩展额外功能
el.focus()
}
})
//在Vue组件的配置项中
directives: {
"指令名": {
inserted () {
// 可以对 el 标签,扩展额外功能
el.focus()
}
}
}
需求: 实现一个 color 指令 - 传入不同的颜色, 给标签设置文字颜色
<div v-color="color">我是内容</div>
directives: {
color: {
inserted (el, binding) {
el.style.color = binding.value
},
update (el, binding) {
el.style.color = binding.value
}
}
}
<template>
<!-- 类名·最好和当前组件名同名 -->
<div>
<!--显示红色-->
<h2 v-color="color1">指令的值1测试</h2>
<!--显示蓝色-->
<h2 v-color="color2">指令的值2测试</h2>
<button @click="color1 = 'blue'">
改变第一个h1的颜色
</button>
</div>
</template>
<script>
//导入组件
export default {
data () {
return {
color1: 'red',
color2: 'blue'
}
},
methods: {
},
// mounted (){
// let input = this.$refs.inp.focus()
// console.log("input"+ input)
// },
// 注册组件 (对于导入的组件名和名称一样时, 我们可以直接使用)
components: {
}
,
directives: {
color: {
inserted (el, binding) {
// 可以对 el 标签,扩展额外功能
//这个el 就相当于是document.querySelecter('color')的意思
el.style.color = binding.value // 可以获取对应的data中的数据 就是我们指令的值color2 ----》 <h2 v-color="color1">指令的值1测试</h2>
el.focus()
},
// update 指令的值修改的时候触发, 提供值变化后, dom的更新逻辑
update (el , binding){
console.log('update 指令的val')
el.style.color = binding.value
}
}
}
}
</script>
<style>
</style>
需求: 实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 => 用户体验不好 封装一个 v-loading 指令,实现加载中的效果
类似于这样
<style>
.loading:before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url("./loading.gif") no-repeat center;
}
</style>
**作用: **让组件内部的一些 结构 支持 自定义
需求: 将需要多次显示的对话框,封装成一个组件
如果想要修改其中的内容该怎么做呢 ?
封装组件时,可以为预留的 插槽提供后备内容(默认内容)
自定义的组件MyDialog.vue
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<!-- 通过slot插槽来进行占位, 然后就可以在App中进行自定义传输 -->
<slot></slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
如果想要实现组件的占位操作:
在App.vue
中 即可通过自定义内容来实现
<Mydialog>
<p>are you shuer</p>
</Mydialog>
但是这样的操作 ,如果我们不传输内容, 那么就会显示为空。 这样明显不符合要求, 所以我们需要给slot
来实现给其中的内容进行赋值
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<!-- 通过slot插槽来进行占位, 然后就可以在App中进行自定义传输 -->
<slot>这是后被内容</slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
一个组件内有多处结构,需要外部传入标签,进行定制 上面的弹框中有三处不同,但是默认插槽只能定制一个位置,这时候怎么办呢?
通过在slot
标签中使用 name属性来进行区分不同的插槽, 然后在需要使用的组件中, 通过v-slot:name属性value
来进行赋值。 从而达到多个弹框出现多个值的情况
插槽只有两种,作用域插槽不属于插槽的一种分类
定义slot 插槽的同时, 是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以用
<slot :id="item.id" msg="测试文本"></slot>
{ id: 3, msg: '测试文本' }
<MyTable :list="list">
<template #default="obj">
<button @click="del(obj.id)">删除</button>
</template>
</MyTable>
(1) 双击显示输入框,输入框获取焦点 (2) 失去焦点,隐藏输入框 (3) 回显标签信息 (4) 内容修改,回车 → 修改标签信息
dblclick="handleClick"
实现,然后在实现的函数中 通过使v-if
的内容为true, 实现点击显示输入框
this.$nextTick(()=> {})
的方式 实现main.js
中全局注册, 然后封装全局指令focus,然后就可以直接通过v-focus
来进行使用因为获取焦点,我们是通过v-if
来向选择的, 所以如果想要失去焦点, 可以直接将if
中的信息修改即可。
所以就可以通过@blur="isEdit = false"
实现失去焦点
回显的信息是通过父组件传入的, 可以通过v-model
实现, 也可以通过前面所学的props
实现。
这里我们使用v-model
实现, v-model
==> :value 和 input的组合
<MyTag v-model="tempText"></MyTag>
, 通过v-model
将需要修改的信息传入子标签props
来进行接受标签内容{{ value}}
实现内容的显示。通过上述的步骤就可以实现数据从父标签传入子标签, 实现标签内容的回显
上述的回显示标签信息是通过父标签传子标签的形式实现的, 但是如何实现子标签传入父标签呢 ? 这里通过回车实现事件的触发, 那么我们就·在回车事件内实现数据的回显。
input
标签中, 同样,我们回车触发事件的内容也是在input
中, 所以我们可以通过e.target.value
获取触发事件的标签的内容, 也就是我们input
标签中的内容, 也就是我们需要回显 的内容。 this.$emit('input', e.target.value)
实现子标签的内容向父标签传递的功能。<template>
<div class="my-tag">
<input
v-if="isEdit"
refs="inp"
v-focus
:value="value"
class="input"
type="text"
placeholder="输入标签"
@blur="isEdit = false"
@keyup.enter="handleEnter"
/>
<!--
:value="values"绑定
v-focus 自动聚焦的方法二 : 在main.js中封装全局指令
@blur="isEdit = false" 失去焦点隐藏 -->
<div class="text" v-else
@dblclick="handleClick">{{ value }}</div>
</div>
</template>
<!--
(1) 双击显示输入框,输入框获取焦点
(2) 失去焦点,隐藏输入框
(3) 回显标签信息
信息是由外部父组件传入的
(4) 内容修改,回车 → 修改标签信息
-->
<script>
export default {
//接受父组件传入的信息
props: {
value: String
},
data() {
return {
isEdit: false
}
},
methods: {
// 使用双击点击事件
handleClick(){
// 需要实现自动聚焦的方式一: ,通过this.$nextTick()
// this.$nextTick(()=> {
// //立刻获取焦点
// this.$refs.inp.focus()
// })
this.isEdit = true
},
//e :可以获取触发事件的事件源
handleEnter(e) {
if(e.target.value.trim() === '') return alert("标签内容不为空")
//获取回车之后里面的内容, 所以获取内容, 然后更新给父组件
this.$emit('input', e.target.value)
this.isEdit= false //隐藏输入框
}
}
}
</script>
<style lang="less" scoped>
.my-tag {
cursor: pointer;
.input {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 100px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
&::placeholder {
color: #666;
}
}
}
</style>
(1) 动态传递表格数据渲染 (2) 表头支持用户自定义 (3) 主体支持用户自定义
通过<slot name="head"></slot>
占位, 然后在父标签中实现内容传递
在父组件App.vue
中, 通过使用<template #head...>
的方式, 实现组件插槽的自定义编辑。
<template>
<div class="table-case">
<!-- 动态的定义表头 -->
<MyTable :data="goods">
<template #head>
<th>编号</th>
<th>图片</th>
<th>名称</th>
<th width="100px">标签</th>
</template>
<!-- 通过表头 ,自定义主体部分. 通过obj对象来接收传入的内容 -->
<template #body="obj">
<td>{{ obj.index + 1}}</td>
<td> <img :src="obj.item.picture" /> </td>
<td> {{ obj.item.name }} </td>
<td> <MyTag v-model="obj.item.tag"></MyTag> </td>
</template>
</MyTable>
</div>
</template>
在子组件MyTable.vue
组件中,通过<slot name="body" :item="item" :index="index"></slot>
占位符的方式, 实现组件内容的占位, 然后再通过:参数=“参数”
的方式, 实现占位中的内容传输
<template>
<table class="my-table">
<thead>
<tr>
<slot name="head"></slot>
</tr>
</thead>
<tbody >
<tr v-for="(item, index) in data" :key="item.id">
<!-- 通过slot插槽占位 ,之后又使用:item 进行插槽传值 -->
<slot name="body" :item="item" :index="index"></slot>
</tr>
</tbody>
</table>
</template>
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有