赞
踩
Vue 快速上手
Vue 概念 / 创建实例 / 插值表达式 / 响应式特性 / 开发者工具
Vue 指令
v-html / v-show / v-if / v-else / v-on / v-bind / v-for / v-model
综合案例 - 小黑记事本
列表渲染 / 删除功能 / 添加功能 / 底部统计 / 清空
概念:Vue 是一个用于 构建用户界面 的 渐进式 框架
基于数据渲染出用户看到的页面
Vue 的两种使用方式:
① Vue 核心包开发
场景:局部 模块改造
② Vue 核心包 & Vue 插件 工程化开发
场景:整站 开发
Vue是什么?
Vue 是一个用于 构建用户界面 的 渐进式 框架
构建用户界面:基于 数据 动态 渲染 页面
渐进式:循序渐进的学习
框架:一套完整的项目解决方案,提升开发效率↑ (理解记忆规则)
规则→ 官网
创建 Vue 实例,初始化渲染的核心步骤:
准备容器
引包 (官网) - 开发版本 / 生产版本
创建 Vue 实例 new Vue()
指定配置项 el data => 渲染数据
① el 指定挂载点,选择器指定控制的是哪个盒子
② data 提供数据
插值表达式是一种 Vue 的模板语法
1. 作用: 利用表达式进行插值,渲染到页面中
表达式:是可以被求值的代码,JS引擎会将其计算出一个结果
2. 语法:{{ 表达式 }}
<p>{{nickname.toUpperCase()}}</p>
<p>{{age+'是成年人'}}</p>
<p>{{age>=18? '成年' :'未成年'}}</p>
<p>{{friend.name}}</p>
<p>{{friend.desc}}</p>
3. 注意点:
(1)使用的数据必须存在 (data)
<p>{{hobby}}</p>
(2)支持的是表达式,而非语句,比如:if for …
<p>{{if}}</p>
(3)不能在标签属性中使用 {{ }} 插值
<p name="{{nickname}}"></p>
利用表达式进行插值,将数据渲染页面中
{{ 表达式 }}
① 使用的数据要存在 (data)
② 支持的是表达式,而非语句 if … for
③ 不能在标签属性里面使用
我们已经掌握了基础的模板渲染,其实除了基本的模板渲染,Vue背后还做了大量工作。
比如:数据的响应式处理→ 响应式:数据变化,视图自动更新
如何访问 or 修改?data中的数据, 最终会被添加到实例上
① 访问数据: “实例.属性名”
② 修改数据: “实例.属性名” = “值”
数据改变,视图会自动更新
聚焦于数据 → 数据驱动视图
使用 Vue 开发,关注业务的核心逻辑,根据业务修改数据即可
数据改变,视图自动更新
使用 Vue 开发 → 专注于业务核心逻辑 即可
data中的数据, 最终会被添加到实例上
① 访问数据: “实例.属性名”
② 修改数据: “实例.属性名” = “值”
(1)通过谷歌应用商店安装 (国外网站)
(2)极简插件: 下载 → 开发者模式 → 拖拽安装 → 插件详情允许访问文件
https://chrome.zzzmh.cn/index
打开 Vue 运行的页面,调试工具中 Vue 栏,即可查看修改数据,进行调试。
Vue 会根据不同的【指令】,针对标签实现不同的【功能】
指令:带有 v- 前缀 的 特殊 标签属性
作用:设置元素的 innerHTML
语法:v-html = "表达式 "
指令就是带有 v- 前缀 的特殊 属性,不同属性 对应 不同的功能
学习不同指令 → 解决不同业务场景需求
v-html = "表达式 " → 动态设置元素 innerHTML
1. 作用: 辅助 v-if 进行判断渲染
2. 语法: v-else v-else-if = “表达式”
3. 注意: 需要紧挨着 v-if 一起使用
1. 作用: 注册事件 = 添加监听 + 提供处理逻辑
2. 语法:
① v-on:事件名 = “内联语句”
② v-on:事件名 = “methods中的函数名”
3. 简写:@事件名
1. 作用: 动态的设置html的标签属性 → src url title /…
2. 语法: v-bind:属性名=“表达式”
3. 注意: 简写形式 :属性名=“表达式”
核心思路分析:
① 数组存储图片路径 → [ 图片1, 图片2, 图片3, … ]
② 准备下标 index,数组[下标] → v-bind 设置 src 展示图片 → 修改下标切换图片
1. 作用: 基于数据循环, 多次渲染整个元素 → 数组、对象、数字…
<p v-for="...">我是一个内容</p>
2. 遍历数组语法:
v-for = “(item, index) in 数组”
Ø item 每一项, index 下标
Ø 省略 index: v-for = “item in 数组”
明确需求:
① 基本渲染 → v-for
② 删除功能 → 用 filter 根据 id 从数组中删除对应项
语法: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作用:给元素添加的唯一标识。
1. 作用: 给 表单元素 使用, 双向数据绑定 → 可以快速 获取 或 设置 表单元素内容
① 数据变化 → 视图自动更新
② 视图变化 → 数据自动更新
2. 语法: v-model = ‘变量’
功能需求:
① 列表渲染
② 删除功能
③ 添加功能
④ 底部统计 和 清空
功能总结:
① 列表渲染:
v-for key 的设置 {{ }} 插值表达式
② 删除功能
v-on 调用传参 filter 过滤 覆盖修改原数组
③ 添加功能
v-model 绑定 unshift 修改原数组添加
④ 底部统计 和 清空
数组.length累计长度
覆盖数组清空列表
v-show 控制隐藏
通过 “.” 指明一些指令 后缀,不同 后缀 封装了不同的处理操作 → 简化代码
① 按键修饰符
@keyup.enter
→ 键盘回车监听
② v-model修饰符
v-model.trim
→ 去除首尾空格
v-model.number
→ 转数字
③ 事件修饰符
@事件名.stop → 阻止冒泡
@事件名.prevent → 阻止默认行为
为了方便开发者进行样式控制, Vue 扩展了 v-bind 的语法,可以针对 class 类名 和 style 行内样式 进行控制 。
语法 :class = "对象/数组"
① 对象
→ 键就是类名,值是布尔值。如果值为 true
,有这个类,否则没有这个类
适用场景:一个类名,来回切换
② 数组
→ 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表
适用场景:批量添加或删除类
核心思路:
所谓切换高亮,其实就是改下标
语法 :style = “样式对象”
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
适用场景:某个具体属性的动态设置
**概念:**基于现有的数据,计算出来的新属性。 依赖的数据变化,自动重新计算。
语法:
① 声明在 computed 配置项中,一个计算属性对应一个函数
② 使用起来和普通属性一样使用 {{ 计算属性名 }}
计算属性 → 可以将一段 求值的代码 进行封装
data: {
list: [
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
computed: {
计算属性名 () {
基于现有数据,编写求值逻辑
return 结果
}
},
computed 计算属性:
**作用:**封装了一段对于数据的处理,求得一个结果。
语法:
① 写在 computed 配置项中
② 作为属性,直接使用 → this.计算属性 {{ 计算属性 }}
methods 方法:
**作用:**给实例提供一个方法,调用以处理业务逻辑。
语法:
① 写在 methods 配置项中
② 作为方法,需要调用 → this.方法名( ) {{ 方法名() }} @事件名=“方法名”
缓存特性(提升性能):
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,
依赖项变化了,会自动重新计算 → 并再次缓存
计算属性是属性,能访问,应该也能修改了?
计算属性默认的简写,只能读取访问,不能 “修改”。
如果要 “修改” → 需要写计算属性的完整写法。
computed: { 计算属性名 () { 一段代码逻辑(计算逻辑) return 结果 } } computed: { 计算属性名: { get() { 一段代码逻辑(计算逻辑) return 结果 }, set(修改的值) { 一段代码逻辑(修改逻辑) } } }
需求说明:
渲染功能
删除功能
添加功能
统计总分,求平均分
业务技术点总结:
v-if v-else v-for v-bind:class
点击传参 filter过滤覆盖原数组
.prevent 阻止默认行为
v-model v-model修饰符(.trim .number)
unshift 修改数组更新视图
计算属性 reduce求和
作用:监视数据变化,执行一些 业务逻辑 或 异步操作。
语法:
① 简单写法 → 简单类型数据,直接监视
data: {
words: '苹果',
obj: {
words: '苹果'
}
},
watch: {
// 该方法会在数据变化时,触发执行
数据属性名 (newValue, oldValue) {
一些业务逻辑 或 异步操作。
},
'对象.属性名' (newValue, oldValue) {
一些业务逻辑 或 异步操作。
}
}
② 完整写法 → 添加额外配置项
(1) deep: true 对复杂类型深度监视
(2) immediate: true 初始化立刻执行一次handler方法
data: { obj: { words: '苹果', lang: 'italy' }, }, watch: {// watch 完整写法 数据属性名: { deep: true, // 深度监视 handler (newValue) { console.log(newValue) } } } data: { obj: { words: '苹果', lang: 'italy' }, }, watch: {// watch 完整写法 数据属性名: { deep: true, // 深度监视 immediate: true, // 是否立刻执行一次handler handler (newValue) { console.log(newValue) } } }
需求:默认文本,一进入页面,立刻翻译一次
watch侦听器的语法有几种?
① 简单写法 → 监视简单类型的变化
watch: {
数据属性名 (newValue, oldValue) {
一些业务逻辑 或 异步操作。
},
'对象.属性名' (newValue, oldValue) {
一些业务逻辑 或 异步操作。
}
}
② 完整写法 → 添加额外的配置项 (深度监视复杂类型,立刻执行)
watch: {// watch 完整写法
数据属性名: {
deep: true, // 深度监视(针对复杂类型)
immediate: true, // 是否立刻执行一次handler
handler (newValue) {
console.log(newValue)
}
}
}
需求说明:
业务技术点总结:
思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来)
Vue生命周期:一个Vue实例从 创建 到 销毁 的整个过程。
生命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁
Vue生命周期过程中,会自动运行一些函数,被称为==【生命周期钩子】→ 让开发者可以在【特定阶段】运行自己的代码==。
created 数据准备好了,可以开始发送初始化渲染请求。
mounted 模板渲染完成,可以开始操作DOM了
功能需求:
基本介绍:
Vue CLI 是 Vue 官方提供的一个全局命令工具。
可以帮助我们快速创建一个开发 Vue 项目的标准化基础架子。【集成了 webpack 配置】
好处:
使用步骤:
yarn global add @vue/cli
或 npm i @vue/cli -g
vue --version
vue create project-name
(项目名-不能用中文) yarn serve
或 npm run serve
(找package.json)VUE-DEMO │─node_modules 第三包文件夹 ├─public 放html文件的地方 │ ├─favicon.ico 网站图标 │ └─< index.html index.html 模板文件 ③ > ├─src 源代码目录 → 以后写代码的文件夹 │ └─assets 静态资源目录 → 存放图片、字体等 │ └─components 组件目录 → 存放通用组件 │ └─< App.vue App根组件 → 项目运行看到的内容就在这里编写 ② > │ └─< main.js 入口文件 → 打包或运行,第一个执行的文件 ① > └─.gitignore git忽视文件 └─babel.config.js babel配置文件 └─jsconfig.json js配置文件 └─package.json 项目配置文件 → 包含项目名、版本号、scripts、依赖包等 └─README.md 项目说明文档 └─vue.config.js vue-cli配置文件 └─yarn.lock yarn锁文件,由yarn自动生成的,锁定安装版本
main.js核心代码
//1. 导入 Vue
import Vue from 'vue'
//2. 导入 App.vue
import App from './App.vue'
//3. 实例化 Vue,将 App.vue 渲染到 index.html 容器中
new Vue({
render: h => h(App),
}).$mount('#app')
**① 组件化:**一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用 → 提升开发效率。
组件分类:普通组件、根组件。
**② 根组件:**整个应用最上层的组件,包裹所有普通小组件。
1. 语法高亮插件:Vetur
2. 三部分组成:
◆ template:结构 (有且只能一个根元素)
◆ script: js逻辑
◆ style: 样式 (可支持less,需要装包)
3. 让组件支持 less
(1) style标签,lang=“less” 开启less功能
(2) 装包: yarn add less less-loader
(1) 组件化:
页面可拆分成一个个组件,每个组件有着独立的结构、样式、行为
① 好处:便于维护,利于复用 → 提升开发效率。
② 组件分类:普通组件、根组件。
(2) 根组件:
整个应用最上层的组件,包裹所有普通小组件。
一个根组件App.vue,包含的三个部分:
① template
结构 (只能有一个根节点)
② style
样式 (可以支持less,需要装包 less 和 less-loader )
③ script
行为
组件注册的两种方式:
① 创建 .vue 文件 (三个组成部分)
② 在使用的组件内导入并注册
使用:
◆ 当成 html 标签使用 <组件名></组件名>
注意:
// 导入需要注册的组件
//import 组件对象 from '.vue文件路径'
import HmHeader from './components/HmHeader'
export default {
// 局部注册
components: {
'组件名': 组件对象,
HmHeader: HmHeader
}
}
① 创建 .vue 文件 (三个组成部分)
② main.js 中进行全局注册
// 导入需要全局注册的组件
import HmButton from './components/HmButton'
// 调用 Vue.component 进行全局注册
// Vue.component('组件名', 组件对象)
Vue.component('HmButton', HmButton)
技巧:
◆ 一般都用局部注册,如果发现确实是通用组件,再定义到全局。
普通组件的注册使用:
① 局部注册:
(1) 创建.vue组件 (单文件组件)
(2) 使用的组件内导入,并局部注册 components: { 组件名:组件对象 }
② 全局注册:
(1) 创建.vue组件 (单文件组件)
(2) main.js内导入,并全局注册 Vue.component(组件名, 组件对象)
<组件名></组件名>
技巧:
一般都用局部注册,如果发现确实是通用组件,再抽离到全局。
页面开发思路:
将来 → 通过 js 动态渲染,实现功能
◆ 组件的三大组成部分 (结构/样式/逻辑)
scoped样式冲突 / data是一个函数
◆ 组件通信
组件通信语法 / 父传子 / 子传父 / 非父子 (扩展)
◆ 综合案例:小黑记事本 (组件版)
拆分组件 / 渲染 / 添加 / 删除 / 统计 / 清空 / 持久化
◆ 进阶语法
v-model原理 / v-model应用于组件 / sync修饰符 / ref 和 $refs / $nextTick
结构<template>
只能有一个根元素
样式<style>
全局样式(默认):影响所有组件
局部样式:scoped 下样式,只作用于当前组件
逻辑<script>
el 根实例独有, data 是一个函数,
其他配置项一致
默认情况:写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。
scoped原理?
data-v-hash
值 的属性[data-v-hash值]
的属性选择器最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
一个组件的 data 选项必须是一个函数。→ 保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象。
组件三大组成部分的注意点:
组件通信, 就是指 组件与组件 之间的数据传递。
⚫ 组件的数据是独立的,无法直接访问其他组件的数据。
⚫ 想用其他组件的数据 → 组件通信
思考:
组件关系分类:
父子关系 props 和 $emit
非父子关系
通用解决方案:Vuex (适合复杂业务场景)
父子通信流程图:
父组件通过 props 将数据传递给子组件
子组件利用 $emit 通知父组件,进行修改更新
父子关系 → props & $emit
非父子关系 → provide & inject 或 eventbus
通用方案 → vuex
2.1 父传子props:
① 父中给子添加属性传值 ② 子props 接收 ③ 子组件使用
2.2 子传父$emit:
① 子$emit 发送消息 ②父中给子添加消息监听 ③ 父中实现处理函数
什么是 prop
Prop 定义:组件上 注册的一些 自定义属性
Prop 作用:向子组件传递数据
特点:
⚫ 可以 传递 任意数量 的prop
⚫ 可以 传递 任意类型 的prop
**思考:**组件的 prop 可以乱传么?
**作用:**为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误
语法:
① 类型校验
② 非空校验
③ 默认值
④ 自定义校验
props: { 校验的属性名: 类型 // Number String Boolean ... }, props: { 校验的属性名: { type: 类型, // Number String Boolean ... required: true, // 是否必填 default: 默认值, // 默认值 validator (value) { // 自定义校验逻辑 return 是否通过校验 } } },
共同点:都可以给组件提供数据。
区别:
⚫ data 的数据是自己的 → 随便改
⚫ prop 的数据是外部的 → 不能直接改,要遵循 单向数据流
单向数据流:父级 prop 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。
口诀:谁的数据谁负责
需求说明:
① 拆分基础组件
② 渲染待办任务
③ 添加任务
④ 删除任务
⑤ 底部合计 和 清空功能
⑥ 持久化存储
核心步骤:
① 拆分基础组件
新建组件 → 拆分存放结构 → 导入注册使用
② 渲染待办任务
提供数据==(公共父组件)== → 父传子传递 list → v-for 渲染
③ 添加任务
收集数据 v-model → 监听事件 → 子传父传递任务 → 父组件 unshift
④ 删除任务
监听删除携带 id → 子传父传递 id → 父组件 filter 删除
⑤ 底部合计 和 清空功能
底部合计:父传子传递 list → 合计展示
清空功能:监听点击 → 子传父通知父组件 → 父组件清空
⑥ 持久化存储:watch监视数据变化,持久化到本地
作用:非父子组件之间,进行简易消息传递。(复杂场景 → Vuex)
import Vue from 'vue'
const Bus = new Vue()
export default Bus
created () {
Bus.$on('sendMsg', (msg) => {
this.msg = msg
})
}
Bus.$emit('sendMsg', '这是一个消息')
provide & inject 作用:跨层级共享数据。
export default {
provide () {
return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型【响应式】
userInfo: this.userInfo,
}
}
}
export default {
inject: ['color','userInfo'],
created () {
console.log(this.color, this.userInfo)
}
}
**原理:**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>
① 父传子:数据 应该是父组件 props 传递 过来的,拆解 v-model 绑定数据
② 子传父:监听输入,子传父传值给父组件修改
父组件(使用)
<BaseSelect :cityId="selectId" @事件名="selecteId = $event" />
子组件(封装)
<select :value="cityId" @change="handleChange">...</select>
<script>
props: {
cityId: String
},
methods: {
handleChange (e) {
this.$emit('事件名', e.target.value)
}
}
</script>
v-model
简化代码,实现 子组件 和 父组件数据 双向绑定① 子组件中:props 通过 value 接收,事件触发 input
②父组件中:v-model给组件直接绑数据==(:value+@input)==
父组件(使用)
<BaseSelect v-model="selectId"></BaseSelect>
子组件(封装)
<select :value="value" @change="handleChange">...</select>
<script>
props: {
value: String
},
methods: {
handleChange (e) {
this.$emit('事件名', e.target.value)
}
}
</script>
① 父传子:父组件动态传递 prop 数据,拆解v-model,绑定数据
② 子传父:监听输入,子传父传值给父组件修改
本质:实现了实现 子组件 和 父组件数据 的双向绑定
① 子组件中:props 通过 value 接收,事件触发 input
② 父组件中: v-model 给组件直接绑数据
**作用:**可以实现 子组件 与 父组件数据 的 双向绑定,简化代码
**特点:**prop属性名,可以自定义,非固定为 value
**场景:**封装弹框类的基础组件, visible属性 true显示 false隐藏
**本质:**就是 :属性名 和 @update:属性名 合写
**作用:**利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例
**特点:**查找范围 → 当前组件内 (更精确稳定)
① 获取 dom:
<div ref="chartRef">我是渲染图表的容器</div>
恰当时机, 通过 this.$refs.xxx, 获取目标标签
mounted () {
console.log(this.$refs.chartRef)
},
// 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(document.querySelector('.box'));
② 获取组件:
<BaseForm ref="baseForm"></BaseForm>
this.$refs.baseForm.组件方法()
简单来说,当Vue的数据发生变化时,它并不会立即更新页面,而是将更新操作放在一个队列中,等到浏览器空闲时再批量执行这些更新操作。这样做的好处是,可以减少浏览器的重绘次数,提高页面的性能。
需求:编辑标题, 编辑框自动聚焦
this.isShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点
问题:“显示之后”,立刻获取焦点是不能成功的!
**原因:**Vue 是 异步更新 DOM (提升性能)
$nextTick:等 DOM 更新后, 才会触发执行此方法里的函数体
语法: this.$nextTick(函数体)
this.$nextTick(() => {
this.$refs.inp.focus()
})
this.$nextTick(() => {
// 业务逻辑
})
自定义指令
基本语法 (全局&局部注册) / 指令的值 / v-loading 指令封装
插槽
默认插槽 / 后备内容 / 具名插槽 / 作用域插槽
综合案例:商品列表
MyTag 组件封装 / MyTable 组件封装
路由入门
单页应用程序 / 路由概念 / VueRouter 的基本使用 / 组件目录存放问题
自定义指令:自己定义的指令, 可以封装一些 dom 操作, 扩展额外功能
全局注册 - 语法
Vue.directive('指令名', {
"inserted" (el) {
// 可以对 el 标签,扩展额外功能
el.focus()
}
})
局部注册 – 语法
directives: {"指令名": {
inserted () {
// 可以对 el 标签,扩展额外功能
el.focus()
}
}
}
<input v-指令名 type="text">
使用:
mounted () {
需求:当页面加载时,让元素将获得焦点
(autofocus在safari浏览器有兼容性)
操作dom:dom元素.focus()
mounted (){
this.$refs.inp.focus()
}
麻烦
使用:
<input v-指令名type="text">
简洁
自定义指令的作用?
封装一些 dom 操作,扩展额外功能,例如获取焦点
自定义指令的使用步骤?
在 inserted 钩子函数中,配置指令dom逻辑
需求:实现一个 color 指令 - 传入不同的颜色, 给标签设置文字颜色
●语法:在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值
<div v-color="color">我是内容</div>
●通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数。
directives: {
color: {
inserted (el, binding) {
el.style.color = binding.value
},
update (el, binding) {
el.style.color = binding.value
}
}
}
① v-指令名 = “指令值” ,通过 等号 可以绑定指令的值
② 通过 binding.value 可以拿到指令的值
③ 通过 update 钩子,可以监听指令值的变化,进行dom更新操作
场景:实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 => 用户体验不好
需求:封装一个 v-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;
}
(1) 准备类名 loading,通过伪元素提供遮罩层
(2) 添加或移除类名,实现loading蒙层的添加移除
(3) 利用指令语法,封装 v-loading 通用指令
inserted 钩子中,binding.value 判断指令的值,设置默认状态
update 钩子中,binding.value 判断指令的值,更新类名状态
作用:让组件内部的一些 结构 支持 自定义
需求: 将需要多次显示的对话框, 封装成一个组件
问题:组件的内容部分,不希望写死,希望能使用的时候自定义。怎么办?
插槽基本语法:
<slot></slot>
占位<MyDialog></MyDialog>
标签内部, 传入结构替换slot<template> <div class="dialog"> <div class="dialog-header"> <h3>友情提示</h3> <span class="close">✖ </span> </div> <div class="dialog-content"> <slot></slot> </div> <div class="dialog-footer"> <button>取消</button> <button>确认</button> </div> </div> </template> <MyDialog> 你确认要退出本系统么? </MyDialog>
场景:当组件内某一部分结构不确定,想要自定义怎么办?
用插槽 slot 占位封装
使用:插槽使用的基本步骤?
通过插槽完成了内容的定制,传什么显示什么, 但是如果不传,则是空白
能否给插槽设置 默认显示内容 呢?
插槽后备内容:封装组件时,可以为预留的 <slot>
插槽提供后备内容(默认内容)。
● 语法: 在 <slot>
标签内,放置内容, 作为默认显示内容
<MyDialog></MyDialog>
● 效果:
●外部使用组件时,不传东西,则slot会显示后备内容
<MyDialog>我是内容</MyDialog>
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖ </span>
</div>
<div class="dialog-content">
<slot>我是后备内容</slot>//默认显示的内容
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
如何给插槽设置默认显示内容?
在slot标签内,写好后备内容
什么时候插槽后备内容会显示?
当使用组件并未给我们传入具体标签或内容时
需求:一个组件内有多处结构,需要外部传入标签,进行定制
默认插槽:一个的定制位置
具名插槽语法:
<div class="dialog-header">
<slot name="head"></slot>
</div>
<div class="dialog-content">
<slot name="content"></slot>
</div>
<div class="dialog-footer">
<slot name="footer"></slot>
</div>
<MyDialog>
<template v-slot:head>
大标题
</template>
<template v-slot:content>
内容文本
</template>
<template v-slot:footer>
<button>按钮</button>
</template>
</MyDialog>
组件内 有多处不确定的结构 怎么办?
具名插槽
v-slot:插槽名 可以简化成什么?
#插槽名
作用域插槽: 定义 slot 插槽的同时, 是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以用。
场景:封装表格组件
通过 作用域插槽 传值绑定,进而使用
<MyTable :list="list">
<button>删除</button>
</MyTable>
<MyTable :list="list2">
<button>查看</button>
</MyTable>
基本使用步骤:
<slot :id="item.id" msg="测试文本"></slot>
{ id: 3, msg: '测试文本' }
#插槽名= "obj"
接收,默认插槽名为 default<MyTable :list="list">
<template #default="obj">
<button @click="del(obj.id)">删除</button>
</template>
</MyTable>
作用域插槽的作用是什么?
可以给插槽上绑定数据,供将来使用组件时使用
作用域插槽使用步骤?
(1)给 slot 标签, 以 添加属性的方式传值
(2)所有属性都会被收集到一个对象中
(3)template中, 通过 #插槽名= "obj"
接收
需求说明:
(1) 双击显示输入框,输入框获取焦点
(2) 失去焦点,隐藏输入框
(3) 回显标签信息
(4) 内容修改,回车 → 修改标签信息
(1) 动态传递表格数据渲染
(2) 表头支持用户自定义
(3) 主体支持用户自定义
商品列表的实现封装了几个组件?
2个组件,标签组件 和 表格组件
封装用到的核心技术点有哪些?
(1)props父传子 $emit子传父 v-model
(2)$nextTick 自定义指令
(3)插槽:具名插槽,作用域插槽
●单页面应用(SPA): 所有功能在 一个html页面 上实现
● 具体示例: 网易云音乐 https://music.163.com/
开发分类 | 实现方式 | 页面性能 | 开发效率 | 用户体验 | 学习成本 | 首屏加载 | SEO |
---|---|---|---|---|---|---|---|
单页 | 一个html页面 | 按需更新性能高 | 高 | 非常好 | 高 | 慢 | 差 |
多页 | 多个html页面 | 整页更新性能低 | 中等 | 一般 | 中等 | 快 | 优 |
单页面应用
系统类网站 / 内部网站 / 文档类网站 /移动端站点
VS
多页面应用
公司官网 / 电商类网站
所有功能在一个html页面上实现
优点:按需更新性能高,开发效率高,用户体验好
缺点:学习成本,首屏加载慢,不利于SEO
系统类网站 / 内部网站 / 文档类网站 /移动端站点
单页面应用程序,之所以开发效率高,性能高,用户体验好
最大的原因就是:页面按需更新
要按需更新,首先就需要明确:访问路径 和 组件的对应关系!
访问路径 和 组件的对应关系如何确定呢? 路由
生活中的路由:设备和ip的映射关系
Vue中路由:路径 和 组件 的 映射 关系
路由是一种映射关系
路径 和 组件 的映射关系
根据路由就能知道不同路径的,应该匹配渲染哪个组件
目标:认识插件 VueRouter,掌握 VueRouter 的基本使用步骤
作用:修改地址栏路径时,切换显示匹配的组件
说明:Vue 官方的一个路由插件,是一个第三方包
官网:https://v3.router.vuejs.org/zh/
5个基础步骤 (固定)
① 下载: 下载 VueRouter 模块到当前工程,版本3.6.5
yarn add vue-router@3.6.5
② 引入
import VueRouter from 'vue-router'
③ 安装注册
Vue.use(VueRouter)
④ 创建路由对象
const router = new VueRouter()
⑤ 注入,将路由对象注入到new Vue实例中,建立关联
new Vue({
render: h => h(App),
router
}).$mount('#app')
VueRouter 的 使用 (5 + 2)
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 },
]
})
② 配置导航,配置路由出口(路径匹配的组件显示的位置)
<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 官方插件 VueRouter
5个基础步骤
① 下包 ② 引入 ③ Vue.use 安装注册
④ 创建路由对象 ⑤ 注入Vue实例
2个核心步骤
① 创建组件,配置规则 (路径组件的匹配关系 )
② 配导航,配置路由出口 router-view (组件展示的位置)
注意:.vue文件 本质无区别。
路由相关的组件,为什么放在 views 目录呢? 组件分类
组件存放目录问题
import Find from './views/Find.vue'
import My from './views/My.vue'
import Friend from './views/Friend.vue'
组件分类:Vue文件分2类;页面组件&复用组件 注意:都是==.vue文件(本质无区别)==
分类开来 更易维护
●src/views文件夹
●页面组件 - 页面展示 - 配合路由用
● src/components文件夹
● 复用组件 - 展示数据 - 常用于复用
小练习:以下 .vue 文件,属于什么分类组件?应该放在哪个目录?
页面组件 和 复用组件,便于维护
页面组件 - views文件夹 => 配合路由,页面展示
复用组件 - components文件夹 => 封装复用
路由进阶
① 路由模块封装
② 声明式导航 & 导航高亮 / 精确匹配&模糊匹配 / 自定义高亮类名
声明式导航传参 ( 查询参数传参 & 动态路由传参 )
③ 路由重定向 / 路由404 / 路由模式
④ 编程式导航 / 编程式导航传参 ( 查询参数传参 & 动态路由传参 )
面经基础版
一级路由 / 二级路由 / 导航高亮 / 请求渲染 / 路由传参 / 缓存组件
问题:所有的路由配置都堆在main.js中合适么?
目标:将路由模块抽离出来。 好处:拆分模块,利于维护
绝对路径:@指代src目录,可以用于快速引入组件
路由模块的封装抽离的好处是什么?
拆分模块,利于维护
以后如何快速引入组件?
基于 @ 指代 src 目录,从 src 目录出发找组件
需求:实现导航高亮效果
vue-router 提供了一个全局组件 router-link (取代 a 标签)
① 能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #
② 能高亮,默认就会提供高亮类名,可以直接设置高亮样式
vue-router提供的全局组件, 用于替换 a 标签
<router-link to=“/路径值” >
</router-link>
必须传入to属性, 指定路由路径值
能跳转,能高亮 (自带激活时的类名)
说明:我们发现 router-link 自动给当前导航添加了 两个高亮类名
① router-link-active 模糊匹配 (用的多)
to=“/my” 可以匹配 /my /my/a /my/b …
② router-link-exact-active 精确匹配
to=“/my” 仅可以匹配 /my
小结
router-link 会自动给当前导航添加两个类名,有什么区别呢?
router-link-active 模糊匹配 (用的多)
router-link-exact-active 精确匹配
说明:router-link 的 两个高亮类名 太长了,我们希望能定制怎么办?
const router = new VueRouter({
routes: [...],
linkActiveClass: "类名1",
linkExactActiveClass: "类名2"
})
小结
如何自定义 router-link 的 两个高亮类名?
linkActiveClass 模糊匹配 类名自定义
linkExactActiveClass 精确匹配 类名自定义
目标:在跳转路由时, 进行传值
查询参数传参
动态路由传参
查询参数传参
① 语法格式如下
● to=“/path?参数名=值”
② 对应页面组件接收传递过来的值
● $route.query.参数名
① 配置动态路由
const router = new VueRouter({
routes: [
...,
{
path: '/search/:words',
component: Search
}
]
})
② 配置导航链接
l to=“/path==/参数值==”
③ 对应页面组件接收传递过来的值
l $route==.params.参数名==
声明式导航跳转时, 有几种方式传值给路由页面?
① 查询参数传参 (多个参数)
跳转:to=“/path==?参数名=值==”
接收:$route.query.参数名
② 动态路由传参 (简洁优雅)
路由: /path/:参数名
跳转: to=“/path==/值==”
接收:$route.params.参数名
**问题:**配了路由 path: “/search/:words” 为什么按下面步骤操作,会未匹配到组件,显示空白?
原因: /search/:words 表示,必须要传参数。如果不传参数,也希望匹配,可以加个可选符 “?”
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search }
]
})
**问题:**网页打开, url 默认是 / 路径,未匹配到组件时,会出现空白
**说明:**重定向 → 匹配path后, 强制跳转path路径
语法: { path: 匹配路径, redirect: 重定向到的路径 },
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home'},
{ path: '/home', component: Home },
{ path: '/search/:words', component: Search }
]
})
**作用:**当路径找不到匹配时,给个提示页面
**位置:**配在路由最后
**语法:**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 }
]
})
问题: 路由的路径看起来不自然, 有#,能否切成真正路径形式?
● hash路由(默认) 例如: http://localhost:8080/#/home
●history路由(常用) 例如: http://localhost:8080/home (以后上线需要服务器端支持)
const router new VueRouter({
routes,
mode:"history
}
问题:点击按钮跳转如何实现?
编程式导航:用JS代码来进行跳转
两种语法:
① path 路径跳转
② name 命名路由跳转
① path 路径跳转 (简易方便)
this.$router.push('路由路径')
this.$router.push({
path: '路由路径'
})
② name 命名路由跳转 (适合 path 路径长的场景)
this.$router.push({
name: '路由名'
})
{ name: '路由名', path: '/path/xxx', component: XXX },
问题:点击搜索按钮,跳转需要传参如何实现?
两种传参方式:查询参数 + 动态路由传参
两种跳转方式,对于两种传参方式都支持:
① path 路径跳转传参
② name 命名路由跳转传参
① path 路径跳转传参 (query 查询参数 传参)
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
this.$router.push({
path: '/路径',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
② name 命名路由跳转传参 (query传参)
this.$router.push({
name: '路由名字',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
//$route.query.参数名 接收
① path 路径跳转传参 (动态路由传参)
this.$router.push('/路径/参数值')
this.$router.push({
path: '/路径/参数值'
})
② name 命名路由跳转传参 (动态路由传参)
this.$router.push({
name: '路由名字',
params: {
参数名: '参数值',
}
})
编程式导航有几种跳转方式 ?
① 通过路径跳转 (简易方便)
② 通过路由名字跳转 (适合路径名字长的场景)
this.$router.push('路由路径')
this.$router.push({
path: '路由路径'
})
this.$router.push({
name: '路由名'
})
{ name: '路由名', path: '/path/xxx', ... },
编程式导航,如何跳转传参 ?
① query传参
this.$router.push({
name: '路由名字',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
② 动态路由传参 (需要配动态路由)
this.$router.push({
name: '路由名字',
params: {
参数名: '参数值',
}
})
分析:配路由 + 实现功能
① 首页 和 面经详情,两个一级路由
② 首页内嵌四个可切换页面 (嵌套二级路由)
① 首页请求渲染
② 跳转传参 到 详情页,详情页渲染
③ 组件缓存,优化性能
问题:从面经 点到 详情页,又点返回,数据重新加载了 → 希望回到原来的位置
原因:路由跳转后,组件被销毁了,返回回来组件又被重建了,所以数据重新被加载了
解决:利用 keep-alive 将组件缓存下来
1. keep-alive是什么
keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。
2. keep-alive的优点
在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,
减少加载时间及性能消耗,提高用户体验性。
<template>
<div class="h5-wrapper">
<keep-alive>
<router-view></router-view>
</keep-alive>
</div>
</template>
问题:缓存了所有被切换的组件
3. keep-alive的****三个属性
① include : 组件名数组,只有匹配的组件会被缓存
② exclude : 组件名数组,任何匹配的组件都不会被缓存
③ max : 最多可以缓存多少组件实例
4. keep-alive的使用会触发两个生命周期函数
activated 当组件==被激活(使用)==的时候触发 → 进入这个页面的时候触发
deactivated 当组件不被使用的时候触发 → 离开这个页面的时候触发
组件缓存后就不会执行组件的created, mounted, destroyed 等钩子了
所以其提供了actived 和 deactived钩子,帮我们实现业务需求。
activated () {
console.log('actived 激活 → 进入页面');
},
deactivated() {
console.log('deactived 失活 → 离开页面');
}
Vue 的内置组件,包裹动态组件时,可以缓存
组件切换过程中 把切换出去的组件保留在内存中(提升性能)
① include : 组件名数组,只有匹配的组件会被缓存
② exclude : 组件名数组,任何匹配的组件都不会被缓存
③ max : 最多可以缓存多少组件实例
activated 当组件==被激活(使用)==的时候触发 → 进入页面触发
deactivated 当组件不被使用的时候触发 → 离开页面触发
① 配路由 ② 实现页面功能
children
① 查询参数传参,$route.query.参数名 (适合多个参数)
② 动态路由传参,$route.params.参数名 (更简洁直观)
keep-alive
三个属性: include exclude max
两个钩子: activated deactivated
目标:基于 VueCli 自定义创建项目架子
目标:认识代码规范
代码规范:一套写代码的约定规则。例如:“赋值符号的左右是否需要空格” “一句结束是否是要加;” …
老话说:“没有规矩不成方圆” → 正规的团队 需要 统一的编码风格
JavaScript Standard Style 规范说明 https://standardjs.com/rules-zhcn.html
下面是这份规则中的一小部分:
l 字符串使用单引号 ‘abc’
l 无分号 const name = ‘zs’
l 关键字后加空格 if (name = ‘ls’) { … }
l 函数名后加空格 function name (arg) { … }
l 坚持使用全等 === 摒弃 ==
…
目标:学会解决代码规范错误
如果你的代码不符合 standard 的要求,ESlint 会跳出来刀子嘴,豆腐心地提示你。
比如:在main.js中随意做一些改动,添加一些分号,空行。
目标:学会解决代码规范错误
两种解决方案:
① 手动修正
根据错误提示来一项一项手动修改纠正。
如果你不认识命令行中的语法报错是什么意思,根据错误代码去 [ESLint 规则表] 中查找其具体含义。
② 自动修正
基于 vscode 插件 ESLint 高亮错误,并通过配置 自动 帮助我们修复错误。
// 当保存的时候,eslint自动帮我们修复错误
"editor.codeActionsOnSave": {
"source.fixAll": true
},
// 保存代码,不自动格式化
"editor.formatOnSave": false
目标:明确 vuex 是什么,应用场景,优势
1. 是什么:
vuex 是一个 vue 的 状态管理工具,状态就是数据。
大白话:vuex 是一个插件,可以帮我们管理 vue 通用的数据 (多组件共享的数据)
2. 场景:
① 某个状态 在 很多个组件 来使用 (个人信息)
② 多个组件 共同维护 一份数据 (购物车)
3. 优势:
① 共同维护一份数据,数据集中化管理
② 响应式变化
③ 操作简洁 (vuex提供了一些辅助函数)
目标:基于脚手架创建项目,构建 vuex 多组件数据共享环境
效果是三个组件, 共享一份数据:
● 任意一个组件都可以修改数据
● 三个组件的数据是同步的
目标:安装 vuex 插件,初始化一个空仓库
目标:明确如何给仓库 提供 数据,如何 使用 仓库的数据
State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储。
在 state 对象中可以添加我们要共享的数据。
// 创建仓库
const store = new Vuex.Store({
// state 状态, 即数据, 类似于vue组件中的data
// 区别:
// 1. data 是组件自己的数据
// 2. state 是所有组件共享的数据
state: {
count: 101
}
})
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。