Vue2:组件高级(上)

Vue2:组件高级(上)

Date: May 20, 2023
Sum: 组件样式冲突、data函数、组件通信、props、组件生命周期、vue3.x中全局配置axios


目标:

能够掌握 watch 侦听器的基本使用

能够知道 vue 中常用的生命周期函数

能够知道如何实现组件之间的数据共享

能够知道如何在 vue3.x 的项目中全局配置 axios

前言:以下使用较老的axios,否则会报错

npm i [email protected] -S


组件之间的样式冲突

样式冲突问题:

默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题

导致组件之间样式冲突的根本原因是:

① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的

② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

思考:如何解决组件样式冲突的问题

为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:

Vue2:组件高级(上)_第1张图片



style 节点的 scoped 属性

为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:

Vue2:组件高级(上)_第2张图片

注意:父组件与子组件都要的style都要加上scoped

默认情况:写在组件中的样式会 全局生效 一因此很容易造成多个组件之问的样式冲突问题

  1. 全局样式:默认组件中的样式会作用到全局
  2. 局部样式:可以给组件加上 scoped 属性,可以让样式只作用于当前组件

原理

  1. 当前组件内标签都被添加data-v-hash值 的属性
  2. css选择器都被添加 [data-v-hash值] 的属性选择器

最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

Vue2:组件高级(上)_第3张图片



/deep/ 样式穿透

如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用 /deep/ 深度选择器。

Vue2:组件高级(上)_第4张图片

注意:/deep/ 是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep() 替代 /deep/。

Vue3中的样式结构::deep(标签)





data必须是一个函数

原因

目的:保证每个组件实例,维护独立的一份数据对象。

每次创建新的组件实例,都会新执行一次data 函数,得到一个新对象。

Vue2:组件高级(上)_第5张图片

举例

data()函数能够保证每个组件的数据是独立的

Vue2:组件高级(上)_第6张图片




组件通信

基础概念:

基础概念

组件通信:指组件与组件之间的数据传递

  • 组件的数据是独立的,无法直接访问其他组件的数据。
  • 想使用其他组件的数据,就需要组件通信

组件之间的关系

在项目开发中,组件之间的关系分为如下 3 种:

① 父子关系 ② 兄弟关系 ③ 后代关系

AB是父子关系,BC有一个共同的父级节点,故二者为兄弟关系。B和EFI都为特殊的兄弟关系。

A和DGH属于后代关系

Vue2:组件高级(上)_第7张图片



父子组件之间的数据共享

父子组件之间的数据共享又分为:

① 父 -> 子共享数据 ② 子 -> 父共享数据 ③ 父 <-> 子双向数据同步

通信过程:

1-父组件通过 props 将数据传递给子组件

2-子组件利用 $emit 通知父组件修改更新

Vue2:组件高级(上)_第8张图片


父向子组件共享数据

父组件通过 v-bind 属性绑定向子组件共享数据。同时,子组件需要使用 props 接收数据。

Untitled

案例:

App.vue




MySon.vue




效果:

Vue2:组件高级(上)_第9张图片


子向父组件共享数据

子组件通过自定义事件的方式向父组件共享数据。

具体步骤:

子组件:

  1. 声明自定义事件 2. 数据变化时,触发自定义事件

父组件:

  1. 监听子组件的自定义事件 numchang 2.通过形参,接收子组件传递过来的数据

Vue2:组件高级(上)_第10张图片

案例:

App.vue




Son.vue




效果:

Vue2:组件高级(上)_第11张图片


父子组件之间数据的双向同步

父组件在使用子组件期间,可以使用 v-model 指令维护组件内外数据的双向同步:

具体步骤:

  1. 父组件向子组件的props中传递数据
    1. 这里通过 v-model 方式进行双向数据绑定,维护组件两方数据同步
  2. 子组件声明emits属性,组件内的元素需要以 update: 的方式开头,这里需要更新哪个数据,就把相应数据的值丢过来,比如number
    1. 通过 $emits 的方式将数据发送出去

Vue2:组件高级(上)_第12张图片

好处:父组件中不用再监听自定义事件,也不用再额外定义事件处理函数

案例:

  • Code:

    App.vue

    
    
    
    

    Son.vue

    
    
    
    

效果:

Vue2:组件高级(上)_第13张图片



兄弟组件之间的数据共享

2023Vue教程的做法

