Vue学习

1、vue介绍

Vue属于JS框架,快速构建前端界面的技术。目前主流版本Vue2和Vue3。

Vue核心思想:不操作dom,通过控制数据(数据驱动),就可以完成页面的所有操作。

学习Vue框架:需要与前面学习的HTML、CSS、JS(DOM)进行对比,知道框架到底帮助我们将哪些技术进行封装(不用书写),哪些技术进行改变。

官网:https://cn.vuejs.org/

2、BS和CS架构

软件设计架构中有两种架构方式:

BS架构:Browser Server :浏览器与服务器模型架构

主要基于浏览器编写网页(HTML、CSS、JS)

CS架构:Client Server : 客户端与服务器模型架构

主要基于H5C3等技术,最终可以生成安卓,苹果等平台的App。

3、设计模式

设计模式:对各种问题的一个有效解决方案。

单例设计模式:保证程序中的对象唯一性。

目标:理解MVVM、MVC、MVP

MV系列技术框架:

  • M:model,模型层(数据、后台返回的、页面获取的、通过表达式运算结果)
  • V:View ,视图层(页面)

MVC:

  • M:model,模型层(数据、后台返回的、页面获取的、通过表达式运算结果)
  • V:View ,视图层(页面)
  • C:Controller,控制器(层),逻辑代码

MVP:

  • M:model,模型层(数据、后台返回的、页面获取的、通过表达式运算结果)
  • V:View ,视图层(页面)
  • P:Presenter:表示器,用于连接M层、V层,完成Model层与View层的交互,还可以进行业务逻辑的处理。

MVVM:目前主流的前端框架底层设计思想

  • M:model,模型层(数据、后台返回的、页面获取的、通过表达式运算结果)
  • V:View ,视图层(页面)
  • VM:ViewModel,视图模型,数据的双向绑定(当Model中的数据发生改变时View就感知到,当View中的数据发生变化时Model也能感知到),是MVVM模式的核心。ViewModel 层把 Model 层和 View 层的数据同步自动化了,解决了 MVP 框架中数据同步比较麻烦的问题,不仅减轻了 ViewModel 层的压力,同时使得数据处理更加方便——只需告诉 View 层展示的数据是 Model 层中的哪一部分即可。

4、Vue体验

4.1、vue入门

# vue2的写法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        {{count}}
        <button @click="add">数据++</button>
    </div>
    <!-- 引入在线版本 -->
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script> -->
    <!-- 引入离线版本 -->
    <script src="./js/vue.js"></script>
    <script>
        new Vue({
            el:"#app",
            data:{
                count:0
            },
            methods:{
                add:function(){
                    this.count++;
                }
            }
        })
        
    </script>
</body>
</html>
# vue3的写法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 视图层View -->
    <div id="app">
        {{count}}
        <button @click="add">++</button>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        // vue3不能直接new Vue,需要从Vue底层解构出createApp的函数
        let { createApp } = Vue;
        // 通过createApp函数来完成vue的创建(app实例)
        let app = createApp({
            // 这里书写的data必须是一个函数
            // data:function(){}
            // es6中函数的简化方式
            data(){
                // let  obj = {}
                return {
                    count:0
                };
            },
            methods:{
                add(){
                    console.log("add....");
                    this.count++;
                }
            }
        });
        // console.log(app);
        app.mount("#app");

    </script>
</body>
</html>

5、Vue的基础法语

Vue2与Vue3的基础语法基本相同

5.1、模版语法

模版:在HTML中使用vue相关的语法和指令。

5.1.1、插值表达式(胡须表达式)

