你确认要退出本系统么?
-
{{ index + 1 }}
目录:
1. Vue 快速上手
vue概念/创建实例/插值表达式/响应式特性/开发者工具
2. Vue 指令
v-html/v-show/v-if/v-else/v-on/v-bind/v-for/v-model
3. 综合案例-小黑记事本
列表渲染/删除功能/添加功能/底部统计/清空
概念:Vue 是一个用于构建用户界面的渐进式框架
1. 构建用户界面:基于数据渲染 出用户看到的页面。
2. 渐进式: 循序渐进 (学一点用一点)
Vue的两种使用方式:
Vue 核心包开发
场景:局部模块改造
Vue 核心包 & Vue插件 工程化开发
场景:整体开发
3. 框架:一套完整的项目解决方案
优点:大大提升开发效率 (70%)
缺点:需要理解记忆规则 -> 官网
核心步骤:
1. 准备容器
2. 引包 (官网) - 开发版本 / 生产版本
3. 创建 Vue 实例 new Vue()
4. 指定配置项 -> 渲染数据(哪个容器,哪个数据)
+ el 指定容器
+ data 提供数据
引包:
Vue2 网址:v2.cn.vuejs.org
{{msg}}
{{count}}
插值表达式是一种 Vue 的模板语法
{{ msg }}
data: {
msg: 'Hello 黑马'
}
1. 作用:利用表达式进行插值,渲染到页面中
表达式:是可以被求值的代码,JS 引擎会将其计算出一个结果。
money + 100
money - 100
money * 10
money / 10
price >= 100 ? '真贵' : '还行'
obj.name
arr.name
arr[0]
fn()
obj.fn()
2. 语法: {{ 表达式 }}
{{ title }}
{{ nickname.toUpperCase }}
{{ age >= 18? '成年' : '未成年' }}
3. 注意点:
使用的数据必须存在
支持的是表达式,而非语句,比如:if、for...
不能在标签属性中使用 {{ }} 插值
{{msg}}
{{msg.toUpperCase()}}
{{msg + 'Nice to meet you'}}
{{ age >= 18 ? '成年' : '未成年' }}
我们已经掌握了基础的模板渲染,其实除了基本的模板渲染,Vue背后还做了大量工作。
比如:数据的响应式处理
响应式:数据变化,视图自动更新
{{ msg }}
Vue核心特征:响应式
数据改变,试图会自动更新
安装 Vue开发者工具:装插件调试 Vue 应用
(1) 通过谷歌应用商店安装 (国外网站)
(2) 极简插件:搜索 vue (选择第一个) ->下载 -> 开发者模式 -> 拖拽安装 -> 插件详情允许访问文件
极简插件_Chrome扩展插件商店_优质crx应用下载
(3) 打开 Vue 运行的页面,调试工具中 Vue 栏,即可查看修改数据,进行调试。
开发者模式
插件详情允许访问文件
Vue 会根据不同的 [ 指令 ],针对标签实现不同的 [功能]。
指令:带有 v- 前缀 的特殊 标签属性
...
...
(一) 指令初始 v-html
作用:设置元素的 innerHTML
语法:v-html = "表达式"
div id="app">
(二) 指令 v-show 和 v-if
v-show
1. 作用:控制元素显示隐藏
2. 语法:
v-show = "表示式" 表达式值 true 显示,false 隐藏
3. 底层原理:切换 CSS 的 display: none 来控制显示隐藏
4. 场景:频繁切换显示隐藏的场景
v-if
1. 作用:控制元素显示隐藏 (条件渲染)
2. 语法:
3. 底层原理: 根据判断条件 控制元素的创建和移除
4. 场景: 要么显示,要么隐藏,不频繁切换的场景
v-if = "表达式" 表达式值 true 显示,false 隐藏
我是v-show控制的盒子
我是v-if控制的盒子
(三) 指令v-else和v-else-if
1. 作用:辅助 v-if 进行判断渲染
2. 语法:v-else v-else-if = "表达式"
3. 注意: 需要紧挨着 v-if 一起使用
<
div id="app">
性别:男
性别:女
成绩评定为A: 奖励电脑一台
成绩评定为B: 奖励周末郊游
成绩评定为C: 奖励零食礼包
成绩评定为D: 惩罚一周不能玩手机
(四) 指令 v-on
1. 作用: 注册事件 = 添加监听 + 提供处理逻辑
2. 语法:
方式1. v-on: 事件名 = "内联语句" // 内联语句:可执行代码
方式2. v-on: 事件名 = "methods中的函数名"
3. 简写:@事件名
button v-on:click="count++"> 按钮
button @click="count++"> 按钮
(1) 第一种事件注册方式
// v-on: 可以用 @ 替换
{{ count }}
// v-on: 可以用 @ 替换
(2) 第二种注册事件方式
button @click="fn">-
const app = new Vue({
el: '#app',
data: {
// 提供数据
count: 100
},
methods: {
// 提供处理逻辑函数
fn() {
console.log('提供逻辑代码')
}
}
})
黑马程序员
4. 注意:methods 函数内的 this 执行Vue 实例
(3) Vue指令v-on调用传参
小黑自动售货机
银行卡余额: {{ money }}
(五) 指令 v-bind
1. 作用: 动态设置 html的 标签属性 -> src、url、title ...
2. 语法: v-bind:属性名= "表达式"
3. 注意:简写形式::属性名="表达式"
案例-波仔的学习之旅
核心思路分析:
数组存储图片路径 -> [图片1,图片2,图片3,...]
准备下标 index,数组 [下标] -> v-bind设置 src展示图片 -> 修改下标切换图片
(六) 指令 v-for
1. 作用:基于数据循环,多次渲染整个元素 -> 数组、对象、数字 ...
2. 遍历数组语法:
v-for = "(item,index) in 数组"
item 每一项,index 下标
省略 index,: v-for = "item in 数组"
- {{ item }}
案例-小黑的书架
明确需求:
基本渲染 -> for
删除功能
小黑的书架
-
{{ item.name }}
{{item.author}}
(七) 指令 v-for的key
1. 语法: key属性 = "唯一标识"
2. 作用: 给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用。
v-for 的默认行为会尝试 原地修改数据 (就地复用)
3. 注意点:
key的值只能是字符串或数字类型
key的值必须具有唯一性
推荐使用 id作为 key (唯一),不推荐使用index作为 key (会变化,不对应)
(八) 指令 v-model
1. 作用:给表单元素使用,双向数据绑定 -> 可以快速获取 或 设置 表单元素内容。
1. 数据变化 -> 视图自动更新
2. 视图变化 -> 数据自动更新
2. 语法: v-model = '变量'
账户:
密码:
综合案例 - 小黑记事本
功能需求:
列表渲染
删除功能
添加功能
底部统计和清空
Vue代码
小黑记事本
-
{{ index + 1 }}
(一) 指令修饰符
通过 "."指明一些指令 后缀,不同后缀封装了不同的处理操作 -> 为了简化代码
1. 按键修饰
@keyup.enter -> 键盘回车监听
2. v-model修饰符
v-model.trim -> 去除首尾空格
v-model.number ->转数字
3.事件修饰符
@事件名.stop -> 组织冒泡
@事件名.prevent -> 阻止默认行为
@keyup.enter - > 监听键盘回车事件
(二) v-bind操作class
v-bind对于样式控制的增强
为了方便开发者进行样式控制,Vue 扩展了 v-bind的语法,可以针对 class类名和 style行内样式进行控制。
语法:
:class="对象/数组"
1. 对象 -> 键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类
适合场景:一个类名,来回切换
2. 数组 -> 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表
适合场景:批量添加或删除类
黑马程序员
黑马程序员
案例:京东秒杀 tab 导航高亮
核心思路:
基于数据动态渲染 tab -> v-for
准备下标记录高亮的是哪一个 tab -> activeIndex
style.css
(三) v-bind操作style
语法: :style = "样式对象"
使用场景:某个具体属性的动态设置
(四) v-model应用于其他表单元素
常见的表单元素都可以用 v-model 绑定关联 -> 快速获取 或 设置表单元素的值。
它会根据控件类型自动选取正确的方法来更新元素。
小黑学习网
性名:
是否单身:
性别:
男
女
所在城市:
自我描述:
概念:基于现在的数据,计算出来的新属性。依赖的数据变化,自动重新计算。
(一) 计算属性用法
语法:
声明在 computed 配置项中,一个计算属性对应一个函数
使用起来和普通属性一样使用 {{ 计算属性名 }}
小黑的礼物清单
名字
数量
{{ item.name }}
{{ item.num }}个
礼物总数:{{ totalCount }} 个
(二) 计算属性与methods方法
1. computed 计算属性:
作用:封装了一段对于数据的处理,求得一个结果。
语法:
1. 写在 computed配置项中
2. 作为属性,直接使用 -> this.计算属性 {{计算属性}}
2. methods 方法:
作用:给实例提供一个方法,调用以处理业务逻辑
语法:
1. 写在 methods配置项中
2. 作为方法,需要调用 -> this.方法名() {{方法名()}} @事件名 = "方法名"
3. computed 优势
在 methods 中也可以封装 computed属性,求得一个结果,不过,computed存在缓存特性.
4. 缓存特性 (提升性能):
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 -> 并再次缓存
小黑的礼物清单{{ totalCount() }}
名字
数量
{{ item.name }}
{{ item.num }}个
礼物总数:{{ totalCount() }} 个
(三) 计算属性完整写法
计算属性默认的简写,只能读取访问,不能 "修改"。
如果要"修改" -> 需要写计算属性的完整写法。
姓: +
名: =
{{ funllName }}
综合案例 - 成绩案例
编号
科目
成绩
操作
{{ index+1 }}
{{ item.subject }}
{{ item.score }}
删除
暂无数据
总分:{{ totalScore }}
平均分:{{ averageScore }}
科目:
分数:
(一) watch 监视器用法
作用:监视数据变化,执行一些 业务逻辑 或 异步操作。
语法:
简单写法 -> 简单类型数据,直接监视
完整写法 -> 添加额外配置项
翻译成的语言:
⌨️文档翻译
{{result}}
(二) watch 监视器完整写法
完整写法 -> 添加额外==配置项
deep:true 对复杂类型深度监视
immediate: true 初始化立即执行一次handler 方法
语法:
data: {
obj: {
words: '苹果',
lang: 'italy'
},
},
watch: {
// watch完整写法
数据属性名: {
deep: true, // 深度监视
handler(newValue) {
console.log(newValue)
}
}
}
翻译成的语言:
⌨️文档翻译
{{result}}
目录:
1. 生命周期
生命周期 & 生命周期四个阶段 / 生命周期钩子 / 生命周期案例
2.综合案例: 小黑记账清单
列表渲染(请求) /添加 /删除 /饼图渲染
3.工程化开发入门
工程化开发和脚手架 /项目运行流程 /组件化 /组件注册
4.综合案例: 小兔鲜首页
拆分模块-局部注册 /结构样式完善 /拆分组件-全局注册
思考:
1. 什么时候可以发送初始化渲染请求? (越早越好)
2. 什么时候可以操作dom? (至少dom得渲染出来)
Vue生命周期:一个Vue实例从创建到销毁的整个过程。
生命周期四个阶段:
1 创建 2 挂载 3 更新 4 销毁
Vue生命周期过程中,会自动运行一些函数,被称为 [生命周期钩子] —> 让开发者可以在 [特定阶段] 运行自己的代码。
{{ title }}
{{ count }}
(一) created 应用
created:响应式数据准备好了,可以开始发送初始化渲染请求。
-
{{ item.title }}
{{ item.source}}
{{ item.time }}
(二) mounted 应用
mounted:模块渲染完成,可以开始操作DOM了
要求:获取焦点
案例 - 小黑记账清单
编号
消费名称
消费价格
操作
{{ index + 1}}
{{ item.name }}
{{ item.price.toFixed(2) }}
删除
消费总计: {{ totalPrice.toFixed(2) }}
开发 Vue 的两种方式:
1. 核心包传统开发模式: 基于 html/css/js 文件,直接引入核心包,开发 Vue。
2. 工程化开发模式: 基于构建工具 (例如:webpack) 的环境中开发 Vue。
问题:
1. webpack 配置不简单
2. 雷同的基础配置
3. 缺乏统一标准
因此需要一个工具,生成标准化的配置 (Vue CLI)
基本介绍:
Vue CLI 是Vue官方提供的一个全局命令工具
可以帮助我们快速创建一个开发Vue项目的标准化基础架子。【集成了webpack配置】
好处:
开箱即用,零配置
内置babel等工具
标准化的webpack配置
使用步骤:
按 win+r ,输入 cmd,按回车
输入:npm config set registry https://registry.npm.taobao.org,按回车
全局安装(只需安装一次即可) 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)
1.项目目录介绍
虽然脚手架中的文件有很多,目前咱们只需认识三个文件即可
main.js 入口文件
// 作用:导入App.vue,基于App.vue创建结构渲染index.html
// 1.导入 Vue 核心包
import Vue from 'vue'
// 2. 导入App.vue根组件
import App from './App.vue'
// 提示:当前处于什么环境 (生产环境 / 开发环境)
Vue.config.productionTip = false // false 无提示
// 3. Vue 实例化,提供render方法 -> 基于 App.vue创建结构渲染index.html
new Vue({
// el: '#app',作用:和 $mount('选择器')作用一致,用于指定Vue所管理容器
//简化写法: render: h => h(App),
// 完整写法:
// 基于App创建元素结构,将App.vue渲染到 index.html容器中
render:(createElement) => {
return createElement(App)
}
}).$mount('#app')
2. App.vue App根组件
3. index.html 模板文件
2.运行流程
组件化: 一个页面 可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用 → 提升开发效率。
组件分类:普通组件、根组件。
比如:下面这个页面,可以把所有的代码都写在一个页面中,但是这样显得代码比较混乱,难易维护。咱们可以按模块进行组件划分
1.根组件介绍
整个应用最上层的组件,包裹所有普通小组件
2.组件是由三部分构成
语法高亮插件
三部分构成
template:结构 (有且只能一个根元素)
script: js逻辑
style: 样式 (可支持less,需要装包)
让组件支持less
(1) style标签,lang="less" 开启less功能
(2) 装包: yarn add less less-loader -D 或者npm i less less-loader -D
组件注册的两种方式:
1. 局部注册:只能在注册的组件内使用
+ 创建 .vue 文件 (三个组成部分)
+ 在使用的组件内导入并注册
2. 全局注册:所有组件内都能使用
(一) 局部注册
特点:
只能在注册的组件内使用
步骤:
创建.vue文件(三个组成部分)
在使用的组件内先导入再注册,最后使用
使用方式:
当成html标签使用即可 <组件名>组件名>
注意:
组件名规范 —> 大驼峰命名法, 如 HmHeader
语法:
// 导入需要注册的组件
import 组件对象 from '.vue文件路径'
import HmHeader from './components/HmHeader'
export default { // 局部注册
components: {
'组件名': 组件对象,
HmHeader:HmHeaer,
HmHeader
}
}
6.练习
在App组件中,完成以下练习。在App.vue中使用组件的方式完成下面布局
将下方组件写入到components文件夹内
我是hm-header
我是hm-main
如果 HmFooter + tab 出不来 (< HmFooter>< /HmFoooter>) -> 需要配置 vscode ,在设置中搜索 trigger on tab -> 勾选第一个
(二) 全局注册
特点:
全局注册的组件,在项目的 任何组件 中都能使用
步骤
创建.vue组件(三个组成部分)
main.js中进行全局注册
使用方式
当成HTML标签直接使用
<组件名>组件名>
注意
组件名规范 —> 大驼峰命名法, 如 HmHeader
5.语法
Vue.component('组件名', 组件对象)
例:
// 导入需要全局注册的组件,往代码的顶部编写(规范)
import HmButton from './components/HmButton'
// 进入全局注册 -> 在所有的组件范围内都能直接使用
Vue.component('HmButton', HmButton)
6.练习
在以下3个局部组件中是展示一个通用按钮
HmFooter.vue
main.js
import Vue from 'vue'
import App from './App.vue'
// 导入全局注册的组件
import HmButton from './components/HmButton'
vue.config.productionTip = false
// 进行全局注册
Vue.component('HmButton',HmButton)
new Vue({
render: (createElement) => {
// 基于App创建元素结构
return createElement(App)
}
})
HmHeader.vue
我是hm-header
HmMain.vue
我是hm-main
目录
1. 组件的三大组成部分 (结构 /样式 /逻辑)
scoped样式冲突 / data是一个函数
2. 组件通信
组件通信语法 /父传子 /子传父 /非父子(扩展)
3. 综合案例:小黑记事本(组件版)
拆分组件 /渲染 /添加 /删除 /统计 /清空 /持久化
4. 进阶语法
v-model原理 /v-model应用于组件 /sync修饰符 /ref 和 $refs /$nextTick
六、组件注意点说明
组件的三大组成部分
(一) scoped样式冲突
默认情况:
写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。
-
全局样式: 默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响
-
局部样式: 可以给组件加上scoped 属性,可以让样式只作用于当前组件
代码演示
BaseOne.vue
BaseOne
BaseTwo.vue
BaseTwo
App.vue
scoped原理
-
当前组件内标签都被添加data-v-hash值 的属性
-
css选择器都被添加 [data-v-hash值] 的属性选择器
最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
(二) data是一个函数
data为什么要写成函数
一个组件的 data 选项必须是一个函数。目的是为了:保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会重新执行一次data 函数,得到一个新对象。
代码演示
BaseCount.vue
{{ count }}
App.vue
组件通信
一、什么是组件通信?
组件通信,就是指 组件与组件 之间的 数据传递
-
组件的数据是独立的,无法直接访问其他组件的数据。
-
想使用其他组件的数据,就需要组件通信
组件之间如何通信
思考:
-
组件之间有哪些关系?
-
对应的组件通信方案有哪几类?
二、组件关系分类
-
父子关系
-
非父子关系
通信解决方案
三、父子通信流程
-
父组件通过 props 将数据传递给子组件
-
子组件利用 $emit 通知父组件修改更新
父向子传值步骤
-
给子组件以添加属性的方式传值
-
子组件内部通过props接收
-
模板中直接使用 props接收的值
父向子通信代码示例
父组件通过 props 将数据传递给子组件
父组件App.vue
我是APP组件
子组件Son.vue
我是Son组件 {{ title }}
子向父传值步骤
-
$emit触发事件,给父组件发送消息通知
-
父组件监听$emit触发的事件
-
提供处理函数,在函数的性参中获取传过来的参数
子向父通信代码示例
子组件利用 $emit 通知父组件,进行修改更新
父组件App.vue
我是APP组件
子组件 Son.vue:
我是Son组件 {{ title }}
四、props详解
1. Props 定义
组件上 注册的一些 自定义属性
2. Props 作用
向子组件传递数据
3. 特点
-
可以 传递 任意数量 的prop
-
可以 传递 任意类型 的prop
4. 代码演示
父组件App.vue
子组件UserInfo.vue
我是个人信息组件
姓名:
年龄:
是否单身:
座驾:
兴趣爱好:
props校验
1. 思考
组件的props可以乱传吗
2. 作用
为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误
3. 语法
-
类型校验
-
非空校验
-
默认值
-
自定义校验
4. 代码演示
App.vue
BaseProgress.vue
{{ w }}%
props校验完整写法
1.语法
props: {
校验的属性名: {
type: 类型, // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
},
2. 代码实例
3.注意
1.default 和 required 一般不同时写(因为当时必填项时,肯定是有值的)
2.default 后面如果是简单类型的值,可以直接写默认。如果是复杂类型的值,则需要以函数的形式return一个默认值
四、props&data、单向数据流
1.共同点
都可以给组件提供数据
2.区别
-
data 的数据是 自己 的 → 随便改
-
prop 的数据是 外部 的 → 不能直接改,要遵循 单向数据流
3.单向数据流:
父级props 的数据更新,会向下流动,影响子组件。这个数据流动是单向的
4.代码演示
App.vue
BaseCount.vue
{{ count }}
五、非父子通信-event bus 事件总线
1.作用
非父子组件之间,进行简易消息传递。(复杂场景→ Vuex)
2.步骤
-
创建一个都能访问的事件总线 (空Vue实例)—> utils/EventBus.js
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', '这是一个消息')
3.代码示例
EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
BaseA.vue(接受方)
我是A组件(接收方)
{{msg}}
BaseB.vue(发送方)
我是B组件(发布方)
App.vue
六、非父子通信-provide&inject
1.作用
跨层级共享数据
2.场景
3.语法
-
父组件 provide提供数据
export default {
provide () {
return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型【响应式】
userInfo: this.userInfo,
}
},
data () {
return {
color: 'pink', // 简单类型
userInfo: { // 复杂类型
name: 'zs',
age: 19
}
}
}
}
2.子/孙组件 inject获取数据
export default {
inject: ['color','userInfo'],
created () {
console.log(this.color, this.userInfo)
}
}
4.注意
-
provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)
-
子/孙组件通过inject获取的数据,不能在自身组件内修改
v-model原理
1.原理:
v-model本质上是一个语法糖。例如应用在输入框上,就是value属性 和 input事件 的合写
2.作用:
提供数据的双向绑定
-
数据变,视图跟着变 :value
-
视图变,数据跟着变 @input
3.注意
$event 用于在模板中,获取事件的形参
4.代码示例
5.v-model使用在其他表单元素上的原理
不同的表单元素, v-model 在底层的处理机制是不一样的。比如给checkbox 使用 v-model
底层处理的是 checked属性和change事件。
不过咱们只需要掌握应用在文本框上的原理即可
一、表单类组件封装
父传子: 数据 应该是父组件 props 传递 过来的,v-model 拆解 绑定数据
子传父: 监听输入,子传父传值给父组件修改
需求目标
实现 子组件 和 父组件 数据的双向绑定 (实现App.vue中的selectId和子组件选中的数据进行双向绑定)
代码演示
App.vue
:cityId="selectId"
@changeId = "selectId = $event"
BaseSelect.vue
二、v-model简化代码
1.目标:
父组件通过v-model 简化代码,实现子组件和父组件数据 双向绑定
2.如何简化:
v-model其实就是 :value和@input事件的简写
-
子组件:props通过value接收数据,事件触发 input
-
父组件:v-model直接绑定数据
3.代码示例
子组件
props: {
value: String
},
methods: {
handleChange (e) {
this.$emit('input', e.target.value)
}
}
父组件
.sync修饰符
1.作用
可以实现 子组件 与 父组件数据 的 双向绑定,简化代码
简单理解:子组件可以修改父组件传过来的props值
2.特点
prop属性名,可以自定义,非固定为 value
3.场景
封装弹框类的基础组件, visible属性 true显示 false隐藏
4.本质
.sync修饰符 就是 :属性名 和 @update:属性名 合写
5.语法
父组件
//.sync写法
--------------------------------------
//完整写法
子组件
props: {
visible: Boolean
},
this.$emit('update:visible', false)
6.代码示例
App.vue
BaseDialog.vue
温馨提示:
你确认要退出本系统么?
ref和$refs
1.作用
利用 ref 和 $refs 可以用于 获取 dom 元素 或 组件实例
2.特点:
查找范围 → 当前组件内(更精确稳定)
获取 dom:
-
目标标签 - 添加 ref 属性
我是渲染图表的容器
-
恰当时机,通过 this.$refs.xxx(ref名),获取目标标签
mounted(){
console.log(this.$refs.chartRef)
},
代码示例
App.vue
BaseChart.vue
子组件
获取组件:
-
目标组件 - 添加 ref 属性
-
恰当时机,通过 this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法
this.$refs.baseForm.组件方法()
代码实例:
App.vue
BaseForm.vue
异步更新 & $nextTick
1.需求
编辑标题, 编辑框自动聚焦
-
点击编辑,显示编辑框
-
让编辑框,立刻获取焦点
this.isShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点
2.代码实现
{{ title }}
3.问题
"显示之后",立刻获取焦点是不能成功的!
原因:Vue 是异步更新DOM (提升性能)
4.解决方案
$nextTick:等 DOM更新后,才会触发执行此方法里的函数体
语法: this.$nextTick(函数体)
this.$nextTick(() => {
this.$refs.inp.focus()
})
注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例
目录:
自定义指令
+ 基本语法(全局&局部注册) / 指令的值 / v-loading指令封装
插槽
+ 默认插槽 / 后备内容 / 具名插槽 / 作用域插槽
综合案例: 商品列表
MyTag 组件封装 / MyTable组件封装
路由入门
单页应用程序 / 路由概念 / VueRouter的基本使用 / 组件目录存放问题
自定义指令
自定义指令: 自己定义的指令,可以封装一些 dom 操作,扩展额外功能。
需求:当页面加载时,让元素将获得焦点(autofocus 在safari浏览器有兼容性)
操作 dom: dom元素.focus()
mounted() {
this.$refs.inp.focus()
}
一、全局注册 - 语法
Vue.directive('指令名',{
"inserted" (el) {
// 可以对 el标签,扩展额外功能
el.focus()
}
})
二、局部注册 - 语法
directives: {
"指令名": {
inserted () {
// 可以对 el 标签,扩展额外功能
el.focus()
}
}
}
使用:
全局注册
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 全局注册指令
Vue.directive('focus', {
inserted(el) {
el.focus();
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
自定义指令
局部注册
自定义指令
三、自定义指令 - 指令的值
需求:实现一个 color 指令 - 传入不同的颜色,给标签设置文字颜色
-
语法:在绑定指令时,可以通过 "等号" 的形式为指令绑定 具体的参数值
我是内容
-
通过 binding.value可以拿到指令值,指令值修改会 触发 updata 函数
directives: {
color: {
inserted (el,binding) {
el.style.color = binding.value
},
updata (el,binding) {
el.style.color = binding.value
}
}
}
指令的值1测试
指令的值2测试
四、自定义指令 v-loading
场景:实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 => 用户体验不好
需求:封装一个 v-loading 指令,实现加载中的效果
分析:
-
本质 loading 效果就是一个蒙层,盖在了盒子上
-
数据请求中,开启 loading状态,添加蒙层
-
数据请求完毕,关闭 loading状态,移除蒙层
实现:
-
准备一个 loading 类,通过伪元素定位,设置宽高,实现蒙层
-
开启关闭 loading 状态 (添加移除蒙层),本质只需要添加移除类即可
-
结合自定义指令的语法进行封装复用
.loading:before {
content: "",
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url("./loading.gif") no-repeat center;
}
App.vue
-
{{ item.title }}
{{ item.source }}
{{ item.time }}
插槽
插槽分类:
-
默认插槽:组件内定制一处结构
-
具名插槽:组件内定制多处结构
一、默认插槽
作用:让组件内部的一些结构支持自定义
需求:要在页面中显示一个对话框,封装成一个组件
问题:组件的内部部分,不希望写死,希望能使用的时候自定义。怎么办?
插槽基本语法:
-
组件内需要定制的结构部分,改用
占位
-
使用组件时,
标签内部,传入结构替换 slot
App.vue
你确认要删除吗?
你确认要退出吗?
MyDialog.vue
友情提示
✖️
二、后备内容 (默认内容)
通过插槽完成了内容的定制,传什么显示什么,但是如果不传,则是空白
能否给插槽设置 默认显示内容呢?
插槽后备内容:封装组件时,可以为预留的 < slot >插槽提供后备内容 (默认内容)。
-
语法:在< slot >标签内,放置内容,作为默认显示内容
-
效果:
-
外部使用组件时,不传东西,则 slot 会显示后备内容
-
外部使用组件时,传东西了,则 slot整体会被换掉
我是内容
APP.vue
MyDialog.vue
友情提示
✖️
我是后备内容
三、具名插槽
需求:一个组件内有多处结构,需要外部传入标签,进行定制
默认插槽:一个的定制位置
具名插槽语法:
-
多个 slot 使用 name属性 区分名字
-
template 配合 v-slot: 名字来分发对应标签
大标题
内容文本
-
v-slot:插槽名可以简化成 #插槽名
App.vue
大标题
内容文本
MyDialog.vue
四、作用域插槽
作用域插槽:定义 slot插槽 的同时,是可以传值的。给插槽上可以绑定数据,将来使用组件时可以用。
场景:封装表格组件
-
父传子,动态渲染表格内容
-
利用默认插槽,定制操作列
-
删除或查看都需要用到当前项的 id,属于组件内部的数据
通过作用域插槽传值绑定,进而使用
基本使用步骤:
-
给 slot 标签,以添加属性的方式传值
-
所有添加的属性,都会被收集到一个对象中
{ id: 3, msg: '测试文本'}
-
在 template中,通过 #插槽名 = "obj"
接收,默认插槽名为 default
App.vue
MyTable.vue
序号
姓名
年纪
操作
{{ index +1 }}
{{ item.name }}
{{ item.age }}
单页应用程序 SPA
-
单页面应用 (SPA): 所有功能在 一个html页面 上实现
-
具体示例:网易云音乐 网易云音乐
单页面与多页面的区别
开发分类
实现方式
页面性能
开发效率
用户体验
学习成本
首屏加载
SEO
单页
一个html页面
按需更新性能高
高
非常好
高
慢
差
多页
多个html页面
整页更新性能低
低
一般
中等
快
优
推荐:
单页面应用:系统类网站 / 文档类网站 / 移动端站点
多页面应用: 公司官网 / 电商类网站
思考:
单页面应用程序,之所以开发效率高,性能高,用户体验好
最大的原因就是:页面按需更新
要按需更新,首先就需要明确:访问路径 和 组件 的对应关系!
访问路径 和 组件的对应关系如何确定呢? 路由
路由
一、路由的介绍
生活中的路由:设备和ip的映射关系
Vue中的路由:路径和组件的映射关系
二、VueRouter的介绍
目标:认识插件 VueRouter,掌握 VueRouter的基本使用步骤
作用:修改地址栏路径时,切换显示匹配的组件
说明:Vue官方的一个路由插件,是一个第三方包
官网:Vue Router
三、VueRouter的使用 (5 + 2)
Vue2和vue3版本区别:
5个基本步骤 (固定)
-
下载:下载 VueRouter 模块到当前工程,版本 3.6.5
npm install [email protected]
-
引入
import VueRouter from 'vue-router'
-
安装注册
Vue.use(VueRouter)
-
创建路由对象
const router = new VueRouter()
-
注入,将路由对象注入到 new Vue 实例中,建立关联
new Vue({
render: h => h(App),
router
}).$mount('#app')
2个核心步骤
-
创建需要的组件 (views目录),配置路由规则
Find.vue My.vue Friend.vue
import Find from './views/Find.vue'
import My from './views/My.vue'
import Friend from './views/Friend.vue'
const router = new VueRouter({
routes: [
{ path: '/find',component: Find },
{ path: '/my', component: My},
{ path: '/friend', component: Friend},
]
})
-
配置导航,配置路由出口 (路径匹配的组件显示的位置)
APP.vue
main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import Find from './views/Find'
import My from './views/My'
import Friend from './views/Friend'
Vue.use(VueRouter)
const router = new VueRouter({
// routes: 路由规则集合
// route: 一条路由规则 { path: 路径, component: 组件}
routes: [
{ path: '/find', component: Find },
{ path: '/my', component: My },
{ path: '/friend', component: Friend },
]
})
Vue.config.productionTip = false
new Vue({
render: h => h(App),
// 全称: router: router
router
}).$mount('#app')
Find.vue
发现音乐
发现音乐
发现音乐
发现音乐
发现音乐
另外两个相同。
四、组件存放目录问题
注意:.vue文件本质无区别
路由相关的组件,为什么放在 views 目录呢?
组件分类:.vue 文件分 2类,页面组件 & 复用组件
分类开来更易维护
-
src/views文件夹
-
页面组件 - 页面展示 - 配合路由用
-
src/components文件夹
-
复用组件 - 展示数据 - 常用于复用
目录
路由进阶
1.路由模块封装
2.声明式导航 & 导航高亮 /精确匹配&模糊匹配 /自定义高亮类目
声明式导航传参 (查询参数传参 & 动态路由传参)
3.路由重定向 /路由404 /路由模式
4.编程式导航 / 编程式导航传参 (查询参数传参 & 动态路由传参)
综合案例: 面经基础版
一级路由 / 二级路由 / 导航高亮 / 请求渲染 / 路由传参 / 缓存组件
路由进阶
一、路由的封装抽离
问题:所有的路由配置都堆在 main.js 中合适么?
目标:将路由模块抽离出来。好处:拆分模块,利于维护
-
新建 router文件夹,然后新建 index.js
import Find from '@/views/Find' // @ 代表当前的src目录 (脚手架情况下)
import My from '@/views/My'
import Friend from '@/views/Friend'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
// routes: 路由规则集合
// route: 一条路由规则 { path: 路径, component: 组件}
routes: [
{ path: '/find', component: Find },
{ path: '/my', component: My },
{ path: '/friend', component: Friend },
]
})
export default router
-
在 mian.js中导入
import router from './router/index.js'
二、声明式导航 - 导航链接
需求:实现导航高亮效果
vue-router 提供了一个全局组件 router-link (取代 a 标签)
-
能跳转,配置 to 属性指定路径 (必须)。本质还是 a 标签,to 无需 #
-
能高亮,默认就会提供高亮类名,可以直接设置高亮样式
三、声明式导航 - 两个类名
说明:我们发现 router-link 自动给当前导航添加了两个高亮类名
-
router-link-active 模糊匹配 (用的多)
-
to="/my" 可以匹配 /my /my/a /my/b ...
-
router-link-exact-active 精确匹配
-
to="/my" 仅可以匹配 /my
说明:router-link 的两个高亮类名太长了,我们希望能定制怎么办?
const router = new VueRouter({
linkActiveClass: "类名1",
linkExactActiveClass: "类名2"
})
四、声明式导航 - 跳转传参
目标:在跳转路由时,进行传值
跳转传参的两种方式:
-
查询参数传参
-
动态路由传参
(一) 查询参数传参
-
语法格式如下:
-
to="/path?参数名=值"
-
对应页面组件接收传递过来的值
-
$route.query.参数名
App.vue
首页
搜索页
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
Home.vue
热门搜索:
黑马程序员
前端培训
如何成为前端大牛
search.vue
搜索关键字: {{ $route.query.key }}
搜索结果:
- .............
- .............
- .............
- .............
(二) 动态路由传参
-
配置动态路由
const router = new VueRouter({
routes: [
{ path: '/search/:words', component: Search }
],
})
-
配置导航链接
-
to="/path/参数值"
-
对应页面组件接收传递过来的值
-
$route.params.参数名
// Home.vue
热门搜索:
黑马程序员
前端培训
如何成为前端大牛
// Search.vue
搜索关键字: {{ $route.params.words }}
(三) 动态路由参数可选符
问题:配了路由 path: "/search/:words" 为什么按下面步骤操作,会未匹配到组件,显示空白?
原因: /search/:words 表示必须要传参数。如果不传参数,也希望匹配,可以加个可选符 "?"
const router = new VueRouter({
routes: [
{ path: '/search/:words?', component: Search }
],
})
五、Vue路由 - 重定向
问题:网页打开,url 默认是 / 路径,未匹配到组件时,会出现空白
说明:重定向 -> 匹配 path 后,强制跳转 path 路径
语法: { path: 匹配路径,redirect: 重定向到的路径}
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search }
],
})
六、Vue路由 - 404
作用:当路径找不到匹配时,给个提示页面
位置:配在路由最后
语法:path: "*" (任意路径) - 前面不匹配就命中最后这个
import NotFind from '@views/NotFind'
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search },
// 前面不匹配就命中最后这个
{ path: '*', component: NotFind}
],
})
// NotFind.vue
404 Page Not Found
七、Vue路由 - 模式设置
问题: 路由的路径看起来不自然,有 #,能否切成真正路径形式?
-
hash路径 (默认) 例如:http://localhost:8080/#/home
-
history路由 (常用) 例如: http://localhost:8080/home (以后上线需要服务器端支持)
const router = new VueRouter({
// 一旦采用 history模式,地址栏就没有#,需要后台配置访问规则
mode: "history"
})
编程式导航
一、基本跳转
问题:点击按钮跳转如何实现?
编程式导航:用 JS 代码来进行跳转
两种语法:
-
path 路径跳转
-
name 命名路由跳转
Path 路径跳转 (简单方便)
this.$router.push({
path: '路由路径'
})
this.$router.push({
path: '路由路径'
})
Home.vue
热门搜索:
黑马程序员
前端培训
如何成为前端大牛
name 命名路由跳转 (适合 path 路径长的场景)
this.$router.push({
name: '路由名'
})
{ name: '路由名', path: '/path/xxx', component: xxx },
index.js
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ name: 'search', path: '/search/:words?', component: Search },
{ path: '*', component: NotFind }
],
mode: "history"
})
Home.vue
热门搜索:
黑马程序员
前端培训
如何成为前端大牛
二、路由传参
问题:点击搜索按钮,跳转需要传参如何实现?
两种传参方式: 查询参数 + 动态路由传参
两种跳转方式,对于两种传参方式都支持:
-
path 路径跳转传参
-
name 命名路由跳转传参
path 路径跳转传参:
this.$router.push('/路径/参数值')
this.$router.push({
path: '/路径/参数值'
})
查询参数传参
热门搜索:
黑马程序员
前端培训
如何成为前端大牛
Search.vue
搜索关键字: {{ $route.query.key }}
搜索结果:
- .............
- .............
- .............
- .............
动态路由传参
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ name: 'search', path: '/search/:words?', component: Search },
{ path: '*', component: NotFind }
],
mode: "history"
})
Home.vue
热门搜索:
黑马程序员
前端培训
如何成为前端大牛
Search.vue
搜索关键字: {{ $route.params.words }}
搜索结果:
- .............
- .............
- .............
- .............
name 命名路由跳转传参 (query传参)
this.$router.push({
name: '路由名',
query: {
参数名1: '参数值1',
参数名2: '参数值2',
}
})
Home.vue
热门搜索:
黑马程序员
前端培训
如何成为前端大牛
Search.vue
搜索关键字: {{ $route.query.key }}
搜索结果:
- .............
- .............
- .............
- .............
第二种方式 (param传参):
index.js
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ name: 'search', path: '/search/:words?', component: Search },
{ path: '*', component: NotFind }
],
mode: "history"
})
热门搜索:
黑马程序员
前端培训
如何成为前端大牛
search.vue
搜索关键字: {{ $route.params.words }}
搜索结果:
- .............
- .............
- .............
- .............
小结:
基于 VueCli 自定义创建项目
一、自定义创建项目
目标:基于 VueCli 自定义创建项目架子
---
二、ESlint代码规范及手动修复
代码规范:一套写代码的约定规则。例如:赋值符号的左右是否需要空格?一句结束是否是要加;?...
老话说:"没有规矩不成方圆" —> 正规的团队 需要 统一的编码风格
JavaScript Standard Style 规范说明 JavaScript Standard Style
下面是这份规则中的一小部分:
-
字符串使用单引号 'abc'
-
无分号 const name = 'zs'
-
关键字后加空格 if (condition) { ... }
-
函数名后加空格 function name (arg) { ... }
-
坚持使用全等 === (摒弃 ==)
-
......
代码规范错误
如果你的代码不符合standard的要求,eslint会跳出来刀子嘴,豆腐心地提示你。
下面我们在main.js中随意做一些改动:添加一些空行,空格。
import Vue from 'vue'
import App from './App.vue'
import './styles/index.less'
import router from './router'
Vue.config.productionTip = false;
new Vue ( {
render: h => h(App),
router
}).$mount('#app')
按下保存代码之后:
你将会看在控制台中输出如下错误:
eslint 是来帮助你的。心态要好,有错,就改。
手动修正
根据错误提示来一项一项手动修正。
如果你不认识命令行中的语法报错是什么意思,你可以根据错误代码去 [ ESLint 规则表] 中查找其具体含义。
打开 ESLint 规则表,使用页面搜索(Ctrl + F)这个代码,查找对该规则的一个释义。
三、通过eslint插件来实现自动修正
1. eslint会自动高亮错误显示
2. 通过配置,eslint会自动帮助我们修复错误
-
如何安装 (VSCode插件)
-
如何配置
// 当保存的时候,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 是什么,应用场景,优势。
是什么
vuex 是一个 vue 的状态管理工具,状态就是数据。
大白话:vuex 是一个插件,可以帮我们管理 vue 通用的数据 (多组件共享的数据),例如:购物车数据 个人信息数据
场景
-
某个状态在很多个组件来使用 (个人信息)
-
多个组件共同维护一份数据 (购物车)
优势
-
共同维护一份数据,数据集中化管理
-
响应式变化
-
操作简洁 (vuex 提供了一些辅助函数)
注意:
官方原文:
-
不是所有的场景都适用于vuex,只有在必要的时候才使用vuex
-
使用了vuex之后,会附加更多的框架中的概念进来,增加了项目的复杂度 (数据的操作更便捷,数据的流动更清晰)
Vuex就像《近视眼镜》, 你自然会知道什么时候需要用它~
二、需求: 多组件共享数据
目标:基于脚手架创建项目,构建 vuex 多组件数据共享环境
效果是三个组件共享一份数据:
-
任意一个组件都可以修改数据
-
三个组件的数据是同步的
效果是三个组件共享一份数据:
-
任意一个组件都可以修改数据
-
三个组件的数据是同步的
1.创建项目
vue create vuex-demo
目前只选这个,剩余的照常。
2.创建三个组件, 目录如下
|-components
|--Son1.vue
|--Son2.vue
|-App.vue
3.源代码如下
App.vue
在入口组件中引入 Son1 和 Son2 这两个子组件
根组件
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App)
}).$mount('#app')
Son1.vue
Son1 子组件
从vuex中获取的值:
Son2.vue
Son2 子组件
从vuex中获取的值:
三、vuex 的使用 - 创建仓库
1.安装 vuex
安装vuex与vue-router类似,vuex是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。
yarn add vuex@3 或者 npm install vuex@3 --legacy-peer-deps
2.新建 store/index.js
专门存放 vuex
为了维护项目目录的整洁,在src目录下新建一个store目录其下放置一个index.js文件。 (和 router/index.js
类似)
3.创建仓库 store/index.js
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)
// 创建仓库 store
const store = new Vuex.Store()
// 导出仓库,给 main.js使用
export default store
4. 在 main.js 中导入挂载到 Vue 实例上
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store
}).$mount('#app')
此刻起, 就成功创建了一个 空仓库!!
5.测试打印Vuex
App.vue
export default {
name: 'app',
// 检验: this.$store
created () {
console.log(this.$store)
},
data: function () {
return {
}
},
四、核心概念 - state 状态
1.目标
明确如何给仓库提供数据,如何使用仓库的数据
2.提供数据
State 提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。
打开项目中的 store.js 文件,在state对象中可以添加我们要共享的数据。
// 创建仓库 store
const store = new Vuex.Store({
// state 状态, 即数据, 类似于vue组件中的data,
// 区别:
// 1.data 是组件自己的数据,
// 2.state 中的数据整个vue项目的组件都能访问到
state: {
count: 101
}
})
3.访问Vuex中的数据
问题: 如何在组件中获取count?
-
通过$store直接访问 —> {{ $store.state.count }}
获取 store:
1.Vue模板中获取 this.$store
2.js文件中获取 import 导入 store
模板中: {{ $store.state.xxx }}
组件逻辑中: this.$store.state.xxx
JS模块中: store.state.xxx
-
通过辅助函数 (简化)
mapState 是辅助函数,帮助我们把 store 中的数据 自动映射到组件的计算属性中
核心步骤:
5.代码实现
模板中使用
组件中可以使用 $store 获取到vuex中的store对象实例,可通过state属性属性获取count, 如下
state的数据 - {{ $store.state.count }}
组件逻辑中使用
将state属性定义在计算属性中 State | Vuex
state的数据 - {{ count }}
// 把state中数据,定义在组件内的计算属性中
computed: {
count () {
return this.$store.state.count
}
}
5.3 js文件中使用
//main.js
import store from "@/store"
console.log(store.state.count)
每次都像这样一个个的提供计算属性, 太麻烦了,我们有没有简单的语法帮我们获取state中的值呢?
五、通过辅助函数 - mapState获取 state中的数据
mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便的用法
用法 :
1.第一步:导入mapState (mapState是vuex中的一个函数)
import { mapState } from 'vuex'
2.第二步:采用数组形式引入state属性
mapState(['count'])
上面代码的最终得到的是 类似于
count () {
return this.$store.state.count
}
3.第三步:利用展开运算符将导出的状态映射给计算属性
computed: {
...mapState(['count'])
}
state的数据:{{ count }}
六、开启严格模式及Vuex的单项数据流
1.目标
明确 vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据
2.直接在组件中修改Vuex中state的值
Son1.vue
button @click="handleAdd">值 + 1
methods:{
handleAdd (n) {
// 错误代码(vue默认不会监测,监测需要成本)
// 严格模式 (有利于初学者,检测不规范的代码 => 上线时需要关闭)
this.$store.state.count++
// console.log(this.$store.state.count)
},
}
3.开启严格模式
通过 strict: true
可以开启严格模式,开启严格模式后,直接修改state中的值会报错
state数据的修改只能通过mutations,并且mutations必须是同步的
七、核心概念-mutations
目标:掌握 mutations 的操作流程,来修改 state 数据。(state 数据的修改只能通过 mutations)
1.定义mutations
const store = new Vuex.Store({
state: {
count: 0
},
// 定义mutations
mutations: {
}
})
2.格式说明
mutations是一个对象,对象中存放修改state的方法
mutations: {
// 方法里参数 第一个参数是当前store的state属性
// payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷
addCount (state) {
state.count += 1
}
},
3.组件中提交 mutations
this.$store.commit('addCount')
index.js
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)
// 创建仓库 store
const store = new Vuex.Store({
// 通过 state 可以提供数据 (所有组件共享的数据)
// 开启严格模式
strict: true,
// 1. 通过 state 可以提供数据 (所有组件共享的数据)
state: {
title: '大标题',
count: 100
},
// 2. 通过 mutations 可以通过修改数据的方法
mutations: {
// 所有 mutation函数,第一个函数都是 state
addCount (state) {
state.count += 1
},
addFive (state) {
state.count += 5
},
ChangeTitle (state) {
state.title = '小标题'
}
}
})
// 导出仓库,给 main.js使用
export default store
Son1.vue
Son1 子组件
从vuex中获取的值:
八、带参数的 mutations
1.目标:
掌握 mutations 传参语法
2.语法
看下面这个案例,每次点击不同的按钮,加的值都不同,每次都要定义不同的mutations处理吗?
提交 mutation 是可以传递参数的
this.$store.commit('xxx', 参数)
2.1 提供mutation函数(带参数 - 提交载荷 payload)
mutations: {
...
addCount (state, n) {
state.count += n
}
},
2.2 页面中提交mutation
handle ( ) {
this.$store.commit('addCount', 10)
}
小tips: 提交的参数只能是一个, 如果有多个参数要传, 可以传递一个对象
this.$store.commit('addCount', {
count: 10
})
九、练习-mutations的减法功能
1.步骤
2.代码实现
Son1.vue
Son1 子组件
从vuex中获取的值:
store/index.js
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)
// 创建仓库 store
const store = new Vuex.Store({
// 通过 state 可以提供数据 (所有组件共享的数据)
// 开启严格模式
strict: true,
// 1. 通过 state 可以提供数据 (所有组件共享的数据)
state: {
title: '大标题',
count: 100
},
// 2. 通过 mutations 可以通过修改数据的方法
mutations: {
// 所有 mutation函数,第一个函数都是 state
// 注意点: mutation参数有且只能有一个,如果需要多个参数,包装成一个对象或数组
addCount (state, n) {
state.count += n
},
// addCount (state, n) {
// state.count += n
// },
ChangeTitle (state, newTitle) {
state.title = newTitle
}
}
})
// 导出仓库,给 main.js使用
export default store
十、练习-Vuex中的值和组件中的input双向绑定
1.目标
实时输入,实时更新,巩固 mutations 传参语法
2.实现步骤
3.代码实现
App.vue
根组件 - {{ title }}
store/index.js
mutations: {
changeCount (state, newCount) {
state.count = newCount
}
},
十一、辅助函数- mapMutations
mapMutations和mapState很像,它把位于mutations中的方法提取了出来。
mutations: {
subCount (state, n) {
state.count -= n
},
}
import { mapMutations } from 'vuex'
methods: {
...mapMutations(['subCount','changeTitle'])
}
上面代码的含义是将mutations的方法映射到组件methods中,等价于
methods: {
// commit(方法名, 载荷参数)
subCount (n) {
this.$store.commit('subCount', n)
},
}
此时,就可以直接通过this.subCount调用了
this.subCount(10) 调用
但是请注意: Vuex中mutations中要求不能写异步代码,如果有异步的ajax请求,应该放置在actions中
十二、核心概念 - actions
state是存放数据的,mutations是同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化),
actions 则负责进行 异步操作
说明:mutations必须是同步的 (便于监测数据变化,记录调试)
需求: 一秒钟之后, 要给一个数 去修改state
1.定义actions
mutations: {
changeCount (state, newCount) {
state.count = newCount
}
}
actions: {
setAsyncCount (context, num) {
// 一秒后, 给一个数, 去修改 num
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
},
2.组件中通过dispatch调用
setAsyncCount () {
this.$store.dispatch('setAsyncCount', 666)
}
Index.js
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)
// 创建仓库 store
const store = new Vuex.Store({
// 通过 state 可以提供数据 (所有组件共享的数据)
// 开启严格模式
strict: true,
// 1. 通过 state 可以提供数据 (所有组件共享的数据)
state: {
title: '大标题',
count: 100
},
// 2. 通过 mutations 可以通过修改数据的方法
mutations: {
// 所有 mutation函数,第一个函数都是 state
addCount (state, n) {
state.count += n
},
ChangeTitle (state, obj) {
state.title = obj.title
},
changeCount (state, newCount) {
state.count = newCount
}
},
// 3.actions 处理异步,注意:不能直接操作 state,需要commit mutation
actions: {
// context 上下文 (此处未分模块,可以当成 store仓库)
// context.commit('mutation名字',额外参数)
changeCountAction (context, num) {
// 这里是setTimeout 模拟异步,以后大部分场景是发请求
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
}
})
// 导出仓库,给 main.js使用
export default store
Son1.vue
Son1 子组件
从vuex中获取的值:
十三、辅助函数 -mapActions
1.目标:掌握辅助函数 mapActions,映射方法
mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中
actions: {
changeCountAction (context, num) {
setTimeout(() => {
context.commit('changeCount', num)
},1000)
}
}
import { mapActions } from 'vuex'
methods: {
...mapActions(['changeCountAction'])
}
Son2.vue
import { mapActions } from 'vuex'
methods: {
...mapActions(['changeCountAction'])
}
//mapActions映射的代码 本质上是以下代码的写法
//methods: {
// changeCountAction (n) {
// this.$store.dispatch('changeCountAction', n)
// },
//}
直接通过 this.方法 就可以调用
this.changeCountAction(666) 调用
十四、核心概念 - getters
除了state之外,有时我们还需要从state中 筛选出符合条件的一些数据,这些数据是依赖state的,此时会用到getters
例如,state中定义了list,为1-10的数组,
state: {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
组件中,需要 显示所有大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它
1.定义getters
getters: {
// 注意:
// (1) getters函数的第一个参数是 state
// (2) getters函数必须要有返回值
filterList: (state) {
return state.list.filter(item => item > 5)
}
}
2.访问getters
2.1 通过store 访问 getters
{{ $store.getters.filterList }}
2.2 通过辅助函数 - mapGetters
computed: {
...mapGetters(['filterList'])
}
{{ filterList }}
十五、使用小结
模块 module(进阶语法)
1.目标
掌握核心概念 module 模块的创建
2.问题
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。(当项目变得越来越大的时候,Vuex会变得越来越难以维护)
由此,又有了Vuex的模块化
state: {
userInfo: {
name: 'zs',
age: 18
},
theme: 'dark',
desc: '测试项目'
},
3.模块定义 - 准备 state
定义两个模块 user 和 setting
user中管理用户的信息状态 userInfo modules/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数据
1.目标:
掌握模块中 state 的访问语法
尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的 state 中,属性名就是模块名
2.使用模块中的数据
-
直接通过模块名访问 $store.state.模块名.xxx
-
通过 mapState 映射:
-
默认根级别的映射 mapState([ 'xxx' ])
-
子模块的映射 :mapState('模块名', ['xxx']) - 需要开启命名空间 namespaced:true
modules/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
}
3.代码示例
$store直接访问
...mapState('user', ['userInfo']),
...mapState('setting', ['theme', 'desc']),
mapState辅助函数访问
...mapState('user', ['userInfo']),
...mapState('setting', ['theme', 'desc']),
二、获取模块内的getters数据
1.目标:
掌握模块中 getters 的访问语
2.语法:
使用模块中 getters 中的数据:
-
直接通过模块名访问$store.getters['模块名/xxx ']
-
通过 mapGetters 映射
-
默认根级别的映射 mapGetters([ 'xxx' ])
-
子模块的映射 mapGetters('模块名', ['xxx'])
- 需要开启命名空间
3.代码演示
modules/user.js
{{ $store.getters['user/UpperCaseName'] }}
Son1.vue 直接访问getters
{{ $store.getters['user/UpperCaseName'] }}
Son2.vue 通过命名空间访问
computed:{
...mapGetters('user', ['UpperCaseName'])
}
三、获取模块内的mutations方法
1.目标:
掌握模块中 mutation 的调用语法
2.注意:
默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
3.调用方式:
-
直接通过 store 调用 $store.commit('模块名/xxx ', 额外参数)
-
通过 mapMutations 映射
-
默认根级别的映射 mapMutations([ 'xxx' ])
-
子模块的映射 mapMutations('模块名', ['xxx']) - 需要开启命名空间
4.代码实现
modules/user.js
const mutations = {
setUser (state, newUserInfo) {
state.userInfo = newUserInfo
}
}
modules/setting.js
const mutations = {
setTheme (state, newTheme) {
state.theme = newTheme
}
}
Son1.vue
export default {
methods: {
updateUser () {
// $store.commit('模块名/mutation名', 额外传参)
this.$store.commit('user/setUser', {
name: 'xiaowang',
age: 25
})
},
updateTheme () {
this.$store.commit('setting/setTheme', 'pink')
}
}
}
Son2.vue
methods:{
// 分模块的映射
...mapMutations('setting', ['setTheme']),
...mapMutations('user', ['setUser']),
}
四、获取模块内的actions方法
1.目标:
掌握模块中 action 的调用语法 (同理 - 直接类比 mutation 即可)
2.注意:
默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
3.调用语法:
-
直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)
-
通过 mapActions 映射
-
默认根级别的映射 mapActions([ 'xxx' ])
-
子模块的映射 mapActions('模块名', ['xxx']) - 需要开启命名空间
4.代码实现
需求:
modules/user.js
const actions = {
setUserSecond (context, newUserInfo) {
// 将异步在action中进行封装
setTimeout(() => {
// 调用mutation context上下文,默认提交的就是自己模块的action和mutation
context.commit('setUser', newUserInfo)
}, 1000)
}
}
Son1.vue 直接通过store调用
methods:{
updateUser2 () {
// 调用action dispatch
this.$store.dispatch('user/setUserSecond', {
name: 'xiaohong',
age: 28
})
},
}
Son2.vue mapActions映射
methods:{
...mapActions('user', ['setUserSecond'])
}
五、Vuex模块化的使用小结
1.直接使用
-
state --> $store.state.模块名.数据项名
-
getters --> $store.getters['模块名/属性名']
-
mutations --> $store.commit('模块名/方法名', 其他参数)
-
actions --> $store.dispatch('模块名/方法名', 其他参数)
2.借助辅助方法使用
1.import { mapXxxx, mapXxx } from 'vuex'
computed、methods: {
// ...mapState、...mapGetters放computed中;
// ...mapMutations、...mapActions放methods中;
...mapXxxx('模块名', ['数据项|方法']),
...mapXxxx('模块名', { 新的名字: 原来的名字 }),
}
2.组件中直接使用 属性 {{ age }}
或 方法 @click="updateAge(2)"