文章目录
- Vue快速上手
- Vue是什么
- 第一个Vue程序
- 插值表达式
- Vue核心特性:响应式
- Vue指令
- v-html
- v-show 与 v-if
- v-else 与 v-else-if
- v-on
- v-bind
- v-for
- v-model
- 指令修饰符
- 计算属性
- watch侦听器(监视器)
- watch——简写
- watch——完整写法
- Vue生命周期 和 生命周期的四个阶段
- Vue生命周期函数(钩子函数)
- 工程化开发
- 脚手架 Vue cli
- 脚手架目录文件介绍
- 组件化开发
- 普通组件的注册使用
- scoped样式冲突
- 组件通信
- 父子通信流程图
- prop
- 非父子通信
- 表单类组件封装
- .sync修饰符
- ref和$refs
- Vue异步更新、$nextTick
- 自定义指令
- 插槽
- 默认插槽
- 插槽后备内容(默认值)
- 具名插槽
- 作用于插槽
- 路由
- VueRouter的介绍
- VueRouter的使用(5+2)
- 路由的封装抽离
- 使用router-link(声明式导航)
- 声明式导航——跳转传参
- 重定向
- 编程式导航
- 组件缓存(keep-alive)
- ESlint代码规范
- vuex
- 概述
- 创建一个空仓库
- 提供与访问vuex的数据
- 修改vuex的数据
- actions
- getters
- 模块module(进阶语法)
- Vue3快速入门
- 创建Vue3项目
- 组合式API —— setup选项
- 组合式API —— reactive 函数
- 组合式API —— ref 函数
- 组合式API —— computed
- 组合式API —— watch
- 组合式API —— 生命周期函数
- 组合式API —— 父子通信
- 组合式API —— 模板引用
- 组合式API —— provide 和 inject
- 新特性 —— defineOptions
- 新特性 —— defineModel
- Pinia
- 手动添加Pinia到Vue项目
- Pinia基础使用 —— 计数器案例
- action异步实现
- storeToRefs方法
- Pinia持久化插件
Vue快速上手
Vue是什么
概念:Vue是一个用于构建用户界面的渐进式框架
- 优点:大大提升开发效率
- 缺点:需要理解记忆规则
Vue的两种使用方式:
- Vue核心包开发
场景:局部模块改造 - Vue核心包 & Vue插件工程化开发
场景:整站开发
第一个Vue程序
穿件Vue实例,初始化渲染的核心步骤:
- 准备容器
- 引包(官网)——
开发版本
/ 生产版本 - 创建Vue实例 new Vue()
- 指定配置项 el data => 渲染数据
- el指定挂载点,选择器指定控制饿时哪个盒子
- data提供数据
代码演示
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<!--创建Vue实例,初始化渲染1. 准备容器(Vue所管理的范围)2. 引包(开发版本包 / 生产版本包) 官网3. 创建实例4. 添加配置项 => 完成渲染
-->
<div id="app"><!-- 这里将来会编写一些用于渲染的代码逻辑 -->{{msg}}
</div><!--引入的是开发版本包(包含完整的注释和警告)-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>// 一但引入了Vue.js核心包,在全局环境,就有了Vue构造函数const app = new Vue({// 通过el配置选择器,指定Vue管理的是哪个盒子el: '#app',// 通过data提供数据data:{msg: '小吴在敲Bug'}})
</script>
</body>
</html>
插值表达式
插值表达式是一种Vue的模版语法
- 作用: 利用表达式进行插值,渲染到页面中
表达式:是可以被求值的代码,JS引擎会将其计算出一个结果 - 语法: {{ 表达式 }}
- 使用的数据必须存在(data)
- 支持的是表达式,而非语句
- 不能再标签属性中使用{{}}插值
代码演示
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<!--插值表达式:Vue的一种模板语法作用:利用 表达式进行插值渲染语法:{{表达式}}
-->
<div id="app"><p>{{msg}}</p><p>{{msg.toUpperCase()}}</p><p>{{msg + '你好'}}</p><p>{{age>18? '成年':'未成年'}}</p></div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>const app = new Vue({el: '#app',data:{msg: '小吴在敲Bug',age: 21}})
</script>
</body>
</html>
Vue核心特性:响应式
我们已经掌握了寄出的模版渲染,其实除了基本的模版渲染,Vue背后还做了大量工作。
比如:数据的响应式处理 —— 数据变化,视图自动更新
聚焦于数据——>数据驱动视图
使用Vue开发,关注 业务的核心逻辑
,根据业务 修改数据即可
代码演示
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app"><p>{{msg}}</p></div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>const app = new Vue({el: '#app',data:{// 响应式msg: '小吴在敲Bug'}})// 1. 访问数据 实例.属性名// 2. 修改数据 实例.属性名=新值
</script>
</body>
</html>
测试
Vue指令
Vue会根据不同的指令,针对不同标签实现不同的功能
指令:带有 v-前缀
的特殊 标签属性
v-html
v-html指令:
-
作用:向指定节点中渲染包含html结构的内容。
-
与插值语法的区别:
-
v-html会替换掉节点中所有的内容,{{xx}}则不会。
-
v-html可以识别html结构。
-
-
严重注意:v-html有安全性问题!!!!
-
在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
-
一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
-
代码演示
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app"><div v-html="msg"></div></div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>const app = new Vue({el: '#app',data:{msg:`<a href="www.4399.com">4399小游戏</a>`}})
</script>
</body>
</html>
v-show 与 v-if
v-show
- 作用:控制元素显示和隐藏
- 语法:v-show=“表达式” | 表达式值true显示,false隐藏
- 原理:
切换display:none
控制显示隐藏 - 场景:频繁切换显示隐藏的场景
v-if
- 作用:控制元素显示隐藏(
条件渲染
) - 语法:v-if=“表达式” | 表达式值true显示,false隐藏
- 原理:基于
条件判断
,是否创建或移除元素节点 - 场景:要么显示,要么隐藏,不频繁切换的场景
v-else 与 v-else-if
- 作用:辅助v-if进行判断渲染
- 语法:v-else | v-else-if=“表达式”
- 注意:需要紧挨着v-if一起使用
代码演示
<!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="app"><div ><p v-if="gender===1">性别:♂ 男</p><p v-else>性别:♀ 女</p><hr><p v-if="score==='A'">成绩评定A:奖励电脑一台</p><p v-else-if="score==='B'">成绩评定B:奖励周末郊游</p><p v-else-if="score==='C'">成绩评定C:奖励零食礼包</p><p v-else>成绩评定D:惩罚一周不能玩手机</p></div>
</div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {gender: 1,score: 'B'}})
</script></body>
</html>
v-on
- 作用:注册时间 = 添加监听 + 提供处理逻辑
- 语法:
- v-on:事件名 = “内联语句”
- v-on:事件名 = “methods中的函数名”
- 简写:@事件名
v-on调用传参
- 语法:@click=函数名(参数1,参数2……)
- 接收参数使用函数的形参,函数名(a,b){}
代码演示:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="app"><button @click="count--">-</button><span>{{count}}</span><button v-on:click="count++">+</button>
</div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>const app = new Vue({el: '#app',data:{count: 0}})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><div id="app"><button @click="fn()">切换显示隐藏</button><h1 v-show="isShow">小吴在敲Bug</h1></div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {isShow:true},methods:{fn(){this.isShow=!this.isShow}}})</script>
</body>
</html>
v-bind
- 作用:动态的设置html的标签属性
- 语法:v-bind:属性名=“表达式”
- 注意:简写形式 :属性名=“表达式”
代码演示
<!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="app"><img v-bind:src="imgUrl" v-bind:title="msg" alt=""><img :src="imgUrl" :title="msg" alt=""></div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {msg:'波仔喝奶茶',imgUrl:'./imgs/10-02.png'}})</script>
</body>
</html>
v-bind对于样式控制的增强
为了方便开发者进行样式控制,Vue扩展了v-bind的语法,可以针对class类名和style行内样式进行控制。
语法 :class="对象/数组"
-
对象 ——> 键就是类名,值是布尔值。如果值为true,有这个类,否则没有这个类
<div class="box" :class="{类名1:布尔值,类名2:布尔值}"></div>
-
数组 ——> 数组中所有的类,都会添加到盒子上,本质就是一个class列表
<div class="box" :class="[类名1,类名2,类名3]"></div>
v-for
- 作用:基于数据循环,多次渲染整个元素
- 语法:
v-for = “(item,index) in 数组”
- item每一项,index下标
代码演示
<!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="app"><h3>小黑水果店</h3><ul><li v-for="(item, index) in list">{{ item }} - {{ index }}</li></ul><ul><li v-for="item in list">{{ item }}</li></ul></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {list: ['西瓜', '苹果', '鸭梨', '榴莲']}})</script>
</body>
</html>
v-for中的key
- 语法::key属性 = “唯一标识”
- 作用:给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用
- 注意点:
- key的值只能是字符串或数字类型
- key的值必须具有唯一性
- 推荐使用id作为key(唯一),不推荐使用index作为key(会变化,不对应)
v-model
- 作用:给表单元素使用,双向数据绑定 ——> 可以快速获取或设置表单元素内容
- 数据变化 —— 视图自动更新
- 视图变化 —— 数据自动更新
- 语法:v-model=“变量”
代码演示
<!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="app"><!-- v-model 可以让数据和视图,形成双向数据绑定(1) 数据变化,视图自动更新(2) 视图变化,数据自动更新可以快速[获取]或[设置]表单元素的内容-->账户:<input type="text" v-model="username"> <br><br>密码:<input type="password" v-model="password"> <br><br><button @click="login">登录</button><button @click="reset">重置</button></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {username: '',password: ''},methods: {login () {console.log(this.username, this.password)},reset () {this.username = ''this.password = ''}}})</script>
</body>
</html>
指令修饰符
通过“.
”指明一些指令后缀
,不同后缀
封装了不同的处理操作(简化代码)
- 按键修饰符
- keyup.enter —— 键盘回车监听
- v-model修饰符
- v-model.trim —— 去除首尾空格
- v-model.number —— 转数字
- 事件修饰符
- @事件名.stop —— 阻止冒泡
- 事件名.prevent —— 阻止默认行为
计算属性
概念:基于现有的数据
,计算出来的新属性
。依赖
的数据变化,自动
重新计算
语法:
- 声明在
computed配置项
中,一个计算属性对应一个函数 - 使用起来和普通属性一样使用{ {
计算属性名
} }
computed: {计算属性名() {基于现有的数据,编写求值逻辑return 结果}
}
computed计算属性 VS methods方法
- computed计算属性:
- 作用:封装了一段对于数据的处理,求得一个结果
- 语法:
- 写在
computed
配置中 - 作为属性,直接使用 ——
this.计算属性
| { {计算属性
} }
- 写在
- 缓存特性(提升性能):
- 计算属性会对计算出来的
结果缓存
,再次使用直接读取缓存,依赖项变化了,会自动
重新计算,并再次缓存
- 计算属性会对计算出来的
- methods方法:
- 作用:给实例提供一个方法,调用以处理业务逻辑
- 语法:
- 写在
methods
配置项中 - 作为方法,需要调用 ——
this.方法名()
| @事件名=“方法名
”
- 写在
计算属性完整写法
计算属性默认的简写,只能读取访问, 不能"修改"
如果要 修改
,需要写计算属性的 完整写法
computed: {计算属性名: {get(){一段逻辑代码return 结果},set(修改的值){一段逻辑代码}}
}
watch侦听器(监视器)
作用:监视数据变化,执行一些业务逻辑或异步操作
watch——简写
data:{words: '苹果'
},
watch: {// 该方法会在数据变化时,触发执行words —— 数据属性名(newValue,oldValue){一些业务逻辑 或 异步操作},'对象.属性名'(newValue,oldValue){一些业务逻辑 或 异步操作}
}
watch——完整写法
- deep: true 对复杂类型深度监视
- immediate: true 初始化立刻执行一次 handler方法
watch: {数据属性名:{ //数据属性是对象deep: true, //深度监视,对对象里的所有子属性进行监视immediate: true // 立刻执行,打开页面就马上执行一次handler(newValue,oldValue){一些业务逻辑 或 异步操作}
}
Vue生命周期 和 生命周期的四个阶段
Vue生命周期:一个Vue实例从创建
到销毁
的整个过程
生命周期四个阶段:
- 创建:响应式数据
- 挂载:渲染模版
- 更新:数据修改,跟新视图
- 销毁:关闭浏览器
Vue生命周期函数(钩子函数)
Vue生命周期过程中,会自动运行一些函数
,被称为生命周期钩子
,让开发者可以在特定阶段
运行自己的代码
// 1.创建阶段(准备数据)
beforeCreate(){console.log("beforeCreate 响应式数据准备好之前")
},
created(){console.log("create 响应式数据准备好之后")
},
// 2.挂载阶段(渲染模板)
beforeMount(){console.log("beforeMount 模板渲染之前")
},
mounted(){console.log("mounted 模板渲染之后")
},
// 3.更新阶段
beforeUpdate(){console.log("beforeUpdate 数据修改了,视图还没更新")
},
updated(){console.log("updated 数据修改了,视图已经更新" )
},
// 4.卸载阶段
beforeDestroy(){console.log("beforeDestroy")
},
destroyed(){console.log("destroyed")
}
工程化开发
开发Vue的两种方式:
- 核心包传统开发模式:基于html/css/js文件,直接引入核心包,开发Vue
- 工程化开发模式:基于构建工具(webpack)的环境中开发Vue
脚手架 Vue cli
基本介绍:
- Vue cli 是 Vue 官方提供的一个全局命令工具
- 可以帮助我们快速创建一个开发 Vue 项目的标准化基础架子(集成了web怕吃苦配置)
好处:
- 开箱既用,零配置
- 内置babel等工具
- 标准化
使用步骤:
- 全局安装(一次):
npm i @vue/cli -g
- 查看 Vue 版本:
vue --version
- 创建项目架子:
vue create 项目名称 (项目名不能用中文)
- 启动项目:
npm run serve (找package.json)
脚手架目录文件介绍
组件化开发
- 组件化: 一个页面可以拆分成
一个个组件
,每个组件有着自己独立的结构
、样式
、行为
- 好处:便于维护,利于复用 —— 提升开发效率
- 组件分类:普通组件、根组件
根组件: 整个应用 最上层
的组件,包裹了所有普通小组件
- template
结构
- style
样式(可以支持less,需要安装less和less-loader)
- script
行为
普通组件的注册使用
组件注册的两种方式
- 局部注册:
只能在注册的组件内使用
- 创建.vue文件
- 在使用的组件内导入并注册
- 全局注册:所有组件内部都能使用
- 创建.vue文件
main.js
中进行全局注册
使用: 当成html标签使用 <组件名></组件名>
注意: 组件命名规范 —— 大驼峰命名法
scoped样式冲突
默认情况: 写在组件中的样式会 全局生效
,因此很容易造成多个组件之间的样式冲突问题
-
全局样式: 默认组件中的样式会作用到全局
-
局部样式: 可以给组件加上
scoped
属性,可以让样式只作用于当前组件
<style scoped></style>
组件通信
组件通信,就是指 组件与组件
之间的数据传递
- 组件的数据是
独立
的,无法直接访问其他组件的数据 - 想用其他组件的数据 —— 组件通信
组件关系分类:
父子关系
- 非父子关系
组件通信解决方案:
父子通信流程图
-
父组件通过
props
将数据传递给子组件
-
子组件利用
$emit
通知父组件修改更新
prop
prop定义:组件上
注册的一些 自定义属性
prop作用:向子组件传递数据
特点:
- 可以传递
任意数量
的prop - 可以传递
任意类型
的prop
props校验
作用: 为组件的prop指定 验证要求
,不符合要求,控制台就会有 错误提示
语法:
-
类型校验
props:{校验的属性名:类型 // 类型有 Number String Boolean Array Object Function }
-
非空校验
-
默认值
-
自定义校验
props:{校验的属性名:{type: 类型, // 类型有 Number String Boolean Array required: true, // 是否必填default: 默认值, // 默认值validator (value) { // 形参可以接受到传过来的值// 自定义校验逻辑return 是否通过校验 // true 通过校验 false 不通过校验}} }
非父子通信
作用: 非父子组件之间,进行简易消息传递
事件总线
-
创建一个都能访问到的事件总线(空Vue实例)
import Vue from 'vue' const Bus = new Vue() export default Bus
-
A 组件(接收方),监听
Bus 实例
的事件created(){Bus.$on('sendMsg',(msg) =>{this.msg = msg}) }
-
B组件(发送方),触发
Bus 实例
的事件Bus.$emit('sendMsg','这是一个消息')
provide & inject
provide & inject作用:跨层级
共享数据
-
父组件provide提供数据
export default {provide() {return {// 普通类型【非响应式】color: this.color,// 复杂类型【响应式】userInfo: this.userInfo}} }
-
子/孙组件inject取值使用
export default {inject: ['color','userInfo']created(){console.log(this.color,this.userInfo)} }
表单类组件封装
表单类组件封装
- 父传子:数据应该是在父组件
props
传递过来的,v-model拆解
绑定数据 - 子传父:监听输入,子传父传值给父组件修改
v-model简化代码
父组件v-model 简化代码
,实现子组件与父组件数据 双向绑定
- 子组件中:props通过
value
接收,事件触发input
- 父组件中:
v-model
给组件直接绑定数据
.sync修饰符
作用:可以实现子组件
与父组件数据
的双向绑定
特点:prop属性名,可以自定义
,非固定为value
本质:就是:属性名
和@update:属性名
和写
<BaseDialog :visble.sync="isShow" />
ref和$refs
作用:利用ref和$refs可以用于获取dom元素
,或组件实例
特点
:查找范围 —— 当前组件内(更精确稳定)
-
获取dom:
- 目标标签 —— 添加 ref 属性
<div ref="chartRef">我是渲染图表的容器</div>
- 恰当时机,通过this.$refs.xxx,获取目标标签
mounte(){console.log(this.$refs.chartRef) }
- 目标标签 —— 添加 ref 属性
-
获取组件:
- 目标组件 —— 添加ref属性
<BaseForm ref="baseForm"></BaseForm>
- 恰当时机,通过this.$refs.xxx,获取目标组件,就可以
调用组件对象里面的方法
this.$refs.baseForm.组件方法()
- 目标组件 —— 添加ref属性
Vue异步更新、$nextTick
$nextTick:等DOM更新后
,才会触发执行此方法的函数体
语法: this.$nextTick(函数体)
this.$nextTick(() => {// 业务逻辑
})
自定义指令
自定义指令:自己定义的指令,可以封装一些DOM操作
,扩展额外功能
-
全局注册 —— 语法(main.js中编写语句)
Vue.directive('指令名',{// inserted 会在指令所在的元素,被插入到页面中时触发inserted (el){// 可以对 el 标签,扩展额外功能el.focus()} })
-
局部注册 —— 语法
directives:{指令名:{inserted(el){// 可以对 el 标签,扩展额外功能el.focus()}} }
-
使用
<input v-指令名 type="text">
插槽
作用:让组件内部的一些
结构
支持自定义
默认插槽
插槽基本语法:
- 组件内需要定制的结构部分,改用<slot></slot>占位
- 使用组件时,<组件名></组件名>标签内部,传入结构替换slot
插槽后备内容(默认值)
插槽后备内容:封装组件时,可以为预留的<slot>
插槽提供后备内容
(默认值)
- 语法:在<slot>标签内,放置内容,作为默认值
- 效果:
- 外部使用组件时,不传东西,则slot会显示默认值
<组件名></组件名>
- 外部使用组件时,传东西了,则slot整体会换掉
<组件名>传的新内容</组件名>
- 外部使用组件时,不传东西,则slot会显示默认值
具名插槽
具名插槽语法
- 多个slot使用name属性区分名字
- template配合v-slot:名字来分发对应标签
v-slot:插槽名
可以简化成#插槽名
作用于插槽
基本使用步骤:
-
给slot标签,以添加属性的方式传值
<slot :id="item.id" msg="测试文本"></slot>
-
所有添加的属性,都会被收集到一个对象中
{id: 3, msg: '测试文本'}
-
咋提template中,通过
#插槽名="obj"
接收,默认插槽名为default
<MyTable :list="list"><template #default="obj"><button @click="del(obj.id)">删除</button></template> </MyTable>
路由
VueRouter的介绍
作用:修改
地址栏路径时,切换显示
匹配的组件
说明:Vue官方的一个路由插件,是一个第三方包
官网
VueRouter的使用(5+2)
五个基本步骤(main.js
)
-
下载:下载VueRouter模板到当前工程
npm install vue-router@3.6.5
-
引入
import VueRouter from 'vue-router'
-
安装注册
Vue.use(VueRouter)
-
创建路由对象
const router = new VueRouter()
-
注入,将路由对象注入到new Vue实例中,建立关联
new Vue({render: h => h(App),router }).$mount('#app')
2个核心步骤
-
创建需要的组件(views目录),配置路由规则
const router = new VueRouter({// routers 路由规则们// router 一条路由规则{path: 路径, component: 组件}routes: [{path: '/find', component: Findchildren:[{ //子路由path: '路由路径',component: 组件名}]},{path: '/my',component: My},{path: '/friend',component: Friend}] })
-
配置导航:配置路由出口(路径匹配的组件显示的位置)
<template><div><div class="footer_wrap"><a href="#/find">发现音乐</a><a href="#/my">我的音乐</a><a href="#/friend">朋友</a></div><div class="top"><!-- 路由出口 → 匹配的组件所展示的位置 --><router-view></router-view></div></div> </template>
路由的封装抽离
问题:所有的路由配置都堆在main.js中不合适
目标:将路由模块抽离出来
好处:拆分模块
,利于维护
-
创建router\index.js目录与文件
-
在index.js文件中编写路由规则
import Find from "@/views/Find"; import My from "@/views/My"; import Friend from "@/views/Friend";import Vue from "vue"; import VueRouter from "vue-router"; Vue.use(VueRouter) //VueRouter插件初始化const router = new VueRouter({// routers 路由规则们// router 一条路由规则{path: 路径, component: 组件}routes: [{path: '/find', component: Find},{path: '/my',component: My},{path: '/friend',component: Friend}] })// 导出router export default router
-
在main.js文件中引用
使用router-link(声明式导航)
vue-router提供了一个全局组件router-link(取代了a标签)
能跳转
,配置to属性指定路径(必须
),本质还是a标签,to无需#
能高亮
,默认就会提供高亮类名
,可以直接设置高亮样式
router-link —— 两个类名
- router-link-active
模糊匹配(常用)
to=“/my” 可以匹配 /my /my/a /my/b …… - router-link-exact-active
精确匹配
to=“/my” 仅可以匹配 /my
自定义匹配类名
说明:router-link的两个类名太长了
,我们可以自定义匹配类名
const router = new VueRouter({// routers 路由规则们// router 一条路由规则{path: 路径, component: 组件}routes: [ …… ],林肯ActiveClass: '精确匹配的类名',linkExactActiveClass: '模糊匹配的类名'
})
声明式导航——跳转传参
目标:在跳转路由时,进行传值
- 查询参数传参
- 语法:
to="/path?参数名=值(&参数名n=值n)
- 对应页面组件接收传递过来的值:
$router.query.参数名
- 语法:
- 动态路由传参
- 配置动态路由:
path:"/path/:参数名
/path/:参数名,必须要传参数,如果不传参数,也希望匹配,可以加个可选符?
(/path/:参数名?)const router = new VueRouter({routes: [{ path: '/search/:words', component: Search }] })
- 配置导航链接:
to="/path/参数值
- 对应页面组件接收传递过滤的值:
$route.params.参数名
- 配置动态路由:
重定向
问题:网页打开,url默认是/路径,未匹配到组件时,会出现空白
重定向:匹配到path后,强制跳转path路径
语法
: {path: 匹配路径,redirect: 重定向到的路径}
const router = new VueRouter({routes: [{path: '/',redirect: '/home'},{path: '/home', component: Home},{path: '/search', component: Search}]
})
404页面设置
作用: 当路径找不到匹配时,给个提示页面
位置: 配在路由最后
语法:path:“*”(任意路径)
—— 前面的路由不匹配就匹配最后一个
const router = new VueRouter({routes: [{path: '/',redirect: '/home'},{path: '/home', component: Home},{path: '/search', component: Search},{path: "*",component: NotFind}]
})
路由模式
问题:路由的路径看起来不自然,有#,能否切换成真正的路径形式?
- hash路由(默认):例如:http://localhost:8080/#/home
- history路由(常用):例如:http:/localhost:8080/home
const router = new VueRouter({routes: [ …… ],mode: "history"
})
编程式导航
问题:点击按钮跳转如何实现?
编程式导航:用JS代码来进行跳转
基本跳转
-
path路径跳转(简易方便)
this.$router.push('路由路径')this.$router.push({path: '路由路径' })
-
name命名路由跳转(适合path路径长的场景)
this.$router.push({name: '路由名' }){name: '路由名',path: '/path',component: 组件名}
路由传参
两种传参方式:查询参数+动态路由传参
两种跳转方式,对于两种传参方式都支持
-
path路径跳转传参(query传参)
this.$router.push('路由路径?参数名1=参数值1&参数名2=参数值2')this.$router.push({path: '路由路径',query:{参数名1: '参数值1',参数名2: '参数值2'} })
-
path路径跳转传参(动态路由传参)
this.$router.push('路由路径/参数值')this.$router.push({path: '路由路径/参数值' })
-
name命名路由跳转传参(query传参)
this.$router.push({path: '路由名',query:{参数名1: '参数值1',参数名2: '参数值2'} })
-
name命令路由跳转传参(动态路由传参)
this.$router.push({path: '路由名',params:{参数名: '参数值'} })
组件缓存(keep-alive)
-
使用
keep-alive
包裹路由出口,可以使得切换组件时上一个组件不会被销毁<keep-alive><router-view></router-view> </keep-alive>
-
keep-alive的三个属性
- include:组件名数组,只有匹配的组件会被缓存
- exclude:组件名数组,任何匹配的组件都不会被缓存
- max:最多可以缓存多少组件实例
<keep-alive :include="['组件名1','组件名2']"> <router-view></router-view> </keep-alive>
-
两个钩子
- activated:进入页面钩子
- deactivated:离开页面钩子
ESlint代码规范
代码规范:一套写代码的约定规则
老话说:“没有规矩不成方圆
” —— 正规的团队需要统一
的编码风格
如果你的代码不符合standard的要求,ESlint会跳出来刀子嘴,豆腐心提示你
JavaScript Standard Style 规范说明
- 字符串使用单引号:‘abc’
- 无分号:const name = ‘xw’
- 关键字后加空格:if (name = ‘ls’) { ···}
- 函数名后加空格: function name (arg) { ··· }
- 坚持使用全等 === 摒弃 ==
……
代码规范错误解决方案
- 手动修正
- 更具错误提示一项一项手动修改纠正
- 如果不认识命令行中的语法报错是什么意思,根据错误代码去ESlint规则表中查找其具体含义
- 自动修正
- 快捷修复,右键点击唤出快捷菜单,点击“Fix ESLint Problems"。
- 快捷修复,右键点击唤出快捷菜单,点击“Fix ESLint Problems"。
vuex
概述
vuex是一个vue的状态管理工具(状态就是数据)
大白话:vuex是一个插件,可以帮我们管理vue通用的数据(多组件共享的数据)
场景:
- 某个状态在很多个组件来使用(个人信息)
- 多个组件共同维护一份数据(购物车)
优势:
- 共同维护一份数据,数据集中化管理
- 响应式变化
- 操作简洁(vuex提供了一些辅助函数)
创建一个空仓库
多组件共享的数据环境
目标:安装vuex插件,初始化一个空仓库
-
npm install vuex@3
-
新建 store/index.js专门存放vuex
-
Vue.use(Vuex)
创建创库new Vuex.Store()
// 这里存放的就是 vuex的相关核心代码 import Vue from 'vue' import Vuex from 'vuex'// 插件安装 Vue.use(Vuex)// 创建仓库(空仓库) const store = new Vuex.Store()// 导出给main.js使用 export default store
-
在main.js中导入挂载到Vue实例上
提供与访问vuex的数据
目标:明确如何给仓库提供数据,如何使用仓库数据
-
提供数据: State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。在State对象中可以添加我们要共享的数据
// 创建仓库 const store = new Vuex.Store({// state状态:即数据,类似于Vue组件中的data/*区别:1.data是自己的数据2.State是所有组件共享的数据*/state: {count: 100} })
-
使用数据:
- 通过store直接访问
获取 store: (1)this.$store (2)import 导入 store模板中:{{$store.state.xxx}} 组件逻辑中:this.$store.state.xxx JS模块中:store.state.xxx
- 通过辅助函数(简化): mapState是辅助函数,帮助我们把store中的数据自动映射到组件的计算属性中
import { mapState } from 'vuex'
mapState(['count'])
computed: { ...mapState(['count']) }
- 通过store直接访问
修改vuex的数据
目标:明确vuex同样是遵循单向数据流,组件中不能直接修改仓库的数据
通过strict: true
可以开启严格模式(项目上线移除掉,会吃性能)
// 创建仓库(空仓库)
const store = new Vuex.Store({// 开启严格模式strict: true,// state状态:即数据,类似于Vue组件中的data/*区别:1.data是自己的数据2.State是所有组件共享的数据*/state: {title: '大标题',count: 100}
})
mutations
目标掌握mutations的操作流程,来修改state数据(state数据的修改只能通过mutations)
-
定义mutations对象,对象中存放修改state的方法
const store = new Vuex.Store({state: {title: '大标题',count: 100},// 定义mutationsmutations: {// 第一个参数是当前store的state属性addCount (state){state.count += 1}} })
-
组件中提交调用mutations
this.$store.commit('addCount')
mutations传参语法
-
提供 mutation 函数(带参数 —— 提交载荷 payload)
mutations: {...addCount (state, n) {state.count += n} }
-
页面中调用 mutation
this.$store.commit('addCount', 10)
辅助函数(mapMutations):mapMutations 和 mapState 很像,它是把位于mutations中的方法提取出来,映射到组件methods中
mutations: {subCount (state, n) {state.count -= n}
}
import { mapMutations } from 'vuex'methods: {...mapMutations(['subCount'])
}
this.subCount(10) //JS中调用
actions
目标:明确 actions 的基本语法,处理异步操作
说明:mutations 必须是同步的(便于检测数据变化,记录调试)
-
提供actions方法
actions: {setAsyncCount (context, num) {// 一秒钟后,给一个数,去修改 numsetTimeout(() => {context.commit('changeCount', num)}, 1000)} }
-
页面中 dispatch 调用
this.$store.dispatch('setAsyncCount', 200)
辅助函数mapActions
目标:掌握辅助函数 mapActions,映射方法
mapAction 是把位于actions中的方法提取出来,映射到组件methods中
actions: {setAsyncCount (context, num) {// 一秒钟后,给一个数,去修改 numsetTimeout(() => {context.commit('changeCount', num)}, 1000)}
}
import { mapActions } from 'vuex'methods: {...mapActions(['setAsyncCoun'])
}
this.setAsyncCoun(666) //调用
getters
目标:掌握核心概念 getters 的基本语法(类似于计算属性)
说明:除了state之外,有时我们还需要从state中派生出一些状态,这些状态是依赖state的,此时会用到getters
例如:state 中定义了list,为1-10的数组,组件中,需要显示所有大于5的数据
state: {list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
-
定义 getters
getters: {/*注意:(1) getters函数的第一个参数是 state(2)getters函数必须要有返回值*/filterList (state) {return state.list.filter(item => item > 5)} }
-
访问getters
-
通过store访问getters
{{ $store.getters.filterList }}
-
通过辅助函数 mapGetterrs映射
import { mapGetters } from 'vuex'
computed: {...mapGetters(['filterList']) }
{{ filterList }} //使用
-
模块module(进阶语法)
由于vuex使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿(当项目变得越来越大的时候,vuex会变得越来越难以维护)
模块拆分:
user模块:store/modules/user.js
-
创建文件
-
编写模块
// user模块 const state = {userInfo: {name: 'zhangsan',age: 18},score: 80 } const mutations = {} const actions = {} const getters = {}// 导出模块 export default {state,mutations,actions,getters }
-
导入和挂载
import user from '@/store/modules/user' const store = new Vuex.Store({modules: {user} })
访问模块中的state
尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的state中,属性名就是模块名
使用模块中的数据:
- 直接通过模块名访问
$store.state.模块名.xxx
- 通过mapState映射
- 默认根级别的映射
mapState(['xxx'])
- 子模块的映射
mapState('模块名',['xxx'])
需要开启命名空间export default {namespaced: true, // 开启命名空间state,mutations,actions,getters }
- 默认根级别的映射
使用模块中getters中的数据
- 直接通过模块名访问**
$store.getters['模块名/xxx']
** - 通过mapGetters映射
- 默认根级别的映射
mapGetters(['xxx'])
- 子模块的映射
mapGetters('模块名',['xxx']
需要开启命名空间export default {namespaced: true, // 开启命名空间state,mutations,actions,getters }
- 默认根级别的映射
调用子模块中mutation
注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块
- 直接通过 store 调用
$store.commit('模块名/xxx',额外参数)
- 通过 mapMutations 映射
- 默认根级别的映射
mapMutations(['xxx'])
- 子模块的映射
mapMutations('模块名',['xxx'])
需要开启命名空间export default {namespaced: true, // 开启命名空间state,mutations,actions,getters }
- 默认根级别的映射
调用子模块中action
注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块
- 直接通过 store 调用
$store.dispatch('模块名/xxx',额外参数)
- 通过 mapMutations 映射
- 默认根级别的映射
mapActions(['xxx'])
- 子模块的映射
mapActions('模块名',['xxx'])
需要开启命名空间export default {namespaced: true, // 开启命名空间state,mutations,actions,getters }
- 默认根级别的映射
Vue3快速入门
Vue3的优势
Vue3 组合式API vs Vue2 选项式 API
-
代码量变少了
-
分散式维护转为集中式维护,更容易封装复用
需求:单击按钮,让数字+1 -
选项式API
<script> export default {data () {return {count: 0}},methods: {addCount () {this.count++}} } </script>
-
组合式API
<script setup> import [ ref } from 'vue' const count = ref(0) const addCount = () => count.value++ </script>
创建Vue3项目
create-vue是Vue官方新的脚手架工具,底层切换到了 vite(下一代构建工具),为了开发提供极速响应
前提环境条件:已安装 16.0 或更高版本的 Node.js
创建一个Vue应用:npm init vue@latest(这一指令将会安装并执行create-vue)
项目目录
关键文件:
- vite.config.js —— 项目的配置文件 基于vite的配置
- package.json —— 项目包文件 核心依赖项变成了 Vue3.x 和 vite
- main.js —— 入口文件 createApp函数创建应用实例
- app.vue —— 根组件 SFC单文件组件 script - template - style
- 变化一:脚本script和模板template顺序调整
- 变化二:模板template不再要求唯一根元素
- 变化三:脚步script添加setup标识支持组合式API
- Index.html —— 单页入口 提供id为app的挂载点
组合式API —— setup选项
setup选项的写法和执行时机
setup选项中写代码的特点
<script setup> 语法糖
组合式API —— reactive 函数
作用:接受对象类型数据的参数传入并返回一个响应式的对象
核心步骤:
<script setup>
// 导入
import { reactive } from 'vue'// 执行函数,传入参数,变量接收
const state = reactive(对象类型数据)</script>
- 从 vue 包中导入 reactive 函数
- 在<script setup> 中执行 reactive 函数并传入类型为对象的初始值,并使用变量接收返回值
组合式API —— ref 函数
作用:接受简单类型或者对象类型的数据传入并返回一个响应式的对象
核心语法
<script setup>
// 导入
import { ref } from 'vue'// 执行函数,传入参数,变量接收
const state = ref(简单类型或者复杂类型数据)</script>
- 从 vue 包中导入 ref 函数
- 在 <script setup> 中执行 ref 函数并传入初始值,使用变量接收 ref 函数的返回值
- ref参数类型支持更好但是必须通过
.value
访问修改
组合式API —— computed
计算属性基本思想和Vue2的完全一致,组合式API下的计算属性只是修改了写法
核心步骤:
<script setup>
// 导入
import { computed } from 'vue'// 执行函数 变量接收 在回调参数中return计算值
const computedState = computed( () => {return 基于响应式数据做计算之后的值
})
</script>
- 导入computed函数
- 执行函数在回调参数中return基于响应式数据计算的值,用变量接收
计算属性Demo
<script setup>
import {computed, ref} from "vue";// 声明数据
const list = ref([1,2,3,4,5,6,7,8])// 基于list派生一个计算属性,从list中过滤出 > 2
const computedList = computed(()=>{return list.value.filter((item) => item>2 )
})// 定义一个修改数组的方法
const addFn = () => {list.value.push(307)
}
</script><template><div><div>原始数据:{{list}}</div><div>计算后的数据:{{computedList}}</div><button @click="addFn">修改</button></div>
</template>
最佳实践
- 计算书中不应该有“副作用”
- 比如异步请求/修改DOM
- 避免直接修改计算属性的值
- 计算属性应该是只读的,特殊情况可以配置 get set
组合式API —— watch
作用:侦听一个或者多个数据的变化,数据变化时执行回调函数
两个额外参数:1. immediate(立即执行) 2.deep(深度侦听)
监听单个数据
- 导入watch函数
- 执行watch函数传入要侦听的响应式数据==(ref对象)==和回调函数
<script setup>
// 1.导入watch
import { ref, watch } from 'vue'
const count = ref(0)// 2.调用watch 侦听变化
watch(count,(newValue, oldValue) => {console.log(`count发生了变化,老值为${oldValue},新值为${newVaule}`)
})
</script>
监听多个数据
说明:同时监听多个响应式数据的变化,不管哪个数据变化都需要执行回调函数
<script setup>
// 1.导入watch
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('cp')// 2.调用watch 侦听变化
watch([count, name],// watch([ref对象1,ref对象2], (newArr, oldArr) => { …… })(count,([newCount, newName], [oldCount, oldName]) => {console.log('count或者name发生了变化',[newCount, newName], [oldCount, oldName])}
)
</script>
immediate
说明:在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调
<script setup>
// 1.导入watch
import { ref, watch } from 'vue'
const count = ref(0)// 2.调用watch 侦听变化
watch(count,(newValue, oldValue) => {console.log(`count发生了变化,老值为${oldValue},新值为${newVaule}`)}, {immediate: true}
)
</script>
deep
说明:deep深度监视,默认watch进行的是,浅层监视
coust ref1 = ref(简单类型) 可以直接监视
coust ref2 = ref(复杂类型) 监视不到复杂类型内部数据的变化
<script setup>
const userInfo = ref({name: '张三',age: 18
})watch(userInfo, (newValue, oldValue) => {console.log(newValue, oldValue)
},{deep: true,immediate: true
})
</script>
精确侦听对象的某个属性
需求:在不开启deep的前提下,侦听age的变化,只有age变化时才执行回调
const info = ref({name: 'xw',age: 21
})
watch(() => info.value.age,(newValue, oldValue) => console.log('age发生变化了')
)
组合式API —— 生命周期函数
Vue3 的生命周期API(选项式 VS 组合式)
选项式API | 组合式API |
---|---|
beforeCreate/created | setup |
beforeMount | OnBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
组合式API —— 父子通信
组合式API下的父传子
- 父组件中给子组件绑定属性
- 子组件内部通过props选项接收
组合式API下的子传父
- 父组件中给子组件标签通过@绑定事件
- 子组件通过emit方法触发事件
组合式API —— 模板引用
通过ref标识获取真实的dom对象或者组件实例对象
- 调用ef函数生成一个ref对象
- 通过ref标识绑定ref对象到标签
<script setup>
import { ref } from 'vue'
// 1.调用ref函数得到ref对象
const h1Ref = ref(null)
</script><template>
<!-- 2.通过ref标识绑定ref对象 -->
<h1 ref="h1Ref">我是dom标签h1</h1>
</template>
defineExpose()
默认情况下在<script setup>语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法是允许访问
组合式API —— provide 和 inject
作用和场景:顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
跨层传递普通数据
- 顶层组件通过provide函数提供数据
- 底层组件通过inject函数获取数据
跨层传递响应式数据
- 在调用provide函数时,第二个参数设置为ref对象
跨层传递方法
- 顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件中的数据
新特性 —— defineOptions
有
<script setup>
之前,如果要定义 props,emits 可以轻而易举地添加一个与 setup
平级的属性。但是有了<script setup>
后,就没法这么干了 setup 属性已经没有了,自然无法添加与其平级的属性为了解决这一问题,引入了 defineProps 与 defineEmits 这两个宏。但这只解决了 props 与 emits 这两个属性。如果我们要定义组件的 name 或其他自定义的属性,还是得回到最原始的用法——再添加一个普通<script>标签。这样就会存在两个<script>标签。让人无法接受
所以在Vue 3.3 中引入了 defineOptions 宏。顾名思义,主要是用来定义 Options API 的选项。可以用defineOptions 定义任意的选项, props,emits,expose,slots厨卫(因为这些可以使用 defineXXX 来做到)
<script setup>
defineOptions({name: 'Foo',inheritAttrs: false// ... 更多自定义属性
})
</script>
新特性 —— defineModel
-
在Vue3中,自定义组件上使用v-model,相当于传递一个modelValue属性,同时触发 update:modelValue 事件
<Chile v-model="isVisible"> // 相当于 <Child :modelValue="isVisible" @update:modelValue="isVisible=$event">
<script setup> const modelValue = defineModel() modelValue.value++ </script>
Pinia
Pinia 是 Vue 的最新 状态管理工具,是 Vuex 的代替品
- 提供更加简单的API(去掉了 mutation)
- 提供符合,组合式风格的API(和Vue3新语法统一)
- 去掉了 modules 的概念,每一个 store 都是要给独立的模板
- 配合 TypeScript 更加友好,提供可靠的类型推断
手动添加Pinia到Vue项目
-
使用 Vite 创建一个空的 Vue3 项目
npm create vue@latest
-
按照官方文档 安装 pinia 到项目中
import {createApp} from 'vue' import {createPinia} from 'pinia' import App from './App.vue'const pinia = createPinia() // 创建Pinia实例 const app = createApp(App) // 创建根实例 app.use(pinia) // Pinia插件的安装配置 app.mount('#app') // 视图挂载
Pinia基础使用 —— 计数器案例
-
创建一个创库 store/counter.js
-
定义store
import { defineStore } from "pinia" import {computed, ref} from "vue";// 定义store // defineStore('仓库的唯一标识', () => { ... })export const useCounterStore = defineStore('counter', () => {// 声明数据 state —— countconst count = ref(0)// 声明操作数据的方法 actionsconst addCount = (add) => count.value+=add// 声明基于数据派生的计算属性 gettersconst double = computed(() => count.value*2)// 声明数据 state —— msgconst msg = ref('hello pinia')return {count,addCount,double,msg} })
-
组件使用store
<script setup> import { useCounterStore } from '@/store/counter' const counterStore = useCounterStore() // 声明实例 </script><template><div>我是Son1.vue - {{counterStore.count}} - {{counterStore.double}}<button @click="counterStore.addCount(5)">+</button></div></template>
action异步实现
编写方式:异步action函数的写法和组件中获取异步数据的写法完全一致
接口地址:http://geek.itheima.net/v1_0/channels
需求:再Pinia中获取频道列表数据并把数据渲染App组件模板中
-
创建一个创库 store/channel.js
-
定义 store
import { defineStore } from 'pinia' import {ref} from "vue"; import axios from "axios";export const useChannelStore = defineStore('channel', () => {// 声明数据const channelList = ref([])// 声明操作数据的方法const getList = async () => {const {data: { data }} = await axios.get('http://geek.itheima.net/v1_0/channels')channelList.value = data.channelsconsole.log(data.channels)}// 声明getters相关return {channelList,getList} })
-
组件使用store
storeToRefs方法
请注意,store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value,就像 setup 中的 props 一样,如果你写了,我们也不能解构它
为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:
注意:方法不需要使用storeToRefs方法解构,直接解构即可
Pinia持久化插件
官方文档
-
安装插件 pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate
-
在main.js中导入使用
import {createApp} from 'vue' import App from './App.vue' import {createPinia} from 'pinia' // 导入持久化插件 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const pinia = createPinia() // 创建Pinia实例 const app = createApp(App) // 创建根实例 app.use(pinia.use(piniaPluginPersistedstate)) // Pinia插件的安装配置 app.mount('#app') // 视图挂载
-
store仓库中,persist: true开启