在html中可以使用 双大括号完成数据渲染{{ 变量、调用语句、表达式、简单的运算 }}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <!-- 在页面上需要渲染数据,可以使用vue中提供的插值表达式完成 -->
        <h3>插值表达式:</h3>
        <!-- 使用vue实例中定义的变量,data中的 -->
        <p>姓名:{{ username }}</p>
        <p>年龄:{{ age }}</p>
        <p>性别:{{ sex }}</p>
        <!-- 运算 -->
        <!-- 表达式:有运算结果的式子,都算 -->
        <p>加法运算:{{ 1+2 }}</p>
        <p>比较运算:{{ 1 < 4 }}</p>
        <p>三目运算:{{ age < 18 ? "未成年" : "成年" }}</p>
        <button @click="age--">修改年龄</button>
        <!-- 调用语句:后期开发中使用的比较少,函数一般都会存在返回值 -->
        <p>{{ addSum }}</p>
        <!-- 插值表达式中,如果渲染的数据为underline数据不会显示 -->
        <p>{{ addSum(2,4) }}</p>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            // 使用data定义数据
            data(){
                return{
                    username:"张无忌",
                    age:23,
                    sex:"男"
                }
            },
            methods:{
                addSum(a,b){
                    console.log("a,b", a, b);
                    // return a + b;
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>
</html>

5.1.2、v-cloak

v-cloak : 用来屏蔽插值表达式在网络等原因导致数据无法渲染的时候,页面会出现{{ name }}

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        [v-cloak]{
            display: none;
        }
    </style>
</head>

<body>
    <!-- 
        插值表达式的闪动问题:
        仅限于使用script标签引入vue的js文件,
        如果后期使用vue的脚手架创建项目,问题已经解决
    -->
    <div id="app">
        <p v-cloak>{{name}}</p>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            data(){
                return {
                    name:"杭州"
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

</html>

5.2、文本类指令

在vue中提供了大量的指令(一共13个),快速帮助我们完成数据与页面的绑定、渲染等操作。

vue中提供的指令,都是以v-开始,如果某个指令使用频率特别高,会有简化写法。

回顾,在dom中,如果需要获取或者设置标签中的文本数据,可以通过innerHTML、innerText、textContent。

document.querySelector(“.box”).innerHTML += “”

5.2.1、v-html和v-text

v-text : 渲染的数据中包含html标签,标签不会被解析渲染,标签会原样显示在页面上

v-html:渲染的数据中包含html标签,标签被解析渲染

v-text和v-html,他们会将标签中默认的数据全部覆盖(替换)

{{ }} 将数据插入插值表达式所在的位置上

<body>
    <div id="app">
        <!-- 在标签中间通过插值表达式插入数据 -->
        <p>姓名:{{ name }}</p>
        <!-- 在标签上书写属性,对应的vue的指令,使用到vue选项中的数据,直接书写即可 -->
        <!-- 
            v-text : 渲染的数据中包含html标签,标签不会被解析渲染,标签会原样显示在页面上
         -->
        <p v-text="name2">这个数据您看不到</p>
        <!-- 
            v-html:渲染的数据中包含html标签,标签被解析渲染
         -->
        <p v-html="name2"></p>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            data(){
                return{
                    name:"灭绝小甜甜",
                    name2:"灭绝<b>小甜甜</b>"
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

5.2.2、v-pre

元素内具有 v-pre,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。

<body>
    <pre>
        好好学习
        天天向上
    </pre>
    <div id="app">
        <p v-pre>vue中的插值表达式格式:{{ name }}</p>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({

        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

5.3、属性绑定

5.3.1、v-bind基本使用

v-bind:用于将标签上的属性名或属性值转成vue中的动态的数据

v-bind:属性名=“变量名 | 表达式 | 调用语句”

v-bind:[属性名]=“变量名 | 表达式 | 调用语句”

v-bind可以简化成:

<body>
    <div id="app">
        <!-- 
            vue是基于数据驱动的方式进行编程(让程序员专注于数据的处理,而不需要进行dom操作)
            不管是在页面上,还是在vue的配置项中,尽可能的去操作数据,控制渲染
            在html中一个完整的标签:
                <标签名 属性名="属性值" 属性名="属性值">文本数据或者子标签</标签名>

            v-bind指令:让标签的属性名或属性值变成动态的数据(来自于vue配置项中)
         -->
         <div v-bind:name="value">123213</div>
         <div v-bind:name="1+1">123213</div>
         <div v-bind:name="msg()">123213</div>
         <!-- 如果标签的 属性名也是动态的,需要使用[] 然后引用vue中对应的变量 -->
         <div v-bind:[attrname]="attrValue">123213</div>
         <hr>
         <img v-bind:src="img1" alt="" width="100">
         <img v-bind:src="img2" alt="" width="100">
         <!-- v-bind可以简化为: -->
         <img :src="img3" alt="" width="100">
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            data(){
                return {
                    value:"李四",
                    attrname:"age",
                    attrValue:23,
                    img1:"https://img13.360buyimg.com/babel/s1180x940_jfs/t20260529/111640/23/37858/94517/6475c864F5396934a/0bd76f0443ad5fca.png.avif",
                    img2:"https://img1.baidu.com/it/u=1960110688,1786190632&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=281",
                    img3:'https://img2.baidu.com/it/u=1906732828,3160455141&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=666'
                }
            },
            methods:{
                msg(){
                    return "hello";
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

5.3.2、特殊使用

如果一个对象中的所有数据都需要绑定的某个标签的属性中,这是可以进行v-bind的简化属性

v-bind='对象变量名名'

<body>
    <div id="app">
        <!-- 一个标签上的动态属性数据都来自于某个指定的对象 -->
        <img :src="imgObj.src" v-bind:alt="imgObj.alt" :width="imgObj.width">
        <!-- 使用v-bind=对象,相当于底层将对象中的所有数据解构出来,放到当前这个标签上 -->
        <img v-bind="imgObj">
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            data(){
                return {
                    imgObj:{
                        width:"100",
                        src: 'https://img2.baidu.com/it/u=1906732828,3160455141&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=666',
                        alt:"图片"
                    }
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

5.3.3、练习

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4k3xN50-1686296270819)(课堂笔记.assets/image-20230605163025697.png)]

<body>
    <div id="app">
        <input type="text" placeholder="请输入验证码">
        <button :disabled="disabled" v-on:click="changeTime">{{btnTitle}}</button>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            data() {
                return {
                    disabled: false,
                    btnTitle: "获取验证码",
                }
            },
            methods: {
                changeTime() {
                    // 在vue的其他配置项中,如果需要使用别的配置项中的数据,必须通过this来获取
                    this.disabled = true;
                    let t = 60;
                    let timerID = setInterval(() => {
                        console.log(this);
                        this.btnTitle = t;
                        if (t <= 0) {
                            clearInterval(timerID);
                            this.disabled = false;
                            this.btnTitle = "获取验证码";
                        }
                        t--;
                    }, 1000);

                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

5.4、事件绑定

回顾事件(event):页面上发生的具体某个动作,基于这个动作需要给出相应的处理函数。

事件源target:事件具体在哪个位置(物体、标签)上发生

事件对象event:在事件源上发生具体的某个动作,然后将与动作有关系的数据封装成一个对象,传递给事件处理函数

事件处理函数:事件发生之后,对应的一段js代码(函数)

事件类型:在事件源上发生的具体是什么事件(click、mouseover、scroll、resize等)

绑定事件:

  • 在标签上直接绑定

    <button onclick="" >
        按钮
    </button>
    
    
  • 通过dom对象绑定事件

    <button >按钮</button>
    <script>
       let btn = document.querySelector("button")
       btn.onClick = function(){}
    </script>
    
    
  • 使用事件的绑定函数addEventListener

    <button >按钮</button>
    <script>
       let btn = document.querySelector("button")
       btn.addEventListener("事件名",function(){})
    </script>
    
    

复习:什么是事件委派,事件委派解决什么问题?

5.4.1、vue中的事件绑定

在vue中绑定使用v-on指令。可以简化为@

重点:掌握事件绑定时传递参数与事件对象

<body>
    <div id="app">
        <!-- 直接通过v-on绑定事件 -->
        <button v-on:click="show">按钮</button>
        <!-- 在绑定事件的同时可以传递一些参数给处理函数 -->
        <button v-on:click="show2(11,22,33)">按钮</button>
        <!-- 
            在调用通过事件触发函数执行的时候,如果没有传递任何参数
            默认会传递一个事件对象$event
        -->
        <button v-on:click="show3">按钮</button>
        <input type="text" v-on:keyup="demo">
        <!-- 传递参数的同时,还需要使用事件对象,需要手动传递事件对象 -->
        <div class="box" v-on:mouseover="fn(11,22,$event)">我是div</div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            methods:{
                show:function(){
                    console.log("show");
                },
                show2(a,b,c){
                    console.log("show2:a,b,c",a,b,c);
                },
                show3(event) {
                    console.log(event);
                },
                demo(e){
                    console.log(e);
                },
                fn(a,b,c){
                    console.log("fn a,b:",a,b);
                    console.log(c);
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

5.4.2、事件修饰符

在处理事件时调用 event.preventDefault()event.stopPropagation() 是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理 DOM 事件的细节会更好。

为解决这一问题,Vue 为 v-on 提供了事件修饰符。修饰符是用 . 表示的指令后缀,包含以下这些:

  • .stop : 阻止事件冒泡
  • .prevent :阻止事件默认行为
  • .self : 事件只能通过当前标签触发才执行
  • .capture : 捕获事件
  • .once : 事件只执行一次
  • .passive
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            width: 300px;
            height: 300px;
            background-color: deeppink;
        }
        .box2{
           width: 200px;
            height: 200px;
            background-color: #0a0;
        }
        .box3{
           width: 100px;
            height: 100px;
            background-color: #0af;
        }
    </style>
</head>

<body>
    <div id="app">
        <a href="https://www.baidu.com" @click.prevent>baidu</a>
        <div class="box" @click="box1">
            <!-- 需要注意事件修饰符的书写顺序,会导致执行的效果不一样 -->
            <div class="box2" @click.stop.self="box2">
                <!-- 事件修饰符,在事件后面可以连续书写对应的修饰符 -->
                <div class="box3" @click.top="box3"></div>
            </div>
        </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            methods:{
               box3(e){
                console.log("box3");
                // e.stopPropagation(); 
               },
               box2(){
                console.log("box2");
               },
               box1(){
                console.log("box1");
               }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

</html>

5.4.3、按键修饰符

vue3中测试中发现,键盘码失效了

在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在 v-on@ 监听按键事件时添加按键修饰符。

Vue 为一些常用的按键提供了别名:

  • .enter
  • .tab
  • .delete (捕获“Delete”和“Backspace”两个按键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
<body>
    <div id="app">
        <input type="text" v-on:keyup.enter="demo">
        <input type="text" v-on:keyup.ctrl.enter="demo">
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            methods:{
                demo(e){
                    console.log(e);
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

后期还需要研究表单相关的修饰符。

5.5、v-if指令

5.5.1、v-if基本使用

vue中也提供类似于if、if-else、if else if else 等这样的页面上进行判断的结构

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            width: 100px;
            height: 100px;
            background-color: #0a0;
        }
        .box2{
            width: 100px;
            height: 100px;
            background-color: #fa0;
        }
    </style>
</head>

<body>
    <div id="app">
        <p>flag:{{flag}}</p>
        <button @click="changeFlag">切换</button>
        <div class="box" v-if="flag"></div>
        <div class="box2" v-else="flag"></div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            data(){
                return {
                    flag:false
                }
            },
            methods:{
                changeFlag(){
                    this.flag = !this.flag; 	
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

</html>

5.5.2、选项卡练习

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .item1{
            width: 200px;
            height: 100px;
            background-color: #fa0;
        }
        .item2{
            width: 200px;
            height: 100px;
            background-color: deeppink;
        }
        .item3{
            width: 200px;
            height: 100px;
            background-color: #0af;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="btns">
            <button @click="changeDiv('新闻')">新闻</button>
            <button  @click="changeDiv('八卦')">八卦</button>
            <button  @click="changeDiv('体育')">体育</button>
        </div>
        <div class="cotent-list">
            <div class="item1" v-if="title=='新闻'">新闻列表</div>
            <div class="item2" v-if="title=='八卦'">八卦内容</div>
            <div class="item3" v-if="title=='体育'">体育竞技</div>
        </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        // 创建app对象
        const app = createApp({
            data(){
                return {
                    title:"新闻"
                }
            },
            methods:{
                changeDiv(val){
                    console.log(val);
                    this.title = val;
                }
            }
        });
        // 让app与页面view进行绑定
        app.mount("#app");
    </script>
</body>

</html>

5.6、v-show指令

v-show:可以控制某个标签是否显示,默认使用的display:none样式来控制。

v-if:他是通过控制dom,来完成标签是否渲染在页面上。

在安全方向:v-if比v-show安全。

性能方式:v-if操作dom,性能较差。

拓展:重绘和重排

作业:实现表格隔行变色,鼠标悬停在某行上高亮。:nth-child(2n) :nth-child(2n-1) :hover

<body>
    <div id="app">
        <!-- 
            v-show:
        -->
        <button @click="flag=!flag">切换</button> 
        <p>flag:{{flag}}</p>
        <h3 v-show="flag">flag:{{flag}}</h3>
        <h3 v-if="flag">flag:{{flag}}</h3>
    </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        // 解构出createApp方法
        let { createApp } = Vue;
        // 创建app的实例
        const app = createApp({
            data(){
                return{
                    flag:false
                }
            }
        });
        app.mount("#app");
    </script>
</body>

v-if vs v-show

v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。

v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。

相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。

总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。

5.7、v-for列表渲染

我们可以使用 v-for 指令基于一个数组(对象、字符串、数字)来渲染一个列表。v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名

<body>
    <div id="app">
        <!-- 
        <p>{{arr[0]}}</p>
        <p>{{arr[1]}}</p>
        <p>{{arr[2]}}</p>
        <p>{{arr[3]}}</p>
        <p>{{arr[4]}}</p> -->
        <!-- 
            在vue中提供的v-for来完成循环操作,可以通过循环来渲染页面的一些列表(表格)数据
            v-for:遍历数组、对象、字符串、数字
            v-for语法:
                v-for="(item,index) in 数组、对象、字符串、数字"
        -->
        <h3>v-for遍历数组</h3>
        <!-- item是遍历出来数组中的某个具体的值 -->
        <p v-for="item in arr">{{item}}</p>
        <!-- v-for遍历的时候,可以书写两个参数,第一个是数据,第二个是数据在数组中的索引 -->
        <p v-for="(item , index) in arr">{{item}}-----{{index}}</p>
        <h3>商品数据:</h3>
        <p v-for="item in goods">
            名称:{{item.name}}
            单价:{{item.price}}
            数量:{{item.count}}
            小计:{{item.price * item.count}}
        </p>
        <hr>
        <h3>遍历对象</h3>
        <!-- 使用v-for遍历对象的时候,第一个参数是对象的属性值,第二个参数是对象的属性名 -->
        <p v-for="(value , key) in student">{{key}}::::{{value}}</p>
        <h3>遍历字符串</h3>
        <span v-for="item in str">{{ item }}</span>
        <br>
        <!-- 遍历字符串的时候,第一个参数是字符串中的数据,第二个参数是字符串的位置(索引) -->
        <p v-for="(item,index) in str">{{ item }}====={{index}}</p>
        <br>
        <h3>提供普通的循环,从1开始到指定的数字结束</h3>
        <p v-for="x in 10">{{x}}</p>
        <br><br><br><br><br><br><br><br><br><br>
    </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        // 解构出createApp方法
        let { createApp } = Vue;
        // 创建app的实例
        const app = createApp({
            data() {
                return {
                    arr: [11, 22, 33, 44, 55],
                    goods: [
                        { id: 1, name: "西瓜", price: 3.8, count: 10 },
                        { id: 2, name: "苹果", price: 5.8, count: 4 },
                        { id: 3, name: "桃子", price: 1.8, count: 7 },
                        { id: 4, name: "香蕉", price: 2.8, count: 9 },
                    ],
                    student:{
                        name:"小明",
                        age:18,
                        sex:"不详",
                        score:99,
                        address:"杭州"
                    },
                    str:"别睡觉啦~~,要下课了~~~"
                }
            }
        });
        app.mount("#app");
    </script>
</body>

5.8、v-for中的key属性

v-for指令中,建议添加key属性,用于进行唯一标识(给vue的底层进行虚拟dom对比的时候使用)

<body>
    <div id="app">
        <!-- 
            v-for中的key属性,要求值一定具备唯一性,
            存在为了提高性能(vue底层使用虚拟dom和diff算法)
            值只能是数字或者字符串
        -->
        <h3>商品数据:</h3>
        <p v-for="(item,index) in goods" :key="index">
            名称:{{item.name}} 
            单价:{{item.price}}
            数量:{{item.count}}
            小计:{{item.price * item.count}}
        </p>
        
        <br><br><br><br><br><br><br><br><br><br>
    </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        // 解构出createApp方法
        let { createApp } = Vue;
        // 创建app的实例
        const app = createApp({
            data() {
                return {
                    goods: [
                        { id: 1, name: "西瓜", price: 3.8, count: 10 },
                        { id: 2, name: "苹果", price: 5.8, count: 4 },
                        { id: 3, name: "桃子", price: 1.8, count: 7 },
                        { id: 4, name: "香蕉", price: 2.8, count: 9 },
                    ],
                }
            }
        });
        app.mount("#app");
    </script>
</body>

5.9、表单v-model指令

v-model 还可以用于各种不同类型的输入,<textarea><select> 元素。它会根据所使用的元素自动使用对应的 DOM 属性和事件组合:

  • 文本类型的 <input><textarea> 元素会绑定 value property 并侦听 input 事件;
  • <input type="checkbox"><input type="radio"> 会绑定 checked property 并侦听 change 事件;
  • <select> 会绑定 value property 并侦听 change 事件

v-model 会忽略任何表单元素上初始的 valuecheckedselected attribute。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。你应该在 JavaScript 中使用data 选项来声明该初始值。

<body>
    <div id="app">
        <!-- 
            有时是需要获取表单中的数据,
            有时是需要给表单中回显数据 
            针对表单项,input、select、textarea提供一个双向绑定的指令
            v-model:
            针对input输入项,v-model的底层相当于使用 input事件+value属性

        -->
       <input type="text" placeholder="请输入数据" @input="inputData" :value="msg">
       <input type="text" placeholder="请输入数据" :value="msg">
       <h1>输入的数据:{{ msg }}</h1> 
       <button @click="msg='数据'">设置数据</button>
       <hr>
       <input type="text" placeholder="请输入数据" v-model="msg">

       <h3>v-model的修饰符</h3>
       <!-- 
            input输入项默认拿到的数据是字符串类型
            v-model.number 将得到的数据转成number类型
            v-model.trim 将得到的数据去掉尾随空格。
        -->
       年龄:<input type="text" v-model.number="age" @input="show">
       姓名:<input type="text" v-model.trim="name" @input="show2">
    </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        // 解构出createApp方法
        let { createApp } = Vue;
        // 创建app的实例
        const app = createApp({
            data(){
                return{
                    msg:"默认值",
                    age:0,
                    name:""
                }
            },
            methods:{
                inputData(e){
                    console.log(e.target.value);
                    this.msg = e.target.value;
                },
                show(){
                    console.log(typeof this.age);
                    console.log(this.age);
                },
                show2(){
                    console.log(this.name);
                }
            }
        });
        app.mount("#app");
    </script>
</body>

<body>
    <div id="app">
        <!-- 
            表单项中有的是让用户输入的,有的是让用户选择,
            如果是选择的项,必须给出对应的value属性
            单选和复选,默认不给value属性,选中之后获取的是值on
            针对下拉框,option标签不写value属性,默认会使用option中的文本作为value属性值

            v-model:
             number : 输入的数据转成数字(有bug)
             trim: 取出输入的数据前后空白
             lazy: 将input的默认的input事件改为change事件,提高性能
         -->
        <p>姓名:<input type="text" placeholder="姓名" v-model.lazy="username"></p>
        <p>密码:<input type="password" placeholder="密码" v-model="password"></p>
        <p>性别:
            <input type="radio" v-model="sex" value=""><input type="radio" v-model="sex" value=""></p>
        <p>爱好:
            <input type="checkbox" v-model="like" value="篮球">篮球
            <input type="checkbox" v-model="like" value=""><input type="checkbox" v-model="like" value=""><input type="checkbox" v-model="like" value="rap">rap
        </p>
        <p>
            地址:
            <select v-model="address">
                <option value="杭州">杭州</option>
                <option value="宁波">宁波</option>
                <option value="上海">上海</option>
                <option value="镇江">镇江</option>
            </select>
        </p>
    </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        // 解构出createApp方法
        let { createApp } = Vue;
        // 创建app的实例
        const app = createApp({
            data() {
                return {
                    username: "zhangsan",
                    password: "",
                    sex:"女",
                    like:['跳','rap'],
                    address:"镇江"
                }
            }
        });
        app.mount("#app");
    </script>
</body>

练习:数据添加,练习vue的数据驱动思想和v-model

5.10、样式

5.10.1、控制style属性

        通过v-bind指令,操作style属性或者class属性
        v-bind 
            控制style属性的值:
                字符串格式的样式:在vue的data中定义字符串格式的css样式代码
                对象格式:将css样式代码书写成对象格式
                数组格式:将css样式书写在data中,分别采用的不同的变量表示不同的样式,然后style中通过数组引入多个样式

<body>
    <div id="app">
        <div style="width:200px;height: 100px;background-color: #fa0;"></div>
        <br>
        <div :style="divStyle"></div>
        <br>
        <!-- style原始属性数据可以和动态的style一起存在 -->
        <div style="background-color: #fa0;" :style="{width:'300px',height:'100px'}"></div>
        <br>
        <div style="background-color: #0a0;" :style="{width:w+'px',height:h+'px'}"></div>
        <button @click="changeWidthAndHeight">修改宽和高</button>
        <br><br>
        <div style="background-color: #0a0;" :style="[width,height]"></div>
    </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        // 解构出createApp方法
        let { createApp } = Vue;
        // 创建app的实例
        const app = createApp({
            data(){
                return{
                    divStyle:"width:200px;height: 100px;background-color: #fa0;",
                    w:300,
                    h:150,
                    width:"width:200px",
                    height:"height: 100px",
                }
            },
            methods:{
                changeWidthAndHeight(){
                    this.w += 20
                    this.h += 10
                }
            }
        });
        app.mount("#app");
    </script>
</body>

5.10.2、控制class

控制class,其实就是让标签上的类名变成动态的,可以通过字符串方式、对象方式、数组方式完成。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .a {
            background-color: deeppink;
            transition: all 2s;
        }

        .b {
            width: 200px;
        }

        .c {
            height: 100px;
        }

        .box {
            border: 10px solid goldenrod;
        }

        .box2 {
            border-radius: 50%;
        }

        .box3 {
            background-color: #0a0;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="a b c"></div>
        <br>
        <!-- 
          v-bind : 处理class时,值也可以是字符串,对象和数组
        -->
        <div class="a b c" :class="box"></div>
        <br><br>
        <!-- 
          对象方式:控制的还是要不要添加某个类名
          通过对象中给出的某个数值型的真假,来确定要不要给标签上添加某个class对应的类名
        -->
        <div class="a b c" :class="{'box':true,'box2':false,'box3':true}"></div>
        <br><br>
        <div class="a b c" :class="{'box':flag,'box2':flag,'box3':flag}"></div>
        <button @click="flag=!flag">修改</button>
        <!-- 采用数组方式 -->
        <br><br>
        <div class="a b c" :class="[cls1,cls2,cls3]"></div>
    </div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        // 解构出createApp方法
        let { createApp } = Vue;
        // 创建app的实例
        const app = createApp({
            data() {
                return {
                    box: "title",
                    flag: true,
                    cls1:"box",
                    cls2:"box2",
                    cls3:"box3",
                }
            },
            methods: {

            }
        });
        app.mount("#app");
    </script>
</body>

</html>

6、v-for与v-if版本之间的差异

v-for和v-if都是vue中提供的可以在模版(html中)使用的指令,有时会将他们混在一起使用。

在Vue2中v-for的优先级别v-if高,在同一个标签上同时使用v-for和v-if的时候,先执行v-for,再执行v-if。

在Vue3中v-for的优先级别v-if低,在同一个标签上同时使用v-for和v-if的时候,先执行v-if,再执行v-for。

先判断,后循环,目的是为提高效率,防止循环过程中,判断一直不成立,循环在浪费性能。

<body>
    <div id="app">
        <h3>女生列表</h3>
        <!-- 如果需要先循环后判断,可以节奏vue中提供的虚拟标签来完成循环 -->
        <template v-for="item in stusArr">
            <p v-if="item.sex == ''">
                {{item}}
            </p>
        </template>
        <h3>男生列表</h3>
        <template v-for="item in stusArr">
            <p v-if="item.sex == ''">
                {{item}}
            </p>
        </template>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const app = createApp({
            data() {
                return {
                    stusArr: [
                        { id: 1, name: "张无忌", sex: '男' },
                        { id: 2, name: "赵敏", sex: '女' },
                        { id: 3, name: "张三丰", sex: '男' },
                        { id: 4, name: "张翠山", sex: '男' },
                        { id: 5, name: "小昭", sex: '女' },
                        { id: 6, name: "周芷若", sex: '女' },
                    ]
                }
            }
        });
        app.mount("#app");
    </script>
</body>

7、响应式基础

选用选项式 API 时,会用 data 选项来声明组件的响应式状态。此选项的值应为返回一个对象的函数。Vue 将在创建新组件实例的时候调用此函数,并将函数返回的对象用响应式系统进行包装。此对象的所有顶层属性都会被代理到组件实例 (即方法和生命周期钩子中的 this) 上。

大白话:在vue的data配置项中定义的变量(基本类型、对象、数组)他们都会被vue进行进行处理(vue2使用的劫持Object.defineProperty,vue3使用的Proxy进行代理),处理后的这些变量具备的响应式(在data配置项以外的其他位置,html模版,vue的其他配置项)任何位置使用的时候,只要数据发生改变,使用这些变量的相关代码都会重新执行(重新渲染)。

有时在编写代码的时候,需要在data中定义变量,进行数据占位。因为数据不是vue实例创建时候就能够确定的,而是随着程序的运行,或者是通过ajax调用后台接口获取数据。

<body>
    <div id="app">
        {{ }}
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            data(){
                // 在data中定义的所有数据,都会被vue进行代理(劫持)处理
                // 只有被vue代理或者劫持的数据才能配合发布订阅模式完成数据的响应式
                // 响应式:修改数据的时候,使用到数据的地方(模版、vue的其他配置项)会跟着发生变化
                return {
                    msg: 'Hello',
                    arr:[11,22,33],
                    student:{
                        name:"zs",
                        age:18
                    },
                    someObject: {}
                }
            },
            mounted(){
                console.log(this);
                // console.log(this.msg);
                // console.log(this.arr);
                // console.log(this.student);
                // 自己写代码的时候,尽量不要使用_ 或者$ 开始一些变量或者函数
                // 因为这些变量主要是为vue的底层提供
                console.log(this.$data.msg);
                console.log(this.msg);
                console.log("this.someObject", this.someObject);

                let otherObject = {};
                // 通过this找到someObject,它是一个代理对象,
                // 在赋值的时候,是在调用调用对象中的set方法,给其中的属性赋值
                // 而不是直接将 otherObject 简单的赋值给 someObject
                this.someObject = otherObject;
                console.log(this.someObject == otherObject);
               
            }
        });
        app.mount("#app");
    </script>
</body>

8、计算属性

8.1、计算属性介绍

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护

推荐使用计算属性来描述依赖响应式状态的复杂逻辑

计算属性是基于它们的响应式依赖进行缓存的,计算属性比较适合对多个变量或者对象进行处理后返回一个结果值,也就是说多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化。

计算属性定义在Vue对象中,通过关键词 computed 属性对象中定义一个个函数,并返回一个值,使用计算属性时和 data 中的数据使用方式一致。

计算属性配置项:弥补data配置项的不足(data主要用于定义变量,而变量的一些复杂逻辑处理无法再data中实现)

供计算属性配置来弥补:计算属性中书写的内容,也是变量,也会具备响应式

<body>
    <div id="app">
        <span>{{num1}}</span> + 
        <span>{{num2}}</span> =
        <span>{{ sum }}</span>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            data(){
                return{
                    num1:10,
                    num2:20,
                    
                }
            },
            methods:{},
            // 计算属性配置项:弥补data配置项的不足(data主要用于定义变量,而变量的一些复杂逻辑处理无法再data中实现)
            // 提供计算属性配置来弥补:计算属性中书写的内容,也是变量,也会具备响应式
            computed:{
                // 在computed中定义的函数,函数名与data中定义的变量名效果一致
                // computed中的函数名,就是一个变量,在html模版中,或者vue的其他配置项中可以直接使用这个名称
                sum(){
                    console.log(this);
                    return this.num1 + this.num2; 	
                }
            },
            methods:{
                show(){}
            }
        });
        app.mount("#app");
    </script>
</body>

8.2、计算属性与函数对比

计算属性具有依赖性,只有当依赖的值发生改变,才会重新计算

同等条件下,计算属性优于 方法 以及 js表达式。

<body>
    <div id="app">
        <p>计算属性:{{ sum }}</p>
        <p>计算属性:{{ sum }}</p>
        <p>计算属性:{{ sum }}</p>
        <hr>
        <p>函数:{{ getSum() }}</p>
        <p>函数:{{ getSum() }}</p>
        <p>函数:{{ getSum() }}</p>
        <button @click="num1++">修改数据</button>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            data(){
                return{
                    num1:10,
                    num2:20,
                }
            },
            computed:{
                /*
                    1、缓存:
                    计算属性如果关联的数据,没有发生变化,计算属性默认会将计算的结果进行缓存
                    后续只要关联的数据不发生变化,那么就一直使用缓存中的那个结果,而计算属性是不会被执行的
                    2、执行的时机
                    只有计算属性中的函数名被使用了,计算属性才会执行,如果没有使用,计算属性即使存在,函数也不运行。
                    3、返回比较:
                    计算属性必须有返回值,函数可以没有。
                */
                sum(){
                    console.log("计算属性执行");
                    return this.num1 + this.num2;
                }
            },
            methods:{
                // 只要函数被调用,就一定会执行
                getSum(){
                    console.log("普通函数执行");
                    return this.num1 + this.num2;
                }
            }
        });
        app.mount("#app");
    </script>
</body>

8.3、可更改的计算属性

<body>
    <div id="app">
        <input type="text" v-model.lazy="firstName" placeholder="">
        <input type="text" v-model.lazy="lastName" placeholder="">
        <input type="text" v-model.lazy="fullName" placeholder="全名" >
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            data(){
                return{
                    firstName: 'John',
                    lastName: 'Doe',
                    respData:[],
                }
            },
            methods:{
                loadData(){
                    // 使用ajax,调用接口,获取数据
                    // 将数据赋值给data中的respData变量
                }
            },
            computed:{
                // fullName(){
                //     return this.firstName + " " + this.lastName;
                // }
                fullName:{
                    get(){
                        // get 返回的数据来自于接口,还需要对接口的数据进行处理之后才能返回
                        // 处理 respData 中的数据,将结果返回
                        console.log("get...");
                        return this.firstName + " " + this.lastName;
                    },
                    set(val){
                        console.log("set...set");
                        console.log(val);
                        // this.firstName = val.split(" ")[0];
                        // this.lastName = val.split(" ")[1];
                        [this.firstName , this.lastName] = val.split(" ");
                    }
                }
            }
        });
        app.mount("#app");
    </script>
</body>

9、侦听器(watch)

9.1、监听器基本使用

用来监听vue中的数据(变量、数组、对象等)变化。只要被监听的数据发生改变,就会出发监听的函数执行。

<body>
    <div id="app">
        <p>num:{{num}}</p>
        <button @click="num++">修改数据</button>
        num:{{ str }}
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            data(){
                return{
                    num:0,
                    str:"偶数"
                }
            },
            // watch配置项用来监听data或者计算属性中的数据变化
            watch:{
                // 普通的写法与函数没有区别
                // 监听data中的num的变化
                // 第一个参数:变化后的新值
                // 第二个参数:变化前的旧值
                num(newVal,oldVal){
                    // console.log(newVal, oldVal);
                    this.str = newVal % 2 === 0 ? "偶数": "奇数";
                }
            }
        });
        app.mount("#app");
        
    </script>
</body>

9.2、深度监听

在进行watch监听对象的时候,如果需要深层监听对象中的属性数据变化,需要将监听书写成对象格式,

其中监听的执行函数使用handler属性编写

深度监听,需要使用deep:true进行配置

<body>
    <div id="app">
        {{stu}}
        <button @click="stu.age++">修改年龄</button>
        <button @click="stu.wife.age++">修改妻子年龄</button>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            data(){
                return{
                    stu:{
                        name:"张无忌",
                        age:18,
                        wife:{
                            name:"小昭",
                            age:13
                        }
                    }
                }
            },
            watch:{
                // 可以监听对象中的某个单独的属性
                "stu.age"(newVal,oldVal){
                    console.log("stu.age",newVal);
                },
                // 使用监听器中的深度监听
                stu:{
                    // 需要深层次监听某个对象,函数需要使用handler书写
                    handler:function(newVal , oldVal){
                        console.log("newVal", newVal);
                    },
                    deep:true
                }
                
            }
        });
        app.mount("#app");        
    </script>
</body>

9.3、立即执行监听器

监听器默认是不执行的,只有被监听的数据发生改变的时候,才会执行。

希望监听器一加载就执行,需要将监听书写为对象,其中配置 immediate:true

<body>
    <div id="app">
        {{stu}}
        <button @click="stu.age++">修改年龄</button>
        <button @click="stu.wife.age++">修改妻子年龄</button>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            data(){
                return{
                    stu:{
                        name:"张无忌",
                        age:18,
                        wife:{
                            name:"小昭",
                            age:13
                        }
                    }
                }
            },
            watch:{
                // 使用监听器中的深度监听
                stu:{
                    // 需要深层次监听某个对象,函数需要使用handler书写
                    // 监听器一加载就执行,这是回调handler函数中的旧值为undefined
                    handler:function(newVal , oldVal){
                        console.log("newVal", newVal);
                        console.log("oldVal", oldVal);
                    },
                    // 深度监听
                    deep:true,
                    // 让监听立即执行(默认是不执行,只有监听到数据变化才执行)
                    immediate:true
                }
                
            }
        });
        app.mount("#app");
        
        // let arr = [11,22,33];
        // console.log(arr);
        // arr[0]= 111;
        // console.log(arr);
    </script>
</body>

9.4、监听器的回调时机

当你更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调。

默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。

如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post'

<body>
    <div id="app">
        <button @click="flag = !flag">切换</button>
        <div class="box" v-if="flag"></div>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            data(){
                return{
                    flag:true
                }
            },
            watch:{
                flag:{
                    handler:function(newVal , oldVal){
                        setTimeout(() => {
                            console.log(document.querySelector(".box"));
                        }, 0);
                    },
                    // 保证在页面标签渲染完成之后,才去执行监听器的回调函数
                    // flush:"post",
                }
                
            }
        });
        app.mount("#app");

    </script>
</body>

10、生命周期

生命周期:针对Vue实例从Vue被创建到最后Vue实例被销毁的整个过程,为Vue的一个完整生命周期。

Vue2和Vue3的生命周期有所不同,特别针对销毁的时候,给出的声明周期函数有变化。

将Vue的生命周期分成4个阶段(创建初始化、挂载、更新、销毁),8个函数。在Vue的不同的生命周期阶段,允许程序员调用对应的生命周期函数,来添加相关的逻辑代码。

  var LIFECYCLE_HOOKS = [
      'beforeCreate',   : vue实例被创建了,但还没有初始化
      'created',    : vue实例初始化完成之后
      'beforeMount', : vue中的数据等还没有给页面上挂载前
      'mounted',   : vue中的数据与页面已经挂载完成
      'beforeUpdate', : vue中的数据发生的改变,但是数据还没有被渲染到页面上
      'updated',   : vue中的数据发生改变,并且已经渲染完成
      'beforeDestroy', : vue实例不在被使用,需要销毁了,但是还没有被销毁前
      'destroyed', : vue实例被销毁了
      'activated',
      'deactivated',
      'errorCaptured',
      'serverPrefetch',
      'renderTracked',
      'renderTriggered'
  ];

<body>
    <div id="app">
        <h1>{{num}}</h1>
        <button @click="num++">修改</button>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let {createApp} = Vue;
        const app = createApp({
            
            data(){
                return{
                    num:1
                }
            },
            methods:{},
            computed:{},
            watch:{},
            // 生命周期是函数,需要书写成函数格式
            beforeCreate(){
                console.log("beforeCreate", document.querySelector("h1"));
            },
            created() {
                // 发送ajax,获取数据
                console.log("created", document.querySelector("h1"));
            },
            beforeMount() {
                console.log("beforeMount", document.querySelector("h1"));
            },
            mounted() {
                // 操作dom
                console.log("mounted", document.querySelector("h1"));
            },
            beforeUpdate() {
                console.log("beforeUpdate");
            },
            updated() {
                console.log("updated");
            },
            // 学习完组件之后,可以观察
            beforeUnmount() {
                console.log("beforeUnmount");
            },
            unmounted() {
                console.log("unMounted");
            }

        });
        app.mount("#app");
    </script>
</body>

重点使用:

  • created : 在其中发生ajax,获取接口数据
  • mounted : 在其中获取dom,操作dom

11、组件

组件:页面上每个区域,每个按钮等等,都可以看做一个独立的单元,这个独立的单元会包含(html、js、css),它就是一个独立的组件,可以在其他的页面上去引用这个组件。

在开发项目的过程中,尽可能将页面抽取成不同的组件,然后将这些组件组合在一起。

11.1、自定义组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p>num:{{num}}</p>
        <hr>
        <!-- 使用自定义组件 -->
        <my-book></my-book>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp} = Vue;

        // 自定义一个组件(其实就是一个vue的实例)
        const book = {
            // 通过template配置项书写模版的html结构
            template:`
                <div>
                    <p>书名:{{bookname}}</p>
                    <p>作者:{{author}}</p>
                    <button @click='show'>按钮</button>
                </div>
            `,
            data(){
                return{
                    bookname:"西游记",
                    author:"老罗"
                }
            },
            methods:{
                show(){
                    console.log(this);
                }
            }
        };
        // 注册组件:全局注册,局部注册
        const app = createApp({
            data(){
                return {
                    num:10
                }
            }
        });
        // 全局注册
        app.component("MyBook",book);
        app.mount("#app")
    </script>
</body>
</html>

11.2、组件中的生命周期

程序中app实例,还有自定义组件,将自定义组件使用在app实例对应的模版上。

app的生命周期与自定义组件的生命周期执行时间:

1、先执行app的初始化(beforeCreate、created)
2、准备挂载app实例,发现app中使用了自定义组件,这时会出发app的生命周期beforeMount
3、在app的beforeMount触发后,开始初始化自定义组件(beforeCreate、created)
4、自定义组件自己的内部数据与自己模版开始进行挂载操作,执行自定义组件生命周期(beforeMount、mounted)
5、执行app实例的mounted生命周期

<body>
    <div id="app">
        <p>num:{{num}}</p>
        <button @click="num++">app组件中的num</button>
        <hr>
        <!-- 使用自定义组件 -->
        <demo></demo>
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp} = Vue;

        // 自定义一个组件(其实就是一个vue的实例)
        const demo = {
            // 通过template配置项书写模版的html结构
            template:`
                <div>
                    <p>count:{{count}}</p>
                    <button @click='count++'>按钮</button>
                </div>
            `,
            data(){
                return{
                   count:0
                }
            },
            beforeCreate() { console.log("demo beforeCreate"); },
            created() { console.log("demo created"); },
            beforeMount() { console.log("demo beforeMount"); },
            mounted() { console.log("demo mounted"); },
            beforeUpdate() { console.log("demo beforeUpdate"); },
            updated() { console.log("demo updated"); },
            beforeUnmount() { console.log("demo beforeUnmount"); },
            Unmounted() { console.log("demo Unmounted"); },
        };
        // 注册组件:全局注册,局部注册
        const app = createApp({
            data(){
                return {
                    num:10,
                    
                }
            },
            beforeCreate(){console.log("app beforeCreate");},
            created() { console.log("app created"); },
            beforeMount() { console.log("app beforeMount"); },
            mounted() { console.log("app mounted"); },
            beforeUpdate() { console.log("app beforeUpdate"); },
            updated() { console.log("app updated"); },
            beforeUnmount() { console.log("app beforeUnmount"); },
            Unmounted() { console.log("app Unmounted"); },
            
        });
        // 全局注册
        app.component("demo", demo);
        app.mount("#app")
    </script>
</body>

11.3、全局注册

在vue3中通过app.component可以给app实例上注册其他的组件,主要注册完成,那么在任意的组件中都可以使用注册在app上的那些组件。

<body>
    <div id="app">
        <!-- 使用自定义组件 -->
        <demo></demo>
        
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp} = Vue;
        const com = {
            template:`<div>
                <h2>我是另外一个组件222</h2>
                </div>`
        }

        const demo = {
            // 通过template配置项书写模版的html结构
            template:`
                <div>
                    <h1>组件1111</h1>
                    <!-- 由于com是全局注册的,所以可以直接在demo自定义组件中使用 -->
                    <com></com>
                </div>
            `,
            
        };
        // 注册组件:全局注册,局部注册
        const app = createApp({
            data(){
                return {
                    num:10,
                }
            },
            
            
        });


        // 全局注册
        app.component("demo", demo);
        app.component("com", com);

        app.mount("#app")
    </script>
</body>

11.4、局部注册

局部注册:将组件注册其他组件的内部,需要在组件内部的配置项中使用components属性进行配置

<body>
    <div id="app">
        <!-- 使用自定义组件 -->
        <abc></abc>
        
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp} = Vue;
        const com = {
            template:`<div>
                <h2>我是另外一个组件222</h2>
                </div>`
        }

        const demo = {
             components: {
                com: com
            },
            template:`
                <div>
                    <h1>组件1111</h1>
                    <com></com>
                </div>
            `,
            
        };

        const app = createApp({
            // 将组件注册在某个实例的内部
            components:{
                abc:demo,
                com:com
            },
            data(){
                return {
                    num:10,
                }
            },
            
            
        });
        app.mount("#app")
    </script>
