vue组件

448 阅读7分钟

vue组件

1、创建vue组件

建组件的方式有两种,一种是局部组件,一种是全局组件

1.1局部组件

介绍

通过对象的方式创建了一个组件,由对象的template属性指定组件的结构,然后组件必须在根实例中的components属性中注册才能实现引用,这种创建方式称为局部组件

代码示例

<div id="app">
	<!-- 3.引用组件 -->
	<component-a></component-a>
</div>

<script>
// 1.创建组件
var ComponentA = { template: `<div>hello vue</div>` }

new Vue({
  el: '#app',
  // 2.注册局部组件
  components: {
    'component-a': ComponentA,
  }
})
</script>

1.2 全局组件

介绍

全局组件使用Vue.component的方法创建,第一个参数为组件名,第二个参数是对象由template属性指定组件结构,并且不需要通过components属性注册就能直接调用

代码示例

<div id="app">
	<!-- 2.引用组件 -->
	<component-a></component-a>
</div>

<script>
// 1.创建全局组件
Vue.component("component-a", {
    template: `<div>hello vue</div>`
})

new Vue({
  el: '#app'
})
</script>

注意

对比后可以明显发现两者的创建方式不一样,

可以理解为需要在components中注册才可以在页面调用的为局部组件,使用Vue.component的方法创建为全局组件

1.3 组件实例的属性

介绍

不管是局部组件还是全局组件中,我们留意到了它们共同存在一个对象,对象下都是通过template来指定结构,但是这个对象可远不止只有template这个属性,它包含了根实例可用的所有属性(包data、methods、computed、watch等), 也就是说组件也是一个Vue实例对象。

比如在组件定义data属性

代码示例

<div id="app">
	<!-- 2.引用组件 -->
	<component-a></component-a>
</div>

<script>
// 1.创建全局组件
Vue.component("component-a", {
	data: function(){
        return {
            message: "hello vue"
        }
	},
    template: `<div>{{message}}</div>`
})

new Vue({
  el: '#app'
})
</script>

注意

组件中的data数据声明和根实例中不一样, 组件的data声明必须是一个函数,在函数中返回一个对象,该对象才是组件的数据,data数据可以在template中渲染。除了data的定义和根实例不一样外,其他属性都是一样的。

2、组件传值

2.1 组件的props

介绍

组件传值的关键属性props,实现步骤:先在组件中通过props声明可接收的属性,然后在引用组件时传递对应的属性和值

代码示例

<div id="app">
	<!-- 3.引用组件 -->
	<goods-item title="原装正品 iphone x" price="10000"></goods-item>
	<!-- 4.再次引用组件-->
	<goods-item title="假一赔十 iphone 8" price="8000"></goods-item>
</div>

<script>
// 1.创建商品组件
Vue.component("goods-item", {
	// 2.定义props
	props: ["title", "price"],
    template: `<div>
    	<h4>{{title}}</h4>
    	<p>{{price}}</p>
    </div>`
})

new Vue({
  el: '#app'
})
</script>

注意

props的值是一个数组,里面声明了组件可接收的属性,如上例title,price,注意组件内部访问props的值和访问data中的值是一样的

2.2 动态的props值

介绍

上例中我们发现props的属性都是属于一个商品的信息,但是一个商品的信息往往是使用对象来表示的,那么组件的props也是可以接收对象类型值的,并且有两种传递方式
  • 直接传递对象
  • 传递data数据

代码示例

1.直接传递对象

<div id="app">
	<!-- 3.引用组件 -->
	<goods-item :goods="{title: '原装正品 iphone x', price: '10000'}"></goods-item>
	<!-- 4.再次引用组件-->
	<goods-item :goods="{title: '假一赔十 iphone 8', price: '8000'}"></goods-item>
</div>

