Vue学习笔记

406 阅读25分钟

前言

Vue是什么?

Vue是一套用于构建用户界面渐进式JavaScript框架

渐进式:Vue可以自底向上逐层应用,它的核心库轻量,只关心图层,便于与第三方库或既有项目整合

  • 应对简单应用:只需一个轻量小巧的核心库
  • 应对复杂应用:可以引入Vue插件

Vue作者 —— 尤雨溪

尤雨溪知乎 尤雨溪微博 尤雨溪Github

Vue特点

1、组件化

网页的每个组件模块都是一个vue文件,其中包含了该组件的html、css、js文件,这种模式提高了代码复用率,便于维护和别处引用

在这里插入图片描述

2、声明式编码

编码人员无需直接操作DOM,提高了了开发效率

3、使用虚拟DOM和优秀的Diff算法,尽量复用DOM节点

Vue官方库

用于使用官方提供的组件实现某些功能 在这里插入图片描述 Awesome-Vue -- Github 和Vue相关的包

如何安装Vue

Vue安装


一、基础

钩子函数

1.1、模板语法

1.1.1、插值语法{{xxx}}

  • 语法{{xxx}}、xxx可以是JS表达式,也可以是变量名或属性名,且可以读取到data中的所有区域

  • 功能:==解析标签体内容==

  • 注意:容器与Vue实例之间是严格的一一对应关系

绑定Vue对象中的data数据,从而可以通过data动态修改HTML设置的插值表达式中变量的值.插值表达式中允许用户输入JS代码片段

下面<div>容器内的代码又称Vue模板,其中的代码符合HTML规范的同时,加入了一些Vue语法

<body>
	<div id="root">
		<!--通过vue动态修改-->
		<!--若{{}}传入内传入JS表达式,则会输出其返回值-->
        <h1>Hello {{name}},{{age}},{{1+0}},{{Date.now()}}</h1>
    </div>
</body>

必须创建一个Vue实例并传入一个配置对象,才能让Vue工作

<script>
	//创建Vue实例
	new Vue({//只传一个参数:配置对象
	   el: '#root',//el即element,用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
	   //id选择器用#,类选择器用.,以此类推
	   //这里还有一种写法:el:document.getElementById("root");
	
	   data: {
	   	 name:'Vue',
	   	 age: 18
	   },
	})
</script>

在这里插入图片描述


1.1.2、指令语法v-xxx

  • 语法:v-???,以v-bind为例,<a v-bind:href="xxx">或是<a :href="xxx">v-bind简写为:),xxxJS表达式,可以直接读取到data中的所有属性
  • 功能:==解析标签==,包括标签属性、标签体内容、绑定事件等
  • 注意:Vue中,指令的形式都是v-开头

1.1.2.1、v-bind: —— 单向数据绑定

作用:响应式地更新 HTML 属性,给html标签中任何属性动态绑定

下面演示中,v-bind指令将<a>元素的href属性与url值绑定,从而实现动态改变该超链接

<h2>指令语法</h2>

<a id="link1" v-bind:href="url">点击前往Vue文档</a>
<!--或者简写为-->
<a id="link1" :href="url">点击前往Vue文档</a>
<!--将url绑定给href-->

<script>
	new Vue({
		el:"link1",
		data:{
			url:'https://v2.cn.vuejs.org/v2/guide/instance.html',
		}
	})
</script>

在这里插入图片描述

1.2、数据绑定

1.2.1、单向数据绑定 - 使用v-bind:

数据只能从data流向页面,即:可以通过改变data而改变页面上对应部分的值,但不能通过改变页面的某部分值来改变其对应的data

单向数据绑定<input type="text" :value="name">

1.2.2、双向数据绑定 - 使用v-model

v-model只能作用域表单类元素(输入类元素)上,它们都有 value

v-model的三个修饰符

  • lazy:当失去焦点时再收集数据
  • number:输入字符串转化为有效的数字
  • trim:输入首尾空格过滤

数据在data页面之间的流动是双向的,可以通过其中任意一方而影响另外一方

双向数据绑定<input type="text" v-model:value="name">
//v-model简写如下,即删掉:value
双向数据绑定<input type="text" v-model="name">
    <div id="root">
        单向数据绑定<input type="text" :value="name">
        <br>
        双向数据绑定<input type="text" v-model:value="name">
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        new Vue({//单向数据绑定和双向数据绑定都依赖这里的name
            el:'#root',
            data:{
                name:"jack"
            }
        })
    </script>

在这里插入图片描述

1.1.3、简写

  • v-bind:可以简写为:
  • v-on:可以简写为@
  • v-model:value=“xxx”可以简写为v-model=xxx
  • 动态参数缩写:v-on:event可以缩写为@[event]

1.2、el 与 data 的两种写法

==原则:== 由Vue管理的函数(比如data),不应当写为=>函数,否则this就不再是Vue实例

1.2.1、el 的两种写法

  • el:'xxx'
  • vm.$mount('xxx');
    <div id="root">
        <h1>你好, {{name}}</h1>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#root', //第一种写法
            data:{
                name:"jack"
            }
        })

        vm.$mount('#root');//第二种写法
    </script>

第二种写法的实际应用

setTimeout(()=>{
	v.$mount('root');
},2000);

可以发现第二种方法更加灵活

1.2.2、data的两种写法

  • 对象式——data:{}
  • 函数式——data:function(){ return{} },函数式必须返回一个对象,在组件中必须使用函数式
             //data的第二种写法:函数式
             
             data:function(){//普通函数
                console.log(this);//Vue

                return{
                    name:'jack'
                }
             }

             data:()=>{// =>函数
                console.log(this);//window
                
                return{
                    name:'rory'
                }
             }

一般不使用data的=>函数写法,而是使用简化的普通函数写法

data(){
	return{
		name:'jack'
	}
}

==在组件中,data必须使用函数式,否则会报错==

1.3、MVVM模型

释义

1、M:Model——模型,对应data中的数据 2、V:View——视图,对应模板 3、VM:ViewModel——视图模型,对应Vue实例对象 在这里插入图片描述

4、data中所有的属性,最后都会出现在vm上 5、vm上所有的属性以及Vue原型上所有的属性,在Vue模板中都可以直接使用

1.4、Object.defineProperty()方法

为对象添属性和对应的值,该方法添加的属性和值默认不可以被枚举、遍历 ,可以通过设置enumerable的值使其可以枚举

Object.defineProperty()方法的配置项

  • enumerable:控制属性是否可以被枚举,默认值是false
  • writable:控制属性是否可以被修改,默认值为false
  • configurable:属性是否可以被删除,默认值为fasle
  • get:function(){}:一般简称为getter,简写为get(){},当读取由Object.defineProperty()方法添加的属性时,get函数就会被调用,且返回值就是Object.defineProperty()方法添加的属性的值
  • set:function(value){}:一般简称为setter,简写为set(value){},当修改由Object.defineProperty()方法添加的属性时,set函数就会被调用,且会受到修改的具体的值
//Object.defineProperty()为对象添加属性,传入三个参数
Object.defineProperty(对象, 添加的属性名, 配置数据)

//举例,之后举例各配置项时省略此obj
var obj = {
	name:'jack',
	sex:'male'
}

Object.defineProperty(obj,'age',{
	value:19
})
for(let x in obj){
	console.log(x);
}

在这里插入图片描述

修改enumerabletrue后,Object.defineProperty()方法添加的属性可以被遍历

Object.defineProperty(obj,'age',{
	value:19enumerable:true
})

在这里插入图片描述 设置getter,便于读取并改变Object.defineProperty()方法添加的属性

        let number = 18;
        Object.defineProperty(obj,'age',{
            

            get: function() {
                console.log('有人读取了age属性值');
                return number;
            }
        })

在这里插入图片描述 设置setter,便于修改Object.defineProperty()方法添加的属性的值

        let number = 18;
        Object.defineProperty(obj,'age',{
            set(value){
                console.log('有人修改了age属性值,且修改后的值是'+  value);
                number = value;
            }
        })

在这里插入图片描述

1.5、数据代理

定义:通过一个对象代理对另一个对象中属性的操作(读 / 写),被配置在data中的东西才会做数据劫持和数据代理

举例:通过vm对象来代理data对象中属性的操作。vm将data对象中的属性存在了vm._data中,所以可以通过vm._data实现对data对象中属性的操作

优点:可以更加方便地操作data对象中的数据

实现原理:

  • 通过object.defineProperty()把data对象中所有属性添加到vm上
  • 每一个添加到vm上的属性,都会被设置一个gettersetter
  • 通过gettersetter在内部去操作data中对应的属性

在这里插入图片描述

一个简单的数据代理

        let obj1 = {
            x:100
        };

        let obj2 = {
            y:200
        };

        Object.defineProperty(obj2,'x',{//通过obj2操作obj1中的X属性
            get: function() {
                return obj1.x;
            },

            set: function(value) {
                obj1.x = value;
            }
        })

使用vm修改data内属性的实例

    <div id="role-info">
        <h1>角色: {{name}}</h1>
        <h1>年龄:  {{age}}</h1>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示


        let data = {
            name: "jack",
            age: 18
        };

        const vm = new Vue({
            el:'#role-info',
            data: data//在外定义data再引入钩子函数,避开数据劫持
                //这里的data相当于vm._data
        })
    </script>

在这里插入图片描述

1.6、事件处理

1.6.1、定义及方法

定义:

方法: 1、使用v-on:event@event绑定事件(以下均使用@简写),例如:v-on:click 绑定一个鼠标点击事件 2、为绑定的事件绑定回调函数:@click="functionName(parameter1,,parameter2,……)",根据需要传参,若不传参,则函数名后不加()。使用占位符$传入event,即$event,可以传入event对象

<button @click="showInfo2(55,$event)">点我在控制台输出传入的参数</button>

<!--下面是不传参的写法-->
<button @click="showInfo2">点我在控制台输出传入的参数</button>

3、事件的回调函数配置在methods对象中,其最终会在vm上。methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象

