组件
组件化:将页面拆分成多个组件,每个组件有自己独立的 结构、样式 和 行为
组件化的好处:提高开发效率,便于代码的维护和复用
单文件组件
一个 xxx.vue 文件就是一个 组件,也叫 单文件组件。比如 HelloWorld.vue 就是一个单文件组件
<template>
<!-- HTML结构 -->
<!-- 里面只能有一个根元素(Vue2) -->
<div class="box"></div>
</template>
<script>
// JavaScript行为
// 通常需要导出一个Vue配置对象
export default {
// data、methods、computed、watch...
}
</script>
<style scoped>
/* CSS样式 */
/* 默认只支持css语法,使用less语法的两个前提: */
/* 1.安装 less、less-loader 依赖包 */
/* 2.给style标签添加 lang="less" 属性 */
</style>
组件注册
组件注册:使用特定语法注册组件,然后将 组件对象 作为 html自定义标签 使用
为什么组件要先注册再使用?
为了明确组件之间的依赖关系,方便进行组件的复用和组件间的数据传递
局部注册
局部注册的组件只能在 当前组件 中使用。语法:
// App.vue
// 局部注册(只在当前组件中使用)
import 组件对象 from '组件文件路径'
export default {
components: {
组件名: 组件对象
// ... ...
}
}
全局注册
全局注册的组件在 所有组件 中都能使用,这类组件一般是通用的,如按钮组件、提示框组件等。语法:
// main.js
// 全局注册(通用组件,可以在任意组件中使用)
import 组件对象 from '组件文件路径'
Vue.component("组件名", 组件对象);
// ... ...
eg.组件的拆分与注册
<!-- App.vue -->
<template>
<!-- 在组件内部使用局部注册的组件 -->
<div class="box">
<MyHeader/>
<MyMain/>
<MyFooter/>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue'
import MyMain from './components/MyMain.vue'
import MyFooter from './components/MyFooter.vue'
export default {
// 局部注册
components: {
MyHeader,
MyMain,
MyFooter,
}
}
</script>
// main.js
import MyButton from "./components/MyButton.vue";
// 全局注册
Vue.component("MyButton", MyButton);
<!-- MyHeader.vue -->
<template>
<div class="header">
<span>MyHeader</span>
<!-- 使用全局注册的组件 -->
<MyButton/>
</div>
</template>
组件通信
父子关系是相对的,一个组件既可以作为父组件,也可以是子组件
非直接嵌套的两个组件不构成父子关系!
父子组件通信
# 父传子
- 自定义属性配合props接收
- 可以传递任意数量
- 可以传递任意类型
- 不能直接修改,数据流是单向的
# 子传父
- 自定义事件配合$emit触发
- 主要是为了间接修改父组件中的数据
- 需要一个触发时机
进行组件通信时,一般将公共数据维护在父组件中,需要时再传递给子组件;子组件不能直接修改父组件传递的数据,只能通过
$emit触发父组件中的事件处理函数,通知父组件修改。这就是Vue单向数据流的体现
props类型检查
子组件可以对接收的数据进行 类型检查。当父组件传递的数据不符合要求时,会在浏览器控制台中输出错误信息,帮助开发人员纠错
// 使用props的对象形式进行类型检查
props: {
nums: {
// 类型检查
type: Number,
// 是否必传
required: true,
// 没有传递数据时的默认值
default: 10,
// 自定义校验规则,返回true则校验通过
// value 接收到的数据
validator(value) {
if (value >= 0 && value <= 100) return true
}
}
}
非父子组件通信
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)
}
}
eventBus事件总线
事件总线 的本质就是一个空的 Vue 实例,这个实例拥有 $on()、$emit() 方法,用来在不同组件间传递数据
组件深入
组件中的data函数
# 为什么组件中的 data() 必须是一个函数?
为了保证每个组件实例,都维护一份独立的数据,不要相互影响
每复用一次组件,都会重新执行一次data()函数,得到一个新的对象
scoped原理
# scoped解决样式冲突的原理(默认组件中的样式全局生效)
给当前组件内的所有标签都添加一个 `data-v-hash值` 的自定义属性
给 style 中每个CSS选择器添加 `[data-v-hash值]` 的属性选择器
最终效果: 样式只作用于当前组件
v-model原理
v-model / .sync 双向绑定语法
组件上的事件都是自定义事件,因为组件本身就是一个自定义的标签
ref&$refs
<template>
<div id="app">
<BaseForm ref="baseForm"></BaseForm><br>
<button @click="resetForm">重置数据</button>
</div>
</template>
<script>
import BaseForm from './components/BaseForm.vue'
export default {
components: {
BaseForm
},
methods: {
resetForm() {
// $refs 获取子组件的组件实例
// reset() 子组件中的方法,清空表单数据
this.$refs.baseForm.reset()
}
}
}
</script>
$refs的特点
# 用来获取指定的DOM元素或组件实例
- 只在当前组件中查找
- 可以在父组件中访问子组件实例上的属性或方法
注意点
- this:获取当前组件的组件实例
- this.$refs.组件ref属性值:获取子组件的组件实例,以访问子组件中的数据和方法
$nextTick&DOM异步更新
为了提高渲染性能,Vue 中的 DOM 更新是 异步 的。当响应式数据改变影响DOM元素的显示与隐藏时,立即操作DOM元素 可能会有问题(获取不到)
使用 $nextTick 函数可以解决这个问题,将函数中的 DOM 操作推迟到 DOM 更新完成之后自动执行
handleEdit() {
// 控制输入框的显示
this.isShowEdit = true
// 错误:由于Vue操作DOM是异步的,所以此时还获取不到输入框
// this.$refs.inp.focus()
// console.log(this.$refs.inp) //undefined
// 正确:在DOM更新完毕后,执行其中的代码
this.$nextTick(()=>{
// 让输入框自动聚焦
this.$refs.inp.focus()
})
}
自定义指令
Vue 允许开发者自定义指令,封装一些 DOM 操作,简化开发
注册自定义指令
自定义指令 需要先注册再使用。注册方式有两种:
局部注册:只能在当前组件中使用。位置xxx.vue
directives:{
指令名,指令配置对象
// ... ...
}
全局注册:在所有组件中都能使用。位置main.js
Vue.directive(指令名,指令配置对象)
// ... ...
eg.利用自定义指令修改文字颜色
<div>
<!-- 使用自定义指令 -->
<h2 v-color="colorName">Hello Vue!</h2>
<!-- 修改颜色 -->
<button @click="colorName = colorName === 'red' ? 'blue' : 'red'">改变颜色</button>
</div>
export default {
data() {
return {
// 文字颜色
colorName: 'red'
}
},
// 局部注册-自定义指令
directives: {
color: {
// 修改文字颜色
// el就是自定义指令绑定的那个DOM元素
// binding.value可以获取指令传递的值
// inserted 函数仅在DOM首次渲染时执行一次
inserted(el,binding) {
el.style.color = binding.value
},
// update 函数每次指令值改变时都会执行
update(el,binding) {
el.style.color = binding.value
}
}
}
}
自定义指令和内置指令的用法基本一致,在标签上书写
v-指令名即可
插槽
使用
插槽进行占位,可以动态设置组件的 UI 结构。在组件复用时,子组件中的部分内容不是固定的
默认插槽
默认插槽:子组件中只有一个插槽,并且没有定义名称,这个插槽就是默认插槽
<!-- 封装插槽(子组件) -->
<template>
<slot>后备内容(默认模版结构)</slot>
</template>
<!-- 使用插槽(父组件) -->
<child-component>
<!-- 传递给插槽的模版结构 -->
<!-- 子组件标签中间的所有内容都会添加到默认插槽的位置 -->
</child-component>
实际上,默认插槽有一个默认的名字
default,可以使用下面具名插槽的语法
具名插槽
具名插槽:在子组件中的不同位置可以定义多个插槽,多个插槽使用 name 属性区分(有名字的插槽)
<!-- 封装插槽(子组件) -->
<template>
<slot name='插槽1名称'></slot>
<slot name='插槽2名称'></slot>
</template>
<!-- 使用插槽(父组件) -->
<!-- #插槽名称 是 v-slot:插槽名称 的简写形式-->
<child-component>
<template #插槽1名称>
<!-- 传递给插槽1的模版结构 -->
</template>
<template #插槽2名称>
<!-- 传递给插槽2的模版结构 -->
</template>
</child-component>
eg.具名插槽示例
作用域插槽
作用域插槽:用来传递子组件内部的数据
<!-- 封装插槽(子组件) -->
<template>
<slot name='插槽名称' :属性名1='数据1' :属性名2='数据2'></slot>
</template>
<!-- 使用插槽(父组件) -->
<child-component>
<template #插槽名称='接收的数据|对象'>
<!-- 传递给插槽的模版结构 -->
<!-- 可以在模版中使用子组件传递的数据 -->
</template>
</child-component>
作用域插槽会把所有要传递的数据封装为一个对象返回,可以直接在模版中进行解构
eg.作用域插槽示例
以上就是本篇的全部内容,如果你愿意,可以在这里复习一下:Vue工程化开发&组件进阶&插槽