05vue之组件

1. 组件化编程

根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护

1.1 组件

在 Vue 中,组件就是用来封装视图的,说白了就是封装 HTML;组件思想就是把一个很大的复杂的 Web 页面视图给拆分成一块一块的组件视图,然后利用某种特定的方式把它们组织到一起完成完整的 Web 应用构建

模块化是一种思想,一种构建方式,把一种很复杂的事务拆分成一个一个小模块,然后通过某种特定的方式把这些 小模块组织到一起相互协作完成这个复杂的功能

组件可以扩展HTML元素,封装可重用的代码。

在较高层面上,组件是自定义元素,Vue.js的编译器为它添加特殊功能。

在有些情况下,组件也可以变现为用 is 特性进行扩展原有的HTML元素

所有的Vue组件同时也都是Vue实例,所有可接受相同的选项的对象(除了一些根级特有的选项)并提供相同的生命周期钩子

组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护。

1.2 组件的构成

组件主要包含三个部分:

  • template模板:html结构,只可以定义一个父元素
  • JS处理逻辑:主要作用是处理数据
  • style处理样式:如果不加 scoped,默认就是全局样式
<!-- template模板 -->
<template>
	<div class="title">
		<h1>{{ msg }}</h1>
	</div>
</template>

<!-- 逻辑处理 -->
<script>
export default {
    props: []/{},  // props参数:可以是数组或者对象
    // 自定义数据 
	data() {
		return {
			msg: 'hello vue.js'
		};
	},
    computed: {}, //计算属性
    methods: {},  //自定义方法              
    watch: {}, //监控
    filters: {}, // 过滤器
    directives: {}, // 自定义指令
    components: {}, //自定义组件
};
</script>

<!-- 样式处理 -->
<style lang="scss" scoped></style>

.vue 组件中的 data 必须是一个函数,一般是返回一个数据对象,而不能直接指向一个数据对象

1.3 组件的定义和使用

1.3.1 全局组件

定义组件

<template>
  <div class="user">
    <h1>这是一个全局组件</h1>
  </div>
</template>

<script>
export default {}
</script>

<style lang="scss" scoped>
</style>

将组件注册为全局组件

一般是在main.js文件中 注册全局组件

// 1. 引入组件
import user from './components/user.vue'

// 2.  将组件注册为全局组件,名称为 my-user
Vue.component('my-user', user)

1.3.2 局部组件

定义组件

<template>
  <div class="helloWorld">
    <h1>这是一个局部组件</h1>
  </div>
</template>

<script>
export default {}
</script>

<style lang="scss" scoped>
</style>

使用组件

<template>
  <div class="test">
    <!-- 3. 使用组件 -->
    <user></user>
  </div>
</template>

<script>
// 1. 引入组件
import user from './user.vue'
export default {
  // 2.  注册声明组件
  components: {
    user,
  },
}
</script>

<style lang="scss" scoped>
</style>

1.3.3 使用组件的步骤

步骤1:使用 import 语法导入需要的组件

步骤2:使用 components 节点注册组件

步骤3: 以标签形式使用刚才注册的组件

1.3.4 组件间的样式

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

所以一般情况下,我们会给组件设置 scoped 属性,从而防止组件之间的样式冲突问题

<style scoped>
    XXX
</style>

style节点的 scoped 属性,用来自动为每个组件分配唯一的 “自定义属性”,并自动为当前组件的DOM标签和style样式应用这个 “自定义属性”,从而防止样式的 冲突问题

/deep/ 深度选择器

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

<style scoped>
    /deep/ .title{
        color:"red"
    }
</style>

1.4 一些概念

模块化:JS模块化、CSS模块化、资源模块化

组件化:复用现有的UI结构、样式、行为

规范化:目录结构的划分、编码规范化、接口规范化、文档规范化、Git分支管理

自动化:自动化构建、自动部署、自动化测试、

2 组件插槽

插槽可以实现 父组件与子组件之间的通信

如果子组件中的插槽的name和父组件不一致,那么通过父组件传递过去的 标签数据 就不会显示

2.1 插槽的定义

<slot name="slot-name"></slot>

2.2 插槽的使用

<div slot='slot-name'></div>

案例:

子组件

<template>
  <div class="children">
    <slot name="userNameSlot"></slot>
    <div>子组件里面内容</div>
    <slot name="contSlot"></slot>
  </div>