4、methods对象中配置的函数,不可以=>函数

    <div id="info">
        <h1>欢迎来到{{name}}</h1>
        <button v-on:click="showInfo1">点击提示信息</button>
        <button @click="showInfo2(55,$event)">点我在控制台输出传入的参数</button>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#info',
            data: {
                name: '德莱联盟',
            },
            methods:{
                showInfo1(event){
                    //此处的this是vm
                    alert('欢迎来到德莱联盟~');
                },

                showInfo2(number,event){
                    console.log(number,event);
                }
            }
        })
    </script>

在这里插入图片描述

1.6.2、事件修饰符

修改事件触发时的行为

格式:

  • 事件绑定时:@event.修饰符="fn"
  • 事件回调函数中调用相关API

Vue中的事件修饰符

  • prevent:阻止默认事件
  • stop:阻止事件冒泡
  • once:事件只触发一次
  • capture:使用事件的捕获模式
  • self:只有event.target是当前操作的元素时才触发事件
  • passive:事件的默认行为为立即执行,不需要等待事件回到执行完毕

一个事件可以赋予多个修饰符,比如: @mouseover.prevent.stop.once="fn",设置的修饰符顺序会影响起作用的顺序

演示如下

    <style>
        *{
            margin-top: 20px;
        }
    </style>
  • prevent修饰符演示
    <div id="info">
        <h1>{{name}}示例</h1>
        <!--阻止默认事件(常用)-->
        <a href="https://blog.csdn.net/liushi21?spm=1000.2115.3001.5343" @click.prevent="showInfo">prevent修饰符阻止超链接的自动跳转</a>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#info',
            data: {
                name: '事件修饰符',
            },
            methods:{
                showInfo(e){
                    alert('欢迎来到我的博客~~');
                }
            }
        })
    </script>

在这里插入图片描述

  • stop修饰符演示
    <div id="info">
        <h1>{{name}}示例</h1>
         <!--阻止事件冒泡(常用)-->
        <div class="demo1" style="height: 60px; width:500px; background-color: rgb(163, 163, 228);" @click="showInfo">
            <button @click.stop="showInfo">包含我的div也设置了点击事件,点击我体验stop修饰符阻止事件冒泡</button>
        </div>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#info',
            data: {
                name: '事件修饰符',
            },
            methods:{
                showInfo(e){
                    alert('欢迎来到我的博客~~');
                }
            }
        })
    </script>

在这里插入图片描述

  • once修饰符演示
    <div id="info">
        <h1>{{name}}示例</h1>
        <!--事件只触发一次-->
        <button @click.once="showInfo">事件只触发一次,该button触发一次后再次点击就会无效</button>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#info',
            data: {
                name: '事件修饰符',
            },
            methods:{
                showInfo(e){
                    alert('欢迎来到我的博客~~');
                }
            }
        })
    </script>

在这里插入图片描述

  • capture修饰符演示
    <div id="info">
        <h1>{{name}}示例</h1>
        <!--使用事件捕获模式-->
        <div @click.capture="showMsg(1)" style="background-color: skyblue; padding: 5px;">
            div1 - 在捕获阶段处理事件
            <div @click="showMsg(2)" style="background-color: rgb(253, 149, 105); padding: -5px;">
                div2
            </div>
        </div>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#info',
            data: {
                name: '事件修饰符',
            },
            methods:{
                showMsg(Msg) {
                    console.log(Msg);
                }
            }
        })
    </script>

在这里插入图片描述

  • self修饰符演示
    <div id="info">
        <h1>{{name}}示例</h1>
        <!--只有event.target是当前操作的元素时才触发事件-->
        <div @click.self="showInfo_1" style="background-color: rgb(18, 179, 243); padding: 5px;">
            <button @click="showInfo_1">event.target是当前操作的元素时才触发事件</button>
        </div>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#info',
            data: {
                name: '事件修饰符',
            },
            methods:{
                showInfo_1(e){
                    console.log(e.target);
                }
            }
        })
    </script>

在这里插入图片描述

  • passive修饰符演示
    <div id="info">
        <h1>{{name}}示例</h1>
        <!--事件的默认行为为立即执行,不需要等待事件回到执行完毕-->
        <h3>passive事件的默认行为为立即执行对比示例</h3>
        <ul @wheel.passive="demo" style="background-color: rgb(233, 187, 141);width: 60px; height: 200px;overflow: auto;">
            <!--scoll是滚动条滚动(键盘上下键或者鼠标滚轮或者拖动),wheel是鼠标滚轮滚动或者拖动-->
            <li>1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
            <li>6</li>
            <li>7</li>
            <li>8</li>
            <li>9</li>
            <li>10</li>
        </ul>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#info',
            data: {
                name: '事件修饰符',
            },
            methods:{
                demo(){
                    for (let i = 0; i < 100000; i++){
                        console.log('正在滚动');
                    }
                    //鼠标滚轮滚动触发上面循环,上面循环执行完后页面滚轮才会滚动一点并触发下面代码,这会造成卡顿

                    console.log('到底了,滚不动了');
                }
            }
        })
    </script>

-- 添加passive,可以发现很卡 在这里插入图片描述 -- 添加passive,可以发现卡顿现象得到了好转,但是在关闭页面时仍然很卡,说明passive存在局限,它只是能在一定程度上进行优化,但不能从根源上解决卡顿的问题,在移动端上的项目,常用到passive 在这里插入图片描述


1.6.3、键盘事件

1、Vue中常用的按键别名

Vue中常用按键别名
回车Enter
删除Delete(退格键 或者 删除键)
退出Esc
空格Space
换行Tab(必须使用 keydown 绑定)
Up
Down
Left
Right

当用名字为组合词的按键绑定事件时,键的名字应该拆分、都变为小写,并用-连接,才会产生想要的效果,比如大小写切换键CapsLk在绑定事件时应该写为caps-lock

2、Vue未提供别名的按键,可以使用按键原始的key值去绑定,但要注意转为kebab-case

3、系统修饰键(用法特殊):CtrlAltShiftMeta(也即Win键)

上面四个特殊

  • 配合keyup使用: 效果为按下修饰键同时,再按下其他键,随后释放其他键,事件才会触发
  • 配合keydown使用: 效果为正常触发事件,二者同时按下就会触发
<input type="text" placeholder="按下ctrl+c并松开触发" @keyup.ctrl.c="showInfo">

4、指定具体的按键还可以使用keyCode(已废弃)

5、定制按键别名:Vue.config.keyCodes.自定义键名 = 键码,然后就可以使用自定义键名去绑定

   <div id="root">
        <h2>键盘事件</h2>
        <input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo">
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        new Vue({
            el:"#root",
            methods: {
                showInform(e) {
                    console.log(e.target.value);
                }
            }
        })
    </script>

1.7、计算属性

1、定义:本身不存在,而是通过已有的属性计算得来(可随网页操作动态变化) 2、原理:底层借助了Object.defineproperty()方法提供的gettersetter 3、get函数和set函数执行的时间详见下面示例代码的注释 4、优势:与methods实现相比,内部有缓存机制(复用),效率高,调试方便 5、注意:

  • 计算属性最终会出现在vm上,所以直接读取使用即可,例如直接读取使用计算属性fullInfo:{{fullInfo}}
  • 修改计算属性必须通过set函数去响应修改,且set中要引起计算时依赖的属性发生改变
    <div id="root">
        账户: <input type="text" v-model="userName"></br>
        密码: <input type="text" v-model="passwords"></br>
        <h2>用户账号完整信息: {{fullinfo}}</h2>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:"#root",
            data: {
                userName: 'userName',
                passwords: 'passwords',
            },

            //计算属性
            computed: {//计算属性存在缓存,而methods不存在缓存
                fullinfo: {
                    //get作用:当有人读取fullInfo时,get就会被调用,且返回值就作为fullName的值
                    //get调用的时机:1、初次读取计算数据时,2、计算属性所依赖的数据发生变化时
                    get() {//this = Vue
                        console.log('get被调用');
                        return this.userName + '-' + this.passwords;
                    },

                    //set作用,当fullInfo被修改时,
                    set(value) {
                        const arr = value.split('-');
                        this.userName = arr[0];
                        this.password = arr[1];
                    }
                }

            }
        })
    </script>

在这里插入图片描述

1.7.1、计算属性的简写

只考虑读取而不考虑修改时,才能使用简写

            //计算属性
            computed: {//计算属性存在缓存,而methods不存在缓存
                fullinfo: function(){
                    //只读不改时才能使用简写
                    console.log('get被调用了');
                    return this.userName +'-'+this.passwords; 

                }
            }
        })

1.8、监视属性

监视属性 - watch

1、监视某属性的变化,监视属性发生变化时自动调用回调函数

2、监视的属性必须存在才能进行监视

3、监视的两种方法

  • new Vue时引入watch配置:适用于创建vm时监视的对象已经确定
  • 通过vm.$watch监视

new Vue时引入watch配置

    <div id="root">
        <h2>天气好热啊, 想去{{info}}</h2>
        <button @click="changeTodo">切换todo</button>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:"#root",
            data:{ 
                ischange:true
            },
            computed:{//计算属性
                info(){
                    return this.ischange ? '游泳' : '打游戏' 
                }
            },
            methods:{
				changeTodo(){
					this.ischange = !this.ischange
				}
			},
            watch:{//监视方法一,适用于创建vm时监视的对象已经确定
                info:{
                    //当ischange发生改变时,调用hander函数
                    handler(newValue, oldValue){
                        //oldValue 和 newValue是对应的是watch所监视的info的旧值和新值
                        console.log('info被修改了' ,oldValue, newValue);
                    }
                }
            }
        })
    </script>

通过vm.$watch监视(更灵活)

    <div id="root">
        <h2>天气好热啊, 想去{{info}}</h2>
        <button @click="changeTodo">切换todo</button>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:"#root",
            data:{ 
                ischange:true
            },
            computed:{//计算属性
                info(){
                    return this.ischange ? '游泳' : '打游戏' 
                }
            },
            methods:{
				changeTodo(){
					this.ischange = !this.ischange
				}
			}
        })

        //监视方法二,适用于创建vm时监视的对象不确定,需要后续根据用户的行为进行监视
        vm.$watch('ischange',{
            handler(newValue, oldValue){
                //oldValue 和 newValue是对应的是watch所监视的info的旧值和新值
                console.log('info被修改了' ,oldValue, newValue);
            }
        })
    </script>

