组件的三大组成部分
组件的样式冲突 scoped
<style scoped>
</style>
data 是一个函数
components/BaseButton.vue
<template>
<div class="BaseButton">
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<script>
export default {
// data必须写成函数,不能是对象
data() {
return {
count: 999
}
}
}
</script>
<style>
</style>
App.vue
<template>
<div id="app">
<BaseButton></BaseButton>
<BaseButton></BaseButton>
<BaseButton></BaseButton>
</div>
</template>
<script>
import BaseButton from './components/BaseButton.vue'
export default {
name: 'App',
components: {
BaseButton
}
}
</script>
<style>
</style>
组件通信
父 -> 子 props
components/SonTest.vue
<template>
<div class="son">
{{ title }}
<!-- 渲染使用 -->
</div>
</template>
<script>
export default {
// 2.通过props进行接受
// 这里的名字必须和App.vue里的<Son :title="MyTitle"></Son> 相同
props: ['title']
}
</script>
<style scoped>
div {
border: 1px solid black;
width: 100px;
height: 100px;
}
</style>
App.vue
<template>
<div id="app">
我是爸爸
<!-- 1.给组件标签,添加属性的方式,传值 -->
<SonTest :title="MyTitle"></SonTest>
</div>
</template>
<script>
import SonTest from './components/SonTest.vue'
export default {
components: {
SonTest
},
data() {
return {
MyTitle: 'slxhhhh'
}
}
}
</script>
<style >
div {
border: 1px solid black;
width: 300px;
height: 200px;
}
</style>
prop概念
components/UserInfo.vue
<template>
<div class="UserInfo">
<h1>个人信息</h1>
<h3>姓名{{ username }}</h3>
<h3>年龄{{ age }}</h3>
<h3>是否单身{{ isSingle }}</h3>
<h3>座驾{{ car.brand }}</h3>
<h3>兴趣爱好{{ hobby.join('、') }}</h3>
</div>
</template>
<script>
export default {
props: ['username', 'age', 'isSingle', 'car', 'hobby']
}
</script>
<style>
</style>
App.vue
<template>
<div id="app">
<UserInfo
:username="username"
:age="age"
:isSingle="isSingle"
:car="car"
:hobby="hobby"
></UserInfo>
</div>
</template>
<script>
import UserInfo from './components/UserInfo.vue'
export default {
components: {
UserInfo
},
data() {
return {
username: 'slx',
age: 20,
isSingle: true,
car: {
brand: 'affrf'
},
hobby: ['aaaa', 'cccc', 'bbbb']
}
}
}
</script>
<style >
div {
border: 1px solid black;
width: 300px;
height: 400px;
}
</style>
props校验
类型校验 基础写法+完整写法
components/BaseProgress.vue
<template>
<div class="BaseProgress">
<div class="progress">
<!-- 在这里面width 不用加{{}} 并且{}里面 是js对象 ,所以要遵守驼峰命名法 也就是 background-color 要变成 backgroundColor-->
<div
class="inner"
:class="{
low: w < 50,
high: w > 70,
over: w == 100
}"
:style="{ width: w + '%' }"
>
<span>{{ w }}%</span>
</div>
</div>
</div>
</template>
<script>
export default {
// 没有校验
// props: ['w']
// 1.基础校验
// props: {
// // 校验的属性名:类型 Number、String Boolean
// w: Number
// }
// 2.完整写法(类型、非空、默认、自定义校验
props: {
w: {
// 写成对象形式
type: Number,
// required: true,
default: 10,
validator(value) {
// return false
console.log(value)
if (value >= 0 && value <= 100) {
return true
}
console.log('传入的值要是0~100')
return false
}
}
}
}
</script>
<style scoped>
.BaseProgress {
margin: 10px;
}
.progress {
width: 300px;
height: 40px;
margin-bottom: 40px;
background-color: #cdf92c;
border-radius: 25px;
padding: 5px;
}
.inner {
background-color: #0df6c7;
border-radius: 25px;
height: 35px;
margin-top: 3px;
/* width: 20%; */
}
.low {
background-color: #92ee61;
}
.high {
background-color: rgb(141, 179, 216);
}
.over {
background-color: rgb(0, 128, 255);
}
.inner span {
width: 100%;
text-align: right;
display: block;
line-height: 90px;
}
</style>
App.vue
<template>
<div id="app">
<!-- 没传值,就用默认的10 -->
<BaseProgress></BaseProgress>
<BaseProgress :w="width"></BaseProgress>
</div>
</template>
<script>
import BaseProgress from './components/BaseProgress.vue'
export default {
components: {
BaseProgress
},
data() {
return {
// width: 'sfsd'
width: 20
}
}
}
</script>
<style >
</style>
prop & data 、单向数据流
components/BaseProgress.vue
<template>
<div class="BaseProgress">
<div class="progress">
<!-- 在这里面width 不用加{{}} 并且{}里面 是js对象 ,所以要遵守驼峰命名法 也就是 background-color 要变成 backgroundColor-->
<div
class="inner"
:class="{
low: w < 50,
high: w > 70,
over: w == 100
}"
:style="{ width: w + '%' }"
>
<span>{{ w }}%</span>
</div>
</div>
<button @click="handleFn(75)">设置75%</button>
<button @click="handleFn(100)">设置100%</button>
<!-- props传过来的数据(外部数据),不能直接改 -->
</div>
</template>
<script>
export default {
props: {
w: {
// 写成对象形式
type: Number,
// required: true,
default: 10,
validator(value) {
if (value >= 0 && value <= 100) {
return true
}
console.log('传入的值要是0~100')
return false
}
}
},
methods: {
handleFn(tt) {
this.$emit('changeWidth', tt)
}
}
// 父组件的props更新,会单向向下流动,影响到子组件
}
</script>
<style scoped>
.BaseProgress {
margin: 10px;
}
.progress {
width: 300px;
height: 40px;
margin-bottom: 40px;
background-color: #cdf92c;
border-radius: 25px;
padding: 5px;
}
.inner {
background-color: #0df6c7;
border-radius: 25px;
height: 35px;
margin-top: 3px;
/* width: 20%; */
}
.low {
background-color: #92ee61;
}
.high {
background-color: rgb(141, 179, 216);
}
.over {
background-color: rgb(0, 128, 255);
}
.inner span {
width: 100%;
text-align: right;
display: block;
line-height: 90px;
}
</style>
App.vue
<template>
<div id="app">
<!-- 没传值,就用默认的10 -->
<!-- <BaseProgress></BaseProgress> -->
<BaseProgress :w="width" @changeWidth="changeFn"></BaseProgress>
</div>
</template>
<script>
import BaseProgress from './components/BaseProgress.vue'
export default {
components: {
BaseProgress
},
data() {
return {
// width: 'sfsd'
width: 20
}
},
methods: {
changeFn(tt) {
this.width = tt
}
}
}
</script>
<style >
</style>
子 -> 父 $emit
components/Son.vue
<template>
<div class="son" style="border: 3px solid #000; margin: 10px">
我是Son组件 {{ title }}
<button @click="changeFn">修改title</button>
</div>
</template>
<script>
export default {
name: 'Son-Child',
props: ['title'],
methods: {
changeFn() {
// 通过this.$emit() 向父组件发送通知
this.$emit('changTitle','传智教育')
},
},
}
</script>
<style>
</style>
App.vue
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 2.父组件对子组件的消息进行监听 -->
<Son :title="myTitle" @changTitle="handleChange"></Son>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
myTitle: '学前端,就来黑马程序员',
}
},
components: {
Son,
},
methods: {
// 3.提供处理函数,提供逻辑
handleChange(newTitle) {
this.myTitle = newTitle
},
},
}
</script>
<style>
</style>
总结
【综合案例】——小黑记事本— 组件版
components/TodoHeader.vue
<template>
<div class="head">
<h1>小黑记事本</h1>
<input
@keyup.enter="handleAdd"
v-model="todoName"
type="text"
placeholder="请输入待办事项"
/>
<button @click="handleAdd">添加任务</button>
</div>
</template>
<script>
export default {
data() {
return {
todoName: ''
}
},
methods: {
handleAdd() {
if (this.todoName.trim() == '') {
// 输入许多的空格,无效输入,不让添加
alert('请输入内容')
return
}
this.$emit('add', this.todoName)
// 点击添加之后,输入框清空
this.todoName = ''
}
}
}
</script>
<style>
.head {
width: 243px;
/* background-color: #584949; */
}
input {
height: 30px;
vertical-align: middle;
}
.head button {
height: 30px;
}
</style>
components/TodoMain.vue
<template>
<section class="body">
<ul>
<li v-for="(item, index) in todoList" :key="item.id">
<span>{{ index + 1 }}</span>
<span class="content">{{ item.name }}</span>
<button @click="handleDel(item.id)">×</button>
</li>
</ul>
</section>
</template>
<script>
export default {
props: {
todoList: Array
},
methods: {
handleDel(tt) {
this.$emit('del', tt)
}
}
}
</script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
}
.body li {
width: 234px;
height: 50px;
display: flex;
line-height: 50px;
/* justify-content: space-between; */
background-color: #de8282;
border-bottom: black solid 2px;
}
.body li .content {
flex: 1;
}
.body li button {
height: 50%;
align-self: center;
display: none;
}
.body li:hover button {
display: block;
width: 20px;
}
</style>
components/TodoFooter.vue
<template>
<div v-show="todoList.length > 0" class="footer">
<span
>合计:<strong>{{ todoList.length }}</strong></span
>
<button @click="clear()">清空任务</button>
</div>
</template>
<script>
export default {
props: {
todoList: Array
},
methods: {
clear() {
this.$emit('clear')
}
}
}
</script>
<style>
.footer {
width: 234px;
display: flex;
justify-content: space-between;
}
</style>
components/App.vue
<template>
<div id="app">
<TodoHeader @add="handleAdd"></TodoHeader>
<TodoMain :todoList="todoList" @del="handleDel"></TodoMain>
<TodoFooter @clear="handleClear" :todoList="todoList"></TodoFooter>
</div>
</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
},
// 渲染功能:
// 1.提供数据:写在公共的父组件 App.vue
// 2.通过父组件,将数据传递给TodoMain
// 3.利用v-for 渲染
// 添加功能
// 1.收集表单数据 v-model
// 2.监听事件(回车+点击 都要添加
// 3.子传父 将任务名称传给父App.vue
// 4.进行添加 unshift(自己的数据,自己负责)
// 删除功能
// 1.监听事件(删除的点击) 携带id
// 2.子传父,将删除的id传给父组件App.vue
// 3.父组件删除 filter(自己的数据,自己负责)
// 底部合计:父传子list 渲染
// 清空功能:子传父,通知到父组件 父组件清空
// 持久化存储:watch深度监视-> 往本地存 -> 进入页面优先读取本地
data() {
return {
todoList: JSON.parse(localStorage.getItem('list')) || [
{ id: 1, name: '吃水果' },
{ id: 2, name: '喝酸奶' }
]
}
},
methods: {
handleAdd(todoName) {
this.todoList.unshift({
id: +new Date(),
name: todoName
})
},
handleDel(tt) {
this.todoList = this.todoList.filter((item) => item.id != tt)
},
handleClear() {
this.todoList = []
}
},
watch: {
todoList: {
deep: true,
handler(newValue) {
localStorage.setItem('list', JSON.stringify(newValue))
}
}
}
}
</script>
<style >
#app {
/* width: 234px;
height: 200px;
display: flex;
flex-direction: column; */
margin: 50px 50px;
}
</style>
非父子通信 event bus事件总线
components/BaseA.vue
<template>
<div class="A">
我是A组件(接收方)
<p>{{ msg }}</p>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
created() {
// 2.在A组件(接受方)监听Bus的事件(订阅消息)
// 事件名,回调
Bus.$on('sendMsg', (msg) => {
console.log(msg)
this.msg = msg
})
},
data() {
return {
msg: ''
}
}
}
</script>
<style>
.A {
width: 200px;
height: 100px;
border: 1px solid black;
}
</style>
components/BaseB.vue
<template>
<div class="B">
我是B组件(发布方)
<button @click="clickSend">发布通知</button>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
methods: {
clickSend() {
// 3.B组件(发送方)触发事件的方式传递参数(发布消息)
Bus.$emit('sendMsg', '恐龙扛狼') // Bus.$on('sendMsg', (msg) => {})和这里的名字同名
}
}
}
</script>
<style>
.B {
width: 200px;
height: 100px;
border: 1px solid black;
}
</style>
App.vue
<template>
<div id="app">
<BaseA></BaseA>
<BaseB></BaseB>
</div>
</template>
<script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
export default {
components: {
BaseA,
BaseB
}
}
</script>
<style>
</style>
utils/EventBus.js
utils通常用来放工具
// 创建一个都能访问到的事件总线(空的Vue实例)
import Vue from 'vue'
const Bus = new Vue()
export default Bus
非父子通信——provide & inject(扩展)
v-model原理
$evevnt也就是e
如果应用于复选框,就是check属性,和change事件的合写
表单类组件封装
components/BaseSelect.vue
<template>
<div class="BaseSelect">
<!-- <select v-model="cityId">
这样写是不行的,因为父组件的数据,只能父组件改,
而v-model是双向的,他在尝试更改父亲的数据,他要造反,要谋权篡位,所以报错了
-->
<!--
:value="cityId" 用来父传子
@change="handleChange" 用来子传父
-->
<select :value="cityId" @change="handleChange">
<option value="101">北京</option>
<option value="102">深圳</option>
<option value="103">上海</option>
<option value="104">广州</option>
<option value="105">辽宁</option>
<option value="106">福建</option>
</select>
</div>
</template>
<script>
export default {
props: {
cityId: String
},
methods: {
handleChange(e) {
console.log(e.target.value)
this.$emit('changeId', e.target.value)
}
}
}
</script>
<style>
</style>
App.vue
<<template>
<!-- :cityId="selectId" 用来父传子
@changeId="selectId = $event" 用来子传父
-->
<BaseSelect :cityId="selectId" @changeId="selectId = $event"></BaseSelect>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
components: {
BaseSelect
},
data() {
return {
selectId: '106'
}
}
}
</script>
<style>
</style>
@changeId="selectId = $event
" 中的$event是什么
在Vue中,$event是一个特殊的变量,用于在事件处理函数中访问触发事件时传递的数据。它代表了事件对象,包含了与事件相关的信息。
在你提供的代码中,@changeId="selectId = $event"表示当 BaseSelect 组件触发 changeId 事件时,将事件对象中的数据赋值给 selectId。
具体来说,$event 是一个占位符,用于接收传递给事件处理函数的参数。在这个例子中,BaseSelect 组件在触发 changeId 事件时,会将选中的城市ID作为参数传递给事件处理函数。然后,通过 $event 来访问这个参数,并将其赋值给 selectId。
例如,如果 BaseSelect 组件在触发 changeId 事件时传递了一个值为 123 的参数,那么 selectId 将被赋值为 123。代码示例中的 @changeId=“selectId = $event” 就是将事件对象中的数据赋值给 selectId 的方式。
请注意,$event 只是一个约定俗成的命名,你也可以使用其他变量名来接收事件对象中的数据。例如,你可以使用 @changeId=“selectId = value”,其中 value 是你自己定义的变量名,它将接收事件对象中的数据。
总之,$event 是Vue中的一个特殊变量,用于在事件处理函数中访问事件对象中传递的数据。它可以帮助你在事件处理函数中获取和处理事件的相关信息。
v-model 简化代码
components/BaseSelect.vue
<template>
<div class="BaseSelect">
<!-- :value="value" 父传子 -->
<select :value="value" @change="handleChange">
<option value="101">北京</option>
<option value="102">深圳</option>
<option value="103">上海</option>
<option value="104">广州</option>
<option value="105">辽宁</option>
<option value="106">福建</option>
</select>
</div>
</template>
<script>
export default {
// 父传子
props: {
value: String
},
methods: {
// 子传父
// 监听
handleChange(e) {
console.log(e.target.value)
this.$emit('input', e.target.value)
}
}
}
</script>
<style>
</style>
App.vue
<template>
<!-- v-model => :value(父传子) + @input 子传父 -->
<BaseSelect v-model="selectId"></BaseSelect>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
components: {
BaseSelect
},
data() {
return {
selectId: '106'
}
}
}
</script>
<style>
</style>
小结
封装输入框组件
components/BaseInput.vue
<template>
<div class="BaseInput">
<input @change="handleChange" :value="value" type="text" />
</div>
</template>
<script>
export default {
props: {
value: String
},
methods: {
handleChange(e) {
console.log(e.target.value)
this.$emit('input', e.target.value)
}
}
}
</script>
<style>
</style>
App.vue
<template>
<div class="app">
<BaseInput v-model="text"></BaseInput>
</div>
</template>
<script>
import BaseInput from './components/BaseInput.vue'
export default {
components: {
BaseInput
},
data() {
return {
text: 'slx'
}
}
}
</script>
<style>
</style>
.sync修饰符
在书写上比较比v-model麻烦,所以,封装表单还是v-model,其余的用.sync较好
components/SB.vue
<template>
<div v-show="visable" class="base-dialog-wrap">
<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 @click="close">取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
visable: Boolean
},
methods: {
close() {
this.$emit('update:visable', false)
}
}
}
</script>
<style scoped>
.base-dialog-wrap {
width: 300px;
height: 200px;
box-shadow: 2px 2px 2px 2px #ccc;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 0 10px;
z-index: 9;
background-color: #fff;
}
.base-dialog .title {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #000;
}
.base-dialog .content {
margin-top: 38px;
}
.base-dialog .title .close {
width: 20px;
height: 20px;
cursor: pointer;
line-height: 10px;
}
.footer {
display: flex;
justify-content: flex-end;
margin-top: 26px;
}
.footer button {
width: 80px;
height: 40px;
}
.footer button:nth-child(1) {
margin-right: 10px;
cursor: pointer;
}
</style>
App.vue
<template>
<div class="app">
<button @click="isShow = true">退出按钮</button>
<div :class="{ show_bg: isShow }">
<SB :visable.sync="isShow"></SB>
<!-- :visable.sync => :visable + @update:visable -->
</div>
</div>
</template>
<script>
import SB from './components/SB.vue'
export default {
data() {
return {
isShow: false
}
},
methods: {},
components: {
SB
}
}
</script>
<style>
.show_bg {
position: absolute;
top: 0;
z-index: 2;
width: 100%;
height: 100%;
background-color: #c4bbbba4;
}
</style>
ref 和 $refs
获取dom元素
<div ref="mychart" class="BaseEchart"></div>
var myChart = echarts.init(this.$refs.mychart)
components/BaseEchart.vue
<template>
<div ref="mychart" class="BaseEchart"></div>
</template>
<script>
import * as echarts from 'echarts'
export default {
mounted() {
// var myChart = echarts.init(document.querySelector('.BaseEchart'))
var myChart = echarts.init(this.$refs.mychart)
var option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}
]
}
option && myChart.setOption(option)
}
}
</script>
<!-- <style scoped> -->
<style >
.BaseEchart {
width: 500px;
height: 500px;
}
</style>
App.vue
<template>
<div class="app">
<div class="BaseEchart">捣乱的div</div>
<BaseEchart></BaseEchart>
</div>
</template>
<script>
import BaseEchart from './components/BaseEchart.vue'
export default {
components: {
BaseEchart
}
}
</script>
<style >
</style>
获取组件实例
componentes/BaseForm.vue
<template>
<div class="BaseForm">
用户名: <input v-model="username" type="text" /><br />
密码: <input v-model="password" type="text" /><br />
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: ''
}
},
methods: {
getValues() {
return {
username: this.username,
psd: this.password
}
},
clearValues() {
this.username = ''
this.password = ''
}
}
}
</script>
<style>
</style>
App.vue
<template>
<div class="app">
<BaseForm ref="Form"></BaseForm>
<button @click="getDate">获取数据</button>
<button @click="clearDate">清空数据</button>
</div>
</template>
<script>
import BaseForm from './components/BaseForm.vue'
export default {
components: {
BaseForm
},
methods: {
getDate() {
console.log('www')
console.log(this.$refs.Form.getValues())
},
clearDate() {
this.$refs.Form.clearValues()
}
}
}
</script>
<style>
</style>
Vue 异步更新、$nextTick
App.vue
<template>
<div class="app">
<div v-if="isEdit">
<input ref="inp" v-model="editValue" type="text" />
<button @click="over">ok</button>
</div>
<div v-else>
{{ title }}
<button @click="changeTitle">edit</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
editValue: '',
title: '我是title',
isEdit: false
}
},
methods: {
changeTitle() {
// 1.显示输入框(异步dom更新
this.isEdit = true
// 2.让输入框获取焦点
// $nextTick等dom更新完,立刻执行函数体
this.$nextTick(() => {
this.$refs.inp.focus()
})
},
over() {
this.title = this.editValue
this.isEdit = false
}
}
}
</script>
<style>
</style>