第一网站ppt模板免费下载,WordPress搬家后所有页面404,网站建设程序招聘,网站建设入驻文章目录 Vue2进阶学习笔记前言1、Vue脚手架学习1.1 Vue脚手架概述1.2 Vue脚手架安装1.3 常用属性1.4 插件 2、组件基本概述3、非单文件组件3.1 非单文件组件的基本使用3.2 组件的嵌套 4、单文件组件4.1 快速体验4.2 Todo案例 5、浏览器本地存储6、组件的自定义事件6.1 使用自定… 文章目录 Vue2进阶学习笔记前言1、Vue脚手架学习1.1 Vue脚手架概述1.2 Vue脚手架安装1.3 常用属性1.4 插件 2、组件基本概述3、非单文件组件3.1 非单文件组件的基本使用3.2 组件的嵌套 4、单文件组件4.1 快速体验4.2 Todo案例 5、浏览器本地存储6、组件的自定义事件6.1 使用自定义事件传递数据6.2 解绑自定义事件 7、全局事件总线8、消息订阅和发布9、过渡和动画9.1 手工实现9.2 使用第三方库 10、Vue中AJAX的使用10.1 快速体验10.2 跨域问题 11、插槽11.1 默认插槽11.2 具名插槽11.3 作用域插槽 12、Vuex12.1 Vuex介绍12.2 快速体验12.3 getters配置项12.4 mapStatemapGetters12.5 mapActionsmapMutations12.6 Vuex中的模块化技术 13、路由13.1 路由介绍13.2 快速体验13.3 路由嵌套13.4 路由传参13.5 路由的命名13.6 路由的props配置13.7 router-link的replace属性13.8 编程式路由导航13.9 缓存路由组件13.10 生命周期钩子13.11 路由守卫13.12 路由的两种工作模式 14、Vue的UI组件库  Vue2进阶学习笔记 
前言 欢迎来到知识汲取者的个人博客在这篇文章中我将为你介绍Vue.js的一些进阶知识帮助你快速入门并掌握Vue开发的一些进阶技巧。Vue.js是一个流行的JavaScript框架被广泛用于构建现代化、交互式的Web应用程序。它采用了MVVMModel-View-ViewModel架构模式通过数据驱动视图的方式实现了高效的前端开发。在这片文章中我们将学习Vue脚手架的使用、组件的封装与使用、事件与动画、插槽、Vuex和VueRouter、最后还有一些常用Vue组件库的推荐如果你是一个初学者相信通过本文的学习一定可以让你对Vue有一个更加深入的认知同时快速掌握这些进阶知识。 PS对于文章一些描述不当、存在错误的地方还请大家指出笔者不胜感激 推荐阅读 Vue2官方文档Vue3官方文档Vue2基础速通 1、Vue脚手架学习 
1.1 Vue脚手架概述 什么是脚手架   上面是Vue脚手架的官方介绍我们可以理解脚手架就是为自动帮我们构建项目的工具。Vue脚手架全名Vue Command Line Interface简称VueCLI直接翻译就是Vue命令接口工具不得不佩服第一次将这个东西翻译成”脚手架“这个词的人VueCLI正如工地上的脚手架一样能够快速帮我们自动搭建好Vue项目 Home | Vue CLI (vuejs.org)Vue脚手架官方文档   脚手架有什么作用  自动一键生成vuewebpack的项目模版包括依赖库从而大大减少项目构建所需的时间   脚手架如何使用  下文会讲   
1.2 Vue脚手架安装 Step1安装node.js  参考文章  我们需要用到npm命令  Step2安装VueCLI npm install -g vue/cli下载过慢的可以配置淘宝镜像 npm config set https://registry.npm.taobao.org安装后重启cmd窗口输入vue指令如果呈现以下这样就说明你的Vue脚手架已经安装好了   Step3创建Vue项目 vue create 项目名注意时你要切换到你需要创建项目的目录不要在VueCLI的安装目录下直接创建Vue项目    Step4运行创建好的Vue项目 注意要先使用cd vue_test指令进入刚刚创建好的Vue项目中然后再执行npm run serve指令运行Vue项目在Vue脚手架初始化创建项目时他会自带一个helloword并且它会将这个项目默认部署在一个微型服务器上这个服务器的默认端口号是8080所以成功运行后可以直接在浏览器访问到Vue脚手架自带的HelloWord项目  备注停止运行直接在cmd窗口中按Ctrl  C  可以看到项目无法访问了 Vue模板项目的结构 Vue 脚手架隐藏了所有 webpack 相关的配置若想查看具体的 webpakc 配置 请执行vue inspect  output命令 模板项目默认是引入精简版的Vue缺少模板解析器这只是一种精简版此外还有很多不同的精简版引入精简版Vue的目的是节约内存因为如果一直使用完整版的Vue当我们的项目被webpack打包时会讲模板解析器打包而webpack打包后的项目是已经经过解析了的此时模板解析器显得很多余。所以当我们要使用精简版vue使用tempate添加DOM时会报错可以使用render函数解决 
render(createElement){return h1,你好
}
//简写
render:h  h(‘h1’,你好)1.3 常用属性 ref用来给元组或子组件注册引用信息id的替代者 当用在HTML标签上是获取真实的DOM元素和id的作用一样当用在组件标签上则是获取组件的实例对象这个和id不一样id是获取组件所在根标签的真实DOM即获取的是组件最外层的div 实例 div refa/div
this.$refs.aprops App组件 !-- :让数据动态绑定这样就能够使age进行运算否则age就是一个字符串 --
Student name张三 :age18/StudentStudent组件 h2{{name}}/h2
h2{{age1}}/h2  !--此处的age在页面中是19如果age不加:此处就是181--
//简单接收
props[name,age]
//接收且对数据进行限制
props:{name:String,age:Number
}
//更加完善的配置
props:{name:{String,    required:true //name是必要的没有app组件没有传name过来就会报错}age:{Number,default:18 // 设置默认值没有传ageage默认就是18}
}注意如果props和data中数据出现重名props中的数据优先级更高props中的数据最好不要去修改否则会出现警告可能会导致Vue发生奇怪的bug所以要按规范写如果真的想要修改可以在组件中重新定义一个变量  mixin混合把多个组件共有的配置提取成一个混入对象作用是提高代码的复用性 mixin.js 用于存放要复用的代码可以复用数据、函数包括生命周期函数如果数据和普通函数在原组件中也存在则以原来的为准原来没有的混合(mixin.js)中有的就直接在原来的组件中加上。但是对于生命周期函数而言两者都要 //混合1
export const mixin  {data(){return{name:张三}}
}
//混合2
export const minx2  {mounted(){console.log(mounted被执行了)}
}Student组件 import {mixin,mixin2} from ../mixin
export default{name:School,data(){return{name:一中}}//局部混合mixins:[mixin,mixin2]
}main.js: //引入APP组件
import {mixin,mixin2} from ./mixin
Vue.mixin(mixin)
Vue.mixin(mixin2)scoped随机生成一个data-v类似于UUID 作用方式样式冲突因为在一个Vue项目中最终所有的样式都会汇总到一起如果出现重名就会发生样式覆盖后引入的组件会覆盖前引入组件的样式就是学CSS时的”就近原则“ style scoped classdemo
/style1.4 插件 
作用用于增强Vue 
本质包含install方法的一个对象install的第一个参数是Vue第二个以后的参数是插件使用者传递的数据。 Step1定义一个插件 export default {install(Vue, name){console.log(Vuename)//插件中还可以进行一些全局配置比如过滤器、全局指令、在Vue原型中添加数据、定义混入对象...}
}Step2使用插件 Vue.use(plugins, 张三)2、组件基本概述 什么是模块  模块是一个提供特定功能的JS程序一般的表现形式就是一个JS文件。模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程有多种属性分别反映其内部特性。   模块化的作用 提高复用率模块化对每一个功能进行了划分能够更好地移植相同代码提高编码的效率降低编码的复杂度模块化很好的界定各个功能特别是对于大型系统的开发让开发者能够分工明确提高系统的可维护性模块化让系统更加清晰能够更好地定位问题和修改代码  什么是组件  组件是实现应用中局部功能代码和资源的集合。组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程由多种属性、状态反映其内部特性。 个人见解是一个可重复利用的Vue实例本质是代码的复用类似于Java中的类Java中的类也可以看作是一个组件   组件化的优点 提高复用率。提高编码效率。高扩展性。提高了可维护性。  组件化和模块化的区别  两者一般很难区分因为两者的主要目的都相同 都是为了提高代码的复用率模块就是完成某一功能的程序而组件是可重用代码的集合组件和模块有时是可以相互转换的。但一般而言模块的层级是要大于组件的层级的一个大型项目会先进性模块划分然后再对每个模块进行组件划分。 PS这两个概念在我看来并不是说一定要马上进行区分通过后期编写项目都是可以慢慢深入理解区分的现在我们只需要大致了解即可因为他对我们的编码并没有影响   
3、非单文件组件 一个文件中包含n个组件n2 3.1 非单文件组件的基本使用 
!DOCTYPE html
html langenheadmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/titlescript src./js/vue.js/script
/head
div idapp!-- Step3将组件引入页面中 --school/schoolhrstudent/student
/div
scriptVue.config.productionTip  false;//Step1创建组件const school  Vue.extend({template: divh2学校姓名{{schoolName}}/h2h2学校地址{{schoolAddress}}/h2/div,data() {return {schoolName: 一中,schoolAddress: 长沙}}});//Step1创建组件const student  {template: divh2学生姓名{{studentName}}/h2h2学生年龄{{studentAge}}/h2/div,data() {//此处return可以避免组件之间数据共享的冲突使用return可以让每个组件互不影响return {studentName: 张三,studentAge: 18}}}//Step2全局注册组件Vue.component(student, student)new Vue({el: #app,//Step2注册组件components: {// school: school//简写形式school}});
/scriptbody/body/html注意事项 当组件名是多个单词是可以使用kebab-case方式和cameCase方式 备注使用kebab-case方式命名组件需要使用引号包裹使用cameCase组件必须是在脚手架环境下后面学脚手架时会详细学习否则会报错  组件名不能和HTML的标签同名否则会报错  组件的名字直接在注册时确定但是可以使用name属性在创建组件时确定  
3.2 组件的嵌套 
!DOCTYPE html
html langenheadmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/titlescript src./js/vue.js/script
/headbodydiv idrootapp/app/divscriptVue.config.productionTip  false;//创建student组件const student  {template: divh2{{student}}/h2/div,data() {return {student: 张三}}}//创建school组件const school  {template: divh2{{school}}/h2student/student/div,data() {return {school: 一中}},components: {student}}//创建app组件vm下面就它一个组件const app  {template: divschool/school/div,components: {school}}//创建vmnew Vue({el: #root,data: {},components: {app}})/script
/body/html知识拓展VueComponent 
上面组件的本质都是一个VueComnent的构造函数且不是程序员定义的是Vue.extend生成的我们只需要直接使用组件标签Vue在解析模板时会自动帮我们去调用VueComnent函数也就是执行new VueComnent(options)每次调用Vue.extend都会返回一个全新的VueComponent对象组件配置中data函数、methods函数、watch函数等的this都是VueComponent简称vc组件实例对象而 new Vue配置中所有的this都是vm函数拥有显示原型对象对象拥有隐式原型对象两者都指向同一个原型对象一个重要的内置关系VueComponent.prototype.__proto__  Vue.prototype 所以组件实例对象vc可以访问到Vue实例对象vm上的属性和方法 
4、单文件组件 一个文件中只包含一个组件常见形式。 4.1 快速体验 #mermaid-svg-L0K7PHvcbk70epJp {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-L0K7PHvcbk70epJp .error-icon{fill:#552222;}#mermaid-svg-L0K7PHvcbk70epJp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-L0K7PHvcbk70epJp .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-L0K7PHvcbk70epJp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-L0K7PHvcbk70epJp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-L0K7PHvcbk70epJp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-L0K7PHvcbk70epJp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-L0K7PHvcbk70epJp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-L0K7PHvcbk70epJp .marker.cross{stroke:#333333;}#mermaid-svg-L0K7PHvcbk70epJp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-L0K7PHvcbk70epJp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-L0K7PHvcbk70epJp .cluster-label text{fill:#333;}#mermaid-svg-L0K7PHvcbk70epJp .cluster-label span{color:#333;}#mermaid-svg-L0K7PHvcbk70epJp .label text,#mermaid-svg-L0K7PHvcbk70epJp span{fill:#333;color:#333;}#mermaid-svg-L0K7PHvcbk70epJp .node rect,#mermaid-svg-L0K7PHvcbk70epJp .node circle,#mermaid-svg-L0K7PHvcbk70epJp .node ellipse,#mermaid-svg-L0K7PHvcbk70epJp .node polygon,#mermaid-svg-L0K7PHvcbk70epJp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-L0K7PHvcbk70epJp .node .label{text-align:center;}#mermaid-svg-L0K7PHvcbk70epJp .node.clickable{cursor:pointer;}#mermaid-svg-L0K7PHvcbk70epJp .arrowheadPath{fill:#333333;}#mermaid-svg-L0K7PHvcbk70epJp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-L0K7PHvcbk70epJp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-L0K7PHvcbk70epJp .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-L0K7PHvcbk70epJp .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-L0K7PHvcbk70epJp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-L0K7PHvcbk70epJp .cluster text{fill:#333;}#mermaid-svg-L0K7PHvcbk70epJp .cluster span{color:#333;}#mermaid-svg-L0K7PHvcbk70epJp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-L0K7PHvcbk70epJp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}                                                                                      编写功能组件           编写app组件           编写main.js           编写index.html           测试          Step1编写功能组件 每一个组件都是一个小的部件完成一个小的功能 1Student组件 template//这里写html代码注意div必须要divh2{{studentName}}/h2h2{{studentAge}}/h2h2 v-ifisShow{{studentSex}}/h2button clickshowSchool点击显示性别/button/div
/templatescript//这里写JS代码export default {name:Student,data(){return{studentName:张三,studentAge:19,studentSex:男,isShow:false}},methods:{showSchool(){this.isShow  true;}}}
/scriptstyle
/*这里写css代码*/
/style2School组件 templatedivh2{{schoolName}}/h2/div
/templatescriptexport default {name:School,data(){return{schoolName:一中}}}
/scriptstyle
/styleStep2编写APP组件 App组件用户统一管理其它所有的组件APP组件是”万人之上一人之下“它只归vm管理 templatedivStudent/StudentSchool/School/div
/templatescriptimport Student from ./Student.vue
import School from ../School.vue
export default {name:App,components:{Student,School}
}
/scriptstyle/styleStpe3编写main.js mian.js负责创建vm import App from ./App.vuenew Vue({el: #root,components: { App }
})Step4编写index.html index.html真正用来展示的网页 !DOCTYPE html
html langenheadmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/headbodydiv idrootApp/App/divscript src../js/vue.js/scriptscript typemodule src./main.js/script
/body/html备注暂时还看不到效果因为单文本组件需要在脚手架环境下使用 
4.2 Todo案例 采用Vue单文本组件的形式编写一个Todo案例具体效果如下所示  Step1拆分组件  技巧将功能和位置在一块的看作一个整体将其作为一个组件组件内部若功能相同且会发生动态变化则可以拆分成一个子组件组件的命名要能够见名知意如果组件的命名不合理就说明有可能你的组件拆分不合理 一个组件在用放在组件本身即可一些组件再用放在父组件上提高复用性 注意组件名不要和HTML中的关键字发生冲突  Step2搭建环境 在我们要编码的目录使用vue脚手架提供的vue指令vue create todo-list初始化一个Vue工程然后进入todo-list目录使用npm run serve命令运行项目  Step3编码细节、bug拉满对于我这种初学Vue的人而言还是有难度的还好张老师够牛讲解很详细  CSS部分style标签最好养成添加scoped属性的习惯。因为最终所有组件的CSS样式都会融合到一个文件中如果不添加会导致融合后发生CSS样式冲突添加scoped属性能够保障这个CSS样式只作用于本组件App组件中的CSS样式可以不添加scoped属性因为App组件中的代码属于全局性的是对所有组件生效的  动态数据传递如果想要将父组件的属性传递到子组件中可以使用props  在父组件中定义一个数据用户存储任务然后遍历组件得到多个子组件并且将遍历的对象传递给子组件 注意动态数据的传递需要使用v-bind进行绑定(:是简写形式)  动态修改一个标签的属性也需要使用v-bind指令  使用uuid的简化形式nanoid  组件间的通信。兄弟组件传值将需要传值的对象放在两者相同父组件中常用的是App、全局事件总线、消息订阅和发布、vuex…… 将需要传值的对象放在两者相同父组件中实现步骤 1在父组件中定义一个要传递的值此外再定义一个待参的函数 2将函数通过v-bind指令传递给子组件子组件调用函数将要传递的值作为函数的参数 3父组件接收到函数中的参数然后在通过v-bind指令传递给另一个组件 注意事项props、data、computed中的属性值不能重名。 props属性是父组件传递给子组件的值一般在子组件中不要随便修改props中的值所以v-model绑定的值最好不要是props虽然不报错但不规范 缺点使用props进行组件间的通信会造成代码污染一些组件并不会使用到props传递的值但是为了传递需要定义这个值  Vue监测props是浅层次的数据变化比如todo.a222Vue不能检测到修改todo{a:1,b2}Vue能检测到修改   
5、浏览器本地存储 
浏览器中有两个地方可以用于缓存数据LocalStorage和SessionStorage。它们的共同点都是储大小为5MB都保存在客户端不与服务器进行交互通信有相同的Web API不同点是localStorage 存储持久数据浏览器关闭后数据不丢失除非主动删除数据sessionStorage 数据在当前浏览器窗口关闭后自动删除 
示例主要有以下四个API 
!DOCTYPE html
html langenheadmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0titleLocalStorage/title
/headbodybutton onclicksaveData()setItem/buttonbutton onclickreadData()getItem/buttonbutton onclickremoveData()removeItem/buttonbutton onclickclearData()clear/buttonscriptfunction saveData() {localStorage.setItem(msg1, abc);localStorage.setItem(meg2, 123);localStorage.setItem(msg3, new Date);let p  {name: 张三,age: 18};localStorage.setItem(msg4, JSON.stringify(p));}function readData() {let p  JSON.parse(localStorage.getItem(msg4));console.log(p);}function removeData() {localStorage.removeItem(msg4);}function clearData() {localStorage.clear();}/script
/body/html只需要将localStorage换成sessionStorage就能存入浏览器的SessionStorage中 对Todo案例进一步改造(todo-list-storage)让数据能够缓存到浏览器中  使用watch属性监视todos只要todos发生改变就将todos的值存入localStorage中         watch: {// 监视todos一但todos发生改变就将todos的新值存入localStorage中todos(value) {localStorage.setItem(todos, JSON.stringify(value))}}初始化时需要读取localStorage中的todos其次应当注意用户首次登录时localStorage中没有todos为null此时footer会统计todostodos.length会报错需要使用 todos: JSON.parse(localStorage.getItem(todos)) || []启用深度监视因为相对于todos而言checked在第二层watch默认是浅度监视只能监视第一层第一层是todos中的属性第二层是todo中的属性         watch: {// 监视todos一但todos发生改变就将todos的新值存入localStorage中todos: {// 开启深度监视todos中todo的checked属性会被监视deep: true,handler(value) {localStorage.setItem(todos, JSON.stringify(value))}}}6、组件的自定义事件 
6.1 使用自定义事件传递数据 $on监听事件$off移除监听事件$emit触发事件$once监听事件只监听一次 子组件传递数据给父组件后两种方法都使用了自定义的事件只适用于子组件给父组件传递数据  v-bindprops函数  v-on$emit  ref$on这种方式更加灵活但感觉有点绕  上代码 App.vue templatediv idapp!-- 方式一props  函数 --SchoolTest :getSchoolNamegetName/SchoolTest!-- 方式二v-on  $emit --!-- StudentTest v-on:getStudentNamegetName/ --!-- 简写形式 --StudentTest getStudentNamegetName/!-- 方式三ref  $on --TeacherTest refteacher//div
/templatescriptimport SchoolTest from ./components/SchoolTestimport StudentTest from ./components/StudentTestimport TeacherTest from ./components/TeacherTestexport default {name: App,components: {SchoolTest,StudentTest,TeacherTest},mounted() {// 三秒后触发事件setTimeout(()  {// 给TeacherTest组件绑定sendTeacherName事件当getTeacherName事件触发时调用getName方法this.$refs.teacher.$on(sendTeacherName, this.getName)}, 1000)},methods: {getName(value) {console.log(App组件收到, value)}}}
/scriptstyle/styleSchoolTest.vue  备注关于这里为什么要加一个Test好像是现在Vue不支持单个单词当组件名了  templatebutton clicksendSchoolNameSchool/button
/templatescriptexport default {name: SchoolTest,props: [getSchoolName],data() {return {name: 东山中学}},methods: {sendSchoolName() {console.log(SchoolTest组件发送, this.name)this.getSchoolName(this.name)}}}
/scriptstyle/styleStudent.vue templatebutton clicksendStudentNameStudent/button
/templatescript
export default {name:StudentTest,data() {return {name:张三}},methods:{sendStudentName(){console.log(StudentTest组件发送, this.name)// $emit作用是触发事件本质是获取组件对象vc然后通过vc对象触发事件this.$emit(getStudentName, this.name)}}
}
/scriptstyle/styleTeacherTest.vue template
button clicksendTeacherNameTeacher/button
/templatescript
export default {name:TeacherTest,data() {return {name:李四}},methods:{sendTeacherName(){console.log(TeacherTest组件发送, this.name)// $emit作用是触发事件本质是获取组件对象vc然后通过vc对象触发事件this.$emit(sendTeacherName, this.name)}}
}
/scriptstyle/style效果展示   
6.2 解绑自定义事件 解绑一个事件 示例 在上一个案例的基础上进行实验先在StudentTest组件中创建一个按钮然后给按钮绑定一个unbind事件 button clickunbind解绑事件/buttonunbind事件         unbind() {this.$off(getStudentName)// getStudentName就是绑定在TeacherTest组件上的自定义组件}效果展示  注意区分组件事件和DOM事件getStudentName是组件事件而SendStudentName是DOM事件  解绑多个事件 // 解绑多个自定义组件事件
this.$off([event1, event2, ...])
// 解绑所有的自定义组件事件
this.$off()注意事项 注意点1$on中避免使用匿名函数 前面我们是通过在App的methods中定义了触发自定义事件后执行的事件然后通过this直接引用此时的this指向App组件         mounted() {// 当getTeacherName事件触发时调用getName方法this.$refs.teacher.$on(sendTeacherName, this.getName)},methods: {getName(value) {console.log(App组件收到, value)}}但如果我们在这个地方使用匿名函数的形式来调用呢 this.$refs.teacher.$on(sendTeacherName, function(value){console.log(App组件收到, value)console.log(this)  // 此时的this指向触发sendTeacherName事件的对象也就是TeacherTest组件对象
}) 解决方法使用箭头函数这是由于ES6规定箭头函数没有自己的this他会使用父级的this 注意点2组件使用原生DOM事件相关的API需要添加native修饰符 原生的DMO事件有clickv-on:click TeacherTest refteacher click.nativealert/methods: {alert() {alert(你好)}
}如果不添加native绑定的事件会没有作用  
7、全局事件总线 可以实现任意组件通信我直呼牛逼 全局事件总线的实现思路添加一个中间量通过中间量来传递组件间的数据 
需要有一个东西设它为x能够被所有的组件识别x需要拥有$on、$off、$emit这几个事件API 
具体实现方式有两种 
第一点毋庸置疑只有原型对象Prototype能够被所有的组件所识别第二点原型对象没有那几个事件的API只有Vue对象或者VueComponent对象上有即vm对象和vc对象所以我们可以将vm或vc对象当成x绑定到原型对象Prototype上这样就能实现任意组件间的通信了 
通常我们会将x命名为bus并且一般原型对象的属性按照习惯都会加一个$所以x就成了$bus 
其一所有的vc和vm都有同一个原型对象参考下图 示例 
mian.js 
import Vue from vue
import App from ./App.vueVue.config.productionTip  false// 方式一将vc当作工具人
// const VueComponent  Vue.extend({})
// const vc  new VueComponent
// Vue.prototype.$bus  vc;new Vue({render: h  h(App),beforeCreate() {// 方式二将vm当作工具如推荐使用这种方式更加简洁Vue.prototype.$bus  this // 安装全局事件总线}
}).$mount(#app)StudentTest.vue 
templatediv!-- 实现兄弟组件间的通信StudentTest组件和TeacherTest组件的通信 --button clicksendMsg stylecolor: red;Student/button/div
/templatescriptexport default {name: StudentTest,methods: {// 测试事件总线sendMsg() {console.log(StudentTest组件发送数据, 666)// 触发School组件的alert事件this.$bus.$emit(alert, 666)}}}
/scriptstyle/styleTeacherTest.vue 
templatediv/div
/templatescriptexport default {name: TeacherTest,mounted() {// 给$bus绑定一个alert事件当$bus触发alert就进行输出this.$bus.$on(alert, (data)  console.log(TeacherTest组件接收, data))},beforeDestroy() {// 当TeacherTest组件销毁时就解绑$bus上的alertthis.$bus.$off(alert)}}
/script效果展示 
点击StudentTest组件的按钮就能将数据发送给TeacherTest组件 实战训练 将todo-list-event中父孙传值。之前App组件与MyItem组件间的传值是使用props函数的形式进行传值的需要经过中间层MyList这样显得很没必要现在就直接使用全局事件总线实现App组件与MyItem之间的直接通信 8、消息订阅和发布 什么是消息订阅和发布  消息的订阅和发布简称发布-订阅publish-subscribe是一种通信模式是指消息的发布者不会将消息直接发送给特定的接收者也称订阅者它会将要发布的消息分为不同的类别无需了解订阅者是谁订阅者只会接收某一种或多种类型的消息而不是全部接收同样的订阅者也无需了解发布者是谁   消息订阅和发布的作用是什么  实现发布者和订阅者的解耦让系统具有更高的扩展性和可维护性   常见的发布-订阅 DOM操作中的addEventListenerVue中的事件总线的概念Node.js中的EventEmitter以及内置库 举个例子假如你在某平台订阅了某个专题该系统会自动在该专题更新的时候主动推送信息给到你而不用你手动地去查阅。这个例子就类似发布-订阅模式  发布-订阅的具体流程  发布-订阅是对象中的一种一对多的依赖关系当一个对象触发一个事件的时候所有订阅该事件的对象将得到通知。    发布者通过事件中心派发事件  订阅者通过事件中心进行事件的订阅  事件中心负责存放事件和订阅者的关系   如何在项目中应用发布-订阅模式  一种是自己实现另一种是直接引用别人开发的库这里我使用的是pubsub-js  使用步骤  Step1安装pubsub-js npm i pubsub-jsStep2引入pubsub-js import pubsub from pubsub-jsStep3订阅消息             this.pubId  pubsub.subscribe(hello, function(msgName, data) {// console.log(this) // 注意没有使用箭头函数此时这里的this是undefinedconsole.log(Subscribe接收, msgName, data)})Step4发布消息             publishMsg() {console.log(Publish发送, 你好)pubsub.publish(hello, 你好)}示例 PublishTest.vue消息的发布者 templatedivbutton clickpublishMsgPublish/button/div
/templatescriptimport pubsub from pubsub-jsexport default {name: PublishTest,methods: {publishMsg() {console.log(Publish发送, 你好)pubsub.publish(hello, 你好)}}}
/scriptstyle
/styleSubscribeTest.vue消息的订阅者 templatedivbuttonSubscribe/button/div
/templatescriptimport pubsub from pubsub-jsexport default {name: SubscribeTest,mounted() {// 订阅 hello 这个消息msgName是订阅的消息名data是消息的内容this.pubId  pubsub.subscribe(hello, function(msgName, data) {// console.log(this) // 注意没有使用箭头函数此时这里的this是undefinedconsole.log(Subscribe接收, msgName, data)})},beforeDestroy() {// 组件销毁后关闭订阅pubsub.unsubscribe(this.pubId)},}
/scriptstyle
/style效果展示   实战演练  修改之前的todo-list-bus将里面的组件间通信修改成使用发布-订阅模式  添加一个编辑按钮实现编辑功能 注意事项添加isEdit时会出现给对象新增一个属性失效这是由于直接添加的数据没有Getter和Setter也就是没有做数据代理在Vue核心的15节数据更新时的底层原理有讲过。解决方案vm.$set(target,attribute,val)  vue-cli3.0版本后无法使用foo.hasOwnProperty(bar)具体参考vue-cli3中不能使用hasOwnProperty的解决办法  在设计编辑的输入框时我们想要点击编辑然后就将焦点移到要编辑的数据上但是发现失效了 // 编辑itemhandlerEdit(todo) {if (!Object.prototype.hasOwnProperty.call(todo, isEdit)) { // 也可以使用 todo.isEdit  undefined 进行判断// 只有当todo上没有isEdit属性时才需要在他上面添加一个isEdit属性this.$set(todo, isEdit, true)// console.log(Object.prototype.hasOwnProperty.call, Object.prototype.hasOwnProperty.call(todo, isEdit))} else {todo.isEdit  !todo.isEdit}// 这一行并不起效*this.$refs.inputTitle.focus()}*这是由于只有当handlerEdit函数执行完才会执行解析模板并不是一修改数据就解析模板只有解析模板后才能成功展示编辑的input框当运行到*行时由于input框还没有展示出来所以此时就不起效了 解决方案1使用setTimeout函数因为定时器是一个异步的操作             setTimeout(()  {this.$refs.inputTitle.focus()}, 200)解决方案2使用$nextTick$nextTick指定的回调函数会在DOM节点更新完毕后执行             this.$nextTick(function(){this.$refs.inputTitle.focus()})参考文章 setTimeout时间设置为0详细解析  9、过渡和动画 
9.1 手工实现 
示例一实现一个动态切换效果 CSS实现 
templatedivbutton clickchange显示/隐藏/buttonh3 :classactive你好啊/h3/div
/templatescript
export default {name: DemoTest,data() {return {isShow: true,active: [] // 注意如果将active定义成字符串使用赋值不会引起Vue的模板解析}},methods: {// ? 无法实现class的切换效果change() {if (this.active.length  0) {// this.active[0]  comethis.active.unshift(come)} else if (this.active.shift() ! go) {this.active.unshift(go)} else {console.log(this.active)this.active.unshift(come)}}},
}
/scriptstyle scoped
h3 {background-color: orange;
}.come {/* 动画从左往右from 到 to*/animation: slide 1s;
}.go {/* 动画从右往左reverse是动画反转即是 to 到 from */animation: slide 1s reverse;
}keyframes slide {from {transform: translateX(-100%);}to {transform: translateX(0px);}
}
/style半Vue实现 
templatedivbutton clickisShow  !isShow显示/隐藏/buttontransition appearh3 v-showisShow你好啊/h3/transition/div
/templatescript
export default {name: DemoTest,data() {return {isShow: true}},
}
/scriptstyle scoped
h3 {background-color: orange;
}.v-enter-active{/* 动画从左往右from 到 to*/animation: slide 1s;
}
.v-leave-active{/* 动画从右往左reverse是动画反转即是 to 到 from */animation: slide 1s reverse;
}keyframes slide {from{transform: translateX(-100%);}to{transform: translateX(0px);}
}
/style注意 如果给transition的name属性添加值则.v-enter-active和.v-leave-active前面的v都需要改成name对应的值  要想使DOM一上来就有动画效果就需要设置appear属性appear属性要设置为真或者直接写上appear属性 方式一transition appear
方式二transition :appeartrue如果直接是 appeartrue浏览器的控制台会报错但是仍然有效果纯Vue实现 
templatedivbutton clickisShow  !isShow显示/隐藏nbsp;Demo2/buttontransition appear namehelloh3 v-showisShow你好啊/h3/transition/div
/templatescript
export default {name: DemoTest2,data() {return {isShow: true}},
}
/scriptstyle scoped
h3 {background-color: orange;/* transition: .5s linear; */
}/* 动画开始动画结束 */
.hello-enter-active,
.hello-leave-active {transition: .5s linear;
}/* 进入的起点动画所到最左边离开的终点 */
.hello-enter,
.hello-leave-to {transform: translateX(-100%);
}/* 进入的终点动画所到最右边离开的起点 */
.hello-enter-to,
.hello-leave {transform: translateX(0);
}
/style注意当我们有多个标签使用同一个动画时必须使用transition-group属性里面的元素必须要有key属性 transition-group appear namehelloh3 v-showisShow key1你好啊/h3h3 v-showisShow key2你好啊/h3/transition-group如果多个标签是紧挨着的可以使用一个div包裹起来但是如果互斥则使用这种方式无法实现 transition appear namehellodiv v-showisShowh3你好啊/h3h3你好啊/h3/div/transition9.2 使用第三方库 这里演示使用Animate.css这个动画库实现动画效果 官网Animate.css | A cross-browser library of CSS animations. Step1安装 npm install animate.cssStep2引入 import animate.cssStep3 nameanimate__animated animate__bounce是必须项 enter-active-class动画开始的效果 leave-active-class动画结束的效果  templatedivbutton clickisShow  !isShow显示/隐藏nbsp;Demo3/buttontransition-group appear nameanimate__animated animate__bounceenter-active-classanimate__hinge leave-active-classanimate__backInUph3 v-show!isShow key1你好啊/h3h3 v-showisShow key2你好啊/h3/transition-group/div
/templatescriptimport animate.cssexport default {name:DemoTest3,data() {return {isShow:true}},
}
/scriptstyle scoped
h3 {background-color: orange;/* transition: .5s linear; */
}
/style实战演练 修改todo-list-bus案例将MyList组件的删除和添加增加一个动画效果 10、Vue中AJAX的使用 
10.1 快速体验 Step1开启服务器    Step2下载并引入axios npm i axios # 下载axios
import axios from axios # 引入axios关于axios这里就不多做介绍了感兴趣的可以参考我之前写过的文章【AJAX是什么】【AJAX的基本使用】_   Step3编码 !-- 对应的结构 --h1测试发送AJAX/h1button clickgetStudentMsgByAjax获取学生信息/button!-- 对应的方法 --methods: {getStudentMsgByAjax() {axios.get(http://localhost:5000/students).then(response  {console.log(请求成功了, response.data)},error  {console.log(请求失败了, error.message)})}}10.2 跨域问题 什么是跨域 我们通常所说的跨域 是由浏览器同源策略限制的一类请求场景。 同源策略Same origin policy是一种约定它是浏览器最核心也最基本的安全功能如果缺少了同源策略则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的浏览器只是针对同源策略的一种实现  为什么会出现跨域 当前所处位置http://localhost:8080/要发送的位置http://localhost:5000。协议和域名相同但是端口号不同违背了同源策略此时请求发送出去了同时服务器也接受了请求但是返回的数据被浏览器拦截了并不能被发送AJAX的程序接收  如何解决跨域问题  方案一使用CORS解决跨域 CORSCross-Origin Resource Sharing跨源资源共享是一个系统它由一系列传输的 HTTP 标头组成这些 HTTP 标头决定浏览器是否阻止前端 JavaScript 代码获取跨源请求的响应。CORS 给了 web 服务器这样的权限即服务器可以选择允许跨源请求访问到它们的资源。 特点后端解决需要浏览器和后端同时支持请求分为复杂请求和简单请求 跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng.com)  方案二使用JSONP解决跨域 JSONPJSON with Padding是JSON的一种使用模式可以让网页从别的网站获取资料即跨域读取数据。 特点前后端一起解决只能解决GET请求跨域  方案三配置代理服务器常用、推荐 配置一个代理服务器http://localhost:8080/代理服务器让它和http://localhost:8080/AJAX发送方进行交互这样就符合了同源策略。同源问题的本质是浏览器的为了安全而增加的限制这样 AJAX的接收方 发送的数据有代理服务器接收则有代理服务器交给AJAX的发送方这样就不违背同源策略了成功解决跨域问题。根本原因是利用服务器之间通信不使用同源策略 开启代理服务器的方法反向代理  借助nginx  借助vue-cli 在vue.config.js中配置以下参数     devServer: {// proxy中配置的是AJAX接收方的访问地址proxy: http://localhost:5000}注意现在AJAX的访问地址就是http://localhost:8080/students了而不是http://localhost:5000/students。当你代理服务器中有所请求的资源就不会转发请求   推荐阅读跨域的十种解决方案 - 掘金 (juejin.cn)  示例 借助vue-cli解决跨域问题 App.vue 
templatediv idapph1测试发送AJAX/h1button clickgetStudentMsgByAjax获取学生信息/buttonbr/button clickgetCarMsgByAjax获取汽车信息/button/div
/templatescriptimport axios from axiosexport default {name: App,components: {},methods: {getStudentMsgByAjax() {axios.get(http://localhost:8080/server1/students).then(response  {console.log(server1请求成功了, response.data)},error  {console.log(server1请求失败了, error.message)})},getCarMsgByAjax() {axios.get(http://localhost:8080/server2/cars).then(response  {console.log(server2请求成功了, response.data)},error  {console.log(server2请求失败了, error.message)})}},}
/scriptstyle
/stylevue.config.js 
const { defineConfig }  require(vue/cli-service)
module.exports  defineConfig({transpileDependencies: true,// 方式一开启代理服务器只能代理一个服务器/* devServer: {proxy: http://localhost:5000} */// 方式二开启代理服务器代理多个服务器devServer: {proxy: {/server1: {target: http://localhost:5000,// 此时访问的路径是 http://localhost:5000/server1/students需要使用正则表达式去掉 /server1pathRewrite: { ^/server1:  },// 用于支持 websocketws: true,// 代理服务器告诉server1请求来自于哪里true说谎本来是来自8080结果说来自5000false如实回答来自8080changeOrigin: true // 用于控制请求头的host属性的值},/server2: {target: http://localhost:5001,pathRewrite: { ^/server2:  },ws: true,changeOrigin: true}}}
})效果展示 实战演练 开发一个GitHub用户搜索框效果展示如下  注意事项  通过import方式引入第三方库vue-cli会对引入的第三方库进行严格检查只要其中有用到但没有找到的资源就会直接报错 解决方法有如下几种 1将缺失的资源下载到项目中 2如果缺失的资源我们当前并没有用到可以直接注释掉或者删除掉不推荐 3不将要引入的第三方库放在assets目录下而是放在public中直接在页面中使用link标签引入而不是在组件中引入   对象传参头次见好用             this.info  {...this.info, ...dataObj}使用vue_resource替代axios这个用的比较少基本上已经停止维护了Vue官方推荐使用axios  11、插槽 什么是插槽  插槽Slot是Vue提供的一个概念它允许开发者在组件外部将不确定的部分定义为一个插槽然后整体解析到组件的内部。插槽分为插槽的入口和插槽的出口。参考下图  作用让父组件可以向子组件指定位置插入html结构也是一种组件间通信的方式适用于 父组件  子组件。   插槽的分类 默认插槽没有名字的插槽具名插槽具有名字插槽作用域插槽作用域插槽其实就是带数据的插槽即带参数的插槽简单的来说就是子组件提供给父组件的参数该参数仅限于插槽中使用父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容 备注在 2.6.0 中我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute而在3.0中已经移除了旧的写法  
11.1 默认插槽 
示例 App.vue 
templatediv idappdiv classcontainerCategoryTest title美食!-- 使用插槽将内容传递到组件中 --img srchttps://xingqiu-tuchuang-1256524210.cos.ap-shanghai.myqcloud.com/12497/202301241615404.jpeg alt/CategoryTestCategoryTest title游戏ulli v-for(game,index) in games :keyindex{{game}}/li/ul/CategoryTestCategoryTest title电影video controls srchttp://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4/video/CategoryTest/div/div
/templatescriptimport CategoryTest from ./components/CategoryTestexport default {name: App,components: {CategoryTest},data() {return {foods: [火锅, 烧烤, 小龙虾, 牛排],games: [红色警戒, 穿越火线, 劲舞团, 超级玛丽],films: [《教父》, 《拆弹专家》, 《你好李焕英》, 《尚硅谷》]}},}
/scriptstyle.container {display: flex;justify-content: space-around;}h3 {text-align: center;background-color: yellow;}img {width: 100%;}video {width: 100%;}
/styleCategoryTest.vue 注意插槽标签slot中可以有内容当外部没有传值给插槽时就会显示slot中的内容当传值就以传的值为准 templatediv classcategoryh3{{title}}分类/h3!-- 使用插槽使用它占位如果在App中传来元素就在这里展示 --slot/slot/div
/templatescript
export default {name: CategoryTest,props:[title]
}
/scriptstyle scoped
.category {background-color: skyblue;width: 200px;height: 300px;
}
/style11.2 具名插槽 
App.vue CategoryTest title电影video slotvideo controls srchttp://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4/video/CategoryTestCategoryTest.vue slot namevideo/slot如果slot属性是加在template标签上 template slotlistulli经典/lili热门/lili推荐/li/ul/template2.6提供一个新写法 template v-slot:listulli经典/lili热门/lili推荐/li/ul/template11.3 作用域插槽 用于将插槽出口所在组件的数据传递到插槽入口所在组件只能在组件中的插槽内部使用也就是将子组件中的数据传递给父组件 App.vue 
templatediv idappdiv classcontainerCategoryTest title游戏template scopedata  !-- 推荐奖 scope 替换成 slot-scope --ulli v-for(game,index) in data.gamesData :keyindex{{game}}/li/ul/template
/CategoryTest
CategoryTest title游戏template scope{gamesData} !-- 使用ES6的解构赋值 --ulli v-for(game,index) in gamesData :keyindex{{game}}/li/ul/template
/CategoryTest
/div
/div
/templatescriptimport CategoryTest from ./components/CategoryTestexport default {name: App,components: {CategoryTest}}
/scriptstyle.container {display: flex;justify-content: space-around;}h3 {text-align: center;background-color: yellow;}
/styleCategoryTest.vue 
templatediv classcategoryh3{{title}}分类/h3!-- 使用插槽使用它占位如果在App中传来元素就在这里展示 --slot :gamesDatagames/slot/div
/templatescriptexport default {name: CategoryTest,props: [title],data() {return {games: [红色警戒, 穿越火线, 劲舞团, 超级玛丽]}}}
/scriptstyle scoped.category {background-color: skyblue;width: 200px;height: 300px;}
/style12、Vuex 
12.1 Vuex介绍 Vuex是什么  Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式  库可以认为是状态管理模式的一种具体实现。它采用集中式存储管理应用的所有组件的状态并以相应的规则保证状态以一种可预测的方式发生变化。 简而言之Vuex就是专门提供组件间数据共享的。前面最先使用props来传递组件间的数据如果组件层数过多会存在冗余的props后面我们使用了全局事件总来实现组件间数据的传递但是当一个项目中组件过多且组件间数据的传递较多这是用使用全局事件总线会显得很复杂。  vuex官方文档Vuex 是什么 | Vuex (vuejs.org) Github地址vuejs/vuex   什么是状态管理模式  状态管理模式是指将项目中所有组件的共享状态抽取出来以一个全局单例模式进行管理。这种模式下任何组件都能获取状态或者触发行为。一个状态管理应用应该包含以下三部分 状态驱动应用的数据源视图以声明方式将状态映射到视图操作响应在视图上的用户输入导致的状态变化   为什么使用状态管理模式  能够让我们的代码更加简洁、更加结构化且易维护。   什么使用使用Vuex? 多个组件依赖同一状态。来自不同组件的行为需要变更同一状态。 温馨提示如果您不打算开发大型单页应用使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是如果您需要构建一个中大型单页应用您很可能会考虑如何更好地在组件外部管理状态Vuex 将会成为自然而然的选择。  备注Actions、Mutations、State都是对象并且它们仨都被store对象管理 
12.2 快速体验 Step1安装Vuex 温馨提示再2022年2月7日nmp i vuex指令默认安装Vuex4版本了。Vuex4版本只能在Vue3中使用也就是说Vue2中只能使用Vuex3版本Vue3中推荐使用Vuex4版本 npm i vuex3
# 注意一定要加3指定Vuex的版本否则默认就是安装Vuex4Step2编写store 文件所在目录src/store/index.js 或者是 src/store.js要让store能够被所有组件识别要让store能够管理Actions、Mutations、State三个对象 index.js // 该文件用于创建Vuex最为核心的store
import Vue from vue
import Vuex from vuex// 让Vuex能够被所有组件识别
Vue.use(Vuex)// 准备actions用于响应组件中的动作
const actions  {add(context, value) {console.log(Actions中的add方法被调用了, context, value)context.commit(ADD, value)},sub(context, value) {console.log(Actions中的sub方法被调用了, context, value)context.commit(SUB, value)}};
// 准备mutations用于操作数据state
const mutations  {ADD(state, value) {console.log(Mutations中的ADD方法被调用了, state, value)state.sum  value},SUB(state, value) {console.log(Mutations中的SUB方法被调用了, state, value)state.sum - value},};
// 准备state用户存储数据
const state  {sum: 0 // 当前所求的和
};
// 创建Store对象
export default new Vuex.Store({/*     actions: actions,mutations: mutations,state: state */// 对象和属性同名可以简写ES6actions,mutations,state
});Step3编写main.js  文件执行时会优先解析import所对应的文件这也就是为什么Vue.use(Vuex)要写在store后面详情见P108 17:56 所以要明确Store对象的创建是通过Vuex对象的Store构造函数生成的所以要先Vue.use(Vuex)  import Vue from vue
import App from ./App.vue
// import Store from ./store/index // index是能够被Vue-cli识别的可以省略
import store from ./storeVue.config.productionTip  falsenew Vue({render: h  h(App),// store: store // 对象和变量同名可以简写ES6语法store
}).$mount(#app)Step3编码 CountTest.vue templatedivh1当前求和为{{ $store.state.sum}}/h1!-- 这里可以通过添加.number也可以使用v-bind绑定option的所有value属性 --select v-model.numbern name id   option value11/optionoption value22/optionoption value33/option/selectnbsp;nbsp;button clickincrement/buttonnbsp;nbsp;button clickdecrement-/buttonnbsp;nbsp;button clickincrementOdd当前求和为奇数再加/buttonnbsp;nbsp;button clickincrementWait等一等再加/button/div
/templatescript
export default {name: CountTest,data() {return {n: 1 // 当前用户选择的数字}},methods: {increment() {this.$store.dispatch(add, this.n)},decrement() {this.$store.dispatch(sub, this.n)},incrementOdd() {if (this.$store.state.sum % 2) {// 如果是奇数就加// 可以直接跳过Actions直接将数据传递给Mutationsthis.$store.commit(ADD, this.n)}},incrementWait(){setTimeout((){this.$store.dispatch(add,this.n)}, 500)}}
}
/scriptstyle scoped/styleApp.vue templatediv idappCountTest//div
/templatescriptimport CountTest from ./components/CountTestexport default {name: App,components: {CountTest},mounted() {// console.log(APP, this)},}
/scriptstyle
/styleStep4测试   
12.3 getters配置项 
在store目录下的index.js中配置getters 
// 准备getters用于加工state中的数据
const getters  {bigSum(state) {return state.sum * 10}
}然后就可以使用 $store.getters.bigSum获取到加工的数据了 
12.4 mapStatemapGetters 
首先需要在组件中引入mapState和mapGetters 
import {mapState, mapGetters} from vuex借助mapState简化计算属性computed中获取状态this.$store.state.a借助mapGetters简化计算属性computed中获取状态this.$store.bigSum. computed: {// 手写计算属性/* a() {return this.$store.state.sum - 1},b() {return this.$store.state.sum  1},c() {return this.$store.state.sum / 2},d() {return this.$store.state.sum * 2} */// 可以通过mapState将上面那段代码进行简写使用ES6对象展开运算符将此对象混入到外部对象中// ...mapState({a:a,b:b,c:c,d:d})// 当对象名和值相同时可以进一步简写...mapState([a,b,c,d]),/* --------------------------------------------------- *//* bigSum(){return this.$store.getters.bigSum} */// 借助mapGetters上面的代码进行简写...mapGetters([bigSum])}12.5 mapActionsmapMutations 
首先需要在组件中引入mapActions和mapMutations 
import {mapActions, mapMutations} from vuex借助mapActions简化methos中this.$store.commit()借助mapMutations简化methods中this.$store.dispatch() 
button clickincrement(n)methods: {// 原始写法increment不需要传参/* increment() {this.$store.commit(ADD, this.n)}, */// 使用mapActions属性对上面那段代码进行简写/* increment(value) {this.$store.commit(ADD, value)},  */// 需要注意使用 mapMutations 等价于上面的代码传的值是event// 因为他会有一个默认参数前面我们有传参所以就默认是事件所以需要主动传参...mapMutations({ increment: ADD})}mapActions和mapMutations一样也有应该默认的参数所以必须传值否则传的就是event对象 
button clickdecrement(n)-/button...mapActions({decrement:sub}),实战演练使用Vuex实现多组件间数据的共享  对于生成唯一的id可以使用nanoid import {nanoid} from nanoid
const id  id:nanoid()12.6 Vuex中的模块化技术 
前面我们通过给store对象添加Actions、Mutations、State、Getters四个对象用于实现组件间的数据共享但是存在应该问题当共享的数据很多时很让Vuex显得十分的环论此时我们就需要使用Vuex的模块化技术了。 
在/store/index.js中对store的四个对象进行分类 
const personObj  {namespace:true,const actions:{},const mutations:{},const state:{},const getters{}
}
const carObj  {namespace:true,const actions:{},const mutations:{},const state:{},const getters{}
}
export default new Vue.Store({moudules:{// personObj:personObj 使用对象的简写形式personObj,carObj}
})需要注意 
如果我们想要通过…mapState第一个参数读取personObj中的属性并进行解构赋值必须写上namespace:true 
// 不设置namespacetrue它默认是false则只能通过下面这种方式读取
...mapState(personObj,carObj)
// 通过这种方式引入都需要添加应该前缀personObj比如我们要读取personObj中的a属性就需要写成personObj.a// 设置namespacetrue
...mapState(personObj,[a,b])此时如果我们项直接调用commit中的方法由于现在store中存在多个Mutations属性所以我们需要指明我们要使用哪一个Mutations中的commit方法 
...mapMutations(personObj,{increment:ADD})
// 现在就指明了要使用personObj中的Mutations属性中Commit同理我们如果想要调用Actions中的dispatch方法也需要指明 
...mapActions(personObj,{increment:add})getters也是一样 
...mapGetters(personObj,[bigSum])对于非简写形式 
add(){//this.$store.personObj.state.personListthis.$store.commit(personObj/ADD,personList)
}getters中没有分类所以获取getters中的数据 
bigSum(){this.$store.getters[personObj/bigSum]
}通过5和6我们可以发现不使用简写显得很不简洁、美观所以推荐直接使用map的写法。但并不是map的写法就完胜非简写形式当我们要对数据进行逻辑判断时我们需要使用非简写形式因为map写法无法进行数据的判断 
13、路由 
13.1 路由介绍 什么是路由    路由routing是指分组从源到目的地时决定端到端路径的网络范围的进程。简而言之粗略地讲路由就是用来根据路由表转发IP数据包的一个装置能够实现多个主机之间的互通相当于一个快递中间站属于硬件层面上的。   但在Vue 中路由Vue Router是一个不太相同的概念它是SPASingle Page Application单页面应用的路径管理器是WebApp的链接路径管理系统用于页面跳转属于软件层面上的其实Vue Router就是一个插件库本质是一组key-value的对应关系。 官方文档介绍 | Vue Router (vuejs.org)   什么是SPA  SPASingle Page Application是单页面应用也就是说整个应用只有一个页面   为什么要使用SPA  早期的Web开发都是多页面应用一个应用中存在多给页面页面页面之间使用a标签或者href属性实现页面之间的跳转这样存在一个弊端页面频繁跳转会导致页面抖动1。而使用路由后就能实现一个单页面应用这样就能够防止页面抖动页面间的跳转都是通过路由Vue Router实现的跳转编程了局部刷新   路由的分类  前端路由 理解value是component用于展示页面的内容就是Vue Router工作过程当浏览器的路径发生改变时对应组件就会显示  后端路由 理解value是fanction用于处理客户提交的请求其实就是DispatchServlet工作过程服务器接收一个请求时根据请求路径找到匹配的函数来处理请求然后返回响应数据   
13.2 快速体验 入门 | Vue Router (vuejs.org) #mermaid-svg-vgxgWYbXZpscoqN4 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-vgxgWYbXZpscoqN4 .error-icon{fill:#552222;}#mermaid-svg-vgxgWYbXZpscoqN4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vgxgWYbXZpscoqN4 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-vgxgWYbXZpscoqN4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vgxgWYbXZpscoqN4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vgxgWYbXZpscoqN4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vgxgWYbXZpscoqN4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vgxgWYbXZpscoqN4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vgxgWYbXZpscoqN4 .marker.cross{stroke:#333333;}#mermaid-svg-vgxgWYbXZpscoqN4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vgxgWYbXZpscoqN4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vgxgWYbXZpscoqN4 .cluster-label text{fill:#333;}#mermaid-svg-vgxgWYbXZpscoqN4 .cluster-label span{color:#333;}#mermaid-svg-vgxgWYbXZpscoqN4 .label text,#mermaid-svg-vgxgWYbXZpscoqN4 span{fill:#333;color:#333;}#mermaid-svg-vgxgWYbXZpscoqN4 .node rect,#mermaid-svg-vgxgWYbXZpscoqN4 .node circle,#mermaid-svg-vgxgWYbXZpscoqN4 .node ellipse,#mermaid-svg-vgxgWYbXZpscoqN4 .node polygon,#mermaid-svg-vgxgWYbXZpscoqN4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vgxgWYbXZpscoqN4 .node .label{text-align:center;}#mermaid-svg-vgxgWYbXZpscoqN4 .node.clickable{cursor:pointer;}#mermaid-svg-vgxgWYbXZpscoqN4 .arrowheadPath{fill:#333333;}#mermaid-svg-vgxgWYbXZpscoqN4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vgxgWYbXZpscoqN4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vgxgWYbXZpscoqN4 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-vgxgWYbXZpscoqN4 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-vgxgWYbXZpscoqN4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vgxgWYbXZpscoqN4 .cluster text{fill:#333;}#mermaid-svg-vgxgWYbXZpscoqN4 .cluster span{color:#333;}#mermaid-svg-vgxgWYbXZpscoqN4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vgxgWYbXZpscoqN4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}                                                                    安装VueRouter           创建路由组件           创建Router实例           将Router实例挂载到main.js中          Step1安装Vue-Router npm i vue-router3温馨提示2022年2月7日以后vue-router安装默认是4版本而vue-router的4版本只能在vue3中使用vue-router3只能在vue2中使用和Vuex类似  Step2将vue-router引入项目中 在src目录先创建router目录然后编写index.js内容如下: index.js import VueRouter from vue-router
import AboutTest from ../components/AboutTest
import HomeTest from ../components/HomeTest// 创建一个路由
export default new VueRouter({routes: [{path: /about,component: AboutTest},{path: /home,component: HomeTest}]
})备注这样写在引入Vuex中已经讲过了就JS文件在初始化时会优先解析import对应的js文件如果我们在main.js中引入后面有需要使用Vue.use(VueRouter)就会报错因为VueRouter main.js import Vue from vue
import App from ./App.vue
// 引入vue-router
import VueRouter from vue-router
// 引入路由器对象
// import router from ./router/index // index可以省略
import router from ./routerVue.config.productionTip  false
Vue.use(VueRouter)new Vue({render: h  h(App),// router: router// 使用对象的简写形式router
}).$mount(#app)Step3编写组件 1HomeTest.vue templatedivh2我是Home的内容/h2/div
/templatescript
export default {name: HomeTest
}
/script2AboutTest.vue templatedivh2我是About的内容/h2/div
/templatescript
export default {name:AboutTest
}
/script3App.vue templatediv idappdiv classrowdiv classcol-xs-offset-2 col-xs-8div classpage-headerh2Vue Router Demo/h2/div/div/divdiv classrowdiv classcol-xs-2 col-xs-offset-2div classlist-group!-- 原始使用a标签实现页面跳转多页面应用 --!-- a classlist-group-item active href./about.htmlAbout/a --!-- a classlist-group-item href./home.htmlHome/a --!-- 使用vue-router实现页面跳转单页面应用 --router-link classlist-group-item active-classactive to/aboutAbout/router-linkrouter-link classlist-group-item active-classactive to/homeHome/router-link/div/divdiv classcol-xs-6div classpaneldiv classpanel-body!-- 指定组件呈现的位置 --router-view/router-view/div/div/div/div/div
/templatescriptexport default {name: App}
/scriptStep4测试  备注 没有显示的组件就销毁了。按照开发规范我们会将路由组件放在pages目录下一般组件放在components目录下每一个组件都有一个$route挂载VC身上通过$route能够获取自己路由相关信息一个应用只有一个$router可以通过VC获取  实战演练 嵌套路由详细代码请参考博主的Github\Gitee仓库   
13.3 路由嵌套 略……详情请参考博主的Github\Gitee参考库 import VueRouter from vue-router
import AboutTest from ../pages/AboutTest
import HomeTest from ../pages/HomeTestimport NewsTest from ../pages/NewsTest
import MessageTest from ../pages/MessageTestimport DetailTest from ../pages/DetailTest// 创建一个路由
export default new VueRouter({routes: [{path: /about,component: AboutTest},{path: /home,component: HomeTest,children: [{path: news,component: NewsTest},{path: message,component: MessageTest,children: [{path: detail,component: DetailTest}]}]}]
})13.4 路由传参 
效果展示 MessgeTest.vue消息的发送者 divulli v-fordata in dataList :keydata.id!--方式一 --!-- router-link :to/home/message/detail?id666title${data.title}{{data.title}}/router-link --!-- 方式二(推荐使用虽然配置多但是清晰明了) --router-link :to{path:/home/message/detail,query:{id:data.id,title:data.title}}{{data.title}}/router-link/li/ulhrrouter-view/router-view/divDetailTest.vue消息的接收者 
templatedivulli消息编号{{$route.query.id}}/lili消息标题{{$route.query.title}}/li/ul/div
/templatescript
export default {name:DetailTest,mounted(){console.log(this.$route.query.id)}
}
/scriptstyle/style备注这种参数被称为query参数 
除了query参数还有params参数这是一种rest风格的传参方式 Step1需要配置src/router/index.js文件中配置path进行占位                 {path: message,component: MessageTest,children: [{name: detail,// 占位path: detail/:id/:title,component: DetailTest}]}Step2进行parms方式传参注意需要对字符串进行JS解析同时使用模板字符串                 !-- params传参方式一 --
router-link :to/home/message/detail/${data.id}/${data.title}{{data.title}}/router-link!-- params传参方式二(上面没有使用name属性因为我没有试出来) --router-link :to{name: detail,params:{id:data.id,title:data.title}}{{data.title}}/router-link注意使用方式二只能使用name属性来匹配路由不能使用path否则直接报错  Step3接收 $route.params.id
$route.params.title13.5 路由的命名 这个和代理服务器中服务器名称类似(●ˇ∀ˇ●)主要起到简化路径书写的作用 routes: [{name: apath: /about,component: AboutTest}}]此时router-link标签中的to就可以使用以下写法了 
!--不加name属性的写法--
router-link to/about/router-link
!--添加name属性的写法--
router-link :to{name:about}/router-link13.6 路由的props配置 前面我们学习了路由的两种传参方式query和params这两种方式存在一个小毛病也就是接收参数的组件如果想要使用都需要通过this.$route.query.属性或者this.$route.params.属性来获取参数这样写会显得较为繁琐其实我感觉没什么大不了的可能是Vue的开发者比较喜欢追求完美吧毕竟别人的学艺术的完美可以props属性来简化这种写法 通过在路由的配置文件也就是/src/router/index.js中配置props需要明确的是那个组件需要用到这个属性就在该组件所在路由中配置一个props属性然后还需要再改组件中配置一个props然后就能够直接在该组件中调用该属性了 
方式一传对象 {path: message,component: MessageTest,children: [{path: detail,component: DetailTest,// props的第一种写法该对象中所有的key-value都以props的形式传递到DetailTest组件中props: { a: 1 }}],}DetailTest.vue组件进行接收 
首先声明 props:[a]
然后就可以直接使用了 liprops传递值:a{{a}}/li不足只能传固定值 
方式二传boolean // 传递布尔值布尔值为真就会将该路由组件所有的params参数传递给DetailTest组件props: trueMessageTest.vue组件发送 router-link :to{name: detail,params:{id:data.id,title:data.title}}DetailTest.vue组件接收 
先声明    props: [id, title]
然后直接使用!-- 通过props:true 直接使用传递的属性 --li消息编号{{ id }}/lili消息编号{{title }}/li不足只能传递params参数 
方式三传函数 // 传递函数函数返回值中所有的key-value都会以props的形式传递给DetailTest组件props($route) {// 并且这个函数自带一个参数也就是$routeconsole.log($route)return { id: $route.params.id, title: $route.params.title }}// 使用解构赋值进行简写props(params:{id,title}) {// 并且这个函数自带一个参数也就是$routeconsole.log($route)return { id, title}}MessageTest.vue组件发送数据 
DetailTest.vue组件接收数据 
先声明    props: [id, title]
然后直接使用!-- 通过props:true 直接使用传递的属性 --li消息编号{{ id }}/lili消息编号{{title }}/li相较于前面两种方式来说更加加完美 
不足代码写的相对较多一点 
总的来讲各有优缺吧你只要能实现就好黑猫白猫能抓住老鼠就是好猫 
13.7 router-link的replace属性 router-linke默认开启的是一个push模式 push模式就是每点击一次视图切换都会将之前的URL进行push存入一个栈中每点击一次回退按钮就pop出一个URL同时回退到之前的视图知道栈中所有的URL被pop  除了push模式还有一种模式称为replace模式所谓的replace模式就是指每次进行一次视图切换上一个视图都会直接被销毁并不会push到栈中所以无法进行回退 直接router-link标签中添加replace属性 // 完整写法
:replacetrue
// 简写形式
replace13.8 编程式路由导航 前面完美使用router-link标签实现组件间的切换本质上router-link是一个a标签底层会将router-link标签转换成一个a标签但如果我们要设置一个按钮实现跳转又该怎么办呢其实也可以使用router-link包裹按钮然后实现组件间的切换但是我们有一种更好的方式也就是编程式路由导航 示例 
这里就暂且使用伪代码的形式进行演示了详细代码请参考博主的Github或Gitee仓库 
!-- template --
button clickjump(data)点击跳转/button!-- js --
methods:{jump(data){// 使用$router.push实现网页跳转这里表示真正的跳转而是组件间的切换this.$router.push({name:detail // 这里使用name属性当然也可以直接使用path明确指定要切换的组件// 可以给jump函数传递参数然后通过路由传递给其它组件query:{id: data.id,title: data.title}})}
}// 使用$router.back实现网页回退
this.$router.back({console.log(网页回退)}})
// 使用$router.forward实现网页前进
this.$router.forward({console.log(网页回退)}})       
// 使用$router.go实现网页任意的前进或后退正数表示前景负数表示后退
this.$router.go({this.$router.go(-2)console.log(网页连续后退两次)}})13.9 缓存路由组件 
使用keep-alive包裹router-view即可实现组件中数据的缓存 
router-link to/home/router-link
router-link to/about/router-link
keep-alive includeHomeTest!-- 现在用户输入home组件中的数据就能够进行进行缓存了切换还能展示数据但是不会缓存about组件中的数据。不加include属性就是默认缓存所有的路由组件中的数据--router-view/router-view
/keep-alive也可以同时配置多个缓存 
keep-alive :include[HomeTest,AboutTest]router-view/router-view
/keep-alive13.10 生命周期钩子 本小节将要学习两个全新的生命周期钩子这两个钩子是独属于路由组件的。 activated()路由组件激活被调用。 激活是指路由组件被页面展示也就是当用户点击router-link标签跳转到对应路由组件此时该路由组件就处于激活状态  deactivated()路由组件失活被调用 失活是指路由组件被隐藏切换当用户点击router-link标签跳转到另一个组件此时该路由组件被切换了此时就处于失活状态  这个生命周期钩子常用于一下场景 当我们想要在路由组件销毁时触发一个方法但该路由组件使用了缓存keep-alive此时该路由组件被切换不会被销毁也就无法触发destroyed()方法这就需要使用deactivated()钩子了。 同理mouted钩子只有组件在初始化时才会被调用加入我们切换组件且组件使用了缓存这时是不会触发mouted钩子的这就需要使用activated()钩子了   
13.11 路由守卫 路由守卫就类似于一个拦截器 在src/router/index中编写 
全局前置路由守卫router.beforeEach({}) 
const router  new VueRouter({routes:[{path: /home,component:HomeTest,meta:{isAuth:true}}]
})
// 全局前置路由守卫路由初始前被调用路由切换前被调用
router.beforeEach((to, from, next){console.log(to,from,next)// 判断是否需要鉴权if(to.meta.isAuth){// 需要鉴定权限if(localStorage.getItem(user) ! null  localStorage.getItem(user) ! ){// 满足条件放行next()}}// 放行next()
})
export default router全局后置路由守卫router.afterEach({}) 
const router  new VueRouter({routes:[{path: /home,component:HomeTest,meta:{title:主页}}]
})
// 全局后置路由守卫路由初始后被调用路由切换后被调用
router.afterEach((to, from){console.log(to,from)document.title  to.meta.title || 用户中心系统
})
export default router应用场景Vue开发项目都是开发单页面应用这就导致我们切换组件不会切换网页的标题为了实现切换组件的同时切换网页标题就需要使用后置路由守卫 
独享路由守卫beforeEnter(前置独享路由守卫注意没有后置独享路由守卫) 
export default new VueRouter({routes:[{path: /home,component:HomeTest,// 只针对HomeTest组件起作用只读HomeTest进行鉴权路由初始前被调用路由切换前被调用beforeEnter((to, from, next){if(localStorage.getItem(user) ! null  localStorage.getItem(user) ! ){// 满足条件放行next()}})}]
})组件内路由守卫beforeRouterEnter进入路由守卫beforeRouterLeave离开路由守卫 
在HomeTest.vue组件中进行编写 
scriptexport default {name: Abouttest,// 进入如有守卫通过路由规则进入该组件时被调用beforeRouterEnter(to, from, next){},// 离开路由守卫beforeRouterLeave(to, from, next){}}
/script注意和全局路由守卫的区别全局路由守卫是组件切换时立马执行前置路由守卫紧接着就执行后置路由守卫但组件内路由守卫是组件切换时立马执行进入路由守卫但是并不会紧接着触发离开路由守卫因为此时我们还没有离开该组件只有当我们离开该组件时才会调用离开路由组件 
13.12 路由的两种工作模式 vue-router 默认为 hash 模式使用 URL 的 hash 来模拟一个完整的 URL当 URL 改变时页面不会重新加载# 就是 hash符号中文名为哈希符或者锚点在 hash 符号后的值称为 hash 值 hash模式监听浏览器地址hash值变化执行相应的js切换网页  history模式利用history API实现url地址改变网页内容改变  两者的区别  hash模式不美观但是 # 后面的内容不会发送给服务器兼容性特别好。 使用hash模式如果地址通过第三方手机App分享若App校验严格则地址会被标记为不合法  history模式美观但是兼容性较差应用部署上线时需要后端人员的出手才能解决刷新页面服务端404的问题   
14、Vue的UI组件库 
移动端常用的UI组件库 Mint UI (mint-ui.github.io)Vant 4 - Lightweight Mobile UI Components built on Vue (vant-ui.github.io)cube-ui Document (didi.github.io)NutUI - 移动端 Vue2、Vue3、小程序 组件库 (jd.com) PC端常用的UI组件库 Element - 网站快速成型工具iView / View Design 一套企业级 UI 组件库和前端解决方案 (iviewui.com)  
如果还有其它好用的组件库欢迎评论区补充 参考资料 尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili跨域的十种解决方案 - 掘金 (juejin.cn)跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng.com) 抖动是指由于用户频繁操作导致浏览器出现颤动的现象抖动会影响用户体验 ↩︎