学习Vue2看这一篇就够了!
简介什么是vue 创建第一个vue实例步骤 插值表达式{{}}响应式特征Vue指令条件渲染v-showv-ifv-else、v-else-if 事件绑定v-on 属性绑定v-bind 双向绑定v-model 列表渲染v-forv-for中的key 指令修饰符按键修饰符v-model修饰符`v-model.trim``v-model.number` 事件修饰符@事件名.stop@事件名.prevent v-bind对于样式操作的增强操作class操作style computed计算属性computed计算属性 VS methods方法computed计算属性methods计算属性计算属性的优势 计算属性完整写法 watch侦听器(监视器)vue生命周期vue生命周期钩子工程化开发和脚手架开发vue两种模式脚手架Vue CLI基本介绍好处使用步骤 项目目录以及运行流程项目目录介绍运行流程 组件化开发根组件 App.vue介绍组件由三部分组成 普通组件注册-局部注册普通组件注册-全局注册组件三大组成部分scoped解决样式冲突**默认情况**代码演示scoped原理 data必须是一个函数data为什么要写成函数代码演示 组件通信什么是组件通信?组件之间如何通信组件关系分类通信解决方案父子通信流程父向子通信代码示例子向父通信代码示例 什么是propsProps 定义Props 作用特点代码演示 props校验思考作用语法代码演示 props校验完整写法语法代码实例注意 props&data、单向数据流共同点区别单向数据流代码演示 非父子通信-event bus 事件总线作用步骤代码示例总结 非父子通信-provide&inject作用场景语法注意 v-model原理原理作用注意代码示例v-model使用在其他表单元素上的原理 表单类组件封装需求目标代码演示 v-model简化代码原理作用目标如何简化代码示例 .sync修饰符作用特点场景本质语法代码示例 ref和$refs作用特点:语法注意代码示例 异步更新 & $nextTick需求代码实现问题解决方案 自定义指令指令介绍自定义指令自定义指令语法指令中的配置项介绍代码示例 自定义指令-指令的值需求语法代码示例 自定义指令-v-loading指令的封装场景需求分析实现准备代码 插槽-默认插槽作用需求问题插槽的基本语法代码示例 插槽-后备内容(默认值)问题插槽的后备内容(默认值)语法效果 插槽-具名插槽需求具名插槽语法v-slot的简写 作用域插槽插槽分类作用场景使用步骤代码示例 单页应用程序介绍概念具体示例单页应用 VS 多页面应用 路由介绍思考路由的介绍 路由的基本使用目标作用说明官网VueRouter的使用(5+2)代码示例两个核心步骤 组件的存放目录问题组件分类存放目录 路由的封装抽离声明式导航-导航链接需求解决方案通过router-link自带的两个样式进行高亮 声明式导航-两个类名router-link-activerouter-link-exact-active在地址栏中输入二级路由查看类名的添加 声明式导航-自定义类名(了解)1.问题解决方案代码演示 声明式导航-查询参数传参目标跳转传参查询参数传参代码演示 声明式导航-动态路由传参动态路由传参方式查询参数传参 VS 动态路由传参 动态路由参数的可选符(了解)问题原因 Vue路由-重定向问题解决方案语法代码演示 Vue路由-404作用位置语法代码示例 Vue路由-模式设置问题语法 编程式导航-两种路由跳转方式问题方案语法path路径跳转语法代码演示 path跳转方式name命名路由跳转代码演示通过name命名路由跳转 编程式导航-path路径跳转传参问题两种传参方式传参path路径跳转传参(query传参)path路径跳转传参(动态路由传参) 编程式导航-name命名路由传参name 命名路由跳转传参 (query传参)name 命名路由跳转传参 (动态路由传参)总结 如何配置二级路由keep-alive的三个属性6.额外的两个生命周期钩子 VueCli 自定义创建项目ESlint代码规范及手动修复JavaScript Standard Style 规范说明代码规范错误手动修正 通过eslint插件来实现自动修正Vuex 概述是什么使用场景优势注意多组件共享数据创建项目创建三个组件, 目录如下源代码如下 vuex 的使用 - 创建仓库安装 vuex新建 `store/index.js` 专门存放 vuex创建仓库 `store/index.js`在 main.js 中导入挂载到 Vue 实例上测试打印Vuex 核心概念 - state 状态目标提供数据访问Vuex中的数据通过$store访问的语法代码实现模板中使用组件逻辑中使用js文件中使用 通过辅助函数 - mapState获取 state中的数据第一步:导入mapState (mapState是vuex中的一个函数)第二步:采用数组形式引入state属性第三步:利用**展开运算符**将导出的状态映射给计算属性 开启严格模式及Vuex的单项数据流目标直接在组件中修改Vuex中state的值开启严格模式 核心概念-mutations定义mutations格式说明组件中提交 mutations 带参数的 mutations目标语法提供mutation函数(带参数)提交mutation 练习-mutations的减法功能步骤代码实现 Vuex中的值和组件中的input双向绑定目标实现步骤代码实现 辅助函数- mapMutations核心概念 - actions定义actions**组件中通过dispatch调用** mapActions核心概念 - getters定义getters使用getters原始方式-$store辅助函数 - mapGetters 使用小结核心概念 - module目标问题模块定义 - 准备 state 获取模块内的state数据目标使用模块中的数据代码示例 获取模块内的getters数据目标语法代码演示 获取模块内的mutations方法目标注意调用方式代码实现 获取模块内的actions方法目标注意调用语法代码实现 Vuex模块化的使用小结直接使用借助辅助方法使用简介
什么是vue
vue是用于构建用户界面的渐进式框架。
官网:https://v2./
创建第一个vue实例
步骤
创建一个容器
引包(官网)
创建vue实例,new Vue({})
指定配置项,渲染数据
el:指定挂载点
data:提供数据
举个例子将hello word渲染到界面上:
<body><div class="app"><h1>{{meg}}</h1></div><!-- 引入开发版本的包,包含完整注释和警告 --><script src="/npm/vue@2.7.14/dist/vue.js"></script><script>const app = new Vue({el:'.app',data:{meg:'hello word'}})</script></body>
插值表达式{{}}
插值表达式是一种vue模板语法
语法:
{{表达式}}{{msg}}
注意:
在插值表达式中使用的数据 必须在data中进行了提供,如果在data中不存在 则会报错支持的是表达式,而非语句,比如:if for …不能在标签属性中使用 {{ }} 插值 (插值表达式只能标签中间使用)
正确示范:<p>{{msg}}</p> //hello<p>{{msg.toUpperCase()}}</p> //HELLO<P>{{msg + 'word'}}</P><p>{{age >10? '成年':'未成年'}}</p> //成年错误示范:<p>{{hello}}</p> //数据不存在<p>{{if}}</p> //不知此语句<p>{{title='{{msg}}'}}</p> //不能在标签属性中使用<script>const app = new Vue({el:'#box',data:{msg:'hello',meg:'word',age:18,friend:{name:'mike',}}})</script>
响应式特征
数据改变,试图会自动更新。
访问数据:实例.属性名
修改数据:实例.属性名 = ’值’
Vue指令
条件渲染
v-show
作用:控制元素显示与隐藏。
语法:v-show = '表达式‘ 表达式的值true为显示,false为隐藏。
原理:切换display:none控制显示与隐藏
场景:应用与频繁切换显示与隐藏的场景
v-if
作用:控制元素显示与隐藏。
语法:v-if = ’表达式’表达式的值true为显示,false为隐藏。
原理:基于条件判断,是否创建或移除元素节点
场景: 要么显示,要么隐藏,不频繁切换的
<div id="app"><div class="box" v-show="flag">我是v-show控制的盒子</div><div class="box" v-if="flag">我是v-if控制的盒子</div></div><script>const app = new Vue({el: '#app',data: {flag: true}})</script>
v-else、v-else-if
作用:辅助v-if进行判断,要紧挨着v-if一起使用
<div id="app"><p>性别:♂ 男</p><p>性别:♀ 女</p><hr><p>成绩评定A:奖励电脑一台</p><p>成绩评定B:奖励周末郊游</p><p>成绩评定C:奖励零食礼包</p><p>成绩评定D:惩罚一周不能玩手机</p></div><script src="/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {gender: 2,score: 95}})</script>
事件绑定
v-on
使用Vue时,如需为DOM注册事件,及其的简单,语法如下:
<button v-on:事件名=“内联语句”>按钮<button v-on:事件名=“处理函数”>按钮<button v-on:事件名=“处理函数(实参)”>按钮v-on:
简写为@
内联语句
<div id="app"><button @click="count--">-</button><span>{{count }}</span><button v-on:click="count++">+</button></div><script src="/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {count: 100}})</script>
事件处理函数
注意:
事件处理函数应该写到一个跟data同级的配置项(methods)中methods中的函数内部的this都指向Vue实例
<div id="box"><button @click="fn">切换</button><h1 v-show="ishsow">黑马程序员</h1></div><script src="/npm/vue@2.7.14/dist/vue.js"></script><script>const app = new Vue({el:'#box',data:{ishsow:true},methods:{fn(){this.ishsow = !this.ishsow}}})</script>
属性绑定
v-bind
**作用:**动态设置html的标签属性 比如:src、url、title语法:**v-bind:**属性名=“表达式”**v-bind:**可以简写成 =>:比如,有一个图片,它的src
属性值,是一个图片地址。这个地址在数据 data 中存储。
则可以这样设置属性值:
<img v-bind:src="url" />
<img :src="url" />
(v-bind可以省略)
<div id="app"><img v-bind:src="imgUrl" v-bind:title="msg" alt=""><img :src="imgUrl" :title="msg" alt=""></div><script src="/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {imgUrl: './imgs/10-02.png',msg: 'hello 波仔'}})</script>
双向绑定
v-model
所谓双向绑定就是:
数据改变后,呈现的页面结果会更新页面结果更新后,数据也会随之而变
作用:给表单元素(input、radio、select)使用,双向绑定数据,可以快速获取或设置表单元素内容
**语法:**v-model=“变量”
**需求:**使用双向绑定实现以下需求
点击登录按钮获取表单中的内容点击重置按钮清空表单中的内容
<div id="app">账户:<input type="text"> <br><br>密码:<input type="password"> <br><br><button>登录</button><button>重置</button></div><script src="/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {username: '',password: ''},})</script>
列表渲染
v-for
Vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。
v-for 指令需要使用(item, index) in arr
形式的特殊语法,其中:
item 是数组中的每一项index 是每一项的索引,不需要可以省略arr 是被遍历的数组
此语法也可以遍历对象和数字
//遍历对象<div v-for="(value, key, index) in object">{{value}}</div>value:对象中的值key:对象中的键index:遍历索引从0开始//遍历数字<p v-for="item in 10">{{item}}</p>item从1 开始
v-for中的key
语法:key=“唯一值”
作用:给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用。
**为什么加key:**Vue 的默认行为会尝试原地修改元素(就地复用)
实例代码:
<ul><li v-for="(item, index) in booksList" :key="item.id"><span>{{item.name }}</span><span>{{item.author }}</span><button @click="del(item.id)">删除</button></li></ul>
注意:
key 的值只能是字符串 或 数字类型key 的值必须具有唯一性推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(会变化,不对应)
指令修饰符
指令修饰符就是通过“ . ”指明一些指令的后缀,不同的后缀封装了不同的处理操作->简化代码。
按键修饰符
@click.enter
只有点击enter键时才会触发。
<button @click.enter>按钮</button>
v-model修饰符
v-model.trim
去除空格
v-model.number
转数字
事件修饰符
@事件名.stop
阻止冒泡
@事件名.prevent
阻止默认行为
v-bind对于样式操作的增强
操作class
为了方便开发者进行样式控制, Vue 扩展了 v-bind 的语法,可以针对 class 类名 和 style 行内样式 进行控制 。
语法:class=“对象/数组”
对象 → 键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类,适用于一个类名来回切换
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
数组 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表 ,适用于批量添加或删除类
<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>
操作style
语法:style = “样式对象”
适用于某个具体属性的动态设置
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
computed计算属性
基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。
语法:
声明在computed 配置项中,一个计算属性对应一个函数使用起来和普通属性一样使用 {{ 计算属性名}}
注意:
computed配置项和data配置项是同级的computed中的计算属性虽然是函数的写法,但他依然是个属性computed中的计算属性不能和data中的属性同名使用computed中的计算属性和使用data中的属性是一样的用法computed中计算属性内部的this依然指向的是Vue实例
<script>const app = new Vue({el: '#app',data: {// 现有的数据list: [{id: 1, name: '篮球', num: 1 },{id: 2, name: '玩具', num: 2 },{id: 3, name: '铅笔', num: 5 },]},computed:{totalCount(){// return 6//基于现有的数据,编写求值逻辑//计算属性函数内部,可以直接通过this访问app实例// console.log(this);//对this.list数组里面的num进行求和 ->reducelet total = this.list.reduce((sum,item) => sum + item.num,0)return total}}})</script>
computed计算属性 VS methods方法
computed计算属性
作用:封装了一段对于数据的处理,求得一个结果
语法:
写在computed配置项中作为属性,直接使用 js中使用计算属性: this.计算属性模板中使用计算属性:{{计算属性}}
methods计算属性
作用:给Vue实例提供一个方法,调用以处理业务逻辑。
语法:
写在methods配置项中作为方法调用 js中调用:this.方法名()模板中调用 {{方法名()}} 或者 @事件名=“方法名”
计算属性的优势
缓存特性(提升性能)
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,
依赖项变化了,会自动重新计算 → 并再次缓存
methods没有缓存特性
计算属性完整写法
计算属性默认的简写,只能读取访问,不能 “修改”如果要 “修改” → 需要写计算属性的完整写法watch侦听器(监视器)
1.简单写法
watch: {数据属性名 (newValue, oldValue) {一些业务逻辑 或 异步操作。 },'对象.属性名' (newValue, oldValue) {一些业务逻辑 或 异步操作。 }}
2.完整写法
watch: {// watch 完整写法数据属性名: {deep: true, // 深度监视(针对复杂类型)immediate: true, // 是否立刻执行一次handlerhandler (newValue) {console.log(newValue)}}}
vue生命周期
Vue生命周期:就是一个Vue实例从创建 到 销毁 的整个过程。
生命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁
1.创建阶段:创建响应式数据
2.挂载阶段:渲染模板
3.更新阶段:修改数据,更新视图
4.销毁阶段:销毁Vue实例
vue生命周期钩子
Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】→ 让开发者可以在【特定阶段】运行自己的代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><div id="app"><h3>{{title }}</h3><div><button @click="count--">-</button><span>{{count }}</span><button @click="count++">+</button></div></div><script src="/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {count: 100,title: '计数器'},//1、创建阶段 (准备数据)beforeCreate(){console.log('响应式数据准备之前',this.count); //undefined},created(){console.log('响应数据准备之后',this.count) //100//this.数据名 = 请求回来的数据//可以开始发送初始化渲染的请求了},//2、挂载阶段(渲染模板)beforeMount(){console.log('模板渲染之前',document.querySelector('h3').innerHTML);},mounted(){console.log('模板渲染之后',document.querySelector('h3').innerHTML);},//3、更新阶段(修改数据 ->更新视图)beforeUpdate(){console.log('数据修改了,视图还没有更新',document.querySelector('span').innerHTML);},updated(){console.log('数据修改了视图已经修改了',document.querySelector('span').innerHTML);},//4、卸载阶段beforeDestroy(){console.log('卸载前');},destroyed(){console.log('卸载后');}})</script></body></html>
工程化开发和脚手架
开发vue两种模式
核心包传统开发模式:基于html / css / js 文件,直接引入核心包,开发 Vue。工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue。脚手架Vue CLI
基本介绍
Vue CLI 是Vue官方提供的一个全局命令工具
可以帮助我们快速创建一个开发Vue项目的标准化基础架子。【集成了webpack配置】
好处
开箱即用,零配置内置babel等工具标准化的webpack配置使用步骤
全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g查看vue/cli版本: vue --version创建项目架子:vue create project-name(项目名不能使用中文)启动项目:yarn serve或者npm run serve(命令不固定,找package.json)项目目录以及运行流程
项目目录介绍
运行流程
组件化开发
组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用 → 提升开发效率。
组件分类:普通组件、根组件。
比如:下面这个页面,可以把所有的代码都写在一个页面中,但是这样显得代码比较混乱,难易维护。咱们可以按模块进行组件划分
根组件 App.vue
介绍
整个应用最上层的组件,包裹所有普通小组件
组件由三部分组成
三部分构成
template:结构 (有且只能一个根元素)script: js逻辑style: 样式 (可支持less,需要装包)
让组件支持less
(1) style标签,lang=“less” 开启less功能
(2) 装包: yarn add less less-loader -D 或者npm i less less-loader -D
普通组件注册-局部注册
只能在注册的组件内使用,组件名规范 —> 大驼峰命名法, 如 HmHeader
现在components文件夹下创建对应的组件
每个组件内部都是解构、样式、行为三部分组成,互相独立,互不影响。
然后再App.vue里面导入注册的组件
<script>import 组件对象 from '.文件路径'; import HmFooter from './components/HmFooter.vue';import HmHeader from './components/HmHeader.vue';import HmMain from './components/HmMain.vue';export default{components:{'组件名':组件对象 HmHeader:HmHeader,HmMain:HmMain,HmFooter:HmFooter}}</script>
当成HTML标签使用
普通组件注册-全局注册
创建.vue文件 在main.js中进行全局注册//导入需要全局注册的组件import HmButton from './components/HmButton'//进行全局注册在所有组件范围内都能直接使用//ponent(组件名,组件对象)ponent('HmButton',HmButton)
组件三大组成部分
scoped解决样式冲突
默认情况
写在组件中的样式会全局生效→ 因此很容易造成多个组件之间的样式冲突问题。
全局样式: 默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响
局部样式: 可以给组件加上scoped属性,可以让样式只作用于当前组件
代码演示
BaseOne.vue
<template><div class="base-one">BaseOne</div></template><script>export default {}</script><style scoped></style>
BaseTwo.vue
<template><div class="base-one">BaseTwo</div></template><script>export default {}</script><style scoped></style>
App.vue
<template><div id="app"><BaseOne></BaseOne><BaseTwo></BaseTwo></div></template><script>import BaseOne from './components/BaseOne'import BaseTwo from './components/BaseTwo'export default {name: 'App',components: {BaseOne,BaseTwo}}</script>
scoped原理
当前组件内标签都被添加data-v-hash值的属性css选择器都被添加 [data-v-hash值] 的属性选择器最终效果:必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
data必须是一个函数
data为什么要写成函数
一个组件的data选项必须是一个函数。目的是为了:保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会新执行一次data 函数,得到一个新对象。
代码演示
BaseCount.vue
<template><div class="base-count"><button @click="count--">-</button><span>{{ count }}</span><button @click="count++">+</button></div></template><script>export default {data: function () {return {count: 100,}},}</script><style>.base-count {margin: 20px;}</style>
App.vue
<template><div class="app"><BaseCount></BaseCount></div></template><script>import BaseCount from './components/BaseCount'export default {components: {BaseCount,},}</script><style></style>
组件通信
什么是组件通信?
组件通信,就是指组件与组件之间的数据传递
组件的数据是独立的,无法直接访问其他组件的数据。想使用其他组件的数据,就需要组件通信
组件之间如何通信
组件关系分类
父子关系非父子关系通信解决方案
父子通信流程
父组件通过props将数据传递给子组件子组件利用$emit通知父组件修改更新父向子通信代码示例
父组件通过props将数据传递给子组件
父组件App.vue
<template><div class="app" style="border: 3px solid #000; margin: 10px">我是APP组件 <Son></Son></div></template><script>import Son from './components/Son.vue'export default {name: 'App',data() {return {myTitle: '学前端,就来黑马程序员',}},components: {Son,},}</script><style></style>
子组件Son.vue
<template><div class="son" style="border:3px solid #000;margin:10px">我是Son组件</div></template><script>export default {name: 'Son-Child',}</script><style></style>
父向子传值步骤
给子组件以添加属性的方式传值子组件内部通过props接收模板中直接使用 props接收的值
子向父通信代码示例
子组件利用$emit通知父组件,进行修改更新
子向父传值步骤
$emit触发事件,给父组件发送消息通知父组件监听$emit触发的事件提供处理函数,在函数的性参中获取传过来的参数
什么是props
Props 定义
组件上 注册的一些 自定义属性
Props 作用
向子组件传递数据
特点
可以 传递任意数量的prop可以 传递任意类型的prop代码演示
父组件App.vue
<template><div class="app"><UserInfo:username="username":age="age":isSingle="isSingle":car="car":hobby="hobby"></UserInfo></div></template><script>import UserInfo from './components/UserInfo.vue'export default {data() {return {username: '小帅',age: 28,isSingle: true,car: {brand: '宝马',},hobby: ['篮球', '足球', '羽毛球'],}},components: {UserInfo,},}</script><style></style>
子组件UserInfo.vue
<template><div class="userinfo"><h3>我是个人信息组件</h3><div>姓名:</div><div>年龄:</div><div>是否单身:</div><div>座驾:</div><div>兴趣爱好:</div></div></template><script>export default {}</script><style>.userinfo {width: 300px;border: 3px solid #000;padding: 20px;}.userinfo > div {margin: 20px 10px;}</style>
props校验
思考
组件的props可以乱传吗
作用
为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示→ 帮助开发者,快速发现错误
语法
类型校验非空校验默认值自定义校验代码演示
App.vue
<template><div class="app"><BaseProgress :w="width"></BaseProgress></div></template><script>import BaseProgress from './components/BaseProgress.vue'export default {data() {return {width: 30,}},components: {BaseProgress,},}</script><style></style>
BaseProgress.vue
<template><div class="base-progress"><div class="inner" :style="{ width: w + '%' }"><span>{{ w }}%</span></div></div></template><script>export default {props: ['w'],}</script><style scoped>.base-progress {height: 26px;width: 400px;border-radius: 15px;background-color: #272425;border: 3px solid #272425;box-sizing: border-box;margin-bottom: 30px;}.inner {position: relative;background: #379bff;border-radius: 15px;height: 25px;box-sizing: border-box;left: -3px;top: -2px;}.inner span {position: absolute;right: 0;top: 26px;}</style>
props校验完整写法
语法
props: {校验的属性名: {type: 类型, // Number String Boolean ...required: true, // 是否必填default: 默认值, // 默认值validator (value) {// 自定义校验逻辑return 是否通过校验}}},
代码实例
<script>export default {// 完整写法(类型、默认值、非空、自定义校验)props: {w: {type: Number,//required: true,default: 0,validator(val) {// console.log(val)if (val >= 100 || val <= 0) {console.error('传入的范围必须是0-100之间')return false} else {return true}},},},}</script>
注意
1.default和required一般不同时写(因为当时必填项时,肯定是有值的)
2.default后面如果是简单类型的值,可以直接写默认。如果是复杂类型的值,则需要以函数的形式return一个默认值
props&data、单向数据流
共同点
都可以给组件提供数据
区别
data 的数据是自己的 → 随便改prop 的数据是外部的 → 不能直接改,要遵循单向数据流单向数据流
父级props 的数据更新,会向下流动,影响子组件。这个数据流动是单向的,谁的数据谁负责
代码演示
App.vue
<template><div class="app"><BaseCount></BaseCount></div></template><script>import BaseCount from './components/BaseCount.vue'export default {components:{BaseCount},data(){},}</script><style></style>
BaseCount.vue
<template><div class="base-count"><button @click="count--">-</button><span>{{ count }}</span><button @click="count++">+</button></div></template><script>export default {// 1.自己的数据随便修改 (谁的数据 谁负责)data () {return {count: 100,}},// 2.外部传过来的数据 不能随便修改//props: {// count: {// type: Number,// }, //}}</script><style>.base-count {margin: 20px;}</style>
非父子通信-event bus 事件总线
作用
非父子组件之间,进行简易消息传递。(复杂场景→ Vuex)
步骤
创建一个都能访问的事件总线 (空Vue实例)
import Vue from 'vue'const Bus = new Vue()export default Bus
A组件(接受方),监听Bus的 $on事件
created () {Bus.$on('sendMsg', (msg) => {this.msg = msg})}
B组件(发送方),触发Bus的$emit事件
Bus.$emit('sendMsg', '这是一个消息')
代码示例
EventBus.js
import Vue from 'vue'const Bus = new Vue()export default Bus
BaseA.vue(接受方)
<template><div class="base-a">我是A组件(接收方)<p>{{msg}}</p> </div></template><script>import Bus from '../utils/EventBus'export default {data() {return {msg: '',}},}</script><style scoped>.base-a {width: 200px;height: 200px;border: 3px solid #000;border-radius: 3px;margin: 10px;}</style>
BaseB.vue(发送方)
<template><div class="base-b"><div>我是B组件(发布方)</div><button>发送消息</button></div></template><script>import Bus from '../utils/EventBus'export default {}</script><style scoped>.base-b {width: 200px;height: 200px;border: 3px solid #000;border-radius: 3px;margin: 10px;}</style>
App.vue
<template><div class="app"><BaseA></BaseA><BaseB></BaseB> </div></template><script>import BaseA from './components/BaseA.vue'import BaseB from './components/BaseB.vue' export default {components:{BaseA,BaseB}}</script><style></style>
总结
1.非父子组件传值借助什么?
2.什么是事件总线
3.发送方应该调用事件总线的哪个方法
4.接收方应该调用事件总线的哪个方法
5.一个组件发送数据,可不可以被多个组件接收
非父子通信-provide&inject
作用
跨层级共享数据
场景
语法
父组件 provide提供数据export default {provide () {return {// 普通类型【非响应式】color: this.color, // 复杂类型【响应式】userInfo: this.userInfo, }}}
2.子/孙组件 inject获取数据
export default {inject: ['color','userInfo'],created () {console.log(this.color, this.userInfo)}}
注意
provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)子/孙组件通过inject获取的数据,不能在自身组件内修改v-model原理
原理
v-model本质上是一个语法糖。例如应用在输入框上,就是value属性 和 input事件 的合写
<template><div id="app" ><input v-model="msg" type="text"><input :value="msg" @input="msg = $event.target.value" type="text"></div></template>
作用
提供数据的双向绑定
数据变,视图跟着变 :value视图变,数据跟着变 @input
注意
$event用于在模板中,获取事件的形参
代码示例
<template><div class="app"><input v-model="msg" type="text" /><br /><input :value="msg1" @input="msg1 = $event.target.value" type="text" ></div></template><script>export default {data() {return {msg:'',msg1: ''}},}</script><style></style>
v-model使用在其他表单元素上的原理
不同的表单元素, v-model在底层的处理机制是不一样的。比如给checkbox使用v-model
底层处理的是 checked属性和change事件。
不过咱们只需要掌握应用在文本框上的原理即可
表单类组件封装
需求目标
实现子组件和父组件数据的双向绑定 (实现App.vue中的selectId和子组件选中的数据进行双向绑定)
代码演示
App.vue
<template><div class="app"><BaseSelect :cityId="selectId" @changeId="selectId = $event"></BaseSelect></div></template><script>import BaseSelect from './components/BaseSelect.vue'export default {data() {return {selectId: '102',}},components: {BaseSelect,},methods: {handleChange(e) {console.log(e);this.selectId = e}}}</script><style></style>
BaseSelect.vue
<template><div><select :value="cityId" @change="handleChange"><option value="101">北京</option><option value="102">上海</option><option value="103">武汉</option><option value="104">广州</option><option value="105">深圳</option></select></div></template><script>export default {props:{cityId:String},methods:{handleChange(e){//console.log(e.target.value);this.$emit('changeId',e.target.value)}}}</script><style></style>
v-model简化代码
原理
v-model
本质上是一个语法糖,例如应用在输入框上,就是value
和input
事件的合写
作用
提供数据的双向绑定
数据变,视图跟着变:value视图变,数据跟着变 @input$event用于在模板中,获取事件的形参
<template><div id="app"><input v-model="msg" type="text"><input :value="msg" @input="msg=$event.target.value" type="text"></div></template>
v-model="msg"就等价于:value="msg" @input="msg=$event.target.value"
目标
父组件通过v-model简化代码,实现子组件和父组件数据双向绑定
如何简化
v-model其实就是 :value和@input事件的简写
子组件:props通过value接收数据,事件触发 input父组件:v-model直接绑定数据
代码示例
子组件
<select :value="value" @change="handleChange">...</select>props: {value: String},methods: {handleChange (e) {this.$emit('input', e.target.value)}}
父组件
<BaseSelect v-model="selectId"></BaseSelect>
.sync修饰符
作用
可以实现子组件与父组件数据的双向绑定,简化代码
简单理解:子组件可以修改父组件传过来的props值
特点
prpo属性名,可以自定义,非固定为value
场景
封装弹框类的基础组件, visible属性 true显示 false隐藏
本质
.sync修饰符就是:属性名和@update:属性名合写
语法
父组件
//.sync写法<BaseDialog :visible.sync="isShow" />--------------------------------------//完整写法<BaseDialog :visible="isShow" @update:visible="isShow = $event" />
子组件
props: {visible: Boolean},this.$emit('update:visible', false)
代码示例
App.vue
<template><div class="app"><button @click="isShow = true">退出按钮</button><!-- :visible.sync =>:value + @update:visible --><BaseDialog :visible.sync="isShow"></BaseDialog></div></template><script>import BaseDialog from "./components/BaseDialog.vue"export default {data() {return {isShow:false}},methods: {},components: {BaseDialog,},}</script><style></style>
BaseDialog.vue
<template><div v-show="visible" class="base-dialog-wrap"><div class="base-dialog"><div class="title"><h3>温馨提示:</h3><button @click="close" class="close">x</button></div><div class="content"><p>你确认要退出本系统么?</p></div><div class="footer"><button @click="close">确认</button><button>取消</button></div></div></div></template><script>export default {props:{visible:Boolean},methods:{close(){this.$emit('update:visible',false)}}}</script><style scoped>.base-dialog-wrap {width: 300px;height: 200px;box-shadow: 2px 2px 2px 2px #ccc;position: fixed;left: 50%;top: 50%;transform: translate(-50%, -50%);padding: 0 10px;}.base-dialog .title {display: flex;justify-content: space-between;align-items: center;border-bottom: 2px solid #000;}.base-dialog .content {margin-top: 38px;}.base-dialog .title .close {width: 20px;height: 20px;cursor: pointer;line-height: 10px;}.footer {display: flex;justify-content: flex-end;margin-top: 26px;}.footer button {width: 80px;height: 40px;}.footer button:nth-child(1) {margin-right: 10px;cursor: pointer;}</style>
ref和$refs
作用
利用ref 和 $refs 可以用于 获取 dom 元素 或 组件实例
特点:
查找范围 → 当前组件内(更精确稳定)
语法
1.给要获取的盒子添加ref属性
<div ref="chartRef">我是渲染图表的容器</div>
2.获取时通过 $refs获取 this.$refs.chartRef 获取
mounted () {console.log(this.$refs.chartRef)}
注意
之前只用document.querySelect(‘.box’) 获取的是整个页面中的盒子
代码示例
App.vue
<template><div class="app"><div class="base-chart-box">这是一个捣乱的盒子</div><BaseChart></BaseChart></div></template><script>import BaseChart from './components/BaseChart.vue'export default {components:{BaseChart}}</script><style>.base-chart-box {width: 200px;height: 100px;}</style>
BaseChart.vue
<template><div ref="myChat" class="base-chart-box">子组件</div></template><script>import * as echarts from 'echarts'export default {mounted() {// 基于准备好的dom,初始化echarts实例// const myChart = echarts.init(document.querySelector('.base-chart-box'))const myChart = echarts.init(this.$refs.myChat)// 绘制图表myChart.setOption({title: {text: 'ECharts 入门示例',},tooltip: {},xAxis: {data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],},yAxis: {},series: [{name: '销量',type: 'bar',data: [5, 20, 36, 10, 10, 20],},],})},}</script><style scoped>.base-chart-box {width: 400px;height: 300px;border: 3px solid #000;border-radius: 6px;}</style>
异步更新 & $nextTick
需求
编辑标题, 编辑框自动聚焦
点击编辑,显示编辑框让编辑框,立刻获取焦点
代码实现
<template><div class="app"><div v-if="isShowEdit"><input type="text" v-model="editValue" ref="inp" /><button>确认</button></div><div v-else><span>{{ title }}</span><button @click="editFn">编辑</button></div></div></template><script>export default {data() {return {title: '大标题',isShowEdit: false,editValue: '',}},methods: {editFn() {// 显示输入框this.isShowEdit = true // 获取焦点($nextTick等dom更新完立刻执行准备的函数体)this.$nextTick(()=>{this.$refs.inp.focus() })//也可以实现同样的效果但是不精准// setTimeout(()=>{// this.$refs.inp.focus() // },1000)} },}</script>
问题
“显示之后”,立刻获取焦点是不能成功的!
原因:Vue 是异步更新DOM (提升性能)
解决方案
$nextTick:等 DOM更新后,才会触发执行此方法里的函数体
语法:this.$nextTick(函数体)
this.$nextTick(() => {this.$refs.inp.focus()})
注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例
自定义指令
指令介绍
内置指令:v-html、v-if、v-bind、v-on… 这都是Vue给咱们内置的一些指令,可以直接使用
自定义指令:同时Vue也支持让开发者,自己注册一些指令。这些指令被称为自定义指令
每个指令都有自己各自独立的功能
自定义指令
概念:自己定义的指令,可以封装一些DOM操作,扩展额外的功能
自定义指令语法
全局注册
//在main.js中Vue.directive('指令名', {"inserted" (el) {// 可以对 el 标签,扩展额外功能el.focus()}})
局部注册
//在Vue组件的配置项中directives: {"指令名": {inserted () {// 可以对 el 标签,扩展额外功能el.focus()}}}
使用指令
注意:在使用指令的时候,一定要先注册,再使用,否则会报错
使用指令语法: v-指令名。如:
注册指令时不用加v-前缀,但使用时一定要加v-前缀
指令中的配置项介绍
inserted:被绑定元素插入父节点时调用的钩子函数
el:使用指令的那个DOM元素
代码示例
需求:当页面加载时,让元素获取焦点(autofocus在safari浏览器有兼容性)
App.vue 局部注册
<template><div><h1>自定义指令</h1><input v-focus ref="inp" type="text"></div></template><script>export default {// mounted () {// this.$refs.inp.focus()// }// // 2. 局部注册指令directives: {// 指令名:指令的配置项focus: {inserted (el) {el.focus()}}}}</script><style></style>
main.js全局注册
import Vue from 'vue'import App from './App.vue'Vue.config.productionTip = false// // 1. 全局注册指令Vue.directive('focus', {// inserted 会在 指令所在的元素,被插入到页面中时触发inserted (el) {// el 就是指令所绑定的元素// console.log(el);el.focus()}})new Vue({render: h => h(App),}).$mount('#app')
自定义指令-指令的值
需求
实现一个 color 指令 - 传入不同的颜色, 给标签设置文字颜色
语法
1.在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值
<div v-color="color">我是内容</div>
2.通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数
directives: {color: {//1. inserted 提供的是元素被添加到页面中时的逻辑inserted (el, binding) {el.style.color = binding.value},// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑update (el, binding) {el.style.color = binding.value}}}
代码示例
App.vue
<template><div><h1 v-color="color1">指令的值1测试</h1><h1 v-color="color2">指令的值2测试</h1></div></template><script>export default {data () {return {color1: 'red',color2: 'orange'}},directives: {color: {// 1. inserted 提供的是元素被添加到页面中时的逻辑inserted (el, binding) {// console.log(el, binding.value);// binding.value 就是指令的值el.style.color = binding.value},// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑update (el, binding) {console.log('指令的值修改了');el.style.color = binding.value}}}}</script><style></style>
自定义指令-v-loading指令的封装
场景
实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态=> 用户体验不好
需求
封装一个 v-loading 指令,实现加载中的效果
分析
1.本质 loading效果就是一个蒙层,盖在了盒子上
2.数据请求中,开启loading状态,添加蒙层
3.数据请求完毕,关闭loading状态,移除蒙层
实现
1.准备一个 loading类,通过伪元素定位,设置宽高,实现蒙层
2.开启关闭 loading状态(添加移除蒙层),本质只需要添加移除类即可
3.结合自定义指令的语法进行封装复用
.loading:before {content: "";position: absolute;left: 0;top: 0;width: 100%;height: 100%;background: #fff url("./loading.gif") no-repeat center;}
准备代码
<template><div class="main"><div class="box" v-loading="isLoading"><ul><li v-for="item in list" :key="item.id" class="news"><div class="left"><div class="title">{{ item.title }}</div><div class="info"><span>{{ item.source }}</span><span>{{ item.time }}</span></div></div><div class="right"><img :src="item.img" alt=""></div></li></ul></div><div class="box2" v-loading="isLoading2"></div></div></template><script>// 安装axios => yarn add axiosimport axios from 'axios'// 接口地址:/api/news// 请求方式:getexport default {data () {return {list: [],isLoading: true,isLoading2: true}},async created () {// 1. 发送请求获取数据const res = await axios.get('/api/news')setTimeout(() => {// 2. 更新到 list 中,用于页面渲染 v-forthis.list = res.data.datathis.isLoading = false}, 2000)},directives: {loading: {inserted (el, binding) {binding.value ? el.classList.add('loading') : el.classList.remove('loading')},update (el, binding) {binding.value ? el.classList.add('loading') : el.classList.remove('loading')}}}}</script><style>.loading:before {content: '';position: absolute;left: 0;top: 0;width: 100%;height: 100%;background: #fff url('./loading.gif') no-repeat center;}.box2 {width: 400px;height: 400px;border: 2px solid #000;position: relative;}.box {width: 800px;min-height: 500px;border: 3px solid orange;border-radius: 5px;position: relative;}.news {display: flex;height: 120px;width: 600px;margin: 0 auto;padding: 20px 0;cursor: pointer;}.news .left {flex: 1;display: flex;flex-direction: column;justify-content: space-between;padding-right: 10px;}.news .left .title {font-size: 20px;}.news .left .info {color: #999999;}.news .left .info span {margin-right: 20px;}.news .right {width: 160px;height: 120px;}.news .right img {width: 100%;height: 100%;object-fit: cover;}</style>
插槽-默认插槽
作用
让组件内部的一些结构支持自定义
需求
将需要多次显示的对话框,封装成一个组件
问题
组件的内容部分,不希望写死,希望能使用的时候自定义。怎么办
插槽的基本语法
组件内需要定制的结构部分,改用****占位使用组件时, ****标签内部, 传入结构替换slot给插槽传入内容时,可以传入纯文本、html标签、组件代码示例
MyDialog.vue
<template><div class="dialog"><div class="dialog-header"><h3>友情提示</h3><span class="close">✖️</span></div><div class="dialog-content"><!-- 1. 在需要定制的位置,使用slot占位 --><slot></slot></div><div class="dialog-footer"><button>取消</button><button>确认</button></div></div></template><script>export default {data () {return {}}}</script><style scoped>* {margin: 0;padding: 0;}.dialog {width: 470px;height: 230px;padding: 0 25px;background-color: #ffffff;margin: 40px auto;border-radius: 5px;}.dialog-header {height: 70px;line-height: 70px;font-size: 20px;border-bottom: 1px solid #ccc;position: relative;}.dialog-header .close {position: absolute;right: 0px;top: 0px;cursor: pointer;}.dialog-content {height: 80px;font-size: 18px;padding: 15px 0;}.dialog-footer {display: flex;justify-content: flex-end;}.dialog-footer button {width: 65px;height: 35px;background-color: #ffffff;border: 1px solid #e1e3e9;cursor: pointer;outline: none;margin-left: 10px;border-radius: 3px;}.dialog-footer button:last-child {background-color: #007acc;color: #fff;}</style>
App.vue
<template><div><!-- 2. 在使用组件时,组件标签内填入内容 --><MyDialog><div>你确认要删除么</div></MyDialog><MyDialog><p>你确认要退出么</p></MyDialog></div></template><script>import MyDialog from './components/MyDialog.vue'export default {data () {return {}},components: {MyDialog}}</script><style>body {background-color: #b3b3b3;}</style>
插槽-后备内容(默认值)
问题
通过插槽完成了内容的定制,传什么显示什么, 但是如果不传,则是空白
能否给插槽设置 默认显示内容 呢?
插槽的后备内容(默认值)
封装组件时,可以为预留的<slot>
插槽提供后备内容(默认内容)。
语法
在 标签内,放置内容, 作为默认显示内容
效果
外部使用组件时,不传东西,则slot会显示后备内容 外部使用组件时,传东西了,则slot整体会被换掉插槽-具名插槽
需求
一个组件内有多处结构,需要外部传入标签,进行定制
上面的弹框中有三处不同,但是默认插槽只能定制一个位置,这时候怎么办呢?
具名插槽语法
多个slot使用name属性区分名字template配合v-slot:名字来分发对应标签
v-slot的简写
v-slot写起来太长,vue给我们提供一个简单写法v-slot —> #
作用域插槽
插槽分类
默认插槽
具名插槽
插槽只有两种,作用域插槽不属于插槽的一种分类
作用
定义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>
代码示例
MyTable.vue
<template><table class="my-table"><thead><tr><th>序号</th><th>姓名</th><th>年纪</th><th>操作</th></tr></thead><tbody><tr><td>1</td><td>赵小云</td><td>19</td><td><button>查看 </button></td></tr><tr><td>1</td><td>张小花</td><td>19</td><td><button>查看 </button></td></tr><tr><td>1</td><td>孙大明</td><td>19</td><td><button>查看 </button></td></tr></tbody></table></template><script>export default {props: {data: Array}}</script><style scoped>.my-table {width: 450px;text-align: center;border: 1px solid #ccc;font-size: 24px;margin: 30px auto;}.my-table thead {background-color: #1f74ff;color: #fff;}.my-table thead th {font-weight: normal;}.my-table thead tr {line-height: 40px;}.my-table th,.my-table td {border-bottom: 1px solid #ccc;border-right: 1px solid #ccc;}.my-table td:last-child {border-right: none;}.my-table tr:last-child td {border-bottom: none;}.my-table button {width: 65px;height: 35px;font-size: 18px;border: 1px solid #ccc;outline: none;border-radius: 3px;cursor: pointer;background-color: #ffffff;margin-left: 5px;}</style>
App.vue
<template><div><MyTable :data="list"></MyTable><MyTable :data="list2"></MyTable></div></template><script>import MyTable from './components/MyTable.vue'export default {data () {return {list: [{id: 1, name: '张小花', age: 18 },{id: 2, name: '孙大明', age: 19 },{id: 3, name: '刘德忠', age: 17 },],list2: [{id: 1, name: '赵小云', age: 18 },{id: 2, name: '刘蓓蓓', age: 19 },{id: 3, name: '姜肖泰', age: 17 },]}},components: {MyTable}}</script>
单页应用程序介绍
概念
单页应用程序:SPA【Single Page Application】
是指所有的功能都在一个html页面上实现
具体示例
单页应用网站: 网易云音乐 /
多页应用网站:京东 /
单页应用 VS 多页面应用
单页应用类网站:系统类网站 / 内部网站 / 文档类网站 / 移动端站点
多页应用类网站:公司官网 / 电商类网站
路由介绍
思考
单页面应用程序,之所以开发效率高,性能好,用户体验好
最大的原因就是:页面按需更新
比如当点击【发现音乐】和【关注】时,只是更新下面部分内容,对于头部是不更新的
要按需更新,首先就需要明确:访问路径和组件的对应关系!
访问路径 和 组件的对应关系如何确定呢?路由
路由的介绍
生活中的路由:设备和ip的映射关系
Vue中的路由:路径和组件的映射关系
路由的基本使用
目标
认识插件 VueRouter,掌握 VueRouter 的基本使用步骤
作用
修改地址栏路径时,切换显示匹配的组件
说明
Vue 官方的一个路由插件,是一个第三方包
官网
https://v3./zh/
VueRouter的使用(5+2)
固定5个固定的步骤(不用死背,熟能生巧)
下载 VueRouter 模块到当前工程,版本3.6.5
yarn add vue-router@3.6.5
main.js中引入VueRouter
import VueRouter from 'vue-router'
安装注册
Vue.use(VueRouter)
创建路由对象
const router = new VueRouter()
注入,将路由对象注入到new Vue实例中,建立关联
new Vue({render: h => h(App),router:router}).$mount('#app')
当我们配置完以上5步之后 就可以看到浏览器地址栏中的路由 变成了 /#/的形式。表示项目的路由已经被Vue-Router管理了
代码示例
main.js
// 路由的使用步骤 5 + 2// 5个基础步骤// 1. 下载 v3.6.5// yarn add vue-router@3.6.5// 2. 引入// 3. 安装注册 Vue.use(Vue插件)// 4. 创建路由对象// 5. 注入到new Vue中,建立关联import VueRouter from 'vue-router'Vue.use(VueRouter) // VueRouter插件初始化const router = new VueRouter()new Vue({render: h => h(App),router}).$mount('#app')
两个核心步骤
创建需要的组件 (views目录),配置路由规则 配置导航,配置路由出口(路径匹配的组件显示的位置)App.vue
<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>
组件的存放目录问题
注意:.vue文件本质无区别
组件分类
.vue文件分为2类,都是.vue文件(本质无区别)
页面组件 (配置路由规则时使用的组件)复用组件(多个组件中都使用到的组件)
存放目录
分类开来的目的就是为了更易维护
src/views文件夹
页面组件 - 页面展示 - 配合路由用
src/components文件夹
复用组件 - 展示数据 - 常用于复用
路由的封装抽离
问题:所有的路由配置都在main.js中合适吗?
目标:将路由模块抽离出来。 好处:拆分模块,利于维护
路径简写:
脚手架环境下@指代src目录,可以用于快速引入组件
声明式导航-导航链接
需求
实现导航高亮效果
如果使用a标签进行跳转的话,需要给当前跳转的导航加样式,同时要移除上一个a标签的样式,太麻烦!!!
解决方案
vue-router 提供了一个全局组件 router-link (取代 a 标签)
能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #能高亮,默认就会提供高亮类名,可以直接设置高亮样式
语法:<router-link to="path的值">
发现音乐</router-link>
发现音乐 我的音乐 朋友
<div><div class="footer_wrap"><router-link to="/find">发现音乐</router-link><router-link to="/my">我的音乐</router-link><router-link to="/friend">朋友</router-link></div><div class="top"><!-- 路由出口 → 匹配的组件所展示的位置 --><router-view></router-view></div></div>
通过router-link自带的两个样式进行高亮
使用router-link跳转后,我们发现。当前点击的链接默认加了两个class的值router-link-exact-active
和router-link-active
我们可以给任意一个class属性添加高亮样式即可实现功能
声明式导航-两个类名
当我们使用跳转时,自动给当前导航加了两个类名
router-link-active
模糊匹配(用的多)
to=“/my” 可以匹配 /my /my/a /my/b …
只要是以/my开头的路径 都可以和 to="/my"匹配到
router-link-exact-active
精确匹配
to=“/my” 仅可以匹配 /my
在地址栏中输入二级路由查看类名的添加
声明式导航-自定义类名(了解)
1.问题
router-link的两个高亮类名 太长了,我们希望能定制怎么办
解决方案
我们可以在创建路由对象时,额外配置两个配置项即可。linkActiveClass
和linkExactActiveClass
const router = new VueRouter({routes: [...],linkActiveClass: "类名1",linkExactActiveClass: "类名2"})
代码演示
// 创建了一个路由对象const router = new VueRouter({routes: [...], linkActiveClass: 'active', // 配置模糊匹配的类名linkExactActiveClass: 'exact-active' // 配置精确匹配的类名})
声明式导航-查询参数传参
目标
在跳转路由时,进行传参
比如:现在我们在搜索页点击了热门搜索链接,跳转到详情页,需要把点击的内容带到详情页,改怎么办呢?
跳转传参
我们可以通过两种方式,在跳转的时候把所需要的参数传到其他页面中
查询参数传参动态路由传参
查询参数传参
如何传参?
<router-link to="/path?参数名=值"></router-link>
如何接受参数
固定用法:$router.query.参数名
代码演示
App.vue
<template><div id="app"><div class="link"><router-link to="/home">首页</router-link><router-link to="/search">搜索页</router-link></div><router-view></router-view></div></template><script>export default {};</script><style scoped>.link {height: 50px;line-height: 50px;background-color: #495150;display: flex;margin: -8px -8px 0 -8px;margin-bottom: 50px;}.link a {display: block;text-decoration: none;background-color: #ad2a26;width: 100px;text-align: center;margin-right: 5px;color: #fff;border-radius: 5px;}</style>
Home.vue
<template><div class="home"><div class="logo-box"></div><div class="search-box"><input type="text"><button>搜索一下</button></div><div class="hot-link">热门搜索:<router-link to="/search?key=黑马程序员">黑马程序员</router-link><router-link to="/search?key=前端培训">前端培训</router-link><router-link to="/search?key=如何成为前端大牛">如何成为前端大牛</router-link></div></div></template><script>export default {name: 'FindMusic'}</script><style>.logo-box {height: 150px;background: url('@/assets/logo.jpeg') no-repeat center;}.search-box {display: flex;justify-content: center;}.search-box input {width: 400px;height: 30px;line-height: 30px;border: 2px solid #c4c7ce;border-radius: 4px 0 0 4px;outline: none;}.search-box input:focus {border: 2px solid #ad2a26;}.search-box button {width: 100px;height: 36px;border: none;background-color: #ad2a26;color: #fff;position: relative;left: -2px;border-radius: 0 4px 4px 0;}.hot-link {width: 508px;height: 60px;line-height: 60px;margin: 0 auto;}.hot-link a {margin: 0 5px;}</style>
Search.vue
<template><div class="search"><p>搜索关键字: {{$route.query.key }} </p><p>搜索结果: </p><ul><li>.............</li><li>.............</li><li>.............</li><li>.............</li></ul></div></template><script>export default {name: 'MyFriend',created () {// 在created中,获取路由参数// this.$route.query.参数名 获取console.log(this.$route.query.key);}}</script><style>.search {width: 400px;height: 240px;padding: 0 20px;margin: 0 auto;border: 2px solid #c4c7ce;border-radius: 5px;}</style>
router/index.js
import Home from '@/views/Home'import Search from '@/views/Search'import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter) // VueRouter插件初始化// 创建了一个路由对象const router = new VueRouter({routes: [{path: '/home', component: Home },{path: '/search', component: Search }]})export default router
main.js
import Vue from 'vue'import App from './App.vue'import router from './router/index'Vue.config.productionTip = falsenew Vue({render: h => h(App),router}).$mount('#app')
声明式导航-动态路由传参
动态路由传参方式
配置动态路由
动态路由后面的参数可以随便起名,但要有语义
const router = new VueRouter({routes: [...,{path: '/search/:words', component: Search }]})
to="/search/黑马程序员"
this.$route.params.words
配置导航链接
to=“/path/参数值”
对应页面组件接受参数
$route.params.参数名
params后面的参数名要和动态路由配置的参数保持一致
查询参数传参 VS 动态路由传参
查询参数传参 (比较适合传多个参数)
跳转:to=“/path?参数名=值&参数名2=值”获取:$route.query.参数名
动态路由传参 (优雅简洁,传单个参数比较方便)
配置动态路由:path: “/path/:参数名”跳转:to=“/path/参数值”获取:$route.params.参数名
注意:动态路由也可以传多个参数,但一般只传一个
动态路由参数的可选符(了解)
问题
配了路由 path:“/search/:words” 为什么按下面步骤操作,会未匹配到组件,显示空白?
原因
/search/:words 表示,必须要传参数。如果不传参数,也希望匹配,可以加个可选符"?"
const router = new VueRouter({routes: [...{path: '/search/:words?', component: Search }]})
Vue路由-重定向
问题
网页打开时, url 默认是 / 路径,未匹配到组件时,会出现空白
解决方案
重定向→ 匹配 / 后, 强制跳转 /home 路径
语法
{path: 匹配路径, redirect: 重定向到的路径 },比如:{path:'/' ,redirect:'/home' }
代码演示
const router = new VueRouter({routes: [{path: '/', redirect: '/home'},...]})
Vue路由-404
作用
当路径找不到匹配时,给个提示页面
位置
404的路由,虽然配置在任何一个位置都可以,但一般都配置在其他路由规则的最后面
语法
path: “*” (任意路径) – 前面不匹配就命中最后这个
import NotFind from '@/views/NotFind'const router = new VueRouter({routes: [...{path: '*', component: NotFind } //最后一个]})
代码示例
NotFound.vue
<template><div><h1>404 Not Found</h1></div></template><script>export default {}</script><style></style>
router/index.js
...import NotFound from '@/views/NotFound'...// 创建了一个路由对象const router = new VueRouter({routes: [...{path: '*', component: NotFound }]})export default router
Vue路由-模式设置
问题
路由的路径看起来不自然, 有#,能否切成真正路径形式?
hash路由(默认) 例如: http://localhost:8080/#/homehistory路由(常用) 例如: http://localhost:8080/home (以后上线需要服务器端支持,开发环境webpack给规避掉了history模式的问题)
语法
const router = new VueRouter({mode:'histroy', //默认是hashroutes:[]})
编程式导航-两种路由跳转方式
问题
点击按钮跳转如何实现?
方案
编程式导航:用JS代码来进行跳转
语法
两种语法:
path 路径跳转 (简易方便)name 命名路由跳转 (适合 path 路径长的场景)
path路径跳转语法
特点:简易方便
//简单写法this.$router.push('路由路径')//完整写法this.$router.push({path: '路由路径'})
代码演示 path跳转方式
name命名路由跳转
特点:适合 path 路径长的场景
语法:
路由规则,必须配置name配置项
{name: '路由名', path: '/path/xxx', component: XXX },
通过name来进行跳转
this.$router.push({name: '路由名'})
代码演示通过name命名路由跳转
编程式导航-path路径跳转传参
问题
点击搜索按钮,跳转需要把文本框中输入的内容传到下一个页面如何实现?
两种传参方式
1.查询参数
2.动态路由传参
传参
两种跳转方式,对于两种传参方式都支持:
① path 路径跳转传参
② name 命名路由跳转传参
path路径跳转传参(query传参)
//简单写法this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')//完整写法this.$router.push({path: '/路径',query: {参数名1: '参数值1',参数名2: '参数值2'}})
接受参数的方式依然是:$route.query.参数名
path路径跳转传参(动态路由传参)
//简单写法this.$router.push('/路径/参数值')//完整写法this.$router.push({path: '/路径/参数值'})
接受参数的方式依然是:$route.params.参数值
**注意:**path不能配合params使用
编程式导航-name命名路由传参
name 命名路由跳转传参 (query传参)
this.$router.push({name: '路由名字',query: {参数名1: '参数值1',参数名2: '参数值2'}})
name 命名路由跳转传参 (动态路由传参)
this.$router.push({name: '路由名字',params: {参数名: '参数值',}})
总结
编程式导航,如何跳转传参?
1.path路径跳转
query传参
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')this.$router.push({path: '/路径',query: {参数名1: '参数值1',参数名2: '参数值2'}})
动态路由传参
this.$router.push('/路径/参数值')this.$router.push({path: '/路径/参数值'})
2.name命名路由跳转
query传参
this.$router.push({name: '路由名字',query: {参数名1: '参数值1',参数名2: '参数值2'}})
动态路由传参 (需要配动态路由)
this.$router.push({name: '路由名字',params: {参数名: '参数值',}})
如何配置二级路由
const router = new VueRouter({routes: [{path:'/',component:Layout,//通过children配置项,可以配置嵌套子路由//1.在children配置项中,配置规则//2.准备二级路由出口children:[{path:'/article',component:Article},{path:'/collect',component:Collect},{path:'/Like',component:Like},{path:'/user',component:User}]},{path:'/detail',component:ArticleDetail}]})
## keep-alive### 问题从面经列表 点到 详情页,又点返回,数据重新加载了 → **希望回到原来的位置**![](C:\Users\Geng\Documents\Interview Notes\图片资料\Vue\1682578630060.png)### 原因当路由被**跳转**后,原来所看到的组件就**被销毁**了(会执行组件内的beforeDestroy和destroyed生命周期钩子),**重新返回**后组件又被**重新创建**了(会执行组件内的beforeCreate,created,beforeMount,Mounted生命周期钩子),**所以数据被加载了**### 什么是keep-alivekeep-alive 是 Vue 的内置组件,当它包裹动态组件时,**会缓存不活动的组件实例,而不是销毁**它们。keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件中。**优点:**在组件切换过程中把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性。App.vue```js<template><div class="h5-wrapper"><keep-alive><router-view></router-view></keep-alive></div></template>
问题:
缓存了所有被切换的组件
keep-alive的三个属性
① include : 组件名数组,只有匹配的组件会被缓存
② exclude : 组件名数组,任何匹配的组件都不会被缓存
③ max : 最多可以缓存多少组件实例
App.vue
<template><div class="h5-wrapper"><keep-alive :include="['LayoutPage']"><router-view></router-view></keep-alive></div></template>
6.额外的两个生命周期钩子
keep-alive的使用会触发两个生命周期函数
activated当组件被激活(使用)的时候触发 → 进入这个页面的时候触发
deactivated当组件不被使用的时候触发 → 离开这个页面的时候触发
组件缓存后就不会执行组件的created, mounted, destroyed等钩子了
所以其提供了actived 和deactived钩子,帮我们实现业务需求。
VueCli 自定义创建项目
1.安装脚手架 (已安装)
npm i @vue/cli -g
2.创建项目
vue create hm-exp-mobile
选项
Vue CLI v5.0.8? Please pick a preset:Default ([Vue 3] babel, eslint)Default ([Vue 2] babel, eslint)> Manually select features选自定义
手动选择功能 选择vue的版本
3.x> 2.x
是否使用history模式 选择css预处理 选择eslint的风格 (eslint 代码规范的检验工具,检验代码是否符合规范)比如:const age = 18; => 报错!多加了分号!后面有工具,一保存,全部格式化成最规范的样子 选择校验的时机 (直接回车)
选择配置文件的生成方式 (直接回车)
是否保存预设,下次直接使用? => 不保存,输入 N
等待安装,项目初始化完成 启动项目
npm run serve
ESlint代码规范及手动修复
代码规范:一套写代码的约定规则。例如:赋值符号的左右是否需要空格?一句结束是否是要加;?…
没有规矩不成方圆
ESLint:是一个代码检查工具,用来检查你的代码是否符合指定的规则(你和你的团队可以自行约定一套规则)。在创建项目时,我们使用的是 JavaScript Standard Style 代码风格的规则。
JavaScript Standard Style 规范说明
建议把:/rules-zhcn.html 看一遍,然后在写的时候, 遇到错误就查询解决。
下面是这份规则中的一小部分:
字符串使用单引号– 需要转义的地方除外无分号– 这没什么不好。不骗你!关键字后加空格if (condition) { ... }
函数名后加空格function name (arg) { ... }
坚持使用全等===
摒弃==
一但在需要检查null || undefined
时可以使用obj == null
…
代码规范错误
如果你的代码不符合standard的要求,eslint会跳出来刀子嘴,豆腐心地提示你。
下面我们在main.js中随意做一些改动:添加一些空行,空格。
import Vue from 'vue'import App from './App.vue'import './styles/index.less'import router from './router'Vue.config.productionTip = falsenew Vue ( {render: h => h(App),router}).$mount('#app')
按下保存代码之后:
你将会看在控制台中输出如下错误:
eslint 是来帮助你的。心态要好,有错,就改。
手动修正
根据错误提示来一项一项手动修正。
如果你不认识命令行中的语法报错是什么意思,你可以根据错误代码(func-call-spacing, space-in-parens,…)去 ESLint 规则列表中查找其具体含义。
打开 ESLint 规则表,使用页面搜索(Ctrl + F)这个代码,查找对该规则的一个释义。
通过eslint插件来实现自动修正
eslint会自动高亮错误显示通过配置,eslint会自动帮助我们修复错误如何安装 如何配置
// 当保存的时候,eslint自动帮我们修复错误"editor.codeActionsOnSave": {"source.fixAll": true},// 保存代码,不自动格式化"editor.formatOnSave": false
注意:eslint的配置文件必须在根目录下,这个插件才能才能生效。打开项目必须以根目录打开,一次打开一个项目注意:使用了eslint校验之后,把vscode带的那些格式化工具全禁用了 Beatify
settings.json 参考
{"window.zoomLevel": 2,"workbench.iconTheme": "vscode-icons","editor.tabSize": 2,"emmet.triggerExpansionOnTab": true,// 当保存的时候,eslint自动帮我们修复错误"editor.codeActionsOnSave": {"source.fixAll": true},// 保存代码,不自动格式化"editor.formatOnSave": false}
Vuex 概述
目标:明确Vuex是什么,应用场景以及优势
是什么
Vuex 是一个 Vue 的 状态管理工具,状态就是数据。
大白话:Vuex 是一个插件,可以帮我们管理 Vue 通用的数据 (多组件共享的数据)。例如:购物车数据 个人信息数
使用场景
某个状态 在 很多个组件 来使用 (个人信息)
多个组件 共同维护 一份数据 (购物车)
优势
共同维护一份数据,数据集中化管理响应式变化操作简洁 (vuex提供了一些辅助函数)注意
官方原文:
不是所有的场景都适用于vuex,只有在必要的时候才使用vuex使用了vuex之后,会附加更多的框架中的概念进来,增加了项目的复杂度 (数据的操作更便捷,数据的流动更清晰)
Vuex就像《近视眼镜》, 你自然会知道什么时候需要用它~
多组件共享数据
目标:基于脚手架创建项目,构建 vuex 多组件数据共享环境
效果是三个组件共享一份数据:
任意一个组件都可以修改数据三个组件的数据是同步的
创建项目
vue create vuex-demo
创建三个组件, 目录如下
|-components|--Son1.vue|--Son2.vue|-App.vue
源代码如下
App.vue
在入口组件中引入 Son1 和 Son2 这两个子组件
<template><div id="app"><h1>根组件</h1><input type="text"><Son1></Son1><hr><Son2></Son2></div></template><script>import Son1 from './components/Son1.vue'import Son2 from './components/Son2.vue'export default {name: 'app',data: function () {return {}},components: {Son1,Son2}}</script><style>#app {width: 600px;margin: 20px auto;border: 3px solid #ccc;border-radius: 3px;padding: 10px;}</style>
main.js
import Vue from 'vue'import App from './App.vue'Vue.config.productionTip = falsenew Vue({render: h => h(App)}).$mount('#app')
Son1.vue
<template><div class="box"><h2>Son1 子组件</h2>从vuex中获取的值: <label></label><br><button>值 + 1</button></div></template><script>export default {name: 'Son1Com'}</script><style lang="css" scoped>.box{border: 3px solid #ccc;width: 400px;padding: 10px;margin: 20px;}h2 {margin-top: 10px;}</style>
Son2.vue
<template><div class="box"><h2>Son2 子组件</h2>从vuex中获取的值:<label></label><br /><button>值 - 1</button></div></template><script>export default {name: 'Son2Com'}</script><style lang="css" scoped>.box {border: 3px solid #ccc;width: 400px;padding: 10px;margin: 20px;}h2 {margin-top: 10px;}</style>
vuex 的使用 - 创建仓库
安装 vuex
安装vuex与vue-router类似,vuex是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。
yarn add vuex@3 或者 npm i vuex@3
新建store/index.js
专门存放 vuex
为了维护项目目录的整洁,在src目录下新建一个store目录其下放置一个index.js文件。 (和router/index.js
类似)
创建仓库store/index.js
// 导入 vueimport Vue from 'vue'// 导入 vueximport Vuex from 'vuex'// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化Vue.use(Vuex)// 创建仓库 storeconst store = new Vuex.Store()// 导出仓库export default store
在 main.js 中导入挂载到 Vue 实例上
import Vue from 'vue'import App from './App.vue'import store from './store'Vue.config.productionTip = falsenew Vue({render: h => h(App),store}).$mount('#app')
此刻起, 就成功创建了一个空仓库!!
测试打印Vuex
App.vue
created(){console.log(this.$store)}
核心概念 - state 状态
目标
明确如何给仓库 提供 数据,如何 使用 仓库的数据
提供数据
State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。
打开项目中的store.js文件,在state对象中可以添加我们要共享的数据。
// 创建仓库 storeconst store = new Vuex.Store({// state 状态, 即数据, 类似于vue组件中的data,// 区别:// 1.data 是组件自己的数据, // 2.state 中的数据整个vue项目的组件都能访问到state: {count: 101}})
访问Vuex中的数据
问题: 如何在组件中获取count?
通过$store直接访问 —> {{ $store.state.count }}通过辅助函数mapState 映射计算属性 —> {{ count }}
通过$store访问的语法
获取 store:1.Vue模板中获取 this.$store2.js文件中获取 import 导入 store模板中:{{$store.state.xxx }}组件逻辑中: this.$store.state.xxxJS模块中: store.state.xxx
代码实现
模板中使用
组件中可以使用$store获取到vuex中的store对象实例,可通过state属性属性获取count, 如下
<h1>state的数据 - {{ $store.state.count }}</h1>
组件逻辑中使用
将state属性定义在计算属性中 /zh/guide/state.html
<h1>state的数据 - {{count }}</h1>// 把state中数据,定义在组件内的计算属性中computed: {count () {return this.$store.state.count}}
js文件中使用
//main.jsimport store from "@/store"console.log(store.state.count)
每次都像这样一个个的提供计算属性, 太麻烦了,我们有没有简单的语法帮我们获取state中的值呢?
通过辅助函数 - mapState获取 state中的数据
mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便的用法
用法 :
第一步:导入mapState (mapState是vuex中的一个函数)
import {mapState } from 'vuex'
第二步:采用数组形式引入state属性
mapState(['count'])
上面代码的最终得到的是类似于
count () {return this.$store.state.count}
第三步:利用展开运算符将导出的状态映射给计算属性
computed: {...mapState(['count'])}
<div> state的数据:{{ count }}</div>
开启严格模式及Vuex的单项数据流
目标
明确 vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据
直接在组件中修改Vuex中state的值
Son1.vue
button @click="handleAdd">值 + 1</button>methods:{handleAdd (n) {// 错误代码(vue默认不会监测,监测需要成本)this.$store.state.count++// console.log(this.$store.state.count) },}
开启严格模式
通过strict: true
可以开启严格模式,开启严格模式后,直接修改state中的值会报错
state数据的修改只能通过mutations,并且mutations必须是同步的
核心概念-mutations
定义mutations
const store = new Vuex.Store({state: {count: 0},// 定义mutationsmutations: {}})
格式说明
mutations是一个对象,对象中存放修改state的方法
mutations: {// 方法里参数 第一个参数是当前store的state属性// payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷addCount (state) {state.count += 1}},
组件中提交 mutations
this.$mit('addCount')
带参数的 mutations
目标
掌握 mutations 传参语法
语法
看下面这个案例,每次点击不同的按钮,加的值都不同,每次都要定义不同的mutations处理吗?
提交 mutation 是可以传递参数的this.$mit('xxx', 参数)
提供mutation函数(带参数)
mutations: {...//payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷addCount (state, count) {state.count = count}},
提交mutation
handle ( ) {this.$mit('addCount', 10)}
小tips: 提交的参数只能是一个, 如果有多个参数要传, 可以传递一个对象
this.$mit('addCount', {count: 10})
练习-mutations的减法功能
步骤
代码实现
Son2.vue
<button @click="subCount(1)">值 - 1</button><button @click="subCount(5)">值 - 5</button><button @click="subCount(10)">值 - 10</button>export default {methods:{subCount (n) { this.$mit('addCount', n)},}}
store/index.js
mutations:{subCount (state, n) {state.count -= n},}
Vuex中的值和组件中的input双向绑定
目标
实时输入,实时更新,巩固 mutations 传参语法
实现步骤
代码实现
App.vue
<input :value="count" @input="handleInput" type="text">export default {methods: {handleInput (e) {// 1. 实时获取输入框的值const num = +e.target.value// 2. 提交mutation,调用mutation函数this.$mit('changeCount', num)}}}
store/index.js
mutations: {changeCount (state, newCount) {state.count = newCount}},
辅助函数- mapMutations
mapMutations和mapState很像,它把位于mutations中的方法提取了出来,我们可以将它导入
import {mapMutations } from 'vuex'methods: {...mapMutations(['addCount'])}
上面代码的含义是将mutations的方法导入了methods中,等价于
methods: {// commit(方法名, 载荷参数)addCount () {this.$mit('addCount')}}
此时,就可以直接通过this.addCount调用了
<button @click="addCount">值+1</button>
但是请注意: Vuex中mutations中要求不能写异步代码,如果有异步的ajax请求,应该放置在actions中
核心概念 - actions
state是存放数据的,mutations是同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化),
actions则负责进行异步操作
说明:mutations必须是同步的
需求: 一秒钟之后, 要给一个数 去修改state
定义actions
mutations: {changeCount (state, newCount) {state.count = newCount}}actions: {setAsyncCount (context, num) {// 一秒后, 给一个数, 去修改 numsetTimeout(() => {mit('changeCount', num)}, 1000)}},
组件中通过dispatch调用
setAsyncCount () {this.$store.dispatch('setAsyncCount', 666)}
mapActions
1.目标:掌握辅助函数 mapActions,映射方法
mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中
Son2.vue
import {mapActions } from 'vuex'methods: {...mapActions(['changeCountAction'])}//mapActions映射的代码 本质上是以下代码的写法//methods: {// changeCountAction (n) {// this.$store.dispatch('changeCountAction', n)// },//}
直接通过 this.方法 就可以调用
<button @click="changeCountAction(200)">+异步</button>
核心概念 - getters
除了state之外,有时我们还需要从state中筛选出符合条件的一些数据,这些数据是依赖state的,此时会用到getters
例如,state中定义了list,为1-10的数组,
state: {list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
组件中,需要显示所有大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它
定义getters
getters: {// getters函数的第一个参数是 state// 必须要有返回值filterList: state => state.list.filter(item => item > 5)}
使用getters
原始方式-$store
<div>{{ $store.getters.filterList }}</div>
辅助函数 - mapGetters
computed: {...mapGetters(['filterList'])}
<div>{{ filterList }}</div>
使用小结
核心概念 - module
目标
掌握核心概念 module 模块的创建
问题
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
这句话的意思是,如果把所有的状态都放在state中,当项目变得越来越大的时候,Vuex会变得越来越难以维护
由此,又有了Vuex的模块化
模块定义 - 准备 state
定义两个模块user和setting
user中管理用户的信息状态 userInfomodules/user.js
const state = {userInfo: {name: 'zs',age: 18}}const mutations = {}const actions = {}const getters = {}export default {state,mutations,actions,getters}
setting中管理项目应用的 主题色 theme,描述 desc,modules/setting.js
const state = {theme: 'dark'desc: '描述真呀真不错'}const mutations = {}const actions = {}const getters = {}export default {state,mutations,actions,getters}
在store/index.js
文件中的modules配置项中,注册这两个模块
import user from './modules/user'import setting from './modules/setting'const store = new Vuex.Store({modules:{user,setting}})
使用模块中的数据, 可以直接通过模块名访问$store.state.模块名.xxx
=>$store.state.setting.desc
也可以通过 mapState 映射
获取模块内的state数据
目标
掌握模块中 state 的访问语法
尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的 state 中,属性名就是模块名
使用模块中的数据
直接通过模块名访问 $store.state.模块名.xxx通过 mapState 映射: 默认根级别的映射 mapState([ ‘xxx’ ])子模块的映射 :mapState(‘模块名’, [‘xxx’]) - 需要开启命名空间namespaced:truemodules/user.js
const state = {userInfo: {name: 'zs',age: 18},myMsg: '我的数据'}const mutations = {updateMsg (state, msg) {state.myMsg = msg}}const actions = {}const getters = {}export default {namespaced: true,state,mutations,actions,getters}
代码示例
$store直接访问
$store.state.user.userInfo.name
mapState辅助函数访问
...mapState('user', ['userInfo']),...mapState('setting', ['theme', 'desc']),
获取模块内的getters数据
目标
掌握模块中 getters 的访问语
语法
使用模块中 getters 中的数据:
直接通过模块名访问$store.getters['模块名/xxx ']
通过 mapGetters 映射 默认根级别的映射mapGetters([ 'xxx' ])
子模块的映射mapGetters('模块名', ['xxx'])
- 需要开启命名空间
代码演示
modules/user.js
const getters = {// 分模块后,state指代子模块的stateUpperCaseName (state) {return state.userInfo.name.toUpperCase()}}
Son1.vue 直接访问getters
<!-- 测试访问模块中的getters - 原生 --><div>{{ $store.getters['user/UpperCaseName'] }}</div>
Son2.vue 通过命名空间访问
computed:{...mapGetters('user', ['UpperCaseName'])}
获取模块内的mutations方法
目标
掌握模块中 mutation 的调用语法
注意
默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
调用方式
直接通过 store 调用 $mit('模块名/xxx ', 额外参数)通过 mapMutations 映射 默认根级别的映射 mapMutations([ ‘xxx’ ])子模块的映射 mapMutations(‘模块名’, [‘xxx’]) - 需要开启命名空间代码实现
modules/user.js
const mutations = {setUser (state, newUserInfo) {state.userInfo = newUserInfo}}
modules/setting.js
const mutations = {setTheme (state, newTheme) {state.theme = newTheme}}
Son1.vue
<button @click="updateUser">更新个人信息</button> <button @click="updateTheme">更新主题色</button>export default {methods: {updateUser () {// $mit('模块名/mutation名', 额外传参)this.$mit('user/setUser', {name: 'xiaowang',age: 25})}, updateTheme () {this.$mit('setting/setTheme', 'pink')}}}
Son2.vue
<button @click="setUser({ name: 'xiaoli', age: 80 })">更新个人信息</button><button @click="setTheme('skyblue')">更新主题</button>methods:{// 分模块的映射...mapMutations('setting', ['setTheme']),...mapMutations('user', ['setUser']),}
获取模块内的actions方法
目标
掌握模块中 action 的调用语法 (同理 - 直接类比 mutation 即可)
注意
默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
调用语法
直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)通过 mapActions 映射 默认根级别的映射 mapActions([ ‘xxx’ ])子模块的映射 mapActions(‘模块名’, [‘xxx’]) - 需要开启命名空间代码实现
需求:
modules/user.js
const actions = {setUserSecond (context, newUserInfo) {// 将异步在action中进行封装setTimeout(() => {// 调用mutation context上下文,默认提交的就是自己模块的action和mit('setUser', newUserInfo)}, 1000)}}
Son1.vue 直接通过store调用
<button @click="updateUser2">一秒后更新信息</button>methods:{updateUser2 () {// 调用action dispatchthis.$store.dispatch('user/setUserSecond', {name: 'xiaohong',age: 28})},}
Son2.vue mapActions映射
<button @click="setUserSecond({ name: 'xiaoli', age: 80 })">一秒后更新信息</button>methods:{...mapActions('user', ['setUserSecond'])}
Vuex模块化的使用小结
直接使用
state --> $store.state.模块名.数据项名getters --> $store.getters[‘模块名/属性名’]mutations --> $mit(‘模块名/方法名’, 其他参数)actions --> $store.dispatch(‘模块名/方法名’, 其他参数)借助辅助方法使用
1.import { mapXxxx, mapXxx } from ‘vuex’
computed、methods: {
//…mapState、…mapGetters放computed中;
//…mapMutations、…mapActions放methods中;
…mapXxxx(‘模块名’, [‘数据项|方法’]),
…mapXxxx(‘模块名’, { 新的名字: 原来的名字 }),
}
2.组件中直接使用 属性{{ age }}
或 方法@click="updateAge(2)"