一、组件的三大组成部分(结构/样式/逻辑)
1. scoped样式冲突
默认情况:写在组件中的样式会全局生效 -> 因此很容易造成多个组件之间的样式冲突问题。
1. 全局样式:默认组件中的样式会作用到全局
2. 局部样式:可以给组件加上scoped属性,可以让样式只作用于当前组件
scoped的原理:
1. 当前组件内部标签都被添加data-v-hash值 的属性
2. CSS选择器都被添加[data-v-hash值] 的属性选择器
最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到
示例:
components/BaseOne.vue
components/BaseTwo.vue
<template>
<div class="base-one">
BaseTwo
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
div {
border: 3px solid red;
margin: 30px;
}
</style>
App.vue
<template>
<div id="app">
<BaseOne></BaseOne>
<BaseTwo></BaseTwo>
</div>
</template>
<script>
import BaseOne from './components/BaseOne'
import BaseTwo from './components/BaseTwo'
export default {
name: 'App',
components: {
BaseOne,
BaseTwo
}
}
</script>
效果:
2. data是一个函数
一个组件的data选项必须是一个函数。 -> 保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会执行一次data函数得到一个新对象
data() {
return {
count: 100
}
},
示例:
components/BaseCount.vue
<template>
<div class="base-count">
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 100,
}
},
}
</script>
<style>
.base-count {
margin: 20px;
}
</style>
App.vue
<template>
<div class="app">
<baseCount></baseCount>
<baseCount></baseCount>
<baseCount></baseCount>
</div>
</template>
<script>
import baseCount from './components/BaseCount'
export default {
components: {
baseCount,
},
}
</script>
<style>
</style>
效果:
二、组件通信
组件通信,就是指组件与组件之间的数据传递。
- 组件的数据是独立的,无法直接访问其他组件的数据。
- 想用其他组件的数据 -> 组件通信。
不同的组件关系 和 组件通信方案分类
组件关系分类:
1. 父子关系
2. 非父子关系
1. 组件通信语法
2. 父传子
父组件通过 props 将数据传递给子组件
- ①父组件:给组件添加标签,添加属性的方式,传值
- ②子组件:通过props进行接收
- ③子组件:渲染使用
示例:
效果:
什么是 prop
Prop定义:组件上 注册的一些自定义属性
Prop作用:向子组件传递数据
特点:
- 可以传递任意数量的prop
- 可以传递任意类型的prop
示例:
App.vue
<template>
<div class="app">
<UserInfo
:username="username"
:age="age"
:isSingle="isSingle"
:car="car"
:hobby="hobby"
></UserInfo>
</div>
</template>
<script>
import UserInfo from './components/UserInfo.vue'
export default {
data() {
return {
username: '小帅',
age: 28,
isSingle: true,
car: {
brand: '宝马',
},
hobby: ['篮球', '足球', '羽毛球'],
}
},
components: {
UserInfo,
},
}
</script>
<style>
</style>
components/UserInfo.vue
<template>
<div class="userinfo">
<h3>我是个人信息组件</h3>
<div>姓名:{{ username }}</div>
<div>年龄:{{ age }}</div>
<div>是否单身:{{ isSingle ? '是' : '否'}}</div>
<div>座驾:{{ car.brand }}</div>
<div>兴趣爱好: {{ hobby.join('、') }}</div>
</div>
</template>
<script>
export default {
props: ['username', 'age', 'isSingle', 'car', 'hobby']
}
</script>
<style>
.userinfo {
width: 300px;
border: 3px solid #000;
padding: 20px;
}
.userinfo > div {
margin: 20px 10px;
}
</style>
效果:
props校验
思考:组件的prop可以乱传吗?
作用:为组件的prop指定验证要求,不符合要求,控制台就会有错误提示 -> 帮助开发者,快速发现错误
语法:
①类型校验
props: {
校验的属性名:类型 // Number String Boolean···
},
②非空校验
③默认值
④自定义校验
props: {
校验的属性名: {
type: 类型, // Number String Boolean···
required: true, // 是否必填
default: 默认值, // 默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
},
示例:
App.vue
<template>
<div class="app">
<BaseProgress :w="width"></BaseProgress>
</div>
</template>
<script>
import BaseProgress from './components/BaseProgress.vue'
export default {
data() {
return {
width: 30,
}
},
components: {
BaseProgress,
},
}
</script>
<style>
</style>
components/BaseProgress.vue
<template>
<div class="base-progress">
<div class="inner" :style="{ width: w + '%' }">
<span>{{ w }}%</span>
</div>
</div>
</template>
<script>
export default {
// props: ["w"],
// 1.基础写法(类型校验)
// props: {
// // 类型校验
// w: Number
// }
// 2.完整写法(类型、是否必填、默认值、自定义校验)
props: {
w: {
type: Number,
required: true,
default: 0,
validator (value) {
// console.log(value)
if(value >= 0 && value <= 100) {
return true
} else {
console.error('传入的prop w必须是0-100的数字')
return false
}
}
}
}
}
</script>
<style scoped>
.base-progress {
height: 26px;
width: 400px;
border-radius: 15px;
background-color: #272425;
border: 3px solid #272425;
box-sizing: border-box;
margin-bottom: 30px;
}
.inner {
position: relative;
background: #379bff;
border-radius: 15px;
height: 25px;
box-sizing: border-box;
left: -3px;
top: -2px;
}
.inner span {
position: absolute;
right: 0;
top: 26px;
}
</style>
效果:
prop & data 单向数据流
共同点: 都可以给组件提供数据
区别:
- data的数据是自己的 -> 随便改
- prop的数据是外部的 -> 不能直接改,要遵循单向数据流
单向数据流:父组件的prop的数据更新,会单向的向下流动,影响到子组件。这个数据流动是单向的。
示例:
App.vue
<template>
<div class="app">
<BaseCount
:count="count"
@changeCount="handleChange"
></BaseCount>
</div>
</template>
<script>
import BaseCount from './components/BaseCount.vue'
export default {
components:{
BaseCount
},
data(){
return {
count:100
}
},
methods:{
handleChange(newCount) {
this.count = newCount
}
}
}
</script>
<style>
</style>
components/BaseCount.vue
<template>
<div class="base-count">
<button @click="handleSub">-</button>
<span>{{ count }}</span>
<button @click="handleAdd">+</button>
</div>
</template>
<script>
export default {
// 1.自己的数据随便修改 (谁的数据 谁负责)
// data () {
// return {
// count: 100,
// }
// },
// 2.外部传过来的数据 不能随便修改
// 单向数据流:父组件的prop更新,会单向的向下流动,影响到子组件
props: {
count: Number
},
methods: {
handleAdd() {
this.$emit('changeCount', this.count + 1)
},
handleSub() {
this.$emit('changeCount', this.count - 1)
}
}
}
</script>
<style>
.base-count {
margin: 20px;
}
</style>
3. 子传父
子组件利用 $emit 通知父组件,进行修改更新
- ①子组件:$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() {
// 1. 通过$emit,向父组件发送消息通知
this.$emit('changeTitle', '传智教育')
}
}
}
</script>
<style>
</style>
App.vue
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 2. 父组件,对消息进行监听 -->
<Son :title="myTitle" @changeTitle="handleChange"></Son>
</div>
</template>
<script>
import Son from "./components/Son.vue"
export default {
name: "App",
components: {
Son,
},
data() {
return {
myTitle: "学前端,就来黑马程序员",
}
},
methods: {
// 提供对应的处理函数,提供逻辑
handleChange(newTitle) {
// console.log(newTitle)
this.myTitle = newTitle
}
}
}
</script>
<style>
</style>
效果:
4. 非父子(拓展)— event bus 事件总线
作用:非父子组件之间,进行简易消息传递。(复杂场景 -> Vuex)
1. 创建一个都能访问到的事件总线(空Vue实例) -> utils/EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
2. A组件(接收方),监听Bus实例的事件
created() {
Bus.$on('sendMsg', (msg) => {
this.msg = msg
})
}
3. B组件(发送方),触发Bus实例的事件
Bus.$emit('sendMsg', '这是一个消息')
示例:
创建一个都能访问到的事件总线(空Vue示例) -> src/utils/EventBus.js
import Vue from 'vue'
// 1. 创建一个都能访问到的事件总线(空的Vue实例)
const Bus = new Vue()
export default Bus
src/components/BaseA.vue:接收方
<template>
<div class="base-a">
我是A组件(接收方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
// 在A组件(接收方),进行监听Bus的事件(订阅消息)
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
})
},
}
</script>
<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
src/components/BaseB.vue:发送方
<template>
<div class="base-b">
<div>我是B组件(发布方)</div>
<button @click="sendMsgFn">发送消息</button>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
methods: {
sendMsgFn() {
// 3. B组件(发送方),触发事件的方式传递参数
// 任何监听了这个消息的组件都可以接收到对应的消息
Bus.$emit('sendMsg', '今天天气不错,适合旅游')
},
},
}
</script>
<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
src/components/BaseC.vue:接收方
<template>
<div class="base-c">
我是C组件(接收方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
})
},
}
</script>
<style scoped>
.base-c {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
App.vue
<template>
<div class="app">
<BaseA></BaseA>
<BaseB></BaseB>
<BaseC></BaseC>
</div>
</template>
<script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
import BaseC from './components/BaseC.vue'
export default {
components:{
BaseA,
BaseB,
BaseC
}
}
</script>
<style>
</style>
5. 非父子通信(拓展) — provide & inject
provide & inject作用:跨层级共享数据。
1. 父组件provide提供数据
export default {
provide() {
return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型 【响应式】
userInfo: this.userInfo,
}
}
}
2. 子 / 孙组件 inject 取值使用
export default {
inject: ['color', 'userInfo'],
created() {
console.log(this.color, this.userInfo)
}
}
示例:
App.vue
<template>
<div class="app">
我是APP组件
<button @click="change">修改数据</button>
<SonA></SonA>
<SonB></SonB>
</div>
</template>
<script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {
provide() {
return {
// 简单类型 是非响应式的
color: this.color,
// 复杂类型 是响应式的
userInfo: this.userInfo,
}
},
data() {
return {
color: 'pink',
userInfo: {
name: 'zs',
age: 18,
},
}
},
methods: {
change() {
this.color = 'red'
this.userInfo.name = 'ls'
},
},
components: {
SonA,
SonB,
},
}
</script>
<style>
.app {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
}
</style>
components/SonA.vue
<template>
<div class="SonA">我是SonA组件
<GrandSon></GrandSon>
</div>
</template>
<script>
import GrandSon from '../components/GrandSon.vue'
export default {
components:{
GrandSon
}
}
</script>
<style>
.SonA {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
height: 200px;
}
</style>
components/SonB.vue
<template>
<div class="SonB">
我是SonB组件
</div>
</template>
<script>
export default {
}
</script>
<style>
.SonB {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
height: 200px;
}
</style>
components/GrandSon.vue
<template>
<div class="grandSon">
我是GrandSon
{{ color }} -{{ userInfo.name }} -{{ userInfo.age }}
</div>
</template>
<script>
export default {
inject: ['color', 'userInfo'],
}
</script>
<style>
.grandSon {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
height: 100px;
}
</style>
效果:
三、综合案例:小黑记事本(组件版)
需求说明:
①拆分基础组件
新建组件 -> 拆分存放结构 -> 导入注册使用
②渲染待办任务
提供数据(公共父组件) -> 父传子传递list -> v-for渲染
③添加任务
收集数据 v-model -> 监听事件 -> 子传父传递任务 -> 父组件unshift
④删除任务
监听删除携带id -> 子传父传递id -> 父组件filter删除
⑤底部合计 和 清空功能
底部合计:父传子传递list -> 合计展示
清空功能:监听点击 -> 子传父通知父组件 -> 父组件清空
⑥持久化存储
watch监视数据变化,持久化到本地
TodoHeader.vue
<template>
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input
@keyup.enter="handleAdd"
placeholder="请输入任务" class="new-todo" v-model="todoName"/>
<button class="add" @click="handleAdd">添加任务</button>
</header>
</template>
<script>
export default {
data() {
return {
todoName: ''
}
},
methods: {
handleAdd() {
// console.log(this.todoName)
// 非空判断
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 class="destroy" @click="handleDel(item.id)"></button>
</div>
</li>
</ul>
</section>
</template>
<script>
export default {
props: {
list: Array,
},
methods: {
handleDel(id) {
this.$emit('delete', id)
}
}
};
</script>
<style>
</style>
TodoFooter.vue
<template>
<!-- 统计和清空 -->
<footer class="footer">
<!-- 统计 -->
<span class="todo-count">合 计:<strong> {{ list.length }} </strong></span>
<!-- 清空 -->
<button class="clear-completed" @click="clear">清空任务</button>
</footer>
</template>
<script>
export default {
props: {
list: Array
},
methods: {
clear() {
this.$emit('clear')
}
}
};
</script>
<style>
</style>
App.vue
<template>
<!-- 主体区域 -->
<section id="app">
<TodoHeader @add="handleAdd"></TodoHeader>
<TodoMain :list="list" @delete="handleDel"></TodoMain>
<TodoFooter :list="list" @clear="handleClear"></TodoFooter>
</section>
</template>
<script>
import TodoHeader from "./components/TodoHeader.vue";
import TodoMain from "./components/TodoMain.vue";
import TodoFooter from "./components/TodoFooter.vue";
// 渲染功能:
// 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(自己的数据自己负责)
// 底部合计
// 1. 父传子传list
// 2. 数据统计
// 清空功能
// 1. 子传父,通知到父组件
// 2. 由父组件进行清空
// 持久化存储
// 1. watch深度监视list的变化,往本地存储,进入页面优先读取本地
export default {
data() {
return {
list: JSON.parse(localStorage.getItem("list")) || [
{ id: 1, name: "打篮球" },
{ id: 2, name: "跳绳" },
{ id: 3, name: "打羽毛球" },
{ id: 4, name: "游泳" },
],
};
},
components: {
TodoHeader,
TodoMain,
TodoFooter,
},
watch: {
// 对list进行监视
list: {
deep: true,
handler(newValue) {
localStorage.setItem("list", JSON.stringify(newValue));
}
},
},
methods: {
// 删除
handleDel(id) {
this.list = this.list.filter((res) => res.id !== id);
},
// 添加
handleAdd(todoName) {
// console.log(todoName)
this.list.unshift({
id: +new Date(),
name: todoName,
});
},
// 清空
handleClear() {
this.list = [];
},
},
};
</script>
<style>
</style>
效果:
四、进阶语法
1. v-model原理
原理:v-model本质上是一个语法糖。例如应用在输入框上,就是value属性 和 input事件的合写。
作用:提供数据的双向绑定
①数据变化,视图跟着变 :value
②视图变,数据跟着变 @input
注意:$event用于在模板中,获取事件的形参
<template>
<div id="app">
<input v-model="msg" type="text">
<input :value="msg" @input="msg = $event.target.value" type="text">
</div>
</template>
示例:
App.vue
<template>
<div class="app">
<input type="text" v-model="msg1"/>
<br />
<!-- 模板中获取事件的形参 -> $event 获取 -->
<input type="text" :value="msg2" @input="msg = $event.target.value">
</div>
</template>
<script>
export default {
data() {
return {
msg1: '',
msg2: ''
}
},
}
</script>
<style>
</style>
效果:
2. v-model应用于组件
表单类组件封装 & v-model简化代码
1. 表单类组件 封装
①父传子:数据应该是父组件 props 传递过来的,v-model 拆解 绑定数据
②子传父:监听输入,子传父传值给父组件修改
App.vue
<template>
<div class="app">
<BaseSelect
:cityId="selectId"
@changeId="selectId = $event"
>
</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>
<style>
</style>
components/BaseSelect.vue
<template>
<div>
<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>
</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>
效果:
2. 父组件v-model简化代码,实现子组件和父组件数据双向绑定
①子组件中:props通过value接收,事件触发input
②父组件中:v-model给组件直接绑定数据(:value + @input)
示例:
BaseSelect.vue
<template>
<div>
<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>
</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>
<div class="app">
<BaseSelect
v-model="selectId"
>
</BaseSelect>
</div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data() {
return {
selectId: '103',
}
},
components: {
BaseSelect,
},
methods: {
handleChange(e) {
console.log(e);
this.selectId = e
}
}
}
</script>
<style>
</style>
效果:
3. .sync修饰符
作用:可以实现子组件和父组件数据的双向绑定,简化代码
特点:prop属性名,可以自定义,非固定为value
场景:封装弹框类的基础组件,visible属性:true显示,false因此
本质:就是 :属性名 和 @update:属性名 合写
示例:
App.vue
<template>
<div class="app">
<button class="logout"
@click="isShow = true"
>退出按钮</button>
<!-- :visible.sync 等价于 :visible + @update:visible -->
<BaseDialog
:visible.sync="isShow"
></BaseDialog>
</div>
</template>
<script>
import BaseDialog from "./components/BaseDialog.vue"
export default {
data() {
return {
isShow: false
}
},
methods: {
},
components: {
BaseDialog,
},
}
</script>
<style>
</style>
BaseDialog.vue
<template>
<div class="base-dialog-wrap" v-show="visible">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button class="close" @click="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: {
visible: Boolean
},
methods: {
close() {
this.$emit('update:visible', 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;
}
.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>
效果:
4. ref 和 $refs / $nextTick
作用:利用ref 和 $refs可以用于获取dom元素,或 组件实例
特点:查找范围 -> 当前组件内(更精确稳定)
①获取dom:
1. 目标标签 — 添加ref属性
<div ref="chartRef">我是渲染图表的容器</div>
2. 恰当时机,通过this.$refs.xxx,获取目标标签
mounted() {
console.log(this.$refs.chartRef)
),
示例:
App.vue
<template>
<div class="app">
<div class="base-chart-box">
这是一个捣乱的盒子
</div>
<BaseChart></BaseChart>
</div>
</template>
<script>
import BaseChart from './components/BaseChart.vue'
export default {
components:{
BaseChart
}
}
</script>
<style>
.base-chart-box {
width: 200px;
height: 100px;
}
</style>
BaseChart.vue
<template>
<div ref="mychart" class="base-chart-box">子组件</div>
</template>
<script>
import * as echarts from 'echarts'
export default {
mounted() {
// 基于准备好的dom,初始化echarts实例
// const myChart = echarts.init(document.querySelector('.base-chart-box'))
const myChart = echarts.init(this.$refs.mychart)
// console.log(this.$refs.mychart)
// 指定图表的配置项和数据
const option = {
title: {
text: 'ECharts 入门示例',
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20],
},
],
}
// 使用刚指定的配置项和数据显示图表
myChart.setOption(option)
},
}
</script>
<style scoped>
.base-chart-box {
width: 400px;
height: 300px;
border: 3px solid #000;
border-radius: 6px;
}
</style>
效果:
②获取组件:
1.目标组件 — 添加ref属性
<BaseForm ref="baseForm"></BaseForm>
2. 恰当时机,通过this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法
this.$refs.baseForm.组件方法()
示例:
BaseForm.vue
<template>
<div class="app">
<div>
账号: <input v-model="username" type="text">
</div>
<div>
密码: <input v-model="password" type="text">
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: 'admin',
password: '123456',
}
},
methods: {
// 收集表单数据,返回一个对象
getValues() {
return {
username: this.username,
password: this.password
}
},
// 重置表单
resetValues() {
this.username = ''
this.password = ''
}
}
}
</script>
<style scoped>
.app {
border: 2px solid #ccc;
padding: 10px;
}
.app div{
margin: 10px 0;
}
.app div button{
margin-right: 8px;
}
</style>
App.vue
<template>
<div class="app">
<BaseForm ref="baseForm"></BaseForm>
<button @click="handleGet">获取数据</button>
<button @click="handleReset">重置数据</button>
</div>
</template>
<script>
import BaseForm from './components/BaseForm.vue'
export default {
data() {
return {
}
},
components: {
BaseForm,
},
methods: {
handleGet() {
console.log(this.$refs.baseForm.getValues())
},
handleReset() {
this.$refs.baseForm.resetValues()
}
}
}
</script>
<style>
</style>
效果:
5. Vue异步更新、$nextTick
$nextTick:等DOM更新后,才会触发执行此方法里的函数体
语法:this.$nextTick(函数体)
this.$nextTick(() => {
// 业务逻辑
})
需求:编辑标题,编辑框自动聚焦
1. 点击编辑,显示编辑框
2. 让编辑框,立刻获取焦点
this.isShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点
问题:”显示之后“,立刻获取焦点是不能成功的!
原因:Vue是异步更新DOM(提升性能)
this.$nextTick(() => {
this.$refs.inp.focus()
})
示例代码:
App.vue
<template>
<div class="app">
<div v-if="isShowEdit">
<input type="text" v-model="editValue" ref="inp" />
<button>确认</button>
</div>
<div v-else>
<span>{{ title }}</span>
<button @click="handleEdit">编辑</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
title: '大标题',
isShowEdit: false,
editValue: '',
}
},
methods: {
handleEdit() {
// 1. 显示输入框(异步DOM更新)
this.isShowEdit = true
// 2. 让输入框 获取焦点($nextTick等DOM更新完, 立刻去执行指定的函数体)
this.$nextTick(() => {
this.$refs.inp.focus()
})
}
},
}
</script>
<style>
</style>
效果:点击编辑后,显示输入框,并获取焦点