打造灵活可复用的Web应用:Vue组件化开发指南!

简介: 组件(Component)是Vue最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用is特性进行了扩展的原生HTML元素。

一、组件的简介


1.1、官方概念


组件(Component)是Vue最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用is特性进行了扩展的原生HTML元素。


Vue让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树。


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ugvqRms-1690870417749)(/004.webp)]


所有的Vue组件同时也都是Vue的实例,所以可以接受相同的选项对象。


1.2、简单介绍下is特性


通俗来说,比如说有些元素,比如ul 里面只能直接包含li元素、select里面只能包含option、table表格里面只能包含tr、td、tbody等。比如像这样:


<ul>
    <li></li>
    <li></li>
</ul>


这种写法是没有问题的,浏览器可以正确的解析,但是你要这么做,浏览器就看不懂了,比如下面的:


<ul>
    <my-li></my-li>
    <my-li></my-li>
</ul>


这样就不能使用my-li这种标签了,如果要达到我们的目的,我们就要使用is特性。像这样,其中is属性的值是组件名:


<ul>
    <li is="my-li"></li>
    <li is="my-li"></li>
</ul>


1.3、组件化和模块化的区别


组件和模块的定位不同。组件一般用于前端,模块化在后台运用的比较多。例如vue中的组件,主要是为了拆分vue实例的代码量,让我们可以以不同的组件来划分不同的功能模块,将来我们需要什么样的功能,就直接调用对应的组件即可。


模块化中的模块一般指的是 Javascript 模块


组件则包含了 template、style 和 script,而它的 Script 可以由各种模块组成。也可以理解为“框架”,意思是把功能进行划分,将同一类型的代码整合在一起。组件化就相当于做一个页面,把页面中的每一个独立的功能拆分出来,可以尽情的拆分,最后组装成一个完整的页面。


组件化


主要从ui界面上进行划分。例如前端的组件化,方便ui组件的调用。类似于以前的导航栏。


模块化


主要从代码逻辑的角度进行划分,方便代码分层开发,保证每个功能模块职责单一。


二、简单的HelloWorld案例


2.1、使用组件的步骤


创建组件构造器

注册组件

使用组件


2.2、案例

<body>
    <div id="myDiv">
        <my-component></my-component>
        <my-component></my-component>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 1、创建组件构造器
        const myComponent = Vue.extend({
            template: `
                    <div>
                        <h1>爱学习的少年</h1>
                        <p>HelloWorld</p>
                        <p>HelloWorld</p>
                    </div>
                    `
        });
        // 2、注册组件
        Vue.component("my-component",myComponent);
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>


三、创建全局组件


3.1、方式一


<body>
    <div id="myDiv">
        <my-component></my-component>
        <my-component></my-component>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 1、创建组件构造器
        const myComponent = Vue.extend({
            template: `       // 模板只能有一个而且只能有一个根元素
                    <div>
                        <h1>爱学习的少年</h1>
                        <p>HelloWorld</p>
                        <p>HelloWorld</p>
                    </div>
                    `
        });
        // 2、注册组件【注意:如果使用的是驼峰命名,则在使用组件的时候需要使用'-'连接】
        Vue.component("myComponent",myComponent);
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>


3.2、方式二