<script>
// 1.创建商品组件
Vue.component("goods-item", {
	// 2.定义props
	props: ["goods"],
    template: `<div>
    	<h4>{{goods.title}}</h4>
    	<p>{{goods.price}}</p>
    </div>`
})

new Vue({
  el: '#app'
})
</script>

注意

只需要声明一个props属性goods,goods是一个对象,在传值时需要使用v-bind:或缩写:来传递动态值,如果不使用v-bind:,值会解释为普通的字符串

2.3 组件接收props的多种方法

介绍

我们都是使用数组的方式定义组件的props,但其实定义props的方法有很多种,数组只是常用的一种

代码示例

Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 匹配任何类型)
    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
      }
    }
  }
})

3、组件传递事件

3.1 传递普通事件

介绍

往上例的商品组件中新增一个购买商品的按钮,然后点击该按钮时触发外部传递过来的购买事件,之所以把购买事件封装到外部,是因为在大型的应用中往往有多个地方可以进行购买操作

代码示例

<div id="app">
	<!-- 2.传递事件, buyGoods是事件名 -->
    <goods-item :goods="goods" @buy-goods="buyGoods"></goods-item>
</div>

<script>
    Vue.component("goods-item", {
        props: ["goods"],
        methods: {
            // 4.购买事件
            handleBuy: function(){
                // 5.主动触发传递的事件
                this.$emit("buy-goods");
            }
        },
        template: `<div>
            <h4>{{goods.title}}</h4>
            <p>{{goods.price}}</p>
          	<!-- 3.新增按钮,添加点击事件 -->
            <button @click="handleBuy">立即购买</button>
        </div>`
    })

	new Vue({
        el: '#app',
        data: {
            goods: {
                title: '原装正品 iphone x',
                price: 10000
            }
        },
        methods: {
        	// 1.封装购买商品事件
            buyGoods: function(){
                alert("购买了商品")
            }
        }
    })
</script>

注意

给组件传递事件的流程:

  1. 先在外部定义需要传入的事件,如根实例的buyGoods
  2. 将该事件传入组件,@buy-goods="buyGoods"注意buy-goods是自定义事件名(不要使用大写字母)
  3. 组件使用实例的emit方法主动触发事件,该方法接收要触发事件的事件名,如this.emit("buy-goods");

3.2 子组件传值到父组件

介绍

组件之间是采用单向数据流,也就是父组件可以使用props往子组件传递数据,如果想在子组件中往父组件传递数据,可以通过事件方式实现。

代码示例

<div id="app">
    <goods-item :goods="goods" @buy-goods="buyGoods"></goods-item>
</div>

<script>
    Vue.component("goods-item", {
        props: ["goods"],
        methods: {
            handleBuy: function(){
                // 1.第二个参数可以传数据给父组件
                this.$emit("buy-goods", { title: this.goods.title });
            }
        },
        template: `<div>
            <h4>{{goods.title}}</h4>
            <p>{{goods.price}}</p>
            <button @click="handleBuy">立即购买</button>
        </div>`
    })

	new Vue({
        el: '#app',
        data: {
            goods: {
                title: '原装正品 iphone x',
                price: 10000
            }
        },
        methods: {
        	// 2.通过参数接收子组件的数据
            buyGoods: function(data){
                alert("购买了商品:" + data.title)
            }
        }
    })
</script>

注意

在子组件中使用this.@emit()触发事件时,可以通过参数传入数据,父组件事件中可接收对应的数据

3.3 兄弟组件传值

子传父》父传兄

3.4 自定义事件通讯

介绍

兄弟组件,跨层级组件都可以通过自定义事件来进行通讯

event.js

import Vue from 'vue'

export default new Vue()

说明

1.Vue本身具有自定义事件的能力

2.event是new Vue()的实例,已经具备onemit自定义事件的能力,所以不需要引入eventBus

index父组件

<template>
    <div>
        <Input @add="addHandler"/>
        <List :list="list" @delete="deleteHandler"/>
    </div>