设置配置项immediate为true,可以在初始化时让监视属性中设置的回调函数调用一次

    watch:{
        info:{
        	immediate:true,//
            //当ischange发生改变时,调用hander函数
            handler(newValue, oldValue){
                //oldValue 和 newValue是对应的是watch所监视的info的旧值和新值
                console.log('info被修改了' ,oldValue, newValue);
            }
        }
    }

在这里插入图片描述

1.8.1、深度监视

监视多级结构中属性的变化

1、监视多级结构中某个属性的变化:'对象名.属性名' - 将其写在字符串里

        const vm = new Vue({
            el:"#root",
            data:{ 
                ischange:true,
                numbers:{//注意这里numbers中存放的是num1和num2的地址,而不是他们的值
                    num1:1,
                    num2:2
                }
            },
            watch:{
                'numbers.num1':{
                    handler(){
                        console.log('num1发生了变化');
                    }
                }
            }
        })

2、监视多级结构中所有属性的变化,设置配置项deep:true

        const vm = new Vue({
            el:"#root",
            data:{ 
                ischange:true,
                numbers:{
                    num1:1,
                    num2:2
                }
            },
            numbers:{
                deep:true,//设置后,numbers对象内部的任意值改变都会触发 handler()
                handler(){
                    console.log('numbers改变了');
                }
            }
        })

Vue可以监测到多级结构中属性的改变,但watch默认不监测对象内部值的改变(它只监测第一层而不深入),为watch设置deep:true后,watch才能监测对象内部值的改变

1.8.2、监视的简写形式

简写的前提:不需要immediate也不需要deep,只需要回调函数

完整写法:

        watch:{//监视方法一,适用于创建vm时监视的对象已经确定
            info:{
                //当ischange发生改变时,调用hander函数
                handler(newValue, oldValue){
                    //oldValue 和 newValue是对应的是watch所监视的info的旧值和新值
                    console.log('info被修改了' ,oldValue, newValue);
                }
            }
        }

        vm.$watch('ischange',{
            handler(newValue, oldValue){
                console.log('info被修改了' ,oldValue, newValue);
            }
        })

简写:

        watch:{
            info(newValue, oldValue) {//直接用监视的属性作为函数名
                console.log('info被修改了,从 '+oldValue+' 被修改为 '+newValue);
            }
        }

        vm.$watch('ischange',function() {//第二个参数传入一个函数,不能写=>函数
            console.log('info被修改了,从 '+oldValue+' 被修改为 '+newValue);
        })

1.8.3、computed 与 watch 之间的区别

  • watch 可以完成 computed 的功能
  • watch 有 computed 不能完成的功能,比如:watch可以进行异步操作

Vue中注意的原则

  • 所有被Vue管理的函数都写成普通函数形式
  • 所有不被Vue管理的函数都写成=>函数形式

1.9、绑定样式

在应用界面中,class绑定和style绑定专门用来实现元素的动态样式效果

1.9.1、class绑定

写法:class = 'xxx',xxx可以是字符串、对象或数组

  • 字符串写法适用于:类名不确定,需要动态获取
  • 对象写法适用于:需要绑定多个样式,但样式的个数、名字不确定
  • 要绑定多个样式,样式的个数名字都确定,但是需要根据实际情况决定哪些用哪些不用
  • 字符串写法, 适用于:样式类名不确定,需要动态指定
    <div id="root">
        <!--字符串写法, 适用于:样式类名不确定,需要动态指定-->
        <div class="basic" :class="mood" @click="changeMood">{{name}}</div>
    </div>

    <style>
        .basic{
            width: 400px;
            height: 100px;
            background-color: skyblue;
        }

        .liushi1{
            width: 400px;
            height: 100px;
            background-color: lightsalmon;
            border-radius: 30%;
        }

        .liushi2{
            width: 400px;
            height: 100px;
            background-color: crimson;
        }

        .liushi3{
            width: 400px;
            height: 100px;
            background-color: cyan;
            border: solid 2px;
            border-radius: 10%;
        }
    </style>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#root',
            data: {
                name: 'class样式绑定',
                mood: 'basic',
            },
            methods: {
                changeMood(){
                    const styleArr = ['lishi1','liushi2','liushi3'];
                    this.mood = styleArr[Math.floor(Math.random()*3)];
                }
            }
        })
    </script>

在这里插入图片描述

  • 数组写法,适用于:要绑定的样式个数不确定,名字也不确定(数组中的样式会一起作用与标签)
    <div id="root">
        <!--数组写法, 适用于:要绑定的样式个数不确定,名字也不确定-->
        <div class="basic" :class="classArr">{{name}}</div>

		<!--也可以写成以下形式,但不推荐-->
		<div class="basic" :class="['style1','style2']">{{name}}</div>
    </div>

    <style>
        .basic{
            width: 400px;
            height: 100px;
            background-color: skyblue;
        }
        .style1{
            width: 400px;
            height: 100px;
            border: solid 3px black;
        }

        .style2{
            border-radius: 50%;
        }
    </style>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#root',
            data: {
                name: 'class样式绑定',
                mood: 'basic',
                //样式绑定的数组写法,
                classArr: ['style1','style2']
            }
        })
    </script>

在这里插入图片描述

  • 对象写法,适用于:要绑定的样式名字、个数都确定, 但要动态决定有些样式用不用
    <div id="root">
        <!--对象写法,适用于:要绑定的样式名字、个数都确定, 但要动态决定有些样式用不用-->
        <div class="basic" :class="classObj">{{name}}</div>
        
        <!--也可以写成以下形式,但不推荐-->
        <div class="basic" :class="{liushi1:false,liushi2:true,liushi3:false}">{{name}}</div>
    </div>

    <style>
        .basic{
            width: 400px;
            height: 100px;
            background-color: skyblue;
        }

        .liushi1{
            width: 400px;
            height: 100px;
            background-color: lightsalmon;
            border-radius: 30%;
        }

        .liushi2{
            width: 400px;
            height: 100px;
            background-color: crimson;
        }

        .liushi3{
            width: 400px;
            height: 100px;
            background-color: cyan;
            border: solid 2px;
            border-radius: 10%;
        }
    </style>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#root',
            data: {
                name: 'class样式绑定',
                mood: 'basic',
                classObj:{
                    liushi1:false,
                    liushi2:true,
                    liushi3:false,
                }
            }
        })
    </script>

在这里插入图片描述

1.9.2、style绑定

写法:

  • :style="fontSize: xxx",其中xxx是动态值,且为字符串
  • :style="[a,b]",其中a、b是样式对象

注意:样式对象中,属性名在CSS中若为用-连接的合成词,那么应当去掉-,并使用小驼峰写法

  • 对象写法
    <div id="root">
        <!--对象写法-->
        <div class="basic" :style="styleObj">{{name}}</div><br/>
    </div>

    <style>
        .basic{
            width: 400px;
            height: 100px;
            background-color: skyblue;
        }
    </style>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#root',
            data: {
                name: 'Style样式绑定',
                mood: 'basic',
                styleObj: {
                    fontSize:'50px',//字符串
                    color: 'orange',

                }
        })
    </script>        

在这里插入图片描述

  • 数组写法
    <div id="root">
        <!--数组写法-->
        <div class="basic" :style="styleArr">{{name}}</div>
    </div>

    <style>
        .basic{
            width: 400px;
            height: 100px;
            background-color: skyblue;
        }
    </style>

    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        const vm = new Vue({
            el:'#root',
            data: {
                name: 'Style样式绑定',
                mood: 'basic',
                styleArr: [//数组中存对象,一起起作用
                    {
                        fontSize: '50px',
                        color: 'red',
                    },
                    {
                        backgroundColor:'orange'
                    }
                ]
            }
        })
    </script>        

在这里插入图片描述

1.10、条件渲染

符合了某些条件,那么就对其渲染某些东西

涉及v-ifv-elsev-else-ifv-show

1、 v-show

  • 适用于:切换评率较高的场景
  • 特点:只是通过修改display为none来隐藏样式,并没有移除DOM元素。v-show一定可以获取到元素

v-show="xxx",xxx是一个可以被转化为布尔值的表达式,为true,则启动后面的渲染,否则不渲染

        <h2 v-show="false">欢迎来到条件渲染</h2>
        <h2 v-show="3 === 3">第二段话被渲染</h2>
        <!--v-show通过调整display属性控制显示和隐藏-->

2、v-ifv-else-ifv-else

适用于:切换频率较低的场景 特点:会直接移除不展示的DOM元素。v-if可能获取不到元素,因为v-if会将不展示的元素从DOM中移除

v-if可以单独使用,也可以和其他两个组合使用

v-if="xxx"v-else-if="xxx",xxx是一个可以被转化为布尔值的表达式;v-else后面不用跟条件

    <div id="root">
        <h2>当前num的值为:{{num}}</h2><br/>
        <button @click="num++">点击触发n++</button><br/><br/>
        <div v-if="num === 1">abc</div>
        <div v-else-if="num === 2">def</div>
        <div v-else-if="num === 3">ghi</div>
        <div v-else>num已经大于3</div>
    </div>


    <script>
        Vue.config.productionTip = false;//阻止Vue在启动时生成生产提示

        new Vue({
            el:'#root',
            data:{
                num:0
            }
        })
    </script>

在这里插入图片描述

<template>搭配v-if渲染分组

当要用v-if渲染多个元素时,可以使用<template>标签作为不可见的包裹元素包裹需要被渲染的多个元素,最终页面上的渲染结果不会包含<template>

        <template v-if="num === 1">
            <div>
                <h1>abc</h1>
                <h1>def</h1>
                <h1>hgi</h1>
            </div>
        </template>

        <div v-else>num已经大于1</div>

在这里插入图片描述

1.11、列表渲染

