Vue2.0

文章目录

一、Vue核心

001 Vue简介

Vue是什么

一套用于构建用户界面的渐进式javascript框架;Vue可以自底向上逐层的应用。
简单应用:只需一个轻量小巧的核心库
复杂应用:可以引入各式各样的Vue插件

Vue特点

  1. 采用组件化模式,提高代码复用率,且让代码更好维护
  2. 声明式编码,让编码人员无需直接操纵DOM,提高开发效率
  3. 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点。

002 搭建Vue开发环境

1)安装Vue开发者工具

1.安装步骤
  • 上官网下载Vue-Devtools,选择Get the Chrome Extension,这是Vue2谷歌浏览器用的,我们选择谷歌浏览器。
  • 在浏览器打开扩展程序,打开开发者模式,谷歌用chrome://extensions/打开),将下载的Vue开发者工具添加到浏览器中,拖拽即可。
  • 最好将开发者工具固定到浏览器导航栏方便后面使用
2.错误处理

在安装的时候很有可能会报错,如图:
在这里插入图片描述
解决方法
直接把下载的文件后缀名改成压缩文件后缀(zip),然后给他解压,解压文件名不要有中文,然后再 加载已解压的扩展程序,完成配置。
在这里插入图片描述
在这里插入图片描述
成功
请添加图片描述

2)安装Vue

先在官网下载Vue,开发时用开发版,项目上下后再使用生产版, 开发版会有一些开发提示,体积比较大,生产版体积比较小。

  1. 直接用 script 标签引入
    <script type=“text/javascript” src=“下载的Vue在项目下的路径”> </script>
    
  2. 用NPM,要配合命令行工具,不推荐

003 Vue的简单使用

hello案例

  1. 要使用Vue,就必须创建Vue实例(new),并且传入一个配置对象
  2. html容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
  3. 容器里的代码被称为Vue模板。工作过程为:当js中执行到Vue代码段时,根据选择器就会把html中的对于部分拿到Vue中解析,如果在其中出现了vue语法,根据js中的Vue代码做出相应变换,得到正常的html语法代码,将新的html返回执行。
  4. 容器和Vue实例是一对一的关系,实例和容器的绑定都会选择第一个,后面的都无效。
  5. 真实开发只有一个Vue实例,会配合组件一起使用
  6. Vue语法的{{ }}里其实是js表达式(一个表达式可以产生一个值,放到任何地方)
  7. 一旦data中的数据改变,页面中用到data的地方也会自动更新。
  8. 执行命令npm run dev运行vue项目
<div id="root">
    <h1>Hello {{name}}</h1>
    <h1>今年{{age}}</h1>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false
    // 创建Vue示例,只传一个配置对象,作为参数
    const vm = new Vue({
        el:'#root',  // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符,用DOM选中方式也可以,比较笨重麻烦
        data: {  // data存数据,供el指向的容器使用,在容器中可以直接用{{}}调用数据,暂时先为一个对象
            name:'Vue.js',
            age:20
        }
    })
</script>

004 模板语法

插值语法

  1. 功能:用于解析标签体内容
  2. 写法:双大括号{{}}表达式
<h1>Hello,{{name}}</h1>

指令语法

  1. 功能:用于解析标签(包括标签属性、标签体内容、绑定事件)
  2. 写法:以v-开头,比如这里,a标签中用v-bind绑定了herf,那么它后面双引号内的url就会判定为js表达式。
// v-bind可以简写为 :
<a v-bind:href="url">点我跳转</a>
<h1>Hello,{{name}}</h1>

005 Vue数据绑定

1)单向数据绑定v-bind

v-bind就是单向的数据绑定,当被绑定的数据改变时,不能改变原始vue中的数据
比如这里输入框内数据改变,data中的name不会变

单向数据绑定:<input type="text" v-bind:value="name">
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
    el:'#root',
    data:{
        name:'Vue'
    }
})
</script>

2)双向数据绑定v-model

v-model用于双向数据绑定,但是只能绑定表单类数据,即有valve值的标签,绑定value值。

双向数据绑定:<input type=“text” v-model:value=“name”>

可以简写为v-model,因为只能绑定valve。

3)data和el的两种写法

  1. el有2种写法
    (1)new Vue的时候在参数中配置el
    (2)先创建Vue实例对象,再通过对象.$mount(‘#root’)指定el值。
  2. data有两种写法
    (1)对象式
    (2)函数式
  3. 重要原则:使用vue管理函数,不要写箭头函数,否则不能让this指向实例

006 MVVM模型

  1. M:模型(Model),对应data中的数据
  2. V:视图(View),模板,就是html中的部分
  3. VM:视图模型(ViewModel),Vue实例对象
  4. 这个模型就是通过VM把V和M联系起来,data中的数据都放到了VM上,使得V可以通过VM访问M的数据;同时VM本身就有一些属性,以及原型上的属性和方法,M可以直接访问到。

007 数据代理

数据代理:通过一个代理对象,对另一个对象中的属性操作(读/写)。

1)Object.defineProperty

  • 使用这个方法可以给对象添加属性,而且可以给这个属性设置一些特殊属性,比如默认通过这个方法添加的属性不能被枚举,不能被修改,可以停过enumerable,writable修改。
  • get函数,每次读取person的age属性值时,都会执行一次get函数,返回值就是age的值(相当于监听age被访问的事件)
  • set函数,每次修改person的age属性值时执行
  • 使用get函数和set函数,就把number和person.age两个数据双向绑定了,不管改变哪一边,两个数据都会改变。
let person = {
	name:’Jack’,
	sex: ‘男’
}

Object.defineProperty(person,'age',{
	value:18,
	enumerable:true, //可枚举
	writable:true // 可修改
	configurable:true //可删除
})

var number = 18;
Object.defineProperty(person,'age',{
	get:function(){
		return number;
	},
	set:function(value){
		number = value;
	}
})

2)Vue中的数据代理

  1. vm中的数据代理:通过vm对象来代理data对象中属性的操作
  2. Vue中数据代理的好处:更加方便的操作data中的数据
  3. 基本原理:使用Object.defineProperty方法,把data添加到vm内部的_data上,而且使用get和set双向绑定data和_data,且vm会监听_data内的数据,当_data改变时,用到它的地方也会改变,也就是data会改变,同样用到data的地方也会改变,页面中data也就改了,页面中访问的数据是data中的数据,访问它会调用get从vm中的_data拿数据。
  4. 使用v-model可以直接实现双向数据绑定
<div id="root">
    <h1>数据:{{name}}</h1>
</div>

<script type="text/javascript">
Vue.config.productionTip = false

const vm = new Vue({
    el:'#root',
    data:{
        name:'Jack'
    }
})   
</script>

006 Vue事件 method

1)事件处理

  1. 在html中使用v-on:xxx绑定事件,xxx就是对应的事件名
  2. 事件的回调需要配置在vm的methods对象中,最终会在vm上
  3. methods中的事件this指向vm或组件实例对象,不要使用箭头函数
  4. 可以将v-on:简写为@,如@click = 'demo',效果一样
  5. 函数可以直接传参,但是要注意最好传一个$event来保存原来的event,否则函数的就只有传进去的几个参数,不能使用event了。
<div id="root">
    <h1>欢迎你,{{name}}!</h1>
    <button @click = 'changeName($event,"GOOD")'>点击提示</button>
</div>
    
<script type="text/javascript">
Vue.config.productionTip = false

const vm = new Vue({
    el:'#root',
    data:{
        name:'Jack'
    },
    methods:{
        changeName(event,number){
            alert(number);
            console.log(event);
        }
    }
})
</script>

2)事件修饰符

  1. prevent:阻止默认事件,等价于preventDefault
  2. stop:阻止事件冒泡
  3. once:事件只触发一次
  4. capture:使用事件的捕获模式
  5. self:只有event.target是当前操作元素时才触发
  6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
  7. 修饰符可以连续写
<button @click.prevent.stop.prevent = 'changeName($event,"GOOD")'>点击提示</button>
methods:{
	changeName(e,number){
		alert(number);
		console.log(event);
	}
}

3)键盘事件

键盘事件绑定

常用有两个:

  1. keyup:按下去抬起来触发
  2. keydown:按下去就触发
    示例:输入回车后在控制台输出,用keyCode判断是否为回车键。
<input type="text" placeholder="按下回车键提示输入" @keyup="showInfo">
const vm = new Vue({
    el:'#root',
    data:{
        name:'Jack'
    },
    methods:{
        showInfo(e){
            if(e.keyCode == 13)
                console.log(e.target.value);
        }
    }
})
按键别名
  1. 实际上Vue给一些常用的按键取了别名
    回车 => enter
    删除 => delete
    退出 => esc
    空格 => space
    换行 => tab(必须配和keydown使用)
    上 => up
    下 => down
    左 => left
    右 => right
  2. Vue未提供别名的按键,可以使用按键原始的key去绑定,但是要转换为kebab-case短横线命名(针对由多个单词组成的按键名)(使用key就可以拿到按键名)
  3. 系统修饰键(用法特殊):ctrl、alt、shift、meta
    (1)配合keyup使用:按下按键的同时,再使用其他键,随后释放其他键,事件才被触发(其他键为任何键)
    (2)配合keydown使用,正常触发事件。
  4. 也可以使用keyCode(键码)去指定具体的按键,已经弃用
  5. Vue.config.keyCode.自定义键名 = 键码,可以去定制按键别名
    前面回车案例改造为同时按下ctrl+y,给keyup事件绑定修饰enter,触发条件就变成了摁下enter键,函数内就不需要判断了。
<input type="text" placeholder="按下回车键提示输入" @keyup.ctrl.y="showInfo">
const vm = new Vue({
    el:'#root',
    data:{
        name:'Jack'
    },
    methods:{
        showInfo(e){
             console.log(e.target.value);
        }
    }
})

007 计算属性 computed

1)姓名案例——联动效果

1.插值语法实现

注意要用v-model,让输入框的数据和data双向绑定

<div id="root">
    姓:<input type="text" v-model='firstName'><br>
    名:<input type="text" v-model='lastName'><br><br>
    <!-- 姓只取前三位 -->
    全名:<span>{{firstName.slice(0,3)}}-{{lastName}}</span>
</div>

<script>
    const vm = new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        }
    })
</script>
2.封装methods方法实现

因为methods中的方法属于vm,vm中又有data,且已经和输入框双向绑定了,所以,数据一旦发生变化,Vue模板就会重新解析,所以函数中数据总是拿到最新的。

<div id="root">
    姓:<input type="text" v-model='firstName'><br>
    名:<input type="text" v-model='lastName'><br><br>
    <!-- 姓只取前三位 -->
    全名:<span>{{fullName()}}</span>
</div>

<script>
    const vm = new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        },
        methods:{
            // 数据一旦发生变化,Vue模板就会重新解析,所以函数中数据总是拿到最新的
            fullName(fName, lName){
                return this.firstName.slice(0,3)+'-'+this.lastName;
            }
        }
    })
</script>
3.计算属性实现
  • Vue把data中的数据,全部当作属性,值就是属性值
  • 计算属性时Vue的另一个配置项,叫computed,内部的属性就是计算属性,属性值为数据;虽然写的时候computed内部的数据都是以对象写的,但是存到vm中的是属性
  • computed内的数据为了能够和计算用到的数据实时绑定,所以用get函数返回属性值,而且Vue修改了computed内部数据对象的this,指向vm,可以直接通过this访问vm的属性
  • 如果计算属性需要被修改,可以使用set让它依赖的属性与之双向绑定。
  • 重点:缓存,get函数第一次被调用时,会缓存数据,后面的再访问这个数据时就不需要再调用get了;当get依赖的数据发生改变时,再重新调用get。这是计算属性的优势。
<div id="root">
    姓:<input type="text" v-model='firstName'><br>
    名:<input type="text" v-model='lastName'><br><br>
    <!-- 姓只取前三位 -->
    全名:<span>{{fullName}}</span>
</div>
<script>
    const vm = new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        },
        computed:{
            fullName:{
                get(){
                    // 此处的this是vm
                    return this.firstName + '-' + this.lastName;
                }
            }
        }
    })
</script>
4.计算属性总结
  1. 定义:要用的属性不存在,要通过已有的属性计算得来
  2. 原理:底层借助了Object.defineProperty方法提供的getter和setter
  3. get函数再初次读取数据时,和依赖的数据发生改变时才会执行,其他时候的访问都是直接读取缓存
  4. 优势:与method方法相比,有缓存机制,效率高,调试方便
  5. 备注:计算属性最后出现在vm上,直接调用即可,如果计算属性需要被修改,记得要用set函数与依赖的数据实现双向绑定。