</template>

父组件

<template>
  <div id="app">
    <children>
      <h1 slot="userNameSlot">插槽 userNameSlot 显示的内容</h1>
      <h1 slot="contSlot">插槽 contSlot 显示的内容</h1>
    </children>
  </div>
</template>

3 组件的 props

props 是组件的自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性!

prop 是子组件用来接受父组件传递过来的数据的一个自定义属性

父组件的数据需要通过 props 把数据传给子组件,子组件需要显式地用 props 选项声明 "prop"

prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来

组件可以为 props 指定验证要求:

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
      
    propA: Number,
      
    // 多个可能的类型
    propB: [String, Number],
      
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
      
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
      
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
      
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

type 可以是下面原生构造器:

String 
Number 
Boolean 
Array 
Object 
Date 
Function 
Symbol

组件中封装的自定义属性是只读的,不能直接修改 props 的值

要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的

4 组件间的通信

组件间通信主要有5种方式:props、vue自定义事件、pubsub第三方库、slot插槽、vuex多组件共享状态

4.1 父组件向子组件通信

4.1.1 props

Prop是父组件用来传递数据的属性

父组件的数据需要通过props将数据传递给子组件,而子组件需要显式的用props声明prop

Prop是单向绑定的,当父组件的属性发生变化时,将传导给子组件,但是反过来不会

注意:不适合隔层组件和兄弟组件间的通信

1、 传递普通变量

image

2、 传递函数、方法

用法与传递普通变量一样
image

4.2 子组件向父组件通信

4.2.1 自定义事件--推荐

一般是 子组件 触发自定义事件,将参数传递给父组件

this.$emit('事件名',参数)

父组件绑定监听事件,接收子组件传递过来的参数

@事件名=回调函数

image

子组件:todoHeader触发事件

<script>
export default {
  methods: {
    add() {
      const { title } = this
      if (title.length > 0) {
        var todoItem = { title, complete: false }
        
        // 1. 使用 $emit('事件名',参数) 触发事件
        this.$emit('addtodoItemSub', todoItem)
          
        this.title = ''
      } else {
        alert('输入不可以为空!')
      }
    },
  },
}
</script>

父组件:绑定监听事件

<template>
  <div class="comtodolist">
    <!-- 1. 给todoHeader绑定addtodoItemSub事件监听;   addtodoItem1是一个回调函数-->
    <todoHeader @addtodoItemSub="addtodoItem1"></todoHeader>
  </div>
</template>

<script>
import todoHeader from '../components/component-todolist/todoHeader.vue'
export default {
  methods: {
    // 2.  添加 todoItem 父组件定义的回调函数
    addtodoItem1(todoItem) {
      this.todos.unshift(todoItem)
    },
  },
}
</script>

4.2.2 ref 结合 $on

使用$on(事件名,回调函数) 绑定监听
image

子组件todoHeader:触发事件

<script>
export default {
  methods: {
    add() {
      const { title } = this
      if (title.length > 0) {
        var todoItem = { title, complete: false }
        // 1. 使用 $emit('事件名',参数) 触发事件
        this.$emit('addtodoItemSub', todoItem)
        this.title = ''
      } else {
        alert('输入不可以为空!')
      }
    },
  },
}
</script>

父组件:绑定监听事件 $on

<template>
  <div class="comtodolist">
    <!--  1.  给子组件设置 ref     -->
    <todoHeader ref="todoheaderref"></todoHeader>
  </div>
</template>

<script>
import todoHeader from '../components/component-todolist/todoHeader.vue'
export default {
  name: 'todolist',
  data() {
    return {
      // 从slocalStorage中读取todos
      todos: JSON.parse(window.localStorage.getItem('todos_key') || '[]'),
    }
  },
  components: {
    todoHeader,
  },
  mounted() {
    // 2.  绑定监听事件  this.$on('addtodoItemSub', this.addtodoItem);
    this.$refs.todoheaderref.$on('addtodoItemSub', this.addtodoItem)
  },
  methods: {
    // 添加
    addtodoItem(todoItem) {
      this.todos.unshift(todoItem)
    },
  },
}
</script>

4.3 pubsub:消息订阅与发布

可以实现任意组件之间的通信:父子组件传值或同胞兄弟之间的传值

# 安装
npm i pubsub-js

# 判断是否安装成功 
npm info pubsub-js

