Vue学习笔记 3 - 组件化 / slot(插槽)

组件化

  • 组件化是 Vue.js 中的重要思想

    • 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
    • 任何的应用都会被抽象成一颗组件树。
基本步骤
  • 组件化的使用分为三个步骤

    • 调用Vue.extend()方法创建组件构造器
    • 调用Vue.component()方法 注册组件
    • 在 Vue 实例的作用范围内使用组件
       
  • 全局组件
<div id="app">
    // 3.使用组件
    <cpn></cpn>
</div>
<script>
    // 1.创建组件构造器对象
    const cpnC = Vue.extend({
        template: `
        <div>
            <h2>标题 1</h2>
            <p>内容 1111</p>
        </div>`
    })
    // 2.注册组件
    Vue.component('cpn', cpnC)

    const app = new Vue({
        el: '#app',
        data: {},
        methods: {}
    })
</script>

标题 1
内容 1111

  • 局部组件
<div id="app">
    <cpn></cpn> //正常使用组件
</div>
<div id="app2">
    <cpn></cpn> //报错
</div>
<script>
    // 1.创建组件构造器对象
    const cpnC = Vue.extend({
        template: `
        <div>
            <h2>标题 1</h2>
            <p>内容 1111</p>
        </div>`
    })

    const app = new Vue({
        el: '#app',
        data: {},
  		// 2.注册组件
        components: {
            cpn: cpnC
        }
    })

    const app2 = new Vue({
        el: '#app2',
        data: {},
        methods: {}
    })
</script>

标题 1
内容 1111

 

父组件与子组件
<script>
    //子组件
    const cpnC1 = Vue.extend({
        template: `
        <div>
            <h2>标题 1</h2>
            <p>内容 1111</p>
        </div>`
    })
	//父组件
    const cpnC2 = Vue.extend({
        template: `
        <div>
            <cpn1></cpn1>
            <h2>标题 2</h2>
            <p>内容 2222</p>
        </div>`,
        components: {
            cpn1: cpnC1
        }
    })
    //root组件
    const app = new Vue({
        el: '#app',
        data: {},
        methods: {},
        components: {
            cpn: cpnC2
        }
    })
</script>
<div id="app">
    <cpn></cpn>
</div>

标题 1
内容 1111
标题 2
内容 2222

 

组件的语法糖注册方式(推荐写法)

语法糖主要是省去了调用Vue.extend()的步骤,而是可以直接用一个对象来代替

  • 全局组件
<div id="app">
    // 3.使用组件
    <cpn></cpn>
</div>
<script>
    Vue.component('cpn', {
        template: `
        <div>
            <h2>标题 1</h2>
            <p>内容 1111</p>
        </div>`
    })

    const app = new Vue({
        el: '#app',
        data: {},
        methods: {}
    })
</script>

标题 1
内容 1111

  • 局部组件
<div id="app">
    <cpn></cpn> //正常使用组件
</div>
<div id="app2">
    <cpn></cpn> //报错
</div>
<script>
	const app = new Vue({
        el: '#app',
        data: {},
        methods: {},
        components: {
            'cpn': {
                template: `
                <div>
                    <h2>标题 1</h2>
                    <p>内容 1111</p>
                </div>`
            }
        }
    })
</script>

标题 1
内容 1111

 

组件模板抽离的写法

刚才我们通过语法糖简化了 Vue 组件中的注册过程,另外还有一个地方的写法比较繁琐,就是 template 模块写法。

	components: {
        'cpn': {
            template: `
            <div>
                <h2>标题 1</h2>
                <p>内容 1111</p>
            </div>`
        }
    }

如果我们能将 template 模块中的 HTML分离出来,然后挂载到对应的组件上,结构必然会更加清晰。

<div id="app">
    <cpn></cpn>
</div>
<script>
    Vue.component('cpn',{
        template:'#cpn'
    })
    
    const app = new Vue({
        el: '#app',
        data: {},
        methods: {}
    })
</script>
  • 使用<script>标签
<script type="text/x-template" id="cpn">
<h2>我是标题</h2>
</script>
  • 使用<templet>标签
<template id="cpn">
    <h2>我是标题2</h2>
</template>

我是标题

 

组件数据的存放

组件对象也有一个 data属性(也可以有 methods 等属性),只是这个 data 属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据。

关于组件中的 data 为什么是函数,原理可以参考视频p57,老师讲的非常透彻易懂

<template id="cpn">
    <h2>{{msg}}</h2>
</template>
<script>
    Vue.component('cpn', {
        template: '#cpn',
        data() {
            return {
                msg: 'Hello Vue'
            }
        }
    })

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

Hello Vue

父子组件的通信

子组件是不能引用父组件或者 Vue 实例中的数据的,但在开发中往往一些数据需要从上层传递到下层,那么如何进行父子组件间的通信呢?

  • 父传子

通过props 向子组件传递数据