5.计算属性简写
  • 条件:只有计算属性只读不改时才可以简写
  • 格式:直接把计算属性当函数写,函数内就是get函数
fullName(){
		// 此处的this是vm
		return this.firstName + '-' + this.lastName;
},

008 监视属性 watch

1)天气案例

天气案例

注意,当页面中没有用到vue数据,但是又对数据发生了改变时,Vue开发者工具目前不会更新数据,以后可能会修复吧。

<body>
    <div id="root">
        <h2>今天天气很{{info}}</h2>
        <button @click="changeWeather">切换天气</button>
    </div>

<script>
    new Vue({
        el:'#root',
        data:{
            isHot:false
        },
        computed: {
            info(){
                return this.isHot ? '炎热' : '凉爽';
            }
        },
        methods: {
            changeWeather(){
                return this.isHot = !this.isHot;
            }
        },
    })
</script>

2)监视属性

  1. 被监视的属性发生变化时回调函数自动调用,进行相关操作
  2. 监视的属性必须存在,才能进行监视
  3. 监视的两种写法
    (1)new Vue时传入watch配置
    (2)通过vm.$watch监视
  4. 回调函数,有两个参数,新值和旧值
  5. 可以监视计算属性
new Vue({
    el:'#root',
    data:{
        isHot:false
    },
    computed: {
        info(){
            return this.isHot ? '炎热' : '凉爽';
        }
    },
    methods: {
        changeWeather(){
            return this.isHot = !this.isHot;
        }
    },
    watch:{
        // 监视isHot
        isHot:{
            // 初始化时让handler调用一下
            immediate:true,
            // 回调函数,有两个参数,新值和旧值
            handler(newValue, oldValue){
                console.log(newValue,oldValue)
            }
        }
    }
})
vm.$watch('isHot',{
    immediate:true,
    handler(newValue, oldValue){
    
    }
})

3)深度监视

  • vue是可以检测到复杂数据类型(对象)中的数据变化的
  • 当监视的属性是一个对象数据类型,想监视其中一个属性时,比如number:{a:1, b:2},只想监视a,可以用'number.a':{ }
  • 如果想监视任意属性不要直接使用number监视,因为他是一个对象,它的值是内存地址,就算它内部的属性值变化了,number的地址是不变的,就监视不到数据变化
  • 使用deep:true属性开启复杂数据的深度监视,开启后只要内部数据改变,就认为整个number改变。
data:{
numbers:{
	a:1,
	b:2
}
watch:{
	numbers:{
		immediate:true,
		deep:true, // 深度监视
		handler(){ }
	}
	'numbers.a':{
		handler(){ }
	}
}

4)监视的简写

当配置项只有handler时,才可以简写

watch:{
	numbers(newV, oldV){
		immediate:true,
		deep:true, // 深度监视
		handler(){ }
	}
}
vm.$watch('numbers',function(){
	// 这里也是最好不要写成箭头函数
})

计算属性与监听属性比较

  • 计算属性能完成的,监听属性一定能完成,但是反过来不一定,比如开启异步任务
  • 计算属性的执行一定是立即的,监听属性可以开启异步任务,比如定时器

009 绑定样式

1)绑定class样式

使用:class给元素绑定动态的class样式

  1. 字符串写法,:class是表达式,适用于样式的类名不确定,需要动态指定
  2. 数组写法,适用于要绑定的样式数量不确定,名字不确定
  3. 对象写法,适用于要绑定的样式数量确定,名字确定,但是不确定用不用
<body>
    <div id="root">
        <!-- 字符串写法,:class是表达式,适用于样式的类名不确定,需要动态指定 -->
        <div class='basic' :class='mood' @click='changeMood'>{{name}}</div> <br><br>
        <!-- 数组写法,适用于要绑定的样式数量不确定,名字不确定 -->
        <div class='basic' :class='classArr' >{{name}}</div>
        <!-- 对象写法,适用于要绑定的样式数量确定,名字确定,但是不确定用不用 -->
        <div class="basic" :class="classObj">{{name}}</div>
    </div>

<script type="text/javascript">
const vm = new Vue({
    el:'#root',
    data:{
        name:'尚硅谷',
        // 字符串
        mood:'small',
        // 数组
        classArr:['big','small','normal'],
        // 对象
        classObj:{
            small:false,
            big:true,
            normal:true
        }
    },
    methods:{
        changeMood(){
            const arr = ['small','normal','big']
            this.mood = arr[Math.floor(Math.random()*3)]
            console.log(this.mood)
        }
    },
})
</script>
</body>

2)绑定style样式

使用:style给元素绑定动态内联style样式,也可以写成字符串、数组、对象形式

// html
 <div class="basic" :style="styleObj">{{name}}</div>
const vm = new Vue{
	el:'#root',
	data:{
		styleOnj:{
			fontSize:'200px'
		},
	}
}

010 条件渲染

1)v-if

写法:

  1. v-if="表达式":< div v-if=’ x===1’> < /div>
  2. v-else-is="表达式":< div v-if-else=’ x===1’> < /div>
  3. v-else:< div v-else> < /div>

注意事项:

  1. 适用于切换频率较低的场景
  2. 不展示的DOM元素直接被删除,所以当元素不能显示时是获取不到的
  3. 后面两种都要配合v-if使用,中间不能有间断

2)v-show

写法: v-show="表达式"
注意事项:

  1. 适用于切换频率较高的场景
  2. 不展示的DOM只是不显示,还是存在的,即使不显示还是能获取到

011 列表渲染

1)渲染方法 v-for

  1. 用于展示列表数据
  2. 语法:v-for="(item, index) in xxx" :key="yyy"
  3. 可以遍历 数组、对象、字符串、指定次数
  4. 给每个li标签指定唯一的key来标识,相同会报错
    在这里插入图片描述
<!-- html -->
<ul>
    <!-- 尽量给li设置不重复的key来标识不同的标签 -->
    <!-- 遍历数组 -->
    <li v-for="p in persons" :key="p.id">{{p.name}}--{{p.age}}</li> <br>
    <li v-for="(p,index) in persons" :key="index">{{p.name}}----{{p.age}}</li><br>
    <!-- 遍历对象 -->
    <li v-for="p in car">{{p}}</li><br>
    <!-- 遍历指定次数 -->
    <li v-for="p in 5">{{p}}</li>
</ul>
persons:[
            {id:'001',name:'Jack',age:18},
            {id:'002',name:'Rose',age:20},
            {id:'003',name:'Daniel',age:20},
        ],
        car:{
            name:'奥迪A8',
            price:'70万',
            color:'黑色'
        }

2)key作用与原理

面试常问题:react和Vue中的key用什么作用?(key的内部原理)

  1. 虚拟DOM中key的作用:
    key是虚拟DOM对象的标识,当状态中数据发生变化时,Vue会根据 新数据 生成 新的虚拟DOM,随后Vue进行 新虚拟DOM和旧虚拟DOM的差异比较
  2. 比较规则:
    (1)旧虚拟DOM中找到了和新虚拟DOM 相同的key,Vue认为这两个是一类数据,查看内部数据有没有变化,没有变化的部分直接用旧的真实DOM,有变化的部分用新的虚拟DOM生成真实DOM
    (2)新的虚拟DOM中某个key在旧的虚拟DOM中找不到,直接用新的虚拟DOM生成真实DOM
  3. 用index作为key存在的问题:
    当列表只是顺序变化,内容没有变化时,由于key也随着index变化了,会影响到差异比较,产生不必要的更新,以及当页面含有输入类的DOM时,会产生错误更新。
  4. 开发者如何选择key?
    (1)最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等
    (2)如果不存在对数据的逆序乱序添加删除等破坏顺序的操作,仅用于展示,用index也是可以的。

3)列表过滤

1.监视属性实现:

使用immediate:true让数据一上来就执行,此时val为空字符串,可以匹配到所有数据,
这样就把所有数据添加到newPerson中,打开界面就有数据显示了,
否则的话,打开页面,newPerson为空数组,不会显示任何数据,只有操作以后才有显示
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
    el:'#root',
    data:{
        keyWord:'',
        person:[
            {id:'001',name:'马冬梅',age:18,sex:'女'},
            {id:'002',name:'马思纯',age:20,sex:'女'},
            {id:'003',name:'周杰伦',age:21,sex:'男'},
            {id:'004',name:'温兆伦',age:22,sex:'男'},
        ],
        newPerson:[]
    },
    watch:{
        keyWord:{
            // 使用immediate:true让数据一上来就执行,此时val为空字符串,可以匹配到所有数据,
            // 这样就把所有数据添加到newPerson中,打开界面就有数据显示了
            // 否则的话,打开页面,newPerson为空数组,不会显示任何数据,只有操作以后才有显示
            immediate:true,
            handler(val){
                this.newPerson = this.person.filter((p)=>{
                    return p.name.indexOf(val)!== -1;
                })
            }
        }
    }
})
</script>

2.计算属性实现:

用户的输入拿keyWord就行,每次输入的改变就会引起计算属性的值的改变,就引起页面用到的Vue模板的改变
计算属性只要用到就会执行,所以一打开网页newPerson就计算了一次,自然就能显示所有数据
computed: {
    newPerson(){
        return this.person.filter((p)=>{
            return p.name.indexOf(this.keyWord)!== -1;
        })
    }
},

4)列表排序

computed: {
    newPerson(){
        const arr =  this.person.filter((p)=>{
            return p.name.indexOf(this.keyWord)!== -1;
        })
        // 升序排序
        return arr.sort((p1,p2)=>{
            return p1.age-p2.age;
        })
    }
},

5)Vue更新数据(监视数据)的原理

只有明白了底层原理,才能更好的运用监视属性,避免出现数据改变了但是在显示中并没有更新的情况。
原理:

  1. Vue会监视data中所有层次的数据
  2. 对象中的数据监测
    通过setter实现监测,且要在new Vue时就传入要监测的数据
    (1)对象中后面再追加的属性(直接追加方式),Vue不做响应处理
    (2)如果想要给新追加的属性也做响应处理,就要用到对应的API:
    Vue.set(target, 属性名/索引, value)
    vm.$set(同上)
    它们不能给vm的根或者根数据对象添加属性
  3. 数组中的数据监测
    (1)通过包裹数组元素更新的方法实现,本质就是重写数组的原生更新方法:
    在数组的这些原生方法上添加重新解析模板方法,这样调用这些方法更新数组就能直接响应。
    (2)需要注意的是,当数组内的数据是对象类型,而要修改的不是整个对象,而是对象内的单个属性时,是当作对象处理的,也就是说Vue不能响应的是修改数组的直接元素
  4. 在Vue修改数组的某个元素一定要用如下方法:
    (1)API:push()、pop()、shift()、unshift()、splice()、sort()、reverse(),要使用其他非变更方法时,用新的数组替代旧的数组即可
    (2)Vue.set() 或 vm.$set()
  5. 数据劫持就是指对数据的更新被拦截下来,做一些额外的操作

    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>Document</title>
    <script type="text/javascript" src="../vue/vue.js"></script>
</head>
<body>
<div id="root">
    <h1>学生信息</h1>
    <button @click="student.age++">年龄+1</button>
    <button @click="addSex">添加sex属性'男'</button>
    <button @click="addFriend">在列表前面添加一个朋友</button>
    <button @click="changeFriendName">修改第一个朋友名字为张三</button>
    <button @click="addHobby">添加一个爱好</button>
    <button @click="changeFirstHobby">修改第一个爱好为学习</button>

    <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>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
    el:'#root',
    data:{
        student:{
            name:'Jack',
            age:18,
            hobby:['抽烟','喝酒','烫头'],
            friends:[
                {name:'Rose',age:19},
                {name:'Kimi',age:21},
                {name:'Joe',age:17}
            ]
        }
    },
    methods:{
        addSex(){
            Vue.set(this.student,'sex','男')
            // vm.$set(this.student,'sex','男')
        },
        addFriend(){
            this.student.friends.unshift({name:'Daniel',age:22})
        },
        changeFriendName(){
            // 这里可以直接修改名字,因为数组里面的每个数据是一个对象,相当于在对象上修改,Vue会监测
            // 注意,不能直接修改的是数组的直接数据
            this.student.friends[0].name = '张三'
        },
        addHobby(){
            this.student.hobby.push('吃火锅')
        },
        changeFirstHobby(){
            // 操作数组的直接元素,不要直接用索引
            this.student.hobby.splice(0,1,'学习')
        }
    },
})
</script> 
</body>
</html>

