# 一、vue 组件属性
# 1、ref 属性
既然 vue 可以做到不操作 DOM 的前提下实现数据绑定,那我们获取 DOM 元素需要使用原生方法吗?
使用原生方法操作 DOM 和 ref 引用操作 DOM,区别就是 ref 引用对原标签没有任何影响,不需要添加 id 或 class 属性。
<template>
<div class="home">
<span id="msg1">元素方法获取DOM </span>
<span ref="msg2">vue的ref属性获取DOM </span>
<hello-world ref="hlw">vue的ref属性化子组件实例</hello-world>
<!-- ================= -->
<button @click="showDOM">打印</button>
</div>
</template>
<script>
import HelloWorld from '../components/HelloWorld'
export default {
components: {
HelloWorld
},
methods: {
show_DOM_VC() {
console.log(document.querySelector('#msg1'))
// 结果:<span id="msg1">元素方法获取DOM </span>
console.log(this.$refs.msg2)
// 结果:<span>vue的ref属性获取DOM </span>
console.log(this.$refs.hlw)
// 结果:VueComponent{...} --- HelloWorld组件的vc
}
}
}
</script>
# 2、props 属性
我们知道 vue 实现了响应式数据,但我们一直都是在非单文件或者单独的组件中使用。
既然现在使用了使用 vue 单文件后,vue 组件产生明显的父子关系、兄弟关系。那我们能不能让 子组件中的某些数据由父组件操作呢?
答案
可以在子组件中使用 props 属性,props 可以是数组或对象,用于接收来自父组件的数据。
如果属性值是对象,还可以进行高级选项配置(如类型检测、自定义验证和设置默认值)
Vue.component('props-demo-advanced', {
props: {
// 检测类型
height: Number,
// 检测类型 + 其他验证
age: {
type: Number,
default: 0, // 通常default 和 required 不会 😂 同时出现
required: true,
validator: function (value) {
return value >= 0
}
},
// 对象默认值
info: {
type: Object,
default: () => ({})
}
}
})
父组件传递的数据类型 type 可以是任何 JavaScript ✍ 数据类型:String
、Number
、Boolean
、Array
、Object
、Date
、Function
、Symbol
等
提示
父组件通过 props 属性不一定非要向子组件传送响应式数据
- 父组件
<template>
<div class="home">
<h3>HomeView组件内容</h3>
子组件内容如下:
<!-- :age 表示属性值为一个✨js表达式,此时 22 是一个number类型 -->
<hello-world userName="lencamo" :age="22"></hello-world>
</div>
</template>
<script>
import HelloWorld from '../components/HelloWorld'
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
</script>
- 子组件
<template>
<div class="hello">
<p>{{ msg }}</p>
<ul>
<li>姓名:{{ userName }}</li>
<li>年龄:{{ age }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Welcome to Your Son component!'
}
},
// 数组方式
props: ['userName', 'age']
// 对象方式
props: {
// userName: String,
// age: Number
userName: {
type: String,
required: true
},
age: {
type: Number,
default: 19
}
}
}
</script>
注意
在封装通用组件的时候,合理地使用props
可以极大的提高组件的复用性
要注意的是,通用组件中封装的自定义属性是 只读的 ,程序员不能直接修改(单向数据流)。
但如果 props 传的是对象类型(对象引用),其实子组件是可以直接修改的,为了解决这个问题 vue3 中提供了
readonly()
如果是自己写的子组件的话,我们可以将从父组件中接收的数据赋值给 data,来达到间接修改 props 的目的。
- 子组件
<template>
<div class="hello">
<p>{{ msg }}</p>
<ul>
<li>姓名:{{ userName }}</li>
<li>
年龄:{{ myAge }}
<button @click="add">加1</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Welcome to Your Son component!',
myAge: this.age // 从这个角度:props数据是优先于data数据的
}
},
props: ['userName', 'age'],
methods: {
add() {
this.myAge++
}
}
}
</script>
拓展:禁用 Attribute 继承
默认情况下,在组件标签上的其他非 Prop 的 Attribute 将自动添加到子组件的根节点上。
常见的非 Prop 的 Attribute 有:class、style、id 属性等
如果我们不想让他们全部都添加到子组件的根节点上,我们可以如下操作:
- 父组件
inheritAttrs: false
<template>
<div class="home">
<h3>HomeView组件内容</h3>
子组件内容如下:
<hello-world name="20级20班" class="active"></hello-world>
</div>
</template>
<script>
import HelloWorld from '../components/HelloWorld'
export default {
name: 'HomeView',
// 禁用 Attribute 继承
inheritAttrs: false,
components: {
HelloWorld
}
}
</script>
- 子组件
$attrs
<template>
<div class="hello">
<!-- 通过$attrs访问 -->
<p :name="$attrs.name">{{ msg }}</p>
<ul :class="$attrs.class">
<li>姓名:{{ userName }}</li>
<li>年龄:{{ age }}</li>
</ul>
</div>
</template>
当子组件有多个根节点时,如果没有显示的绑定 Attribute,控制台会报错
<template>
<div class="hello">
<!-- …… -->
</div>
<div class="world" v-bind="$attrs">
<!-- …… -->
</div>
</template>
# 3、mixin 混入 🌈
官方:
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
理解:
多个组件可以共用的一些相同的配置(此处可以联想一下 Vuex)。和 Vuex 用于状态管理模式不同,mixin 混入用于复用实例对象一样包含实例选项的某些内容。
使用:
mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。
使用体验
使用 mixin 虽然方便,但有一个不好的缺点就是:组件出现了一些新的方法和属性,需要在 mixin.js 文件中查阅,有点 😂 不方便。
- mixin.js
export const alertWin = {
methods: {
showName() {
alert(this.title)
}
}
}
- HelloWorld.vue
<template>
<div class="hello">
<br />
<!-- 这里的showName方法为mixin混合中的方法 -->
<button @click="showName">新增文章</button>
<p>{{ msg }}</p>
</div>
</template>
<script>
import { alertWin } from '@/mixin.js'
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Welcome to Your Vue.js App',
title: '新增'
}
},
mixins: [alertWin]
}
</script>
关于混入和原组件中的一些内容冲突:如果是生命周期钩子的话都要;如果是其他的话选择原组件的内容。
和单文件组件分类一样,mixin 也可以有全局混入:
全局混入
- main.js
// 1、导入
import { alertWin } from '@/mixin.js'
// 2、注册
Vue.mixin(alertWin)
- HelloWorld.vue
<template>
<div class="hello">
<br />
<!-- 这里的showName方法为mixin混合中的方法 -->
<button @click="showName">新增文章</button>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Welcome to Your Vue.js App',
title: '新增'
}
}
}
</script>
但通常不会使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。官方更加推荐将其作为插件 (opens new window)发布,以避免重复应用混入。
# 4、vue 插件
vue 插件的功能就要比 mixin 混入强大的多了,因为 vue 插件中也可以通过全局混入来添加一些组件选项
Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
- main.js
// 1、导入
import plugins from '@/plugins.js'
// 2、注册
Vue.use(plugins)
- plugins.js
如果插件是一个对象,必须提供 install 方法。
const myPlugin = {
install(Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind(el, binding, vnode, oldVnode) {
// 逻辑...
}
// ...
})
}
}
export default myPlugin
如果插件是一个函数 ✨,它会被作为 install 方法。
function myPlugin(Vue, options) {
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
// ...
})
// 4. 添加实例方法(vm和vc都能用)
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
export default myPlugin
应用:全局注册子组件(对象方式)
- main.js
import GlobalComponents from '@/components/index.js'
Vue.use(GlobalComponents)
- src/components/index.js
import PageTools from './PageTools/index.vue'
export default {
install(Vue) {
Vue.component('PageTools', PageTools)
}
}
# 二、scoped 样式
# 1、样式冲突
由于默认情况下, vue 组件的样式会全局 🚩 生效,因此很容易造成多个组件之间的样式冲突问题。至于发生冲突时,最终采取谁的样式方案,和原生 CSS 一样:后来者居上原则。
import HomeView from './view/HomeView'
import AboutWorld from './view/AboutWorld'
解决方案
当前组件的 CSS 样式只影响到自己的 UI 结构。
- 方式一:尽量避免在子组件中编写公共样式,利用元素间的层级关系加以区分。
<style lang="less">
.home-container {
flex: 1;
min-height: 250px;
/* 附带父子✨关系来区分:.home-container下的h3样式 */
h3 {
color: blue;
}
}
</style>
- 方式二:使用
dataset
为当前单文件组件的每一个 DOM 元素添加自定义属性v-xxx
即:vue 中的 scoped 属性
<style lang="less">
/* CSS属性选择器 */
h3[data-v-001] {
color: blue;
}
</style>
# 2、样式隔离
scoped 会对当前组件的所有元素添加属性选择器进行标识(对于引入的组件,会在其最外层容器上进行标记)
<style lang="less" scoped>
h3 {
color: blue;
}
</style>
# 3、样式穿透 ✨
使用scoped
后,我们发现连基本的向下继承也失效了 😭。
问题:
- 公共样式无法向下传递
- 使用第三方组件库(element UI、vant)时,该组件默认样式时无效
解决办法:
注意
- "
/deep/
"是一个 CSS 选择器伪类,用于匹配嵌套层次较深的元素 - "
::v-deep
"是 Vue.js 特有的 Scoped CSS 深度作用选择器,允许开发者在子组件中访问父组件的样式
"/deep/
"选择器已经被 W3C (opens new window) 废弃,因为它会导致样式层叠和命名冲突的问题。目前推荐使用 CSS 变量、组合选择器或 JavaScript 来代替"/deep/"选择器的功能。
<style lang="less" scoped>
/* 使用方式1 */
.inputText ::v-deep input {
background-color: black;
}
/* 效果: */
/* .inputText[data-v-001] input {
background-color: black;
} */
/* 使用方式2 */
::v-deep .inputText input {
background-color: black;
}
/* 效果: */
/* [data-v-001] .inputText input {
background-color: black;
} */
</style>
# 4、CSS 引入
使用 @import 的方式引入外部 CSS 文件,会绕开我们设置的 scoped 也就是只在当前页面生效,所以这里我在这个页面@import 引入的 index.css 实际上是应用到全局中去了。
- 错误示例
<style scoped>
@import url('../styles/index.css');
</style>
- 推荐方式
<!-- style 标签上引入 -->
<style scoped src="../styles/index.css"></style>
<!-- 头部标签内引入 -->
<link rel="stylesheet" href="style.css" />
# 三、class 绑定
在原生 js 中,我们通常采用 element.classList.remove()
、element.classList.add()
、element.classList.toggle()
来添加/删除某些 class,从而实现 class 的动态化。
在 vue 中我们可以对 class 使用 v-bind
指令达到同样的效果。
# 1、对象写法
记忆
重点在:是否要使用某个 class
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"></div>
- 动态控制
data: {
isActive: true,
hasError: false
}
- 渲染结果
<div class="static active"></div>
# 2、数组写法
记忆
重点在:要使用哪个 class
<div class="static" :class="[activeClass, errorClass]"></div>
- 动态控制
有点像映射
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
- 渲染结果
<div class="static active"></div>
# 2、综合应用 ✨
有没有一种写法,即可以像对象写法一样可以决定是否采用某个 class,又可以像数组写法一样来筛选要使用那个 class。
提示
在数组中利用三元表达式,可以到达对象写法一样的效果 🤔
<div class="static" :class="[hasError? 'text-danger' : ''] "></div>
<div class="static" :class="{'text-danger': hasError}"></div>
- 综合写法
<div
class="static"
:class="['baseCss', activeClass, hasError ? 'text-danger' : '', { 'text-danger': hasError }]"
></div>
data: {
activeClass: 'active', // 使用那个
hasError: false // 是否使用
}
# 四、style 绑定
在原生 js 中,我们通常采用 element.style
来进行样式操作,从而实现 style 样式 的动态化。
在 vue 中我们可以对 style 样式使用 v-bind
指令达到同样的效果。
# 1、对象写法
① 方式 1
<div :style="{ color: fontColor, fontSize: fontSize + 'px' }"></div>
data: {
fontColor: "blue",
fontSize: 33
}
② 方式 2
<div :style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
# 2、数组写法
<div v-bind:style="[styleObject1, {backgroundColor: 'purple'}]"></div>