<body>
    <div id="myDiv">
        <my-component></my-component>
        <my-component></my-component>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 1、直接使用Vue.component创建组件
        Vue.component("myComponent",{
            template: `
                    <div>
                        <h1>爱学习的少年</h1>
                        <p>HelloWorld</p>
                    </div>
                    `
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>



3.3、方式三

<body>
    <div id="myDiv">
        <my-component></my-component>
        <my-component></my-component>
    </div>
    <template id="myTemplate">
        <div>
            <h1>爱学习的少年</h1>
            <p>Spring</p>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 1、引入外部一个模板片段
        Vue.component("myComponent",{
            template: "#myTemplate"
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>


3.4、方式四


<body>
    <div id="myDiv">
        <my-component></my-component>
    </div>
    <script type="text/x-template" id="myTemplate">
        <h1>使用script实现注册组件</h1>
    </script>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 注册组件
        Vue.component("myComponent",{
            template: "#myTemplate"
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>


四、创建私有组件


4.1、方式一

<body>
    <div id="myDiv">
        <my-nav></my-nav>
        <my-nav></my-nav>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        const app = new Vue({
            el: "#myDiv",
            components: {
                myNav: {
                    template: "<h1>这个是导航条,私有组件</h1>"
                }
            }
        });
    </script>
</body>



4.2、方式二

<body>
    <div id="myDiv">
        <my-nav></my-nav>
        <my-nav></my-nav>
    </div>
    <template id="myTemplate">
        <h1>这个是导航条,私有组件</h1>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        const app = new Vue({
            el: "#myDiv",
            components: {
                myNav: {
                    template: "#myTemplate"
                }
            }
        });
    </script>
</body>



五、组件填充数据


5.1、说明


组件其实也是Vue的一个实例对象,也可以有自己的data,只不过此处的data的值是一个函数,并且函数必须返回一个Object对象。

data的使用方式和之前的完全一样。


5.2、案例

<body>
    <div id="myDiv">
        <my-nav></my-nav>
        <my-nav></my-nav>
    </div>
    <template id="myTemplate">
        <h1>用户名是: {{username}}</h1>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("my-nav",{
            template: "#myTemplate",
            data: function(){
                return {username: 'HelloWorld'}
            }
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>


5.3、探讨一下为什么data值是一个函数而不是一个对象

<body>
    <div id="myDiv">
        <my-component></my-component>
        <my-component></my-component>
        <my-component></my-component>
    </div>
    <template id="myTemplate">
        <div>
            计数器: {{count}} <br/>
            <button @click="count++">+</button>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 注册组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function () {
                console.log("--"); // 打印了3次
                return {count: 0}
            }
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>



六、组件切换


6.1、方式一:使用v-if判断


<body>
    <div id="myDiv">
        <a href="" @click.prevent="login">登录</a>
        <a href="" @click.prevent="register">注册</a>
        <login v-if="isLogin"></login>
        <register v-else></register>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("login",{
            template: `
                <div>
                    用户名登录: <input type="text" placeholder="用户名登录"/>
                </div>
            `
        });
        Vue.component("register",{
            template: `
                <div>
                    用户名注册: <input type="text" placeholder="用户名注册"/>
                </div>
            `
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                isLogin: true
            },
            methods: {
                login(){
                    this.isLogin = true;
                },
                register(){
                    this.isLogin = false;
                }
            }
        });
    </script>
</body>


6.2、方式二:is实现


<body>
    <div id="myDiv">
        <a href="" @click.prevent="componentName = 'login'">登录</a>  // 记得加单引号
        <a href="" @click.prevent="componentName = 'register'">注册</a> // 记得加单引号
        <component :is="componentName"></component>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("login",{
            template: `
                <div>
                    用户名登录: <input type="text" placeholder="用户名登录"/>
                </div>
            `
        });
        Vue.component("register",{
            template: `
                <div>
                    用户名注册: <input type="text" placeholder="用户名注册"/>
                </div>
            `
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                componentName: "login"
            }
        });
    </script>
</body>


七、父子组件


7.1、初步认识父子组件的定义


<body>
    <div id="myDiv">
        <father></father>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 1、定义子组件
        const children =  Vue.extend({
            template: `
                <h1>这个子组件</h1>
            ` 
        });
        // 2、定义父组件
        const father = Vue.extend({
            template: `
                <div>
                    <h2>这个是父组件</h2>
                    <children></children>
                </div>
            `,
            components: {
                children
            }
        });
        const app = new Vue({
            el: "#myDiv",
            components: {
                father
            }
        });
    </script>
</body>



7.2、父组件向子组件传递数据


7.2.0、问题案例

<body>
    <div id="myDiv">
        <my-component></my-component>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        const app = new Vue({
            el: "#myDiv",
            data: {
                username: "HelloWorld"
            },
            components: {
                myComponent: {
                    template: '<h1>姓名是: {{username}}</h1>' // 会报错
                }
            }
        });
    </script>
</body>
// 按道理来说,子作用域应该可以访问父级别作用域的,但是发现并不行。


解决思路可以这样:在父组件中,可以在使用子组件的时候,通过属性绑定的方式,把需要传递给子组件的数据以属性绑定的形式传递到子组件内部,这样的话,子组件内部就可以接收到了。


方式:通过props向子组件传递数据


7.2.1、形式一


字符串数组,数组中的字符串就是传递时的名称。


说明:让Vue的实例作为父组件,之后再定义一个子组件


<body>
    <div id="myDiv">
        <my-component v-bind:subusername="username"></my-component>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        const app = new Vue({
            el: "#myDiv",
            data: {
                username: "HelloWorld"
            },
            components: {
                myComponent: {
                    props: ['subusername'],
                    template: '<h1>姓名是: {{subusername}}</h1>'
                }
            }
        });
    </script>
</body>



<body>
    <div id="myDiv">
        <my-component v-bind:subaddresses="fatherAddresses"></my-component>
    </div>
    <template id="myTemplate">
        <div>
            <p v-for="item in subaddresses">
                {{item}}
            </p>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 注册组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: ["subaddresses"]
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                fatherAddresses: [
                    '河南林州',
                    "陕西西安",
                    "浙江杭州"
                ]
            }
        });
    </script>
</body>


父组件要向子组件传递数据,总结如下:


第一步:


在父组件去应用子组件的时候,去动态的绑定一个属性。


<my-component v-bind:subusername="username"></my-component>


subusername这个属性就是自定义的一个属性名称,目的是向子组件中传递数据的这么一个名称。而username就是父组件的data中的数据。


第二步:


自定义属性做好之后,子组件还不能直接使用,还需要接收。即:把父组件传递过来的subusername属性,需要在子组件的props数组中定义,注意是一个字符串的形式,这样的话,子组件才能使用这个数据。


也可以这么说:组件中所有的props中定义的数据,都是通过父组件传递给子组件的。


第三步:


子组件在template中使用子组件props定义的名称,就可以使用数据了。


说明几个细节问题:


对于子组件来说,也可以有自己的data属性,也就是说子组件可以有自己的数据,而data中的数据并不是通过父组件传递过来的,而是子组件自身所独有的,常用的方式是:子组件可以通过调用ajax请求数据,之后把查询出来的数据填充到data属性中。而子组件的props属性一定是从父组件传递过来的。

data属性中的数据都是可读可更改的,而props属性中的数据是只读的。


7.2.2、形式二


对象,对象可以设置传递时的类型,也可以设置默认值


<body>
    <div id="myDiv">
        <my-component v-bind:subaddresses="fatherAddresses"></my-component>
    </div>
    <template id="myTemplate">
        <div>
            <p v-for="item in subaddresses">
                {{item}}
            </p>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 注册组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subaddresses: Array  // 对象,可以指定类型
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                fatherAddresses: [
                    '河南林州',
                    "陕西西安",
                    "浙江杭州"
                ]
            }
        });
    </script>