012 收集表单数据

  1. <input type=“text”/>输入框,则v-model收集的是valve值,用户输入的就是valve值
  2. <input type=“radio”/>,则v-model收集的是valve值,且要给标签配置valve值
  3. <input type=“checkbox”/>,则v-model收集的是valve值
    (1)没有配置input的valve属性,那么收集的就是checked,勾选或者未勾选,是布尔值
    (2)配置了valve值,但是初始值不是数组,那么收集的还是布尔值;若果初始值是一个数组,那么收集的就是valve数组成员。
  4. v-model的三个修饰符:
    lazy,失去焦点再收集数据
    number,字符串转数字
    trim,首尾去空格

013 过滤器 filters

  • 使用格式:数据 | 过滤函数,或者v-bind:属性 =“xxx | 过滤器名”
  • 注册过滤器:filters配置,内部为过滤器
  • 过滤器不仅能在插值语法中用,也可以直接在标签内调用;可以接收额外参数,多个过滤器可以串联
  • 并没有改变原本的数据,只返回新数据
//局部过滤器
filters:{
	getTimeFormat(){}
}
//全局过滤器,在实例外配置
Vue.filter(‘getTimeFormat’,()=>{})

014 一些指令

1)v-text指令

v-text=“str”

  1. 作用:向其所在的节点中渲染文本内容
  2. 与插值语法的区别:v-text会替换掉节点中的内容,{{xxx}}不会

2)v-html指令

v-html="str"

  1. 会替换掉结点中的所有内容,而且可以解析结构
  2. 严重注意安全性问题
    (1)在网站上动态渲染任意html时非常危险的,容易导致XSS攻击
    (2)一定要在可信内容上使用v-html,永远不要在用户提交的内容上使用

3)v-cloak指令

  1. v-cloak指令是没有值的,本质是一个特殊属性Vue创建完示例并接管容器后,会删掉v-cloak属性
  2. 配合css使用,可以解决网速慢时页面显示丑,出现原生Vue模板内容的问题
<style>
	[v-cloak]{
		dispaly:none;
	}
</style>
<body>
	<h2 v-cloak>{{name}}</h2>
</body>

4)v-once指令

  1. v-once所在节点在初次动态渲染后,就视为静态内容
  2. 以后的数据变化都不会一起v-once结构更新,可以用于优化性能
<div id="root">
    <h2 v-once>n的初始值: {{n}}</h2>
    <h2>n的当前值: {{n}}</h2>
    <button @click="n++">点我+1</button>
</div>

5)v-pre指令

  1. 跳过其所在节点的Vue编译过程
  2. 可以利用跳过:没有使用指令语法,没有使用插值语法的节点,会加快编译

015 自定义指令

1)函数式

<div id="root">
    <h2 v-once>n的初始值: {{n}}</h2>
    <h2>n的当前值: {{n}}</h2>
    <h2>n放大10倍的值: <span v-big="n"></span></h2>
    <button @click="n++">点我+1</button>
</div>
directives:{
    big(element, binding){
        // 自定义指令不是用返回值操作数据
        element.innerText = binding.value*10
    }
}

2)对象式

3)总结

  1. 定义语法
    (1)局部指令:

    new Vue({
    	directives:{指令名:配置对象} // 或者 directives(){}函数形式
    })
    

    (2)全局指令

    Vue.directive(指令名,配置对象) // 或 Vue.directive(){}函数形式
    
  2. 配置对象中的3个回调
    (1)bind:指令与元素绑定成功时调用
    (2)inserted:指令与所在元素被插入页面时调用
    (3)update:指令与所在模板结构被重新解析时调用
    (4)回调函数的形参是两个:被绑定的元素绑定的数据

  3. 备注
    (1)指令定义时不加v-,使用时才加
    (2)指令名如果是多个单词,要使用kebab-case命名方式,不要使用驼峰命名

    Vue.directive('fbind',{
    	bind(element,binding){
    		element.value = binding.vlaue
    	},
    	inserted(element,binding){
    		element.focus()
    	},
    	update(element,binding){
    		element.value = binding.vlaue
    	},
    })
    

016 生命周期

1)初识生命周期

  1. 又名:生命周期回调函数、生命周期函数、生命周期钩子
  2. 是 Vue在一些特定时刻自动帮我们调用的一些特殊名称函数
  3. 生命周期函数名字不可以更改,内容可以随便写
  4. 生命周期中的this指向vm或组件实例对象
const vm = new Vue({
    el:'#root',
    data:{
        opacity:1
    },
    // 挂载函数:Vue完成解析并把真实DOM放到页面后,调用mouted
    mounted() {
        let flag = true
        setInterval(() => {
            // 这里直接可以直接用this,定时器用的箭头函数,往外找this为mounted的指向,这个函数属于Vue实例对象
            if(flag) this.opacity-=0.003
            else this.opacity+=0.003
            if(this.opacity<=0||this.opacity>=1) flag=!flag
        }, 16);
    },
})

2)挂载流程

红色框就是Vue生命周期函数。

  1. beforeCreate:在实例初始化之后,进行数据侦听和事件配置之前同步调用。
  2. created:在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。
  3. beforeMount:在**挂载开始之前(生成真实DOM之前)**被调用:相关的 render 函数首次被调用
  4. mounted:实例被挂载后调用,生成真实DOM后这时 el 被新创建的 vm. e l 替换了。如果根实例挂载到了一个文档内的元素上,当 m o u n t e d 被调用时 v m . el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm. el替换了。如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.el 也在文档内。
    注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick
  5. template不是周期函数,是Vue的一个配置项,内部为只有一个根元素的html结构,Vue在解析会把其中的内容当作模板解析,并且替换掉所绑定的html元素结构。

3)更新流程

只要更改vm内的数据,就会触发

  1. beforeUpdate:在数据发生改变后DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。
  2. updated:在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
    当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
    注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick

4)销毁流程

调用vm.$destroy(),完全销毁vm实例,清理它与其他实例的连接,解绑它的全部指令及事件监听器(自定义事件解绑,原生事件一直存在)

  1. beforeDestroy实例销毁之前调用。在这一步,实例仍然完全可用,但是此时对数据的更新不会再引起页面变化了,因为马上就要销毁,没有更新的必要。
  2. destroyed实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的自定义事件的监听器被移除,所有的子实例也都被销毁。原生DOM事件依然有效

5)总结

vm的生命周期:
将要创建 => beforCreate
创建完毕 => created
将要挂载 => beforeMount
挂载完毕 => mounted (重要钩子
将要更新 => beforeUpdate
更新完毕 => updated
将要销毁 => beforeDestroy (重要钩子
销毁完毕 => destroyed

当不同生命周期函数和其他Vue配置项都想用同一个函数比如定时器时,可以把它的id挂载到vm上,也就是给this添加。
请添加图片描述

6)使用示例

<body>
<div id="root">
    <h2 :style="{opacity}">欢迎光临</h2>
</div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    
    const vm = new Vue({
        el:'#root',
        data:{
            opacity:1
        },
        // 挂载函数:Vue完成解析并把真实DOM放到页面后,调用mouted
        mounted() {
            let flag = true
            setInterval(() => {
                // 这里直接可以直接用this,定时器用的箭头函数,往外找this为mounted的指向,这个函数属于Vue实例对象
                if(flag) this.opacity-=0.003
                else this.opacity+=0.003
                if(this.opacity<=0||this.opacity>=1) flag=!flag
            }, 16);
        },
    })
</script>

二、Vue组件化编程

017 对组件的理解

1)模块

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

2)组件

  1. 理解:用来实现局部(特定)功能效果的代码集合(html、css、js、image、mp4······)
  2. 为什么:一个界面的功能很复杂
  3. 作用:复用编码,简化项目编码、

3)组件分类

  1. 非单文件组件:一个文件中包含n个组件
  2. 单文件组件:一个文件只包含一个组件,一般用单独的.vue文件

018 非单文件组件

Vue中使用文件的三大步骤

  1. 定义组件(创建组件)
    (1)使用const 组件 = Vue.extend({ })创建组件,创建Vue实例用的配置项这里几乎都可以用,但是也有点区别
    (2)不要写el,这里只是定义,后面可以给任意元素用的
    (3)data必须写成函数,要不然同一个文件用了多个一样的组件,那么内部数据名完全一样,存在数据引用问题
    (4)使用template可以配置组件结构
  2. 注册组件(key:value形式)
    (1)局部:new Vue时,给实例对象配置components,内部可以注册多个组件 components:{ 组件1名:组件1, 组件2名:组件2}
    (2)全局:使用Vue.component('组件名',组件)
  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>Document</title>
    <script type="text/javascript" src="../vue/vue.js"></script>
</head>
<body>
<div id="root">
    <!-- 第三步:编写组件标签 -->
    <school></school>
    <hr>
    <student></student>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
// 第一步:创建school组件
const school = Vue.extend({
    // 原来Vue实例创建时用的配置项这里大多都能用
    // 1.组件创建时不需要指定el配置项,因为要复用,可以给很多对象用
    // 2.data要用函数写,返回值就是数据,写成对象的话,一个页面用同一个组件,那么它们的数据也就是相同的,这就不是复用了
    template:`
    <div>
        <h2>学校:{{name}}</h2>
        <h2>地址:{{adress}}</h2>
    </div>
    `,
    data(){
        return {
            name:'HIT',
            adress:'山东威海'
        }
    },
})
// 创建student组件
const student = Vue.extend({
    template:`
    <div>
        <h2>姓名:{{stuName}}</h2>
        <h2>年龄:{{age}}</h2>
    </div>
    `,
    data(){
        return {
            stuName:'Jack',
            age:18
        }
    },
})
// 第二步:注册全局组件
Vue.component('student',student)
// 第二步:局部注册组件
new Vue({
    el:'#root',
    // 为这个对象配置组件
    components:{
        // key:value形式 注册组件名:创建时组件名
        school:school,
    }
})
</script>
</body>
</html>

019 组件的几个注意事项

  1. 关于组件名
    (1)一个单词组成的,首字母可以大写也可以小写(school/School),醉胡到页面中首字母都是大写
    (2)多个单词组成的,可以用 kebab-case命名,也可以驼峰命名 CamelCase,但是这种写法要Vue脚手架支持
  2. 关于组件标签
    (1)第一种写法,<school></school>
    (2)第二种写法,<school/>,使用这种方法最好也有脚手架支持,否则只能解析第一个
  3. 一个简写方式:
    const school = Vue.extend(options) 可以简写为 const school = options
    

020 组件嵌套

school内部嵌套student,要注意stuednt应该比school先创建,这样school在创建时才能直接用student

<script type="text/javascript">
Vue.config.productionTip = false
// 创建student组件
const student = Vue.extend({
    template:`
    <div>
        <h2>姓名:{{stuName}}</h2>
        <h2>年龄:{{age}}</h2>
    </div>
    `,
    data(){
        return {
            stuName:'Jack',
            age:18
        }
    },
})
// 创建school组件
const school = Vue.extend({
    // 原来Vue实例创建时用的配置项这里大多都能用
    // 1.组件创建时不需要指定el配置项,因为要复用,可以给很多对象用
    // 2.data要用函数写,返回值就是数据,写成对象的话,一个页面用同一个组件,那么它们的数据也就是相同的,这就不是复用了
    template:`
    <div>
        <h2>学校:{{name}}</h2>
        <h2>地址:{{adress}}</h2>
        <student></student>
    </div>
    `,
    data(){
        return {
            name:'HIT',
            adress:'山东威海'
        }
    },
    // 注册student组件
    components:{
        student:student,
    },
})
// 第二步:局部注册组件
new Vue({
    el:'#root',
    // 为这个对象配置组件
    components:{
        // key:value形式 注册组件名:创建时组件名
        school:school,
    }
})
</script>

021 VueComponent

1)理解VueComponent

  1. 之前创建的组件实例例如school,本质是一个VueComponent构造函数,且不是程序员定义的,是Vue.extend生成的
  2. 我们只需要写组件标签,Vue在解析模板时会帮我们创建school组件的实例对象,即Vue帮我们执行:new VueComponent(options)
  3. 特别注意每次调用Vue.extend时,Vue都是返回一个 全新的 VueComponent
  4. this指向:
    (1)new Vue中函数的this指向Vue实例
    (2)VueComponent中也可以把配置项写成函数,它的this指向VueComponent实例对象
  5. VueComponent的实例对象(组件实例对象),以后简称vc