**作用:**非父子组件之间,进行简易消息传递。(复杂场景→ Vuex)

步骤

1-创建一个都能访问的事件总线 (空Vue实例)

注:把这个放在utils下的EventBus.js中

import Vue from 'vue'
const Bus = new Vue()
export default Bus

2-A组件(接受方),监听Bus的 $on事件

// 先导入Bus
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
	// 再从 created 阶段就监听 $on 事件
  created() {
    Bus.$on('sendMsg', (msg) => {
      // console.log(msg)
      this.msg = msg
    })
  },
}

3-B组件(发送方),触发Bus的$emit事件

注:这个在组件内

import Bus from '../utils/EventBus'
export default {
  methods: {
    sendMsgFn() {
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
    },
  },
}

图示

注意:这是个一对多的发送

Untitled

案例:传递A组件数据给B组件

Vue2:组件高级(上)_第14张图片

  • Code:

    BaseA.vue

    
    
    
    
    
    

    BaseB.vue

    
    
    
    
    
    

2021Vue教程的做法

兄弟组件之间实现数据共享的方案是 EventBus。

可以借助于第三方的包 mitt 来创建 eventBus 对象,从而实现兄弟组件之间的数据共享。

示意图如下:

Vue2:组件高级(上)_第15张图片

理解:在数据接收方调用on方法来声明自定义事件,在数据发送方通过emit方法来触发emit事件

3.1 安装 mitt 依赖包

在项目中运行如下的命令,安装 mitt 依赖包:

npm install [email protected]

3.2 创建公共的 EventBus 模块

在项目中创建公共的 eventBus 模块如下:

// eventBus.js

// 导入 mitt 包
import mitt from 'mitt'
// 创建 EventBus 的实例对象
const bus = mitt()

// 将 EventBus 的实例对象共享出去
export default bus

3.3 在数据接收方自定义事件

在数据接收方,调用 bus.on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件。

示例代码如下:

// 导入 eventBus.js 模块, 得到共享的bus对象

export default {
	data() {return { count: 0}},
	created() {
		// 在created生命周期函数中声明自定义事件
		// 调用 bus.on 方法注册一个自定义事件,通过事件处理函数的形参数接收数据
		bus.on('countChange', (count) => {
			this.count = count
		})
	}
}

3.4 在数据接发送方触发事件

在数据发送方,调用 bus.emit(‘事件名称’, 要发送的数据) 方法触发自定义事件。示例代码如下:

// 导入 eventBus.js 模块,得到共享的 bus 对象
import bus from './eventBus.js'

export default {
	data() {return { count: 0}},
	methods: {
		addCount() {
			this.count++
			bus.emit('countChange', this.count) // 调用 bus.emit() 方法触发自定义事件,并发送数据
		}	
	}
}

案例:

  • Code:

    Left.vue

    
    
    
    

    Right.vue

    
    
     
    

效果:

Vue2:组件高级(上)_第16张图片



后代关系组件之间的数据共享-provide&inject

作用:跨层级共享数据

场景

Vue2:组件高级(上)_第17张图片

后代关系组件之间共享数据,指的是父节点的组件向其子孙组件共享数据。此时组件之间的嵌套关系比较复杂,可以使用 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)
  }
}

图示

Vue2:组件高级(上)_第18张图片

注意:

1-provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)如上图所示,如果我用button修改color,那么图中元素不会有变动,而用button修改userInfo中的数据,则图中相应元素会有变动。

2-子/孙组件通过inject获取的数据,不能在自身组件内修改


补充:2021版的Vue课程

父节点对外共享响应式的数据

值得注意的是,provide中return回去的数据,并非是响应式的数据,即若我在父组件中用button修改p标签的颜色,子组件的中的p标签颜色不会跟着一块变。

父节点使用 provide 向下共享数据时,可以结合 computed 函数向下共享响应式的数据。示例代码如下:

Vue2:组件高级(上)_第19张图片

子孙节点使用响应式的数据

如果父级节点共享的是响应式的数据,则子孙节点必须以 .value 的形式进行使用。示例代码如下:

Vue2:组件高级(上)_第20张图片



vuex

vuex 是终极的组件之间的数据共享方案。在企业级的 vue 项目开发中,vuex 可以让组件之间的数据共享变得高效、清晰、且易于维护。


个人总结:

父子关系

① 父 -> 子 属性绑定
② 子 -> 父 事件绑定
③ 父 <-> 子 组件上的 v-model