</body>

11.5、组件中的data函数为什么返回一个对象

组件中的data为一个函数, 目的是可以返回一个对象,返回对象的原因:

组件在每次使用的时候都相当于在调用组件,而data针对每次使用组件是,都给对应的使用位置(组件)返回一个独立的对象

保证组件在多次复用的时候,data中的响应式状态(变量)之间互不影响。

<body>
    <div id="app">
        <demo></demo>
        <hr>
        <demo></demo>
        
    </div>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp} = Vue;

        const demo = {
            template:`
                <div>
                    <h1>组件1111</h1>
                    <p>count:{{count}}</p>
                    <button @click="count++">++</button>
                </div>
            `,
            data(){
                return{
                    count:0
                }
            }
            
        };

        const app = createApp({
            components:{
                demo,
            },
            data(){
                return {
                    num:10,
                }
            },
            
            
        });
        app.mount("#app")
    </script>
</body>

11.6、组件使用的注意事项

  • 可以将组件名的模版单独书写在页面上,在组件中使用template:“选择器”,让组件与模版绑定
  • 不管是在定义组件,还是注册组件,建议采用大驼峰,页面上需要转换为相应等价的 kebab-case (短横线连字符)
<body>
    <div id="app">
        <!-- html是不区分大小写的,因此在Vue3中要求组件对应的标签名,采用所有单词全部小写,中间使用减号隔开 -->
        <my-book></my-book>
    </div>

    <!-- 
        可以使用template标签来定义组件的模版,
        然后在组件中使用template:"选择器"方式,将组件与模版进行绑定 
    -->
    <template id="book">
        <div>
            <p>书名:{{ name }}</p>
            <p>作者:{{ author }}</p>
            <p>价格:{{ price }}</p>
        </div>
    </template>


    <script src="./js/vue3.js"></script>
    <script>
        let { createApp} = Vue;
        // 在定义组件的时候,尽量组件名采用大驼峰
        const book = {
            template:"#book",
            data(){
                return{
                    name:"天龙八部",
                    author:"金庸",
                    price:9.9
                }
            }
        }
        const app = createApp({
            // 局部注册
            components:{ 
                // 注册的时候也建议大驼峰
                MyBook:book
             }
        });
        app.mount("#app")
    </script>