2)一个重要内置关系

  1. 内置关系:VueComponent.prototype.__proto__ === Vue.prototype
  2. 为什么要有这个关系:让vc可以访问到vm原型上的属性、方法。比如,VueComponent的实例要访问某个方法时,VueComponent中没有,就会去VueComponent的原型上找,也就是VueComponent.prototype,要是还没有,那么应该按着隐式原型链查下去,也就是VueComponent.prototype._ _ proto _ _,Vue让它指向Vue.prototype了。

022 单文件组件

1)学习

  • 在.vue文件中的最外层只能用三个标签,来分隔html、css、js三个模块
  • js模块的数据要暴露出去,其他文件引入这个文件时才能真正的访问到数据,这里是js模块化部分内容
  • 使用import 方式引入组件,只能在脚手架中正常使用,后面将要讲脚手架
  • 安装了Vue组件的vetur插件后,输入<和回车就可以快速获得单文件组件结构
  • 基本结构
    <template>
    <!-- html -->
    <div></div>
    </template>
    
    <script>
    // js
    export default {
    
    }
    </script>
    
    <style>
    /* css */
    </style>
    

2)实例

结构
在这里插入图片描述

<!-- School.vue -->
<!-- html结构 -->
<template>
<div class="demo">
    <h2>学校名称:{{SchoolName}}</h2>
    <h2>学校地址:{{adress}}</h2>
</div>
</template>
<!-- 组件交互 -->
<script>
// 这里要把数据暴露出去,有三种暴露方法(js的模块化部分)
// export xxx
// export { xxx, yyy}
// export default xxx
// 这里本来是 export default school,
// 简化为export default Vue.extend({xxx}),再简写为export default {xxx},Vue.extend是可以省略的
    export default{
        name:'School',
        data(){
            return{
                SchoolName:'HIT',
                adress:'山东威海'
            }
        },
        methods: {
            showName(){
                alert(this.SchoolName);
            }
        },
    }
</script>
<!-- 组件的样式 -->
<style>
.demo{
    background-color:pink;
}
</style>
<!-- Student.vue -->
<!-- html结构 -->
<template>
<div class="demo">
    <h2>学生姓名:{{StudentName}}</h2>
    <h2>年龄{{age}}</h2>
</div>
</template>
<!-- 组件交互 -->
<script>
export default {
    name:'Student',
    data(){
        return{
            StudentName:'Jack',
            age:18
        }
    },
}
</script>
<!-- 组件的样式 -->
<style>
.demo{
    background-color:blueviolet;
}
</style>
<!-- App.vue -->
<!-- App组件,把所有其他组件整合到这里注册,并且使用 -->
<template>
<div>
    <School/>
    <br>
    <Student/>
</div>
</template>
<script>
import School from './School.vue'
import Student from './Student.vue'
export default {
    name:'App',
    components:{
        Student,
        School
    }
}
</script>
<style>
</style>
import App from './App.vue'
// main.js
// 入口文件,准备了整个框架,也就是注册App
new Vue({
    el: '#root',
    template:`<App/>`,
    components: {
        App
    }
})
<!-- index.html -->
<!-- 项目中使用了组件的界面,喜好元素,引入框架即可,元素的获取方式要和main入口文件的选定方式一致 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="root">
    </div>
    <script type="text/javascript" src="../vue/vue.js"></script>
    <script type="text/javascript" src="./main.js"></script>
</body>
</html>

三、脚手架Vue CLI

022 初始脚手架

1)说明

  1. Vue脚手架(Vue CLI)是Vue官方提供的标准化开发工具(开发平台)
  2. 最新版本是4.x
  3. 文档:https://cli.vuejs.org/zh/

2)具体步骤

  1. 安装脚手架:首先安装全局脚手架,方便以后直接用,使用命令 npm install -g @vue/cli
  2. 创建项目:到自己想要创建项目的目录下,使用命令创建项目vue create 项目名
  3. 启动项目:到创建的目录下,使用命令 npm run serve,启动项目

3)备注

  • 安装脚手架可能会出现一些警告,不用管,这是脚手架开发者的问题
  • 脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,执行命令vue inspect > output.js查看

023 分析脚手架结构

在这里插入图片描述
创建好的脚手架文件结构如图,其中

  • public文件夹下一般是放和页面有关的文件
  • src文件夹下放的是一些自定义的需要引入的资源,如asset文件夹下放图片、components文件夹下放单文件组件。
  • App.vue和main.js就是单文件组件的部分父组件和容器配置组件文件,完全一样
  • 剩下的文件就是一些项目版本号、模块信息等配置,一般不要动他
  • 注意: 脚手架现在用可能会有一些报错,比如组件名Component name "Student" should always be multi-word vue/multi-word-component-names,它只建议多个单词组成的模式,我们可以在项目配置文件的vue.config.json中添加一行:lintOnSave:false,忽略这个问题就可以
    尚硅谷老师总结截图
    请添加图片描述
    请添加图片描述
    请添加图片描述请添加图片描述

024 脚手架中关于不同版本的Vue细讲

  • 完整的Vue包含 核心功能+模板解析器
  • 脚手架项目创建后,它内部提供了很多不同版本的Vue,有完整版,也有不同类型的精简版默认引入的是一个不包含模板的精简版Vue
  • main.js文件 :那么我们之前在main.js的new Vue中用template配置项来写App标签的使用,避免后面还要再容器中使用标签,这里配置项中的内容就是模板字符串,默认精简Vue是不能解析的,解决方法有两种:
    (1)引入完整版Vue,这样不太好,项目上线后Vue中的解析模板部分是不需要的,因为上线会再解析一次
    (2)用render函数将App放入容器中,完整的写法如下,函数可以接受一个参数,也是一个方法,可以创建结构
    render(createElement){
    	return createElement('h1','你好啊')
    }
    
    当创建的结构是自定义时,由于我们在文件中引入了App组件,引入的名称也是App,那么直接用App就可以了,不要加双引号
    render(createElement){
    	return createElement(App)
    }
    
    简写,箭头函数、参数只有一个、函数内代码只有一行,参数名简化为h
    render : h=> h(App)
    

025 ref标签属性

ref属性:

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

026 props配置

1)功能

让组件接收外部传入的数据

2)传递数据

<Student name="Jack" sex="男" age="18"> </Student>

3)接收数据

  1. 方式一,简单接收
    export default{
    	data(){
    		return { msg:'我是data中的数据'}
    	},
    	props:['name','age','sex']
    }
    
    有一点要注意,这种方式传进来的数据都是字符串类型,例如年龄传进来是一个字符串,如果想对它进行数字类型的计算,需要把转换为数字,那么可以在接收上设置,让数据传进去的时候就是数字,给age作为数据类型绑定,v-bind:age="18",那么引号内的内容才是数据,这样18就是个数字类型数据了,简写为:age="18"
  2. 限制类型
    接收的同时限制类型:另一种是组件本身就限制了这几种参数的数据类型,使用js的内置数据类型构造函数限制,写法为
    export default{
    	data(){
    		return { msg:'我是data中的数据'}
    	},
    	props:{
    		name:String,
    		sex:{
    			type:String
    			},
    		age:Number
    	}
    }
    
  3. 定默认值和必要性限制
  • props还可以指定数据默认值,当没有传入时,就使用默认值,default:17
  • props还可以指定数据是否必要,也就是必要性限制,当数据为必要时,使用时必须传入该数据,required:true
    props:{
    	name:{
    		type:String,
    		required:true
    	},
    	sex:{
    		type:String,
    		default:'女'
    	},
    	age:{
    		type:Number,
    		default:18
    	},
    }
    

4)修改传入的参数

当用props声明配置的数据,要在方法中修改时,Vue会报错,正确的方法是在data中定义一个数据,它的初值为传入的数据,后面修改data中的这个数据就不会报错了。
这里就是把age给了myAge。

export default{
	data(){
		return { 
			msg:'我是data中的数据',
			myAge: this.age
		}
	},
	props:['name','age','sex']
}

027 mixin混入配置

1)功能

多个组件共用的配置 提取成一个混入对象整合到一个js文件中,然后其他的文件只需要引入它就可以。

2)使用方法

  1. 第一步:定义混合
    {
    	data(){......},
    	methods{......},
    	mounted(){......}
    }
    
  2. 第二步:使用混合
    (1)全局混入
    // 在main.js中配置全局混入
    Vue.mixin(xxx)
    
     (2)局部混入
    
    export default{
    	mixins:['xxx']
    }
    

3)注意事项

混入,不是替换,如果组件中本来就有改配置项,就是把这相同的两个配置项整合到一起,同时,当有相同的内容,优先使用组件中原来的配置内容。
混入配置的的顺序是在元配置之前。

文章目录

028 插件plugins

1)功能

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

2)定义插件

对象.install = function(Vue){
	配置项
	//例如
	Vue.filter(...) //配置全局过滤器
	Vue.mixin(...) //配置全局混合
}
// 插件是单独的js文件,要暴露出去这个插件对象
export default 对象

3)使用插件 Vue.use

在main.js文件中应用插件

Vue.use(插件对象名)

029 scoped样式

作用:让样式局部生效,防止冲突。
当脚手架里有多个组件,都有各自的样式,那么他们的样式是不允许重名的,因为引入这些组件后都相当于在同一个文件中,可以使用scoped属性设置为局部样式,意味着这个样式只负责当前作用域,一般来说App中不会用到,因为App一般是对所有组件的一个汇总设置。
写法<style scoped>

TodoList案例 静态版

Vue_TodoList案例

1)案例功能描述

就是一个简单的待办事项案例,可以输入框输入新的待办事项名来添加待办事项,每个待办可以勾选表示已完成,有全选按钮,每个待办事件有自己的删除按钮,还有一个按钮可以删除所有已完成的事件。

2)组件化编码流程(通用

  1. 拆分静态组件组件要按照功能点拆分,命名不要与html元素冲突
  2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用还是一些组件在用
    (1)一个组件在用:放在这个组件里就行
    (2)一些组件在用:放在它们的共同父组件上
  3. 实现交互:从绑定事件开始,理清事件之间的逻辑

3)props适用

props配置项目配置别的组件或结构在使用当前组件标签时可以传入的一些数据,一般来说适用于:

  1. 父组件 => 子组件 通信:子组件需要父组件的数据
  2. 子组件 => 父组件 通信:子组件要修改父组件的数据,那么不仅要拿到父组件的数据,还要父组件传递数据,这一块目前的处理方法是在子组件调用父组件传进来的函数修改数据。
  3. 注意: props传递过来的数据如果是对象类型,那么修改对象中的属性不会报错,但是不推荐,建议还是不要修改props传递的数据

4)案例源码

在这里插入图片描述

<!-- MyFooter.vue -->
<template>
  <div class="todo-footer" v-show="total">
    <label>
      <input type="checkbox" :checked="isAll" @change="checkAll">
    </label>
    <span>
      <span>已完成 {{doneTotal}} </span>/全部 {{total}}
    </span>
    <button class="btn btn-danger" @click="handleAllDelete">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name:'MyFooter',
  props:['checkAllTodo','deleteAllDoneTodo','todos'],
  methods:{
    checkAll(e){
      // 选择框可以通过e.target.checked拿到选择的情况
        this.checkAllTodo(e.target.checked);
    },
    handleAllDelete(){
      if(confirm("确定那个删除所有已完成事项吗?")){
        this.deleteAllDoneTodo();
      }
    },
  },
  computed: {
    // 使用计算属性计算已完成的事项总数
    total(){
      return this.todos.length
    },
    doneTotal(){
      return this.todos.reduce((pre,current)=>{
        return pre+(current.done?1:0);
      },0)
    },
    isAll(){
      return this.doneTotal=== this.total && this.total>0
    }
  },
}
</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>
<!-- MyHeader.vue -->
<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车确定" @keyup.enter="add"/>
  </div>
</template>

<script>
import {nanoid} from 'nanoid'
export default {
  name:'MyHeader',
  props:['addTodo'],
  methods:{
    add(e){
      // 给输入去掉空格
      if(e.target.value.trim()==='') return alert("输入不能为空");
      // 将用户的输入包装成一个todo对象
      // 这里需要给每一个对象生成唯一的id,
      // 在没有数据库支撑的情况下,可以使用一个uuid库,每次会自动帮我们生成唯一的一串标识
      // 这里使用nanoid,和uuid类似但更轻便,引入后是一个函数,直接调用就可以
      const todo = {id:nanoid(),title:e.target.value,done:false}
      // 这里包装好数据后,由于目前没有学习给兄弟传数据,
      // 所以todos实际应该是存在父亲App中的,这样对于孩子想修改父亲的东西才能实现
      // App把这个函数传给子节点,然后我们在这里调用这个函数执行函数,传入要添加的事件 
      this.addTodo(todo)
      e.target.value = ''
    }
  },
}
</script>