4.3.1 订阅消息(绑定事件监听): 定义函数或方法

// 语法
PubSub.subscribe('方法名',(msg,data)=>{} )
或者是下面的这种
PubSub.subscribe('事件名',function(msg,data){})  data就是参数


// 案例展示:订阅消息 searchInfo
// 注意:此处必须使用 箭头函数,才可以获取 this.searchInfo方法
PubSub.subscribe('searchInfo', (msg, searchName) => {
  this.searchInfo(searchName);
});

methods:{
    searchInfo(searchName){
    	console.log(searchName);
    }
}

4.3.2 发布消息(触发事件):调用函数或方法

// 语法  
PubSub.publish('方法名',data)  

// 案例展示:发布消息 searchInfo
import PubSub from 'pubsub-js';

//发布消息:触发事件 searchNameText参数名
PubSub.publish('searchInfo', searchNameText);

image
image

4.4 eventBus

在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus

使用步骤:

1、 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象

2、 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件

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

image

left.vue 左侧组件

<template>
  <div class="left-container">
    <h1>Left 左侧 组件</h1>
    <hr />
    <h2>{{ msg }}</h2>
    <button @click="sendMsg">点击 将数据发送到 Right 右侧组件</button>
  </div>
</template>

<script>
// 1.  引入 eventBus    
import bus from '@/components/eventBus'
export default {
  data() {
    return {
      msg: 'Left 组件里面的数据:天气不错',
    }
  },
  methods: {
    sendMsg() {
      // 2.  发送数据:触发绑定事件  
      bus.$emit('sendMsgToRight', this.msg)
    },
  },
}
</script>

<style lang="scss" scoped>
.left-container {
  flex: 1;
  height: 300px;
  background-color: yellow;
}
</style>

right.vue 右侧组件

<template>
  <div class="right-container">
    <h1>Right 右侧 组件</h1>
    <hr />
    <h2>{{ msgFromLeft }}</h2>
  </div>
</template>

<script>
// 1.  引入 eventBus      
import bus from '@/components/eventBus'
export default {
  data() {
    return {
      msgFromLeft: '',
    }
  },
  created() {
    // 2.  接收数据:监听绑定的事件,一般是在 created() 生命周期中 监听    
    bus.$on('sendMsgToRight', (val) => {
      this.msgFromLeft = val
    })
  }
}
</script>

<style lang="scss" scoped>
.right-container {
  flex: 1;
  margin-left: 30px;
  height: 300px;
  background-color: red;
}
</style>

4.5 slot插槽

主要是父组件向子组件传递带数据的标签数据

4.6 vuex