兄弟关系

④ EventBus

后代关系

⑤ provide & inject

全局数据共享

⑥ vuex




组件的 props

为了提高组件的复用性,在封装 vue 组件时需要遵守如下的原则:

组件的 DOM 结构、Style 样式 要尽量复用

组件中要展示的数据,尽量由组件的使用者提供

为了方便使用者为组件提供要展示的数据,vue 组件提供了 props 的概念。



基础概念:

概念:组件上 注册的一些 自定义属性

作用:父组件通过 props 向子组件传递要展示的数据

特点:可以传递 任意数量与类型 的prop ;提高了组件的复用性

语法:简易写法

子组件接收

props: ['数据1', '数据2']

举例

传递父组件中的数据到子组件中

使用 v-bind 属性绑定的形式,为组件动态绑定 props 的值

Vue2:组件高级(上)_第21张图片

注意: :username=”username” 左边是子,右边是父

效果:

Vue2:组件高级(上)_第22张图片

  • Code:

    App.vue

    
    
    
    
    
    

    UserInfo.vue

    
    
    
    
    
    


props校验

作用:为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误

语法

  • 类型校验(最常用)
props: {
	校验的属性名:类型    // Number String Boolean ...
}
  • 非空校验
  • 默认值
  • 自定义校验

举例:进度条的进度只能传入数字而不能是其他的数据类型

Vue2:组件高级(上)_第23张图片

BaseProgress.vue 子组件接收父组件的数据

export default {
  // 1.基础写法(类型校验)
  props: {
    w: Number,
  },
}

props校验完整写法

类型校验是最常用的,如果你需要后面几种校验,就需要补充以下的写法:

语法

props: {
  校验的属性名: {
    type: 类型,  // Number String Boolean ...
    required: true, // 是否必填
    default: 默认值, // 默认值
    validator (value) {
      // 自定义校验逻辑
      return 是否通过校验
    }
  }
},

代码示例:


注意

1.default和required一般不同时写(因为当时必填项时,肯定是有值的)

2.default后面如果是简单类型的值,可以直接写默认。如果是复杂类型的值,则需要以函数的形式return一个默认值


props&data、单向数据流

**共同点:**都可以给组件提供数据

区别:

  • data 的数据是自己的 → 随便改
  • prop 的数据是外部的 → 不能直接改,要遵循 单向数据流

单向数据流:

父级props 的数据更新,会向下流动,影响子组件。这个数据流动是单向的,即父的数据更新流向子

子若想影响父的数据,需要通过$.emit来影响父。然后,父在将数据单向流动给子。

Vue2:组件高级(上)_第24张图片

口诀:谁的数据谁负责

案例:子接收父Count值,并且子通过 this.$emit 传递方法给父,让其修改数据

Vue2:组件高级(上)_第25张图片

子想要改变父数据,需要通过 this.$emit 进行传递数据

父接收子changeCount方法,并利用 handleChange 接收数据,从而修改自身Count值

Vue2:组件高级(上)_第26张图片

注意:@changeCount=”handleChange” 左边是子,右边是父

  • Code:

    App.vue

    
    
    
    
    
    

    BaseCount.vue

    
    
    
    
    
    


props 的大小写命名

组件中如果使用“camelCase (驼峰命名法)”声明了 props 属性的名称,

则有两种方式为其绑定属性的值:

Vue2:组件高级(上)_第27张图片

理解:

封装的时候采用驼峰命名法,那么外界在传递属性的时候既可以通过短横线命名,也可以通过驼峰命名法命名

注意:

如果我们在组件命名属性时采用驼峰命名法,


那么,在传递属性时,我们既可以使用驼峰命名法,也可以使用短横线命名法





案例:小黑记事本-组件版

案例效果

Vue2:组件高级(上)_第28张图片

需求说明:

  • 拆分基础组件
  • 渲染待办任务
  • 添加任务
  • 删除任务
  • 底部合计 和 清空功能
  • 持久化存储

拆分基础组件:

咱们可以把小黑记事本原有的结构拆成三部分内容:头部(TodoHeader)、列表(TodoMain)、底部(TodoFooter)

思路:

Vue2:组件高级(上)_第29张图片

具体操作


1-拆分并渲染