<style scoped>
.todo-header{
  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,0,0.075),0 0 8px rgba(82,168,236,0.8);
}
</style>
<!-- MyItem.vue -->
<template>
  <li>
    <label>
      <!-- 通过传进来的事件的done属性判断事件是否勾选,给checked加上冒号作为被绑定的数据 -->
      <!-- 给标签绑定状态改变事件 -->
        <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" />
        <!-- 通过传进来的todo的title展示 -->
        <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo)">删除</button>
  </li>
</template>

<script>
export default {
  name:'MyItem',
  // 先声明好要传进来的数据名
  props:['todo','checkTodo','deleteTodo'],
  methods:{
    handleCheck(id){
      // 通知App给对应的todo的done取反
      this.checkTodo(id)
    },
    handleDelete(t){
      if(confirm("确定删除待办 ‘"+t.title+"’ 吗?")){
        this.deleteTodo(t.id)
      }
    }
  }
}
</script>

<style scoped>
li{
  list-style: none;
  height: 36px;
  padding: 0px 5px;
  border-bottom: 1px solid #ddd;
}
li:hover{
  background-color: #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:hover button{
  display: block;
}
li::before{
  content: initial;
}
li:last-child{
  border-bottom: none;
}
</style>
<!-- MyList.vue -->
<template>
    <ul class="todo-main">
        <MyItem v-for="item in todos" 
        :key="item.id" 
        :todo="item" 
        :checkTodo="checkTodo"
        :deleteTodo="deleteTodo"
       />
    </ul>
</template>

<script>
import MyItemVue from "./MyItem.vue"
import MyItem from "./MyItem.vue"
export default {
    name:'MyList',
    props:['todos','checkTodo','deleteTodo'],
    components:{MyItem},
}
</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>
<!-- App.vue -->
<template>
<div id="App">
  <div class="todo-container">
    <div class="todo-wrap">
      <MyHeader :addTodo="addTodo" />
      <!-- 这里一定要写冒号,这样双引号内的数据才会作为表达式被解析 -->
      <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <MyFooter :checkAllTodo="checkAllTodo" :deleteAllDoneTodo="deleteAllDoneTodo" :todos="todos"/>
    </div>
  </div>
</div>
</template>

<script>
import MyList from './components/MyList.vue'
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
export default {
  name:'App',
  components:{
    MyList,
    MyHeader,
    MyFooter
  },
  data(){
    return{
      // 事件很多,而且每个事件除了事件名,还有循环遍历时的唯一标识id,事件是否完成的布尔值,所以用数组包对象最合适
        todos:[
          {id:'001',title:'吃饭',done:true},
          {id:'002',title:'睡觉',done:true},
          {id:'003',title:'抽烟',done:true},
          {id:'004',title:'烫头',done:false}
        ]
    }
  },
  methods:{
    // 添加todo
    addTodo(e){
      this.todos.unshift(e)
    },
    // 勾选和取消勾选todo
    checkTodo(id){
      this.todos.forEach((todo)=>{
        if(todo.id===id)
          todo.done = !todo.done
      })
    },
    // 勾选或取消全部
    checkAllTodo(f){
      this.todos.forEach((todo)=>{
        todo.done = f
      })
    },
    deleteTodo(id){
      // 删除单个待办
      this.todos = this.todos.filter((todo)=>{
        return todo.id!==id;
      })
    },
    deleteAllDoneTodo(){
      // 删除所有已完成的待办
      this.todos = this.todos.filter((todo)=>{
        return todo.done === false;
      })
    }
  }
}
</script>

<style>
body{
  background: #ffff;
}
.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,255,0.2), 0 1px 2px rgba(0,0,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>
/*
    main.js
    整个项目的入口文件
*/
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭vue生产提示
Vue.config.productionTip = false
// 创建Vue实例对象
new Vue({
    el: '#app',
    // 将App组件放入容器中
    render: h => h(App),
})

029 浏览器本地存储 webStorage

1)基本认识

  1. 存储内容一般支持5MB(不同浏览器可能不一样
  2. 浏览器端通过Window.sessionStorageWindow.localStorage属性来实现本地存储机制

2)相关API

  1. xxxxStorage.setItem('key', 'value');添加
    添加一个缓存,如果键值已经存在了,就替换它
  2. xxxxStorage.getItem('key');获取
    返回值为键对应的值
  3. xxxxStorage.removeItem('key');删除
    把该键对应的内容从存储中删除
  4. xxxxStorage.clear();清空
    清空所有存储数据

3)注意

  1. sessionStorage存储的内容会随着浏览器关闭而清空localStorage不会,只能手动删除。
  2. 如果getItem(xxx)对应的value获取不到,那么返回值是null而不是undefined
  3. JSON.parse(null)的结果还是null

TodoList案例 本地存储版

数据在App.vue中,所以要想到我们也该在这个组件中操作
明确存在缓存中的是什么:是完整的todos,包括它的每一个元素内部的属性和属性值
当数据发生改变时,就更新这个缓存
那么我们想到监视属性,监视todos,而且是深度监视,当他发生改变就更新缓存
同时也应该对todos的初始值做修改,每次打开页面初始化todos都应该从浏览器缓存中找

data(){
    return{
         // 事件很多,而且每个事件除了事件名,还有循环遍历时的唯一标识id,事件是否完成的布尔值,所以用数组包对象最合适
         todos:JSON.parse(localStorage.getItem('todos'))
     }
 },
 watch:{
 	todos:{
	   // 注意要深度监视,当todo的每个对象的每个属性改变时也要更新
	    deep:true,
	    handler(value){
	        localStorage.setItem('todos',JSON.stringify(value)) || []
	    }
	}
},

030 组件自定义事件

  1. 一种组件间的通信方式,适用于:子组件===>父组件
  2. 使用场景:A是父组件,B是子组件,B想给A传递数据,那么就要在A中给B绑定自定义事件,**事件回调是在A中的,**这样B触发事件A才会接到数据
  3. 绑定自定义事件的方式
    (1)第一种:在父组件中<Demo @自定义事件名='回调函数'/><Demo v-on:自定义事件名='回调函数'/>
    (2)第二种:在父组件中先用ref给组件弄个别名,然后在配置项中通过ref的语法拿到组件,给它绑定自定义事件和回调函数,这种写法自由度更高
    <Demo ref='ref名'/>
    ...
    mounted(){
    	this.$refs.ref名.$on('自定义事件名',this.回调函数)
    }
    

(3)如果想事件只触发一次,也可以用once修饰

  1. 触发自定义事件:this.$emit(‘自定义事件名‘, 参数)在子组件中触发
  2. 解绑自定义事件:this.$off(‘自定义事件名’)在子组件中解绑
  3. 在组件上也可以绑定原生DOM事件,不过要用native修饰符,否则会当作自定义事件
  4. 注意:通过this.$refs.ref名.$on('自定义事件名',this.回调函数)绑定自定义事件时,回调函数要么写在methods配置中,要么就用箭头函数,否则回调函数中的this将指向执行这个回调函数的组件!

TodoList自定义事件版本

需要修改的是子传父函数,之前我们的做法是在父组件中写好函数,然后通过组件标签把函数传给子组件props接收
现在我们修改子组件标签的写法,改为给子组件绑定自定义事件,事件名就和回调名一样,那么子组件就不需要在props中接收这个数据,而是直接使用$emit就可以触发事件

<!-- 父组件 -->
<!-- 这里todos是数据,目前还是只能通过props传递,而且对于list和item两个组件,有多层关系,也不要用这种方式改 -->
<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @deleteAllDoneTodo="deleteAllDoneTodo"/>
// 子组件触发事件
methods:{
    checkAll(e){
      // 选择框可以通过e.target.checked拿到选择的情况
        this.$emit('checkAllTodo',e.target.checked);
    },
    handleAllDelete(){
      if(confirm("确定那个删除所有已完成事项吗?")){
        this.$emit('deleteAllDoneTodo');
      }
    },
  },

031 全局事件总线

  1. 一种组件间通信的方式,适用于任意组件间通信。
  2. 安装全局事件总线
    // main.js文件中挂载全局事件总线
    new Vue({
    	beforeCreate() {
            // 安装全局事件总线,一般叫$bus
            // 在vm创建后,但是数据代理模板解析等等都没有开始前,在Vue原型上挂载vm,也就是这里的this
            Vue.prototype.$bus = this
    	}
    })
    
  3. 使用事件总线
    (1)接收数据:A组件想要接收数据,则在A组件中给$bus绑定自定义事件,事件回调就留在A自身,这样别的组件在调用这个回调就能传数据给A
    mounted(){
       // 这里挂载后给x绑定一个hello事件
        this.$bus.$on('hello',(data)=>{
            console.log('我是school组件,我收到数据',data)
        })
    },
    beforeDestroy(){
        // 一般在销毁组件之前,销毁掉这个事件,要不然它一直占着事件名,浪费资源,容易冲突
        this.$bus.$off('hello')
    }
    
    (2)提供数据:在要给A组件发送数据的组件中,使用 b u s 的触发事件发送数据 ‘ t h i s . bus的触发事件发送数据`this. bus的触发事件发送数据this.bus.$emit(‘事件名’,数据)`
  4. $bus事件挂载后,最好在组件销毁前最好把事件从总线上解绑

TodoList全局事件总线版

在这个案例中,Item向App传递了数据,但是怎么实现的呢,App先把函数传给List,List再把函数传给Item,这样逐层传递很麻烦,而且浪费资源,我们在这里用全局事件总线,简化了List作为中间人的步骤

  • 那么我们要先再main.js中给Vue的原型上绑定全局事件总线然后在App(接收数据)组件的mounted中绑定好事件,写好回调函数,然后在Item对应的事件(比如勾选事件、单个删除事件)的回调中触发$bus的事件,这样App中的回调函数就能执行并且有数据传递了
// main.js
beforeCreate() {
  Vue.prototype.$bus = this
},
// App.vue
mounted(){
  this.$bus.$on('checkTodo',this.checkTodo)
  this.$bus.$on('deleteTodo',this.deleteTodo)
},
beforeDestroy(){
  this.$bus.$off('checkTodo')
  this.$bus.$off('deleteTodo')
}
// MyItem.vue
methods:{
  handleCheck(id){
    // 通知App给对应的todo的done取反
    this.$bus.$emit('checkTodo',id)
  },
  handleDelete(t){
    if(confirm("确定删除待办 ‘"+t.title+"’ 吗?")){
      this.$bus.$emit('deleteTodo',t.id)
    }
  }
}

032 消息订阅与发布 pubsub

订阅消息:消息名
发布消息:消息内容

  1. 一种组件间的通信方式,适用于任意组件间通信
  2. 使用步骤:
    (1)安装pubsub:npm i pubsub-js
    (2)引入import pubsub from 'pubsub-js',不管是订阅消息还是发布消息的组件都要引入这个模块
    (3)订阅消息 subscribe:A组件想接收数据,那么A订阅消息(订阅消息个人理解就像是约定好,定义好一个消息),订阅回调留在A组件中
    特别注意 : * 回调函数的第一个参数是消息名,在接收时记得占一个位置,后面的才是传递的数据*
    methods:{
    	demo(data){...}
    },
    mounted(){
    	// 这里的返回值是消息的唯一pid,最好保存在this上,后面好取消订阅
    	this.pid = pubsub.subscribe('消息名', 回调/this.demo)
    }
    
    (4)发布消息 publish:B组件给A组件提供数据,那么B发布消息pubsub.publish('消息名', 数据)
    (5)取消订阅 unsubscribe:最好在组件销毁前取消消息订阅,pubsub.unsubscribe(pid)

TodoList 编辑功能

TodoList可编辑版

编辑功能:点击每个li的编辑功能,title变成输入框,输入完成后,输入框失去焦点就更新title。

1)基础功能实现

  • 先给Item页面增加编辑按钮,以及输入框,并且当要输入时,显示输入框,隐藏编辑按钮,当不输入时,显示原来的span,并且显示编辑按钮,编辑按钮其他逻辑和原来的删除按钮一样
  • 那么可以定义一个变量isEdit,用它的值判断当前是编辑状态还是展示状态
  • 给按钮绑定编辑事件,给输入框绑定失去焦点事件
    Item布局
