Vue2.x技术精讲
目录:
1. Vue 快速上手
vue概念/创建实例/插值表达式/响应式特性/开发者工具
2. Vue 指令
v-html/v-show/v-if/v-else/v-on/v-bind/v-for/v-model
3. 综合案例-小黑记事本
列表渲染/删除功能/添加功能/底部统计/清空
一、vue是什么
概念:Vue 是一个用于构建用户界面的渐进式框架
1. 构建用户界面:基于数据渲染 出用户看到的页面。
2. 渐进式: 循序渐进 (学一点用一点)
Vue的两种使用方式:
-
Vue 核心包开发
-
场景:局部模块改造
-
-
Vue 核心包 & Vue插件 工程化开发
-
场景:整体开发
-
3. 框架:一套完整的项目解决方案
优点:大大提升开发效率 (70%)
缺点:需要理解记忆规则 -> 官网
二、创建一个vue实例
核心步骤:
1. 准备容器
2. 引包 (官网) - 开发版本 / 生产版本
3. 创建 Vue 实例 new Vue()
4. 指定配置项 -> 渲染数据(哪个容器,哪个数据)
+ el 指定容器
+ data 提供数据
引包:
Vue2 网址:v2.cn.vuejs.org
<!-- 创建Vue实例,初始化渲染
1. 准备容器 (Vue所管理的范围)
2. 引包
3. 创建实例
4. 添加配置项 => 完成渲染
-->
<div id="app">
{{msg}}
<h3>{{count}}</h3>
</div>
<!-- 引入的是开发版本的包 - 包含完整的注释和警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
// 一旦引入 Vuejs核心包,在全局环境下,就有了Vue构造函数
const app = new Vue({
// 通过 el配置选择器,指定 Vue 管理的是哪个盒子
el: '#app',
// 通过 data 提供数据
data: {
msg: 'hello 黑马',
count: 666
}
});
</script>
三、插值表达式
插值表达式是一种 Vue 的模板语法
<div id="app">
{{ msg }}
</div>
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. 语法: {{ 表达式 }}
<h3> {{ title }} </h3>
<p> {{ nickname.toUpperCase }}</p>
<p> {{ age >= 18? '成年' : '未成年' }} </p>
3. 注意点:
-
使用的数据必须存在
-
支持的是表达式,而非语句,比如:if、for...
-
不能在标签属性中使用 {{ }} 插值
<div id="app">
<p>{{msg}}</p>
<p>{{msg.toUpperCase()}}</p>
<p>{{msg + 'Nice to meet you'}}</p>
<p>{{ age >= 18 ? '成年' : '未成年' }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
msg: 'hello 黑马',
age: 18
}
});
</script>
四、响应式特性
我们已经掌握了基础的模板渲染,其实除了基本的模板渲染,Vue背后还做了大量工作。
比如:数据的响应式处理
-
响应式:数据变化,视图自动更新
<div id="app">
{{ msg }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
// data中的数据,是会被添加到实例上
// 1.访问数据,实例.属性名
// 2. 修改数据,实例.属性名 = 新值
const app = new Vue({
el: '#app',
// 响应式数据
data: {
msg: '你好,黑马'
}
})
</script>
Vue核心特征:响应式
数据改变,试图会自动更新
五、开发者工具安装
安装 Vue开发者工具:装插件调试 Vue 应用
(1) 通过谷歌应用商店安装 (国外网站)
(2) 极简插件:搜索 vue (选择第一个) ->下载 -> 开发者模式 -> 拖拽安装 -> 插件详情允许访问文件
(3) 打开 Vue 运行的页面,调试工具中 Vue 栏,即可查看修改数据,进行调试。
开发者模式
插件详情允许访问文件
六、Vue指令
Vue 会根据不同的 [ 指令 ],针对标签实现不同的 [功能]。
指令:带有 v- 前缀 的特殊 标签属性
<!-- Vue 指令: v- 前缀的标签属性 -->
<div v-html = "str"></div>
<!-- 普通标签属性 -->
<div class="box">...</div>
<div title="小张">...</div>
(一) 指令初始 v-html
作用:设置元素的 innerHTML
语法:v-html = "表达式"
div id="app">
<div v-html="msg"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
msg:
`<a href="http://itheima.com">黑马程序员</a>`
}
})
</script>
(二) 指令 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 隐藏
<div id="app">
<div v-show="flag" class="box">我是v-show控制的盒子</div>
<div v-if="flag" class="box">我是v-if控制的盒子</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
flag: false;
}
})
</script>
(三) 指令v-else和v-else-if
1. 作用:辅助 v-if 进行判断渲染
2. 语法:v-else v-else-if = "表达式"
3. 注意: 需要紧挨着 v-if 一起使用
<
div id="app">
<p v-if="gender === 1">性别:男</p>
<p v-else>性别:女</p>
<hr>
<P v-if="score >= 90">成绩评定为A: 奖励电脑一台</p>
<P v-else-if="score >= 70">成绩评定为B: 奖励周末郊游</p>
<P v-else-if="score >= 60">成绩评定为C: 奖励零食礼包</p>
<P v-else>成绩评定为D: 惩罚一周不能玩手机</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
gender: 1,
score: 80
}
})
</script>
(四) 指令 v-on
1. 作用: 注册事件 = 添加监听 + 提供处理逻辑
2. 语法:
方式1. v-on: 事件名 = "内联语句" // 内联语句:可执行代码
方式2. v-on: 事件名 = "methods中的函数名"
3. 简写:@事件名
button v-on:click="count++"> 按钮 </button>
button @click="count++"> 按钮 </button>
(1) 第一种事件注册方式
<div id="app">
<!-- <button v-on:click="count--">-</button> -->
// v-on: 可以用 @ 替换
<button @click="count = count - 2">+</button>
<span> {{ count }} </span>
<!-- <button v-on:click="count++">+</button> -->
// v-on: 可以用 @ 替换
<button @click="count = count + 2">+</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
count: 100
}
})
</script>
(2) 第二种注册事件方式
button @click="fn">-</button>
const app = new Vue({
el: '#app',
data: {
// 提供数据
count: 100
},
methods: {
// 提供处理逻辑函数
fn() {
console.log('提供逻辑代码')
}
}
})
<div id="app">
<button @click="fn">切换显示隐藏</button>
<h1 v-show="isShow">黑马程序员</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
isShow: true
},
methods: {
fn() {
// 让提供的所有methods中的函数,this都指向当前实例 (this === app)
// app.isShow = !app.isShow
this.isShow = !this.isShow
}
}
})
</script>
4. 注意:methods 函数内的 this 执行Vue 实例
(3) Vue指令v-on调用传参
<div id="app">
<div class="box">
<h3>小黑自动售货机</h3>
<button @click="buy(5)">可乐5元</button>
<button @click="buy(10)">咖啡10元</button>
</div>
<p>银行卡余额: {{ money }}</p>
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
money: 100
},
methods: {
buy(price) {
this.money -= price
}
}
})
</script>
(五) 指令 v-bind
1. 作用: 动态设置 html的 标签属性 -> src、url、title ...
2. 语法: v-bind:属性名= "表达式"
3. 注意:简写形式::属性名="表达式"
<div id="app">
<!-- v-bind:src => :src -->
<!-- <img v-bind:src="imgUrl" v-bind:title="msg" alt=""> -->
<img :src="imgUrl" :title="msg" alt="">
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
imgUrl: 'images/01.jpg',
msg: 'hello'
}
})
</script>
案例-波仔的学习之旅
核心思路分析:
-
数组存储图片路径 -> [图片1,图片2,图片3,...]
-
准备下标 index,数组 [下标] -> v-bind设置 src展示图片 -> 修改下标切换图片
<div id="app">
<button v-show="index > 0" @click="index--">上一页</button>
<div>
<img v-bind:src="list[index]" alt="">
</div>
<button v-show="index < list.length -1" @click="index++">下一页</button>
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
index: 0,
list: [
`images/bo1.jpg`,
`images/bo2.jpg`,
`images/bo3.jpg`,
`images/bo4.jpg`,
]
}
})
</script>
(六) 指令 v-for
1. 作用:基于数据循环,多次渲染整个元素 -> 数组、对象、数字 ...
2. 遍历数组语法:
v-for = "(item,index) in 数组"
-
item 每一项,index 下标
-
省略 index,: v-for = "item in 数组"
<div id="app">
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: ['西瓜', '苹果', '鸭梨', '榴莲']
}
})
</script>
案例-小黑的书架
明确需求:
-
基本渲染 -> for
-
删除功能
<div id="app">
<h3>小黑的书架</h3>
<ul>
<li v-for="item in booksList" :key="item.id">
<span>{{ item.name }}</span>
<span>{{item.author}}</span>
<!-- 注册点击事件 -> 通过 id 进行删除数组中的对应项 -->
<button @click="del(item.id)">删除</button>
</li>
</ul>
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
booksList: [
{ id: 1, name: '《红楼梦》', author: '曹雪芹' },
{ id: 2, name: '《西游记》', author: '吴承恩' },
{ id: 3, name: '《水浒传》', author: '施耐庵' },
{ id: 4, name: '《三国演义》', author: '罗贯中' },
]
},
methods: {
del(id) {
//通过 id 进行删除数组中的对应项 -> filter(不会改变原数组)
// filter: 根据条件,保留满足条件的对应项,得到一个新数组
this.booksList = this.booksList.filter(item => item.id != id)
}
}
})
</script>
(七) 指令 v-for的key
1. 语法: key属性 = "唯一标识"
2. 作用: 给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用。
v-for 的默认行为会尝试 原地修改数据 (就地复用)
<li v-for="(item,index) in xxx" :key="唯一值">
3. 注意点:
-
key的值只能是字符串或数字类型
-
key的值必须具有唯一性
-
推荐使用 id作为 key (唯一),不推荐使用index作为 key (会变化,不对应)
(八) 指令 v-model
1. 作用:给表单元素使用,双向数据绑定 -> 可以快速获取 或 设置 表单元素内容。
1. 数据变化 -> 视图自动更新
2. 视图变化 -> 数据自动更新
2. 语法: v-model = '变量'
<div id="app">
<!--
v-model 可以快速[获取]或[设置]表单元素的内容
-->
账户: <input type="text" v-model="username"><br><br>
密码: <input type="password" v-model="password"><br><br>
<button @click="login">登录</button>
<button @click="reset">重置</button>
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
password: ''
},
methods: {
login() {
console.log(this.username, this.password);
},
reset() {
this.username = ''
this.password = ''
}
}
})
</script>
综合案例 - 小黑记事本
功能需求:
-
列表渲染
-
删除功能
-
添加功能
-
底部统计和清空
Vue代码
<!-- 主体区域 -->
<section id="app">
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input v-model="todoName" placeholder="请输入任务" class="new-todo" />
<button @click="add" class="add">添加任务</button>
</header>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list" :key="item.id">
<div class="view">
<span class="index"> {{ index + 1 }} </span> <label>{{ item.name }}</label>
<button @click="del(item.id)" class="destroy"></button>
</div>
</li>
</ul>
</section>
<!-- 统计和清空 -> 如果没有任务了,底部隐藏 -> v-show-->
<footer class="footer" v-show="list.length > 0">
<!-- 统计 -->
<span class="todo-count">合计: <strong> {{ list.length }} </strong></span>
<!-- 清空 -->
<button @click="clear" class="clear-completed">
清空任务
</button>
</footer>
</section>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 添加功能
// 1.通过 v-model 绑定 输入框 -> 实时获取表单元素的内容
// 2.点击按钮,进行新增,往数组最前面加 unshift
const app = new Vue({
el: '#app',
data: {
todoName: '',
list: [
{ id: 1, name: '跑步一公里' },
{ id: 2, name: '跳绳200次' },
{ id: 3, name: '游泳100米' },
]
},
methods: {
del(id) {
this.list = this.list.filter(item => item.id !== id)
},
add() {
if (this.todoName.trim() === '') {
alert('请输入任务名称')
return
}
this.list.unshift({
id: +new Date(),
name: this.todoName
})
this.todoName = ''
},
clear() {
this.list = []
}
}
})
</script>
七、指令补充
(一) 指令修饰符
通过 "."指明一些指令 后缀,不同后缀封装了不同的处理操作 -> 为了简化代码
1. 按键修饰
@keyup.enter -> 键盘回车监听
2. v-model修饰符
v-model.trim -> 去除首尾空格
v-model.number ->转数字
3.事件修饰符
@事件名.stop -> 组织冒泡
@事件名.prevent -> 阻止默认行为
<div id="app">
<h3>@keyup.enter - > 监听键盘回车事件 </h3>
<input @keyup.enter="fn" v-model="username" type="text">
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
},
methods: {
fn() {
console.log('键盘回车的时候触发', this.username)
}
}
})
</script>
(二) v-bind操作class
v-bind对于样式控制的增强
为了方便开发者进行样式控制,Vue 扩展了 v-bind的语法,可以针对 class类名和 style行内样式进行控制。
语法:
:class="对象/数组"
1. 对象 -> 键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类
<div class="box" :class="{ 类名1: 布尔值,类名2: 布尔值 }"></div>
适合场景:一个类名,来回切换
2. 数组 -> 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表
<div class="box" :class="[ 类名1,类名2,类名3 ]"></div>
适合场景:批量添加或删除类
<div id="app">
<div class="box" :class="{ pink: true,big: true }">黑马程序员</div>
<div class="box" :class="['pink','pig']">黑马程序员</div>
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
案例:京东秒杀 tab 导航高亮
核心思路:
-
基于数据动态渲染 tab -> v-for
-
准备下标记录高亮的是哪一个 tab -> activeIndex
style.css
<style>
* {
margin: 0;
padding: 0;
}
ul {
display: flex;
border-bottom: 2px solid #e01222;
padding: 0 10px;
}
li {
width: 100px;
height: 50px;
line-height: 50px;
list-style: none;
text-align: center;
}
li a {
display: block;
text-decoration: none;
font-weight: bold;
color: #333333;
}
li a.active {
background-color: #e01222;
color: #fff;
}
</style>
<div id="app">
<ul>
<li v-for="(item,index) in list" :key="item.id" @click="activeIndex = index">
<a :class="{ active:index === activeIndex}" href="#">{{item.name}}</a>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
activeIndex: 0, // 记录高亮
list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]
}
})
</script>
(三) v-bind操作style
语法: :style = "样式对象"
<div class="box" :style="{ CSS属性名1: CSS属性值,CSS属性名2:CSS属性值2 }"></div>
<div id="app">
<div class="box" :style="{ width: '400px',height: '400px',backgroundColor: 'red'}"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
使用场景:某个具体属性的动态设置
(四) v-model应用于其他表单元素
常见的表单元素都可以用 v-model 绑定关联 -> 快速获取 或 设置表单元素的值。
它会根据控件类型自动选取正确的方法来更新元素。
<div id="app">
<h3>小黑学习网</h3>
性名:
<input type="text" v-model="username">
<br><br>
是否单身:
<input type="checkbox" v-model="isSingle ">
<br><br>
<!--
前置理解:
1.name: 给单选框加上 name属性,可以分组 ->同一组互相会排斥
2. value: 给单选框加上 value 属性, 用于提交给投胎的数据
结合 Vue -> v-model
-->
性别:
<input v-model="gender" type="radio" name="gender" value="1">男
<input v-model="gender" type="radio" name="gender" value="2">女
<br><br>
<!--
前置理解:
1. option 需要设置 value 值,提交给后台
2. select 的value值,关联了选中的 option 的 value值
结合 vue 使用 v-model
-->
所在城市:
<select v-model="cityId">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">广州</option>
<option value="104">深圳</option>
</select>
<br><br>
自我描述:
<br>
<textarea v-model="desc"></textarea>
<br><br>
<button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
isSingle: false,
gender: "1",
cityId: '102',
desc: ''
}
})
</script>
八、computed计算属性
概念:基于现在的数据,计算出来的新属性。依赖的数据变化,自动重新计算。
(一) 计算属性用法
语法:
-
声明在 computed 配置项中,一个计算属性对应一个函数
-
使用起来和普通属性一样使用 {{ 计算属性名 }}
<div id="app">
<h3>小黑的礼物清单</h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<!-- 目标:统计求和,求得礼物总数 -->
<p>礼物总数:{{ totalCount }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 3 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
computed: {
totalCount() { // totalCount是一个属性
// 基于现有的数据,编写求值逻辑
// 计算属性函数内部,可以直接通过 this 访问到 app 实例
// 需求: 对 this.list 数组里面的num进行求和 -> reduce
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
</script>
(二) 计算属性与methods方法
1. computed 计算属性:
作用:封装了一段对于数据的处理,求得一个结果。
语法:
1. 写在 computed配置项中
2. 作为属性,直接使用 -> this.计算属性 {{计算属性}}
2. methods 方法:
作用:给实例提供一个方法,调用以处理业务逻辑
语法:
1. 写在 methods配置项中
2. 作为方法,需要调用 -> this.方法名() {{方法名()}} @事件名 = "方法名"
3. computed 优势
在 methods 中也可以封装 computed属性,求得一个结果,不过,computed存在缓存特性.
4. 缓存特性 (提升性能):
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 -> 并再次缓存
<div id="app">
<h3>小黑的礼物清单🛒<span>{{ totalCount() }}</span></h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<p>礼物总数:{{ totalCount() }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 3 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
methods: {
totalCount() {
console.log('计算属性执行了'); // 使用多少次就调用多少次
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
// computed: {
// // 计算属性: 有缓存的,一旦计算出来结果,就会立即缓存
// // 下一次读取 -> 直接读缓存就行
// totalCount() {
// console.log('计算属性执行了'); // 只会读取一次
// let total = this.list.reduce((sum, item) => sum + item.num, 0)
// return total
// }
// }
})
</script>
(三) 计算属性完整写法
计算属性默认的简写,只能读取访问,不能 "修改"。
如果要"修改" -> 需要写计算属性的完整写法。
<div id="app">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<span> {{ funllName }} </span><br><br>
<button @click="changeName">改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备',
},
methods: {
changeName() {
this.funllName = '吕小布'
}
},
computed: {
// 简写 -> 获取,没有配置设置的逻辑
// funllName() {
// return this.firstName + this.lastName
// }
// 完整写法 -> 获取 + 设置
funllName: {
// (1) 当 fullName 计算属性,被获取求值时,执行get (有缓存,优先读取缓存)
// 会将返回值作为求值的结果
get() {
return this.firstName + this.lastName
},
// (2) 当fullName计算属性,被修改赋值时,执行set
// 修改的值,传递给set方法的形参
set(value) {
this.firstName = value.slice(0, 1)
this.lastName = value.slice(1)
}
}
}
})
</script>
综合案例 - 成绩案例
<div id="app" class="score-case">
<div class="table">
<table>
<thead>
<tr>
<th>编号</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody v-if="list.length > 0">
<tr v-for="(item,index) in list" :key="item.id">
<td> {{ index+1 }} </td>
<td> {{ item.subject }} </td>
<!-- 需求: 不及格的标红, <60 分,加上red类 -->
<td :class="{ red: item.score < 60 }"> {{ item.score }} </td>
<td><a @click.prevent="del(item.id)" href="#">删除</a></td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="5">
<span class="none">暂无数据</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<span>总分:{{ totalScore }}</span>
<span style="margin-left: 50px">平均分:{{ averageScore }} </span>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="form">
<div class="form-item">
<div class="label">科目:</div>
<div class="input">
<input type="text" placeholder="请输入科目" v-model.trim="subject" />
</div>
</div>
<div class="form-item">
<div class="label">分数:</div>
<div class="input">
<input type="text" placeholder="请输入分数" v-model.number="score" />
</div>
</div>
<div class="form-item">
<div class="label"></div>
<div class="input">
<button @click="add" class="submit">添加</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, subject: '语文', score: 100 },
{ id: 7, subject: '数学', score: 100 },
{ id: 12, subject: '英语', score: 100 },
],
subject: '',
score: ''
},
computed: {
totalScore() {
return this.list.reduce((sum, item) => sum + item.score, 0)
},
averageScore() {
if (this.list.length === 0) {
return 0
}
return (this.totalScore / this.list.length).toFixed(2)
}
},
methods: {
del(id) {
// console.log(id);
this.list = this.list.filter(item => item.id != id)
},
add() {
if (!this.subject) {
alert('请输入科目')
return
}
if (typeof this.score !== 'number') {
alert('请输入正确的成绩')
return
}
this.list.unshift({
id: +new Date(), subject: this.subject, score: this.score
})
this.subject = ''
this.score = ''
}
}
})
</script>
九、watch 侦听器 (监视器)
(一) watch 监视器用法
作用:监视数据变化,执行一些 业务逻辑 或 异步操作。
语法:
-
简单写法 -> 简单类型数据,直接监视
-
完整写法 -> 添加额外配置项
<div id="app">
<!-- 条件选择框 -->
<div class="query">
<span>翻译成的语言:</span>
<select>
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
</div>
<!-- 翻译框 -->
<div class="box">
<div class="input-wrap">
<textarea v-model="obj.words"></textarea>
<span><i>⌨️</i>文档翻译</span>
</div>
<div class="output-wrap">
<div class="transbox">{{result}}</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 接口地址:https://applet-base-api-t.itheima.net/api/translate
// 请求方式:get
// 请求参数:
// (1)words:需要被翻译的文本(必传)
// (2)lang: 需要被翻译成的语言(可选)默认值-意大利
// -----------------------------------------------
const app = new Vue({
el: '#app',
data: {
// words: ''
obj: {
words: '',
},
result: ''
},
watch: {
// js当中方法名不能出现特殊字符,必须加引号
// newValue新值,oldValue老值 (一般不用)
'obj.words'(newValue) {
// 防抖: 延迟执行 -> 干啥事先等一等,延迟一会,一段时间内没有再次出发,才执行
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
params: {
words: newValue
}
})
this.result = res.data.data
console.log(res.data.data);
}, 300)
}
}
})
</script>
(二) watch 监视器完整写法
完整写法 -> 添加额外==配置项
-
deep:true 对复杂类型深度监视
-
immediate: true 初始化立即执行一次handler 方法
语法:
data: {
obj: {
words: '苹果',
lang: 'italy'
},
},
watch: {
// watch完整写法
数据属性名: {
deep: true, // 深度监视
handler(newValue) {
console.log(newValue)
}
}
}
<div id="app">
<!-- 条件选择框 -->
<div class="query">
<span>翻译成的语言:</span>
<select v-model="obj.lang">
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
</div>
<!-- 翻译框 -->
<div class="box">
<div class="input-wrap">
<textarea v-model="obj.words"></textarea>
<span><i>⌨️</i>文档翻译</span>
</div>
<div class="output-wrap">
<div class="transbox">{{result}}</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 接口地址:https://applet-base-api-t.itheima.net/api/translate
// 请求方式:get
// 请求参数:
// (1)words:需要被翻译的文本(必传)
// (2)lang: 需要被翻译成的语言(可选)默认值-意大利
// -----------------------------------------------
const app = new Vue({
el: '#app',
data: {
// words: ''
obj: {
words: '小黑',
lang: 'italy'
},
result: ''
},
watch: {
obj: {
deep: true, // 深度监视
immediate: true, // 立即执行,一进入页面 handler就立即执行一次
handler(newValue) {
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
params: newValue
})
this.result = res.data.data
console.log(res.data.data);
}, 300)
}
}
}
})
</script>
Vue2 核心技术与实战
目录:
1. 生命周期
生命周期 & 生命周期四个阶段 / 生命周期钩子 / 生命周期案例
2.综合案例: 小黑记账清单
列表渲染(请求) /添加 /删除 /饼图渲染
3.工程化开发入门
工程化开发和脚手架 /项目运行流程 /组件化 /组件注册
4.综合案例: 小兔鲜首页
拆分模块-局部注册 /结构样式完善 /拆分组件-全局注册
一、Vue生命周期和生命周期四个阶段
思考:
1. 什么时候可以发送初始化渲染请求? (越早越好)
2. 什么时候可以操作dom? (至少dom得渲染出来)
Vue生命周期:一个Vue实例从创建到销毁的整个过程。
生命周期四个阶段:
1 创建 2 挂载 3 更新 4 销毁
二、Vue生命周期函数 (钩子函数)
Vue生命周期过程中,会自动运行一些函数,被称为 [生命周期钩子] —> 让开发者可以在 [特定阶段] 运行自己的代码。
<div id="app">
<h3> {{ title }} </h3>
<div>
<button @click="count--">-</button>
<span> {{ count }} </span>
<button @click="count++">+</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
count: 100,
title: '计数器'
},
// 1.创建阶段 (准备数据)
beforeCreate() {
console.log('beforeCreate 响应式数据准备好之前', this.count);
},
created() {
console.log('beforeCreate 响应式数据准备好之后', this.count);
},
// 2.挂载阶段 (渲染模板)
beforeMount() {
console.log('beforeMount 模块渲染之前', document.querySelector('h3').innerHTML);
},
mounted() {
console.log('beforeMount 模块渲染之后', document.querySelector('h3').innerHTML);
},
// 3.更新阶段 (修改数据 -> 更新视图)
beforeUpdate() {
console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
},
updated() {
console.log('Updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML);
},
// 4.卸载阶段
beforeDestroy() {
console.log('beforeDestory');
},
destroyed() {
console.log('destoryed,卸载前');
console.log('清除掉一些Vue以外的资源占用,定时器,延时器...');
}
})
</script>
(一) created 应用
created:响应式数据准备好了,可以开始发送初始化渲染请求。
<div id="app">
<ul>
<li v-for="(item,index) 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>
<script src=" https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
const app = new Vue({
el: '#app',
data: {
list: []
},
async created() {
// 1. 发送请求,获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')
// 2.将数据更新给 data 中的 list
this.list = res.data.data
}
})
</script>
(二) mounted 应用
mounted:模块渲染完成,可以开始操作DOM了
要求:获取焦点
<div class="container" id="app">
<div class="search-container">
<img src="https://www.itheima.com/images/logo.png" alt="">
<div class="search-box">
<input type="text" v-model="words" id="inp">
<button>搜索一下</button>
</div>
</div>
</div>
<script src=" https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
words: ''
},
// 核心思路:
// 1.等输入框渲染出来
// 2.让输入框获取焦点
mounted() {
// console.log(document.querySelector('#inp'));
document.querySelector('#inp').focus()
}
})
</script>
案例 - 小黑记账清单
<div id="app">
<div class="contain">
<!-- 左侧列表 -->
<div class="list-box">
<!-- 添加资产 -->
<form class="my-form">
<input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
<input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
<button @click="add" type="button" class="btn btn-primary">添加账单</button>
</form>
<table class="table table-hover">
<thead>
<tr>
<th>编号</th>
<th>消费名称</th>
<th>消费价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in list" :key="item.id">
<td> {{ index + 1}} </td>
<td> {{ item.name }} </td>
<td :class="{ red: item.price > 500 }"> {{ item.price.toFixed(2) }}</td>
<td><a @click="del(item.id)" href="javascript:;">删除</a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
</tr>
</tfoot>
</table>
</div>
<!-- 右侧图表 -->
<div class="echarts-box" id="main"></div>
</div>
</div>
<script src="echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 接口文档地址:
* https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
*
* 功能需求:
* 1. 基本渲染
* (1) 立刻发送请求获取数据 created
* (2) 拿到数据,存到data的响应式数据中
* (3) 结合数据,进行渲染 v-for
* (4) 消费统计 => 计算属性
* 2. 添加功能
* (1) 收集表单数据 v-model
* (2) 给添加按钮注册点击事件,发送添加请求
* (3) 需不需要重新渲染? 需要。
* 3. 删除功能
* (1) 注册点击事件,传参(传id)
* (2) 根据 id 发送删除请求
* 4. 饼图渲染
* (1) 初始化一个饼图
* (2) 根据数据实时更新饼图 echarts.setOption({ ... })
*
*/
const app = new Vue({
el: '#app',
data: {
list: [],
name: '',
price: ''
},
computed: {
totalPrice() {
return this.list.reduce((sum, item) => sum + item.price, 0)
}
},
created() {
this.getList()
},
mounted() {
this.myChart = echarts.init(document.querySelector('#main'))
this.myChart.setOption({
// 大标题
title: {
text: '消费账单列表',
left: 'center'
},
// 提示框
tooltip: {
trigger: 'item'
},
// 图例
legend: {
orient: 'vertical',
left: 'left'
},
// 数据项
series: [
{
name: '消费账单',
type: 'pie',
// 半径
radius: '50%',
data: [
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
})
},
methods: {
async getList() {
const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
params: {
creator: '小黑'
}
})
this.list = res.data.data
// 更新图表
this.myChart.setOption({
// 数据项
series: [
{
data: this.list.map(item => ({ value: item.price, name: item.name }))
}
]
})
},
async add() {
if (!this.name) {
alert('请输入消费名称')
return
}
if (typeof this.price !== 'number') {
alert('请输入正确的消费价格')
return
}
// 发送添加请求
const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
creator: '小黑',
name: this.name,
price: this.price
})
// 重新渲染一次
this.getList()
this.name = ''
this.price = ''
},
async del(id) {
// 根据 id 发送删除请求
const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
// 重新渲染
this.getList()
}
}
})
</script>
工程化开发入门
开发 Vue 的两种方式:
1. 核心包传统开发模式: 基于 html/css/js 文件,直接引入核心包,开发 Vue。
2. 工程化开发模式: 基于构建工具 (例如:webpack) 的环境中开发 Vue。
问题:
1. webpack 配置不简单
2. 雷同的基础配置
3. 缺乏统一标准
因此需要一个工具,生成标准化的配置 (Vue CLI)
一、脚手架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 模板文件
<body>
<!-- 兼容:给不支持 js的浏览器一个提示 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<!-- Vue所管理的容器,将来创建结构动态渲染这个容器 -->
<div id="app">
<!-- 工程化开发模式中:这里不再直接编写模板语法,通过App.vue提供结构渲染 -->
</div>
<!-- built files will be auto injected -->
</body>
2.运行流程
三、组件化开发
组件化: 一个页面 可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
好处:便于维护,利于复用 → 提升开发效率。
组件分类:普通组件、根组件。
比如:下面这个页面,可以把所有的代码都写在一个页面中,但是这样显得代码比较混乱,难易维护。咱们可以按模块进行组件划分
四、根组件 App.vue
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中使用组件的方式完成下面布局
<template>
<div class="app">
<!-- 头部组件 -->
<HmHeader></HmHeader>
<!-- 主体组件 -->
<HmMain></HmMain>
<!-- 底部组件 -->
<HmFooter></HmFooter>
</div>
</template>
<script>
import HmHeader from './components/HmHeader.vue';
import HmMain from './components/HmMain.vue';
import HmFooter from './components/HmFooter.vue';
export default {
components: {
HmHeader: HmHeader,
HmMain: HmMain,
HmFooter:HmFooter
}
}
</script>
将下方组件写入到components文件夹内
<template>
<div class="hm-header">
我是hm-header
</div>
</template>
<script>
export default {
}
</script>
<style>
.hm-header {
height: 100px;
line-height: 100px;
text-align: center;
font-size: 30px;
background-color: #8064a2;
color: white;
}
</style>
<template>
<div class="hm-main">
我是hm-main
</div>
</template>
<script>
export default {
}
</script>
<style>
.hm-main {
height: 400px;
line-height: 400px;
text-align: center;
font-size: 30px;
background-color: #f79646;
color: white;
margin: 20px 0;
}
</style>
<template>
<div class="hm-footer">
我是hm-footer
</div>
</template>
<script>
export default {
}
</script>
<style>
.hm-footer {
height: 100px;
line-height: 100px;
text-align: center;
font-size: 30px;
background-color: #4f81bd;
color: white;
}
</style>
如果 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
<template>
<button class="hm-button">通用按钮</button>
</template>
<script>
export default {
}
</script>
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
<template>
<div class="hm-header">
我是hm-header
<HmButton></HmButton>
</template>
<script>
export default {
}
</script>
HmMain.vue
<template>
<div class="hm-main">
我是hm-main
<HmButton></HmButton>
</template>
<script>
export default {
}
</script>
目录
1. 组件的三大组成部分 (结构 /样式 /逻辑)
scoped样式冲突 / data是一个函数
2. 组件通信
组件通信语法 /父传子 /子传父 /非父子(扩展)
3. 综合案例:小黑记事本(组件版)
拆分组件 /渲染 /添加 /删除 /统计 /清空 /持久化
4. 进阶语法
v-model原理 /v-model应用于组件 /sync修饰符 /ref 和 $refs /$nextTick
六、组件注意点说明
组件的三大组成部分
(一) 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接收
-
模板中直接使用 props接收的值
父向子通信代码示例
父组件通过 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组件 {{ title }}
</div>
</template>
<script>
export default {
// 2.通过props进行接收
props: ['title']
}
</script>
<style>
</style>
子向父传值步骤
-
$emit触发事件,给父组件发送消息通知
-
父组件监听$emit触发的事件
-
提供处理函数,在函数的性参中获取传过来的参数
子向父通信代码示例
子组件利用 $emit 通知父组件,进行修改更新
父组件App.vue
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 2.父组件监听事件 -->
<Son :title="myTitle" @changeTitle="changeFn"></Son>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
myTitle: '学前端,就来黑马程序员',
}
},
methods: {
// 3. 提供处理函数,形参中获取参数
changeFn(newTitle) {
this.myTitle = newTitle
}
}
</script>
<style>
</style>
子组件 Son.vue:
<template>
<div class="son" style="border:3px solid #000;margin:10px">
我是Son组件 {{ title }}
</div>
</template>
<script>
export default {
props: ['title']
methods: {
handleClick(){
// 1. 通过$emit触发事件,给父组件发送消息通知
this.$emit('changeTitle','传智教育') // 传智教育(实参)传给父组件
}
}
}
</script>
<style>
</style>
四、props详解
1. Props 定义
组件上 注册的一些 自定义属性
2. Props 作用
向子组件传递数据
3. 特点
-
可以 传递 任意数量 的prop
-
可以 传递 任意类型 的prop
4. 代码演示
父组件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校验
1. 思考
组件的props可以乱传吗
2. 作用
为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误
3. 语法
-
类型校验
-
非空校验
-
默认值
-
自定义校验
4. 代码演示
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校验完整写法
1.语法
props: {
校验的属性名: {
type: 类型, // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
},
2. 代码实例
<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>
3.注意
1.default 和 required 一般不同时写(因为当时必填项时,肯定是有值的)
2.default 后面如果是简单类型的值,可以直接写默认。如果是复杂类型的值,则需要以函数的形式return一个默认值
四、props&data、单向数据流
1.共同点
都可以给组件提供数据
2.区别
-
data 的数据是 自己 的 → 随便改
-
prop 的数据是 外部 的 → 不能直接改,要遵循 单向数据流
3.单向数据流:
父级props 的数据更新,会向下流动,影响子组件。这个数据流动是单向的
4.代码演示
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 事件总线
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(接受方)
<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>
六、非父子通信-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.代码示例
<template>
<div class="app">
<input v-model="msg1" type="text" />
<br />
<!-- 模块中获取事件的形参, —> $event 获取 -->
<input :value="msg2" @click="msg2 = $event.target.value" type="text" />
</div>
</template>
<script>
export default {
data() {
return {
msg1: '',
msg2: '',
}
},
}
</script>
<style>
</style>
5.v-model使用在其他表单元素上的原理
不同的表单元素, v-model 在底层的处理机制是不一样的。比如给checkbox 使用 v-model
底层处理的是 checked属性和change事件。
不过咱们只需要掌握应用在文本框上的原理即可
一、表单类组件封装
父传子: 数据 应该是父组件 props 传递 过来的,v-model 拆解 绑定数据
子传父: 监听输入,子传父传值给父组件修改
需求目标
实现 子组件 和 父组件 数据的双向绑定 (实现App.vue中的selectId和子组件选中的数据进行双向绑定)
代码演示
App.vue
<template>
<div class="app">
:cityId="selectId"
@changeId = "selectId = $event"
<BaseSelect></BaseSelect>
</div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data() {
return {
selectId: '102',
}
},
components: {
BaseSelect,
},
}
</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){
this.$emit('changeId', e.target.value)
}
}
}
</script>
<style>
</style>
二、v-model简化代码
1.目标:
父组件通过v-model 简化代码,实现子组件和父组件数据 双向绑定
2.如何简化:
v-model其实就是 :value和@input事件的简写
-
子组件:props通过value接收数据,事件触发 input
-
父组件:v-model直接绑定数据
3.代码示例
子组件
<select :value="value" @change="handleChange">...</select>
props: {
value: String
},
methods: {
handleChange (e) {
this.$emit('input', e.target.value)
}
}
父组件
<BaseSelect v-model="selectId"></BaseSelect>
.sync修饰符
1.作用
可以实现 子组件 与 父组件数据 的 双向绑定,简化代码
简单理解:子组件可以修改父组件传过来的props值
2.特点
prop属性名,可以自定义,非固定为 value
3.场景
封装弹框类的基础组件, visible属性 true显示 false隐藏
4.本质
.sync修饰符 就是 :属性名 和 @update:属性名 合写
5.语法
父组件
//.sync写法
<BaseDialog :visible.sync="isShow" />
--------------------------------------
//完整写法
<BaseDialog
:visible="isShow"
@update:visible="isShow = $event"
/>
子组件
props: {
visible: Boolean
},
this.$emit('update:visible', false)
6.代码示例
App.vue
<template>
<div class="app">
<button @click="openDialog">退出按钮</button>
<BaseDialog :isShow="isShow"></BaseDialog>
</div>
</template>
<script>
import BaseDialog from './components/BaseDialog.vue'
export default {
data() {
return {
isShow: false,
}
},
components: {
BaseDialog,
},
}
</script>
<style>
</style>
BaseDialog.vue
<template>
<div class="base-dialog-wrap" v-show="isShow">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button class="close">x</button>
</div>
<div class="content">
<p>你确认要退出本系统么?</p>
</div>
<div class="footer">
<button>确认</button>
<button>取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isShow: Boolean,
}
}
</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
1.作用
利用 ref 和 $refs 可以用于 获取 dom 元素 或 组件实例
2.特点:
查找范围 → 当前组件内(更精确稳定)
获取 dom:
-
目标标签 - 添加 ref 属性
<div ref="chartRef">我是渲染图表的容器</div>
-
恰当时机,通过 this.$refs.xxx(ref名),获取目标标签
mounted(){
console.log(this.$refs.chartRef)
},
代码示例
App.vue
<template>
<div class="app">
<BaseChart></BaseChart>
</div>
</template>
<script>
import BaseChart from './components/BaseChart.vue'
export default {
components:{
BaseChart
}
}
</script>
<style>
</style>
BaseChart.vue
<template>
<div class="base-chart-box" ref="baseChartBox">子组件</div>
</template>
<script>
// yarn add echarts 或者 npm i echarts
import * as echarts from 'echarts'
export default {
mounted() {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.querySelect('.base-chart-box'))
// 绘制图表
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>
获取组件:
-
目标组件 - 添加 ref 属性
<BaseForm ref="baseForm"></BaseForm>
-
恰当时机,通过 this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法
this.$refs.baseForm.组件方法()
代码实例:
App.vue
<template>
<div class="app">
<BaseForm></BaseForm>
<button @click="handleGet">获取数据</button>
<button @click="handleSet>重置数据</button>
</div>
</template>
<script>
import BaseForm from './components/BaseForm.vue';
export default {
data () {
return {
}
},
methods: {
handleGet (){
console.log(this.$refs.baseForm);
},
handleReset () {
this.$refs.baseForm.resetValues()
}
},
components: {
BaseForm
}
}
</script>
BaseForm.vue
<template>
<form action="">
账户:<input type="text" v-model="account">
密码: <input type="password" v-model="password">
</form>
</template>
<script>
export default {
data (){
return {
account: '',
password: ''
}
},
methods: {
// 方法1: 收集表单数据,返回一个对象
getValues () {
return {
account: this.account,
password: this.password
}
},
// 方法2: 重置表单
resetValues () {
this.account = ''
this.password = ''
}
}
}
</script>
异步更新 & $nextTick
1.需求
编辑标题, 编辑框自动聚焦
-
点击编辑,显示编辑框
-
让编辑框,立刻获取焦点
this.isShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点
2.代码实现
<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() {
// 1.显示输入框 (异步dom更新)
this.isShowEdit = true
// 获取焦点
// this.$refs.inp.focus()
// $nextTick等dom更新完,立刻执行准备的函数体
this.$nextTick(() => {
this.$refs.inp.focus()
})
}
},
}
</script>
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()
}
}
}
使用:
<input v-指令名 type="text">
全局注册
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')
<template>
<div>
<h1>自定义指令</h1>
<input v-focus type="text">
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
局部注册
<template>
<div>
<h1>自定义指令</h1>
<input v-focus type="text">
</div>
</template>
<script>
export default {
directives: {
focus: {
inserted (el) {
el.focus()
}
}
}
}
</script>
<style>
</style>
三、自定义指令 - 指令的值
需求:实现一个 color 指令 - 传入不同的颜色,给标签设置文字颜色
-
语法:在绑定指令时,可以通过 "等号" 的形式为指令绑定 具体的参数值
<div v-color="color">我是内容</div>
-
通过 binding.value可以拿到指令值,指令值修改会 触发 updata 函数
directives: {
color: {
inserted (el,binding) {
el.style.color = binding.value
},
updata (el,binding) {
el.style.color = binding.value
}
}
}
<template>
<div>
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
</div>
</template>
<script>
export default {
data () {
return {
color1: 'red',
color2: 'green'
}
},
directives: {
color: {
// 1. inserted 提供的是元素被添加到页面中时触发
inserted (el,binding) {
el.style.color = binding.value
},
// 2. update 指令的值被修改的时候触发
update () {
el.style.color = binding.value
}
}
}
}
</script>
<style>
</style>
四、自定义指令 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
<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>
</template>
<script>
// 安装axios => yarn install axios
import axios from 'axios'
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data() {
return {
list: [],
isLoading: true
}
},
async created() {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')
setTimeout(() => {
// 2. 更新到 list 中
this.list = res.data.data
this.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></slot>
占位 -
使用组件时,
<MyDialog></MyDialog>
标签内部,传入结构替换 slot
App.vue
<template>
<div>
<!-- 2.在使用组件时,组件标签内填入内容 -->
<MyDialog>你确认要删除吗?</MyDialog>
<MyDialog>你确认要退出吗?</MyDialog>
</div>
</template>
<script>
import MyDialog from "./components/MyDialog.vue"
export default {
data() {
return {}
},
components: {
MyDialog,
},
}
</script>
<style>
body {
background-color: #b3b3b3;
}
</style>
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>
二、后备内容 (默认内容)
通过插槽完成了内容的定制,传什么显示什么,但是如果不传,则是空白
能否给插槽设置 默认显示内容呢?
插槽后备内容:封装组件时,可以为预留的 < slot >插槽提供后备内容 (默认内容)。
-
语法:在< slot >标签内,放置内容,作为默认显示内容
-
效果:
-
外部使用组件时,不传东西,则 slot 会显示后备内容
<MyDialog></MyDialog>
-
外部使用组件时,传东西了,则 slot整体会被换掉
<MyDialog>我是内容</MyDialog>
-
APP.vue
<template>
<div>
<MyDialog></MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
data() {
return {
}
},
components: {
MyDialog
}
}
</script>
<style>
body {
background-color: #b3b3b3;
}
</style>
MyDialog.vue
<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>
<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>
三、具名插槽
需求:一个组件内有多处结构,需要外部传入标签,进行定制
默认插槽:一个的定制位置
具名插槽语法:
-
多个 slot 使用 name属性 区分名字
<div class="dialog-header">
<slot name="head"></slot>
</div>
<div class="dialog-center">
<slot name="content"></slot>
</div>
<div class="dialog-footer">
<slot name="footer"></slot>
</div>
-
template 配合 v-slot: 名字来分发对应标签
<MyDialog>
<template v-slot:head>
大标题
</template>
<template v-slot:content>
内容文本
</template>
<template v-slot:footer>
<button>按钮</button>
</template>
</MyDialog>
-
v-slot:插槽名可以简化成 #插槽名
App.vue
<template>
<div>
<MyDialog>
<template #head>
<div>大标题</div>
</template>
<template #content>
<div>内容文本</div>
</template>
<template #footer>
<button>按钮</button>
</template>
</MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
data() {
return {
}
},
components: {
MyDialog
}
}
</script>
<style>
body {
background-color: #b3b3b3;
}
</style>
MyDialog.vue
<template>
<div class="dialog">
<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>
</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>
四、作用域插槽
作用域插槽:定义 slot插槽 的同时,是可以传值的。给插槽上可以绑定数据,将来使用组件时可以用。
场景:封装表格组件
-
父传子,动态渲染表格内容
-
利用默认插槽,定制操作列
-
删除或查看都需要用到当前项的 id,属于组件内部的数据
通过作用域插槽传值绑定,进而使用
基本使用步骤:
-
给 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>
App.vue
<template>
<div>
<MyTable :data="list">
<!-- 3. 通过template #插槽名="命令名"接收 -->
<template #default="obj">
<button @click="del(obj.row.id)">删除</button>
</template>
</MyTable>
<MyTable :data="list2">
<template #default=" { row } ">
<button @click="show(row)">查看</button>
</template>
</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 },
]
}
},
methods: {
del (id) {
this.list = this.list.filter(item => item.id !== id)
},
show (row) {
alert(`性名: ${row.name};年龄: ${row.age}`)
}
},
components: {
MyTable
}
}
</script>
MyTable.vue
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in data" :key="item.id">
<td> {{ index +1 }} </td>
<td> {{ item.name }} </td>
<td> {{ item.age }} </td>
<td>
<!-- 1. 给slot标签,添加属性的方式传值 -->
<slot :row="item" msg="测试文本"></slot>
<!-- 2. 将所有的属性,添加到一个对象中 -->
<!--
{
row: { id: 2, name: '孙大明', age: 19},
msg: '测试文本'
}
-->
</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>
单页应用程序 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 vue-router@3.6.5
-
引入
import VueRouter from 'vue-router'
-
安装注册
Vue.use(VueRouter)
-
创建路由对象
const router = new VueRouter()
-
注入,将路由对象注入到 new Vue 实例中,建立关联
new Vue({
render: h => h(App),
router
}).$mount('#app')
2个核心步骤
-
创建需要的组件 (views目录),配置路由规则
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>
APP.vue
<template>
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/friend">朋友</a>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style>
body {
margin: 0;
padding: 0;
}
.footer_wrap {
position: relative;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap a {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap a:hover {
background-color: #555;
}</style>
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
<template>
<div>
<p>发现音乐</p>
<p>发现音乐</p>
<p>发现音乐</p>
<p>发现音乐</p>
<p>发现音乐</p>
</div>
</template>
<script>
export default {
name: 'FindMusic'
}
</script>
<style>
</style>
另外两个相同。
四、组件存放目录问题
注意:.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 无需 #
-
能高亮,默认就会提供高亮类名,可以直接设置高亮样式
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</rout-link>
<router-link to="/my">我的音乐</rout-link>
<router-link to="/part">朋友</rout-link>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>
</template>
三、声明式导航 - 两个类名
说明:我们发现 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
<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>
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
<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>
(二) 动态路由传参
-
配置动态路由
const router = new VueRouter({
routes: [
{ path: '/search/:words', component: Search }
],
})
-
配置导航链接
-
to="/path/参数值"
-
-
对应页面组件接收传递过来的值
-
$route.params.参数名
-
// Home.vue
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
// Search.vue
<p>搜索关键字: {{ $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
<template>
<div>
<h1> 404 Page Not Found</h1>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
七、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
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
methods: {
// 通过路径的方式跳转:
// (1) this.$router.push('路由路径') [简写]
// (2) this.$router.push({ path: '路由路径'}) [完整写法]
goSearch () {
this.$router.push('/search')
}
}
}
</script>
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
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
methods: {
// 通过命名路由的方式跳转:(需要给路由起名字) 适合路径名字长的场景
goSearch () {
this.$router.push({
name: 'search'
})
}
}
}
</script>
二、路由传参
问题:点击搜索按钮,跳转需要传参如何实现?
两种传参方式: 查询参数 + 动态路由传参
两种跳转方式,对于两种传参方式都支持:
-
path 路径跳转传参
-
name 命名路由跳转传参
path 路径跳转传参:
this.$router.push('/路径/参数值')
this.$router.push({
path: '/路径/参数值'
})
查询参数传参
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input v-model="inpValue" type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
data () {
return {
inpValue: ''
}
},
methods: {
goSearch() {
// this.$router.push(`/search?key=${this.inpValue}`)
this.$router.push({
path: '/search',
query: {
key: this.inpValue
}
})
}
}
</script>
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.params.words);
}
}
</script>
动态路由传参
// 创建了一个路由对象
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
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input v-model="inpValue" type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
data () {
return {
inpValue: ''
}
},
methods: {
goSearch() {
this.$router.push(`/search/${this.inpValue}`)
}
}
}
</script>
Search.vue
<template>
<div class="search">
<p>搜索关键字: {{ $route.params.words }} </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.params.words);
}
}
</script>
name 命名路由跳转传参 (query传参)
this.$router.push({
name: '路由名',
query: {
参数名1: '参数值1',
参数名2: '参数值2',
}
})
Home.vue
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input v-model="inpValue" type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
data () {
return {
inpValue: ''
}
},
methods: {
goSearch() {
this.$router.push({
name: 'search',
query: {
key: this.inpValue
}
})
}
}
}
</script>
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.params.words);
}
}
</script>
第二种方式 (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"
})
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input v-model="inpValue" type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'FindMusic',
data () {
return {
inpValue: ''
}
},
methods: {
goSearch() {
this.$router.push({
name: 'search',
params: {
words: this.inpValue
}
})
}
}
}
</script>
search.vue
<template>
<div class="search">
<p>搜索关键字: {{ $route.params.words }} </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.params.words);
}
}
</script>
小结:
基于 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 这两个子组件
<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 = false
new 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 的使用 - 创建仓库
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, 如下
<h1>state的数据 - {{ $store.state.count }}</h1>
组件逻辑中使用
将state属性定义在计算属性中 State | Vuex
<h1>state的数据 - {{ count }}</h1>
// 把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'])
}
<div> state的数据:{{ count }}</div>
六、开启严格模式及Vuex的单项数据流
1.目标
明确 vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据
2.直接在组件中修改Vuex中state的值
Son1.vue
button @click="handleAdd">值 + 1</button>
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
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label>{{ this.$store.state.count}}</label>
<br>
<button @click="handleAdd">值 + 1</button>
<button @click="addFive">值 + 5</button>
<button @click="changeFn">改标题</button>
</div>
</template>
<script>
export default {
name: 'Son1Com',
methods: {
// 应该通过 musation 核心概念,进行修改数据
handleAdd () {
this.$store.commit('addCount')
},
addFive () {
this.$store.commit('addFive')
},
changeFn () {
this.$store.commit('ChangeTitle')
}
}
}
</script>
<style lang="css" scoped>
.box {
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>
八、带参数的 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
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label>{{ this.$store.state.count}}</label>
<br>
<button @click="handleAdd(1)">值 + 1</button>
<button @click="handleAdd(5)">值 + 5</button>
<button @click="handleAdd(10)">值 + 10</button>
<button @click="changeFn">改标题</button>
</div>
</template>
<script>
export default {
name: 'Son1Com',
methods: {
// 应该通过 musation 核心概念,进行修改数据
handleAdd (n) {
// 注意点: mutation参数有且只能有一个,如果需要多个参数,包装成一个对象或数组
// this.$store.commit('addCount', n)
this.$store.commit('addCount', n
}
)
},
changeFn () {
this.$store.commit('ChangeTitle','黑马程序员')
}
}
}
</script>
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
<template>
<div id="app">
<h1>根组件 - {{ title }} </h1>
<input type="text" :value="count" @input="handleInput">
<Son1></Son1>
<hr>
<Son2></Son2>
</div>
</template>
<script>
import Son1 from './components/Son1.vue'
import Son2 from './components/Son2.vue'
import { mapState } from 'vuex'
console.log(mapState(['count', 'title']))
export default {
name: 'app',
computed: {
...mapState(['count', 'title'])
},
data: function () {
return {
}
},
methods: {
handleInput (e) {
// 1. 实时获取输入框的值
const num = +e.target.value
// 2. 提交 mutation,调用mutation函数
this.$store.commit('changeCount', num)
}
},
components: {
Son1,
Son2
}
}
</script>
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
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label>{{ this.$store.state.count}}</label>
<br>
<button @click="addCount(1)">值 + 1</button>
<button @click="addCount(1)">值 + 1</button>
<button @click="addCount(5)">值 + 5</button>
<button @click="addCount(10)">值 + 10</button>
<button @click="handleChange">一秒后改成666</button>
<button @click="changeFn">改标题</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
name: 'Son1Com',
computed: {
...mapState(['count'])
},
methods: {
...mapMutations(['addCount']),
// 应该通过 musation 核心概念,进行修改数据
// handleAdd (n) {
// this.addCount(n)
// },
changeFn () {
this.$store.commit('ChangeTitle', {
title: '哈哈'
})
},
handleChange () {
// 调用 actions
this.$store.dispatch('changeCountAction', 666)
}
}
}
</script>
十三、辅助函数 -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'])
}
<div>{{ filterList }}</div>
十五、使用小结
模块 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
<!-- 测试访问模块中的getters - 原生 -->
<div>{{ $store.getters['user/UpperCaseName'] }}</div>
Son1.vue 直接访问getters
<!-- 测试访问模块中的getters - 原生 -->
<div>{{ $store.getters['user/UpperCaseName'] }}</div>
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
<button @click="updateUser">更新个人信息</button>
<button @click="updateTheme">更新主题色</button>
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
<button @click="setUser({ name: 'xiaoli', age: 80 })">更新个人信息</button>
<button @click="setTheme('skyblue')">更新主题</button>
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调用
<button @click="updateUser2">一秒后更新信息</button>
methods:{
updateUser2 () {
// 调用action dispatch
this.$store.dispatch('user/setUserSecond', {
name: 'xiaohong',
age: 28
})
},
}
Son2.vue mapActions映射
<button @click="setUserSecond({ name: 'xiaoli', age: 80 })">一秒后更新信息</button>
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)"