/**
 * 渲染功能:
 * 1. 子组件提供数据给父组件
 * 2. 父传数据给子
 * 3. 利用 v-for 渲染数据
 */
  • Code:

    App.vue

    
    
    
    
    
    

    TodoMain.vue

    
    
    
    
    
    

2-添加功能(添加、删除、统计、清空、持久化存储)

/**
 * 添加功能:
 * 1. 收集表单数据 v-model
 * 2. 监听事件(回车+点击 都要进行添加)
 * 3. 子传父,将任务名称传递给父组件
 * 4. 父组件进行添加 unshift(自己的数据自己负责)
 */
/**
 * 删除功能:
 * 1. 监听时间(监听删除的点击)携带id
 * 2. 子传父,将删除的id传递给父组件App.vue
 * 3. 进行删除 filter (自己的数据自己负责)
 */
// 底部合计:父组件传递list到底部组件  —>展示合计
// 清空功能:监听事件 —> **子组件**通知父组件 —>父组件清空
// 持久化存储: watch监听数据变化,持久化到本地
  • Code:

    App.vue

    
    
    
    
    
    

    TodoHeader.vue

    
    
    
    
    
    

    TodoMain.vue

    
    
    
    
    
    

    TodoFooter.vue

    
    
    
    
    
    



v-model 原理

基本原理

**原理:**v-model本质上是一个语法糖。例如应用在输入框上,就是value属性 和 input事件 的合写

结合这段代码理解:

Vue2:组件高级(上)_第30张图片


注意:$event 用于在模板中,获取事件的形参

用:v-model提供数据的双向绑定

  • 数据变,视图跟着变 :value
  • 视图变,数据跟着变 @input

代码实例:两个input是同步进退的

Vue2:组件高级(上)_第31张图片






v-model使用在其他表单元素上的原理

不同的表单元素, v-model在底层的处理机制是不一样的。比如给checkbox使用v-model底层处理的是 checked属性和change事件。

不过咱们只需要掌握应用在文本框上的原理即可



表单类组件封装

目标:实现子组件和父组件数据的双向绑定

案例:实现App.vue中的selectId和子组件选中的数据进行双向绑定

Vue2:组件高级(上)_第32张图片

App.vue






BaseSelect.vue








v-model 简化代码

**目标:**父组件通过v-model 简化代码,实现子组件和父组件数据 双向绑定

简化:

v-model其实就是 :value和@input事件的简写

  • 子组件:props通过value接收数据,事件触发 input
  • 父组件:v-model直接绑定数据

案例

子组件


props: {
  value: String
},
methods: {
  handleChange (e) {
		// 将这里的handleChange改成input
    this.$emit('input', e.target.value)
  }
}

父组件


总结

Vue2:组件高级(上)_第33张图片

可以结合上面的 父子组件之间数据 的双向同步



.sync修饰符(建议对比理解前几个双向绑定)

作用:可以实现 子组件父组件数据双向绑定,简化代码

简单理解:子组件可以修改父组件传过来的props值

特点:prop属性名,可以自定义,非固定为 value (这与v-model不同)

场景: 封装弹框类的基础组件, visible属性 true显示 false隐藏

理解:如果封装的不是value,而是这种弹框类的组件,建议用.sync建立双向绑定

Vue2:组件高级(上)_第34张图片

本质: .sync修饰符 就是 :属性名@update:属性名 合写

语法

父组件

//.sync写法

--------------------------------------
//完整写法

子组件

props: {
  visible: Boolean
},

this.$emit('update:visible', false)

案例

Vue2:组件高级(上)_第35张图片

  • Code:

    App.vue

    
    
    
    
    
    

    BaseDialog.vue

    
    
    
    
    
    


ref和$refs

作用: 利用ref 和 $refs 可以用于 获取 dom 元素 或 组件实例

理解:每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。

**特点:**查找范围 → 当前组件内(更精确稳定)

技术诞生原因

如果使用querySelector进行查找图标.box, 可能在整个页面找到多个.box。

因此,为了更加精准地获取DOM元素,就需要ref与$refs这种技术

Vue2:组件高级(上)_第36张图片

语法

1.给要获取的盒子添加ref属性

我是渲染图表的容器

2.获取时通过 r e f s 获取 t h i s . refs获取 this. refs获取this.refs.chartRef 获取

mounted () {
  console.log(this.$refs.chartRef)
}

注意

之前只用document.querySelect(‘.box’) 获取的是整个页面中的盒子

案例-1:获取DOM元素