</template>

<script>
import Input from './Input' //子组件
import List from './List'  //子组件

export default {
    components: {
        Input,
        List
    },
    data() {
        return {
            list: [
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                }
            ]
        }
    },
    methods: {
        addHandler(title) {
            this.list.push({
                id: `id-${Date.now()}`,
                title
            })
        },
        deleteHandler(id) {
            this.list = this.list.filter(item => item.id !== id)
        }
    },
    created() {
        // eslint-disable-next-line
        console.log('index created')
    },
    mounted() {
        // eslint-disable-next-line
        console.log('index mounted')
    },
    beforeUpdate() {
        // eslint-disable-next-line
        console.log('index before update')
    },
    updated() {
        // eslint-disable-next-line
        console.log('index updated')
    },
}
</script>

Input 子组件

说明

通过event.$emit调用自定义事件获取其他组件传递的数据

<template>
    <div>
        <input type="text" v-model="title"/>
        <button @click="addTitle">add</button>
    </div>
</template>

<script>
import event from './event'

export default {
    data() {
        return {
            title: ''
        }
    },
    methods: {
        addTitle() {
            // 调用父组件的事件
            this.$emit('add', this.title)

            // 调用自定义事件
            event.$emit('onAddTitle', this.title)

            this.title = ''
        }
    }
}
</script>

htmlList 子组件

说明

通过event.$on绑定自定义事件并传递数据过去

<template>
    <div>
        <ul>
            <li v-for="item in list" :key="item.id">
                {{item.title}}

                <button @click="deleteItem(item.id)">删除</button>
            </li>
        </ul>
    </div>
</template>

<script>
import event from './event'

export default {
    // props: ['list']
    props: {
        // prop 类型和默认值
        list: {
            type: Array,
            default() {
                return []
            }
        }
    },
    data() {
        return {

        }
    },
    methods: {
        deleteItem(id) {
            this.$emit('delete', id)
        },
        addTitleHandler(title) {
            // eslint-disable-next-line
            console.log('on add title', title)
        }
    },
    created() {
        // eslint-disable-next-line
        console.log('list created')
    },
    mounted() {
        // eslint-disable-next-line
        console.log('list mounted')

        // 绑定自定义事件
        event.$on('onAddTitle', this.addTitleHandler)
    },
    beforeUpdate() {
        // eslint-disable-next-line
        console.log('list before update')
    },
    updated() {
        // eslint-disable-next-line
        console.log('list updated')
    },
    beforeDestroy() {
        // 及时销毁,否则可能造成内存泄露
        event.$off('onAddTitle', this.addTitleHandler)
    }
}
</script>

注意

绑定自定义事件的组件,要及时销毁该事件,否则可能造成内存泄露

4、生命周期

说明

  1. created和mounted的区别 created是把vue的实例初始化,只存在js内存的一个变量而已,并没有渲染; mounted 是真正在页面上绘制完成,页面已经渲染完成,这个时候才可以操作DOM元素
  2. beforeDestroy 中可能要做什么 解除绑定,销毁子组件以及事件监听

4.1 单个组件

  • 挂载阶段
    • beforeCreated
    • created
    • beforeMounted
    • mounted
  • 更新阶段
    • beforeUpdate
    • updated
  • 销毁阶段
    • beforeDestroy
    • destroyed

4.3 父子关系

  • 创建,渲染 创建初始化实例是从外到内,渲染是从内到外
    • 父 beforeCreated
    • 父 created
    • 父 beforeMounted
    • 子 beforeCreated
    • 子 created
    • 子 beforeMounted
    • 子 mounted
    • 父 mounted
  • 更新 更上面类似
    • 父 beforeUpdate
    • 子 beforeUpdate
    • 子 updated
    • 父 updated
  • 销毁
    • 父 beforeDestroy
    • 子 beforeDestroy
    • 子 destroyed
    • 父 destroyed