<li>
  <label>
    <!-- 通过传进来的事件的done属性判断事件是否勾选,给checked加上冒号作为被绑定的数据 -->
    <!-- 给标签绑定状态改变事件 -->
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" />
      <!-- 通过传进来的todo的title展示 -->
      <span v-show="!todo.isEdit">{{todo.title}}</span>
      <!-- 输入框 -->
      <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)">
  </label>
  <button class="btn btn-danger" @click="handleDelete(todo)">删除</button>
  <!-- 编辑按钮 -->
  <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
</li>
// 编辑事件,完成输入框的切换
handleEdit(t){
  // 这里不要直接加isEdit,直接加不能引起Vue重新解析模板
  // 还要判断todo上面是不是已经有了isEdit,已经有了就不要再添加了,浪费资源
  if(t.hasOwnProperty('isEdit'))
    t.isEdit = true
  else
    this.$set(t,'isEdit',true)
},
// 失去焦点事件,完成数据更新
handleBlur(t,e){
  t.isEdit = false
  if(!e.target.value.trim())
    return alert("输入不能为空");
  this.$bus.$emit('updateTitle',t.id,e.target.value)
}
// App中的数据更新回调函数,全局总线的绑定和解绑这里省略
updateTitle(id,title){
  this.todos.forEach((todo)=>{
    if(todo.id===id)
      todo.title = title
  })
}

2)$nextTick

语法this.$nextTick(回调函数)
作用:在下一次DOM更新后执行回调
使用位置:当改变数据后,要基于新的DOM进行一些操作但是当前函数不结束DOM不会更新,此时用$nextTick就很合适

TodoList案例:
前面的写法还可以改进,就是当我们点击完编辑后,输入框自动获取焦点,这样就不用手动点了,获取焦点的写法是:对象.focus()
但是如果我们在handleEdit按钮点击事件回调函数中直接写获取焦点,有一个问题就是,此时页面还没有重新解析,输入框并没有展示,这样获取焦点就无效
有一种解决办法V是ue提供的API $nextTick,它可以指定回调函数,这个函数在DOM节点更新后才执行。

handleEdit(t){
  // 这里不要直接加isEdit,直接加不能引起Vue重新解析模板
  // 还要判断todo上面是不是已经有了isEdit,已经有了就不要再添加了,浪费资源
  if(t.hasOwnProperty('isEdit'))
    t.isEdit = true
  else
    this.$set(t,'isEdit',true)
  this.$nextTick(function(){
    // 这里前提是给input输入框用ref指定一个称 inputTitle
    this.$refs.inputTitle.focus()
  })
},

033 Vue动画

1)作用

在插入、更新或移出DOM元素时,在合适的时候给元素添加样式类名。

2)写法

  1. 单个过渡元素使用<transition>标签包裹要过度的元素,给标签配置name属性;多个过渡元素使用<transition-group>标签包裹。

    <transition name="hello">
    	<h1 v-show="isShow">你好啊</h1>
    </transition>
    
  2. 准备好元素的样式:

    状态进入的样式离开的样式
    起点.v-enter.v-leave
    过程中.v-enter-active.v-leave-active
    终点.v-enter-to.v-leave-to

    这些名称都是要在css也就是style标签中要配置的样式,v是被绑定这些样式的<transition>name属性名,不配置默认为v

  3. 这样我们就只需要控制元素的展示与否,在元素展示状态发生变化时,Vue就会自动帮我们绑定对应样式

3)第三方动画库 animate.css

在npm中有一个animate.css动画库,里面有一些基本的集成动画
使用:

  1. 库引入
    进npm搜这个库的名字点击去就行
    在这里插入图片描述
    点animate.style/进入使用教程,可以在线使用也可以下载使用
    请添加图片描述
    请添加图片描述
    右边这些就是集成动画名,点击可以查看效果,使用的时候点旁边会出现的复制
    下载了这库记得要import引入
  2. 给元素配置动画
    请添加图片描述
    引入这个库,动画样式就不用自己写了,在包裹标签transition中配置好官方规定的name,用enter-active-class配置进入的动画,后面跟的是复制过去的动画名(要使用官方复制按钮),用leave-active-class配置离开的动画。

4)自定义动画和第三方动画库简单使用案例

Vue动画案例

<!-- Student组件(动画组件) -->
<!-- Student.vue -->
<template>
  <div>
    <button @click="changeShow">点我切换</button>
    <transition name="hello">
      <h1 v-show="isShow">你好啊</h1>
    </transition>
    <transition 
    name="animate__animated animate__bounce"
    enter-active-class="animate__backInRight"
    leave-active-class="animate__bounceOut"
    >
      <h1 v-show="isShow" style="background-color: orange">我是第三方库实现的</h1>
    </transition>
  </div>
</template>

<script>
import 'animate.css'
export default {
  name:'Student',
  data(){
    return {
      isShow:true
    }
  },
  methods:{
    changeShow(){
      this.isShow = !this.isShow
    }
  }
}
</script>

<style>
h1{
  background-color: pink;
  width: 500px;
  height: 80px;
}
.hello-enter-active{
 animation: xiaoshi 0.5s linear;
}
.hello-leave-active{
 animation: xiaoshi 0.5s linear reverse;
}
@keyframes xiaoshi{
  from{
    transform: translateX(-100%);
  }
  to{
    transform: translateX(0px)
  }
}
</style>
<!-- App组件,引入动画组件注册使用即可 -->
<!-- App.vue -->
<template>
  <div id="App">
    <Student/>
  </div>
</template>

<script>
import Student from './components/Student.vue'

export default {
  name:'App',
  components:{
    Student
  }
}
</script>

<style>
</style>

四、Vue中的ajax

034 Vue解决跨域问题

1)什么是跨域

跨域:违背了同源策略——协议名、域名、端口号必须一致。
就是说一个页面请求访问另一个页面时,要遵循同源策略,请求跨域正常发送到服务器,但是服务器不会返回数据。

2)解决跨域

  1. 方式一:CORS
    标准的解决方法,这种方式不用前端人员做任何处理,需要后端人员封装好(配置好特殊响应头),使用CORS后,在请求页面时会携带配置好的特殊响应头这是真正的解决跨域
  2. 方式二:JSONP
    一种巧妙的方式,借助了script标签在引入外部资时,不受同源策略限制的特点,他需要前后端配合,而且只能解决get请求,开发中用的很少。
  3. 方式三:配置代理服务器(常用)
    为了解决客户端和服务端的跨域问题,我们可以配置代理服务器,它起到中间人作用,客服端把请求发给它,它再把请求发给真正的服务器,服务器也先把数据发给它,它再把数据发给客户端,那么它的必须和客户端满足同源策略,服务器之间是没有同源策略这一说法的,没有跨域问题,这样就解决了原始服务器和客户端的跨域问题。
    开发代理服务器方法:
    (1) nginx,后端知识多
    (2)vue-cli,vue脚手架

3)脚手架配置代理——方式一:单个代理

进入Vue脚手架vue-cli配置参考栏,找到devServer.proxy代理服务器这里,根据官方文档配置好。
请添加图片描述

devServer: {
	// 开启代理服务器
	// 注意url,这个代理服务器和我们的请求页面所处环境一样,所以不要写代理服务器的url,
	// 而是给他配置它要转发请求给服务器的完整url
  	proxy: 'http://localhost:5000'
}

脚手架中的public文件夹下的内容就是代理服务的内容,当请求的内容在代理服务器中有时,代理服务器不会转发请求。
这种方式的缺点是,不能配置多个代理,只能把请求转发给一个服务器,还有就是不能灵活控制走不走代理,代理服务有的话就会自动不走代理。

4)脚手架配置代理——方式二:多个代理

还是在配置参考里,官方已经示范了第二种配置多个代理方式
请添加图片描述
按照这个格式就可以写任意多套代理,不要和方式一重复哈,只能选择其中一种代理方式。

  devServer: {
    proxy: {
      '/api': { // '/api'是请求前缀,紧跟在端口号后面,有这个前缀就走代理,没有就不走代理(前缀名api可以随便写)
        target: 'http://localhost:5000',  // 目标服务器
        pathRewrite: {'^/api':''},  // 重写路径配置,key:value形式,这里key我们用正则表达式写
        ws: true, // 用于支持websocket
        changeOrigin: true  // 这个配置用于向服务器发送自己的url时,选择原始真实的url还是符合服务器的虚拟url
      },
      '/demo1': { // 第二个
        target: 'http://localhost:5000',
        pathRewrite: {'^/demo1':''}, 
        // ws、changeOrigin默认为true
      },
      '/demo2': { // 第二个
        target: 'http://localhost:5000',
        pathRewrite: {'^/demo2':''}, 
        // ws、changeOrigin默认为true
      },
    }
  },
  • ‘/api’:请求前缀,紧跟在端口号后面,有这个前缀就走代理,没有就不走代理(前缀名api可以随便写)
  • target:目标服务器url
  • pathRewrite:重写路径(官方文档没有这个配置),这里是跨域配置/api在实际访问中显示在url中的样式。
  • ws:是否支持websocket
  • changeOrigin:是否改变源地址,这个配置用于向服务器发送自己的url时,选择原始真实的url(false)还是符合服务器的虚拟url(true)

特别注意:
这里配置了pathRewrite,重写路径,这是因为我们这里写了前缀,代理服务器转发这个请求url时是带着前缀转发的,这与客户端最开始想要请求的url不一致 ,后端没有配置的话就会访问不到返回404,所以这里重写, 用正则表达式匹配到前缀,把它替换为空串

035 axios库

1)什么是axios

官方定义:是一个基于 promise 的对ajax进行封装的网络请求库,可以用于浏览器和 node.js。
官网:https://www.axios-http.cn/
点击起步查看官方文档
点击起步查看官方文档。

2)使用 axios 请求方式发送请求

可以看官方文档,这里写一些常用的例子。

// 引入模块
const axios = require('axios')
  1. 默认、无参
    axios 默认为get请求方式
    axios({
    	// 请求地址
    	url:'http://localhost:9999/Student'
    }).then(res=>{
    	// then里面只写了一个回调函数,根据promise的原理,这是成功的回调
    	console.log(res)
    }).catch(err=>{
    	// 捕获异常,也就等于是失败的回调
    	console.log(err)
    })
    
  2. 指定请求方式为get的无参数请求
    axios({
    	url:'http://localhost:9999/Student',
    	method:'get' // method配置请求方式
    }).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })
    
  3. get、有参——url携带参数形式
    ? 号分隔 url 和 参数,参数为 key=value 格式,不同参数之间用 : 号分隔
    axios({
    	url:'http://localhost:9999/Student?id=1', 
    }).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })
    
  4. get、有参——params形式
    axios({
    	url:'http://localhost:9999/Student',  
    	params:{
    		id:'1',
    		name:'张三'
    	}
    }).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })	
    
  5. post、无参
    axios({
    	url:'http://localhost:9999/Student', 
    	method:'post',
    }).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })	
    
  6. post、有参——params形式
    使用这种方式实际上就是把参数用?拼接到url后面,是不安全的,一般不要用这种方式
    axios({
    	url:'http://localhost:9999/Student', 
    	method:'post',
    	params:{
    		name:'张三'
    	}
    }).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })	
    
  7. post、有参——data形式
    axios({
    	url:'http://localhost:9999/Student', 
    	method:'post',
    	data:{
    		name:'张三'
    	}
    }).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })
    

注意: axios使用post携带参数请求默认使用application/json,后台有可能收到 name null,
解决方法 :
(1)使用params传递参数
(2)“name=张三”(后面axios.xxx方式中用到了)
(3)服务器端给接收的参数加上@requestBody

3)使用 axios.xxx 请求方式发送请求

xxx是这种方式的请求方式

  1. get、无参
    axios.get('http://localhost:9999/Student'
    ).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })
    
  2. get、有参
    ? 号分隔 url 和 参数,参数为 key=value 格式,不同参数之间用 & 号分隔
    axios.get('http://localhost:9999/Student?id=1', 
    		  {params:{id:1,neme:'张三'}}
    ).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })
    
  3. post、无参
    axios.post('http://localhost:9999/Student'
    ).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })	
    
  4. post、有参——name=张三 形式
    使用 & 连接多个参数,post、有参 推荐使用这种方式
    // 注意这里的数据传递方式
    axios.post('http://localhost:9999/Student', "name=张三&age=20"
    ).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })	
    
  5. post方式有参请求——对象形式
  6. 这种形式传递参数需要后端对传递过去的数据做一个解析,转为json格式
    axios.post('http://localhost:9999/Student', {name:'张三',sex:'男'}
    ).then(res=>{
    	console.log(res)
    }).catch(e=>{
    	console.log(e)
    })	
    