<body>
<div id="app">
    <!--用 v-bind 才会把 movies 当做变量而不是字符串-->
    <cpn :cmovies="movies" :cmsg="msg"></cpn>
</div>
</body>
<template id="cpn">
    <div>
        <ul>
            <li v-for="item in cmovies">{{item}}</li>
        </ul>
    </div>
</template>

<script>
    //子组件
    const cpn = Vue.extend({
        template: '#cpn',
        //父传子:props
        // props: ['cmovies', 'cmsg']
        props: {
            // 1.类型限制
            cmovies: Array,
            cmsg: String,

            // 2.提供一些默认值
            cmsg: {
                type: String,
                default: 'child', //默认值
                required: true //必须传入值
            },
            // 类型是对象或者数组时,默认值必须是一个函数
            cmovies: {
                type: Array,
                default() {
                    return []
                }
            }
        }
    })
    //root组件
    const app = new Vue({
        el: '#app',
        data: {
            msg: 'hello',
            movies: ['dodo', 'bibi', 'pepe']
        },
        methods: {},
        components: {
            cpn: cpn
        }
    })
</script>

使用props时,如果使用驼峰标识需要进行转化

<body>
<div id="app">
    <cpn :c-info="info"></cpn>
</div>

<template id="cpn">
    <div>{{cInfo}}</div>
</template>
</body>

<script>
    const cpn = {
        template: '#cpn',
        props: {
            cInfo: {
                type: Object,
                default() {
                    return {}
                }
            }
        }
    }

    //root组件
    const app = new Vue({
        el: '#app',
        data: {
            info: {
                id: '1',
                name: 'dodo',
                age: '11'
            }
        },
        methods: {},
        components: {
            cpn: cpn
        }
    })
</script>

{ “id”: “1”, “name”: “dodo”, “age”: “11” }

  • 子传父

通过事件向父组件发送消息

  • 自定义事件流程:

    • 在子组件中,通过.$emit()来触发事件
    • 在父组件中,通过v-on来监听子组件事件
       
  • 简单列表案例

<body>
<!--父组件模板-->
<div id="app">
    <!--监听事件(默认自动传递参数)-->
    <cpn @itemclick="cpnClick"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
    <div>
        <button v-for="item in categories" @click="btnClick(item)">
            {{item.name}}
        </button>
    </div>
</template>
</body>

<script>
    //子组件
    const cpn = {
        template: '#cpn',
        data() {
            return {
                categories: [
                    {id: '1', name: '列表 1'},
                    {id: '2', name: '列表 2'},
                    {id: '3', name: '列表 3'},
                    {id: '4', name: '列表 4'}
                ]
            }
        },
        methods: {
            btnClick(item) {
                //发射事件(名称,事件)
                this.$emit('itemclick', item)
            }
        }
    }

    //root组件
    const app = new Vue({
        el: '#app',
        data: {},
        methods: {
            cpnClick(item) {
                console.log(item)
            }
        },
        components: {
            cpn: cpn
        }
    })
</script>
  • 简单计数器案例
<body>
<!--父组件模板-->
<div id="app">
    <!--通过v-on监听事件。发生两个事件时,调用同一个函数changeTotal-->
    <cpn @increment="changeTotal" @decrement="changeTotal"></cpn>
    <h2>点击次数:{{total}}</h2>
</div>
<!--子组件模板-->
<template id="cpn">
    <div>
        <button @click="increment">+1</button>
        <button @click="decrement">-1</button>
    </div>
</template>
</body>

<script>
    //子组件
    const cpn = {
        template: '#cpn',
        data() {
            return {
                count: 0
            }
        },
        methods: {
            increment() {
                this.count++;
                //子组件发出事件
                this.$emit('increment', this.count)
            },
            decrement() {
                this.count--;
                this.$emit('decrement', this.count)
            }
        }
    }

    //root组件
    const app = new Vue({
        el: '#app',
        data: {
            total: 0
        },
        methods: {
            changeTotal(count) {
                this.total = count
            }
        },
        components: {
            cpn: cpn
        }
    })
</script>
  • 结合双向绑定案例(p64思路分析)
<div id="app">
    <cpn :number1="num1" :number2="num2" @number1change="num1change" @number2change="num2change"/>
</div>

<template id="cpn">
    <div>
        <h2>props:{{number1}}</h2>
        <h2>data:{{dnumber1}}</h2>
        <!--直接双向绑定会报错,不要直接绑定 props ,使用 data 或 computed 绑定-->
        <!--<input type="text" v-model="dnumber1">-->
        <input type="text" :value="dnumber1" @input="num1Input">
        <h2>{{number2}}</h2>
        <input type="text" :value="dnumber2" @input="num2Input">
    </div>
</template>