1.11.1、v-for指令

  作用:用于展示列表数据,对标签使用v-for,会根据遍历对象的length生成对应数量的相同标签      语法;v-for="(item, property, index) in items" :key="xxx"   - items是遍历的源数据数   - item是被迭代的数组元素的别名   - property为键名   - index为当前项的索引   - key可以是index,也可以是遍历对象的唯一标识   - 可以用of代替in      遍历范围:数组、对象、字符串、指定次数

  • v-for遍历数组
        <ul id="root">
            <li v-for="(item,index) in items" :key="item.info">
                {{arrName}}[{{index}}]: {{item.info}}
            </li>
        </ul>
        new Vue({
            el:'#root',
            data:{
                arrName: 'Persons',//v-for可以访问所有父作用域的属性
                items: [
                    {info: 'Rory'},
                    {info: 'Jack'},
                    {info: 'Tom'}
                ]
            }
        })

在这里插入图片描述

  • v-for遍历对象

参数至少要传入一个第一个value来获取对象中属性的值

        <ul id="root">
            <li v-for="(value, property, index) in Obj" :key="value.name">
                {{index}}: {{property}} - {{value}}
            </li>
        </ul>
        new Vue({
            el:'#root',
            data:{
                Obj:{
                    name: 'Rory',
                    age: 18,
                    school: 'sc1'
                }
        })

在这里插入图片描述

  • v-for遍历字符串
        <ul id="root">
            <li v-for="(char, index) in str">
               {{index}}:  {{char}}
            </li>
        </ul>
        new Vue({
            el:'#root',
            data:{
                str: 'hello v-for'
            }
        })

在这里插入图片描述

  • v-for遍历指定次数
        <ul id="root">
            <li v-for="(num, index) in 5" :key="nums">
               {{index}}:  {{num}}
            </li>
        </ul>

在这里插入图片描述

1.11.2、key的作用与原理

key用于给节点标签做一个标识,以便区分

标识原则:尽量使用数组内元素来标识

注意:如果没写key,则遍历时,Vue会自动将index作为key的值


key的内部原理:

  • 虚拟DOM中key的作用: key是虚拟DOM中对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

  • 对比规则: -- 旧虚拟DOM中找到了与新虚拟DOM相同的key:  --- 若虚拟DOM中内容没变, 直接使用之前的真实DOM  --- 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM   -- 旧虚拟DOM中未找到与新虚拟DOM相同的key:创建新的真实DOM,随后渲染到到页面

  • 用index作为key可能会引发的问题: -- 若对数据进行逆序添加、逆序删除等破坏顺序操作:   会产生没有必要的真实DOM更新 --> 界面效果没问题, 但效率低 -- 若结构中还包含输入类的DOM:   会产生错误DOM更新 --> 界面有问题

  • 开发中如何选择key? -- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值 -- 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用index作为key是没有问题的

1.11.3、列表过滤

v-for中,根据一定原则不显示一些数据,从而显示一个数组经过过滤后的版本,而不更改或重置原始数据

    <div id="root">
        <h2>角色列表</h2><br/>
        <input type="text" placeholder="请输入角色名字" v-model="keyWord"><br/>
        <ul id="root">
            <li v-for="(p,index) in filpersons" :key="persons.id">
            <!--注意这里遍历的是过滤后的数组-->
            
                {{p.name}}  {{p.age}}
            </li>
        </ul>
    </div>
  • 方法一:利用计算属性 - computed
    new Vue({
        el:'#root',
        data:{
            keyWord:'',
            persons: [
                {id: '001', name: 'doctor', age: 5000},
                {id: '002', name: 'Rory', age: 26},
                {id: '003', name: 'Amy', age: 25}
            ],
        },
        computed:{
            filpersons(){
                return this.persons.filter((p)=>{
                    return p.name.indexOf(this.keyWord) !== -1
                })
            }
        }
    })
  • 方法二:数据监视 - watch
    new Vue({
        el:'#root',
        data:{
            keyWord:'',
            persons: [
                {id: '001', name: 'doctor', age: 5000},
                {id: '002', name: 'Rory', age: 26},
                {id: '003', name: 'Amy', age: 25}
            ],
            filpersons:[]//过滤后的数组,存放过滤后的元素
        },
        watch: {
        	//监视输入的keyWord
            keyWord:{
                immediate:true,
                handler(val){
                    this.filpersons = this.persons.filter((p)=>{
                        return p.name.indexOf(val) !== -1
                    })
                }
            }
        }
})

在这里插入图片描述

1.12、列表排序

在计算属性conputed中利用数组的sort()方法

1.13、Vue监测数据改变的原理

1、Vue会监视data所有层次的数据

2、监测对象中的数据:   通过setter实现监视,并且需要在new Vue时就传入需要监测的数据   - 对象中后追加的属性,Vue默认不做响应式处理   - 如果需要给后添加的属性做响应式处理,那么应当使用下面两个API     -- Vue.set(target,propertyName/index,value)     -- vm.$set(target,propertyName/index,value)

3、监测数组中的数据   通过包裹数组更新元素的方法实现,本质上是做了两件事   - 调用原生对应的方法对数组进行更新   - 重新解析模板,进而更新页面

不可以对数组中某个元素直接通过索引值进行赋值修改

4、在Vue中修改数组中的某个元素时,应当使用以下方法   -JS数组自带的API: push()pop()shift()unshift()splice()sort()reverse()   -Vue.set()vm.$set(),注意,这两个方法不能为 vm 或者 vm 的根数据对象vm._data)添加属性

    <div id="root">
        <h2>学生信息</h2>

        <button @click="student.age++">年龄+1岁</button><br/>
        <button @click="addSex">添加性别属性,默认为男</button><br/>
        <button @click="addFridend">在列表首位添加一个朋友</button><br/>
        <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button><br/>
        <button @click="addHobby">添加一个爱好</button><br/>
        <button @click="updataHobby">修改第一个爱好为: 游泳</button><br/>


        <h3>姓名:{{student.name}}</h3>
        <h3>年龄:{{student.age}}</h3>
        <h3 v-if="student.sex">性别: {{student.sex}}</h3>
        <h3>爱好</h3>
        <ul>
            <li v-for="(h,index) in student.hobby" :key="index">
                {{h}}
            </li>
        </ul>

        <h3>朋友们:</h3>

        <ul>
            <li v-for="(f,index) in student.friends" :key="index">
                {{f.name}} -- {{f.age}}
            </li>
        </ul>
    </div>
    new Vue({
        el:'#root',
        data:{
            student:{
                name:'Rory',
                age:18,
                hobby:['学习','打游戏','旅游'],
                friends:[
                    {name: 'Amy', age: 18},
                    {name: 'Doc', age: 19}
                ]
            }
        },
        methods: {
            addSex(){
                Vue.set(this.student,'sex','男');
            },
            addFridend(){
                this.student.friends.unshift({name: 'Jack',age: 18});
            },
            updateFirstFriendName(){
                this.student.friends[0].name = '张三';
            },
            addHobby(){
                this.student.hobby.push('唱歌');
            },
            updataHobby(){
                this.student.hobby.splice(0,1,'游泳');
                //或者:this.$set(this.student.hobby,0,'游泳');
                //或者: Vue.set(this.student.hobby,0,'游泳');
            }
        }

    })

1.14、收集表单数据

  • 若:<input type="text"/>,则v-model收集的是value值,用户输入的内容就是value值
  • 若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value属性
  • 若:<input type="checkbox"/> -- 没有配置value属性,那么收集的是checked属性(勾选 or 未勾选,是布尔值) -- 配置了value属性: ----- v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值) ----- v-model的初始值是数组,那么收集的就是value组成的数组
    <div id="root">
        <form @submit.prevent="demo">
            账号: <input type="text" v-model="account"><br/><br/>
            密码:<input type="password" v-model="passwords"><br/><br/>

            性别:
            男<input type="radio" name="sex" v-model="sex" value="male"><input type="radio" name="sex" v-model="sex" value="female"><br/><br/>

            年龄:
            <input type="number" v-model.number="age"><br/><br/>

            爱好:
            学习<input type="checkbox" v-model="hobby" value="学习">
            游泳<input type="checkbox" v-model="hobby" value="游泳">
            唱歌<input type="checkbox" v-model="hobby" value="唱歌"><br/><br/>

            所属校区
            <select v-model="city">
                <option value="">请选择校区</option>
                <option value="Chengdu">成都校区</option>
                <option value="Shenzhen">深圳校区</option>
            </select><br/><br/>

            其他信息:
            <textarea v-model.lazy="otherInfo"></textarea><br/><br/>

            <input type="checkbox" v-model="agree"> 阅读并接受<a href="https://www.51miz.com/so-wendang/11668709.html?utm_term=7164585&utm_source=baidu6&bd_vid=9350972721321806231" >《用户协议》</a>
            <button>提交</button>
        </form>
    </div>
    new Vue({
        el:'#root',
        data:{
            account:'',
            passwords:'',
            sex: '',
            age:'',
            hobby:[],//涉及多选时,hobby应当设置为数组
            city:'',
            otherInfo:'',
            agree:''
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this._data));
            }
        }

    })

在这里插入图片描述

1.15、filters - 过滤器(Vue3不再支持)

  • 作用:对要显示的数据进行特定方式处理后再显示(适用于一些简单逻辑的处理)

  • 分类:过滤器分为全局过滤器和局部过滤器 -- 全局过滤器:在全局起作用,写在new Vue外 -- 局部过滤器:在其所在的vm内起作用,写在某个new Vue

  • 语法: -- 注册过滤器:Vue.filter(name.callback)或者new Vue{filters:{}} -- 使用过滤器:{{xxx | 过滤器名}}{{xxx | filter(args1, 'args2')}}v-bind:属性 = "xxx | 过滤器名"

  • 注意: -- 当全局过滤器与局部过滤器重名时,会采用局部过滤器 -- 过滤器可以接收额外参数,多个过滤器也可以串联:{{msg | filter1 | filter2 | ...}},这种情况下,filter1被定义为接收单个参数msg的过滤器函数,并将filter1的结果作为参数传给filter2…… -- 过滤器并没有改变原本的数据,而是借原数据产生新的对应数据 --