</body>

12、组件的属性

正常书写html的时候,可以给html的标签上添加不同的属性来完成相关的操作(class指定类名、id),比如img标签会绑定src(书写图片的地址),a标签href(书写跳转的链接地址)等等。

如果是自定义组件,在使用的时候,我们可以可以书写属性,

12.1、父子组件

将一个组件注册在另外一个组件中,这时被注册组件属于子组件,书写的注册组件代码components配置项所在的组件属于父组件。

12.2、组件中的props配置项

组件中使用props接收参数:数组方式和对象方式

在使用组件的时候,可以书写属性

<person name="三哥" age="18" sex="不详"></person>

在自定义组件内部可以使用props配置项来接收传递给自定义组件中的数据

const Person = {
    template:"#person",
    // 在自定义组件中使用props配置项来接收传递给组件的数据
    props:["name","age","sex"],
}

数据传递到组件中之后,可以在当前组件中去直接使用

数据传递到自定义组件中之后,可以通过this直接访问传递进来的数据。

<template id="person">
    <div>
        <p>姓名:{{name}}</p>
        <p>年龄:{{age}}</p>
        <p>性别:{{sex}}</p>
    </div>
</template>

12.3、props的约束

在组件中将props书写为对象方式,针对传递的每个属性可以逐一进行约束限制配置

  • type : 用于限定传递的数据类型
  • required:用于限定属性是否必须传递
  • default:属性若没有传递,使用对应的默认值