</body>



指定默认值:基本类型


<body>
    <div id="myDiv">
        <my-component v-bind:subusername="fatherUsername"></my-component> // HelloWorld
        <my-component></my-component>     //使用组件没有传,就用默认的值
    </div>
    <template id="myTemplate">
        <div>
           {{subusername}}
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 注册组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subusername: {
                    type: String,
                    default: "Spring"
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                fatherUsername: "HelloWorld"
            }
        });
    </script>
</body>


指定默认值:对象或者数组


<body>
    <div id="myDiv">
        <my-component v-bind:subaddresses="fatherAddresses"></my-component>
        <my-component></my-component>
    </div>
    <template id="myTemplate">
        <div>
           <p v-for="item in subaddresses">{{item}}</p>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 注册组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subaddresses: {
                    type: Array,
                    default: function(){
                        return ['HelloWorld' , "Spring"]; // 对象或数组默认值的话要用函数
                    }
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                fatherAddresses: [
                    "河南林州",
                    "浙江杭州"
                ]
            }
        });
    </script>
</body>


指定是否必传


<body>
    <div id="myDiv">
        <my-component v-bind:subusername="fatherUsername"></my-component>
        <my-component></my-component>       // 会报错,要必传才行
    </div>
    <template id="myTemplate">
        <div>
           {{subusername}}
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 注册组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subusername: {
                    type: String,
                    required: true  // 表示必传
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                fatherUsername: "HelloWorld"
            }
        });
    </script>
</body>



7.2.3、写法上的改进:驼峰


<body>
    <div id="myDiv">
        <my-component v-bind:sub-addresses="fatherAddresses"></my-component>// 标签需要"-"连接
        <my-component></my-component>
    </div>
    <template id="myTemplate">
        <div>
           <p v-for="item in subAddresses">{{item}}</p>  // 拿数据依然是驼峰法方式
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 注册组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subAddresses: {    // 驼峰命名
                    type: Array,
                    default: function(){
                        return ['HelloWorld' , "Spring"];
                    }
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                fatherAddresses: [
                    "河南林州",
                    "浙江杭州",
                    "云南大理"
                ]
            }
        });
    </script>
</body>


7.3、子组件向父组件传递数据


方式:通过自定义事件向父组件发送消息


<body>
    <div id="myDiv">
        <my-component @fn="fatherFn"></my-component>
    </div>
    <template id="myTemplate">
        <div>
            <button @click="subCompClick">子组件向父组件传递参数使用的事件的方式</button>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 定义子组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function(){
                return {
                    person: {
                        username: '我是子组件HelloWorld'
                    }
                }
            },
            methods: {
                subCompClick(){
                    // 点击之后,子组件发射一个自定义的事件
                    this.$emit("fn",this.person);
                }
            }
        });


   

const app = new Vue({
            el: "#myDiv",
            data: {
                username: "HelloWorld"
            },
            methods: {
                fatherFn(obj){
                    console.log(obj);
                }
            }
        });
    </script>
</body>



解决的思路是这样的:既然我们能做到父组件的data数据传递到子组件中,我们就可以实现将父组件的方法传递到子组件中。【实际上,虽然我们现在的确是在讲子组件向父组件传递数据,如果要按照这种思维方式去实现代码的话,不太好理解,我个人更推荐这种理解方式,即:现在不考虑子组件向父组件传递数据,我们就一律看做是:父组件向子组件传递数据,如果按照这种方式来去理解的话,写代码就会顺畅很多,通过此种方式去理解代码,写着写着就自然而然的就成了子组件向父组件传递数据了。】==


