【Vue2】Component 组件
Main.JS入口函数,Vue的用法
//导入vue模块,得到Vue构造函数 import Vue from 'vue' // 导入根组件App.vue import App from './App.vue' // 产品配置提示关闭 Vue.config.productionTip = false // 创建一个Vue实例 new Vue({ // el: '#app' 等同于 .$mount('#app') // Vue实例的$mount() 方法,作用和el属性完全一-样! // 把render函数指定的组件,替换 HTML页面的id="app"元素 render: h => h(App), }).$mount('#app')
@符号根路径的配置位置:
配置项的所载文件
jsconfig.json
配置项:
{ "compilerOptions": { "target": "es5", "module": "esnext", "baseUrl": "./", "moduleResolution": "node", "paths": { "@/*": [ "src/*" ] }, "lib": [ "esnext", "dom", "dom.iterable", "scripthost" ] } }
组件的定义:
<template> <!-- 组件模板中,只能存在一个根节点元素 --> <div class="text-box"> <h3>测试组件 {{username}}</h3> <button @click="changeName">修改名称</button> </div> </template> <script> // 将组件导出 export default { // 组件中的data不能是对象,而是一个方法 // [Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions. // data: { // username: 'sadasd' // } data() { return { username: 'Cloud9' } }, // 定义方法 methods: { changeName(event) { this.username = 'Cloud1' } }, filters: {}, // 过滤器 computed: {}, // 计算属性 watch: {}, // 监听器 } </script> <style lang="less" scoped> // 开启CSS样式预处理工具LESS /deep/ h5 { background-color: pink; color: red !important; } </style>
组件的使用:
3. vue组件的三个组成部分
每个.vue组件都由3部分构成,分别是: template ->组件的模板结构 script ->组件的JavaScript行为 style 组件的样式
步骤1 : 使用import语法导入需要的组件
import Left from '@/components/Left.vue'
步骤2:使用components节点注册组件
export default { name: 'App', components: { Left, }, }
步骤3 : 以标签形式使用刚才注册的组件
<template> <div id="app"> <Left ref="left"/><Right/> </div> </template>
使用全局注册的方式注册组件
在Main.JS中做如下配置:
//导入vue模块,得到Vue构造函数 import Vue from 'vue' // 导入根组件App.vue import App from './App.vue' // 产品配置提示关闭? Vue.config.productionTip = false // 全局组件注册, 需要反复使用的组件,可以注册到这里面来 // 1、先导入必要的内容 import Counts from '@/components/Counts.vue' // 2、Vue.Component注册组件, 组件名称和组件资源 Vue.component('Counts', Counts) // 创建一个Vue实例 new Vue({ // el: '#app' 等同于 .$mount('#app') // Vue实例的$mount() 方法,作用和el属性完全一-样! // 把render函数指定的组件,替换 HTML页面的id="app"元素 render: h => h(App), }).$mount('#app')
使用时直接写组件标签即可使用:
<template> <!-- 组件模板中,只能存在一个根节点元素 --> <div class="text-box"> <h3>测试组件 {{username}}</h3> <button @click="changeName">修改名称</button> <!-- 全局注册的组件不需要在子组件引入,可以直接获取 --> <Counts/> </div> </template>
组件的Props属性
通过Props属性来实现父组件给子组件进行传递
每个正在使用的组件需要区别控制不同的数据,提高组件的复用性
Counts组件的Props属性定义:
<template> <div> <h3>这是Count组件</h3> <h5>h5asdasd</h5> <p>默认值 {{counter2}}</p> <button @click="increase">点击加一</button> </div> </template> <script> export default { /** * 声明一个自定义属性 * 这个属性可以让调用组件的组件,通过声明的属性传递不同数据到组件中 * * props是组件的自定义属性,在封装通用组件的时候, * 合理地使用props可以极大的提高组件的复用性! * * //自定义属性的名字,是封装者自定义的(只要名称合法即可) * // props中的数据,可以直接在模板结构中被使用 * props属性是只读的 */ // 简单语法 // props: [ // // 'counter' // ], props: { /** * 更改属性配置,让其具有默认属性 */ counter: { required: true, // 是否强制要求 (组件属性必填项校验) type: Number, // 定义属性数据类型 default: 0 // 定义属性默认值 } }, data() { return { /** * Vue不推荐直接操作props属性,可以将其值赋值给组件进行修改 * data属性允许读写 */ // counter: 0 counter2: this.counter } }, methods: { increase() { this.counter2 += 1 } }, } </script>
因为上面已经把Counts组件放到全局里注册了,
下面使用Counts组件直接写标签即可
App.vue,使用Counts组件:
<template> <div id="app"> <div > <h3>组件Props属性, 传递不同的值</h3> <!-- props属性的值在这里传递进入 通过v-bind指令传递具体值类型,不使用v-bind默认字符串属性 可以设置default属性,让组件可以不设置属性,组件将默认赋值默认参数 --> <Counts style="float: left;" :counter="50" class="aaa"/> <Counts style="float: right;" :counter="100"/> <div style="clear:both;"></div> </div> </div> </template>
组件样式冲突的问题:
Counts.vue的样式标签:
<style scoped> /* 6.组件之间的样式冲突问题 默认情况下,写在.vue组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。 导致组件之间样式冲突的根本原因是: ①单页面应用程序中,所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的 ②每个组件中的样式,都会影响整个index.html页面中的DOM元素 scoped表示只对当前组件样式有效 scoped的原理,是给当前组件内的所有元素添加一个随机的属性 <element data-v-001/> 在scoped中声明的css选择器,全部附加这个data-v-001的属性,用来区别其他的组件 selector[data-v-001] css样式DEEP穿透渲染 */ div { background-color: rgb(210, 255, 192); } h3 { color: rgb(0, 255, 234); } </style>
组件的声明周期:
/** 第一阶段 组件创建时 */ beforeCreate() { }, // 实例前执行 created() { }, // 创建时 beforeMount() { }, // 挂载之前 mounted() { }, // 挂载时 /** 第二阶段 组件运行时 */ beforeUpdate() { }, // 更新之前 updated() { }, // 更新后 /** 第三阶段 组件销毁时 */ beforeDestroy() { }, // 组件元素销毁之前 destroyed() { }, // 组件元素已经销毁时
组件之间数据共享的问题:
一、父组件给子组件传递数据:
定义Son.vue
<template> <div> <h2>这是子组件 Son.vue</h2> <p> 从父组件传递过来的数据: <br> name: {{info.name}} <br> age: {{info.age}} <br> gender: {{info.gender}} <br> </p> </div> </template> <script> export default { props: { infoObj: { type: Object, default: { name: '', age: 0, gender: true, } } } } </script>
定义父组件:
<template> <div> <h2>这是父组件 Father.vue</h2> <p> 父组件的数据: <br> name: {{rowObj.name}} <br> age: {{rowObj.age}} <br> gender: {{rowObj.gender}} <br> </p> <!-- 通过v-bind指令将当前的rowObj属性,传递给子组件prop中的infoObj属性 -->
<!-- 传递方式是引用传递,即infoObj === rowObj 为true --> <son :infoObj="rowObj" /> </div> </template> <script>
// 引入子组件资源 import Son from './Son.vue' export default {
// 注册子组件 components: { Son }, data() { return { rowObj: { id: 1001, name: 'cloud9', age: 22, gender: false }, sonData: { id: null, } } } } </script>
二、反过来,子组件向父组件传递数据
通过一个自定义事件来传递参数
首先是Father.vue的定义:
<template> <div> <h2>这是父组件 Father.vue</h2> <p> 从子组件传递过来的数据 <br> {{ sonData.id }} </p> <!-- 子组件通过自定义事件,传递数据给父组件 @自定义事件名=父组件方法名 --> <son @test="sonDataReceive" /> </div> </template> <script> import Son from './Son.vue' export default { components: { Son }, data() { return { sonData: { id: null, } } }, methods: { // 声明一个方法,且存在一个入参, // 把父组件需要接收的属性用入参赋值 sonDataReceive(val) { this.sonData = val } }, } </script>
然后在Son.vue中定义自定义的事件处理:
<template> <div> <h2>这是子组件 Son.vue</h2> <button @click="sendData"> 向父组件传递数据 </button> </div> </template> <script> export default { data() { return { id: 1376 } }, methods: { /** * 子组件通过事件触发方法将数据传递给父组件 */ sendData() { this.$emit('test', { id: this.id }) this.id ++ } }, } </script>
三、兄弟组件之间的数据交互
在Vue2版本中,兄弟组件的数据共享办法是使用一个EventBus, 事件总线
但是实际用起来,就是两个组件或者多个组件共同控制一个JS实例
EventBus的使用步骤
①创建eventBus.js模块,并向外共享-个Vue的实例对象 ②在数据发送方,调用bus. $emit('事件名称',要发送的数据)方法触发自定义事件 ③在数据接收方,调用bus.$on('事件名称',事件处理函数)方法注册一-个自定义事件
1、在utils目录下创建eventBus.js
import Vue from 'vue' // 向外部共享一个独立的Vue实例 export default new Vue()
2、创建Left.vue 兄弟组件
<template> <div> <h3>LEFT - COMPONENT</h3> <button @click="send"> 向兄弟组件发送数据</button> </div> </template> <script> import bus from '@/utils/eventBus.js' export default { data() { return { message: '这是来自Left组件的数据', } }, methods: { send(){ bus.$emit('share', this.message) } } } </script>
3、创建Right.vue 兄弟组件
<template> <div> <h3>RIGHT - COMPONENT</h3> <p>接收兄弟组件的数据 {{message}}</p> </div> </template> <script> import bus from '@/utils/eventBus.js' export default { data() { return { message: null } }, created() { bus.$on('share', val => { this.message = val }) }, } </script>
组件的Ref引用操作
一.什么是ref引用
ref用来辅助开发者在不依赖于jQuery的情况下,获取DOM元素或组件的引用。
每个vue的组件实例上,都包含一个$refs对象,里面存储着对应的DOM元素或组件的引用。
默认情况下, 组件的$refs指向一个空对象。
二、Ref使用的简单案例:
ref类似ID属性,通过vue的$refs属性引用ref标记,可以获取这个dom对象
从而对这个元素操作
<template> <div> <h3>COMPONENT</h3> <button @click="changeByRef"> 操作下面这个dom元素</button> <div ref="myDiv"> 通过$ref改变这个元素 </div> </div> </template> <script> export default { methods: { changeByRef() { // 通过 $refs操作dom元素 注意,是从本组件自身获取 this.$refs.myDiv.style.backgroundColor = 'rgb(0,122,204)' } } } </script>
三、通过Ref引用组件对象:
在App中引用Left.vue组件,标记ref属性,
然后在事件方法中通过ref引用的dom对象调用组件的方法
<template> <div id="app"> <button @click="actionLeft">操作Left组件</button> <Left ref="left"/> </div> </template> <script> import Left from '@/components/Left.vue'
export default { name: 'App', components: { Left }, methods: { actionLeft() { this.$refs.left.changeByRef2() } } } </script> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
Left.vue组件代码:
<template> <div> <h3>LEFT - COMPONENT</h3> <p> 外部组件$refs引用操作 {{ no }}</p> </div> </template> <script> import bus from '@/utils/eventBus.js' export default { data() { return { no: 100 } }, methods: { // 提供给外部组件操作的方法,演示$refs changeByRef2() { this.no ++ } } } </script>
四、this.$nextTick方法
在App.vue中编写切换控制案例:
<template> <div id="app"> <div> <!-- 切换控制案例 --> <input type="text" v-if="inputVisible" @blur="showButton" ref="testInput"> <button v-else @click="showInput">切换输入框展示</button> </div> </div> </template> <script> export default { name: 'App', components: { }, data() { return { inputVisible: false, } }, methods: { showInput() { /** * 展示输入框,关闭按钮 * */ this.inputVisible = true /** * v-if 接收为true时并不会立即创建dom元素渲染 * this.$refs.testInput.focus() * 这样调用会出问题,因为还没创建dom元素,也就拿不到这个属性和方法 * "TypeError: Cannot read properties of undefined (reading 'focus')" * * 使用 this.nextTick(callBackFunction) * 延迟到dom渲染完成时再执行 * * 组件的$nextTick(cb) 方法,会把cb回调推迟到下一个DOM更新周期之后执行。 * 通俗的理解是:等组件的DOM更新完成之后,再执行cb回调函数。 * 从而能保证cb回调函数可以操作到最新的DOM元素。 * * 所以这种方法只能适配具体的事件去操作,不能直接放在updated勾子函数中 */ this.$nextTick(() => { this.$refs.testInput.focus() }) // 但是还需要让光标聚焦到输入框中 }, showButton() { this.inputVisible = false } }, updated() { /** * 写在updated执行时发生错误 * 因为回显时元素已经删除了,再一次调用无法找到dom元素,导致报错 * [Vue warn]: Error in nextTick: "TypeError: Cannot read properties of undefined (reading 'focus')" */ // this.$nextTick(() => { // this.$refs.testInput.focus() // }) }, } </script> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
数组操作的简便方法:
<script> // 数组操作 let arr = ['张三', '李四', '王五', '赵六', '孙七'] // 遍历 arr.forEach( (x, index, arr) => { console.log(`元素:${x}, 下标 ${index}, 数组:${arr}`) }) // 查找到第一个返回true结束遍历 并且返回true let result = arr.some( (x, index, arr) => { if (x === '王五') { console.log(`下标位置:${index}, 数组:${arr}`) // 结束遍历 return true } }) // 判断每一个元素是否符合自定义规则,只要一个不符合就返回false result = arr.every( (x, index, arr) => { return x.length === 2 }) console.log(result) arr = [ { id: 1001, name: '西瓜', status: true, price: 10, count: 323 }, { id: 1002, name: '榴莲', status: true, price: 30, count: 100 }, { id: 1003, name: '草莓', status: true, price: 440, count: 66 }, { id: 1004, name: '车厘子', status: false, price: 20, count: 45 }, { id: 1005, name: '蓝莓', status: false, price: 80, count: 32 }, { id: 1006, name: '香蕉', status: true, price: 34, count: 34 }, ] // 根据自定义条件 过滤元素,符合条件的元素装载到一个新的数组中 let filteredArr = arr.filter( (x, index, arr) => { return x.count < 100 }) // 计数总价 let totalPrice = 0; filteredArr.forEach(x => totalPrice += x.price * x.count ) console.log(totalPrice) /** * reduce简化处理 * 数组.reduce( (累记变量,当前元素,当前元素下标,数组) => {}, 初始值) * * 对上面的总价计数,可以直接写为这样 */ totalPrice = arr.filter( x => x.count < 100).reduce( (prev, current) => { // return 的结果返回给累计变量赋值,遍历完成后,将累积变量返回出去 return prev + (current.price * current.count) }, 0) console.log(totalPrice) </script>
动态组件
<template> <div id="app"> <h3>APP根组件</h3> <div> <!-- Component动态组件展示 默认离开时销毁组件,展示新的组件是重新创建的 --> <button @click="componentName = 'Left'" >展示左侧组件</button> <button @click="componentName = 'Right'" >展示右侧组件</button> <component keep :is="componentName"></component> </div> </div> </template> <script> import Left from './components/Left.vue' import Right from './components/Right.vue'
export default { name: 'App', components: { Left, Right }, data() { return { componentName: 'Left' } } } </script> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
动态组件默认是以v-if方式展示组件,组件展示就创建,隐藏组件就销毁
如果需要保持组件不销毁,只是隐藏起来,需要使用Keep-Alive组件包裹
<keep-alive> <component keep :is="componentName"></component> </keep-alive>
如果需要保持一些组件就是需要即开即建的状态,可以使用keep-alive组件的exclude属性
也可以使用include属性,一般只使用exclude进行排除操作
<!-- Component动态组件展示 默认离开时销毁组件,展示新的组件是重新创建的 4. keep-aliv e对应的生命周期函数 当组件被缓存时,会自动触发组件的 deactivated生命周期函数。 当组件被激活时,会自动触发组件的 activated生命周期函数。 如需要保持组件中数据不销毁,可以是用keep-alive标签 指定某一个组件不需要缓存 使用exclude排除出去 或者可以用 include指定具体需要包含的组件 <keep-alive include="Left" exclude="Right"> 多个组件使用逗号分割 <keep-alive include="组件1,组件2,..." exclude="组件1,组件2,..."> 注意 keep-alive 会以组件自己的name属性为准,名字不一致将导致排除不会生效 --> <button @click="componentName = 'Left'" >展示左侧组件</button> <button @click="componentName = 'Right'" >展示右侧组件</button> <keep-alive exclude="MyLeft"> <component keep :is="componentName"></component> </keep-alive>
插槽功能:
演示案例,SlotDemo.vue
<template> <div> <!-- 1.什么是插槽 插槽(Slot) 是vue为组件的封装者提供的能力。 允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽。 --> <h3>插槽组件</h3> <!-- 组件调用时自定义的部分 name属性 default默认使用 template abc属性是提供外部组件调用时指明的 --> <div> <slot name="default" msg="这是插槽的数据" :abc="info"/> </div> </div> </template> <script> export default { data() { return { info: { name: 'ssssada', gender: true, age: 30 } } } } </script>
引入资源:
<template> <div id="app"> <h3>APP根组件</h3> <slot-demo> <!-- 提供了插槽标签,可以写自定义内容 1.如果要把内容填充到指定名称的插槽中,需要使用v-slot: 这个指令 2. v-slot: 后面要跟上插槽的名字 3. v-slot: 指令不能直接用在元素身上,必须用在template 标签上一 4. template 这个标签,它是一 个虚拟的标签, 只起到包裹性质的作用,但是,不会被渲染为任何实质性的html元素 <template v-slot:default> #default 缩写语法 2.2后备内容 封装组件时,可以为预留的<slot>插槽提供后备内容( 默认内容)。 如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。示例代码如下: 可以定义多个插槽,使用 #名字 进行区分 作用插槽 --> <template v-slot:default="obj"> <div> <p>{{obj.msg}}</p> <p>这是Slot标签的使用</p> </div> </template> </slot-demo> </div> </div> </template> <script> import SlotDemo from './components/SlotDemo.vue' export default { name: 'App', components: { SlotDemo }, data() { return { componentName: 'Left' } } } </script> <style lang="less"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
对插槽的数据可以使用解构语法:
<!-- 对插槽可以进行结构处理 --> <slot-demo> <template v-slot:default="{ msg, abc }"> <div> <p>{{ msg }}</p> <p>这是Slot标签的使用</p> <p>这是info的数据 {{ abc.age }}</p> </div> </template> </slot-demo>
组件支持声明多个插槽:
<template> <div> <slot name="aaa" /> <slot name="bbb" /> </div> </template> <script> export default { } </script>
调用时:
<slot-demo> <template #aaa>content</template> <template #bbb>content</template> </slot-demo>
组件自定义指令:
CustomCommand.vue 组件案例
<template> <div> <!-- 自定义指令使用 --> <h3 v-color="color">自定义指令组件</h3> <h3 v-color2="color">自定义指令组件</h3> <button @click="color = 'green'"> 点击更换color</button> </div> </template> <script> export default { data() { return { color: 'red' } }, // 定义私有的自定义指令 directives: { // 定义一个名为color的指令名称,指向了一个配置对象 color: { /** * 在元素中标记了这个color指令,会立即触发bind钩子函数 * 但是bind函数只会在指令第一次绑定到元素时执行 */ bind(el, bindObj) { console.log(`color指令触发 ${JSON.stringify(bindObj)}`) // 入参的对象就是 标记的标签元素, 可以获取入参的值 el.style.color = bindObj.value }, /** * update函数支持 dom更新时执行这个函数 * 上面的切换案例 * */ update(el, bindObj) { console.log(`color指令触发 ${JSON.stringify(bindObj)}`) // 入参的对象就是 标记的标签元素, 可以获取入参的值 el.style.color = bindObj.value }, }, /** * 对上面的方法进行合并简写 * 即初始化和dom更新都会调用执行 */ color2(el, bind) { el.style.color = bind.value } } } </script>
如果需要把自定义指令配置到全局中,让所有组件都可以使用
可以在Main.js中配置:
import Vue from 'vue' import App from './App.vue' /** * 配置提示信息 */ // Vue.config.productionTip = false // 全局指令配置 合并写法 // Vue.directive('test', (element, binding) => { // element.style.color = binding.value // }) // 拆开写法 // Vue.directive('test', { // bind(element, binding) { // element.style.color = binding.value // }, // update(element, binding) { // element.style.color = binding.value // } // }) new Vue({ render: h => h(App), }).$mount('#app')