Vue2:组件高级(上)_第37张图片

  • Code:

    App.vue

    
    
    
    
    
    

    BaseChart.vue

    
    
    
    
    
    

案例-2:获取组件实例

Vue2:组件高级(上)_第38张图片

  • Code:

    App.vue

    
    
    
    
    
    

    BaseForm.vue

    
    
    
    
    
    


异步更新 & $nextTick

需求:

编辑标题, 编辑框自动聚焦

  1. 点击编辑,显示编辑框
  2. 让编辑框,立刻获取焦点

Vue2:组件高级(上)_第39张图片

**代码实现:




问题:

“显示之后”,立刻获取焦点是不能成功的!

原因:Vue 是异步更新DOM (提升性能)

解决方案

$nextTick:等 DOM更新后,才会触发执行此方法里的函数体

语法: this.$nextTick(函数体)

this.$nextTick(() => {
  this.$refs.inp.focus()
})

注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例

补充:用setTimeout也能实现,但是它的时间没有$nextTick精准

解决代码

  • Code:

    App.vue

    
    
    
    
    
    

    BaseForm.vue

    
    
    
    
    
    

总结

Vue2:组件高级(上)_第40张图片




组件的生命周期

组件运行的过程:

Vue2:组件高级(上)_第41张图片

组件的生命周期指的是:组件从创建 -> 运行(渲染) -> 销毁的整个过程,强调的是一个时间段

监听组件的不同时刻的方式

vue 框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。

例如:

① 当组件在内存中被创建完毕之后,会自动调用 created 函数

② 当组件被成功的渲染到页面上之后,会自动调用 mounted 函数

③ 当组件被销毁完毕之后,会自动调用 unmounted 函数

案例:

  • Code:

    App.vue

    
    
    
    
    
    

    LifeCycle.vue

    
    
    
    
    
    

理解:代码中的created mounted() unmounted函数放到子组件LifeCycle中,当子组件创建完毕之后,会调用created函数,当组件被渲染到页面上后,会调用mounted函数,当组件被销毁完毕之后,会调用unmounted函数。

监听组件的更新的方式

当组件的 data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证 View 视图展示的数据和Model 数据源保持一致。

当组件被重新渲染完毕之后,会自动调用 updated 生命周期函数。

案例:

  • Code:

    
    
    export default {
      name: 'LifeCycle',
      data() {
        return {
          count: 0,
        }
      },
      updated() {
        console.log('组件被重新渲染完毕了');
      },
    }
    

组件中主要的生命周期函数

Vue2:组件高级(上)_第42张图片

注意:在实际开发中,created 是最常用的生命周期函数!

组件中全部的生命周期函数

Untitled

疑问:为什么不在 beforeCreate 中发 ajax 请求初始数据

发起Ajax请求最好都在creat中

完整的生命周期图示

可以参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程:
https://www.vue3js.cn/docs/zh/guide/instance.html#生命周期图示




vue 3.x 中全局配置 axios

  1. 为什么要全局配置 axios

在实际项目开发中,几乎每个组件中都会用到 axios 发起数据请求。此时会遇到如下两个问题:

① 每个组件中都需要导入 axios(代码臃肿)

② 每次发请求都需要填写完整的请求路径(不利于后期的维护)

Untitled

2. 如何全局配置 axios

在 main.js 入口文件中,通过 app.config.globalProperties 全局挂载 axios,示例代码如下:

Vue2:组件高级(上)_第43张图片

  • Code:

    main.js

    const app = createApp(MyApp)
    axios.defaults.baseURL = 'https://www.escook.cn' //为axios配置请求的根路径
    //将axios挂载为app的全局自定义属性之后,
    //每个组件可以通过this直接访问到全局挂载的自定义属性
    app.config.globalProperties.$http = axios
    

    GetInfo:

    export default {
      name: 'GetInfo',
      methods: {
        async getInfo() {
          const { data: res } = await this.$http.get('/api/get', {
            params: {
              name: 'ls',
              age: 33,
            },
          })
    
          console.log(res)
        },
      },
    }
    

    PostInfo:

    export default {
      name: 'PostInfo',
      methods: {
        async postInfo() {
          const { data: res } = await this.$http.post('/api/post', { name: 'zs', age: 20 })
          console.log(res)
        },
      },
    }
    

你可能感兴趣的:(Vue,javascript,vue.js,前端)