例子:获取时间戳并显示出当前时间

		<div id="root">
			<h2>时间</h2>
            <h3>当前时间戳:{{time}}</h3>
            <h3>转换后时间:{{time | timeFormater()}}</h3>
			<h3>转换后时间:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3>
			<h3>截取年月日:{{time | timeFormater() | mySlice}}</h3>
		</div>
		//全局过滤器
		Vue.filter('mySlice',function(value){
			return value.slice(0,11)
		})

		new Vue({
            el:'#root',
            data:{
                time:1626750147900,
            },
			//局部过滤器
            filters:{
                timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){
                    return dayjs(value).format(str)//这里用了DAY.js,在BootCDN可以下载
                }
            }
        })

1.16、内置指令

已经提到的内置指令

  • v-bind:单向数据绑定,简写为:
  • v-model: 双向数据绑定
  • v-for:遍历数组、对象、字符串
  • v-on:绑定事件监听,简写为@
  • v-ifv-else-ifv-else:条件渲染,动态控制节点是否存在
  • v-show:条件渲染,动态控制节点是否显示
  • v-once:该指令所在节点在初次渲染后,就被视为静态内容,以后数据的改变都不会引起v-once所在结构的更新

其他内置指令

v-text

-- 语法:v-text = "xxx" -- 作用:xxx的值是一个字符串,这个字符串会被当做文本去解析并替换掉v-text所在标签的原有文本

<div v-text="words">你好,v-text</div>
		new Vue({
            el:'#root',
            data:{
                words:'内容已替换,现在显示新内容'
            },
        })

在这里插入图片描述

v-html

  • 语法:v-html="xxx", xxx是html标签
  • 作用:支持结构解析,向指定节点中渲染包含html结构的内容
  • 与插值语法区别: -- v-html替换掉节点中所有的内容{{xx}}则不会 -- v-html:可以识别html结构
  • v-html的安全性问题 -- 在网站上动态渲染任意HTML都很危险,这容易导致XSS攻击 -- 使用v-html应当在可信任的内容上使用,不能在用户提交的内容上使用

v-cloak

v-cloak指令是解决初始化慢而导致页面渲染完毕时闪动一下的一个方案,一般和 display:none; 一起使用.它只是一个属性名,没有值

	<div v-cloak>{{name}}</div>

    <style>
        [v-cloak]{
            display: none !important;
        }
    </style>

v-pre

作用:跳过该节点的编译过程 优势:可利用它跳过没有使用指令语法、插值语法的节点,加快编译速度

1.17、自定义指令

    <div id="root">
        <div v-text="words">你好,v-text</div>
        <h2>n为{num}}, 放大10倍后的n值是: <span v-big="num"></span></h2>
        
        <input type="text" v-f-bind:value="num">
        <!--自定义指令名若为多个单词组成,那么可以在不同单词之间用短横线 - 连接-->
    </div>

1.17.1、函数式

  • 定义局部指令 - directives{}
    new Vue({
        directives:{指令名:配置对象 / 回调函数}
    })
  • 定义全局指令 - Vue.directive()
    Vue.directive(指令名, 配置对象)
    Vue.directive(指令名, 回调函数)
  • 调用时机:指令与元素成功绑定时、指令所在模板被重新解析时
  • 注意: -- 定义指令时不加v-,使用指令时才用v- -- 最好在定义指令时给指令名加上'' -- 指令名若为多个单词,则使用-连接,而不是用小驼峰命名法,此时定义的指令名必须写为字符串形式
directives:{
    big(element,binding){//自定义指令名(指令所在html元素, 指令绑定的变量)
		//函数体
		element.innerText = binding.value*10;
		//注意使用binding的值需要调用binding的value属性
    }
}

1.17.2、对象式

  • 三个常用函数,Vue会在不同时刻调用他们 -- bind(){}:指令与元素成功绑定时调用 -- inserted(){}:指令所在元素被插入页面时调用 -- update(){}:指令所在模块被重新解析时调用
  • 注意:指令的thiswindow
		new Vue({
            el:'#root',
            data:{
                num:10,
            },
            directives:{
                'f-bind':{//当指令名出现了短横线-,那么这里的指令名就应该写为字符串形式
                    bind(){//指令与元素成功绑定时调用
                        //函数体
                        element.value = binding.value;
                    },
                    inserted(){//指令所在元素被插入页面时调用
                        //函数体
                        element.focus();
                    },
                    uodate(){//指令所在模板被重新解析时调用
                        //函数体
                        element.value = binding.value;
                    }
                }
            }
        })

1.18、生命周期

Vue实例的生命周期:每一个vue实例从创建到销毁的过程,这个过程包含八个阶段: 开始创建初始化数据编译模板挂载Dom渲染更新渲染卸载

  • 生命周期又称生命周期回调函数、生命周期函数、生命周期钩子
  • 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写
  • 生命周期函数中的this指向vm组件实例对象

八个生命周期钩子,其他三个钩子在路由部分

1.18.1、挂载流程

1.18.1.1、beforeCreate()

实例、数据监测数数据代理被创建出来之前,这个阶段不能通过vm访问 datamethods

1.18.1.2、created()

实例、数据监测和数据代理被创建完毕,这个阶段 datamethods 已经被初始化,数据也被绑定,可以通过vm对其进行访问和调用,但仍然是虚拟DOM,不可用$el

1.18.1.3、beforeMount()

挂载前,模板template已创建完成,但虚拟DOM尚未变为真实DOM,此时可以在其变为真实DOM前进行最后一次数据更改而不会触发其他的钩子函数,一般可以在此时进行初始数据的获取。

1.18.1.4、mounted()

挂载后,即Vue完成模板的解析并把初始的真实DOM元素放入页面后时。此时模板已经被渲染为真实DOM,用户在页面可以看到渲染完成的效果

    new Vue({
        el:'#root',
        data:{
            //data
        },
        mounted(){
            //函数体
        }
    })

1.18.2、更新流程


只有在view层上面的数据变化才会触发下面的 beforeUpdateupdated

1.18.2.1、beforeUpdate()

View层的数据变化,但页面尚未同步变化

1.18.2.2、updated()

在更新新数据、生成新的虚拟DOM后,新的虚拟DOM与旧的虚拟DOM比较,最晚完成Model ---> View的更新,此时updated阶段即即数据与页面都完成更新

1.18.3、销毁流程

此步之后,页面上只剩下vm曾运行的结构,但是vm功能消失

1.18.3.1、beforeDestroy()

执行销毁操作之前进行($destroy方法被调用前),这个阶段一般进行关闭vm:关闭定时器、取消订阅消息、解除自定义事件等

vm.$destroy():完全销毁一个实例,清除它与其他实例的连接,解除它的全部指令及事件监听器(此处事件指的是自定义事件,不是原生DOM事件)

1.18.3.2、Destroyed

卸载watcher,事件监听,子组件

在这里插入图片描述

二、组件化编程

定义:实现应用中局部功能的代码和资源的集合,组件是可复用的Vue实例 作用:复用代码,将一个复杂的界面的编码简单化,提高运行效率

模块化:应用中的 JS 都以模块来编写的,那这个应用就是一个模块化的应用 组件化:应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用

2.1、非单文件组件

2.1.1、定义及使用

定义:一个文件中包含有n个组件

使用步骤;

  • 第一步:创建组件,使用Vue.extend(options), 配置项options需要注意: -- 不写el,组件的elvm统一管理,由vm设置el决定组件服务于哪个容器 -- data必须写为函数式,避免组件复用时,数据存在引用关系 -- 组件结构的配置由template完成
    //第一步:创建person组件
    const person = Vue.extend({
        //组件定义时不写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
        template:`
            <div>
                <h2>姓名: {{name}}</h2>
                <h2>年龄: {{age}}</h2>  
                <button @click="showThis">点我提示学校名</button>     
            </div>
        `,

        data(){//注意这里的写法 
            return {
                name:'Rory',
                age:18
            }
        },
        methods:{
            showThis(){
                console.log(this)//VueComponent
            }
        }
    })

    //创建school组件
    const schoolInfo = Vue.extend({
        template:`
            <div>
                <h2>学校: {{school}}</h2>
                <h2>班级: {{Grade}}</h2>      
            </div>
        
        `,


        data(){
            return{
                school:'sc1',
                Grade:1
            }
        }
    })
  • 第二步:注册组件(这里以局部组件为例,注意:局部组件只能被注册到一个Vue实例中) -- 局部注册语法:new Vue中传入components配置项,具体如下
    //注册vm
    new Vue({
        el:'#root',
        //第二步:注册组件(局部注册)
        components:{
            //格式:组件标签:组件名
            Person:person,
            School:schoolInfo
        }
    })
  • 第三步,组件引入HTML
    <div id="root">
        <!--第三步:编写组件标签-->
        <Person></Person>
        <hr>
        <School/>
    </div>

在这里插入图片描述 在这里插入图片描述

  • 全局组件注册:可被多个Vue实例使用 -- 语法:Vue.component('组件名', 组件);
    //创建一个全局组件all
    const all = Vue.extend({
        template:`
            <div>
                <h2>{{msg}}</h2>
            </div>    
        `,
        data(){
            return{
                msg:'你好呀'
            }
        }
    })

    //全局注册
    Vue.component('all', all);

直接引用到HTML

<all></all>

2.1.2、注意事项:

1、组件名命名:

  • 名字由多单词组成时 -- -连接,:my-pic -- 大驼峰(需要Vue脚手架支持);MyPic
  • 注意 -- 回避HTML中已有的元素名称 -- 可以使用name配置项指定组件在开发者工具中呈现的名字 2、组件标签写法 -- 第一种:<School><School> -- 第二种:<School/>, 注意:这种方式必须使用脚手架,否则会导致后续组件不能渲染

3、组件创建的简写形式 const School = Vue.extend(options) ---> const School = options

2.1.3、组件嵌套

  • 子组件定义在父组件之前,否则会报错Cannot access '子组件名' before initialization
  • 子组件定义后注册在父组件中
  • 在父组件的template中引入子组件的组件标签
  • 以此类推,套娃
    <div id="root"></div>

先定义子组件

    //定义student子组件,子组件定义在父组件之前
    const student = Vue.extend({
        name: "student",
        template:`
            <div>
                <h2>学生姓名: {{stuName}}</h2>
                <h2>学生年龄: {{age}}</h2>
            </div>
        `,
        data() {
            return {
                stuName: "Jack",
                age: "19"
            }
        }
    })

