目录
4.v-model(把value属性和vue变量双向绑定到一起)
1.vue含义
渐进式javascript框架,而且自底向上,增量开发,组件集合,便于复用
2. Vue和jQuery区别是什么?
jQuery应该算是一个插件, 里面封装了各种易用的方法, 方便你使用更少的代码来操作dom标签
Vue是一套框架, 有自己的规则和体系与语法, 特别是设计思想MVVM, 让数据和视图关联绑定, 省略了很多DOM操作. 然后指令还给标签注入了更多的功能
3.vue-cli的好处和能力
- 统一的项目结构(文件夹+文件+配置代码)
- 开发过程中的webpack各系列支持
- babel支持
- eslint约束语法风格(代码风格)
- 样式预处理器less
- vue单文件支持
- 提供一个开发时服务器,预览代码(预览项目)
- 自动刷新,方便预览
- 热更新 (只刷新修改的部分)
- 基于nodejs的命令行工具
4.vue-cli 安装
目标: 把@vue/cli模块包按到全局, 电脑拥有vue命令, 才能创建脚手架工程
yarn global add @vue/cli
npm install -g @vue/cli
查看vue
脚手架版本
vue --version或者输入vue -V都可以查看是否安装成功
总结: 如果出现版本号就安装成功, 否则失败
注意:路径上不要有vue名字的文件夹, 项目名不能带中文和特殊符号
创建项目
1.基于交互式命令行的方式,创建新版vue项目
# vue和create是命令, vuecli-demo是文件夹名
vue create vuecli-demo
选择模板
可以上下箭头选择, 弄错了ctrl+c重来
回车等待生成项目文件夹+文件+下载必须的第三方包们
进入脚手架项目下, 启动内置的热更新本地服务器 cd就好比"双击"
cd vuecil-demo
npm run serve
# 或
yarn serve
只要看到绿色的 - 啊. 你成功了(底层node+webpack热更新服务)
打开浏览器输入上述地址即可
总结: vue命令创建工程目录, 项目内置webpack本地热更新服务器, 帮我们打包项目预览项目
5.vue-cli覆盖webpack配置
目标:项目中没有webpack.config.js文件,因为vue用的vue.config.js
/* 覆盖webpack的配置 */
module.exports = {
devServer: { // 自定义服务配置
open: true, // 自动打开浏览器
port: 3000
},
lintOnSave: false // 关闭eslint检查
}
注意:每次配置完vue.config.js之后都要重新启动服务器
6.vue-cli 目录分析与清理
vuecil-demo # 项目目录
├── node_modules # 项目依赖的第三方包
├── public # 静态文件目录
├── favicon.ico# 浏览器小图标
└── index.html # 单页面的html文件(网页浏览的是它)
├── src # 业务文件夹
├── assets # 静态资源
└── logo.png # vue的logo图片
├── components # 组件目录
└── HelloWorld.vue # 欢迎页面vue代码文件
├── App.vue # 整个应用的根组件
└── main.js # 入口js文件
├── package.json # 描述项目及项目
├── .gitignore # git提交忽略配置
├── babel.config.js # babel配置
├── README.md # 项目说明
└── package-lock.json # 项目包版本锁定和缓存地址
src/App.vue默认有很多内容, 可以全部删除留下框
assets 和 components 文件夹下的一切都删除掉 (不要默认的欢迎页面)
public/index.html
babel.config.js
main.js
main.js App.vue public/index.html三者的关系图
三个主要文件
7.指令和语法
7.1.插值表达式(声明式渲染)
语法: {{表达式}}
作用:
1.可以在标签处,写表达式和值显示
2.只能用在标签夹着的地方
3.{{}}里只能写表达式,不能写if/for语句
4.把vue变量显示在标签上
7.2.动态属性(v-bind)
语法: v-bind:原生属性='vue变量'
作用: 把vue变量赋予给原生标签的属性
简化写法: 省略v-bind直接写 :
3.事件绑定(v-on)
语法:
v-on:事件类型='一句代码' <button v-on:click='count++'>数量加+</button>
v-on:事件类型='methods里函数名'
v-on:事件类型='methods里函数名(实参)'
拿到事件对象方式:
@事件类型='methods里方法名' - 默认接收e
@事件类型='methods里方法名(实参,$event)' - 需要手动传入$event事件对象到函数里
作用: 当触发标签的事件,会执行methods里的这个方法
4.v-model(把value属性和vue变量双向绑定到一起)
语法:
v-model.修饰符="vue数据变量"
.number 把值转数字(parseFloat)赋予给vue变量(只要被标签一染手,就变成了字符串,number就是为了把value属性值,转成数字再赋予给变量)
.trim 去除两边空格后再赋予给vue变量
.lazy 失去焦点值改变后,才会赋予给vue变量
复习: 输入框事件:
focus - 获得焦点
blur - 失去焦点
change - 失去焦点而且内容改变
input事件 - 实时监测内容改变
其实这里的v-model就相当于input事件,change事件就相当于lazy事件
作用:
1.把vue变量赋予给value属性
2.value属性值改变,同步给vue变量
双向数据绑定
数据变化 -> 更新视图
视图变化 -> 自动同步给vue数据
select绑定v-model,value写在option上
用户选择哪个option,就把对应value值赋予给v-model的vue变量
复选框
v-model的变量如果是 非数组,关联的是checked属性(true/false)
v-model的变量如果是 数组,关联的是value属性(选中了就把value值加到数组里)
单选框
7.5.v-text和v-html
<p v-text="str"></p>
<p v-html="str"></p>v-text:把值当成普通字符串显示,相当于innerText
v-html:把值当成标签解析,相当于innerHTML
注意:指令会覆盖声明式渲染的值(会覆盖插值表达式)
使用场景:一般有富文本的地方会用到
7.6.v-show和v-if
1.v-show和v-if用法(控制所在标签显示或隐藏)
v-show或v-if给一个true值,就显示所在的标签
给一个false值得时候,v-show隐藏靠display:none(应用场景:频繁切换) v-if隐藏直接从DOM树移除掉
2.v-if和v-else配合 和js里的if/else一个意思
3.v-if和v-else-if (多个分支的时候使用)
7.7.v-for
v-for 用数据结构,遍历生成所在标签DOM
口诀: 你想让谁循环,就把v-for写谁身上
语法:v-for='(变量名1,变量名2) in 目标结构'
注意:in两边必须有空格
执行:str接收到每次遍历的值,index每次遍历的索引
v-for可以遍历数组,对象,和固定数字(值是从1开始,因为只有数组才有下标0)
8.事件修饰符
语法:
@事件名.修饰符="methods里函数"
.stop - 阻止事件冒泡
.prevent - 阻止默认行为
.once - 程序运行期间, 事件处理函数只调用一次
@keyup.enter - 检测回车按键
@keyup.esc - 检测返回按键
总结: 修饰符给事件扩展额外功能
注意: 1. 如果需要多个修饰符可以连续一直往后写 eg: @click.prevent.stop
2.可以加方法名,也可以不加方法名 eg: @click.stop 或者 @click.stop="btn"
9.v-for更新监测
只要监测到目标结构新增/删除/顺序改变/重新赋予新数组了(替换) 就会发生更新
改变原数组的方法才能让v-for更新, 如果未更新, 使用Vue.set()方法, 或者覆盖原目标结构
数组排序,翻转可导致v-for更新检测,页面更新
数组截取,修改基础类型的值,都不能导致v-for更新检测
能改变(新增/删除/顺序改变/重新赋予新数组)原数组的, 数组方法, 才能引发v-for的更新(页面的自动变化)
数组方法: sort() / reverse() / splice().... 能触发更新
不能更新方法: slice() / concat() / map() / filter() - 返回的新数组不能触发更新
不能更新的解决方案:
- 解决方案1: 直接把新数组,覆盖回去,赋值给新变量
- 解决方案2:使用vue内置的更新方法,Vue.set() - 相当于this.$set()
参数1:目标结构,参数2:更新哪个下标,参数3:传入值
this.$set(this.arr,0,100)
口诀:
1.只要改变原始数组,就会导致v-for更新检测,页面更新
2.数组方法不行,采用覆盖数组方法和Vue.set()也能让v-for更新检测
10.vue的key属性
目的: 提高dom更新性能,不加key也不影响性能
总结:
1.key属性用不用都不影响功能
2.key属性可以提高 v-for 更新时的性能
3.v-for默认就地更新,如果有key,按照key变化更新
详细: 新的dom结构里没有key(新建),key不存在了-key对应的标签移除,key存在,复用
4.key值不重复的数字或字符串
(使用):有id用id,没有id用索引(就地更新)
vue用diff算法,新虚拟dom,和旧的虚拟dom比较,规则
如果根元素变了 => 删除重建
如果根元素没变,属性改变 => 元素复用,更新属性
如果根元素没变,子元素改变(内容也算子元素)改变
无key: 就地更新
有key: 根据key 复用标签,更新子元素
11.虚拟DOM
- .vue文件中的template里写的标签,都是模板,都要被vue处理后,才能显示到真实DOM页面上
- 内存中生成一样的虚拟DOM结构(本质是JS对象,包含节点信息)
- 流程:template => 虚拟DOM => 显示到网页index.html上
- 因为真实的DOM属性好几百个,没办法快速的知道哪个属性改变了
- 虚拟DOM就是保存节点信息的一个js对象
- 以后vue数据改变生成新的虚拟DOM结构,和旧的虚拟DOM结构对比,只更新变化的部分(重绘/回流)到页面const dom = {
type:"div",
children:[
{
type:"p",
children:["我是p标签"]
}
]
}虚拟DOM的好处:
1.更新只更新变化的部分,页面上会减少回流和重绘,提高了更新DOM的性能
2.虚拟DOM是保存在内存中,体积小,运行效率更高
总结:虚拟DOM保存在内存中,只记录dom关键信息,配合diff算法提高DOM更新的性能
12.动态class
-
不加: 认为后面是普通字符串
-
加: 认为后面的vue变量
语法:
:class='vue变量' 作用:vue变量的值,用作类名给当前标签
:class="['类名1','类名2']" 作用:把多个类名使用
:class="{类名:布尔值}" 作用:当布尔值为true,就把属性(类名)用给class
13.动态style
:style="{css属性:值}" 值类型可以是字符串也可以是变量
eg: :style="{color:colorStr, backgroundColor:'red'}"
14.过滤器
过滤器只能用在,插值表达式和v-bind表达式
目标:
转换格式,过滤器就是一个函数,传入值返回处理后的值
Vue中的过滤器场景:
字母转大写,输入"hello",输出"HELLO"
字符串翻转,"输入hello,world",输出"dlrow,olleh"
语法:
Vue.filter("过滤器名字",(值) => {return"返回处理后的值"})
filters:{过滤器名字:(值) => {return"返回处理后的值"}}
例子:
全局定义字母都大写的过滤器
局部定义字符串翻转的过滤器
总结:
把值转成另一种形式,使用过滤器,Vue3用函数替代了过滤器
全局注册最好在main.js中注册,一处注册到处使用
过滤器代码:
-
过滤器本质上就是一个函数,作用:输入一个值,处理后返回
-
1.定义全局过滤器 - 语法如下
<template>
<div>
<p>{{msg}}</p>
<p>{{msg | toUp}}</p> // 全局过滤器
<p :title="msg | toRev">鼠标停留看看</p> // 局部过滤器
</div>
</template>
<script>
// 1.先引入vue
import Vue from 'vue'
// 2.定义过滤器(全局)
// 参数1:过滤器名(随便起),参数2:过滤器要执行的代码,必须return一个值
// val变量接收到的是 | 左边的结果
Vue.filter("toUp",val => val.toUpperCase())
export default {
data() {
return {
msg:'hello,world'
}
},
// 局部过滤器
filters: {
toRev:val => val.split("").reverse().join("")
}
}
</script>
过滤器传参:
多个过滤器调用:
15.计算属性
目标:
一个数据,依赖另外一些数据计算而得来的结果
语法:
computed:{计算属性名:() => {return计算属性的值}}
注意:
计算属性也是vue数据变量,所以不要和data里重名,用法和data相同
总结:
一个数据,依赖另外一些数据计算而得来的结果
基础语法:
<template>
<div>
<!-- 2.使用计算属性 -->
<p>{{num}}</p> 计算两个数相加的值
<p>{{reverMessage}}</p> 翻转
</div>
</template>
<script>
export default {
data(){
return {
msg:'Hello,world',
a:10,
b:20
}
},
// 1.定义计算属性(也是变量,所以不能和data重名)
computed:{
num() { // num是计算属性名(也是变量名)
return this.a + this.b // 把a和b的和返回给num变量使用
},
reverMessage() {
return this.msg.split("").reverse().join("")
}
}
}
</script>
计算属性和函数的区别:
计算属性 return给reverseMessage这个变量
函数 return给getMessage这个函数调用的地方
<template>
<div>
<p>{{ reverseMessage }}</p>
<p>{{ reverseMessage }}</p>
<p>{{ reverseMessage }}</p>
<p>{{ getMessage() }}</p>
<p>{{ getMessage() }}</p>
<p>{{ getMessage() }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: "我是个字符串",
};
},
computed: {
reverseMessage() {
console.log("计算属性执行");
return this.message.split("").reverse().join(""); // return给reverseMessage这个变量
}
},
methods: {
getMessage(){
console.log("方法执行");
return this.message.split("").reverse().join(""); // // return给getMessage这个函数调用的地方
}
}
};
</script>
console.log打印区别:
结论:
1.计算属性自带缓存,多次使用,直接返回缓存的值,不会频繁触发计算属性函数
2.当计算属性函数里引用的变量发生改变,会自动触发函数执行,重新缓存
使用场景:
当你发现一个变量的值,应该由别人计算而得来,把这个变量定义为计算属性
计算属性根据依赖变量结果缓存,依赖变化重新计算结果存入缓存,比普通方法性能更高
计算属性完整写法:
目标:计算属性也是变量,如果想要直接赋值,需要使用完整写法
语法:
<template>
<div>
<div>
<span>全称</span>
<input type="text" v-model="fullName">
</div>
<div>
<span>姓:</span>
<input type="text" v-model="firstName">
<span>名:</span>
<input type="text" v-model="lastName">
</div>
</div>
</template>
<script>
export default {
data() {
return {
firstName:'',
lastName:''
}
},
computed:{
// 计算属性完整写法:
// 场景: 当你要直接给计算属性赋值的时候,必须写成完整写法
fullName:{
set(val) { // 给fullName赋值(set)的时候,val就是要被赋予的值
// 把第一个字符当做姓,后面的当做名赋予给firstName和lastName
this.firstName = val.substr(0,1) //下标0截取,截取1个
this.lastName = val.substr(1)
console.log(val)
},
get() { // fullName要取值时,触发
console.log('get触发了')
return this.firstName + this.lastName
}
}
}
}
</script>
总结:想要给计算属性赋值,需要使用set方法
16.watch监听器
目标:可以监听data/computed属性值改变
使用场景:监听data/computed里变量的改变
语法:
watch:{
"被监听的属性名" (newVal,oldVal) {
}
}
总结:想要监听一个属性变化,可使用监听属性watch
例子:监听v-model绑定输入框变量的变化
<template>
<div>
<input type="text" v-model="username">
</div>
</template>
<script>
export default {
data(){
return {
username:''
}
},
watch:{
// 参数1:改变后新值 参数2:上一刻旧值
username(newVal,oldVal) {
// 只要监听的变量的值发生了改变,这里马上就触发,newVal接收到的是最新的值
console.log(newVal,oldVal)
}
}
}
</script>
深度监听和立即执行
目标:监听复杂类型,或者立即执行监听函数
语法:
watch: {
"要监听的属性名": {
immediate:true, // 立即监听
deep:true, // 如果监听的变量的值是(数组/对象)复杂数据类型那么就开启深度监听,监听复杂数据类型的变化(想要深度监听里面的值/属性发生改变,则必须设置此项)
// handler方法名固定
handler (newVal,oldVal) {
// 监听到变量值发生改变就马上触发(如果启用了立即监听,则网页打开就触发一次)
}
}
}
总结: immediate立即监听,deep深度监听,handler固定方法触发
例子:
<template>
<div>
<input type="text" v-model="user.nickname">
<input type="text" v-model.number="user.age">
</div>
</template>
<script>
export default {
data(){
return {
user: {
nickname:'',
age:0
}
}
},
// 监听复杂类型的改变
// 需要对此属性进行额外的配置,必须加deep:true
watch:{
user:{
deep:true, // 开启了深度监听(里面属性也监听)
immediate:true, // 立即监听(网页打开就触发一次handler执行)
handler(newVal) { // handler 是固定的
console.log(newVal)
}
}
}
}
</script>
17.Vue组件创建
组件是可复用的Vue实例,封装标签,样式和JS代码
组件化:封装的思想,把页面上'可重用的部分'封装为'组件',从而方便项目的开发和维护
一个页面,可以拆分成一个个组件,一个组件就是一个整体,每个组件可以有自己独立的结构样式和行为(html,css和js)
17.1vue组件_创建
目标:每个组件都是一个独立的个体,代码里体现为一个独立的.vue文件
口诀:哪部分标签复用,就把哪部分封装到组件内
(重要):组件内template只能有一个根标签
(重要):组件内data必须是一个函数,独立作用域
(重要):组件.vue文件,是独立的个体,在当前组件内的方法和变量一定要写在当前script里
总结: 封装标签 + 样式 + js - 组件都是独立的,为了复用
17.2vue组件_注册,引入和使用
全局注册 - main.js中
全局注册之后在哪都能用
import Vue from 'vue'
// 1.引入组件对象
import 组件对象 from 'vue文件路径'
eg:import PannelCom from './components/Pannel.vue'
// 2.注册
// 参数1:组件名(随便定,建议大写开头)
// 参数2:组件对象
Vue.component('组件名',组件对象)
eg:Vue.component('Pannel',PannelCom)
局部注册
// 1.引入组件对象
import 组件对象 from 'vue文件路径'
eg:import Pannel from './components/Pannel.vue'
export default {
// 2.注册
// 注册组件用components
// key是组件名(也是上面要用的标签名)
// value是引过来的组件对象
// 如果key和value同名,可以简写一个key
components: {
'组件名':组件对象
eg:'Pannel':Pannel 省略写法: Pannel
}
}
使用
<template>
<div id='app'>
<h3>案例:折叠面板</h3>
<!--使用组件(把组件的名字当做标签进行使用):运行时,会把这个组件里的template标签替换到这里显示,并且把组件名当做标签使用-->
<Pannel></Pannel>
<Pannel></Pannel>
<Pannel></Pannel>
<div>
</template>
总结:
1.组件创建,组件注册,声明组件名,当做标签使用即可
2.组件名第一个字母最好都大写(代码规范)
18.Vue组件_scoped作用
scoped作用:让当前样式只作用于当前这个组件范围内
scoped原理:在webpack打包的时候,遇到scoped,就会给当前"标签和css选择器"都添加一个data-v-随机数的一个属性,让当前样式里的选择器,只能选中当前页面的标签
19.vue组件通信
父传子
步骤:
1.在子组件里props中声明变量
2.在父组件里使用的时候给变量传值props和data的区别:
data是要给初始值的,而props是要依赖外部给咱传入赋值总结:
父向子传值的目的,为了让组件显示不同的内容,每次调用组件都是独立的
父传子 - 配合循环
目标:把数据循环分别传入给组件内显示
App.vue
<template>
<div>
<MyProduct v-for="obj in list"
:title="obj.proname"
:price="obj.proprice"
:info="obj.info"
:key="obj.id"></MyProduct>
</div>
</template>
<script>
import MyProduct from "./components/MyProduct_13.1";
export default {
components: {
MyProduct
},
data() {
return {
list: [
{ id: 1, proname: "超级好吃的棒棒糖", proprice: 18.8, info: '开业大酬宾, 全场8折' },
{ id: 2, proname: "超级好吃的大鸡腿", proprice: 34.2, info: '好吃不腻, 快来买啊' },
{ id: 3, proname: "超级无敌的冰激凌", proprice: 14.2, info: '炎热的夏天, 来个冰激凌了' },
],
};
},
};
</script>
<style>
</style>
components/MyProduct
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ info }}</p>
</div>
</template>
<script>
export default {
props: ['title', 'price', 'info'] // 声明属性, 等待接收外部传入的值
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
单项数据流
props -> 父向子,单项
props内的变量是只读的,不建议重新赋值
props内的变量的值如果是对象类型的,互相引用,互相影响
注意:不要修改props变量的值
原因:
1.数据导致不一致,(看下图),哪个组件内部改了数据,"基础类型"数据不会影响到props数组的值
2.如果props的值是一个对象(引用关系),改变对象里的属性值,会互相影响
子传父
子触发父的事件,然后往里传值
步骤:
1.App.vue(父),给父组件绑定自定义事件 @自定义事件="父methods里方法名"
2.MyProduct2.vue(子),给真正点击事件中this.$emit("自定义事件",要传的值)
EventBus
目标:常用于跨组件通信时使用
语法:
main.js - 创建一个空的Vue对象,并挂载到项目Vue的原型上(保证所有vue子组件都能访问到空Vue对象)
在要接收值的页面this.$bus.$on("自定义事件名",函数体)
在要传值的页面this.$bus.$emit("对应自定义事件名",实参值)
总结:main.js注册空的Vue对象,只负责$on注册事件,$emit触发事件,一定要确保$on先执行
步骤:
1.main.js里,注册一个全局的对象 Vue.prototype.$bus = new Vue() - 此对象只负责监听和触发事件
2.准备接收数据的.vue文件中,注册事件this.$bus.$on("事件名",(形参) => {})
3.发送数据的.vue文件中,触发事件 this.$bus.$emit("事件名",实参值)
20.Vue生命周期(钩子函数)
目标:vue框架内置函数,随着组件的生命周期,自动按次序执行
作用:特定的时间点,执行某些特定的操作
场景:组件创建完毕后,可以在created生命周期函数中发起Ajax请求,从而初始化data数据
分类:
初始化:beforeCreate,created
挂载:beforeMount,mounted
更新:beforeUpdate,updated
销毁:beforeDestroy,destroyed
打开生命周期官网图片,编写代码案例,来测试各个生命周期方法
beforeCreate() {
// 1. 创建前
console.log("beforeCreate --- 实例初始化后,数据观测之前,创建data和methods事件监听之前");
console.log(this.msg); // undefined
},
created() {
// 2. 创建后
console.log("created --- 实例初始化后, 数据观测以后,创建data和methods事件初始化完毕,创建组件后");
console.log(this.msg); // "我是变量"
},
beforeMount() {
// 3. 挂载前
// template里的vue模板标签,会被vue解析成虚拟DOM(本质上就是一个js对象保存节点信息)
console.log("beforeMount --- 挂载之前,确定替换目标,但是App虚拟DOM未替换到页面上,vue的虚拟DOM, 挂载到真实的网页之前");
// console.log(document.getElementById("myUl").children[1].innerHTML); // 报错
},
mounted() {
// 4. 挂载后
console.log("mounted --- 挂载之后,虚拟DOM替换到指定位置上,真实DOM显示,vue的虚拟DOM, 挂载到真实的网页上");
console.log(document.getElementById("myUl").children[1].innerHTML);
},
beforeUpdate() {
// 5. 更新前
console.log("beforeUpdate --- 数据更新, 页面更新前,更新真实Dom之前");
// 比如点击新增数组元素, vue会触发此生命周期函数, 但是此时页面并未更新, 所以获取不到新增的li标签
// console.log(document.getElementById("myUl").children[4].innerHTML); // 报错
},
updated() {
// 6. 更新后
console.log("updated --- 数据更新, 页面更新后,更新真实DOM之后");
console.log(document.getElementById("myUl").children[4].innerHTML);
},
beforeDestroy() {
// 7. 销毁前
console.log("beforeDestroy --- 实例销毁之前调用,销毁此组件之前");
// watches和移除孩子们,vue内部会自动触发
// 此生命周期函数使用场景: 移除事件监听 this.$but.$off('事件名')
},
destroyed() {
// 8. 销毁后
// (清空一些本地变量 / 全局变量 / 销毁当前组件的eventBus事件, 引用的全局事件)
console.log("destroyed --- 销毁完成,组件已经销毁");
}
21.跨域
什么是跨域呢?
口诀:网页所在的协议/域名/端口,和Ajax要请求的协议/域名/端口有一个对不上就会发生跨域访问
解决:
1.JSONP
前端用script标签src属性请求接口地址(不用ajax),后端要返回"函数(数据)"字符串
2.cors
前端(现代浏览器默认都支持-前端不用动) -- 后端开启cors(开启cors的原理:响应头:Allow-Origin-Access-Control:*,就是告诉浏览器有哪些源允许链接后台,这里的*就代表任何源,什么叫做源呢?就是浏览器里的那个地址栏从哪来的)
3.反向代理
前端开一个本地自己的服务器 - 后端不用动(正常提供接口地址)(原理:服务器请求服务器没有跨域限制)
前端ajax -> 本地自己服务器(开cors) -> 后端服务器(不开cors)
22.axios
目标:axios是一个专门用于发送ajax请求的库
特点:
1.支持客户端发送Ajax请求
2.支持服务端Node.js发送请求
3.支持Promise相关用法
4.支持请求和响应的拦截器功能
5.自动转换JSON数据
axios底层还是原生js实现,内部通过Promise封装的
axios原地会返回一个promise,点then专门用来接收promise这个成功的异步任务(因为网络请求是异步任务),catch用来接收失败的任务
总结:axios这个方法原地返回的是一个Promise对象
基本使用:
1.下载axios库 到当前工程 yarn add axios
2.到要使用的vue页面,引入axios包
import axios from 'axios'
3.使用axios里的方法,执行一次网络请求
export default {
created() {
4.使用axios调用接口
axios((
url:"http://xxxx", // 请求地址
method:"GET", // get post
data: { // 拼接到请求体的参数, post请求的参数
xxx:xxx,
},
params: { // 拼接到请求行的参数,get请求的参数
xxx:xxx
}
)).then(res => {
console.log(res) // 接收请求后台返回的接口数据
}).catch(err => {
console.log(err) // 后台报错返回
})
}
}
把请求的数据铺设到页面
<template>
<div>
<p>所有书籍信息名字</p>
<ul>
<li v-for="obj in list" :key="obj.id">{{ obj.bookname }}</li>
</ul>
</div>
</template>
</script>
import axios from 'axios'
export default {
data(){
return {
list: []
}
},
created() {
axios({
url: "xxx",
method: "GET"
}).then(res => {
this.list = res.data.data;
})
}
};
</script>
axios_get和post方式传参
get方式用params传参, 也就是 (url?后面自带你写的参数) ?bookname=书名
为什么obj里的key要和参数名对应上?
data: {
...this.obj, // 这里就可以直接传obj里的key+value给后台
}
axios_全局默认配置
http://www.axios-js.com axios官网
目标:配置基础前缀地址,统一管理
可以在官网看到axios的很多默认配置
axios.defaults.baseURL = 'http://123.57.109.30:3006'
设置完这句话以后,具体的axios()请求时,会自动在url前面会拼接baseURL 的值
23.ref
目标:利用ref和$refs可以用于获取dom元素,或者组件实例
使用场景1:获取到原生DOM标签
<h1 ref="myH">1.ref获取原生dom</h1>
<button @click='fn'>点击修改上面内容</button>
使用场景2:获取组件对象,调用组件里方法
<h1>2.调用demo组件方法</h1>
<button @click='fn2'>点击demo组件里最后一个高亮</button>
<Demo ref='de'></Demo>
总结:ref定义值,通过$refs.值来获取dom或组件对象
使用场景1:获取到原生DOM标签
使用场景2:获取组件对象,调用组件里方法
24.$nextTick使用
目标:等DOM更新后,触发此方法里函数体执行
例子:点击按钮,count增1后,获取dom马上打印内容
问题:发现DOM是异步更新的
解决:使用$nextTick等待DOM更新后再触发此方法
总结:dom是异步更新的,$nextTick可以等待dom更新后触发此方法
扩展知识:$nextTick()返回Promise对象,可以配合async+await改成同步流程去掉回调函数
<template>
<div>
<p ref="myP">{{ count }}</p>
<button @click="btn">点击数字+1</button>
</div>
</template>
<script>
export default {
data(){
return {
count: 0
}
},
methods: {
btn(){
this.count += 1;
// 问题: 马上获取原生DOM标签的innerHtml打印, 拿不到最新的
// console.log(this.$refs.myP.innerHTML); // 0
// vue数据改变后 - 更新DOM是"异步"的
// 异步代码不会阻塞继续执行, 所以拿到的是p标签原来的内容0
// 异步更新DOM后, 页面上显示了1
// 解决方案: // 固定this.$nextTick(() => {}) 微任务
// DOM更新后, 会触发$nextTick()里的函数体执行
this.$nextTick(() => {
console.log(this.$refs.myP.innerHTML); // 1
})
}
}
}
</script>
$nextTick以及ref的使用场景:
<template>
<div>
<!-- 点击按钮, 输出框出来, 自带聚焦行为 -->
<input type="text" v-if="isShow" ref="myInp"/>
<button v-else @click="btn">点击弹出搜索框</button>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
};
},
methods: {
btn() {
this.isShow = true;
// 自动聚焦-获取到标签(原生DOM)
// 数据改变-页面异步更新的, 所以$nextTick里面等待页面更新完毕, 再去触发focus
console.log(this.$refs.myInp);
this.$nextTick(() => {
this.$refs.myInp.focus();
})
},
},
};
</script>
25.组件name属性使用
目标:给组件name属性赋值,可在注册组件时使用
作用:给当前组件起名,可以用来注册组件使用组件
1.组件定义name属性和值,值就是组件的名字
2.使用组件时,声明组件名字可用name属性值 ( ComName变量的值就是export default后的{} )
26.动态组件
同一个挂载点,可以切换不同组件进行显示
目标:多个组件使用同一个挂载点,并动态切换,这就是动态组件
缺点:组件被切换的时候,会频繁的创建和销毁,性能不高
解决:组件缓存,在挂载点套Vue内置的组件<keep-alive></keep-alive>
需求:完成一个注册功能页面,2个按钮切换,一个填写注册信息,一个填写用户简介信息
动态组件步骤:
1.准备好要切换使用的组件,引入,注册名字
2.确定挂载点,赋予一个变量给is,只要切换变量里的组件名,页面就会切换组件
<template>
<div>
<h1>1.动态组件</h1>
<p>同一个挂载点,切换不同组件显示:</p>
<button @click="componentName = 'UserName'">账号密码填写</button>
<button @click="componentName = 'UserInfo'">个人信息填写</button>
<div style="border: 1px solid red">
<!-- component是vue内置组件 - 专门用于挂载组件配合is属性 -->
<!-- is属性的值就是这个位置要使用的组件名 -->
<component :is="componentName"></component>
</div>
</div>
</template>
<script>
import UserName from "./components/UserName";
import UserInfo from "./components/UserInfo";
export default {
data() {
return {
componentName: "UserName",
};
},
components: {
UserName,
UserInfo,
},
};
</script>
27.组件缓存
<keep-alive></keep-alive>
目标:组件切换会导致组件被频繁销毁和重新创建,性能不高
需求:使用Vue内置的keep-alive组件,可以让包裹的组件保存在内存中不被销毁
演示1:可以先给UserName.vue和UserInfo.vue注册created和destroyed生命周期事件,观察创建和销毁过程
演示2:使用keep-alive内置的vue组件,让动态组件缓存而不是销毁
补充生命周期:
activated - 激活
deactivated - 失去激活状态
问题:组件被切换的时候,会频繁的创建和销毁,性能不高
解决:组件缓存,在挂载点套Vue内置的组件<keep-alive></keep-alive>
效果:频繁切换,组件不会被销毁而是缓存在了内存中
总结:keep-alive可以提高组件的性能,内部包裹的标签不会被销毁和重新创建,触发激活和非激活的生命周期方法
28.组件插槽
目标:用于实现组件的内容分发,通过slot标签,可以接收到写在组件标签内的内容
vue提供组件插槽能力,允许开发者在封装组件时,把不确定的部分定义为插槽
组件插槽:给组件内传递(分发)不同的标签展示,你插入什么就展示什么
插槽指的就是slot
需求:折叠面板案例,想要实现不同内容显示
我们把折叠面板里的Pannel组件,添加组件插槽方式
口诀:
1.(<Pannel>)组件内用<slot></slot>占位
2.使用组件时<Pannel></Pannel>夹着的地方,传入标签替换slot
总结:组件内容分发技术,slot占位,使用组件时传入替换slot位置的标签
组件插槽-插槽默认内容
目标:如果外面不给传,想给这个默认显示内容
口诀:
1.插槽指的是slot标签,slot标签夹着的地方写好默认显示内容
2.<slot>夹着的地方默认写显示内容,如果不给插槽slot传东西,则使用<slot>夹着的内容在原地显示
效果:使用组件不给slot传标签,就显示slot夹着的默认内容 <slot>我是默认的内容</slot>
组件插槽-具名插槽
目标:当一个组件内有2处以上需要外部传入标签的地方
传入的标签可以分别派发给不同的slot位置
要求:v-slot一般用跟template标签使用(template是html5新出标签内容模板元素,不会渲染到页面上,一般被vue解析内部标签)
使用具名插槽:
1.template包裹标签
2.template上v-slot:插槽名
优化:v-lost可以简化成#
组件插槽-作用域插槽
目标:子组件里的值,在给插槽赋值时在父组件环境下使用
复习:插槽内slot中显示默认内容
例子:默认内容在子组件中,但是父亲在给插槽传值,想要改变插槽显示的默认内容
口诀:
1.创建组件,准备slot,在slot上绑定属性和子组件值
2,使用组件,传入自定义标签,用template和v-slot='自定义变量名'
自定义变量名会自动绑定slot上所有属性,就可以使用子组件内的值,并替换slot位置
总结:组件内变量绑定在slot上,然后使用组件v-slot='变量',变量上就会绑定slot身上属性和值
作用: 外部使用组件插槽处, 可以使用组件内变量
作用域插槽口诀:
1.把子组件内容绑定在slot标签属性上 -为了准备传给外面使用组件插槽的地方
2.父使用组件时,template上v-slot='变量名'
变量名上有slot上所有的属性和值
=后面是变量名 :后面是name值
使用场景:
目标: 了解作用域插槽使用场景,自定义组件内标签+内容
场景: list数组需要在子组件中使用,这时候我们需要把list数组通过父向子传值props传递过去,然后进行渲染,假如其中有一个标签不知道自己以后到底是使用图片还是文字,这时候我们先用slot插槽占位,把图片地址渲染上去,页面会显示图片地址,而不是图片,那么就需要用到作用域插槽自定义标签,作用域插槽可以把组件内的值取出来自定义内容
总结:插槽可以自定义标签,作用域插槽可以把组件内的值取出来自定义内容
29.自定义指令
目标:获取标签,扩展额外的功能
使用:在标签上使用v-focus
语法:局部创建
需求:在input框一上来就获取焦点:
全局创建
目标:获取标签,扩展额外的功能
语法:全局注册
Vue.directive("fofo",{
inserted(el){
el.focus()
}
})
使用:在标签上使用v-fofo
全局和局部的区别:
1.全局注册的,到处"直接"使用
2.局部注册的,只能在当前.vue文件内使用
自定义指令-传值
目标:定义color指令-传入一个颜色,给标签设置文字颜色
语法:
Vue.directive("color",{
inserted(el,binding){ // 插入时(第一次创建)触发此函数
console.log(el)
console.log(binding)
el.style.color = binding.value
},
update(el,binding){ // 当指令更新的时候(对应变量变化)-指令会触发这个函数
el.style.color = binding.value
}
})
使用:在标签上使用v-color='red'
总结:
1.v-xxx,自定义指令,获取原生DOM,自定义操作
2.自定义指令是给标签添加额外的功能,而且还可以传入值来操作原生dom标签
30.路由vue-router
路由地址: Vue Router
vue路由就是url的哈希值和组件的映射关系
哈希值和对应vue文件的映射关系就是路由
路由原理手写
目标:了解hash改变,如何显示不同的组件的过程
基本思路:
1.用户点击了页面上的a链接
2.导致了URL地址栏中的Hash值发生了变化
3.前端js监听(window.onhashchange)到Hash地址的变化
4.前端js把当前Hash地址对应的组件渲染到浏览器中(暂时用动态组件显示)
总结:改变浏览器url的hash值,JS监听到hash值改变,把对应的组件显示到同一个挂载点
前端路由原理:hash值和组件的映射关系
思路:
1.创建3个页面组件,创建3个a标签,可以切换hash值
2.暂用动态组件,切换显示页面
3.(关键),JS代码监听hash值改变,修改comName的值 - 切换页面目的
vue-router基本使用 - 7步骤
目标:学会vue官方提供的vue-router路由系统功能模块使用
不自己写js检测和动态组件切换了,让vue-router封装起来了,我们遵守vue-router规则使用即可
步骤:
1.下载vue-router模块到当前工程
yarn add vue-router
2.在main.js中引入vue-router函数
import VueRouter from 'vue-router'
3.在main.js中添加到Vue.use()类中 - 类似于中间件
Vue.use(VueRouter)
4.在main.js中创建路由规则(规则就是数组套对象)-路径和组件名对应关系
import MyHome from "./views/MyHome";
import MyMovie from "./views/MyMovie";
import MyAbout from "./views/MyAbout";
// 4.创建路由规则数组套对象(hash值和组件映射关系)
const routes = [ // 规则数组
{ // 每个对象就是一套一对一的映射规则
path: "/home", // 检测的hash值(不带#)
component: MyHome // 如果url的hash值命中了path,展示此组件页面
},
{
path: "/movie",
component: MyMovie
},
{
path: "/about",
component: MyAbout
},
]
5.利用规则生成路由对象
const router = new VueRouter({
routes: routes // key固定的叫routes(传入路由规则),value是上面规则数组变量名 (可简写)
})
6.把路由对象注入到new Vue中
new Vue({
render: h => h(App),
// 把路由对象(内涵规则) - 注入到vue对象中 - 让vue拥有路由功能
router: router // (可简写)
}).$mount('#app')
7.把components和is,替换成router-view(内置的组件用于挂载切换的页面组件)
// vue-router模块,给vue全局注册的路由内置组件
// 作用: 当hash值改变,命中路由规则数组里的对象,会把对应的component组件显示到这里
<router-view></router-view>
总结:下载路由模块,编写对应规则注入到vue实例上,使用router-view挂载点显示切换的路由
链接导航router-link
目标:可用全局组件router-link来替代a标签
1.vue-router提供了一个全局组件router-link:作用用于提供路由链接
2.router-link实质上最终会渲染成a链接 to属性等价于提供href属性(不要写#)
3.router-link提供了链接导航高亮的功能
总结:链接导航,用router-link配合to,实现点击切换路由
好处:
1.router-link实际上还是a标签,to属性等价于href的值,默认帮我们添加#前缀
2.router-link内置高亮的类名
链接导航router-link -- 高亮类名使用_区别
目标:a标签和router-link区别就是,路由组件激活时自带class
我们只需要声明具体class类名,设置好具体的样式即可
router-link-exact-active (精确匹配)url中hash值,与to属性值完全相同,设置此类名
router-link-active (模糊匹配) url中hash值,包含to属性值,设置此类名
可以再添加个路由路径为/的,观察自动添加的类名
总结:
匹配:浏览器url地址栏里hash值和a标签的href的值 进行匹配
例如: /home /home - 有2个类名
例如: /home / - 只有router-link-active一个类名
链接导航router-link -- 自定义高亮类名active-class
目标:a标签和router-link区别就是,路由组件激活时自带class
router-link上有一个固定的active-class属性名,用于自定义高亮的类名,默认替换router-link-active这个类名
exact-active-class 替换默认的 router-link-exact-active这个类名
active-class 替换默认的 router-link-active这个类名
1.router-link设置属性和类名
<router-link active-class='active' to='/home'>首页</router-link>
2.style中定义类名和具体样式
.active {
color:orange;
}
链接导航router-link - 跳转传参2种方式
两种传参方式
目标:在跳转路由时,可以给路由对应的组件内传值
在router-link上的to属性传值,语法格式如下:
方式一:
传递:to="/path?参数名=值"
接收:$route.query.参数名 - query收集的就是?后面的参数和值
方式二: 动态路由
1.路由规则上,留好参数名
{
path:"/path/:参数名"
eg: path:"/path/:b"
}
2.路由跳转的时候,需要在对应路径传值
to="/path/值"
3.接收:$route.params.参数名
eg:$route.params.b
链接导航-路由重定向和404兜底
目标:默认上来强制显示某个路由页面,找不到路径给个提示页面
网页打开url默认hash值是 空字符串或者/路径
redirect是设置要重定向到哪里
路由最后,path匹配*(任意路径) - 前面不匹配就命中最后这个
路由重定向目的:网页打开就显示一个页面
{
path:'/', // 网页打开默认的hash值 / 根路径
redirect:'/home' // 重定向,马上修改url的hash值为/home,强制切换路由路径
}
// *代表的是通配符,本配置最好写在最后面,路由路径是从上往下匹配
// 404配置
{
path:'*',
component:NotFound // 如果找不到路径,就显示404组件
}
路由模式
目标:修改路由在地址栏的模式
hash路由例如:http://localhost:8081/#/home
hash模式:路由以#/形式展示在url上
history路由例如:http://localhost:8081/home
history模式:路由以路径形式展示在url上
编程式导航
编程式导航其实就是js跳转
注意:要跳的路径一定要跟你路由规则数组里的path对应上
注意: 如果当前已经在/home,你还想往导航里push一个/home就会发生一个警告错误(冗余导航,不要添加重复的路径)
格式3(了解)
其实和上面的两种传参方式没啥区别,就是多传入一个name值,然后把path改换成了name,用name来控制导航跳转
编程式导航 - 跳转传参
目标:JS跳转路由,传参
口诀:
用name+params传 => $route.params.key - 接
用path+query传 => $route.query.key - 接
两个传参区别:
path+query在地址栏上,刷新后还能获取到
name+params在内存中,刷新后就不存在了
路由嵌套
一级路由从根开始写
其他级路由都不要'/'这个根
路由嵌套步骤:
1.根据业务想好,在哪个一级路由页面里嵌入router-link和router-view
2.配置路由规则数组,在哪个一级path里嵌入children,子路由规则
3.给router-link添加to属性,注意从/开始写
全局前置守卫
目标:路由跳转之前,会触发一个函数
使用例子:在跳转路由前,判断用户登陆了才能去<我的音乐>页面,未登录弹框提示回到发现音乐页面
语法:router.beforeEach((to,from,next) => {})
最后要调用next()才会跳转到正常下一个路由页面
全局后置守卫
目标:路由跳转后,触发的函数
语法:router.afterEach((to,from) => {})
路由总结: