什么是Vue
- 是一套用于构建用户界面的渐进式 javascript 框架(渐进式:想用什么就用什么不必全都用)
- 在传统的前端开发中,是基于 jQuery+ 模板引擎 来构建界面的
vue全家桶
- vue 核心库
- vue-router (路由)
- vuex (全局状态管理)
- vue 组件库(快速搭建页面UI效果的方案)
辅助vue开发的工具
- vue-cli (基于webpack一键生成vue工程化项目)
- vite (一键生成vue工程化项目-小而巧)
- vue-detools (浏览器插件,辅助调试的工具)
- vetur (vscode插件,提供语法高亮和智能提示)
vue特性
- 数据驱动视图
- 双向数据绑定
MVVM
- MVVM 是数据驱动视图和双向数据绑定的核心原理
- M-Model --渲染依赖的数据源
- V-view --渲染的DOM结构
- VM-ViewModel --vue实例是MVVM的核心
VUE指令
内容渲染指令
- v-text ---会覆盖元素中的默认内容
- {{ }} ---插值表达式
- v-html ---可以识别标签并渲染标签
- {{ }}中可以是三元运算,可以简单的运算,也可以调用方法
属性绑定指令
- v-bind 给标签属性动态绑定属性值
- 简写 --- :
- 可以做简单的运算,字符串拼接
事件绑定指令
- v-on 给标签绑定事件 (v-on:click="clickFn")
- 处理函数在methods中声明
- 简写---@click="clickFn"
- 事件对象参数e
- 没有参数,回调函数参数e
- 有参数,就用$event占位,在回调中e接收
- 事件修饰符
- .prevent 阻止浏览器默认行为(@click.prevent="clickFn")
- .stop 阻止冒泡(@click.stop="clickFn")
- 按键修饰符
- .enter 回车(@keyup.enter="keyupFn")
- .esc (@keyup.esc="keyupFn")
双向绑定指令
- v-model 双向数据绑定,不操作DOM,快速获取表单数据
- 只要给表单元素绑定才有意义
- 与select下拉表单绑定时,实际绑定的是option中的value属性
- v-model 的修饰符
- .number 自动将用户输入的值转化为数字类型
- .trim 自动过滤输入的空白字符
- .lazy 在输入框失去焦点时才更新
条件渲染指令
- v-if v-else
- v-show
- 为false时隐藏为true时显示
- v-if 的原理是销毁和重建标签
- v-show 的原理是给标签添加display:"none"属性
- 频繁切换时用v-show性能高
- v-if v-else 判断是否为ture为ture显示,否则隐藏
<div v-if="i>90">优秀</div>
<div v-else="i>80">良好</div>
<div v-else="i>70">中等</div>
<div v-else>差劲</div>
循环渲染指令
v-for="(item,index) in list" :key="index"
- 基于 数组,对象,数字 来循环渲染列表结构
- item为数组中的每一项
- index为当前循环项的索引值
- list为循环的数据数组
- key属性为了提升更新元素的性能,有ID绑定ID没有ID绑索引
- 通过索引更改数组中的数据方法this.$set(数组,索引,要更改的数据)
v-for指令中的key理解
- key属性根据虚拟DOM和diff算法提升更新元素的性能
- 数组中哪些方法会 导致数组更新
- push( ) 末尾添加元素
- pop( ) 末尾删除元素
- shift( ) 删除数组第一个元素
- unshift( ) 添加数组第一个元素
- splice( ) 从数组中添加/删除项目,然后返回被删除的项目。
- sort( ) 对数组的元素进行排序
- reverse( ) 翻转数组
- 更新时新旧虚拟dom根据diff算法进行对比
- 尝试复用标签,就地更新内容
- 虚拟Dom:本质上就是保存 节点信息,属性和内容的JS对象
-
保存了标签的最重要的三个信息(类型,属性,子节点信息)
- diff算法
- 同级比较根元素发生变化就会删除整个dom树重新创建
- 根元素没有变化,只是属性变了,那就只更新属性
- 子元素变化 v-for更新分有key和无key
无key:子节点哪里变了更新哪里属性或内容
有key:
1)key为索引 -- 和无key更新一样哪里变了更新哪里
2)key为id或不重复的字符串 -- 根据id比对,内容不变就地复用,极大程度复用标签提升更新性能
动态class绑定
- 直接绑定:
:class="vue数据变量"
- 三元绑定:
:class= "vue数据变量布尔值? '类名':' '"
- 对象绑定:
:class= "{类名:vue数据变量布尔值}"
- 数组绑定:
:class="[vue数据变量]"
- 动态class和静态class并存会合并到一起
style动态绑定
:style="{属性名:属性值}"
计算属性-computed
注意: 计算属性使用时不能加( ),计算属性必须return一个结果
- 一个变量的值,依赖另外一些数据计算得来的结果,就会用到计算属性
- 基于依赖项的值将结果进行缓存,依赖项不变,就直接从内存中取结果,而不是重新计算
- 计算属性也是vue数据变量, 所以不要和data中的变量重名,用法和data中的变量用法完全相同
- 只要计算属性的依赖项发生改变, 那么就会重新计算
computed:{
"计算属性名"( ){
return "值"
}
}
计算属性完整写法
- 计算属性用在v-model上进行双向数据绑定时,改变计算属性的值时,就会用到完整写法
- 获取计算属性值的时候会触发get函数
- 设置计算属性值的时候会触发set函数
computed:{
"计算属性名":{
set(改变后的值){
},
get(){
return "值"
}
}
}
vue监听器watch
- 可以监听data/computed属性值的变化
- 监听基本数据类型
watch:{
"被侦听的属性名"(newval,lodval){
console.log(newval,lodval)
}
}
- 监听引用数据类型(复杂数据类型)
watch:{
"要侦听的属性名":{
immediate: true, //立即执行
deep: true, //深度监听
handler (newval) { //侦听函数
console.log(newval)
}
}
}
vue组件
- 好处:各自独立,方便复用
- 组件是可复用的vue实例
- 组件的组成= 封装标签+样式+js代码
- 组件化: 封装的思想,把页面上(可重用的部分)封装为组件,从而方便项目的开发与维护
- 组件的使用
- 创建组件,封装要复用的标签,样式,js代码
- 导入组价(import 组件对象 from 文件路径)
- 注册组件
- 使用组件, 把组件名当自定义标签使用
(1)全局注册- min.js中
// main.js
import Vue from 'vue'
import 组件对象 from 'vue组件路径'
Vue.component("组件名",组件对象)
(2)局部注册 - 使用组件的vue文件中
// 要使用组件的vue文件
import 组件对象 from 'vue组件路径'
export default {
components:{
"组件名":组件对象
}
}
- 组件的命名
- 大驼峰命名 例如:MyProduct -->推荐使用大驼峰
- 烤串法 例如: my-product
- 组件的使用
- 大驼峰命名 例如:MyProduct
- 烤串法 例如: my-product -->推荐使用烤串法
结论:组件命名用大驼峰,组件使用时用烤串法
- 组件使用时用双标签还是单标签?
如果组件标签需要夹内容就用双标签,不夹内容就用单标签
- 组件中的name属性
组件的name属性的值可用作注册组件时的组件名
- 组件style标签中的scoped属性
- 作用:使当前组件的css样式只在当前组件中生效,不会影响全局
- 原理:会在当前组件的标签上生成一个data-v-hash:8, 每个组件内的data-v-hash:8都是不同的,今后它会配合我们自己写的选择器上加一个属性选择器,从而保证了这个选择器在组件内部的唯一性,而不会影响其他组件
组件之间传值
- 被引入的是儿子
在A组件中, import B from 'B.vue'
A(父) , B(子)
父传子
- 子组件中通过props属性接收数据
//子组件定义
props:{
变量名:{
type:变量数据类型,
default: 默认值
}
}
- 父组件,通过属性的方式传值
<B :变量名="值"> </B>
子传父
- 父传给子的数据在子中是只读的不能更改
- 从父到子的数据流向,叫 单向数据流
- 子想修改父组件传过来的数据只能通过子传父的方式通知父组件修改
- 子传父语法:
(1)父组件内,绑定自定义事件和事件处理函数
<son @自定义事件名="事件处理函数" >
(2)子组件内部在合适的时机触发父组件的自定义事件
this.$emit('父组件中的自定义事件名',传参,传参)
-
单向数据流
父传子时 子组件中的不能更改父组件中的变量(props中的变量都是只读的)
解决方案: 子传父(自通知父, 父自己改)
两个没有关系或者兄弟组件之间传值
- EventBus:
创建一个空白 Vue对象new Vue()
只负责监听$on
和$emit
- 用法
- 创建一个空白Vue对象,封装到EventBus.js文件
- A: 引入空白的vue对象,EventBus.$on("自定义事件名" ,事件处理函数)
- B: 引入相同的这个空白Vue对象, EventBus.$emit("自定义事件名",值)
Vue组件中的生命周期
- 组件从创建到销毁的过程就是组件的生命周期
钩子函数
- 四个阶段八个方法
- 初始化
beforeCreate
created
- created 钩子函数可以获取data中的变量但不能获取真实的DOM
- created 钩子函数可以用来发送axios请求
- 挂载
beforeMount
mounted
- 在mounted钩子函数中才可以拿到真实的DOM
- 更新
beforeUpdate
updated
- data中数据改变后会触发DOM更新在updated钩子函数中可以拿到更新后的"真实DOM"
- 销毁
beforeDestroy
destroyed
- destroyed钩子函数中清除当前组件自己的定时器 / 延时器 / 全局事件 (eventBus)
v-for循环
<p v-for="(item,index) in 数组" :key="item.id">
- 每次循环生成的组件都是全新的互不影响的
axios发送axjx请求
- 支持客户端发送Ajax请求
- 支持服务器端Node.js发送请求
- 支持Promise 相关用法
- 支持请求和响应的拦截器功能
- 自动转换JSON数据
- 什么是ajax?
一种前端异步请求后端数据的技术 - ajax原理?
浏览器window接口的XMLHttpRequest - axios是什么?
基于原生ajax+Promise技术封装用于前后端的请求库
axios的使用
1.下载引入axios(yarn add axios)
//引入
import axios from 'axios'
- 发送请求
axios({
method:"请求方式", //get &put & post...
url:"请求的路径", //http://123.57.109...
data:{
XXX:XXX //请求体参数
},
params:{
XXX:XXX //查询参数,会拼接到url路径后
}
}).then(res={
//请求成功的回调
}).catch(err={
//请求失败的回调
})
$refs获取DOM&组件
- $refs 可以获取DOM标签
- 也可以获取组件实例从而调用组件中的方法或属性
- 只有在Vue中才能使用$refs获取DOM
- $refs使用
- 标签定义ref属性和值
- this.$refs.值 - 原地获取DOM
$nextTick获取更新后的DOM
- 问题 : data数据改变,"dom更新是异步的"
- 在setTimeOut延时器中获取DOM
- 在Vue中提供$nextTick方法(等待DOM更新后再执行此方法中的回调函数)
- $nextTick原理
尝试用Promise.then() / setTimeout & setImmediate - $nextTick使用
this.$nextTick(()=>{
getElementById("myp").innerHTML
})
<keep-alive>组件缓存
- 用<keep-alive>包裹的组件会被缓存到内存中在切换组件的时候就不会被销毁和重建,提升渲染性能
<keep-alive>
<!-- vue内置的组件component, 可以动态显示组件 -->
<component :is="comName"></component>
</keep-alive>
组件插槽
- vue提供组件插槽能力, 允许开发者在封装组件时,把不确定的部分定义为插槽
- 插槽步骤
- 组件内用<slot></slot>占位
- 使用组件时<template></template>夹着的地方, 传入标签替换slot
- <slot>夹着内容默认显示内容, 如果不给插槽slot传东西, 则使用<slot>夹着的内容在原地显示
- 具名插槽(当一个组件内有2处以上需要外部传入标签的地方,传入的标签可以分别派发给不同的slot位置)
- 在slot标签上用name属性命名
<slot name="one"></slot>
<slot name="two"></slot>
2.在template标签上用 v-slot:插槽名 传入具体标签 , v-slot:可以简写成 " # "
<template #one>
<div>
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</template>
<template #two>
<img src="../assets/mm.gif" alt="" />
</template>
-
作用域插槽
子组件里值, 在给插槽赋值时在父组件环境下使用
- 创建组件, 准备slot, 在slot上绑定属性和子组件值
- 使用组件, 传入自定义标签, 用template和v-slot="自定义变量名"
- 自定义变量名会自动绑定slot上所有属性, 就可以使用子组件内值, 并替换slot位置
<template>
<div>
<p>这里是个Pannel3-子组件, 下面是插槽位置</p>
<slot name="one" :row="slotDefault">{{ slotDefault.default1 }}</slot>
</div>
</template>
<script>
export default {
data(){
return {
slotDefault: {
default1: "无名氏",
default2: "孙红雷"
}
}
}
}
</script>
<template>
<div>
<!-- 想要改变默认内容, 但是默认数据在子组件里, 想让插槽使用就使用插槽作用域 -->
<!--
口诀: 1.创建组件, 准备slot, 在slot上绑定属性和子组件值
2. 使用组件, 传入自定义标签, 用template和v-slot="自定义变量名"
3. 自定义变量名会自动绑定slot上所有属性, 就可以使用子组件内值, 并替换slot位置
-->
<Pannel3>
<template #one="scope">
{{ scope.row.default2 }}
</template>
</Pannel3>
</div>
</template>
<script>
import Pannel3 from './Pannel3'
export default {
components: {
Pannel3
}
}
</script>
自定义指令
- 局部注册和使用
<template>
<div>
<input type="text" v-focus />
</div>
</template>
<script>
export default {
// 局部注册
directives: {
focus: { // 自定义指令名
inserted(el){ // 固定配置项 - 当指令插入到标签自动触发此函数
el.focus()
}
},
},
};
</script>
-
全局注册
在main.js用 Vue.directive()方法来进行注册, 以后随便哪个.vue文件里都可以直接用v-fofo指令
Vue.directive("fofo", {
inserted(el){
el.focus()
}
})
自定义指令-接值
- 使用自定义指令, 传入一个值
main.js定义处修改一下
Vue.directive("color", {
inserted(el, binding){ // 插入时触发此函数
el.style.color = binding.value;
},
update(el, binding){ // 更新时触发此函数
el.style.color = binding.value;
}
})
<p v-color="theColor" @click="changeColor">使用v-color指令控制颜色, 点击变蓝</p>
<script>
data() {
return {
theColor: "red",
};
},
methods: {
changeColor() {
this.theColor = 'blue';
},
},
</script>
router 路由
- 路径和组件的映射关系
- vue-router本质是一个第三方包
- 步骤
- 终端下载
yarn add vue-router - 导入路由
import VueRouter from 'vue-router'
- 注册
// 在vue中,使用使用vue的插件,都需要调用Vue.use()
Vue.use(VueRouter)
- 创建规则数组
const routes = [
{
path: "/find",
component: Find
},
{
path: "/my",
component: My
},
{
path: "/part",
component: Part
}
]
- 创建路由对象 - 传入规则
const router = new VueRouter({
routes
})
- 关联到vue实例
new Vue({
router
})
- 设置路由挂载点
<router-view></router-view>
vue路由 - 声明式导航
- vue-router提供了一个全局组件 router-link
- router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
- router-link提供了声明式导航高亮的功能(自带类名)
- router-link自带的2个类名的区别是什么?
- router-link-exact-active (精确匹配) url中hash值路径, 与href属性值完全相同, 设置此类名
- router-link-active (模糊匹配) url中hash值, 包含href属性值这个路径
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/part">朋友</router-link>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
声明式导航 - 跳转传参
- 在跳转路由时, 可以给路由对应的组件内传值
- 在router-link上的to属性传值, 语法格式如
- ?key=value 用$route.query.key 取值
- /值 提前在路由规则/path/:key 用$route.params.key 取值
- /path?参数名=值
- /path/值 – 需要路由对象提前配置 path: “/path/参数名”
对应页面组件接收传递过来的值
$route.query.参数名
$route.params.参数名
创建components/Part.vue - 准备接收路由上传递的参数和值
<template>
<div>
<p>关注明星</p>
<p>发现精彩</p>
<p>寻找伙伴</p>
<p>加入我们</p>
<p>人名: {{ $route.query.name }} -- {{ $route.params.username }}</p>
</div>
</template>
路由定义
{
path: "/part",
component: Part
},
{
path: "/part/:username", // 有:的路径代表要接收具体的值
component: Part
},
导航跳转, 传值给MyGoods.vue组件
<router-link to="/part?name=小传">朋友-小传</router-link>
<router-link to="/part/小智">朋友-小智</router-link>
vue路由 - 重定向&模式&404页面
路由 - 重定向
- 网页打开url默认hash值是/路径
- redirect是设置要重定向到哪个路由路径
const routes = [
{
path: "/", // 默认hash值路径
redirect: "/find" // 重定向到/find
// 浏览器url中#后的路径被改变成/find-重新匹配数组规则
}
]
404页面
- 放在路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个, 显示对应404组件页面
- 步骤:
- 创建NotFound页面
<template>
<img src="404图片路径" alt="">
</template>
<script>
export default {
}
</script>
<style scoped>
img{
width: 100%;
}
</style>
在main.js - 修改路由配置
import NotFound from '@/views/NotFound'
const routes = [
// ...省略了其他配置
// 404在最后(规则是从前往后逐个比较path)
{
path: "*",
component: NotFound
}
]
路由 - 模式设置
- 修改路由在地址栏的模式( hash 模式 或者 history 模式 )
- hash路由例如: http://localhost:8080/#/home
- history路由例如: http://localhost:8080/home (以后上线需要服务器端支持, 否则找的是文件夹)
router/index.js
const router = new VueRouter({
routes,
mode: "history" // 打包上线后需要后台支持, 模式是hash
})
vue路由 - 编程式导航
- 用JS代码跳转, 声明式导航用a标签
main.js - 路由数组里, 给路由起名字
{
path: "/find",
name: "Find",
component: Find
},
{
path: "/my",
name: "My",
component: My
},
{
path: "/part",
name: "Part",
component: Part
},
App.vue - 换成span 配合js的编程式导航跳转
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="btn('/part', 'Part')">朋友</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - js方式跳转路由
// 语法:
// this.$router.push({path: "路由路径"})
// this.$router.push({name: "路由名"})
// 注意:
// 虽然用name跳转, 但是url的hash值还是切换path路径值
// 场景:
// 方便修改: name路由名(在页面上看不见随便定义)
// path可以在url的hash值看到(尽量符合组内规范)
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
}
}
};
</script>
编程式导航 - 跳转传参
- JS跳转路由, 传参
- query / params 任选 一个
- 注意: 使用path会自动忽略params
<template>
<div>
<div class="footer_wrap">
<span @click="btn('/find', 'Find')">发现音乐</span>
<span @click="btn('/my', 'My')">我的音乐</span>
<span @click="oneBtn">朋友-小黑</span>
<span @click="twoBtn">朋友-小白</span>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
// 目标: 编程式导航 - 跳转路由传参
// 方式1:
// params => $route.params.参数名
// 方式2:
// query => $route.query.参数名
// 重要: path会自动忽略params
// 推荐: name+query方式传参
// 注意: 如果当前url上"hash值和?参数"与你要跳转到的"hash值和?参数"一致, 爆出冗余导航的问题, 不会跳转路由
export default {
methods: {
btn(targetPath, targetName){
// 方式1: path跳转
this.$router.push({
// path: targetPath,
name: targetName
})
},
oneBtn(){
this.$router.push({
name: 'Part',
params: {
username: '小黑'
}
})
},
twoBtn(){
this.$router.push({
name: 'Part',
query: {
name: '小白'
}
})
}
}
};
</script>
vue路由 - 守卫
- 路由跳转之前, 先执行一次前置守卫函数, 判断是否可以正常跳转
- next()放行, next(false)留在原地不跳转路由, next(path路径)强制换成对应path路径跳转
// 目标: 路由守卫
// 场景: 当你要对路由权限判断时
// 语法: router.beforeEach((to, from, next)=>{//路由跳转"之前"先执行这里, 决定是否跳转})
// 参数1: 要跳转到的路由 (路由对象信息) 目标
// 参数2: 从哪里跳转的路由 (路由对象信息) 来源
// 参数3: 函数体 - next()才会让路由正常的跳转切换, next(false)在原地停留, next("强制修改到另一个路由路径上")
// 注意: 如果不调用next, 页面留在原地
// 例子: 判断用户是否登录, 是否决定去"我的音乐"/my
const isLogin = true; // 登录状态(未登录)
router.beforeEach((to, from, next) => {
if (to.path === "/my" && isLogin === false) {
alert("请登录")
next(false) // 阻止路由跳转
} else {
next() // 正常放行
}
})