再定义父组件,并将子组件注册在其中,在插值函数中插入子组件的组件标签

    //定义school父组件
    const school = Vue.extend({ 
        name: "school",
        template: `
            <div>
                <h2>学校名: {{schoolName}}</h2>
                <student></student> 
            </div>   
        `,//插入子组件的组件标签<student></student>
        data(){
            return {
                schoolName: "sc1",
            }
        },

        //局部注册组件
        components: {//在父组件里注册子组件,进行组件嵌套
            student
        }
    })

创建vm,绑定,注册、插入父组件

    //创建vm
    new Vue({
        el:'#root',
        template:`
            //插入父组件
            <school/>
        `,
        //注册局部组件
        components: {
            school//组件标签与组件名相同时简写
        }
    })

2.1.4、组件的本质 —— VueComponent 构造函数加粗样式

console.log输出上面的父组件student后,得到 在这里插入图片描述 可以看到组件本质上是一个名为VueComponent的构造函数

  • 组件本质是一个名为VueComponent的构造函数,且不是程序员定义,而是Vue.extend生成
  • 只需要写组件的组件标签,Vue解析时就会帮我们创建对应组件的实例对象,即Vue帮助程序员执行new VueComponent(opyions)步骤
  • 注意:每次调用Vue.extend时,返回的都是VueComponent
  • 正如Vue的实例对象可以简称vm一样,VueComponent的实例也可以简称vc,即组件实例对象

2.1.5、组件的 this

  • 组件配置中: -- data函数methods中的函数、监视属性watch中的函数、计算属性computed中的函数,四者的this指向均为VueComponent实例对象
  • new Vue()配置中 -- 以上四者的this 指向均为Vue实例对象

2.1.6、重要的内置关系

  • 关系:VueComponent.prototype.__proto__ === Vue.prototype
  • 作用:让组件实例对象可以访问 Vue 原型上的属性、方法

2.2、单文件组件

1、定义:一个文件中只包含有一个组件

2、文件格式:vue,例如:test.vue

3、组件结构:

<template>
    <div>
        <!--组件的结构-->
    </div>
</template>

<script>
    //组件交互相关的代码(数据、方法等)
</script>

<style>
    /*组件的样式*/ 
</style>

4、设置暴露, 便于在别出引用组件

以默认暴露为例,其他暴露方式看ES6

    export default {
        name:'person',//组件名
        data(){
            return {
                name:'V',
                年龄:19
            }
        },
        methods: {
            showName(){
                alert(this.name);
            }
        },
    }

一个完整的简单Vue组件:

<template>
    <div class="demo">
        <!--组件的结构-->
        <h2>人物: {{name}}</h2>
        <h2>年龄:{{age}}</h2>
        <button @click="showName">点击我, 显示人物名</button>
    </div>
</template>

<script>
    //组件交互相关的代码(数据、方法等)

    export default {//简写的默认暴露
        name:'person',//组件名
        data(){
            return {
                name:'V',
                年龄:19
            }
        },
        methods: {
            showName(){
                alert(this.name);
            }
        },
    }
</script>

<style>
    /*组件的样式*/ 
    .demo{
        background-color:rgb(238, 152, 103);
    }
</style>

最后使用一个组件引入所有组件汇总

    import person from './persons.vue'

    export default {
        name: 'App',
        components:{
            person
        }
    }

三、Vue-cli —— Vue脚手架

3.1、 Vue-CLI安装

3.2、项目创建

1、在需要创建vue项目的文件夹中打开cmd 2、输入cd 目录名,切换到想要创建Vue项目的目录 3、输入指令vue create xxx,创建你的Vue项目,xxx是Vue项目名,应当避开流行的框架名称 4、选择项目使用Vue2还是Vue3,回车键表示选择 在这里插入图片描述 babel负责将ES6转ES5,eslint负责语法检查

5、出现如下语句后表示创建完毕 在这里插入图片描述

6、根据提示,输入cd 项目文件夹名转到项目文件夹,然后输入npm run serve启动项目

vscode中,在终端输入npm run serve也可启动项目并出现以下结果

在这里插入图片描述 将地址CV到浏览器地址栏然后进入,即可看到Vue准备的<HelloWorld>组件 在这里插入图片描述 7、输入在之前的cmd界面输入Ctrl + C, 可暂停项目

3.3、脚手架文件结构

在这里插入图片描述

  • public -- favicon.ico:页面标签logo -- index.html:主页面的结构
  • src -- assets:存放静态资源,比如图片、视频、音频等 -- components:存放所有的子组件(相对于最大的父组件App.vue) -- App.vue:最大的父组件,汇总所有组件
    -- main.js:JS入口文件,网页加载时首先访问的JS文件,其他JS文件通过它得以被访问
  • .gitignore:git版本管制忽略的配置
  • babel.config.js:babel配置文件
  • package.json:应用包配置文件
  • README.md:应用描述文件,markdown语法格式
  • package-lock.json:包版本控制文件
  • vue.config.js:配置文件

3.3.1、render()函数

new Vue({
  //将App组件放入容器中
  render: h => h(App),
}).$mount('#app')

上面例子中render()函数的完整写法:

render(createElement){
  return createElement(App);//传入组件
}

作用:因为 运行版本的vue文件:vue.runtime.xxx.js不是完整的,它只有核心功能而没有模板解析器,所以它不能使用 template 配置项,这时候就需要使用 render()函数接收到的createElement函数去指定具体内容

3.3.2、修改默认配置

config配置参考

3.4、ref属性

  • id的替代者,被用来给元素或子组件注册引用信息
  • 应用在html标签上获取到的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)

做标识:ref = "xxx"

<!--html标签-->
<h1 ref="xxx">文本</h1>

<!--组件标签-->
<test ref="xxx"/>

获取:this.$refs.xxx

this.$refs.xxx

在这里插入图片描述

3.5、props配置项

1、功能:让组件接受外部传入的数据

2、数据接收,直接在组件标签中传

  • 例一(props数组接收传入的数据,只接收):<test name="Jack"/>, 注意:存储传入数据的属性应该写在props配置项中,而不是data中,例如:props:['name']
  • 例二(props对象接收,限制数据类型):props:{name:String, age:B=Number}
props:{
    name: String,
    age:Number
}
  • 例三(props对象接收,限制类型、必要性,指定默认值): props:{ name{} }
props:{
    name:{
        type:String,//name的类型应当是String
        required:true,//name是必须要传的,required设置必要性
        default:"Mike"//如果没有传name,则name为默认值“Mike”
    },
    age:{
        type:Number,//传入age的数据类型应当为Number
        default:20
    }
}

3、注意:由props声明的属性xxx只读的, 传入其中的值不可以修改,如果直接对props进行修改,那么会报错。如果想修改,那么需要把props中的属性复制data中,然后修改data中的数据,如下:

        data(){
            return {
                theName: this.name,
                theAge: this.age++
            }
        }

3.6、mixin - 混入(混合) 配置项

1、作用:当多个组件中存在设置相同的配置项时,可以使用mixin配置项将这相同的部分专门提取出来设置为一个混入对象,然后在需要用到它的组件中引用即可

-- 在js文件中定义混入:

//分别暴露
export const mixin = {
    methods: {
        showName(){
            alert(this.name)
        }
    }
}

2、 混合过程中,如果混入的组件没有mixin中的配置,则mixin为组件添加;如果有,则mixin以组件中的为主,不会对其覆盖,即:混入过程中与组件发生冲突时,以组件优先

3、 mixin需要设置暴露,再在组件文件里引入,可以引用多个mixin,最后在vm中设置mixin配置项,并以数组形配置需要引入的混入对象

    import {mixin} from './mixin'//引入

    export default{
        name:'roleInfo',
        data(){
            return {
                name: 'Jack',
                age:18,
                msg:'666'
            }
        },
        mixins: [mixin]//在混入配置项中以数组形式配置混入对象
    }

4、 混入的形式

  • 局部混入:mixin:[xxx,yyy,......]
  • 全局混入:Vue.mixin(xxx,yyy,......)

3.7、plugin - 插件

1、作用:增强Vue的功能,比如,你可以将过滤器、全局指令、全局混入、自定义方法等加入到插件中女装只有一次和()

2、位置:写在src文件夹下 3、本质:插件是包含install方法的一个对象,install的第一个参数是Vue,第二个及以后的参数是插件使用者传递的数据 4、插件的定义与设置暴露:

pluginName.install = function(Vue, options){
	//插件功能模块
}

//暴露,以便引用
export {pluginName};

5、main.js中国引入并使用插件,使用插件用Vue.use(xxx)

//引入插件
import plugins from './plugins'

//使用插件
Vue.use(plugins,1,'str');//第二个参数开始都是数据,可以被插件里的方法使用

3.8、scoped样式

1、前言:在组件中通过<style>添加的样式,会汇总在一起渲染,这种情况下有可能出现不同组件间<style>的冲突, 2、作用:scoped样式相当于给样式划定作用域,让样式成为当前组件的局部样式,只在其所处的组件内生效,从而避免了汇总时的潜在冲突问题 3、使用语法:写在组件的<style>标签内即可:<style scoped> 4、注意:scoped样式一般不在App.vue内使用

3.9、TodoList案例

==占坑==

3.10、WebStorage - 浏览器本地存储

1、存储内容大小:一般为5M左右,不同浏览器有差异

2、浏览器端实现本地存储机制所依赖的属性:window.sessionStoragewindow.localStorage

  • window.sessionStorage 本地存储空间:关闭浏览器后,存储的数据会被清空
  • window.localStorage 会话存储空间:管理浏览器后,存储的数据不会被清空,需要手动清空或者手动设置浏览器清楚浏览数据选项才会被清除

3、相关API(二者共用)

  • setItem('key', 'value'):接收一个键和值作为参数,把键值对添加到存储中,若键名存在,则更新其对应的值
  • getItem('xxx'):接收一个键名作为参数,返回键名xxx对应的值
  • removeItem('key'):接收一个键名作为参数,并把该键名从存储中删除
  • clear():清空存储中的所有数据

4、备注

  • getItem('xxx'):如果xxx对应的value获取不到,那么getItem的返回值是null
  • JSON.parse(null)结果是null
  • 传入参数都是字符串形式