<script>
    var vm = new Vue({
        el: '#app',
        data: {
            // 初始化
            num1: 1,
            num2: 2
        },
        methods: {
            num1change(value) {
                this.num1 = parseInt(value)
            },
            num2change(value) {
                this.num2 = parseInt(value)
            }
        },
        components: {
            cpn: {
                template: '#cpn',
                props: {
                    // 使用对象方便限制属性类型
                    number1: Number,
                    number2: Number
                },
                data() {
                    return {
                        dnumber1: this.number1,
                        dnumber2: this.number2,
                    }
                },
                methods: {
                    num1Input(event) {
                        // 1.将 input 中的 value 赋值到 dnumber 中
                        this.dnumber1 = event.target.value;
                        // 2.为了让父组件可以修改值,发出一个事件
                        this.$emit('number1change', this.dnumber1)
                        // 3.同时修饰 dnumber2 的值
                        this.dnumber2 = this.dnumber1 * 100;
                        this.$emit('number2change', this.dnumber2)
                    },
                    num2Input(event) {
                        this.dnumber2 = event.target.value;
                        this.$emit('number2change', this.dnumber2)
                        this.dnumber1 = this.dnumber2 / 100;
                        this.$emit('number1change', this.dnumber1)
                    }
                }
            }
        }
    })
</script>

 

父子组件的访问方式

有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件。

  • 父组件访问子组件:使用$children$refs(常用)
<div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn ref="aaa"></cpn>
    <button @click="btnClick">button</button>
</div>

<template id="cpn">
</template>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Hello,Vue'
        },
        methods: {
            btnClick() {
                // 1.$children
                // console.log(this.$children)
                // console.log(this.$children[0].showmsg())

                // 2.$refs -> 对象类型,默认是一个空的对象
                console.log(this.$refs.aaa.name)
            }
        },
        components: {
            cpn: {
                template: '#cpn',
                data(){
                    return{
                        name:'this is component'
                    }
                },
                methods: {
                    showmsg() {
                        console.log('msg')
                    }
                }
            }

        }
    })
</script>
  • 子组件访问父组件:使用$parent
<div id="app">
    <cpn></cpn>
</div>

<template id="cpn">
    <div>
        <h2>我是子组件</h2>
        <ccpn></ccpn>
    </div>
</template>

<template id="ccpn">
    <div>
        <h2>我是子子组件</h2>
        <button @click="btnClick">button</button>
    </div>
</template>

<script>
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Hello,Vue'
        },
        methods: {},
        components: {
            cpn: {
                template: '#cpn',
                data() {
                    return {
                        name: 'my name is cpn'
                    }
                },
                components: {
                    ccpn: {
                        template: '#ccpn'
                        ,
                        methods: {
                            btnClick() {
                                // 1.访问父组件 $parent
                                console.log(this.$parent.name) // my name is cpn
                                // 2.访问根组件 $root
                                console.log(this.$root.msg) // Hello,Vue
                            }
                        }
                    }
                }
            }

        }
    })
</script>

 

slot(插槽)

为什么使用slot
如何封装此类型的组件
基本使用
<div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn><span>哈哈哈</span></cpn>
</div>
<template id="cpn"> 
    <div>
        <h2>我是cpn</h2>
        <!--默认值-->
        <slot>
            <button>button</button>
        </slot>
    </div>
</template>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Hello,Vue'
        },
        components: {
            cpn: {
                template: '#cpn'
            }
        },
        methods: {}
    })
</script>

 

具名插槽的使用
<div id="app">
    <cpn><span slot="center">标题</span></cpn>
    <cpn><button slot="left">返回</button></cpn>
</div>
<template id="cpn">
    <div>
        <slot name="left"><span>左边</span></slot>
        <slot name="center"><span>中间</span></slot>
        <slot name="right"><span>右边</span></slot>
    </div>
</template>

 

作用域插槽的使用
  • 准则

父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译

<div id="app">
    <cpn v-show="isShow"></cpn>
</div>
<template id="cpn">
    <div>
        <p>我是子组件</p> //true
        <button v-show="isShow">按钮</button> //false
    </div>
</template>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            isShow: true,
            msg: 'Hello,Vue'
        },
        components: {
            cpn: {
                template: '#cpn',
                data() {
                    return {
                        isShow: false
                    }
                }
            }
        }
    })
</script>
  • 总结

父组件替换插槽的标签,但内容由子组件来提供

<div id="app">
    <cpn></cpn>
    <cpn>
        <template slot-scope="slot">
            <span>{{slot.data.join(' - ')}} </span>
        </template>
    </cpn>
</div>
<template id="cpn">
    <div>
        <slot :data="pLanguages">
            <ul>
                <li v-for="item in pLanguages">{{item}}</li>
            </ul>
        </slot>
    </div>
</template>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Hello,Vue'
        },
        components: {
            cpn: {
                template: '#cpn',
                data() {
                    return {
                        pLanguages: ['JavaScript', 'C', 'Java', 'Python', 'Swift']
                    }
                },
                created() {
                    this.pLanguages.join(' - ')
                }
            }
        },
        methods: {}
    })
</script>

 

参考视频:小码哥 Vue 视频教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值