4)axios并发请求

并发请求就是当几个并发的请求都返回之后才处理结果。
使用axios.all([])发起并发请求,并发的几个请求存在数组中作为参数,返回值res也是一个数组,对应的顺序就是all中的请求顺序(也可以直接在返回参数中用多个参数来接,这样就不用拿数组来存取了 then( (res1,res2)=>{ } )

axios.all([
	axios.post('http://localhost:9999/Student', "name=张三&age=20"),  // 请求1
	axios.get('http://localhost:9999/Student?id=1', {params:{id:1,neme:'张三'}}) // 请求2
]).then(res=>{ 
	console.log(res[0]); 
	console.log(res[1]);
}).catch(e=>{
	console.log(e)
})	

5)axios全局配置(默认配置)

指定一些默认配置,它将作用于每个请求。
这样请求就可以不写这些配置而使用默认配置,也就是它们的公共部分。
使用axios.defaults来给axios请求实例添加全局配置。

axios.defaults.baseURL = 'https://api/demo.com';
axios.defaults.timeout = 5000;
axios.get("Student").then().catch();

这样实际请求的url就是在默认配置基础上和axios的配置拼接得到的,并且超时时间配置为5s

6)axios实例创建

我们可以用实例来创建多个请求,实例的原始配置就相当于默认配置,在创建实际请求时用到。
实例就是一个axios,只不过已经配置好了一些内容,在使用时时需要配置剩下的内容。
使用const instance = axios.create()来创建axios实例,使用时,instance.get/post...(url).then().catch()

const instance1 = axios.create({
      baseurl:'http://localhost:8080',
      timeout:'1000'
})

const instance2 = axios.create()// 也可以给实例用默认配置
instance2 .defaults.baseurl = 'http://localhost:9090',
instance2 .defaults.timeout = 3000

//instance1这里用到的参数有 baseurl,timeout,method,url
instance1.post('/userinfo').then(res=>{
  console.log(res)
})

//instance2这里用到的参数有 baseurl,timeout,method,url,params,并且对timeout进行了修改
instance1.get('/orderlist',{
    timeout:'5000'
    params:{}
}).then(res=>{
  console.log(res)
})

7)axios拦截器

  • 官方定义在 请求 或 响应 被 then 或 catch 处理前拦截它们
  • 作用:在每次发起请求或响应时,对操作进行相应的处理。比如发起请求时,我们可以添加一些网页的加载动画、强制登陆,响应时可以进行相应的数据处理。
  • axios给我们提供了两大类拦截器,请求方向 和 响应方向,它们各自又有成功和失败两种,也就是被then或catch处理
  • 使用axios.interceptors.xxx.use方法,传入两个回调函数,第一个对应then,第二个对应catch,注意每个回调函数的末尾都要把数据返回出去,要不然后面的函数拿不到请求或者响应数据结果。
// 使用拦截器 
// 添加请求拦截器 request
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器 response
axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

8)axios的vue模块封装

自定义一个request模块,在其中封装axios,然后在需要的位置引入模块使用。

// 封装

// 封装
import axios from 'axios'

// 方式一,传递三个参数:url,请求成功和失败的回调函数
export function request(config, success, fail) {
  axios({
    url: config
  }).then(res => {
    success(res)
  }).catch(err => {
    fail(err) 
  })
}

// 方式二,传递一个对象作为参数,对象内依然是url、两个回调函数
export function request(config) {
  // 这里封装的时候也是可以有默认配置的
  axios.defaults.baseURL = "http//localhost:9999";
  axios(config.url).then(res => {
    config.sucess(res);
  }).catch(err => {
    config.fail(err);
  })
}

// 方式三,通过promise的方式封装
export function request(config) {
  let newVar = axios.creat({
    url: 'http//localhost:9999',
    timeout: 5000
  });
  // 把这个实例用promise封装起来再返回,其实这样封装是很多余的,因为axios本来就是promise原理
  return new Promise((resolve, reject) => {
    newVar(config).then(res => {
      resolve(res);
    }).catch(err => {
      reject(err);
    })
  })
} 

// 方法四,这才是有意义的封装
export function request(config) {
  let newVar = axios.creat({
    url: 'http//localhost:9999',
    timeout: 5000
  });
  // 返回这个实例
  return newVar(config);
} 
// 使用

// 引入request模块
import { request } from 'request/request'

// 方式一
request('http//localhost:9999/student', res => {
    console.log(res);
}, err => {
    console.log(err);
})

// 方式二
request({
    url: 'student',
    success: res => {
        console.log(res);
    },
    fail: err => {
        console.log(err);
    }
})

// 方式三
request({
    url:'student'
}).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})

// 方式四
request({
    url:'student'
}).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})

036 vue插槽

1)默认插槽

插槽的理解:子组件中的一个占位,当父组件在使用子组件标签时,如果在子组件内部添加了一些结构,我们就把这些结构按照子组件插槽的位置填充。要不然的话子组件是一个复杂结构,父组件添加到结构不能确定放置的位置。
在这里插入图片描述
请添加图片描述

2)具名插槽

定义:就是具有名字的插槽,这样父组件可以在一个子组件里放多个不同结构而且在不同位置。
命名:在子组件用name='xxx'给不同插槽命名在父组件给结构绑定slot='slot名'属性,绑定具名插槽。

<!-- 子组件 -->
<!-- 定义插槽,插槽的位置由子组件决定 -->
<slot name="one">插槽one</slot>
<slot name="two">插槽two</slot>
<!-- 父组件 -->
<Student>
	<!-- 这里就是父组件想给子组件添加的东西 -->
	<h2 slot="one">我是默认插槽</h2>
	<input type="text" slot="two" placeholder="我其实是一个插槽">
</Student>

3)作用域插槽

插槽标签也是可以绑定数据的,当数据在子组件,而父组件想用时,就可以用这种方法传递数据。

<!-- 子组件 -->
<slot :games="games">插槽one</slot>

这个数据被传给添加了插槽的父组件, 父组件必须用 template标签包裹 添加到插槽结构这样才能给 template标签添加scope=’xxx'属性,拿到数据,数据名就叫 xxx , 不用和子组件传过来的数据名一样,但是数据内容一样。

<!-- 父组件 -->
<Student>
	<!-- 包裹插槽填充内容 -->
	<template scope='youxi'> 
		<ul>
			<li v-for="(g,index) in youxi.games" :key="index"> {{g}}</li>
		</ul>
	</template>
</Student>

4)插槽总结

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间的通信方式,适用于 父组件 ==> 子组件。
  2. 分类:默认插槽、具名插槽、作用域插槽
  3. 使用方式:1)2)3)分别有消息用例

五、vuex

037 vuex理解

1)vuex是什么

  1. 概念:专门在vue中实现集中式状态(数据)管理的一个vue插件,对vue应用中多个组件的共享状态进行集中式管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。vuex里的数据相当于全局数据,所有组件都可以用。
  2. GitHub地址:https://github.com/vuejs/vuex
    官方文档:https://vuex.vuejs.org/guide/

2)什么时候用vuex

  1. 多个组件依赖于同一状态
  2. 来自不同组件的行为需要变更同一状态(多个组件不同行为改变同一个数据)
    就是:共享

038 vuex工作原理

在这里插入图片描述

  • vue components:vue组件,他是改变数据的行为发起者。
  • actions:与后端交互的部分,它接收组件发起的行为,并且能够从后端拿到数据,整合后交给mutations
  • mutations:真正的实现者,根据拿到的数据和组件发起的行为更新数据。
  • state:状态,也就是数据。
  • devtools:开发者工具,原理图中指明了开发者工具关注的是mutations,并不能观测到其他几个部分。
    注意:当组件对状态的更新修改与后端没有联系时,可以越过actions直接到mutations

只有dispatchcommit是API,需要使用者亲自调用。

039 vuex环境搭建

1)安装vuex

下载:npm i vuex@3
要注意vuex与vue版本
目前来说,vue2只能用vuex3,vue3只能用vuex4,否则会报错,安装不了。

2)引入vuex

import Vuex from 'vuex'
Vue.use(Vuex)

在引入了vuex以后,我们就可以给vue实例添加 store配置项了。

3)store配置项

actions、mutations、state都是由Vuex管理的,Vuex中用store管理它们,这三个是store的核心配置项,还有其他的配置项
store通常vuex是用一个专门的js文件来保存,这样方便维护。
vue中的格式是,在src新建一个文件夹store,下面的index.js文件用来暴露store

4)求和案例 vuex版

案例效果

Vuex案例

案例结构
在这里插入图片描述

index.js文件,在其中配置原理图的三个配置项:actions、mutations、state

// index.js
// store

// 引入Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 要在创建store之前使用vuex,否则会报错
Vue.use(Vuex)

// actions用于响应组件的动作
// 组件中使用的是这里配置的函数
const actions = {
  // 这里是store的一个推进过程,需要用到store的API,所以函数第一个参数是 一个 上下文对象,由部分的store内容组成
  cheng10(context, n) {
    context.commit('Cheng10',n)
  },
  add1(context) {
    context.commit('Add1')
  }
}

// mutations用于操作数据
// actions中的函数拿到数据后调用这里的函数
// 通常来说这两部分的函数名都一样,在mutations中首字母大写来区分
const mutations = {
  // 这里对数据真实的操作,所以函数第一个参数是state
  Cheng10(state, n) {
    state.num += n;
  },
  Add1(state) {
    state.num += 1;
  }
}

// state存储数据
const state = {num:1}

// 创建store
// 前面为了方便只是写好了三个配置项以及内容,但是并没有在store中声明注册,这里名称和值名字相同直接简写
const store = new Vuex.Store({
  // 在store中声明定义这三个配置
  actions,
  mutations,
  state
})

// 暴露store
export default store

项目组件 add1.vue 和 change10.vue,在组件中要使用共享数据的地方,调用vc上,其实是vs上(main.js中引入以后就有了)的store中的dispatch这个API,使用store中的共享内容,有数据有函数。

// add1.vue

<template>
  <button @click="add1">点我+1</button>
</template>

<script>
import { $dataMetaSchema } from 'ajv'

export default {
  name:'add1',
  methods:{
    add1(){
      this.$store.dispatch('add1')
    }
  }
}
</script>

<style scoped>
button{
  background-color: skyblue
}
</style>
// change10.vue
<template>
  <button @click="Cheng10">点我+10</button>
</template>

<script>
export default {
  name:'cheng10',
  methods:{
    Cheng10(){
      // 组件是事件的发起者,当他需要使用某些共享数据时
      // 调用 dispatch 这个API进入下一个阶段
      // 第一个参数是调用的函数名,第二个往后的参数就是携带的数据
      this.$store.dispatch('cheng10',10)
    }
  }
}
</script>

<style scoped>
button{
  background-color: pink;
}
</style>

App组件中正常引用两个子组件。

main.js文件中要引入store下的index文件

/*
	main.js
    整个项目的入口文件
*/
// 引入Vue
import Vue from 'vue'
import Vuex from 'vuex'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'

// 引入store
import store from './store/index'

Vue.use(Vuex)
// 关闭vue生产提示
Vue.config.productionTip = false

// 创建Vue实例对象
new Vue({
    el: '#app',
    // 将App组件放入容器中
    render: h => h(App),

    store,
})

5)求和案例细节修改

之前在原理图说过组件可以越过actions直接访问到mutations,那么我们在组件中调用store上的函数方法时,直接使用commit调用对应方法就可以了,actions中也不需要配置。
例如:
请添加图片描述
在这里插入图片描述

040 vuex开发者工具使用

点击图中按钮切换到vuex视图,veux的开发者工具始终是关注mutations,原理图中有指示。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

041 getters配置项

  1. 概念:当state中的数据需要经过加工后再使用,可以使用getters加工
  2. 个人理解getters相当于computed,就是原数据的计算属性。
  3. 使用方法:先定义,记得在store中注册,在组件中要使用这类数据就和state一样使用即可{{$store.getters.dashu}}
// state存储数据
const state = { num: 1 }

// getters
const getters = {
  dashu(state) {
    return state.num * 10;
  }
}

042 四个mapXXX方法的使用

mapState方法

用于映射state中的数据作为计算属性