5、查看浏览器本地存储数据的位置

在这里插入图片描述

使用实例:

    <h2>LocalStorage</h2>
    <button onclick="saveData()">点击我保存一个数据</button>
    <button onclick="readData()">点击我读取一个数据</button>
    <button onclick="deleteData()">点击我删除一个数据</button>
    <button onclick="deleteAll()">点我清空所有数据</button>

    <script>
        let p = {name: '张三',age:30}
        function saveData(){
            window.localStorage.setItem('msg1',JSON.stringify(p));
            localStorage.setItem('msg2','hello!');
            localStorage.setItem('msg3',666);
        }

        function readData() {
            localStorage.getItem('msg1');
            
            const res = localStorage.getItem('msg1');
            console.log(JSON.parse(res));
        }

        function deleteData(){
            localStorage.removeItem('msg1');
        }

        function deleteAll() {
            localStorage.clear();
        }
    </script>

3.11、组件的自定义事件

1、一种组件间的通信方式,适用于 子组件为父组件传数据 2、适用场景:A为父组件,B为子组件,B想给A传数据,那么就要在A中给B绑定自定义事件,并在父组件A中设置事件的回调函数 3、自定义事件的绑定:

  • 方法一、父组件中:举例:<goods @showPrice="getGoodsPrice"/><test :getRoleName="getRoleName"/>
  • 方法二、父组件中, 使用ref,计算属性中,使用this.$refs.xxx.$on('eventName',this.回调函数名),xxx是用ref为标签注册的信息
<goods ref="goodsDemo"/>

vm中设置计算属性:
<script>
	mounted() {
    	//this.$refs.goodsDemo.$on('showPrice', this.getGoodsPrice)
    	setTimeout(()=>{
      		this.$refs.goodsDemo.$on('showPrice', this.getGoodsPrice)
    	}, 1000)
    }
</script>    
  • 注意:如果想让自定义事件只触发一次,那么可以使用指令修饰符once或者使用$once方法

4、自定事件的触发: 在事件所绑定的组件中的对应回调函数下设置this.$emit('eventName', 参数) 5、自定义事件的解绑:在对应的解绑回调函数中设置this.$off('eventName')

  • 注意:①如果想一次解绑多个事件,那么需要解绑的事件名以数组形式传入。②不传入参数时,意味着解绑该组件下绑定的所有自定义事件

6、组件上绑定原生DOM事件:在指令后添加native修饰符 7、通过this.$refs.xxx.$on('eventName', 回调)绑定自定义事件时,回调函数只能配置在methods中,或者使用=>函数, 以免this指向出问题

父组件App.vue

<template>
  <div class="app">
    <h1>{{msg}}</h1>

    <!--通过父组件给子组件传递函数类型的额props,实现子组件给父组件传递数据-->
    <test :getRoleName="getRoleName"/>
    <hr/>

    <!--通过父组件给子组件绑定一个自定义事件实现:子组件给父组件传递数据,写法一: 使用v-on-->
    <goods @showPrice="getGoodsPrice"/>

    <!--通过父组件给子组件绑定一个自定义事件实现:子组件给父组件传递数据,写法二: 使用ref
    <goods ref="goodsDemo"/>  -->
  </div>
</template>

<script>
import test from './components/test.vue'
import goods from './components/goods.vue'

export default {
  name: 'App',
  components: { test, goods},
  data(){
    return {
      msg: '测试信息'
    }
  },
  methods: {
    getRoleName(name) {
      console.log('App收到了角色名: ', name)
    },
    getGoodsPrice(price){
      console.log('App收到了商品价格: ', price);
    }
  },
/* mounted() {
    //this.$refs.goodsDemo.$on('showPrice', this.getGoodsPrice)
    setTimeout(()=>{
      this.$refs.goodsDemo.$on('showPrice', this.getGoodsPrice)
    }, 1000)
  } */
}
</script>

<style scoped>
.app{
  background-color: gray;
}
</style>

子组件一:goods.vue

<template>
    <div class="demo">
        <h2>商品:{{name}}</h2>
        <h2>价钱: {{price}}</h2>
        <button @click="sendGoodsPrice">点我把商品价格给App</button>
        <button @click="unbind">解绑showPrice事件</button>
    </div>
</template>

<script>
    export default{
        name:'goods',
        data(){
            return{
                name:'book',
                price: 29.8
            }
        },
        methods: {
            sendGoodsPrice() {
                this.$emit('showPrice',this.price)
            },
            unbind(){
                this.$off('showPrice');
                //不传参就是解绑该组件下绑定的所有自定义事件
            }
        }
    }
</script>

<style>
    .demo{
        background-color: aqua;
    }
</style>

子组件二:test.vue

<template>
    <div class="roleInfo">
        <h2>角色姓名:{{name}}</h2>
        <h2>角色年龄: {{age}}</h2>
        <h1>其他信息: {{msg}}</h1>
        <button @click="sendRoleName">把角色名给App</button>
</div>
</template>

<script>

    export default{
        name:'roleInfo',
        props:['getRoleName'],
        data(){
            return {
                name: 'Jack',
                age:18,
                msg:'666'
            }
        },
        methods: {
            sendRoleName() {
                this.getRoleName(this.name);
            }
        }
    }
</script>

<style>
    .roleInfo{
        background-color: salmon;
    }
</style>

在这里插入图片描述

3.12、全局事件总线(GlobalEventBus) - 任意组件间通信方式

在这里插入图片描述

3.12.1、说明

1、全局事件总线是一种可以实现任意组件间相互通信的方式,如图,如果想要 Achild组件 向Bchild组件 传递数据,那么可以通过全局事件总线先在 Bchild组件 中绑定一个用于实现这种功能的自定义事件,然后设置参数用于接收数据,在数据传递开始时,由传递者 Achild组件 通过全局事件总线对此自定义事件进行触发,开始传递参数。

  • 注意: -- 传递数据时,谁接收数据,谁绑定相关自定义事件;谁发送数据,谁触发相关自定义事件 -- 全局事件总线相当于一个数据的中转站 -- 发送者和接收者必须是成对出现的

2、它本质上是一个对象,它需要满足两个要求

  • 所有的组件对象可以访问到它
  • 它必须可以使用$on$emit$off去绑定、触发、解绑事件 -- $on(): 传入两个参数:绑定事件的事件名、回调函数 -- $emit():传入两个参数:触发的事件名、发出的数据 -- $off: 传入一个参数:解绑的事件的事件名

3.12.2、定义及使用

出于全局事件总线需要满足可以被的组件访问这一要求,所以它应当被安装或定义在组件的原型对象

组件原型对象的的重要关系;VueComponent.prototype === Vue.prototype

main.js中安装全局事件总线:

new Vue({
	//前面省略
	//全局事件总线应当在 beforeCreate 这一钩子中安装
	beforeCreate(){
		Vue.prototype.$bus = this;//this为当前vm,$bus是安装的全局事件总线
	},
})

Achild组件 发送数据,Bchild组件 设置参数接收数据

Bchild.vue使用$on()方法绑定自定义事件并设置接收数据的参数

export default {
	//省略
	mounted(){
		//绑定自定义事件
		this.$bus.$on('eventName', (接收参数)=>{
			//函数体,对接收到的参数进行处理
		})
	}
}

Achild.vue使用$emit()方法发送数据,触发自定义事件

export default {
	//省略
	methods:{
		//触发事件的方法
		sendData(){
			this.$bus.$emit('eventName', 传递的参数);
			//eventName与接收数据的组件的对应eventName一致,即: 成对
		}
	}
}

事件解绑,提高性能

Bchild组件接收数据的组件接收到数据后,需要在钩子 beforeDestory() 中使用$off()方法解绑对应事件,提高性能

export default {
	//省略
	mounted(){
		//绑定自定义事件
		this.$bus.$on('eventName', (接收参数)=>{
			//函数体
		})
	},
	beforeDestory(){
		this.$bus.$off('eventName');//解绑对应事件
	}
}

解绑后,一次组件间的数据传递就算完成

3.12.3、利弊

1、优点:

  • 全局使用,任何组件都可以使用
  • 代码量少,比较灵活
  • 不需要使用缓存的跨组件数据传递

2、缺点:

  • 必须成对
  • 如果事件量大,那么维护会变得很困难
  • 每一次数据传递完成,都需要手动销毁对应事件,否则会引发多次执行,产生bug
  • 复用性差

综上:全局事件总线更适用于小项目

3.13、消息订阅与发布

1、介绍

组件B作为消息发布者,发布了一个名为demo的消息;组件A作为消息订阅者,订阅了组件B中的demo消息,并设置了一个回调getInfo()用来获取demo中想要它想要的数据。当这种关系建立起来后,组件B一旦发布demo消息,便会触发组件AgetInfo()回调函数,使组件A通过getInfo()获取到它订阅的demo消息中它想要获取的参数。

2、注意

  • 需要安装第三方库:pubsub.js -- 安装方法: 管理员身份运行vscode,npm i pubsub-js
  • 订阅消息和发送消息的组件中都引入: import pubsub from 'pubsub-js',否则会报错
  • 引入的pubsub是一个对象

3、订阅消息

订阅者使用 pubsub.js 中的subscribe()方法接收数据并设置响应回调,该方法传入两个参数——第一个是订阅的消息名字符串,第二个是回调函数,应当写为=>函数或是用this调用methods中的回调函数,回调函数中的参数是接收的数据

methods: {
	//订阅事件的回调函数
    demo(msgName, Data){
        console.log('goods组件订阅了test组件的'+msgName+'消息, test组件已发布该消息,goods组件已成功接收其中数据:'+Data);
    }
},
mounted() {
    //订阅消息,这里用this.pubId保存了该订阅的 id
    this.pubId = pubsub.subscribe('GoodsName', this.demo);
    /* 还可以将回调函数写为 =>函数 的形式
    	pubsub.subcribe('GoodsName',(msgName,Data)=>{
    		console.log('goods组件订阅了test组件的'+msgName+'消息, test组件已发布该消息,goods组件已成功接收其中数据:'+Data);
    	})
    */
},

注意:每订阅一个消息,都有一个专属于此订阅的id,取消此订阅必须用这个id去取消