props:{
    // 只是限定数据类型,可以直接写
    name:String,
    // 复杂限定,必须将接受的属性继续书写成对象
    age:{
        // 传递的age必须是数字类型
        type:Number,
        required:true,
        default:20
    },
    sex:{
        type:String,
         default:'女'
    }
},

<body>
    <div id="app">
        <!-- 
            在使用组件的时候,通过属性给自定义组件中传递数据
         -->
        <person name="三哥" v-bind:age=18 sex="不详"></person>
        <hr>
        <person name="三哥" ></person>
    </div>

    <template id="person">
        <div>
           <p>姓名:{{name}}</p>
           <p>年龄:{{age}}</p>
           <p>性别:{{sex}}</p>
        </div>
    </template>


    <script src="./js/vue3.js"></script>
    <script>
        let { createApp} = Vue;
        const Person = {
            template:"#person",
            // 在自定义组件中使用props配置项来接收传递给组件的数据
            props:{
                // 只是限定数据类型,可以直接写
                name:String,
                // 复杂限定,必须将接受的属性继续书写成对象
                age:{
                    // 传递的age必须是数字类型
                    type:Number,
                    required:true,
                    default:20
                },
                sex:{
                    type:String,
                    default:'女'
                }
            },
            data(){
                return {
                    num:100
                }
            },
            mounted(){
                console.log(this);
                console.log(this.name);
                console.log(this.$props.name);
            }
        }
        const app = createApp({
            // 局部注册
            components:{ 
                // 注册的时候也建议大驼峰
                Person: Person
             }
        });
        app.mount("#app")
    </script>