computed: {
  // 对象写法
  ...mapState({
    sum: 'sum',
    school: 'school',
  }),
  // 数组写法
  ...mapState([
    'sum',
    'school'
  ]),

mapGetters方法

用于映射getters中的数据作为计算属性

computed: {
  // 对象写法
  ...mapMutations({
    bigSum: 'bigSum',
    nextName:'nextName'
  }),
  // 数组写法
  ...mapMutations([
    'bigSum',
    'nextName'
  ])
}

mapMutations方法

借助mapMutations生成对应的方法,方法中会调用commit去联系mutations
需要注意的是,这种写法在生成方法是不能传参,如果需要传参,只能在使用方法的地方传参

<!--- 这里的n是data中现有的数据 --->
<span> {{jia10(n)}} </span>
methods: {
  // 对象写法
  ...mapActions({
    jia10: 'Add10',
    x10:'Cheng10'
  }),
  // 数组写法
  ...mapMutations([
    'Add10',
    'Cheng10'
  ])
}

mapActions方法

借助mapActions生成对应的方法,方法中会调用dispatch去联系actions

methods: {
  // 对象写法
  ...mapActions({
    jia10: 'add10',
    x10:'cheng10'
  }),
  // 数组写法
  ...mapActions([
    'add10',
    'cehng10'
  ])
}

043 veux模块化+命名空间(namespaced)

好用的工具工具: http://api.uixsj.cn/hitokoto/get?type=social
一个免费的服务器,每次访问都会给一段随机文字。

1.目的

让代码更好维护,让多种数据分类更明确
当组件很多,共享的数据方法很多时,把他们都放到原来的四个模块里,会很混乱,不好维护。

2.方法

  • 我们可以把组件分组,按照不同功能和共享数据的不同,把它们用到的数据和方法提出来,作为一个单独的模块;
  • 每个模块命名不同,但是内部同样都是由四个部分组成;
  • 每个模块放到不同的js文件中保存在store目录下,然后原来的index文件中注册这几个模块,那么此时的store实际上就是包含这几个不同模块;
  • 在组件使用数据时,首先要引入对应模块文件,然后,不能再像原来那样直接用了,而是模块名.数据/方法的格式使用
  • 模块化的vuex在使用时有很多种简写方式

3.修改store

在每个模块加上namespaced: true开启命名空间,默认为false

// add1模块
const add1 = {
  namespaced: true, // 命名空间开启
  state: { sum: 1 },
  mutations: {  },
  actions: {  },
  getters: { bigSum() { return state.sum + 1 } }
}

// cheng10模块
const cheng10 = {
  namespaced: true, // 命名空间开启
  state: {  },
  mutations: {  },
  actions: {  },
  getters: {  },
}

const store = new Vuex.Store{
  moudles: {
    add1,
    cheng10
  }
}

4.开启命名空间后,组件中读取state数据

// 方式1:自己直接读取
this.$store.state.add1.list
// 方式2:借助mapState读取
...mapState('countAbout',['sum','school','subject'])

5.开启命名空间后,组件中读取getters数据

// 方式1:自己直接读取
this.$store.getters['personAbout/firstPersonName']
// 方式2:借助mapGetters读取
...mapGetters('countAbout',['bigSum'])

6.开启命名空间后,组件中调用dispatch

// 方式1:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
// 方式2:借助mapActions读取
...mapActions('countAbout', { incrementOdd:'add1',incrementWait:'addWait'})

7.开启命名空间后,组件中调用commit

// 方式1:自己直接commit
this.$store.commit('personAbout/add_Person',person)
// 方式2:借助mapMutations读取
...mapMutation('countAbout', {increment:'Add1'})

第6章 路由

044 路由相关理解

1)vue-router的理解

是vue的一个插件库,专门用来实现SPA应

2)SPA应用的理解

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

3)路由的理解

什么是路由

  • 一个路由就是一组映射关系(key-value)
  • key为路径,value可能是function或者component

路由分类

  1. 后端路由
    1)理解:value是function,用于处理客户端提交的请求
    2)工作过程:服务器收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
  2. 前端路由
    1)理解:value是component,用于展示页面内容
    2)工作过程:当浏览器路径改变时,对应的组件就会显示

045 路由的基本使用

1)基本使用方法

  1. 安装路由 npm i vue-router@3
    vue2只能用vue-router3,vue3只能用vue-router4
  2. 在main.js中引用和应用路由插件
    // 引入路由插件
    import VueRouter from 'vue-router'
    // 应用插件
    Vue.use(VueRouter)
    
    之后vue上就有router配置项了,且配置的文件名必须为router
  3. 确定页面布局
    导航区放到app,要展示的页面部分放到不同的组件
  4. 编写router配置项
    // 项目路由配置文件
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    // 引入组件
    import About from './components/About'
    import Home from './components/Home'
    // 应用路由插件
    Vue.use(VueRouter)
    
    // 创建并暴露一个路由器
    export default new VueRouter({
      routes: [
        {
          path: '/about',
          component:About
        },
        {
          path: '/home',
          component:Home
        },
      ]
    })
    
  5. 实现切换
    点击标签后url就变为对应的配置
<!-- 单页面组件使用路由提供的标签 -->
<!-- 最后实际上还是转换成了a标签,不过还有其他操作 -->
<!-- to后面不要写路径,应该写配置的页面路由 -->
<router-link to="/about" class="list-group-item" active-class="active">About</router-link>
<router-link to="/home" class="list-group-item" active-class="active">Home</router-link>
  1. 指定展示位置
    根据当前url和路由配置展示指定内容,指定位置
<!-- 路由占位符 -->
<router-view></router-view>

2)几个使用注意注意事项

  1. 路由分类
    使用路由配置的组件称为路由组件,需要手动引用的组件(使用“<>")称为一般组件
    一般用components文件夹放一般组件,用pages文件夹放路由组件。
  2. 在切换不同路由组件时,被切换的组件是被销毁掉了
  3. 整个应用只有一个router
  4. 每个组件上都有一个$route,它们是不同的

046 嵌套路由(多级路由)

  1. 配置路由规则,使用children配置项
    注意:配置时子路由不要加 / ,否则url会变成一级路由的样子
    routes: [
      {
        path: '/about',
        component: About,
      },
      {
        path: '/home',
        component: Home,
        // 子路由配置
        children: [
          {
            // 子路由表不用加 /
            path:'news', 
            component: News,
          },
          {
            path:'message',
            component: Message,
          }
        ]
      },
    ]
    
  2. 跳转
    注意:路径要写完整,加上父路由路径
    <router-link class="list-group-item" active-class="active"
     to="/home/news">News</router-link>
    

047 路由的query参数

  1. 传递参数
    router-link标签的to属性可以写成对象,利用这一特点来传递query参数,比模板字符串写法更清晰。
    <li v-for="m in messageList" :key="m.id">
    
    <!-- 路由query参数——模板字符串写法 -->
    <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>
    
    <!-- 路由query参数——to的对象写法 -->
    <router-link :to="{
      path: '/home/message/detail',
      query: {
        id: m.id,
        title: m.title
      }
    }">
    {{m.title}}
    </router-link>
    
  2. 接收参数
    传递过去的参数挂载在vm的$route的query上
    $route.query.id
    $route.query.title
    

048 命名路由

  1. 可以简化路由的跳转
  2. 使用
    1)给路由用name配置项命名
    {
      name:'detail'
      path: 'detail',
      component: Detail,
    },
    
    2)简化跳转
    简化前,要写完整路径
    <router-link :to="/home/message/detail">{{m.title}}</router-link>
    
    简化后,只需要写出路由名称,
    这种写法必须用key:value格式,所以to属性必须写成对象格式
    <router-link :to="{name:'detail'}">{{m.title}}</router-link>
    

049 路由的params参数

  1. 配置路由,声明接收params参数
    {
     // 接收id和title两个参数,这里的声明是占位的意思
     name:'detail',
      path: 'detail/:id/:title',
      component:Detail,
    }
    
  2. 传递参数
    同样有两种写法,需要注意的是,to的对象写法中,路径配置只能用name,否则拿不到正确数据
    <router-link :to="`/home/message/detail/666/你好啊`">{{m.title}}</router-link>
    <router-link :to="{
      name:'detail',
      params: {
        id: m.id,
        title: m.title
      }
    }">
    {{m.title}}
    </router-link>
    
  3. 接收参数
    $route.params.id
    $route.params.title
    

050 路由的props配置

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

{
  name:'detail',
  path: 'detail/:id/:title',
  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
    }
  }
}

051 router-link的replace属性

  1. 作用:控制路由跳转时,操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:pushreplace,push是追加历史,replace是替换当前记录。默认为push
  3. 如何开启replace模式:<router-link replace ......>News</router-link>,replace默认为false,为true就是开启了。

052 编程路由导航

  1. 作用:不借助router-link实现跳转,让路由跳转更灵活
  2. 具体编码:
    $router是vm上的,它包含几个常用API
    1)push,按照push模式跳转到页面
    2)replace,按照replace模式跳转到页面
    3)forward,按照历史记录前进一步
    4)back,按照历史记录后退一步
    5)go,跳转自定义步数,正数为前进,负数为倒退
    // $router的两个API
    // push
    this.$router.push({
    	name:'detail',
    	params:{
    		id:xxx,
    		title:xxx
    	}
    })
    // replace
    this.$router.replace({
    	name:'detail',
    	params:{
    		id:xxx,
    		title:xxx
    	}
    })
    
    // 其他API
    this.$router.forward()
    this.$router.back()
    this.$router.go(3) // this.$router.go(-3)
    

053 缓存路由组件 keep-alive

  1. 作用:让不展示的路由组件保持挂载,不被销毁(有时候组件里有还未保存的信息,切走后就被销毁了,这些数据也没了)
  2. 具体编码
    用 keep-alive 标签包裹想要缓存的路由组件展示位置
    include表示要缓存的组件名,不写表示缓存所有组件,多个用数组,写成数组要在前面加上冒号,表示引号内为js代码
    <keep-alive include="News">
    <!-- <keep-alive :include="['News','Message']"> -->
      <router-view></router-view>
    </keep-alive>
    

054 两个生命周期钩子 activated、deactivated

  1. 作用:路由组件所独有的两个钩子用于捕获路由组件的激活状态
  2. activated:路由组件被激活时触发
  3. deactivated:路由组件失活时触发

055 路由守卫

  1. 作用
    路由权限进行控制
  2. 分类:全局守卫独享守卫组件内守卫
  3. 全局守卫:所有路由切换时都要走这一步
    beforeEach((to, from, next) => { }) // 每次路由切换前
    afterEach((to, from) => { }) // 每次路由切换后
    // 全局前置路由守卫
    router.beforeEach((to, from, next) => {
      // to and from are both route objects. must call `next`.
      if (to.meta.isAuth)
        alert('您没有权限查看!')
      else
        next()
    })
    
    // 全局后置路由守卫
    router.router.afterEach((to, from) => {
      // to and from are both route objects.
      if (to.meta.title)
        document.title = to.meta.title;
      else
        document.title = "vue_test";
    })
    
  4. 独享守卫:某个路由单独使用的守卫
    beforeEnter: (to, from, next) => { } // 进入路由时调用,只有这一个
    routes:[
    	{
    	  // 子路由表不用加 /
    	  path:'news', 
    	  component: News,
    	  meta: { isAuth: true },
    	  beforeEnter: (to, from, next) => {
    	    if (xxx)
    	      next();
    	    else
    	      xxx;
    	  }
    	},
    ]
    
  5. 组件内守卫:某个组件单独使用的守卫,作为组件的配置项
    beforeRouteEnter((to, from, next) => { }) // 进入组件时调用
    beforeRouteLeave((to, from, next) => { }) // 离开组件时调用

056 路由器的两种工作模式(hash、history)

  1. 对于一个url来说,什么是hash值?——#及其后面的内容(http://localhost:8080/#/home/news)
  2. hash值不会包含在HTTP请求中,即hash值不会带给服务器
  3. hash模式:
    1)地址中永远带着#号,不美观
    2)若以后将地址通过第三方APP分享,若APP校验严格,则地址会被标记不合法
    3)兼容性好
  4. history模式
    1)地址干净、美观
    2)兼容性和hash模式相比略差
    3)应用部署上线时需要后端人员支持来解决刷新页面服务端404问题

工作模式在路由文件中,用mode配置
history模式的部署上线问题在vue平台有成熟的插件来解决。

057 VUE UI组件库

1)移动端常用组件库

  1. Vant: https://youzan.github.io/vant/#/zh-CN
  2. Cube UI: https://didi.github.io/cube-ui/#/zh-CN
  3. Mint UI: https://mint-ui.github.io/#!/zh-cn

2)PC端常用组件库

  1. Element UI: https://element.eleme.cn/#/zh-CN
  2. IView UI: https://www.iviewui.com/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值