最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【技术胖】Vue3.x从零开始学-第二季 组件篇

    正文概述 掘金(技术胖)   2021-04-06   764

    在学习这个视频之前,你需要先学习《Vue3.x从零开始学-第一集-基础语法篇》。第二季(本教程)主要讲解Vue3中组件的一些知识,包括什么是全局组件、局部组件、组件如何复用、组件间的传值,单项数据流、Non-Props属性、父子组件的 通信、组件双向绑定、组件中使用插槽、还包括动态组件和异步组件。

    但是你还是需要记住,学习本课程前,还是需要先对基础知识有所了解。

    00. 第二季视频列表

    01. [组件]全局组件定义和复用性讲解

    02. [组件]Vue3中的局部组件

    03. [组件]父子组件的静态和动态传值

    03. [组件]父子组件的静态和动态传值

    04. [组件]组件传值时的校验操作

    05. [组件]组件中重要机制-单项数据流

    06. [组件]Non-props使用技巧

    07 [组件]组件中通过事件进行通信

    08. [组件]组件中插槽的使用Slot -1

    09. [组件]组件中插槽的使用Slot-2

    10. [组件]具名插槽简写和作用域插槽

    11. [组件]动态组件和状态保存

    12.[组件]异步组件和Promise讲解

    13.[组件]provide和inject多级组件传值

    01. [组件]全局组件定义和复用性讲解

    一张图了解组件概念

    我们先来看一张Vue3官方给出的图,通过图片能清楚的了解到什么是Vue中组件的一些端倪。

    【技术胖】Vue3.x从零开始学-第二季 组件篇

    图的左边是一个网页,网页分为了头部、左侧主体和右侧边栏。这时候你用组件的概念,就可以先把这个网页分为三个大的组件,分别是头部、左侧和右侧。然后再根据每个大组件里的内容和功能,作更加详细的组件划分。这样就可以把一个复杂的大页面,分解成一个个可以复用的小页面。

    总结如下:

    Vue中的组件是页面中的一部分,通过层层拼装,最终形成了一个完整的组件。这也是目前前端最流行的开发方式。

    Vue3中根组件的讲解

    学完理论,我们打开VSCode代码编辑器,编写一段基本的Vue3结构(因为是本季视频的第一节,所以我还是给你敲一下基本代码)。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="https://unpkg.com/vue@next"></script>
    </head>
    <body>
        <div id="app"></div>
    </body>
    </html>
    

    有了这段代码,在编写相应的Vue代码。用createApp( )创建Vue的实例,然后用mount( )方法挂载到DOM节点上。

    <script>
        const app=Vue.createApp({ })
        const vm=app.mount("#app")
    </script>
    

    这时候的Vue.createApp实际是建立一个Vue的实例,也就是相当于刚才说的第一个根组件。你可以通过对象属性的形式(实际上就是方法接受一个对象实行的参数),来定义根组件的具体样式和方法。比如在根组件上定义一个模板,然后在页面输出JSPang.com这个网址。

    const app=Vue.createApp({
        template:`<h2>JSPang.com</h2>`
    })
    

    写好后,到浏览器中进行预览,可以在页面上看到JSPang.com这个网址。

    这时候我们希望组件再多一行字,比如加上技术胖的中文。代码可以这样编写。

    
    const app=Vue.createApp({
        template:`
            <h2>JSPang.com</h2>
            <div>技术胖的博客</div>
        `
    })
    

    全局组件的定义

    现在页面上已经有两个部分组成,<h2>JSPang.com</h2><div>技术胖的博客</div>。那这时候你就可以把这两部分拆分成两个全局的字组件。代码如下:

    app.component('website',{
        template:` <h2>JSPang.com</h2>`
    })
    app.component('describe',{
        template:` <h2>技术胖的博客</h2>`
    })
    

    组件定义好后,可以直接在根组件上进行使用。

    const app=Vue.createApp({
        template:`
            <website />
            <describe />
        `
    })
    

    现在页面上元素,就被拆分了,一个根组件下面有两个子组件。这里我们并没有什么业务逻辑,所以看起来还没有太多的作用,但是如果一旦业务逻辑复杂,我们这样拆分,会降低开发难度。

    组件的可复用性讲解

    现在要作的事情是定义一个新的计数组件-count,每次点击按钮,组件中的count变量自动加1.

    app.component('count',{
        data(){
            return{
                count:0
            }
        },
        
        template:`<div>{{count}}<button @click="count++">增加1</button></div>`
    })
    

    写好组件以后,就可以在根组件中复用了,这里你可以加入多个<count />组件,比如说是三个。

    const app=Vue.createApp({
        template:`
            <website />
            <describe />
            <count/>
    				<count/>
    				<count/>
        `
    })
    

    然后在浏览器中进行预览,你会发现<count />是互不干扰的,就是因为这个特性,Vue中的组件就具有了复用性。

    全局组件的弊端

    全局组件编写起来确实非常方便,当时全局组件就是你一旦定义了,就会占用系统资源。它是一直存在的,你在任何地方都可以使用这个全局组件。这势必会对性能产生影响,比如一个真实的项目,会有上千个组件,这些组件由不同人编写,如果全部是全局组件,那这个应用打开速度一定是极慢的,而且流畅度也会受到影响。

    全局组件的概括:只要定义了,处处可以使用,性能不高,但是使用起来简单。

    那我们来总结一下:本节课我们学习了什么是Vue中的组件,如何定义全局组件,讲解了组件的复用性,然后又说了一下组件的弊端。这里需要强调的是组件对于Vue的学习非常重要,所以希望小伙伴们能重视这部分内容的学习和练习。

    02. [组件]Vue3中的局部组件

    和全局组件相对应的就是局部组件,你可以把局部组件看成一个变量,然后在使用的地方注册这个组件,才可以使用。局部组件的最大好处就是只有在使用的时候,才会耗费系统资源,不像全局组件一样,一直都存在。你现在看这段话可能不能很好的理解,下面通过代码,来讲解一下什么是局部组件。

    准备使用文件

    打开VSCode,新建一个文件Demo2.html,直接复制Demo1.html的内容。删除一些无用内容。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template: `<h2>JSPang.com</h2>`
        })
        const vm = app.mount("#app")
    </script>
    </html>
    

    这时候可以打开浏览器看一下是否可以显示出正常内容。如果可以正常显示,说明一切正常。接下来就可以编写一个局部组件了。

    创建局部组件

    注册局部组件,其实你可以理解成就是声明一个变量,这个变量是一个对象,所以最外层我们使用大括号(花括号),然后里边和使用Vue.CreateApp( )时传递的参数是一样的。

    const counter = {
        data() {
            return {
                count: 0
            }
        },
        template: `<div>{{count}}<button @click="count++">增加1</button></div>`
    }
    

    注册完组件后,我们还不能直接使用,如果直接使用会报错。这时候要作的是在vue.CreateApp( )方法里进行注册。

    组测局部组件

    注册局部组件的方法很简单,直接用components属性来声明就可以了。

    const app = Vue.createApp({
        components: { counter },
        template: `
            <h2>JSPang.com</h2>
            <counter />
        `
    })
    

    但是这只是简写方法,正确的写法应该是 components: { counter:counter }, ,其实就是给组件起个名字,你甚至可以叫做jspang

    const app = Vue.createApp({
            components: { jspang: counter },
            template: `
            <h2>JSPang.com</h2>
            <jspang />
    

    注册好后,你就可以直接在模板中使用了。需要注意的是,局部变量也是拥有独立性的,所以可以进行复用。

    局部组件的编写有一些不成文的潜规则,下面介绍一下。

    局部组件使用大驼峰命名法

    有时候我们的组件名字会比较长,比如写一个xieDaJiao组件。

    const xieDaJiao = {
            template: `<h2>谢大脚</h2>`
        }
    

    现在变量的名字有大小写,这时候在组测组件时,要使用这种形式(用-切割单词)。

    const app = Vue.createApp({
        components: { 
            jspang: counter, 
            'xie-da-jiao': xieDaJiao 
        },
        template: `
        <h2>JSPang.com</h2>
        <xie-da-jiao />
        <jspang />
        
    `
    

    产生这种问题是因为一个矛盾点,就是变量中不能使用-,而组件调用确实可以的。所以为了区分组件,在定义局部组件的时候,潜规则是手写字幕进行大写,然后使用驼峰命名法。

    const XieDaJiao = {
            template: `<h2>谢大脚</h2>`
        }
    

    这里给出本节课的全部代码,帮助你更好的学习。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const Counter = {
            data() {
                return {
                    count: 0
                }
            },
            template: `<div>{{count}}<button @click="count++">增加1</button></div>`
        }
        const XieDaJiao = {
            template: `<h2>谢大脚</h2>`
        }
        const app = Vue.createApp({
            components: {
                jspang: Counter,
                'xie-da-jiao': XieDaJiao
            },
            template: `
                <h2>JSPang.com</h2>
                <xie-da-jiao />
                <jspang />
                
            `
        })
        const vm = app.mount("#app")
    
    </script>
    
    </html>
    

    03. [组件]父子组件的静态和动态传值

    这是2021年过完春节的第一节课(大年初五),年前和过年期间一直在陪家人,所以没有录课。如果你也很长时间没有学习了,可以稍微复习一下上节课的代码,再来学习。这节课要学习的内容是父子组件的传值,包括静态传值和动态传值。

    准备基础代码

    为了更好的演示视频,需要新建一个文件,这里就叫做Demo3.html,然后复制Demo2.html的代码,删除上节课的代码,留下基础的代码结构。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo03</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template:`<h2>JSPang.com</h2>`
        })
       
        const vm= app.mount("#app")
    </script>
    </html>
    

    需要说明的是,这里没有引用官方的源,而是使用了bootcdn的源。原因在前两节课已经说过,不在这里赘述了。有了最基础的代码,我们再来声明一个全局组件Son

    app.component('Son',{
        template:`<div>Son div </div>`
    })
    

    因为这个组件是全局的,所以定义好之后就可以直接使用了。(不用在注册声明了)

    const app = Vue.createApp({
        template:`
            <h2>JSPang.com</h2>
            <Son />
        `
    })
    

    使用后可以到浏览器中查看效果,如果正常,此时已经形成了父子组件关系。下面就可以讲解如何从父组件向子组件传值了。

    父组件向子组件传值(静态传值)

    子组件里的Son div是写死的,如果想动态的从父组件传递,可以使用属性的方式,然后子组件使用props进行接收。

    比如我们在调用子组件的地方,通过属性name传递一个技小胖的文字进去。

    const app = Vue.createApp({
        template:`
            <h2>JSPang.com</h2>
            <Son name="技小胖" />
        `
    })
    
    

    传递后用props在子组件中进行接受,然后用插值表达式(双花括号{{name}})的形式进行打印出来。

    app.component('Son',{
        props:['name'],
        template:`<div>{{name}} div </div>`
    })
    

    这样就进行了参数的接收和显示。

    动态数据作为参数

    上面代码传递的参数是写死的,不能进行业务逻辑的编写,也就是我们常说的静态参数。如果想让代码更加的灵活,这里可以使用动态参数的传递。动态参数传递首先要把传递参数放到Vue的数据项里,也就是data属性里。

    例如在data中声明一个name属性,然后赋值为技小胖。这时候需要在template中绑定这个name属性。这样你的值就不是静态的了,而是动态值。

    const app = Vue.createApp({
        data(){
            return {
                name: "技小胖"
            }
        },
        template:`
            <h2>JSPang.com</h2>
            <Son v-bind:name="name" />
        `
    })
    

    当然v-bind:name也可以通过简写的方式,写成:name。改写后的代码如下:

    template:`
        <h2>JSPang.com</h2>
        <Son :name="name" />
    `
    

    静态参数这里还有一个小坑需要说明一下,就是静态传递的只能是字符串类型String,而动态传参的就可以是多种类型了,甚至是一个函数(方法).

    传递参数的例子,在下面会详细讲解,这里先来看传递数字的例子。

    举例说明,比如我们现在改回静态传参,而传递的内容是数字123,但在模板中使用typeof查看属性时,仍然是字符串(String)。

    改为静态传参,参数为123.

    template:`
        <h2>JSPang.com</h2>
        <Son name="123" />
    `
    

    在子组件用typeof查看类型,会发现仍然是string类型.

    app.component('Son',{
        props:['name'],
        template:`<div>{{ typeof name}} div </div>`
    })
    

    但是改为动态传参,就变成了数字类型。

    const app = Vue.createApp({
        data(){
            return {
                name:123
            }
        },
        template:`
            <h2>JSPang.com</h2>
            <Son :name="name" />
        `
    })
    

    参数为函数时的用法

    当使用动态传参时,参数几乎可以是任何类型,甚至是一个函数。下面我就举一个传递函数的例子,让你进一步的理解动态参数的作用。

    需求是这样的,我们编写了一个XiaoJieJie的全局子组件,然后点击时,小姐姐会先说请付钱,然后我们传递函数的形式出给钱。

    编写小姐姐的子组件:

    app.component('XiaoJieJie',{
        props:['pay'],
        methods:{
            handleClick(){
                alert('请付钱....')
                this.pay()  // 付多少钱,是顾客说的算的,所以调用时才能确定
            }
        },
        template:`<div @click="this.handleClick"> 和小姐姐,打招呼! </div>`
    })
    

    在父组件调用的时候,pay是一个定义在data中的函数,然后用动态参数的形式进行调用。

    const app = Vue.createApp({
        data(){
            return {
                name:123,
                pay:()=>{
                    alert('给你500元')
                }
            }
        },
        template:`
            <h2>JSPang.com</h2>
            <Son :name="name" />
            <xiao-jie-jie :pay="pay"/>
        `
    }
    

    下集视频我们继续讲解组件传值时的一些校验方法的编写。观看本视频,请关注微信公众号技术胖

    04. [组件]组件传值时的校验操作

    现在你已经可以轻松的给组件进行传值了,用起来很方便和自由,在实际开发中,这些传递的参数也很可能是通过用户的输入而获得的,必要的前端验证是必不可少的。但是在组件中也需要作最基本的验证。这节课我们就一起来学习一下Vue3中组件传值的验证。

    准备基本文件

    复制上节课Demo3.html代码,到Demo4.html中,并删掉XiaoJieJie自定义组件和调用的地方。为了方便你学习,这里也给出你代码,你可以直接复制使用。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo04</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            data() {
                return {
                    name: 123,
                    pay: () => {
                        alert('给你500元')
                    }
                }
            },
    
            template: `
                <h2>JSPang.com</h2>
                <Son :name="name" />
            `
        })
    
       app.component('Son',{
    		    props:['name'],
    		    template:`<div>{{ typeof name}} div </div>`
    		})
        
        const vm = app.mount("#app")
    </script>
    
    </html>
    

    如果你跟着视频操作,你也可以直接在Demo3.html文件中进行修改,这里只是讲课需要,所以另外写了一个文件。

    对类型的校验

    有时候我们希望传递过来的属性是一个字符串,但很可能用户传递过来的就是一个数字,这时候我希望程序能给我一个提示,让我能做一些业务逻辑处理。这就涉及到了对参数类型的判断。

    app.component('Son', {
            props: {
                name: String
            },
            template: `<div>{{name}} div </div>`
        })
    

    这时候代码就有了校验功能,打开控制台Console就可以看到提示。这里需要注意的是,这种提示不会报错和阻断程序,只是会在控制台给出warn警告信息

    这时候把数据项中的123,修改为字符串时,程序就不再报错了。Vue支持的校验类型包括:String、Boolean、Array、Object、Function和Symbol。

    必填校验和默认值设置

    有些参数是必须要传递的,但有些参数就是可以不传的,当不传时。我们只要给系统一个默认值就可以了。所以有必要对这两个知识学习一下。

    required必填项

    如果要求组件使用时,必须传递参数,可以使用required来校验.

    app.component('Son', {
            props: {
                name: {
                    type: String,
                    required: true
                }
            },
            template: `<div>{{name}} div </div>`
        })
    

    这时候的校验规则就是,name的值必须是字符串,并且不可以省略。这时候可以去掉父组件调用时传递的参数,在浏览器中打开控制台看一下警告信息。

    default默认值

    再来看一下默认值的写法,在原来写required的地方直接写default就可以了。比如写成下面的样子。

    app.component('Son', {
            props: {
                name: {
                    type: String,
                    default: 'JSPang.com'
                }
            },
            template: `<div>{{name}} div </div>`
        })
    

    这时候的意思就是在调用组件时,如果不传递参数,则默认值为JSPang.com

    精准校验-validator

    如果上面这些校验都不能满足你的要求,还可以进行精准校验。比如现在要求传递的字符串中必须包括JSPang这几个字符,就可以用validator来进行校验。它是一个函数,并接受一个value值,这个value就是传递过来的值。

    app.component('Son', {
        props: {
            name: {
                type: String,
                validator: function (value) {
                    console.log(value.search("JSPang"))
                    return value.search("JSPang") != -1
                },
                default: 'JSPang.com'
            }
        },
        template: `<div>{{name}} div </div>`
    })
    

    因为使用search来验证,返回来的是字符串出现的位置,没有找到时才显示-1。所以这里判定如果不为-1就是通过验证。

    当没有通过验证时,就是在控制台给出警告。

    好了,这就是本片文章的所有内容了,希望通过学习你可以知道组件传值时的验证方法。

    05. [组件]组件中重要机制-单项数据流

    这节课继续讲组件传递参数时的单项数据流。它是Vue编写组件的一个重要机制,保证了组件的独立性。作为一个程序员,我们有必要知道、了解和精通单项数据流的概念和使用。

    编写一个计数器

    为了更好的说明什么是单项数据流,在文章开始前,我们需要用以前血的知识,编写一个计数器功能。

    新建一个Demo5.html,然后复制Demo4.html的内容,删除多余无用的代码,只保留最基本的代码结构,你也可以复制下面这段代码。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo05</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            data() {
                return {
                    
                }
            },
            template: `
                <h2>JSPang.com</h2>
            `
        })
    
        const vm = app.mount("#app")
    </script>
    
    </html>
    

    复制好代码后,编写一个全局组件,全局组件接受一个参数counter,代码如下。

    app.component('Counter', {
        props: ['counter'],
        template: `
            {{counter}}<button @click="counter+=1">增加数量</button>
        `
    })
    

    编写好全局组件Counter后,就可以在父组件中使用了,因为Counter是传递到子组件里的,所以要在数据项data中进行声明。

    const app = Vue.createApp({
        data() {
            return {
                counter: 0
            }
        },
        template: `
            <h2>JSPang.com</h2>
            <counter :counter="counter"/>
        `
    })
    

    一切好像都很正常,但是当你在浏览器中预览的时候,你会发现点击按钮根本不起作用。这就是单向数据流机制限制的结果。

    单向数据流概念

    什么是单向数据流?官方的解释还是比较晦涩难懂的。

    • 所有的prop都使得其父子prop之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行。

    我第一次读这段文字时,也没有搞清楚它要说的到底是什么,但是经过学习和项目后,其实单项数据流的概念就特别好理解。可以简单的说:数据从父级组件传递给子组件,只能单向绑定。子组件内部不能直接修改从父组件传递过来的数据。

    这也就是为什么,我们上面写的程序不能使用的原因了。那如何修改这个程序,让其好用那,方法很简单,在组件内的数据项中声明一个变量,把父组件传递过来的变量赋值给内部变量,然后就可以随意修改了。

    app.component('Counter', {
        props: ['counter'],
        data() {
            return {
                newCounter: this.counter
            }
        },
        template: `
            {{newCounter}}<button @click="this.newCounter+=1">增加数量</button>
        `
    })
    

    这样就可以进行修改了。当我们通过程序,更好的理解了什么是单项数据流后,我们再来总结一下。单向数据流就是父组件可以向子组件传递数据,但是子组件不能修改数据。

    为什么要有单向数据流机制

    单项数据流的最终目的,就是为了降低组件的耦合度和独立性。比如现在页面上同时调用了三个<counter/>组件,没有单项数据流的机制,很容易变成一个组件数值变化,其他组件的数值也跟着变化的现象。让页面组件的数据耦合在一起,没办法独立使用。

    template: `
        <h2>JSPang.com</h2>
        <counter :counter="counter"/>
        <counter :counter="counter"/>
        <counter :counter="counter"/>
    `
    

    这节课学习的目的就是搞清楚什么是组件中的单项数据流的概念。

    06. [组件]Non-props使用技巧

    这节课主要讲一下Non-props属性,那什么是Non-props属性那,其实就是子组件没有接受父组件传递过来的参数,而子组件完全不变的复制了属性到自己的标签上,就叫做Non-props属性。我相信你现在还不能完全理解这句话,无所谓,我们继续学习。

    基础页面和子组件的准备

    为了展示Non-props属性的风采,我们需要编写一下讲解使用的代码,你可以把这个过程当作复习。新建Demo6.html文件,然后复制Demo5.html的代码到Demo5.html中。删除上节课编写的代码,留下最基本的结构即可。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo06</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template: ` <h2>JSPang.com</h2>`
        })
        const vm = app.mount("#app")
    </script>
    
    </html>
    

    然后我们再编写一个Hello的全局组件,代码如下。

    app.component('Hello', {
        template: `<h3>Hello World</h3>`
    })
    

    写好子组件后,在父组件中直接使用即可。

    const app = Vue.createApp({
        template: `
            <h2>JSPang.com</h2>
            <hello />
        `
    })
    

    初始Non-props 属性

    上面的代码,我们并没有在子组件里接受任何传递过来的参数,就根本没有写props,但这时候在调用的时候,是仍然可以传值的。

    const app = Vue.createApp({
        template: `
        <h2>JSPang.com</h2>
        <hello msg="jspang" />
    `
    })
    

    这时候你打开浏览器中的控制台,查看Elements标签,可以看到此时的<h3>标签上已经由了msg=jspang这样的属性,也就是父组件直接把属性移植给了子属性。

    <h3 msg="jspang">Hello World</h3>
    

    如果要消除 这种移植,或者是复制,可以在子组件里接受这个属性,但并不使用他。

    app.component('Hello', {
         props: ['msg'],
         template: `<h3>Hello World</h3>`
     })
    

    这时候在打开浏览器的Elements标签查看,就没有多余的属性了。

    两种方式理解后,我们再来说什么是Non-prop属性,它就是父组件向子组件传递参数的时候,子组件不写任何的接受方法。这个时候父组件就会直接把属性完全复制给子组件,子组件也是可以得到属性值的。

    最常见的使用案例就是直接在标签上写CSS样式。

    const app = Vue.createApp({
        template: `
            <h2>JSPang.com</h2>
            <hello style="color:red;" />
        `
    })
    

    这时候子组件会直接得到这个属性,也就把字体变成了红色。

    inheritAttrs属性

    有些时候我们就是不想接受参数,也不想让Non-props属性起作用,这时候可以在子组件上使用inheritAttrs属性来解决这个问题。

    app.component('Hello', {
        inheritAttrs: false,
        template: `<h3>Hello World</h3>`
    })
    

    这时候style标签就不会再被复制到子组件上了,组件上的Hello World也不会变成红色了。

    Non-Prop多节点失效解决方法

    刚才说了,组件在没有接受参数时,会把属性自动复制到组件的根节点上,如果组件不是一个根节点,比如写成下面的样子。

    app.component('Hello', {
        template: `
            <h3>Hello World</h3>
            <h3>Hello World</h3>
            <h3>Hello World</h3>
        `
    })
    

    这时候复制就会失效。你当然可以给他们增加一个根节点,但是在不增加根节点的条件下,也是有办法解决的。比如现在我们想让第一个组件中的<h3>标签复制父组件传递过来的属性,就可以写成下面的样子。

    pp.component('Hello', {
        template: `
        <h3 v-bind="$attrs">Hello World</h3>
        <h3>Hello World</h3>
        <h3>Hello World</h3>
    `
    })
    

    注意,测试的$attrs不是单指某一个属性,而是指全部属性。当然这里只有一个属性,所以只复制了一个,比如你再写一个属性,它也是可以复制过来的。

    const app = Vue.createApp({
        template: `
        <h2>JSPang.com</h2>
        <hello style="color:red;" msg="jspang" />
    `
    })
    

    这时候你打开浏览器,会看到两个属性都复制到了第一个<h3>标签上。

    <h3 msg="jspang" style="color: red;">Hello World</h3>
    

    这点需要注意一下,v-bind="$attrs"的意思是把父组件传递的所以参数属性都复制到子组件当中。

    如果你想只复制一个属性,比如现在我们只复制一个属性style,就这个这么写。

    app.component('Hello', {
        template: `
            <h3 v-bind="$attrs">Hello World</h3>
            <h3 v-bind:style="$attrs.style">Hello World</h3>
            <h3>Hello World</h3>
        `
    })
    

    这时候第二个<h3>标签就只复制了style属性。当然你也可以简写,去掉v-bind,而是用:代表。

    在业务逻辑中使用Non-props属性

    刚才讲的都是在标签上使用Non-props属性,如果你想在业务逻辑中使用Non-props属性也是可以的。比如在生命周期中使用。

    app.component('Hello', {
        mounted() {
            console.log(this.$attrs.msg)
        },
        template: `
        <h3 v-bind="$attrs">Hello World</h3>
        <h3 v-bind:style="$attrs.style">Hello World</h3>
        <h3>Hello World</h3>
        `
    })
    

    然后到浏览器中的控制台进行查看,就可以看到打印出了jspang字样。其实this.$attrs是Vue的一个常用属性,我们需要善于利用。

    07 [组件]组件中通过事件进行通信

    前面我们讲了单项数据流的概念,这节学习一下子组件如何通过事件向父组件传递参数,打破单项数据流的限制。在单项数据流中,我们用了计数器Counter这个例子作了个小例子,帮助大家理解。我们先来复习一下什么是单项数据流。

    通过计数器的编写,本节我们的学习目标如下:

    • 子组件调用父组件事件的编写方法
    • 子组件向父组件事件中传递参数的方法
    • 子组件传递参数时,如何通过emits进行校验

    直接开始我们的学习吧。

    编写计数器案例

    新建一个文件Demo7.html文件,然后复制Demo5.html文件到Demo7.html中。进行修改。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo07</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            data() {
                return {
                    counter: 0
                }
            },
            template: `
                        <h2>JSPang.com</h2>
                        <counter :counter="counter"/>  
                 `
        })
        app.component('Counter', {
            props: ['counter'],
            template: `
            {{counter}}<button @click="this.counter+=1">增加数量</button>
        `
        })
    
        const vm = app.mount("#app")
    </script>
    
    </html>
    

    上面的代码,当我们在浏览器中点击增加时,是不能增加counter的值的,这就是Vue单向数据流的限制。但是有时候,我们就是想在子组件里改变父组件传递过来的值,怎么办那?

    子组件调用父组件事件

    这时候就需要子组件调用父组件的事件,从而改变父组件里的值。我们先在父组件里编写一个方法,叫做handleAddCounter。这个方法就作一件事,没执行一次,数据项里的counter加一。

    methods: {
        handleAddCounter() {
            this.counter += 1
        }
    },
    

    父组件有了这个方法,就可以改变couter数据行的值了,有方法后,现在问题就变成了,如何在子组件中调用这个方法了。

    这时候可以先在子组件的模板template中编写一个click事件。

    {{counter}}<button @click="handleClick">增加数量</button>
    

    子组件调用的并不是父组件中的方法,而是子组件中的方法。如果想调用父组件中的handleAddConter方法,这时候可以在子组件中新建一个方法handleClick,然后用$emit调用父组件的响应事件add。具体代码如下.

    app.component('Counter', {
        props: ['counter'],
        emits: ['add'],
        methods: {
            handleClick() {
                this.$emit('add')
            }
        },
        template: `
        {{counter}}<button @click="handleClick">增加数量</button>
    `
    })
    

    这时候的add是什么?add就是响应事件,在父组件的模板template中,添加一个add响应事件,然后响应事件再调用方法handleAdCounter

    父组件里的模板:

    template: `
                <h2>JSPang.com</h2>
                <counter :counter="counter" @add="handleAddCounter"/>  
            `
    

    这时候可以到浏览器看一下结果,你会发现当点击增加按钮的时候,数据项中的counter值,已经可以增加了。只是在控制台会打印出警告。

    [Vue warn]: Extraneous non-emits event listeners (add) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option. 
      at <Counter counter=3 onAdd=fn<bound handleAddCounter> > 
      at <App>
    

    警告的意思你调用的add方法,并没有用emits进行声明。

    app.component('Counter', {
        props: ['counter'],
        emits: ['add'],
        methods: {
            handleClick() {
                this.$emit('add')
            }
        },
    

    声明后,控制台中的警告会消失。这也是Vue对子组件调用父组件时的一种约束,就是调用前需要声明,否则就会报出警告。

    子组件向父组件传递参数

    当我们不是每次想加1的时候,比如这个值是子组件决定的,比如是2吧。这时候子组件需要向父组件传值,也是可以做到的。你可以在子组件中这样编写。

    methods: {
        handleClick() {
            this.$emit('add', 2)
        }
    },
    

    然后在父组件中接受这个参数param (这个参数的名字你可以随便起),为了看的方便,在控制台进行打印。

    methods: {
        handleAddCounter(param) {
            console.log(param)
            this.counter += param
        }
    },
    

    这时候子组件的参数就传递给了父组件,并且可以使用了。当然你还有更好的选择,就是把所有业务逻辑都放在子组件中,然后把结果传递给父组件。我平时更喜欢用这种方式,比如代码可以写成下面的样子。 业务逻辑写在子组件里

    methods: {
        handleClick() {
            this.$emit('add', this.counter + 3)
        }
    },
    

    父组件直接接受结果就可以了

    methods: {
        handleAddCounter(param) {
            console.log(param)
            this.counter = param
        }
    },
    

    对传递值的校验

    在子组件向父组件传递值的时候,还可以开启校验功能。校验是通过emits这个选项来声明的。比如现在我们要求传递到add中的值,不大于20。如果大于20就进行警告。

    emits: {
        add: (value) => {
            return value < 20 ? true : false
        }
    },
    

    我这里的业务逻辑很简,你完全可以根据你的业务逻辑来编写验证过程。

    这就是本节学习的内容了,我们复习一下。这节我们主要讲解了组件间,子组件调用父组件的方法和传递参数的操作。小伙伴们学习后一定要动手练习,只有动手练习才能发现问题,才能真正学会。

    08. [组件]组件中插槽的使用Slot -1

    这节学习的内容是组件中的插槽(Slot)使用。为了更生动的理解插槽,我们使用红浪漫选择技师的案例。

    编写基础结构

    为了更好的讲解,我们新建一个文件Demo8.html,可以复制下面最基本的文件结构。复制好后,我们再编写其他代码。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo08</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template: ` <h2>欢迎光临红浪漫-请选择您的技师</h2>`
        })
    
        const vm = app.mount("#app")
    </script>
    
    </html>
    

    这是最基本的一个Vue3的代码,只有一个模板template。然后我们新建一个全局的子组件。子组件叫做技师组件,里边只有一个最简单的模板。

    app.component('JiShi', {
        template: `
            <div>
                <span>经过你的慎重考虑.最终选择了。</span>
                <span>
                    xxx
                </span>
            </div>
        `
    })
    

    有了子组件后,在父组件中调用这个子组件。

    const app = Vue.createApp({
        template: `
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <ji-shi />
            `
    })
    

    这时候的XXX,当然可以通过属性传递和接受的方式获得,这种方法你已经掌握了,这节我们学习一下用插槽的方式活动slot

    初识插槽

    插槽的声明很简单,只要在子组件中加入<slot></slot>标签即可,然后在父组件中使用双标签进行调用。具体代码如下:

    <script>
        const app = Vue.createApp({
            template: `
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <ji-shi > 谢大脚 </ji-shi>
            `
        })
    
        app.component('JiShi', {
            template: `
            <div>
                <span>经过你的慎重考虑.最终选择了。</span>
                <span>
                    <slot> </slot>
                   
                </span>
            </div>
        `
        })
    
        const vm = app.mount("#app")
    </script>
    

    这时候去浏览器可以看到,页面出现了我们想要的结果。你可以让页面更丰富,因为插槽支持任何的DOM元素,比如我们加入一个<div>然后让字体变成红色和50像素的字体大小。

    const app = Vue.createApp({
        template: `
        <h2>欢迎光临红浪漫-请选择您的技师</h2>
        <ji-shi > <div style="color:red;font-size:50px;">谢大脚</div> </ji-shi>
        `
    })
    

    比如现在调用两次<ji-shi>组件,给与不同的样式也是可以的。

    const app = Vue.createApp({
            template: `
        <h2>欢迎光临红浪漫-请选择您的技师</h2>
        <ji-shi > <div style="color:red;font-size:50px;">谢大脚</div> </ji-shi>
        <ji-shi > <div style="color:green;font-size:50px;">刘英</div> </ji-shi>
        `
    })
    

    插槽中使用子组件

    插槽可以强大到直接使用子组件,接下来就作一个在插槽中使用子组件的小例子。可以先声明一个最简单的子组件。这个子组件叫做project,也就是项目的意思,是为了展示我们选择技师后所作的项目。

    app.component('project', {
            template: `<span style="color:blue;">项目是胖哥老三样</span>`
        })
    

    有了组件后直接可以在父组件的插槽中进行使用,代码如下。

    const app = Vue.createApp({
        template: `
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <ji-shi > 
                    <div style="color:red;font-size:50px;">
                        谢大脚,<project />
                    </div>
            </ji-shi>
            <ji-shi > <div style="color:green;font-size:50px;">刘英</div> </ji-shi>
            `
    })
    

    浏览器中预览,可以看到确实插槽中是可以放置子组件的。

    插槽中使用动态数据

    插槽中也可以直接使用动态数据,也就是我们常说的数据项,比如定义一个数据项,然后在插槽中使用,直接就可以使用了。

    const app = Vue.createApp({
        data() {
            return {
                jishi: '晓红'
            }
        },
        template: `
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <ji-shi > 
                    <div style="color:red;font-size:50px;">
                        {{jishi}},<project />
                    </div>
            </ji-shi>
            <ji-shi > <div style="color:green;font-size:50px;">刘英</div> </ji-shi>
        `
    })
    

    这时候需要注意的是一个变量作用域的问题,比如我们子组件里也有一个数据项jishi,然后给他赋值为谢大脚

    app.component('JiShi', {
        data() {
            return {
                jishi: '谢大脚'
            }
        },
        template: `
        <div>
            <span>经过你的慎重考虑.最终选择了。</span>
            <span>
                <slot> </slot>
                
            </span>
        </div>
    `
    })
    

    这时候你会发现,浏览器中仍然显示的是晓红,这时候我们得出了一个结论,也是方便你记忆。

    • 父模板里调用的数据属性,使用的都是父模板里的数据
    • 子模板里调用的数据属性,使用的都是子模板里的数据

    这节课学习的内容就到这里了,这节课算是一个初识,下节课我们继续学习插槽相关的知识。

    09. [组件]组件中插槽的使用Slot-2

    这节继续讲解插槽相关的知识,在上篇文章的学习中,你已经找到了插槽的简单使用方法。这节课我带着你深入学习,看插槽中经常使用的两个编写方法,插槽的默认值和具名插槽的使用。

    编写一个最基本的文件

    新建一个文件Demo9.html,然后复制下面最简单的代码结构。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo09</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template: ` <h2>欢迎光临红浪漫-请选择您的技师</h2>`
        })
    
        const vm = app.mount("#app")
    </script>
    

    上面的代码很简单,我们不再介绍了。为了学习插槽的默认值,我们先来复习一下上节课学习的内容,编写一个最简单的组件,并使用插槽。

    插槽默认值的写法

    在文件中先编写一个全局的组件JiShi,代码如下。

    app.component('JiShi',{
            template:`
                <div>
                    你选择了<slot></slot>为你服务。
                </div>
            `
        })
    

    有了插槽后就可以在使用组件的时候直接传递值了。

    const app = Vue.createApp({
        template: ` 
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <ji-shi><span style="color:red;">大脚</span></ji-shi>
        `
    })
    

    这些都是上节的知识,我们就当做复习了。现在来了一位新客人,第一次来,不好意思点技师,那我们就需要给他一个默认值,比如默认值是“晓红”。默认值的关键语法就是在<slot>插槽中直接输入值就可以了。

    app.component('JiShi',{
        template:`
            <div>
                你选择了<slot><span style="color:green;">晓红</span></slot>为你服务。
            </div>
        `
    })
    

    现在这种编写方法,在有值传递过来的时候,会显示正常的值,没有值的时候就会显示默认值"晓红"。

    这就是插槽中默认值的使用,如果你已经了解,我们再来学下一个知识,具名插槽。

    具名插槽的使用

    先来看一个需求,就是如果你去洗脚城,需要经历三个过程:给顾客安排位置、选择技师、选择项目。

    这是一个过程,是有先后顺序的,包括在页面上的显示也是要有顺序的。于是我们写了一个组件。组件代码如下。

    app.component('HongLangMan',{
            template:`
                <div>
                     <div>1.男宾一位,请上二楼。</div>
                     <div>2.你选择了大脚为你服务</div>
                     <div>3.你点了198元泰式按摩</div>
                </div>
            `
        })
    

    程序编写的需求是这样的,选择谁为你服务不变,也就是第二个div中的内容不变。但是第一句和第三句要能在父组件里使用插槽的方法进行定义。你这时会发现程序有问题了,要把<slot>放在哪里?好像放在什么地方都不太合适,我们索性放两个<slot>,一个放在上面,一个放在下面,测试一下。

    app.component('HongLangMan',{
        template:`
            <div>
                    <slot></slot>
                    <div>2.你选择了大脚为你服务</div>
                    <slot></slot>
            </div>
        `
    })
    

    然后在父组件调用的时候传递对应的值,比如把代码写成下面的样子。

    const app = Vue.createApp({
        template: ` 
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <hong-lang-man>
                <div>1.女宾一位,请上三楼。</div>
                <div>3.顾客选择了全身SPA。</div>
            </hong-lang-man>
        `
    })
    

    这时候你打开浏览器,会发现插槽的位置出现了两次重复的内容。也表示我们第一次的尝试失败了。

    其实Vue中已经提供了具名插槽的使用方法,就是给<slot>标签加上一个name属性,你可以把子组件写成下面的样子。

    app.component('HongLangMan',{
        template:`
            <div>
                    <slot name="one"></slot>
                    <div>2.你选择了大脚为你服务</div>
                    <slot name="two"></slot>
            </div>
        `
    })
    

    然后父组件可以用下面的方式进行调用。

    const app = Vue.createApp({
        template: ` 
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <hong-lang-man>
                <template v-slot:one><div>1.女宾一位,请上三楼。</div></template>
                <template v-slot:two><div>3.顾客选择了全身SPA。</div></template>
            </hong-lang-man>
        `
    })
    

    到现在就可以在一个组件里,分别定义两个插槽了。希望你能学会这种方法。

    现在来总结一下,通过这节的学习,你应该对插槽中的默认和具名插槽的使用方法有所了解。这些知识在实际开发中使用的还是比较多的,能形成良好的模块机制。

    10. [组件]具名插槽简写和作用域插槽

    这节先讲一下具名插槽的简写方法,算是对上节课遗漏知识点的补充。然后再讲一下本节的重点插槽作用域,这个还是比较难理解的,所以如果你一遍学不懂。没关系,敲两遍代码,然后再来看视频或者看文章,一定可以有一个深刻的理解。先来看具名插槽的简写方法。

    具名插槽的简写方法

    这里我们打开Demo9.html的方法,上节课我们学习了具名插槽(此处如果不会,可以看上节课的视频或者文章)。

    在父模板中确定插槽位置时,使用了v-slot:one这种方法,其实这个语法可以简写成#one。这时候的代码就变成了下面的样子。

    const app = Vue.createApp({
        template: ` 
        <h2>欢迎光临红浪漫-请选择您的技师</h2>
        <hong-lang-man>
            <template v-slot:one><div>1.女宾一位,请上三楼。</div></template>
            <template v-slot:two><div>3.顾客选择了全身SPA。</div></template>
        </hong-lang-man>
    `
    })
    

    修改后,可以打开浏览器,进行预览,发现也可以出现正确的结果。学完具名插槽的简写方法后,就可以学习这节的重点了作用域插槽。

    作用域插槽-基础代码准备

    其实作用域插槽主要解决的问题是,子组件中有插槽,也有变量,这些变量如何在父组件中进行使用。先来看这样一个需求:编写一个子组件,里边有大脚、刘英和晓红三个名字变量,然后在子组件中用循环的形式输出出来。

    在学习作用域插槽前,我们先来完成这个基本需求,复制Demo9的代码到Demo10.html页面中,然后留下最基础的代码。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo10</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template: `  <h2>欢迎光临红浪漫-请选择您的技师</h2>`
        })
    
        const vm = app.mount("#app")
    </script>
    

    上边就是最基础的Vue3代码,不作过多解释,你可以直接复制这段代码,开始你的学习。接下来我们要写一个子组件,满足刚才说的需求。

    <script>
        const app = Vue.createApp({
            template: `  
                <h2>欢迎光临红浪漫-请选择您的技师</h2>
                <list />
            `
        })
        app.component('List', {
            data() {
                return {
                    list: ['大脚', '刘英', '晓红']
                }
            },
            template: `
                <div>
                    <div v-for="item in list">{{item}}</div>    
                </div>
            `
        })
    
        const vm = app.mount("#app")
    </script>
    

    上面的代码,编写了一个子组件list,然后在list组件里,声明了一个list的数组数据项。数据项里有三个值大脚刘英晓红。在模板中试用v-for的形式,进行循环。

    写完这段代码后,可以到浏览器里调试一下,看看可以正常运行吗?如果一切正常,应该在页面上显示出循环的值了。

    作用域插槽-具体讲解

    有了基础代码,就可以学习作用域插槽的内容了。目前子组件里循环使用的是<div>标签,现在要求这个标签是父组件调用时确定,也就是要使用插槽了。(需要说明,这种情况在你写组件时,经常遇到,所以你要足够的重视)。

    我们刚开始的想法,可以是先改造子组件,增加slot插槽,增加插槽后的模板代码如下。

    template: `
        <div>
            <slot v-for="item in list"  />    
        </div>
    `
    

    然后在父组件里进行调用,调用方法如下。

    const app = Vue.createApp({
        template: `  
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <list> <span></span> </list>
        `
    })
    

    这时候在浏览器中查看,打开控制台,可以看到DOM中,是有三个<span>标签的。说明DOM通过插槽的形式,传递过来了。那接下来的问题,就是父组件如何使用子组件中的变量。

    父组件想使用子组件插槽中的值,可以使用:绑定的形式进行传递,比如写成:item="item",具体的代码可以写成下面的样子。

    app.component('List', {
        data() {
            return {
                list: ['大脚', '刘英', '晓红']
            }
        },
        template: `
            <div>
                <slot v-for="item in list" :item="item" />    
            </div>
        `
    

    写完后父组件中用v-slot="xxx"的形式接受,接收后就可以使用了。

    const app = Vue.createApp({
        template: `  
        <h2>欢迎光临红浪漫-请选择您的技师</h2>
        <list v-slot="props"> 
            <span>{{props.item}}</span> 
        </list>
    `
    })
    

    这时候你会发现列表是可以正常显示的,也可以进行更换标签了,你可以试着把<span>标签换成<div>标签来尝试一下。

    注意这里的props是子组件传递过来的数据都是对象,所以我们要加上.item才能使用。可以把代码修改一下。

    <list v-slot="props">
        <div>{{props.item}}-{{props}}</div> 
    </list>
    

    输出结果如下

    大脚-{ "item": "大脚" }
    刘英-{ "item": "刘英" }
    晓红-{ "item": "晓红" }
    

    简化作用域插槽写法

    其实简化的方法也非常简单,只要使用解构的形式,这是ES6的一种写法。如果对这部分,不熟悉,可以看看ES6关于解构的语法知识。

    const app = Vue.createApp({
        template: `  
            <h2>欢迎光临红浪漫-请选择您的技师</h2>
            <list v-slot="{item}"> 
                <div>{{item}}</div> 
            </list>
            `
    })
    

    这种写法虽然简单,但是理解起来还是有些麻烦的。

    总结一下:这节课我们主要学习了二个知识,一个是具名插槽的简写方法,第二个是作用域插槽的使用和简写方法。重点是作用域插槽,也正是因为有了作用域插槽,子组件才可以传递自身的数据项让父组件进行使用。

    11. [组件]动态组件和状态保存

    这篇文章我们主要学习一下Vue3中的一个重要概念—动态组件。有的小伙伴可能一听到动态组件,会觉的很难,其实它并不难懂,而且工作中用的会比较多。

    我们现在来虚拟一个需求,我们需要为红浪漫洗脚城编写一个带图片的选择技师的功能,并且可以让顾客手动切换这些技师。(这个需求其实有几种方法可以实现,但是这里我们为了学习,特意使用了动态组件来作,并不代表是最优解决方案。)

    准备基本代码

    新建一个文件Demo11.html,然后复制Demo10的代码内容到Demo11中,修改成最基础的代码。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo11</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template: ` 
                <h2>欢迎光临红浪漫-请选择您的技师</h2> 
            `
        })
     
        const vm = app.mount("#app")
    </script>
    

    你也可以复制这段代码进行快速学习,甚至是自己手敲一遍(更能加深印象)。

    创建大脚子组件

    现在你可以创建两个子组件,用来展示大脚和刘英的照片。先给出了课程中需要的照片地址(你也可以自己随便找一张照片来代替)

    素材图片(图片地址直接可用):

    谢大脚图片地址: https://newimg.jspang.com/xiedajiao01.jpg
    刘英图片地址:   https://newimg.jspang.com/liuying01.jpg
    

    然后编写两个组件:

    app.component('dajiao',{
        template:`<img src="https://newimg.jspang.com/xiedajiao01.jpg" />`
    })
    
    app.component('liuying',{
        template:`<img src="https://newimg.jspang.com/liuying01.jpg" />`
    })
    

    然后在父组件中直接使用这两个组件。

    const app = Vue.createApp({
        template: ` 
            <h2>欢迎光临红浪漫-请选择您的技师</h2> 
            <dajiao />
            <liuying />
        `
    })
    

    这时候我们可以到浏览器中查看一下结果,如果结果正常,说明我们的编写的没有错误。

    原始切换效果

    现在的需求是如果显示其中一个组件,另一个组件就不显示。这里我最先想到的是用v-show这种代码来实现。

    我们先来定义一个数据项,用来控制显示那个组件,数据项叫做showItem,然后在两个子组件的位置增加v-show属性,控制最终的显示。

    const app = Vue.createApp({
        data(){
            return {showItem:'dajiao'}
        },
        template: ` 
            <h2>欢迎光临红浪漫-请选择您的技师</h2> 
            <dajiao  v-show="showItem==='dajiao'" />
            <liuying v-show="showItem==='liuying'" />
        `
    })
    

    这时候的showItem值是dajiao,所以浏览器中应该只显示dajiao的组件。

    然后再编写一个按钮,用来切换两个组件,按钮需要绑定一个方法handleClick,方法中我使用了三元运算符来编写这部分内容。

    <button @click="handleClick">切换佳丽</button>
    
    methods:{
        handleClick(){
            this.showItem= this.showItem=='dajiao'?'liuying':'dajiao'
        }
    },
    

    编写到这个步骤,可以到浏览器中查看切换效果,如果没有错误,应该可以实现课程开始前说的效果了。但是这种实现方法还是比较麻烦的,而且也不优雅,所以学一下更好的实现方法。

    动态组件优化代码

    使用动态组件的编程方式,就可以省略v-show部分的代码,也可以让代码看起来更加的优雅。

    <component :is="showItem" />
    

    有了上面这段简短的代码,就可以删除掉下面这两句代码了。

    <dajiao  v-show="showItem==='dajiao'" />
    <liuying v-show="showItem==='liuying'" />
    

    那这决代码的意思是,使用一个动态组件,最终展示那个组件,由数据项showItem决定,它的值是dajiao就显示谢大脚的照片,它的值是liuying,就显示刘英的照片。

    是不是用起来非常简单和轻松。

    动态组件中的状态保存

    动态组件用起来非常方便,但是它有一点小问题。我们现在修改一下dajiao组件,把照片变成一个input框。

    app.component('dajiao',{
        template:`<input />`
    })
    

    这时候你到浏览器中查看,并在输入框中输入文字,在切换组件后。你会发现input框中的文字是无法保存的。

    为了保存input框中的文字,可以使用<keep-alive>标签,把动态组件包裹起来。

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

    这时候相当启用了缓存,把input框的值存了起来。在使用动态组件时,经常配合<keep-alive>标签一起使用。

    好了,这就是本节课所学的内容了,希望你可以掌握以上两个重要知识,一个是动态组件的使用,另一个是<keep-alive>标签的使用。

    12.[组件]异步组件和Promise讲解

    这节我们要讲明白Vue中的异步组件,弄明白什么是异步组件,其实是有前置知识的,就是你需要知道什么是同步,什么又是异步。而且你还会要使用Promise,如果你Promise用的不是很熟练,建议你先去复习一下JavaScript的基础知识,然后再进行学习。

    编写基本代码和同步组件

    新建一个文件Demo12.html,然后复制Demo10.html内容,进行修改。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo11</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template: ` 
              
            `
        })
       
        const vm = app.mount("#app")
    </script>
    

    然后编写一个同步组件(其实以前学的全部都是同步组件),并在父组件里使用它。

    const app = Vue.createApp({
        template: `  <div><tongbu /></div>`
    })
    app.component('tongbu',{
        template:`<div>JSPang.com</div>`
    })
    
    const vm = app.mount("#app")
    

    这时候就是一个同步组件,因为调用后,代码直接就会被执行。那我们了解同步组件后,我们再了解一下异步组件。

    vue3中的异步组件

    异步组件就是在调用组件时,这个组件并不立即渲染,而是要等带一些业务逻辑完成后,才会进行执行组件内的逻辑和渲染到页面上。我们先写一个异步组件,感受一下。

    app.component('async-component',Vue.defineAsyncComponent(()=>{
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve({
                    template:`<div>这是一个异步组件</div>`
                })
            },3000)
        })
    
    }))
    

    这段代码中,新建了一个组件叫做async-component,然后用defineAsyncComponent()声明这是一个异步组件,在组件内部我们用Promise来完成逻辑。逻辑非常简单,用setTimeout控制3秒后渲染模板template,展示内容。也就是说3秒后,组件才知道最终展示的内容是什么。这就是一个典型的异步组件。

    异步组件经常在去后台请求数据的时候进行使用,也可以把一个大项目拆分成很多小的异步组件,然后根据需要,异步加载这些小项目。

    如果这节课的内容你还不能理解,有不要灰心。因为这节课的前置知识是你会使用Promise,你可以先去学习一下Promise的知识,然后再回来听这节课。应该可以轻松很多。

    13.[组件]provide和inject多级组件传值

    这节课是第二季的最后一节了,也算遗漏点的知识补充。接下来我们会推出第三季,Vue3中的动画操作,欢迎你继续和技术胖一起学习。本节的要讲的关键词是provide和inject,作用是进行多级组件传值的。

    创建一个多级组件

    新建一个文件Demo13.html,复制基本代码,也可以复制上节课的代码进行修改。

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Demo13</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.0.2/vue.global.js"></script>
    </head>
    
    <body>
        <div id="app"></div>
    </body>
    <script>
        const app = Vue.createApp({
            template: `<div>我有一套房子,我先传给我的儿子</div>`
        })
        const vm = app.mount("#app")
    </script>
    

    现在的需求是,要写一个子组件,然后子组件里调用另一个子组件(也可以想象成孙子组件),然后从最上层的父组件里传递值给子组件。

    我们先来编写两个子组件的代码。

    <script>
        const app = Vue.createApp({
            data(){
                return {house:'北京别墅一套'}
            },
            template: `<div>我有一套房子,我先传给我的儿子</div> `
        })
    
        app.component('child',{
            template:`
                <div>我是子组件,我要把房子再传给我儿子。</div>
            `
        })
    
        app.component('child-child',{
            template:`
                <div>我是孙子,等待接收房子</div>
            `
        })
        const vm = app.mount("#app")
    </script>
    

    基本结果和组件已经有了,下面我们看看这种情况如何传递。

    常用传递方式

    现在的需求就是一层层传递下去,我们可以使用props的形式进行接收,然后继续传递,代码可以可成这样。

    <script>
        const app = Vue.createApp({
            data(){
                return {house:'北京别墅一套'}
            },
            template: `
                <div>我有一套房子,我先传给我的儿子</div>
                <child :house="house" />
             `
        })
    
        app.component('child',{
            props:['house'],
            template:`
                <div>我是子组件,我要把房子再传给我儿子。</div>
                <div>儿子接收{{house}}</div>
                <child-child :house="house" />
            `
        })
    
        app.component('child-child',{
            props:['house'],
            template:`
                <div>我是孙子,等待接收房子</div>
                <div>孙子接收{{house}}</div>
            `
        })
        const vm = app.mount("#app")
    </script>
    

    这时候到浏览器中查看一下结果,可以发现结果是可以成功的。每次用props接收,然后再用绑定属性的方式传递给下一层。

    但现在需求变化了,富豪北京三环内还有一套200平方的房子,不想通过儿子的手,直接传递给孙子,那如何操作那?

    多级组件传值provide和inject

    这时候就可以使用使用provide传递和inject接收了,先在数据项的下面声明一个provide

    const app = Vue.createApp({
        data(){
            return {house:'北京别墅一套'}
        },
        provide:{
            newHouse:'北京200平方房子一套'
        },
        template: `
            <div>我有一套房子,我先传给我的儿子</div>
            <child :house="house" />
            `
    })
    

    然后儿子组件不用作任何更改,直接在孙子组件里用inject接收就可以了。

    app.component('child-child',{
        props:['house'],
        inject:['newHouse'],
        template:`
            <div>我是孙子,等待接收房子</div>
            <div>孙子接收{{house}},{{newHouse}}</div>
        `
    })
    

    写完后,可以打开浏览器中进行查看,发现孙子组件也是可以顺利接收到的。

    我们学习案例的层级并不多,所以你感觉不是很强烈,如果这种层级关系达到5-6级,再使用props进行传递,那就会非常麻烦,而且会有大量的代码冗余。使用provideinject可以解决这个问题。

    好了,《从零开始学Vue3.x-第二季-组件篇》到本季就结束了。第三季《Vue3动画篇》已经开始更新。


    起源地下载网 » 【技术胖】Vue3.x从零开始学-第二季 组件篇

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元