</body>

12.4、动态属性

针对传递的是数组和对象类型的数据,在验证的时候,默认值建议书写成函数方式

address:{
    type:Array,
        // default:[]
        default(){
            return [11,22,33]
        }
},
// 对象类型
wife:{
  type:Object,
  // 如果接受的是对象或者数组,一般建议将default书写成函数,返回默认的数据
  default:function(){
       return{}
  }
}

同时在使用组件的时候,可以传递动态的数据,需要在标签上使用v-bind进行数据绑定

 <person :name="name" v-bind:age="age" :sex="sex" :address="address" :wife="wife"></person>

<body>
    <div id="app">
        <!-- 
            在使用组件的时候,可以将组件上的这些属性书写为动态的数据,使用v-bind绑定data中的数据或者计算属性的数据
         -->
        <person :name="name" v-bind:age="age" :sex="sex" :address="address" :wife="wife"></person>
       
    </div>

    <template id="person">
        <div>
           <p>姓名:{{name}}</p>
           <p>年龄:{{age}}</p>
           <p>性别:{{sex}}</p>
           <p>住址:{{address}}</p>
           <p>领导:{{wife}}</p>
        </div>
    </template>


    <script src="./js/vue3.js"></script>
    <script>
        let { createApp} = Vue;
        const Person = {
            template:"#person",
            // 在自定义组件中使用props配置项来接收传递给组件的数据
            props:{
                name:[String,Number],
                age:{
                    type:Number,
                    required:true,
                    default:20
                },
                sex:{
                    type:String,
                    default:'女'
                },
                // 数组数据
                address:{
                    type:Array,
                    // default:[]
                    default(){
                        return [11,22,33]
                    }
                },
                // 对象类型
                wife:{
                    type:Object,
                    // 如果接受的是对象或者数组,一般建议将default书写成函数,返回默认的数据
                    default:function(){
                        return{}
                    }
                }
            },
            data(){
                return {
                    num:100
                }
            }
        }
        const app = createApp({
            // 局部注册
            components:{ 
                // 注册的时候也建议大驼峰
                Person: Person
             },
             data(){
                return{
                    name:"李四",
                    age:20,
                    sex:"男",
                    address:["北京","上海","杭州"],
                    wife:{
                        name:"容嬷嬷",
                        age:99
                    }
                }
             }
        });
        app.mount("#app")
    </script>
