【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')

 

posted @ 2022-05-07 16:13  emdzz  阅读(3136)  评论(0编辑  收藏  举报