子组件向父组件传递数据/父组件向子组件传递方法,步骤总结如下:


第一步


在父组件中定义一个方法。本案例中Vue实例作为父组件,自定义的方法是fatherFn 。


第二步


在应用子组件的标签上,去动态绑定一个事件。本案例中的事件是fn。


<my-component @fn="fatherFn"></my-component>


代码的含义:就相当于是将父组件中的fatherFn方法的引用传递给子组件的fn事件函数。此时该fn函数肯定是需要在某个时刻要用到的。


第三步


既然我们是子组件向父组件传递数据,那么肯定是在子组件中做了一些操作,然后将数据传递给父组件。本案例中,在子组件template中有一个button按钮,该按钮的作用就是当点击的时候,向父组件传递数据。并且为该按钮绑定了一个事件,属于该按钮的事件,是subCompClick 。


第四步


在子组件中的methods属性中去定义subCompClick事件,当点击按钮的时候就会触发该事件,那么在


subCompClick事件中,操作是:调用$emit方法去发射我们的fn事件,并且通过该方法传递数据,而我们知道,我们的fn其实就是我们第一步骤中的fatherFn 。 然后在fatherFn函数中可以通过参数的形式去接收子组件传递的数据。


<body>
    <div id="myDiv">
        <my-component v-on:get-list-click="fatherHandle"></my-component>
    </div>
    <template id="myTemplate">
        <div>
            <a href=""
               v-for="item in categories"
               @click.prevent="aClick(item)">
                {{item.name}} <br>
            </a>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        // 子组件
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function(){
                return {
                    categories: [
                        {id: 1, name: "动作片"},
                        {id: 2, name: "爱情片"},
                        {id: 3, name: "悬疑片"},
                        {id: 4, name: "悬疑片"}
                    ]
                };
            },
            methods: {
                aClick(item){
                    // 点击之后,子组件发射一个自定义的事件
                    this.$emit("get-list-click",item);
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            methods: {
                fatherHandle(item){
                    console.info("接收的信息是:" ,item);
                }
            }
        });
    </script>
</body>


7.4、案例


该案例:子组件向父组件传数据综合运用。该案例是我们二期jQuery的一个案例:发表评论的案例。


7.4.1、第一步:先把数据给模拟出来


<body>
    <div id="myDiv">
        <p v-for="item in list" :key="item.id">
            评论人: {{ item.user }} 内容: {{ item.content }} 发表日期: {{ item.pubDate }}
        </p>
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        const app = new Vue({
            el: "#myDiv",
            data: {
                list: [
                    {id: 1, user: 'HelloWorld', content: '美好的一天', pubDate: new Date()},
                    {id: 2, user: 'Spring', content: '很高兴', pubDate: new Date()}
                ]
            }
        });
    </script>
</body>


7.4.2、第二步:定义子组件,发表评论


<body>
    <div id="myDiv">
       
        <my-component></my-component>
        <p v-for="item in list" :key="item.id">
            评论人: {{ item.user }} 内容: {{ item.content }} 发表日期: {{ item.pubDate }}
        </p>
    </div>
    <template id="myTemplate">
        <div>
            评论id: <input type="text"> <br/>
            评论人: <input type="text"> <br/>
            评论内容:<textarea></textarea><br/>
            <input type="button" value="提交">
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                list: [
                    {id: 1, user: 'HelloWorld', content: '美好的一天', pubDate: new Date()},
                    {id: 2, user: 'Spring', content: '很高兴', pubDate: new Date()}
                ]
            }
        });
    </script>
</body>


7.4.3、第三步:点击发表评论


为表单控件绑定v-model,目的是获取数据

绑定的数据需要定义在子组件template的data中

为发表按钮提供单击事件

<body>
    <div id="myDiv">
        <my-component @fn="fatherFn"></my-component>
        <p v-for="item in list" :key="item.id">
            评论人: {{ item.user }} 内容: {{ item.content }} 发表日期: {{ item.pubDate }}
        </p>
    </div>
    <template id="myTemplate">
        <div>
            评论id: <input type="text" v-model="id"> <br/>
            评论人: <input type="text" v-model="user"> <br/>
            评论内容:<textarea v-model="content"></textarea><br/>
            <input type="button" value="发表" @click="publish">
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            data:function(){
                return {id: '' , user: '' , content: ''}
            },
            methods: {
                publish: function(){
                    // 发表评论业务处理
                    // 1、构造一个评论数据对象
                    let comment = {id: this.id, user: this.user , content: this.content, pubDate: new Date()};
                    // 2、需要将创建好的评论数据保存到父组件中的data中,涉及到了子组件向父组件传值
                    // >> 子组件发射一个自定义的事件
                    this.$emit("fn",comment);
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                list: [
                    {id: 1, user: 'HelloWorld', content: '美好的一天', pubDate: new Date()},
                    {id: 2, user: 'Spring', content: '很高兴', pubDate: new Date()}
                ]
            },
            methods: {
                fatherFn(comment){
                    // 刚刚新增评论就插入到数组的头部
                    this.list.unshift(comment);
                }
            }
        });
    </script>