4、发送消息

发送消息的组件在methods中创建一个方法,在其中用 pubsub.js 中的publish()方法发送订阅者所订阅事件中的数据,这个方法传入两个参数——第一个是订阅者订阅的消息名字符串,第二个是发送的数据

methods: {
    sendGoodsName(){//为订阅者发送数据的方法名
    	//数据处理过程省略
        pubsub.publish('GoodsName', this.name)//发送数据
    }
}

5、取消订阅(在订阅消息的组件中取消) 一般在生命周期的beforeDestroy阶段取消顶用,使用 pubsub-js 的unsubscribe()方法,传入的参数是订阅消息的id

beforeDestroy() {
    //取消订阅
    pubsub.unsubscribe(this.pubId)
}

进行消息订阅与发送的两个组件的完整代码如下

订阅者:test.vue

<template>
    <div class="roleInfo">
        <h2>角色姓名:{{name}}</h2>
        <h2>角色年龄: {{age}}</h2>
        <h1>其他信息: {{msg}}</h1>
</div>
</template>

<script>
    import pubsub from 'pubsub-js'

    export default{
        name:'roleInfo',
        data(){
            return {
                name: 'Jack',
                age:18,
                msg:'666'
            }
        },
        methods: {
            demo(msgName, Data){
                console.log('goods组件订阅了test组件的'+msgName+'消息, test组件已发布该消息,goods组件已成功接收其中数据:'+Data);
            }
        },
        mounted() {
            //订阅消息
            this.pubId = pubsub.subscribe('GoodsName', this.demo);
        },
        beforeDestroy() {
            //取消订阅
            pubsub.unsubscribe(this.pubId)
        }
    }
</script>

<style>
    .roleInfo{
        background-color: salmon;
    }
</style>

发送者:goods.vue

<template>
    <div class="demo">
        <h2>商品:{{name}}</h2>
        <h2>价钱: {{price}}</h2>
        <button @click="sendGoodsName">发送商品名给test组件</button>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'

    export default{
        name:'goods',
        data(){
            return{
                name:'book',
                price: 29.8
            }
        },
        methods: {
            sendGoodsName(){
                pubsub.publish('GoodsName', this.name)
            }
        }
    }
</script>

<style>
    .demo{
        background-color: aqua;
    }
</style>

效果: 在这里插入图片描述

3.14、$nextTick - 下一轮执行

  • 作用:一个API, 指定一个回调函数,让其在下一次DOM更新完毕后再执行。传入一个参数,参数是其指定的回调函数
  • 使用场合:在改变数据后,需要基于更新后的DOM进行一些操作时,这些操作需要在$nextTick所指定的回调函数中进行
  • 语法如下:
this.$nextTick(fn(){//fn 是指定的回调函数
	//函数内容
});

//也可以使用不设置时间的定时器实现
setTimeout(()=>{
	//函数内容
})

3.15、Vue封装的过渡与动画

1、作用:在插入、更新或者移除DOM元素时,在合适的时机给元素添加样式类名

2、过程图示: 在这里插入图片描述 2、写法:

①、样式准备

  • 元素进入时各阶段样式名: -- v-enter:进入过程的起点 -- v-enter-active:进入过程中 -- v-enter-to:进入过程的终点

  • 元素离开时各阶段样式名: -- v-leave:;离开过程的起点 -- v-leave-active:离开过程中 -- v-leave-to:离开过程的终点

<style>
    /*动画时间0.5s,匀速*/
    .hello2-enter-active, .hello2-leave-active{
        transition: 0.5s linear;
    }

    /*进入的起点、离开的终点*/ 
    .hello2-enter, .hello2-leave-to{
        transform: translateX(-100%);
    }

    /*进入的终点、离开的起点*/
    .hello2-enter-to,.hello2-leave{
        transform: translateY(0);
    }
</style>

②、过渡元素使用<transituion>包裹,并为其配置name属性

<transition name="hello" appear>
	<h1 v-show="isShow">动画效果</h1>
</transition>

appear属性:DOM渲染完成时自动播放动画

③、多个元素过渡动画,绑定同一动画效果: 使用<transition-group>,其中每个元素都要设置key

<transition-group name="hello2" appear> 
    <h1 v-show="!isShow" key="1">动画效果</h1>
    <h1 v-show="isShow" key="2">过渡效果</h1>
</transition-group>

在这里插入图片描述

3、常用的第三方动画库

-- Animate.css,一个CSS3动画库,目前最通用的动画库

-- Anime.js:一个轻量级的用于制作动画的JavaScript库,适用于任何CSS属性,单个CSS转换,SVG或任何DOM属性以及JavaScript对象

-- Hover.css:CSS悬停效果动画库,提供CSS、Sass、LESS

-- WOW.js:滚动展示动画,依赖于animate.css,只能播放一次

-- scollReveal.js:页面滚动展示动画的JavaScript库,但它的动画没有播放次数限制,也不依赖其他任何文件

-- Magic.css:CSS3动画特效包

-- Move.js:一个小型JavaScript库,用于通过js控制CSS动画的执行顺序

-- Greensock:一个JavaScript动画库,对HTML元素进行动画处理, 用于创建高性能,零依赖性,跨浏览器动画,优点是具有高性能动画效果、轻量模块化、无依赖、动画序列可重叠

-- Velocity.js:适用于大量模板使用的场景,支持复杂的逻辑运算,包含 本数据类型、变量赋值和函数等功能。支持客户端和服务器使用。

-- three.js:一个易于使用、轻量级、跨浏览器的通用 JavaScript 3D 库,让开发者能够轻易在浏览器制作 3D 绘图。


四、Vue中的AJAX

需要安装axios库: npm install axios

4.1、Vue脚手架的配置代理

如果前端应用和后端 API 服务器没有运行在同一个主机上,需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置。

1、 方法一:在vue.config.js 中开启代理服务器

module.exports = defineConfig({
  pages: {
    idnex:{
      //入口
      entry:'src/main.js'
    },
  },
  //添加如下配置开启代理服务器	
  devServer: {
    proxy:'https://localhost:5000'
  }
})
  • 优点 -- 配置容易,请求资源时直接发给前端就行
  • 缺点: -- 不能配置多个代理 -- 不能灵活控制请求是否走代理
  • 工作方式: -- ==优先匹配前端资源==

2、方法二:编写vue.config.js配置具体的代理规则

  devServer: {
    proxy:{
      '/api':{// '/api'是请求前缀
        target: '<url>',//请求作用目标
        ws:true,// 用于支持websocket, 一种客户端和服务器的通信方式
        changeOrigin:true, //
      },
      //配置其他代理
      '/foo':{
        target: '<other_url>'
        //省略
      }
    }
  }
  • 优点 -- 可以配置多个代理 -- 可以灵活控制请求是否走代理
  • 缺点 -- 有些繁琐

4.2、Vue - resource:一个发送AJAX的库

一个Vue的插件库,用于发送AJAX,多用于Vue1.x,现在多用axios 安装:npm i vue-resource

main.js中引入插件:

import xxx from 'vue-resource'
//xxx 一般用 vueResource

使用插件:

Vue.ues(xxx)

axios.get('xxx').then();
//将axios替换为this.$http,返回值还是一样的
this.$http.get('xxx').then();

4.3、slot插槽 - 传递动态结构

作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信方式

以下举例中,父组件为App.vue, 子组件为CateGory.vue

4.3.1、默认插槽

需要在组件标签中插入别的标签时,要提前在组件中放入<slot></slot>插槽,这样vue解析组件标签中插入的标签后,就会将其填充到插槽中显示,如果不使用插槽,那么插入组件标签中的标签不会显示

以下以插入图片举例: 组件CateGory.vue

<template>
    <div class="category">
      <h3>{{title}}分类</h3>
      <slot>插槽,等待填充</slot>
    </div>
 </template>

App.vue

<template>
  <div class="container">
    <CateGroy title="美食" :listData="foods">
      <img src="" alt="">
    </CateGroy>
  </div>
</template>
  • <img>标签在App.vue中被解析后将会填充在<slot>中显示,如果不传标签,那么显示插槽中的默认文本

  • 由于动态插入的标签是在App.vue中解析,所以其样式可以写在对应插槽的组件中,也可以写在解析它的App组件中

4.3.2、 具名插槽 - 用于设置多个插槽时,控制标签与插槽的对应关系

即具有名字的插槽

1、slot="xxx"

在标签中设置属性slot="slotName",选择其要目标插槽,在组件的插槽中设置属性name="slotName",为插槽命名

App.vue

<img slot="foodImg" src="" alt="">
<a slot="film_01" href=""></a>

组件CateGory.vue

<slot name="foodImg">具名插槽,等待 foodImg 填充</slot>
<slot name="film_01">具名插槽,等待 film_01 填充</slot>

<img>会填充到名为foodImg的插槽中,而<a>会填充到名为film_01的插槽中,如果在使用多个插槽的情况下不使用具名插槽,那么会出现需要填充的标签绑定为一个整体填充入每一个插槽的情况。

2、v-slot

  • 如果使用<template>包含需要填充如插槽的标签,那么还可以在<template>中设置属性v-slot:slotName来代替slot="slotName"
  • 注意:v-slot只能写在App.vue<template>

App.vue

      <template v-slot:films>
        <div>
          <a href="">xxx</a>
          <a href="">xxx</a>
        </div>
      </template>

组件CateGory.vue

<slot name="films">具名插槽,等待对应标签填充</slot>

4.3.3、作用域插槽

v-solt="{ArrayName}", 或者:slot-scope="{ArrayName}"

数据在子组件自身,但根据数据生成的结构需要组件的使用者来决定

App.vue

<CateGroy title="美食">
  <template slot-scope={foods}>
  <!--foods是组件CateGory中储存信息的数组名-->
    <div>
      <ul>
        <li v-for="(g,index) in foods" :key="index">{{g}}</li>
      </ul>
    </div>
  </template>
</CateGroy>

CateGory.vue

  export default {
    name: "CateGory",
    props: ['title'],
    data(){
    return {
        foods : ['糖醋里脊', '滑蛋虾仁', '蒜蓉粉丝虾']
      }
    }
  }