</body>

12.5、单向数据流

父组件将数据传递给子组件,这时在子组件中可以渲染,也可以作为他的逻辑中使用,但是不能更改父组件传递过来的数据的值。

父组件传递数据给子组件:

  • 传递基本类型数据:这时在子组件中修改数据,会报错(发出警告,不让改)
  • 传递对象类型数据:在子组件中可以修改,因为传递的是引用地址(但是开发中不建议这么做)
<body>
    <div id="app">
        <p>wife:{{wife}}</p>
        <button @click="age++">修改年龄</button>
        <hr>
        <person :age="age" :wife="wife"></person>
    </div>

    <template id="person">
        <div>
            <p>年龄:{{age}}</p>
            <p>wife:{{wife}}</p>
            <button @click="changeAge">自定义组件中修改年龄</button>
            <button @click="changeWifeAge">自定义组件中修改wife年龄</button>
        </div>
    </template>


    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const Person = {
            template: "#person",
            props: ["age","wife"],
            methods: {
                changeAge() {
                    console.log("自定义的changeAge函数执行");
                    this.age++;
                },
                // 在自定义组件中修改传递过来的对象中的数据,是可以改,但不建议
                changeWifeAge(){
                    console.log("自定义的changeWifeAge函数执行");
                    this.wife.age++;
                }
            }
        }
        const app = createApp({
            components: {
                Person: Person
            },
            data() {
                return {
                    age: 20,
                    wife:{
                        name:"灭绝师太",
                        age:100
                    }
                }
            }
        });
        app.mount("#app")
    </script>
</body>

12.6、事件

<body>
    <div id="app">
        <button @click="change">++</button>
        <!-- 
            使用自定义组件,给其绑定一个自定义的事件,事件对应的了一个自定义的函数
            希望自定义的函数可以被调用
            必须想办法让自定义的事件被触发,那么对应的事件处理函数才会被调用
         -->
        <person :age="age" @update-age="demo"></person>
    </div>

    <template id="person">
        <div>
            <p>年龄:{{age}}</p>
            <button @click="changeAge">自定义组件中修改年龄</button>
        </div>
    </template>


    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const Person = {
            template: "#person",
            // 在Vue2和Vue3中,自定义中接受属性数据,使用props配置项
            props: ["age"],
            // 在Vue3中,如果要接受自定义事件,可以使用emits配置项
            emits:["updateAge"],
            // created(){
            //     console.log(this);
            // },
            methods: {
                changeAge() {
                    console.log("自定义的changeAge函数执行");
                    // 在自定义组件中激活父组件中自定义的事件
                    this.$emit("updateAge","给父组件传递数据123213213123");
                },
            }
        }
        const app = createApp({
            components: {
                Person: Person
            },
            data() {
                return {
                    age: 10,
                }
            },
            methods:{
                demo(val){
                    console.log("app demo...",val);
                },
                change(){
                    console.log("app change");
                }
            }
        });
        app.mount("#app")
    </script>
</body>

13、组件之间通信

组件之间的数据传递:父传子、子传父、乱传(组件之间没有直接关系eventbus)

13.1、父传子

父组件将数据传递给子组件:props

<body>
    <div id="app">
        <demo :name="name" :age="age"></demo>
    </div>
    <template id="demo">
        <!-- 
            vue3中组件可以书写多个根标签,
            vue2中要求组件只能有一个根标签 
        -->
        <p>name:{{name}}</p>
        <p>age:{{age}}</p>
    </template>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const demo = {
            template: '#demo',
            props:["name","age"]
        }

        const app = createApp({
            components: { demo },
            data(){
                return{
                    name:"隔壁老王",
                    age:12
                }
            }
        });
        app.mount("#app")
    </script>
</body>

13.2、子传父

默认情况下,子组件是无法直接将数据交给父组件的,必须让父组件提供事件,然后子组件通过$emit触发父组件的事件,进而传递数据给父组件。

<body>
    <div id="app">
        <h3>父组件接收到的数据:{{ msg }}</h3>
        <hr>
        <demo :name="name" :age="age" @translate="getMsg"></demo>
    </div>

    <template id="demo">
        <p>name:{{name}}</p>
        <p>age:{{age}}</p>
        <button @click="setMsg">传递数据给父组件</button>
    </template>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const demo = {
            template: '#demo',
            props:["name","age"],
            emits:["translate"],
            data(){
                return {
                    message:"数据需要给父组件"
                }
            },
            methods:{
                setMsg(){
                    console.log("子组件的setMsg执行");
                    this.$emit("translate",this.message);
                }
            }            
        }

        const app = createApp({
            components: { demo },
            data(){
                return{
                    msg:"",
                    name:"隔壁老王",
                    age:12
                }
            },
            methods:{
                // getMsg是父组件中自定义事件被触发之后才会被执行的处理函数
                getMsg(val){
                    console.log("getMsg" , val);
                    this.msg = val;
                }
            }
        });
        app.mount("#app")
    </script>
</body>

13.3、 r o o t 和 root和 rootparent

<body>
    <div id="app">
        <h1>爷爷组件</h1>
        <hr>
        <father></father>
    </div>

    <template id="father">
        <div>
            <h2>爸爸组件</h2>
            <child></child>
        </div>
    </template>

    <template id="child">
        <h3>孩子组件</h3>
    </template>

    <script src="./js/vue3.js"></script>
    <script>


        let { createApp } = Vue;

        const Child = {
            template: "#child",
            mounted(){
               console.log(this.$root); 
               console.log(this.$root.a); 
               // 在后代组件中,通过$root找到根组件,直接使用根组件中的变量或者函数    
               this.$root.aa();

                /
                console.log("===".repeat(20));
                console.log(this.$parent);
                console.log(this.$parent.b);
                this.$parent.b = 100;
            }
        }

        const Father = {
            template: "#father",
            components: { Child },
            data() {
                return {
                    b: 1,
                }
            },
            watch:{
                b(newVal){
                    console.log("newVal",newVal);
                }
            }
        }


        const app = createApp({
            components: { Father },
            data(){
                return{
                    a:1,
                }
            },
            methods:{
                aa(){
                    console.log("app 实例中的aa是");
                }
            }

        });
        app.mount("#app")
    </script>
</body>

13.4、透传属性 attribute

13.4.1、$attrs属性

在父组件中使用子组件,通过标签上书写属性(id、class、自定义属性、事件)都会传递给子组件对象中的$attrs对象中。

<father id="com"  class="box"  name="zs" age="19" sex="" @change-name="setName"></father>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENerjW0V-1686296270822)(课堂笔记.assets/image-20230609113015507.png)]

如果在子组件中使用props配置项声明接收那些父传子的属性,那么这些属性就不会在$attrs中出现。

$attrs : 它接收的父传子的属性,没有被props和emits接收的剩余属性。

13.4.2、透传属性

当父组件中使用子组件,传递的属性,这时在子组件中没有使用props、emits去接受这些属性或者事件,那么属性数据会被传递给子组件的根标签上。

由于Vue3允许一个组件同时拥有多个根标签,这时透传失效。因此建议定义组件的时候,给标签最外层包裹一个根标签。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            /* 定位、布局、flex、内外边距 */
            /* 盒子相关属性 */
            /* 文字样式 */
            /* 文本样式 */
            /* 其他样式:伪类、伪元素 */
        }
    </style>
</head>

<body>
    <div id="app">
        <h3>app</h3>
        <!-- 使用自定义组件 -->
        <father id="com"  class="box"   style="background-color: #fa0;" name="zs" age="19" sex="" @change-name="setName"></father>
    </div>

    <template id="father">
        <div class="father">
            <h4>自定义组件</h4>
        </div>
        <!-- <div class="father">
            <h4>自定义组件</h4>
        </div> -->
    </template>


    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;


        const Father = {
            template: "#father",
            props:["name","age"],
            emits:['changeName'],
            created(){
                console.log(this.$attrs);
                console.log(this.$attrs.id);
                console.log(this.$attrs.class);
                console.log(this.$attrs.sex);
                this.$attrs.sex = '女'
            }
        }


        const app = createApp({
            components: { Father },
            data(){
                return{
                    a:1,
                }
            },
            methods:{
                setName(){}
            }

        });
        app.mount("#app")
    </script>
</body>

</html>

13.5、$refs属性

在模版(html)中,可以给标签或者组件上绑定ref属性,目的是在vue的实例中,可以动态去获取dom对象,或者组件对象。