</body>


八、综合案例


说明:该案例体现了父子组件相互传递数据。


8.1、第一步:实现父组件数据传递给子组件

<body>
    <div id="myDiv">
        父组件的数据:{{username}} {{age}}
        <my-component :subusername="username" :subage="age"></my-component>
    </div>
    <template id="myTemplate">
        <div>
            通过props属性获取到的父组件中的数据:{{subusername}}---{{subage}}
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subusername: String,
                subage: Number
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                username: 'HelloWorld',
                age: 12
            }
        });
    </script>
</body>



说明:通过案例可以发现,子组件的确可以使用到了父组件中的数据。子组件可以使用props中的数据并显示到页面上,不过props中的数据是从父组件中获取的,是只读数据。


8.2、第二步:看一个细节问题


在子组件中,定义两个input,实现双向绑定,绑定到了props属性的subusername和subage这两个属性,看下实验效果。


<body>
    <div id="myDiv">
        父组件的数据:{{username}} {{age}}
        <my-component :subusername="username" :subage="age"></my-component>
    </div>
    <template id="myTemplate">
        <div>
            通过props属性获取到的父组件中的数据:{{subusername}}---{{subage}} <br/>
            双向绑定props属性中的subusername: <input type="text" v-model="subusername"><br/>
            双向绑定props属性中的subage: <input type="text" v-model="subage"><br/>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subusername: String,
                subage: Number
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                username: 'HelloWorld',
                age: 12
            }
        });
    </script>
</body>



通过实验发现,该案例是有问题的,控制台会报错。


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UlBrOQmL-1690870417750)(/005.png)]


结论:根据提示,避免去直接修改props属性中的数据,因为props属性中的数据都是通过父组件传递过来的,是只读的,避免去覆盖。取而代之的是,可以使用data和computed计算属性。


8.3、第三步:使用data属性


<body>
    <div id="myDiv">
        父组件的数据:{{username}} {{age}}
        <my-component :subusername="username" :subage="age"></my-component>
    </div>
    <template id="myTemplate">
        <div>
            通过props属性获取到的父组件中的数据:{{subusername}}---{{subage}} <br/>
            子组件data属性中的数据:{{data_subusername}}---{{data_subage}} <br/>
            双向绑定data属性中的subusername: <input type="text" v-model="data_subusername"><br/>
            双向绑定data属性中的subage: <input type="text" v-model="data_subage"><br/>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subusername: String,
                subage: Number
            },
            data: function(){
                return {
                    data_subusername: this.subusername,
                    data_subage: this.subage
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                username: 'HelloWorld',
                age: 12
            }
        });
    </script>
</body>



使用data问题解决。但是还有一个需求就是:如果我想改变子组件中的文本框的数据,也想同步修改到父组件,让父组件也实现同步更新。这样的话,就涉及到了子组件向父组件传递数据,需要使用到自定义事件。


8.4、第四步、给子组件的数据设置侦听器


需求:要想实现子组件的数据可以传递到父组件,也就是说子组件中的data数据如果发生了改变,那么父组件也可以感知到,则需要发射自定义事件来解决。此时,可以在子组件中为data中的属性设置侦听器来实现,当属性发生了修改,立马侦听到之后,再发送事件。


<body>
    <div id="myDiv">
        父组件的数据:{{username}} {{age}}
        <my-component :subusername="username" :subage="age"
                      @subchangeusername="fatherChangeUsername"
                      @subchangeage="fatherChangeAge"
        ></my-component>
    </div>
    <template id="myTemplate">
        <div>
            子组件通过props属性获取到的父组件中的数据:{{subusername}}---{{subage}} <br/>
            子组件通过data属性获中的数据:{{data_subusername}}---{{data_subage}} <br/>
            双向绑定props属性中的subusername: <input type="text" v-model="data_subusername"><br/>
            双向绑定props属性中的subage: <input type="text" v-model="data_subage"><br/>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            props: {
                subusername: String,
                subage: Number
            },
            data: function(){
                return {
                    data_subusername: this.subusername,
                    data_subage: this.subage
                }
            },
            watch: {
                data_subusername(newValue){
                    // 发生了修改,发送事件
                    this.$emit("subchangeusername",newValue);
                },
                data_subage(newValue){
                    // 发生了修改,发送事件
                    this.$emit("subchangeage",newValue);
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
                username: 'HelloWorld',
                age: 12
            },
            methods: {
                fatherChangeUsername(value){
                    this.username = value;
                },
                fatherChangeAge(value){
                    this.age = parseInt(value);
                }
            }
        });
    </script>
