# 一、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 ✍ 数据类型:StringNumberBooleanArrayObjectDateFunctionSymbol

提示

  父组件通过 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.use( plugin ) (opens new window)

  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>
更新于 : 8/7/2024, 2:16:31 PM