<body>
    <div id="app">
        <h3 ref="title">app</h3>
        <father ref="son"></father>
    </div>

    <template id="father">
        <div class="father">
            <h4>自定义组件</h4>
        </div>
    </template>


    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;


        const Father = {
            template: "#father",
            data(){
                return{
                    aa:11,
                    bb:22
                }
            },
            methods:{
                demo(){
                    console.log(".... demo .....");
                }
            },
            watch:{
                aa(newVal){
                    console.log("newVal",newVal);
                }
            }
        }

        const app = createApp({
            components: { Father },
            mounted(){
                // 在dom中可以通过document.querySelector("") 获取页面上的标签
                // vue不建议直接操作dom
                // vue中又提供的一个获取dom对象的方式:ref属性
                console.log("document.querySelector()", document.querySelector("h3"));
                console.log("this.$refs.title", this.$refs.title);
                // 通过ref属性配合vue中提供的$refs可以获取到当前使用的某个子组件对象
                console.log(this.$refs.son);
                console.log(this.$refs.son.aa);
                console.log(this.$refs.son.bb);
                this.$refs.son.demo();
                this.$refs.son.aa = 1000;
            }           
        });
        app.mount("#app")
    </script>
</body>

13.6、非父子组件传递

13.6.1、兄弟组件(状态提升)

兄弟组件,他们会有共同的父组件,这时可以采用:

A兄弟通过子传父,将数据交给父组件,然后父组件再通过父传子将数据交给B兄弟。这种操作,称为变量状态的提升。

<body>
    <div id="app">
        <h1>父组件</h1>
        <p>{{ obj }}</p>
        <hr>
        <!-- 使用第一个子组件 -->
        <child @trans-data="getData"></child>
        <hr>
        <!-- 使用第二个子组件 -->
        <son :obj="obj"></son>
    </div>
    <!-- 第一个子组件 -->
    <template id="child">
        <button @click="$emit('transData',person)">将数据交给父组件</button>
        <button @click="person.age++">修改年龄</button>
    </template>
    <!-- 第二个子组件 -->
    <template id="son">
        <p>name:{{obj.name}}</p>
        <p>age:{{obj.age}}</p>
    </template>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const Child = {
            template: '#child',
            emits:['transData'],
            data() { 
                return{
                    person:{
                        name:"张三",
                        age:20
                    }
                }
            },
        }
        const Son = {
            template: '#son',
            props:['obj']
            
        }
        const app = createApp({
            components: { Child,Son },
            data(){
                return{
                    obj:{},
                }
            },
            methods:{
                getData(val){
                    console.log("父组件接到数据:",val);
                    this.obj = val;
                }
            }
        });
        app.mount("#app")
    </script>
</body>

13.6.2、任意组件(vue2)

创建一个对象,用来存放需要多个组件共享的数据,然后任意组件可以去设置获取这个值。

在Vue2中,建议创建一个Vue的实例对象,然后使用其中 o n 、 on、 onemit、$off 三个属性完成数据共享获取操作。这个操作称为EventBus(事件总线,乱传)。

<body>
    <div id="app">
        <one></one>
        <two></two>
    </div>
    <template id="one">
        <div>
            <h3>one组件</h3>
            <button @click="giveData">给其他组件数据</button>
        </div>
    </template>
    <template id="two">
        <h3>two组件:::{{message}}</h3>
       
    </template>
    <script src="./js/vue2.js"></script>
    <script>
        // 需要一个EventBus
        const $BUS = new Vue();


        // one组件
        const one = {
            template: '#one',
            data(){
                return{
                    msg:"one中的数据"
                }
            },
            methods:{
                giveData(){
                    $BUS.$emit("getData",this.msg);
                }
            }
        }
        // two组件
        const two = {
            template: '#two',
            data(){
                return{
                    message:""
                }
            },
            created(){
                // 给EventBus中注册一个自定义的事件
                // console.log($BUS);
                $BUS.$on("getData",(val)=>{
                    console.log("val",val);
                    this.message = val;
                })
            }
        }
        // 根组件
        const app = new Vue({
            components: { one,two }
        });

        app.$mount("#app")
    </script>
</body>

13.6.3、任意组件(vue3)

Vue3不能去创建Vue的实例,主要原因是其中没有$on属性方法了。

// 定义一个事件总线的类
// 核心的方法:
// 1)、绑定事件:可以绑定多个事件
// 2)、触发事件
class EventBus{
    constructor(){
        // 定义一个属性,保存所有绑定的事件:
        this.events = {}
    }
    // 绑定事件:
    subscribe(type,callback){
        if(!this.events[type]){
            this.events[type] = [];
        }
        this.events[type].push(callback);
    }
    // 触发事件
    publish(type,...args){
        this.events[type].forEach(item => {
            item(...args);
        });
    }
}

13.6.4、依赖注入(provide和inject)

13.7、组件通信方式总结

父传子:

  • props属性
  • $parent
  • $root

子传父:

  • this.$emit(“” , 数据) ; 子组件激活父组件中的自定义事件,将数据传递给自定义事件对应处理函数
  • $refs : 允许在父组件中获取子组件对象,进而拿到子组件中的数据或者函数等(尽量少用)

# 14、插槽

14.1、什么是插槽

插槽:挖坑与填坑过程。在自定义组件的时候,可以使用slot标签,在自定义组件的模版中预留插槽(挖坑),然后在父组件中使用自定义,可以在使用自定义的标签之间插入数据(文本,子标签等)填坑。

14.2、默认插槽

在自定义组件的模版中使用slot标签预留插槽位置,这时如果没有给slot命名,那么slot称为默认插槽

<body>
    <div id="app">
        <h2>根组件app</h2>
        <hr>
        <!-- 使用自定义组件 -->
        <!-- <demo /> -->
        <hr>
        <demo>
            <!-- 给预留的插槽中插入数据 -->
            <h4>我的自定义标签中的文本数据</h4>
        </demo>
    </div>



    <!-- 自定义组件模版 -->
    <template id="demo">
        <div>
            <h3>自定义组件</h3>
            <!-- 预留插槽 -->
            <slot></slot>
        </div>
    </template>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const demo = {
            template: '#demo',
        }
        const app = createApp({
            components: { demo }
        });
        app.mount("#app")
    </script>
</body>

14.3、具名插槽

在定义插槽的时候,可以给slot标签上使用name属性进行插槽命名,在使用自定义组件的时候,需要指定将数据插入到具体哪个插槽中,使用v-slot:插槽名,在vue3中要求插入到指定插槽的数据必须使用template标签包裹,v-slot可以简化为#

<body>
    <div id="app">
        <h2>根组件app</h2>
        <hr>
        <!-- 使用自定义组件 -->
        <!-- <demo /> -->
        <hr>
        <demo>
            <!-- 给预留的插槽中插入数据 -->
             <template v-slot:p1>
                <h3 >张三,23</h3>
             </template>
             <!-- vue3中如果指定数据插入到具体某个名称的插槽时,可以将v-slot简化为#号 -->
             <template #p2>
                 <h3>李四,33</h3>
             </template>
             <h3>默认数据</h3>
        </demo>
    </div>
    <!-- 自定义组件模版 -->
    <template id="demo">
        <div>
            <h3>自定义组件</h3>
            <!-- 在定义插槽的时候,使用name属性,给这个插槽命名 -->
            <hr color="blue">
            <slot name="p1"></slot>
            <hr color="deeppink" size="10px">
            <slot name="p2"></slot>
            <hr color="red">
            <slot></slot>
            <hr color="green">
            <h3>22222222222222222</h3>
        </div>
    </template>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const demo = {
            template: '#demo',
        }
        const app = createApp({
            components: { demo }
        });
        app.mount("#app")
    </script>
</body>

14.4、作用域插槽

在自定义组件中预留插槽的时候,可以在slot标签上自定义属性,并携带数据,然后在只使用组件的时候,可以通过v-slot="变量名"接收插槽slot上的所有数据,在当前的组件标签内部可以使用slot标签上的属性数据。

<body>
    <div id="app">
        <h2>根组件app</h2>
        <hr>
        <demo>
            <template v-slot="book">
                <p>插入插槽的数据</p>
                <p>{{book.bookname}}</p>
                <p>{{book.price}}</p>
                <p>{{book.author}}</p>
           </template>
            
        </demo>
    </div>
    <!-- 自定义组件模版 -->
    <template id="demo">
        <div>
            <h3>自定义组件</h3>
            <slot bookname="天龙九部" price="1.8" author="班长"></slot>
        </div>
    </template>
    <script src="./js/vue3.js"></script>
    <script>
        let { createApp } = Vue;
        const demo = {
            template: '#demo',
            props:['name','age']
        }
        const app = createApp({
            components: { demo }
        });
        app.mount("#app")
    </script>
</body>

14.5、$slots

15、脚手架

15.1、vue/cli

15.2、vite

15.3、单页面应用

创建的Vue项目, 其中的每个.vue文件,基本都会由(template、script、style)三部分组成(单页面)

单页面应用:SPA:single page application,单页面应用。

​ 就是整个项目就只有一个html页面(文件),首次加载时,把所有的html,css,js全部加载下来。通过操作dom的删除和创建(添加)来完成页面的切换。

单页面应用优缺点

优点:

1,局部刷新,所以,用户体验好。
2,前后端分离
3,页面效果会比较炫酷(比如切换页面内容时的转场动画)

缺点:
1,不利于seo
2,导航不可用,如果一定要导航需要自行实现前进、后退。
3,初次加载时耗时多
4,页面复杂度提高很多

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值