</body>



九、父组件直接访问子组件


使用方式:$children或者是$refs


9.1、方式一:$children

<body>
    <div id="myDiv">
        <my-component></my-component>
        <my-component></my-component>
        <input type="button" value="单击" @click="handleClick">
    </div>
    <template id="myTemplate">
        <div>
            这个是子组件
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function(){
                return {
                    username: "HelloWorld"
                }
            },
            methods: {
                info(){
                    console.info("子组件的用户名:" + this.username);
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            methods: {
                handleClick(){
                    // 单击父组件的按钮,执行该函数,目的是访问子组件中的数据和调用子组件中的方法
                    console.info(this.$children); // 返回的结果是一个数组
                    for (let comp of this.$children){
                        console.info(comp.username);
                        comp.info();
                    }
                }
            }
        });
    </script>
</body>



总结:此种方式使用不多,要获取具体的子组件还需要通过下标的方式来去获取,非常不方便。


9.2、方式二:$refs


<body>
    <div id="myDiv">
        <my-component ref="comp1"></my-component>
        <my-component ref="comp2"></my-component>
        <input type="button" value="单击" @click="handleClick">
    </div>
    <template id="myTemplate">
        <div>
            这个是子组件
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function(){
                return {
                    username: "HelloWorld"
                }
            },
            methods: {
                info(){
                    console.info("子组件的用户名:" + this.username);
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            methods: {
                handleClick(){
                    // 单击父组件的按钮,执行该函数,目的是访问子组件中的数据和调用子组件中的方法
                    console.log(this.$refs.comp1.username);
                    this.$refs.comp1.info();
                    console.log(this.$refs.comp2.username);
                    this.$refs.comp2.info();
                }
            }
        });
    </script>
</body>



总结:这种方式类似于给组件起了一个id值,通过id的方式直接获取到了某个组件。通常,通过$refs来去获取Dom元素。


<body>
    <div id="myDiv">
        <p ref="p1">HelloWorld</p>
        <p ref="p2">Spring</p>
        <input type="button" value="操作Dom元素" @click="handleClick">
    </div>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        const app = new Vue({
            el: "#myDiv",
            methods: {
                handleClick(){
                    console.log(this.$refs.p1.innerText);
                    console.log(this.$refs.p2.innerText);
                }
            }
        });
    </script>
</body>



十、子组件直接访问父组件和根组件


访问父组件使用方式:$parent


访问根组件使用方式:$root,也就是访问的Vue实例这个根组件


<body>
    <div id="myDiv">
        <my-component></my-component>
    </div>
    <template id="myTemplate">
        <div>
            这个是子组件
            <input type="button" value="访问父组件" @click="subHandleClick">
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            methods: {
                subHandleClick(){
                    // 访问父组件
                    console.log(this.$parent.message);
                    this.$parent.fatherInfo();
                    // 访问根组件
                    console.log(this.$root.message);
                    this.$root.fatherInfo();
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data: {
              message: 'Spring'
            },
            methods: {
                fatherInfo(){
                    console.info("父组件的信息:" + this.message);
                }
            }
        });
    </script>
</body>



十一、插槽


11.1、说明


插槽,其实就相当于占位符。它在组件中给你的HTML模板占了一个位置,让你来传入一些东西。决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性。


插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制。


适用场景:是那些可以将多个组件看做一个整体,这个整体会被复用。但其中的一些部分内容不固定。


11.2、HelloWorld案例


需求:我现在有一个div,是一个子组件,里面有公共的代码,就是p标签,但是这个子组件在不同页面上所展示的效果还是有细微区别的,可能A页面是一个button按钮,B页面可能是一个p标签。


11.2.1、简单案例

<body>
    <div id="myDiv">
        <my-component>
            <button>这个是按钮</button>
        </my-component>
        -------------------------------------- <br>
        <my-component>
            <p>这个是p标签</p>
        </my-component>
    </div>
    <template id="myTemplate">
        <div>
            <p>我是子组件</p>
            <slot></slot>  // 相当于是占位符
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>



11.2.2、改进:为插槽指定默认内容


<body>
    <div id="myDiv">
        <my-component>    
        </my-component>
        -------------------------------------- <br>
        <my-component>
            <p>这个是p标签</p>
        </my-component>
    </div>
    <template id="myTemplate">
        <div>
            <p>我是子组件</p>
            <slot>
                <button>这个是按钮,为插槽指定默认内容</button>
            </slot>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>



总结:该案例是在slot插槽中设置了一个button按钮,相当于是一个默认值,那么此时,在使用该组件的时候,可以不传入了,那么就用默认值button,如果传入了,则就用指定的传入Dom模板。