多组件共享状态(数据的管理

组件间的关系也没有限制

功能比pubsub强大, 更适用于vue项目

5 ref

ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用,可以方便快捷的操作DOM元素

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

5.1 引用 DOM 元素

如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作

<h3 ref="myh3">MyRef 组件</h3>
<button @click="getRef">获取 $refs 引用</button>

<script>
export default {
  methods: {
    getRef() {
        // 通过 this.$refs.引用的名称,获取DOM元素的引用
        console.log(this.$refs.myh3)
        // 操作DOM元素
        this.$refs.myh3.style.color="red"
    }
  }
}
</script>

5.2 引用组件实例

如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:

<!-- 使用 ref 属性,为对应的 “组件” 添加引用名称 -->
<my-counter ref="counterRef"></my-counter>
<button @click="getRef">获取 $refs 引用</button>

<script>
export default {
  methods: {
    getRef() {
        // 通过 this.$refs.引用的名称,获取DOM元素的引用
        console.log(this.$refs.counterRef)
        // 引用到组件的实例后,就可以直接调用组件上面的 methods 了
        this.$refs.counterRef.add()
    }
  }
}
</script>

5.3 this.$nextTick(cb)方法

组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素

this.$nextTick(()=>{
   // TODO
})

6 动态组件

动态组件指的是动态切换组件的显示与隐藏

vue 提供了一个内置的 <component> 组件,专门用来实现动态组件的渲染,主要是用来实现 组件的占位

is 属性的值,表示要渲染的组件的名字,应该是组件在 components 节点下的注册名称

<template>
  <div class="app-container">
    <!-- 2.  点击按钮,动态切换组件 -->  
    <button @click="comName = 'Left'">展示 Left</button>
    <button @click="comName = 'Right'">展示 Right</button>
  
    <!-- 3.  通过 is 属性,动态指定要渲染的组件 -->  
    <component :is="comName"></component>
  </div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'

export default {
  data() {
    return {
      // 1.  comName 表示要展示的组件的名字
      comName: 'Left'
    }
  },
  components: {
    Left,
    Right
  }
}
</script>

使用 keep-alive 保持状态

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 <keep-alive> 组件保持动态组 件的状态

keep-alive 会把内部的组件进行缓存,而不是销毁组件

<keep-alive>
   <component :is="comName"></component>
</keep-alive>

include 属性

include 属性用来指定:只有名称匹配的组件会被缓存(指定哪些组件需要被缓存)。多个组件名之间使用英文的逗号分隔

或者,通过 exclude 属性指定哪些组件不需要被缓存;但是:不要同时使用 include 和 exclude 这两个属性

<keep-alive include="Left">
   <component :is="comName"></component>
</keep-alive>

需要缓存的组件,一定要设置 name 属性,此处获取的就是组件的name

keep-alive 对应的生命周期函数

当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。

当组件被激活时,会自动触发组件的 activated 生命周期函数

  created() {
    console.log('Left 组件被创建了!')
  },
  destroyed() {
    console.log('Left 组件被销毁了~~~')
  },

  // 当组件第一次被创建的时候,既会执行 created 生命周期,也会执行 activated 生命周期
  // 当时,当组件被激活的时候,只会触发 activated 生命周期,不再触发 created。因为组件没有被重新创建
  activated() {
    console.log('组件被激活了,activated')
  },
  deactivated() {
    console.log('组件被缓存了,deactivated')
  }

7 插槽

插槽(Slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的 部分定义为插槽

可以把插槽认为是组件封装期间,为用户预留的内容的占位符

7.1 默认插槽

vue 官方规定:每一个 slot 插槽,都要有一个 name 名称, 如果省略了 slot 的 name 属性,则有一个默认名称叫做 default

left.vue 组件

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 声明一个插槽区域 -->
    <slot name="default">
      <h6>这是 default 插槽的后备内容(默认内容)</h6>
    </slot>
  </div>
</template>

默认情况下,在使用组件的时候,提供的内容都会被填充到名字为 default 的插槽之中(就是默认插槽)

如果要把内容填充到指定名称的插槽中,需要使用 v-slot 指令,v-slot:插槽名,也可以简写为 #插槽名

注意:v-slot: 指令不能直接用在元素身上,必须用在 template 标签上

template 这个标签,它是一个虚拟的标签,只起到包裹性质的作用,不会被渲染为任何实质性的 html 元素

<template>
  <div class="app-container">
      <Left>
        <!-- #default是默认插槽名 -->  
        <template #default>
          <p>这是在 Left 组件的内容区域,声明的 p 标签</p>
        </template>
      </Left>
  </div>
</template>

7.2 具名插槽

如果在封装组件时需要预留多个插槽节点,则需要为每个 slot 插槽指定具体的 name 名称。这种带有具体 名称的插槽叫做“具名插槽”

Article.vue 组件

<template>
  <div class="article-container">
    <h1>Article 组件</h1>
      
    <!-- 1.  文章的标题 -->
    <div class="header-box">
      <slot name="title"></slot>
    </div>

    <!-- 2.  文章的内容 -->
    <div class="content-box">
      <!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,这种用法,叫做 “作用域插槽” -->  
      <slot name="content" msg="hello vue.js" :user="userinfo"></slot>
    </div>

    <!-- 3.  文章的作者 -->
    <div class="footer-box">
      <slot name="author"></slot>
    </div>
  </div>
</template>

在使用 Article.vue 组件的时候,将指定位置的 插槽 填充上内容

 <Article>
      <!-- 1.  title插槽  -->
      <template #title>
        <h3>这里加载title部分</h3>
      </template>

     <!-- 2.  content插槽  -->
      <template #content="{ msg, user }">
        <div>
          <p> content 插槽区域 </p>
          <!-- 可以获取插槽传递过来的内容 -->  
          <p>{{ msg }}</p>
          <p>{{ user.name }}</p>
        </div>
      </template>

      <!-- 2.  author插槽  -->
      <template #author>
        <div>作者:XXX</div>
      </template>
    </Article>
posted @ 2023-10-10 11:15  songxia777  阅读(15)  评论(0编辑  收藏  举报