11.3、具名插槽


说明:如果在一个组件内有多个插槽,如何为指定的插槽填充内容呢,此时就需要为每个插槽提供一个名字,这种插槽就叫做具名插槽。


11.3.1、问题案例

<body>
    <div id="myDiv">
        <my-component>
            <button>此处替换为按钮</button>
            <p>此处是P标签</p>
        </my-component>
    </div>
    <template id="myTemplate">
        <div>
            <slot><p>Tomcat</p></slot>
            <p>我是子组件</p>
            <slot><p>HelloWorld</p></slot>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>


总结:会发现,组件中的slot插槽中的内容都被button和p所替代了。


11.3.2、解决方案一


<body>
    <div id="myDiv">
        <my-component>
            <button slot="slot1">此处替换为按钮</button>
            <p>此处是P标签</p>
        </my-component>
    </div>
    <template id="myTemplate">
        <div>
            <slot name="slot1"><p>Tomcat</p></slot>
            <p>我是子组件</p>
            <slot name="slot2"><p>HelloWorld</p></slot>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>



11.3.3、 解决方案二


<body>
    <div id="myDiv">
        <my-component>
            <template v-slot:slot1>
                <button>此处替换为按钮</button>
            </template>
            <p>此处是P标签</p>
        </my-component>
    </div>
    <template id="myTemplate">
        <div>
            <slot name="slot1"><p>Tomcat</p></slot>
            <p>我是子组件</p>
            <slot name="slot2"><p>HelloWorld</p></slot>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>



注意:在使用slot的时候,单独的使用slot已经在Vue2.6版本已经废弃,取而代之的是v-slot。


11.4、编译作用域


11.4.1、案例1


<body>
    <div id="myDiv">
        <my-component v-show="isShow"></my-component>
    </div>
    <template id="myTemplate">
        <div>
           <p>我是HelloWorld</p>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function () {
                return {
                    isShow: true
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data:{
                isShow: false
            }
        });
    </script>
</body>


看代码:在Vue实例中的data属性中有一个isShow,值是false,在子组件中也有一个isShow属性,值是true,在使用子组件的时候,my-component v-show="isShow"中的isShow实际上使用的是Vue实例中的isShow,所以页面中是不显示子组件的。


注意:通过该案例发现,在使用isShow这个变量的时候,操作是这样的:是看这个isShow这个变量是在哪个模板/组件中的,而不是看这个变量被哪个子组件使用的。以当前案例为例,isShow这个变量是在一个叫做myDiv这个模板中的,所以isShow这个变量的作用域就是Vue实例,那么当然使用的是Vue实例中的data。


11.4.2、案例2

<body>
    <div id="myDiv">
        <my-component v-show="isShow"></my-component>
    </div>
    <template id="myTemplate">
        <div>
           <p v-show="isShow">我是HelloWorld</p>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function () {
                return {
                    isShow: false
                }
            }
        });
        const app = new Vue({
            el: "#myDiv",
            data:{
                isShow: true
            }
        });
    </script>
</body>



     


案例:将Vue实例的data中的isShow的值改为true,子组件中的isShow的值改为false,并且在template模板/子组件中也使用了isShow,会发现,页面依然是不显示子组件内容,此时,模板中的isShow使用的就是子组件中的isShow。


11.5、作用域插槽


11.5.1、说明


含义:父组件替换插槽的标签,但是内容由子组件提供。


11.5.2、案例


需求:子组件有一组数据,比如说是一个数组,那么这些数据需要在多个界面进行展示,可能有的界面需要横向排列,有的界面需要纵向排列,有些界面可能就是直接显示一个数组,问题是:数据在子组件中,希望父组件


告诉我们如何显示,如何做?此时,就需要用到==slot作用域插槽==。


11.5.2.1、方式一


<body>
    <div id="myDiv">
        <my-component></my-component>
        <my-component>
            <div slot-scope="subData">
                {{ subData.data.join(' - ') }}
            </div>
        </my-component>
        <my-component>
            <template slot-scope="subData">
                {{ subData.data.join(' * ') }}
            </template>
        </my-component>
    </div>
    <template id="myTemplate">
        <div>
           <slot :data="addresses">
               <ul>
                   <li v-for="item in addresses">
                       {{ item }}
                   </li>
               </ul>
           </slot>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function () {
                return {
                    addresses: ['河南林州',"浙江杭州","陕西西安"]
                }
            }
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>




11.5.2.2、方式二


自 2.6.0 起有所更新。已废弃的使用 slot-scope,使用v-slot代替,直写在组件标签上。


<body>
    <div id="myDiv">
        <my-component></my-component>
        <my-component v-slot="subData">
            {{ subData.data.join(' - ') }}
        </my-component>
        <my-component  v-slot="subData">
            {{ subData.data.join(' * ') }}
        </my-component>
    </div>
    <template id="myTemplate">
        <div>
           <slot :data="addresses">
               <ul>
                   <li v-for="item in addresses">
                       {{ item }}
                   </li>
               </ul>
           </slot>
        </div>
    </template>
    <script src="../js/vue.js"></script>
    <script type="text/javascript">
        Vue.component("myComponent",{
            template: "#myTemplate",
            data: function () {
                return {
                    addresses: ['河南林州',"浙江杭州","陕西西安", "云南大理"]
                }
            }
        });
        const app = new Vue({
            el: "#myDiv"
        });
    </script>
</body>


相关文章
|
16天前
|
前端开发 JavaScript 安全
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第27天】本文介绍了HTTP/2和HTTPS在前端性能调优中的应用。通过多路复用、服务器推送和头部压缩等特性,HTTP/2显著提升了Web性能。同时,HTTPS确保了数据传输的安全性。文章提供了示例代码,展示了如何使用Node.js创建一个HTTP/2服务器。
30 3
|
21天前
|
移动开发 开发者 HTML5
构建响应式Web界面:Flexbox与Grid的实战应用
【10月更文挑战第22天】随着互联网的普及,用户对Web界面的要求越来越高,不仅需要美观,还要具备良好的响应性和兼容性。为了满足这些需求,Web开发者需要掌握一些高级的布局技术。Flexbox和Grid是现代Web布局的两大法宝,它们分别由CSS3和HTML5引入,能够帮助开发者构建出更加灵活和易于维护的响应式Web界面。本文将深入探讨Flexbox和Grid的实战应用,并通过具体实例来展示它们在构建响应式Web界面中的强大能力。
34 3
|
1月前
|
存储 安全 关系型数据库
后端技术:构建高效稳定的现代Web应用
【10月更文挑战第5天】后端技术:构建高效稳定的现代Web应用
53 1
|
4天前
|
缓存 安全 网络安全
HTTP/2与HTTPS在Web加速中的应用
HTTP/2与HTTPS在Web加速中的应用
|
6天前
|
SQL 安全 前端开发
PHP与现代Web开发:构建高效的网络应用
【10月更文挑战第37天】在数字化时代,PHP作为一门强大的服务器端脚本语言,持续影响着Web开发的面貌。本文将深入探讨PHP在现代Web开发中的角色,包括其核心优势、面临的挑战以及如何利用PHP构建高效、安全的网络应用。通过具体代码示例和最佳实践的分享,旨在为开发者提供实用指南,帮助他们在不断变化的技术环境中保持竞争力。
|
17天前
|
前端开发 安全 应用服务中间件
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第26天】随着互联网的快速发展,前端性能调优成为开发者的重要任务。本文探讨了HTTP/2与HTTPS在前端性能优化中的应用,介绍了二进制分帧、多路复用和服务器推送等特性,并通过Nginx配置示例展示了如何启用HTTP/2和HTTPS,以提升Web应用的性能和安全性。
17 3
|
17天前
|
前端开发 JavaScript API
前端框架新探索:Svelte在构建高性能Web应用中的优势
【10月更文挑战第26天】近年来,前端技术飞速发展,Svelte凭借独特的编译时优化和简洁的API设计,成为构建高性能Web应用的优选。本文介绍Svelte的特点和优势,包括编译而非虚拟DOM、组件化开发、状态管理及响应式更新机制,并通过示例代码展示其使用方法。
33 2
|
17天前
|
测试技术 持续交付 PHP
PHP在Web开发中的应用与最佳实践###
【10月更文挑战第25天】 本文将深入探讨PHP在现代Web开发中的应用及其优势,并分享一些最佳实践来帮助开发者更有效地使用PHP。无论是初学者还是有经验的开发者,都能从中受益。 ###
38 1
|
18天前
|
负载均衡 监控 算法
论负载均衡技术在Web系统中的应用
【11月更文挑战第4天】在当今高并发的互联网环境中,负载均衡技术已经成为提升Web系统性能不可或缺的一环。通过有效地将请求分发到多个服务器上,负载均衡不仅能够提高系统的响应速度和处理能力,还能增强系统的可扩展性和稳定性。本文将结合我参与的一个实际软件项目,从项目概述、负载均衡算法原理以及实际应用三个方面,深入探讨负载均衡技术在Web系统中的应用。
48 2
|
21天前
|
JavaScript 前端开发 持续交付
构建现代Web应用:Vue.js与Node.js的完美结合
【10月更文挑战第22天】随着互联网技术的快速发展,Web应用已经成为了人们日常生活和工作的重要组成部分。前端技术和后端技术的不断创新,为Web应用的构建提供了更多可能。在本篇文章中,我们将探讨Vue.js和Node.js这两大热门技术如何完美结合,构建现代Web应用。
19 4