一、入门
1、安装 Vue
- 准备容器(vue所管理的范围):
<div id="app"></div>
- 引包(开发版本/生产版本)
- 下载地址:https://v2.cn.vuejs.org/v2/guide/installation.html
- 本地引入:
<script src="../vue.js"></script>
- 链接引入:
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
- 创建实例
- 引入 VueJS 核心包,在全局环境下就有了 Vue 构造函数:
- 添加配置 => 完成渲染
2、插值表达式
- 使用的数据必须在 data 中定义
- 支持的是表达式,而非语句如:if、for
- 不能在标签属性中使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>插值表达式</title>
</head>
<body>
<div id="app">
<!-- 插入 data 中的内容 -->
<h2>{{ msg }}</h2>
<!-- 插入数值 -->
<h2>{{ 12 }}</h2>
<!-- 插入字符串 -->
<h2>{{ "msg" }}</h2>
<!-- 插入对象 -->
<h2>{{ {id:1} }}</h2>
<!-- 表达式 -->
<h2>{{ 1>2 ? "true" : "false" }}</h2>
<!-- 反转字符串 -->
<h2>{{ txt.split('').reverse().join('') }}</h2>
<!-- 调用函数 -->
<h2>{{ getContent() }}</h2>
</div>
<!-- 引入 Vue 包 -->
<script src="./vue.js"></script>
<script>
new Vue({
// 通过 el 配置选择器
el: "#app",
// 数据属性
data: {
msg: "hello vue",
flag: false,
txt: "hello",
},
// 存放方法
methods: {
getContent() {
return "content:" + this.msg;
},
},
});
</script>
</body>
</html>
3、Vue 指令
1. v-text/v-html
<div id="app">
<!-- 插入文本 -->
<h2>{{ msg }}</h2>
<h2 v-text="msg"></h2>
<!-- 插入标签 -->
<div v-html="htmlMsg"></div>
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
msg: "插入文本",
htmlMsg: "<h2>插入标签</h2>",
},
// 存放方法
methods: {},
});
2. v-if/v-show
- v-if
- 作用: 控制元素显示隐藏(条件渲染)
- 语法: v-if = “表达式” 表达式值 true 显示, false 隐藏
- 原理: 基于条件判断,是否 创建 或 移除 元素节点
- 场景: 要么显示,要么隐藏,不频繁切换的场景
- v-show
- 作用: 控制元素显示隐藏
- 语法: v-show = “表达式” 表达式值 true 显示, false 隐藏
- 原理: 切换 display:none 控制显示隐藏
- 场景: 频繁切换显示隐藏的场景
<div id="app">
<!-- 条件渲染 -->
<div v-if="isShow">显示</div>
<div v-else>隐藏</div>
<h2 v-show="show">展示</h2>
<h2 v-show="hide">隐藏</h2>
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
isShow: Math.random() > 0.5,
show: true,
hide: false,
},
// 存放方法
methods: {},
});
3. v-else&v-else-if
- 作用: 辅助 v-if 进行判断渲染
- 语法: v-else v-else-if = “表达式”
- 注意: 需要紧挨着 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>
const app = new Vue({
el: '#app',
data: {
gender: 1,
score: 5
}
})
4. v-on
- 作用: 注册事件 = 添加监听 + 提供处理逻辑
- 语法:
- v-on:事件名 = “内联语句”
- v-on:事件名 = “methods中的函数名”
- 简写:@事件名
- 特殊事件
- v-on:click.once:只触发一次
- v-on:click.stop:阻止单击事件继续传播
- v-on:click.prevent:提交事件不再重载页面
- v-on:keyup.enter:绑定回撤按钮
<style>
.box {
width: 200px;
height: 200px;
background-color: red;
}
.active {
width: 200px;
height: 200px;
background-color: green;
}
</style>
<div id="app">
<!-- 绑定事件 -->
<h2>{{ num }}</h2>
<!-- 只触发一次 -->
<button v-on:click.once="bandleClick">点击+1(只触发一次)</button>
<button v-on:click="num++">点击+1</button>
<div class="box" :class="{active:isActive}"></div>
<button @click="changClick">切换样式</button>
<!-- 绑定回车 -->
<input @keyup.enter="submit" />
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
num: 0,
isActive: false,
},
// 存放方法
methods: {
bandleClick() {
this.num += 1;
},
changClick() {
this.isActive = !this.isActive;
},
submit() {
alert("ok");
},
},
});
5. v-bind
- 作用: 动态的设置html的标签属性
- 语法: v-bind:属性名=“表达式”
- 注意: 简写形式 :属性名=“表达式”
<div id="app">
<img v-bind:src="imgUrl" v-bind:title="msg" alt="">
<img :src="imgUrl" :title="msg" alt="">
</div>
const app = new Vue({
el: '#app',
data: {
imgUrl: './images/logo.png',
msg: 'hello vue'
}
})
6. v-for
- 作用: 基于数据循环, 多次渲染整个元素
- **遍历数组语法:**v-for = “(item, index) in 数组”
- item 每一项, index 下标
- 省略 index: v-for = “item in 数组”
<div id="app">
<h3>小黑水果店</h3>
<ul>
<li v-for="(item, index) in list">
{{ item }} - {{ index }}
</li>
</ul>
<ul>
<li v-for="item in list">
{{ item }}
</li>
</ul>
</div>
const app = new Vue({
el: '#app',
data: {
list: ['西瓜', '苹果', '鸭梨', '榴莲']
}
})
7. v-model
- 作用: 给 表单元素 使用, 双向数据绑定 → 可以快速 获取 或 设置 表单元素内容
- 数据变化 → 视图自动更新
- 视图变化 → 数据自动更新
- 语法: v-model = ‘变量’
<div id="app">
<p>{{msg}}</p>
<input type="text" v-model="msg" />
<input type="text" v-model.lazy="msg" />
<input type="text" v-model.trim="msg" />
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
msg: "尔尔",
},
// 存放方法
methods: {},
});
8. 指令修饰符
- 通过 “.” 指明一些指令 后缀,不同 后缀 封装了不同的处理操作
- 按键修饰符
- 键盘回车监听:
@keyup.enter
- 键盘回车监听:
- v-model修饰符
- 过滤首尾空白字符:
v-model.trim
- 输入值转为数值类型:
v-model.number
- 在
change
事件之后进行同步:v-model.lazy
- 过滤首尾空白字符:
- 事件修饰符
- 阻止冒泡:
@事件名.stop
- 阻止默认行为:
@事件名.prevent
- 阻止冒泡:
<div id="app">
<h3>@keyup.enter → 监听键盘回车事件</h3>
<input @keyup.enter="fn" v-model="username" type="text" />
<h3>v-model修饰符 .trim .number</h3>
姓名:<input v-model.trim="username" type="text" /><br />
年纪:<input v-model.number="age" type="text" /><br />
<h3>@事件名.stop → 阻止冒泡</h3>
<div @click="fatherFn" class="father">
<div @click.stop="sonFn" class="son">儿子</div>
</div>
<h3>@事件名.prevent → 阻止默认行为</h3>
<a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
</div>
const app = new Vue({
el: "#app",
data: {
username: "",
age: "",
},
methods: {
fn(e) {
// if (e.key === 'Enter') {
// alert('键盘回车的时候触发', this.username)
// }
alert("键盘回车的时候触发:" + this.username);
},
fatherFn() {
alert("老父亲被点击了");
},
sonFn(e) {
// e.stopPropagation()
alert("儿子被点击了");
},
},
});
<style>
.father {
width: 200px;
height: 200px;
background-color: pink;
margin-top: 20px;
}
.son {
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>
4、Vue特殊指令
1. :class
- v-bind 对于样式控制的增强,语法 :class = “对象/数组”
- 对象 → 键就是类名,值是布尔值:
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }">尔尔</div>
- 适用场景:一个类名,来回切换
- 数组 → 数组中所有的类,都会添加到盒子上:
<div class="box" :class="[类名1, 类名2]">尔尔</div>
- 适用场景:批量添加或删除类
<div id="app">
<div class="box" :class="{ pink: true, big: true }">尔尔</div>
<div class="box" :class="['pink', 'big']">尔尔</div>
</div>
const app = new Vue({
el: "#app",
data: {},
});
<style>
.box {
width: 200px;
height: 200px;
border: 3px solid #000;
font-size: 30px;
margin-top: 10px;
}
.pink {
background-color: pink;
}
.big {
width: 300px;
height: 300px;
}
</style>
2. :style
- v-bind 对于样式控制的增强:语法 :style = “样式对象”
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
- 适用场景:某个具体属性的动态设置
<div id="app">
<div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></div>
</div>
const app = new Vue({
el: "#app",
data: {},
});
<style>
.box {
width: 200px;
height: 200px;
background-color: rgb(187, 150, 156);
}
</style>
3. v-model:单个复选框
- 单个复选框,绑定到布尔值
<div id="app">
<!-- 复选框单选 -->
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{checked}}</label>
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
checked: false,
},
// 存放方法
methods: {},
});
4. v-model:多个复选框
- 多个复选框,绑定到同一个数组
<div id="app">
<!-- 复选框多选 -->
<div class="box">
<input type="checkbox" id="a" value="橘子" v-model="checkedNames" />
<label for="a">橘子</label><br />
<input type="checkbox" id="b" value="苹果" v-model="checkedNames" />
<label for="b">苹果</label><br />
<input type="checkbox" id="c" value="西瓜" v-model="checkedNames" />
<label for="c">西瓜</label><br />
<span>{{checkedNames}}</span>
</div>
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
msg: "尔尔",
checked: false,
checkedNames: [],
},
// 存放方法
methods: {},
});
5. v-model:单选按钮
<div id="example">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
new Vue({
el: '#example',
data: {
picked: ''
}
})
6. v-model:选择框
<div id="example">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '...',
data: {
selected: ''
}
})
7. v-for 列表渲染
- key属性 = “唯一标识”
- **作用:**给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用
- v-for 的默认行为会尝试 原地修改元素 (就地复用)
- 推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(会变化,不对应)
<div id="app">
<div>
<ul>
<!-- :key:跟踪节点标识 -->
<li v-for="item in menus" :key="item.id">
<span>id:{{item.id}},name:{{item.name}}</span>
<button @click="deleteById(item.id)">删除</button>
</li>
</ul>
<ul>
<li v-for="(item,index) in menus" :key="item.id">
<h3>{{index}}--id:{{item.id}},name:{{item.name}}</h3>
</li>
</ul>
<ol>
<li v-for="(value,key) in obj" :key="name">
<h3>{{key}}::{{value}}</h3>
</li>
</ol>
</div>
</div>
new Vue({
el: "#app",
data: {
menus: [
{ id: 1, name: "宫保鸡丁" },
{ id: 2, name: "鱼香肉丝" },
{ id: 3, name: "北京烤鸭" },
{ id: 4, name: "糖醋排骨" },
],
obj: {
title: "测试",
name: "erer",
age: "20",
},
},
methods: {
deleteById(id) {
// filter: 根据条件,保留满足条件的对应项,得到一个新数组。
this.menus = this.menus.filter(item => item.id !== id);
}
},
});
5、计算属性
1. 计算属性
- 概念:基于现有的数据,计算出来的新属性。 依赖的数据变化,自动重新计算。
- 语法:
- 声明在 computed 配置项中,一个计算属性对应一个函数
- 使用起来和普通属性一样使用 {{ 计算属性名 }}
- 计算属性 → 可以将一段 求值的代码 进行封装
- 缓存特性(提升性能):计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 → 并再次缓存
<div id="app">
<p>{{reverseMsg}}</p>
<p>{{fullName}}</p>
<button @click="handleClick">改变</button>
<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>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
msg: "hello erer",
firstName: "erer",
lastName: "尔尔",
list: [
{ id: 1, name: "篮球", num: 3 },
{ id: 2, name: "足球", num: 2 },
{ id: 3, name: "铅笔", num: 5 },
],
},
// 计算属性
computed: {
// computed默认只有get方法
// 最大优点:产生缓存,数据没有变化直接从缓存获取
reverseMsg: function () {
return this.msg.split("").reverse().join("");
},
fullName: function () {
return this.firstName + this.lastName;
},
totalCount() {
return this.list.reduce((sum, item) => sum + item.num, 0);
},
},
// 存放方法
methods: {
handleClick() {
this.msg = "computed";
},
},
});
<style>
table {
border: 1px solid #000;
text-align: center;
width: 240px;
}
th,
td {
border: 1px solid #000;
}
h3 {
position: relative;
}
</style>
2. computed&methods
- computed 计算属性:
- 作用:封装了一段对于数据的处理,求得一个结果。
- 语法:
- 写在 computed 配置项中
- 作为属性,直接使用 → this.计算属性、{{ 计算属性 }}
- methods 方法:
- 作用:给实例提供一个方法,调用以处理业务逻辑。
- 语法:
- 写在 methods 配置项中
- 作为方法,需要调用 → this.方法名( )、 {{ 方法名() }}、 @事件名=“方法名”
3. 计算属性完整写法
<div id="app">
<p>{{msg}}</p>
<input type="text" v-model="content" @input="handleInput" />
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
msg: "hello erer",
},
// 计算属性
computed: {
content: {
set: function (value) {
this.msg = value;
},
get: function () {
return this.msg;
},
},
},
// 存放方法
methods: {
handleInput: function (event) {
const { value } = event.target;
// this.msg = value;
this.content = value;
},
},
});
6、侦听器
<div id="app">
<p>{{msg}}</p>
<input type="text" v-model="msg" /> <br />
<input type="text" v-model="obj.words" /> <br />
<h3>{{students[0].name}}</h3>
<br />
<button @click='students[0].name="Tom"'>改变</button>
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
msg: "尔尔",
obj: {
words: "",
},
students: [{ name: "erer", age: 18 }],
},
watch: {
// key:属于 data 对象的属性名
msg: function (newMsg, oldMsg) {
console.log(newMsg, oldMsg);
},
// 防抖,延时执行
"obj.words"(newWords) {
clearTimeout(this.timer);
// 延时器ID
this.timer = setTimeout(async () => {
console.log(newWords);
}, 300);
},
// 深度监视:object、list
students: {
deep: true,
// 无法获取old值
handler: function (newStudents, oldStudents) {
console.log(newStudents[0].name, oldStudents[0].nam);
},
},
},
});
7、过滤器
1. 局部过滤器
<div id="app">
<h3>{{price | myFilter("$")}}</h3>
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
price: 100,
},
// 局部过滤器
filters: {
myFilter: function (price, type) {
return type + price;
},
},
// 计算属性
computed: {},
// 存放方法
methods: {},
});
2. 全局过滤器
<div id="app">
<h3>{{msg | myReverse}}</h3>
</div>
// 全局过滤器
Vue.filter("myReverse", (val) => {
return val.split("").reverse().join("");
});
8、音乐播放器
<div id="app">
<audio :src="getCurrentSongStr" controls autoplay @ended="handleEnded"></audio>
<ul>
<li :class="{active:index===currentIndex}" v-for="(item,index) in musicData" :key="item.id" @click="handleClick(index)">
<h2>{{item.id + 1}} - 歌名:{{item.name}}</h2>
<p>{{item.author}}</p>
</li>
</ul>
<button @click="handleNext">下一首</button>
</div>
const musicData = [
{
id: 0,
name: "于荣光 - 少林英雄",
author: "于荣光",
songSrc: "./static/于荣光 - 少林英雄.mp3",
},
{
id: 1,
name: "Joel Adams - Please Dont Go",
author: "Joel Adams",
songSrc: "./static/Joel Adams - Please Dont Go.mp3",
},
{
id: 2,
name: "MKJ - Time",
author: "MKJ",
songSrc: "./static/MKJ - Time.mp3",
},
{
id: 3,
name: "Russ - Psycho (Pt. 2)",
author: "Russ",
songSrc: "./static/Russ - Psycho (Pt. 2).mp3",
},
];
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
musicData,
currentIndex: 0,
},
// 存放方法
methods: {
handleClick(id) {
this.currentIndex = id;
},
handleEnded() {
this.handleNext();
},
handleNext() {
if (this.currentIndex === musicData.length - 1) {
this.currentIndex = 0;
} else {
this.currentIndex += 1;
}
},
},
// 计算属性
computed: {
getCurrentSongStr() {
return this.musicData[this.currentIndex].songSrc;
},
},
// 局部过滤器
filters: {},
});
<style>
* {
padding: 0;
margin: 0;
}
ul {
list-style: none;
}
ul li {
margin: 20px 20px;
padding: 10px 5px;
border-radius: 3px;
}
ul li.active {
background-color: #d2e2f3;
}
</style>
二、生命周期
1、四个阶段
- Vue生命周期:一个Vue实例从 创建 到 销毁 的整个过程。
- 生命周期四个阶段:
- 创建:响应式数据(data)、发送初始化渲染请求
- 挂载:渲染模板、操作 dom
- 更新:修改数据,更新视图
- 销毁:销毁实例
2、生命周期函数
- Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】
- 让开发者可以在【特定阶段】运行自己的代码。
- 创建阶段:
beforeCreate()
:组件创建之前created()
:组件创建完成
- 挂载阶段:
beforeMount()
:DOM挂载之前mounted()
:DOM挂载完成
- 更新阶段:
beforeUpdate()
:更新之前的DOMupdated()
:更新之后的DOM
- 销毁阶段:
beforeDestroy()
:销毁之前destroyed()
:销毁完成
3、案例一
<div id="app">
<h3>{{ title }}</h3>
<div>
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</div>
const app = new Vue({
el: '#app',
data: {
count: 100,
title: '计数器'
},
// 1. 创建阶段(准备数据)
beforeCreate() {
// beforeCreate 响应式数据准备好之前 undefined
console.log('beforeCreate 响应式数据准备好之前', this.count)
},
created() {
// created 响应式数据准备好之后 100
console.log('created 响应式数据准备好之后', this.count)
},
// 2. 挂载阶段(渲染模板)
beforeMount() {
// beforeMount 模板渲染之前 {{ title }}
console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
},
mounted() {
// mounted 模板渲染之后 计数器
console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
},
// 3. 更新阶段(修改数据 → 更新视图)
beforeUpdate() {
// beforeUpdate 数据修改了,视图还没更新 100
console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
},
updated() {
// updated 数据修改了,视图已经更新 101
console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML)
},
// 4. 卸载阶段:控制台输入 app.$destroy()
beforeDestroy() {
console.log('beforeDestroy, 卸载前')
console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
},
destroyed() {
console.log('destroyed,卸载后')
}
});
4、案例二
activated()
:组件激活deactivated()
:组件停用
<style>
.active {
color: red;
}
</style>
<div id="app">
<!-- 使用组件 -->
<App></App>
</div>
Vue.component("Test", {
data() {
return {
msg: "尔尔",
isRed: false,
};
},
template: `
<div>
<button @click = "handlerClick">改变</button>
<h3 :class='{active:isRed}'>{{msg}}</h3>
</div>
`,
methods: {
handlerClick() {
this.msg = "erer";
this.isRed = !this.isRed;
},
},
beforeCreate() {
// 组件创建之前:undefined
console.log("组件创建之前:", this.$data);
},
created() {
// 组件创建完成: {__ob__: Observer}
console.log("组件创建完成:", this.$data);
},
beforeMount() {
// DOM挂载之前: <app></app>
console.log("DOM挂载之前:", document.getElementById("app").innerHTML);
},
mounted() {
// DOM挂载完成: <div><div><button>改变</button> <h3>尔尔</h3></div></div>
console.log("DOM挂载完成:", document.getElementById("app").innerHTML);
},
beforeUpdate() {
// 更新之前的DOM: <div><div><button>改变</button> <h3>尔尔</h3></div></div>
console.log("更新之前的DOM:", document.getElementById("app").innerHTML);
},
updated() {
// 更新之后的DOM: <div><div><button>改变</button> <h3>erer</h3></div></div>
console.log("更新之后的DOM:", document.getElementById("app").innerHTML);
},
beforeDestroy() {
console.log("销毁之前");
},
destroyed() {
console.log("销毁完成");
},
// 配合 keep-alive 组件使用,使用缓存
activated() {
console.log("组件被激活了");
},
deactivated() {
console.log("组件被停用了");
},
});
const App = {
data() {
return {
isShow: true,
};
},
template: `
<div>
<keep-alive>
<Test v-if="isShow"></Test>
</keep-alive>
<button @click = "clickHandler">改变生死</button>
</div>
`,
methods: {
clickHandler() {
this.isShow = !this.isShow;
},
},
components: {},
};
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {},
// 挂载局部组件
components: {
App,
},
// 存放方法
methods: {},
// 局部过滤器
filters: {},
// 计算属性
computed: {},
});
5、加载数据
<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>
// 接口地址: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. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data;
}
});
6、获取焦点
<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>
const app = new Vue({
el: '#app',
data: {
words: ''
},
// 核心思路:
// 1. 等input框渲染出来 mounted 钩子
// 2. 让input框获取焦点 inp.focus()
mounted() {
document.querySelector('#inp').focus()
}
});
三、Vue-cli
1、安装 cli3
# 将默认下载地址改成淘宝镜像下载
npm config set registry https://registry.npm.taobao.org/
npm config get registry
# 清除缓存
npm cache clean --force
# 安装cli
npm install -g @vue/cli
# 验证
vue --version
# 创建项目
vue create project-name
# 可选 vue2/vue3
# 启动项目
npm run serve / yarn serve
2、目录介绍
- 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自动生成的,锁定安装版本
3、相关文件
1. index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<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"></div>
<!-- built files will be auto injected -->
</body>
</html>
2. main.js
- 导入 App.vue,基于 App.vue 创建结构渲染 index.html
// 导入 vue 核心包
import Vue from 'vue'
// 导入 App.vue 根组件
import App from './App.vue'
// 提示:当前处于什么环境(生成/开发)
Vue.config.productionTip = false
// vue实例化,提供 render 方法:基于 App.vue 创建结构渲染 index.html
new Vue({
// render: h => h(App),
render: (createElement) => {
return createElement(App);
}
}).$mount('#app')
// .$mount('#app') 等同于 el: "#app",用于指定 vue 管理的容器
3. App.vue
- template:结构 (有且只能一个根元素)
- script: js逻辑
- style: 样式 (可支持less,需要装包)
- style添加 lang=“less”
- 安装依赖:yarn add less less-loader -D / npm install less less-loader --save-dev
<template>
<!-- <div id="app"> -->
<div class="App">
<div class="box">
111
</div>
</div>
<!-- <img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App" /> -->
<!-- </div> -->
</template>
<script>
// import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
// components: {
// HelloWorld,
// },
};
</script>
<style lang="less">
// #app {
// font-family: Avenir, Helvetica, Arial, sans-serif;
// -webkit-font-smoothing: antialiased;
// -moz-osx-font-smoothing: grayscale;
// text-align: center;
// color: #2c3e50;
// margin-top: 60px;
// }
/**
* 让 style 支持 less
* 1、style添加 lang="less"
* 2、安装依赖:yarn add less less-loader -D
*/
.App {
width: 400px;
height: 400px;
background-color: pink;
.box {
width: 100px;
height: 100px;
background-color: blue;
}
}
</style>
4. .gitignore
# 忽略build目录下类型为js的文件的语法检查
build/*.js
# 忽略src/assets目录下文件的语法检查
src/assets
# 忽略src/utils目录下文件的语法检查
src/utils
# 忽略public目录下文件的语法检查
public
# 忽略当前目录下为js的文件的语法检查
#*.js
# 忽略当前目录下为vue的文件的语法检查
#*.vue
四、组件
1、普通组件的注册使用
1. 组件注册方式
- 局部注册:只能在注册的组件内使用
- 创建 .vue 文件 (三个组成部分)
- 在使用的组件内导入并注册
- 导入:
import 组件对象 from '.vue文件路径'
- 注册:
components: { 组件名: 组件对象, }
- 导入:
- 全局注册:所有组件内都能使用
- 创建 .vue 文件 (三个组成部分)
- main.js 中进行全局注册
- 导入:
import 组件对象 from '.vue文件路径'
- 注册:
Vue.component(组件名, 组件对象)
- 导入:
- 使用
- 当成 html 标签使用
<组件名></组件名>
- 组件名规范 → 大驼峰命名法,如:ErerHeader
- 一般都用局部注册,如果发现确实是通用组件,再定义到全局。
- 当成 html 标签使用
2. 局部注册
- 创建 .vue 文件
<template>
<div class="erer-main">
我是 main 组件
</div>
</template>
<script>
export default {
}
</script>
<style>
.erer-main{
height: 500px;
line-height: 500px;
text-align: center;
font-size: 30px;
background-color: #f79646;
color: white;
}
</style>
- 在使用的组件内导入并注册
<template>
<div class="App">
<!-- 头部组件 -->
<erer-header></erer-header>
<!-- 主体组件 -->
<erer-main></erer-main>
<!-- 底部组件 -->
<erer-footer></erer-footer>
</div>
</template>
<script>
// 导入需要注册的组件
// import 组件对象 from '.vue文件路径'
import ErerHeader from "./components/ErerHeader.vue"
import ErerMain from "./components/ErerMain.vue"
import ErerFooter from "./components/ErerFooter.vue"
export default {
// 局部注册
components: {
// 组件名: 组件对象,
ErerHeader: ErerHeader,
ErerMain,
ErerFooter,
}
}
</script>
<style>
.App{
width: 600px;
height: 700px;
background-color: #87ceeb;
margin: 0 outo;
padding: 20px;
}
</style>
3. 全局注册
- 创建 .vue 文件
<template>
<button class="erer-button">通用按钮</button>
</template>
<script>
export default {};
</script>
<style>
.erer-button {
height: 50px;
line-height: 50px;
padding: 0 20px;
background-color: #3bae56;
border-radius: 5px;
}
</style>
- main.js 中进行全局注册
import Vue from "vue";
import App from "./App.vue";
// 导入需要注册的组件
import ErerButton from "./components/ErerButton.vue";
Vue.config.productionTip = false;
// 全局注册
// Vue.component(组件名, 组件对象)
Vue.component("ErerButton", ErerButton);
new Vue({
render: (h) => h(App),
}).$mount("#app");
- 使用组件
<template>
<div class="erer-footer">
我是 footer 组件
<!-- 使用组件 -->
<erer-button></erer-button>
</div>
</template>
<script>
export default {
}
</script>
<style>
.erer-footer{
height: 100px;
line-height: 100px;
text-align: center;
font-size: 30px;
background-color: #4f81bd;
color: white;
}
</style>
2、组件组成部分
- 结构
- 只能有一个根元素
- 样式
- 全局样式(默认):影响所有组件
- 局部样式:scoped 下样式,只作用于当前组件
- 逻辑
- el 根实例独有, data 是一个函数,其他配置项一致
3、样式冲突 scoped
- 默认情况:写在组件中的样式会全局生效 → 因此很容易造成多个组件之间的样式冲突问题。
- 全局样式:默认组件中的样式会作用到全局
- 局部样式:可以给组件加上 scoped 属性, 可以让样式只作用于当前组件
<template>
<div>BaseOne</div>
</template>
<script>
export default {
}
</script>
<style scoped>
div {
border: 3px solid blue;
margin: 30px;
}
</style>
-
scoped原理
- 当前组件内标签都被添加 data-v-hash值 的属性
- css选择器都被添加 [data-v-hash值] 的属性选择器
-
最终效果:
- 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
4、data 是一个函数
- 一个组件的 data 选项必须是一个函数;
- 保证每个组件实例,维护独立的一份数据对象;
- 每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象。
五、组件通讯
1、父子通讯
1. 父传子:props
- 父组件声明变量:
data() { return { myTitle: "尔尔", }; },
- 父组件给子组件标签添加属性的方式传值:
<my-son :title="myTitle"></my-son>
- 子组件内部通过 props 接收父组件传值:
props: ["title",]
- 模版中直接使用:
<div class="my-son">我是 Son 组件:{{ title }}</div>
2. 子传父:$emit
- 在子组件中触发父组件绑定的事件:
<button @click="changeFn">修改 title</button>
- 子组件通过 KaTeX parse error: Expected '}', got 'EOF' at end of input: …ngeFn() { this.emit(“changTitle”, this.title === “erer” ? “尔尔” : “erer”); }, },`
- 在父组件中监听子组件事件:
<my-son :title="myTitle" @changTitle="handleChange"></my-son>
- 父组件处理子组件传来参数:
methods: { handleChange(newVal) { this.myTitle = newVal; } },
3. props 案例
-
props 定义:组件上 注册的一些 自定义属性
-
props 作用:向子组件传递数据
-
特点:
- 可以 传递 任意数量 的prop
- 可以 传递 任意类型 的prop
-
子组件
<template>
<div class="userinfo">
<h3>我是个人信息组件</h3>
<div>姓名:{{ name }}</div>
<div>年龄:{{ age }}</div>
<div>是否单身:{{ isSingle ? "是" : "否" }}</div>
<div>座驾:{{ car.brand }}</div>
<div>兴趣爱好:{{ hobby.join("、") }} </div>
</div>
</template>
<script>
export default {
props: ["name", "age", "isSingle", "car", "hobby"],
};
</script>
<style>
.userinfo {
width: 300px;
border: 3px solid #000;
padding: 20px;
}
.userinfo > div {
margin: 20px 10px;
}
</style>
- 父组件
<template>
<div class="App">
<user-info :name="name" :age="age" :isSingle="isSingle" :car="car" :hobby="hobby"></user-info>
</div>
</template>
<script>
import UserInfo from "./04components/UserInfo.vue";
export default {
components: {
UserInfo,
},
data() {
return {
name: "erer",
age: 12,
isSingle: true,
car: {
brand: "奔驰",
},
hobby: ["python", "游戏"],
};
},
};
</script>
<style>
</style>
4. props 校验
- 作用:
- 为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示
- 帮助开发者,快速发现错误
- 语法:
- 类型校验
- 非空校验
- 默认值
- 自定义校验
- 校验方法一:
props: {
// Number String Boolean ...
校验的属性名: 类型
},
- 校验方法二:
props: {
校验的属性名: {
type: 类型, // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
},
- 案例
<template>
<div class="userinfo">
<h3>我是个人信息组件</h3>
<div>姓名:{{ name }}</div>
<div>年龄:{{ age }}</div>
<div>是否单身:{{ isSingle ? "是" : "否" }}</div>
<div>座驾:{{ car.brand }}</div>
<div>兴趣爱好:{{ hobby.join("、") }} </div>
</div>
</template>
<script>
export default {
props: {
name: String, // 字符串校验
age: {
type: Number, // 数字校验
require: true, // 是否必填
default: 0, // 默认值
validator(value) { // 自定义
if(0 < value && value < 100) {
return true;
}
alert("年龄必须介于0-100之间!");
return false;
},
},
isSingle: Boolean, // 布尔值校验
car: Object, // 对象校验
hobby: Array, // 数组校验
},
};
</script>
<style>
.userinfo {
width: 300px;
border: 3px solid #000;
padding: 20px;
}
.userinfo > div {
margin: 20px 10px;
}
</style>
2、props & data
-
共同点:都可以给组件提供数据。
-
区别:
- data 的数据是自己的 → 随便改
- prop 的数据是外部的 → 不能直接改,要遵循 单向数据流
-
单向数据流:父级 prop 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。
-
子组件
<template>
<div class="base-count">
<button @click="sub">-</button>
<span> {{ count }} - {{ number }}</span>
<button @click="add">+</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 99,
};
},
props: {
number: {
type: Number,
default: 888,
},
},
methods: {
add() {
this.count++;
this.$emit("changNumber", this.number + 1);
},
sub() {
this.count--;
this.$emit("changNumber", this.number - 1);
},
},
};
</script>
<style>
.base-count {
margin: 20px;
}
</style>
- 父组件
<template>
<div class="App">
<base-count :number="number" @changNumber="handleChangeNumber"></base-count>
</div>
</template>
<script>
import BaseCount from "./03components/BaseCount.vue";
export default {
components: {
BaseCount,
},
data() {
return {
number: 1000,
};
},
methods: {
handleChangeNumber(value) {
this.number = value;
},
},
};
</script>
<style>
</style>
3、event bus 事件总线
-
作用:非父子组件之间,进行简易消息传递。(复杂场景 → Vuex)
-
Utils>EventBus.js
import Vue from 'vue'
// 创建一个 所有组件都可以访问的事件总线(空的 vue 实例)
const Bus = new Vue()
export default Bus
- 发送消息:
Bus.$emit('sendMsg','这是一个消息')
<template>
<div class="base-b">
<div>我是B组件(发布方)</div>
<button @click="sendMsgFn">发送消息</button>
</div>
</template>
<script>
import Bus from "@/utils/EventBus";
export default {
methods: {
sendMsgFn() {
// 发送消息:通过触发事件传递消息
Bus.$emit("sendMsg", "今天天气不错,适合旅游");
},
},
};
</script>
<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
- 订阅消息:
created(){ Bus.$on('sendMsg', (msg) => {this.msg = msg}) }
<template>
<div class="base-a">
我是A组件(接受方)
<p>{{ msg }}</p>
</div>
</template>
<script>
import Bus from "@/utils/EventBus";
export default {
data() {
return {
msg: "",
};
},
created() {
// 订阅消息:组件接收方,监听 bus 的事件
Bus.$on("sendMsg", (msg) => {
this.msg = msg;
});
},
};
</script>
<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
4、provide & inject
- provide & inject 作用:跨层级共享数据
- 父组件 provide 提供数据
<template>
<div class="App">
<!-- 7、provide & inject -->
<h2>7、provide & inject</h2>
<button @click="change">修改数据</button>
<son-a></son-a>
<son-b></son-b>
</div>
</template>
<script>
import SonA from "./07components/SonA.vue";
import SonB from "./07components/SonB.vue";
export default {
components: {
SonA,
SonB,
},
provide() {
return {
color: this.color,
userInfo: this.userInfo,
};
},
data() {
return {
color: "pink",
userInfo: {
name: "erer",
age: 50,
},
};
},
methods: {
change() {
// 简单类型非响应式数据
this.color = this.color === "red" ? "pink" : "red";
// 复杂类型响应式数据
this.userInfo.name = this.userInfo.name === "erer" ? "尔尔" : "erer";
},
},
};
</script>
<style>
</style>
- 子/孙组件 inject 取值使用
<template>
<div class="grandSon">
我是GrandSon
{{ color }} - {{ userInfo.name }} - {{ userInfo.age }}
</div>
</template>
<script>
export default {
inject: ['color', 'userInfo'],
}
</script>
<style>
.grandSon {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
height: 100px;
}
</style>
5、v-model 组件封装
1. v-model 原理
- 原理:v-model本质上是一个语法糖。例如应用在输入框上,就是 value属性 和 input事件 的合写。
- 作用:提供数据的双向绑定
- 数据变,视图跟着变 :value
- 视图变,数据跟着变 @input
- 注意:$event 用于在模板中,获取事件的形参
<span>{{ msg }}</span><br><br>
<input v-model="msg" type="text"><br><br>
<!-- 模版中 可以通过 $event 获取事件的形参 -->
<input :value="msg" @input="msg = $event.target.value" type="text"><br><br>
2. 表单类组件封装
- 表单类组件 封装 → 实现 子组件 和 父组件数据 的双向绑定
- 父传子:数据 应该是父组件 props 传递 过来的,将 v-model 拆解绑定数据
- 子传父:监听输入,子传父传值给父组件修改
- 父组件:
<BaseSelect :cityId="selectId" @事件名="selecteId = $event" />
- 子组件:
<select :value="cityId" @change="handleChange">...</select>
props: { cityId: String },
methods: { handleChange (e) { this.$emit('事件名', e.target.value)}}
<template>
<div class="App">
<!-- 9、表单类组件封装 -->
<h2>9、表单类组件封装</h2>
<span>{{ selectId }}</span><br><br>
<base-select :cityId="selectId" @changeId="selectId = $event"></base-select>
</div>
</template>
<script>
import BaseSelect from "./09components/BaseSelect.vue";
export default {
components: {
BaseSelect,
},
data() {
return {
selectId: "102",
};
},
};
</script>
<style>
</style>
<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>
3. v-model 简化代码
- 父组件 v-model 简化代码,实现 子组件 和 父组件数据 双向绑定
- 子组件中:props 通过 value 接收,事件触发 input
- 父组件中:v-model 给组件直接绑数据
v-mode= "参数名"
=>:value="参数名" + @input="参数名 = $event"
<template>
<div class="App">
<!-- 9、表单类组件封装 -->
<h2>9、表单类组件封装</h2>
<span>{{ selectId }}</span><br><br>
<!-- v-model 直接绑数据 -->
<base-select v-model="selectId"></base-select>
</div>
</template>
<script>
import BaseSelect from "./09components/BaseSelect.vue";
export default {
components: {
BaseSelect,
},
data() {
return {
selectId: "102",
};
},
};
</script>
<template>
<div>
<select :value="value" @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 通过 value 接收
props: {
value: String,
},
methods: {
handleChange(e) {
// 事件触发使用 input
this.$emit("input", e.target.value);
},
},
};
</script>
<style>
</style>
6、.sync 修饰符
- 作用:可以实现 子组件 与 父组件数据 的 双向绑定,简化代码
- 特点:prop属性名,可以自定义,非固定为 value
- 场景:封装弹框类的基础组件, visible属性 true显示 false隐藏
- 本质:就是 :属性名 和 @update:属性名 合写
:visible.sync="属性名"
=>:visible="属性名" + @update.visible="属性名 = $event"
<template>
<div class="App">
<!-- 10、.sync 修饰符 -->
<h2>10、.sync 修饰符</h2>
<button @click="isShow = true">退出按钮</button>
<base-dialog :visible.sync="isShow"></base-dialog>
</div>
</template>
<script>
import BaseDialog from "./10components/BaseDialog.vue";
export default {
components: {
BaseDialog,
},
data() {
return {
isShow: false,
};
},
};
</script>
<template>
<div v-show="visible" class="base-dialog-wrap">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button @click="close" class="close">x</button>
</div>
<div class="content">
<p >你确认要退出本系统么?</p>
</div>
<div class="footer">
<button @click="close" >确认</button>
<button @click="close" >取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
visible: Boolean,
},
methods: {
close() {
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped>
</style>
7、ref 和 $refs
-
作用:利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例
-
特点:查找范围 → 当前组件内 (更精确稳定)
- 目标标签 – 添加 ref 属性:
<div ref="属性名">我是渲染图表的容器</div>
- 通过 KaTeX parse error: Expected '}', got 'EOF' at end of input: …nsole.log(this.refs.属性名) },`
- 目标组件 - 添加 ref 属性:
<BaseForm ref="属性名"></BaseForm>
- 调用 r e f s 获取组件实例: ‘ t h i s . refs 获取组件实例:`this. refs获取组件实例:‘this.refs.属性名.组件方法()`
- 目标标签 – 添加 ref 属性:
<template>
<div class="App">
<!-- 11、ref 和 $refs -->
<h2>11、ref 和 $refs</h2>
<!-- 调用 $refs 获取组件实例 -->
<base-chart></base-chart>
<!-- 通过 $refs 获取dom对象 -->
<base-form ref="baseForm"></base-form>
<button @click="handelGet">获取数据</button>
<button @click="handelReset">重置数据</button>
</div>
</template>
<script>
import BaseChart from "./11components/BaseChart.vue";
import BaseForm from "./11components/BaseForm.vue";
export default {
components: {
BaseChart,
BaseForm,
},
methods: {
handelGet() {
console.log(this.$refs.baseForm.getFormData());
},
handelReset() {
this.$refs.baseForm.resetFormData();
},
},
};
</script>
<style>
</style>
<template>
<div ref="myChart" class="base-chart-box">子组件</div>
</template>
<script>
import * as echarts from "echarts";
export default {
mounted() {
// 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(this.$refs.myChart);
// 绘制图表
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>
<template>
<div class="app">
<div>
账号: <input v-model="username" type="text">
</div>
<div>
密码: <input v-model="password" type="text">
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: "admin",
password: "123456",
};
},
methods: {
getFormData() {
//console.log('获取表单数据', this.username, this.password);
return {
username: this.username,
password: this.password,
};
},
resetFormData() {
this.username = "";
this.password = "";
console.log("重置表单数据成功");
},
},
};
</script>
<style scoped>
</style>
8、$nextTick
- $nextTick:等 DOM 更新后, 才会触发执行此方法里的函数体
<template>
<div class="App">
<!-- 12、Vue异步更新、$nextTick -->
<h2>12、Vue异步更新、$nextTick</h2>
<next-tick-case></next-tick-case>
</div>
</template>
<script>
import NextTickCase from "./12components/NextTickCase.vue";
export default {
components: {
NextTickCase,
},
};
</script>
<style>
</style>
<template>
<div class="next-tick-case">
<div v-if="isShowEdit">
<input type="text" v-model="editValue" ref="inp" />
<button>确认</button>
</div>
<div v-else>
<span>{{ title }}</span>
<button @click="handleEdit">编辑</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
title: "大标题",
isShowEdit: false,
editValue: "",
};
},
methods: {
handleEdit() {
// 显示输入框(Vue 是 异步更新 DOM (提升性能))
this.isShowEdit = true;
// 获取焦点(等 DOM 更新后, 才会触发执行此方法里的函数体)
this.$nextTick(() => {
this.$refs.inp.focus();
});
},
},
};
</script>
<style>
</style>
9、对象变更检测
this.$set(this.user, "age", 20);
this.user = Object.assign({}, this.user, {})
<div id="app">
<h3>{{user.name}},{{user.age}},{{user.weight}},{{user.phone}}</h3>
<button @click="handleAdd">添加属性</button>
</div>
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
user: {},
},
created() {
// 模拟异步请求
setTimeout(() => {
this.user = {
name: "尔尔",
};
}, 1000);
},
// 挂载局部组件
components: {},
// 存放方法
methods: {
handleAdd() {
// this.user.age = 20; // 错误方法
// 添加单个响应式属性
this.$set(this.user, "age", 20);
// 添加多个响应式属性
this.user = Object.assign({}, this.user, {
weight: 20,
phone: 18888888888,
});
},
},
// 局部过滤器
filters: {},
// 计算属性
computed: {},
});
10、mixin混入偷懒技术
<div id="app">
<h3>{{msg}}</h3>
</div>
const myMixin = {
data() {
return {
msg: "123",
};
},
created() {
this.sayHello();
},
methods: {
sayHello() {
console.log("hello");
},
},
};
new Vue({
// 绑定模块
el: "#app",
// 数据属性
data: {
title: "尔尔",
},
mixins: [myMixin],
// 挂载局部组件
components: {},
// 存放方法
methods: {},
// 局部过滤器
filters: {},
// 计算属性
computed: {},
});
六、vue进阶
1、自定义指令
- 自定义指令:自己定义的指令, 可以封装一些 dom 操作, 扩展额外功能
- 全局注册
// 全局注册自定义指令
Vue.directive("focus", {
// inserted会在指令所在的元素被插入到页面的时候触发
inserted(el) {
el.focus();
},
});
- 局部注册
export default {
directives: {
// 指令名
focus: {
inserted(el) {
el.focus();
},
},
},
};
- 使用
<input v-指令名 type="text">
2、指令的值
- 语法:在绑定指令时,可以通过 “=” 的形式为指令 绑定 具体的参数值
- 通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数。
<template>
<div>
<h1>自定义指令:指令的值</h1>
<span v-color="color1">指令的值测试1</span><br>
<span v-color="color2">指令的值测试2</span>
</div>
</template>
<script>
export default {
data() {
return {
color1: "red",
color2: "blue",
};
},
directives: {
color: {
inserted(el, binding) {
el.style.color = binding.value;
},
update(el, binding) {
el.style.color = binding.value;
},
},
},
};
</script>
<style>
</style>
3、v-loading
- 分析
- 本质 loading 效果就是一个蒙层,盖在了盒子上
- 数据请求中,开启loading状态,添加蒙层
- 数据请求完毕,关闭loading状态,移除蒙层
- 实现
- 准备一个 loading 类,通过伪元素定位,设置宽高,实现蒙层
- 开启关闭 loading 状态(添加移除蒙层),本质只需要添加移除类即可
- 结合自定义指令的语法进行封装复用
<template>
<div>
<get-news></get-news>
</div>
</template>
<script>
import GetNews from "./components/GetNews.vue";
export default {
components: {
GetNews,
},
};
</script>
<style>
</style>
<template>
<div class="get-news" 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>
</template>
<script>
// 安装axios => yarn add 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 中,用于页面渲染 v-for
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 scoped>
.loading:before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url("@/assets/loading.gif") no-repeat center;
}
.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>
4、插槽
1. 默认插槽
- 作用:让组件内部的一些 结构 支持 自定义
- 使用步骤:
- 先在组件内用
<slot></slot>
占位 - 使用组件时, 传入具体标签内容插入
- 先在组件内用
<template>
<div class="dialog-content">
<slot></slot>
</div>
</template>
2. 后备内容
- 插槽后备内容:封装组件时,可以为预留的
<slot></slot>
插槽提供后备内容(默认内容)。 - 语法: 在
<slot></slot>
标签内,放置内容, 作为默认显示内容 - 效果:
- 外部使用组件时,不传东西,则
<slot></slot>
会显示后备内容 - 外部使用组件时,传东西了,则
<slot></slot>
整体会被换掉
- 外部使用组件时,不传东西,则
<template>
<div class="dialog-content">
<slot>我是后备内容</slot>
</div>
</template>
3. 具名插槽
- 多个
<slot></slot>
使用name属性区分名字
<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>
- template 配合 v-slot: 名字来分发对应标签
<MyDialog>
<template v-slot:head>大标题</template>
<template v-slot:content>内容文本</template>
<template v-slot:footer><button>按钮</button></template>
</MyDialog>
- 具名插槽简化语法:
<MyDialog>
<template #head>大标题</template>
<template #content>内容文本</template>
<template #footer><button>按钮</button></template>
</MyDialog>
4. 插槽案例
- App.vue
<template>
<div>
<my-dialog></my-dialog>
<!-- 没有分发对应标签不生效 -->
<my-dialog>你确认要退出吗?</my-dialog>
<my-dialog>
<template v-slot:content>你确认要删除吗?</template>
</my-dialog>
<my-dialog>
<template #head>大标题</template>
<template #content>内容文本</template>
<template #footer><button>按钮</button></template>
</my-dialog>
</div>
</template>
<script>
import MyDialog from "./components/MyDialog.vue";
export default {
components: {
MyDialog,
},
};
</script>
<style>
</style>
- MyDialog
<template>
<div class="dialog">
<div class="dialog-header">
<slot name="head">
<h3>友情提示</h3>
<span class="close">✖️</span>
</slot>
</div>
<div class="dialog-content">
<slot name="content">我是默认内容</slot>
</div>
<div class="dialog-footer">
<slot name="footer">
<button>取消</button>
<button>确认</button>
</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;
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>
5. 作用域插槽
- 作用域插槽: 定义 slot 插槽的同时, 是可以传值的。
- 给 插槽 上可以 绑定数据,将来 使用组件时可以用。
- 场景:封装表格组件
- 父传子,动态渲染表格内容
- 利用默认插槽,定制操作列
- 删除或查看都需要用到 当前项的 id,属于 组件内部的数据 通过 作用域插槽 传值绑定,进而使用
- 基本使用步骤:
- 给 slot 标签, 以 添加属性的方式传值:
<slot :id="item.id" msg="测试文本"></slot>
- 所有添加的属性, 都会被收集到一个对象中:
{ id: 3, msg: '测试文本' }
- 在template中, 通过
#插槽名= "obj"
接收,默认插槽名为 default
- 给 slot 标签, 以 添加属性的方式传值:
<MyTable :list="list">
<template #default="obj">
<button @click="del(obj.id)">删除</button>
</template>
</MyTable>
- App.vue
<template>
<div>
<MyTable :data="list1">
<template #operation="obj">
<button @click="deleteById(obj.row.id)">删除</button>
</template>
</MyTable>
<MyTable :data="list2">
<!-- 支持结构 -->
<template #operation="{ row }">
<button @click="show(row)">查看</button>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from "./components/MyTable.vue";
export default {
components: {
MyTable,
},
data() {
return {
color1: "red",
color2: "blue",
list1: [
{ 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: {
deleteById(id) {
this.list1 = this.list1.filter((item) => item.id !== id);
},
show(item) {
alert(item.name + ":" + item.age);
},
},
};
</script>
<style>
</style>
- 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>
<slot :row="item" msg="测试文本" name="operation"></slot>
</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>
七、路由
1、路由介绍
1. 单页应用程序
- 单页面应用(SPA): 所有功能在 一个html页面 上实现
开发分类 | 实现方式 | 页面性能 | 开发效率 | 用户体验 | 学习成本 | 首屏加载 | SEO |
---|---|---|---|---|---|---|---|
单页 | 一个html页面 | 按需更新 性能高 | 高 | 非常好 | 高 | 慢 | 差 |
多页 | 多个html页面 | 整页更新 性能低 | 中等 | 一般 | 中等 | 快 | 优 |
- 单页面应用
- 系统类网站 / 内部网站 / 文档类网站 /移动端站点
- 多页面应用
- 公司官网 / 电商类网站
2. 路由介绍
- Vue中路由:路径 和 组件 的 映射 关系
- 根据路由就能知道不同路径的,应该匹配渲染哪个组件
2、VueRouter
1. VueRouter 的 介绍
- 目标:认识插件 VueRouter,掌握 VueRouter 的基本使用步骤
- 作用:修改地址栏路径时,切换显示匹配的组件
- 说明:Vue 官方的一个路由插件,是一个第三方包
- 官网:https://v3.router.vuejs.org/zh/
2. VueRouter 的 使用
- 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')
- 下载 VueRouter 模块到当前工程,版本3.6.5:
import Vue from "vue";
import App from "./App.vue";
// 5个基础步骤
// 1. 下载 v3.6.5
// 2. 引入
import VueRouter from "vue-router";
// 3. 安装注册 Vue.use(Vue插件)
Vue.use(VueRouter);
// 4. 创建路由对象
const router = new VueRouter();
Vue.config.productionTip = false;
new Vue({
render: (h) => h(App),
// 5. 注入到new Vue中,建立关联
router,
}).$mount("#app");
- 2 个核心步骤
- 创建需要的组件 (views目录)
<template>
<div>
<p>发现音乐</p>
<p>发现音乐</p>
<p>发现音乐</p>
<p>发现音乐</p>
</div>
</template>
<script>
export default {
name: 'FindMusic'
}
</script>
<style>
</style>
- 配置路由规则(path: 地址栏路径, component:组 件)
import Vue from "vue";
import App from "./App.vue";
// A.创建需要的组件
import Find from "./views/Find.vue";
import My from "./views/My.vue";
import Friend from "./views/Friend.vue";
// 路由的使用步骤 5 + 2
// 5个基础步骤
// 1. 下载 v3.6.5
// 2. 引入
import VueRouter from "vue-router";
// 3. 安装注册 Vue.use(Vue插件)
Vue.use(VueRouter);
// 4. 创建路由对象
const router = new VueRouter({
// B.配置路由规则
routes: [
{ path: "/find", component: Find },
{ path: "/my", component: My },
{ path: "/friend", component: Friend },
],
});
Vue.config.productionTip = false;
new Vue({
render: (h) => h(App),
// 5. 注入到new Vue中,建立关联
router,
}).$mount("#app");
- 配置导航,配置路由出口(路径匹配的组件显示的位置)
<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>
3. 组件存放目录问题
- 组件分类:页面组件 & 复用组件
- 注意:都是 .vue文件 (本质无区别)
- 分类开来 更易维护
- src/views文件夹:页面组件 - 页面展示 - 配合路由用
- src/components文件夹:复用组件 - 展示数据 - 常用于复用
4. 路由的封装抽离
- @/router/index.js
import Vue from "vue";
// A.创建需要的组件
import Find from "@/views/Find.vue";
import My from "@/views/My.vue";
import Friend from "@/views/Friend.vue";
// 1. 下载 v3.6.5
// 2. 引入
import VueRouter from "vue-router";
// 3. 安装注册 Vue.use(Vue插件)
Vue.use(VueRouter);
// 4. 创建路由对象
const router = new VueRouter({
// B.配置路由规则
routes: [
{ path: "/find", component: Find },
{ path: "/my", component: My },
{ path: "/friend", component: Friend },
],
});
// 导出 router
export default router;
- main.js
import Vue from "vue";
import App from "./App.vue";
// 导入 router
import router from "./router";
Vue.config.productionTip = false;
new Vue({
render: (h) => h(App),
// 5. 注入到new Vue中,建立关联
router,
}).$mount("#app");
3、声明式导航
1. 导航高亮
- vue-router 提供了一个全局组件 router-link (取代 a 标签)
- 能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #
- 能高亮,默认就会提供高亮类名,可以直接设置高亮样式
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style lang="less">
.footer_wrap a.router-link-active {
background-color: purple;
}
</style>
2. 精确匹配/模糊匹配
- router-link-active 模糊匹配 (用的多):to=“/my” 可以匹配 /my、/my/a、/my/b …
- router-link-exact-active 精确匹配:to=“/my” 仅可以匹配 /my
3. 自定义高亮类名
- @/router/index.js
import Vue from "vue";
import Find from "@/views/Find.vue";
import My from "@/views/My.vue";
import Friend from "@/views/Friend.vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{ path: "/find", component: Find },
{ path: "/my", component: My },
{ path: "/friend", component: Friend },
],
// 配置模糊匹配的类名
linkActiveClass: "avtive",
// 配置精确匹配的类名
linkExactActiveClass: "exact-active",
});
export default router;
- App.vue
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style lang="less">
.footer_wrap a.avtive {
background-color: purple;
}
</style>
4. 跳转传参
- 查询参数传参
- 比较适合传多个参数
- 跳转:
to="/path?参数名=值&参数名2=值"
- 获取:
$route.query.参数名
- 动态路由传参
- 优雅简洁,传单个参数比较方便
- 配置动态路由:
path: "/path/参数名"
- 跳转:
to="/path/参数值"
- 获取:
$route.params.参数名
5. 查询参数传参
- 匹配路由规则
import Home from "@/views/Home";
import Search from "@/views/Search";
const router = new VueRouter({
routes: [
{ path: "/home", component: Home },
{ path: "/search", component: Search },
],
});
- 语法格式如下:
to="/path?参数名=值"
<router-link to="/search?key=erer">erer</router-link>
- 对应页面组件接收传递过来的值:
$route.query.参数名
<p>搜索关键字: {{ $route.query.key }} </p>
<script>
export default {
created () {
console.log(this.$route.query.key);
}
}
</script>
6. 动态路由传参
- 配置动态路由
import Home from "@/views/Home";
import Search from "@/views/Search";
const router = new VueRouter({
routes: [
{ path: "/home", component: Home },
{ path: "/search/:words", component: Search },
],
});
- 配置导航链接:
to="/path/参数值"
<router-link to="/search/erer">erer</router-link>
- 对应页面组件接收传递过来的值:
$route.params.参数名
<p>搜索关键字: {{ $route.params.words }} </p>
<script>
export default {
created () {
console.log(this.$route.params.words);
}
}
</script>
7. 动态路由参数可选符
- 问题:配了路由
path: "/search/:words"
当访问路径是 http://localhost:8080/#/search/ 会显示空白 - 原因:
/search/:words
表示,必须要传参数。 - 解决:如果不传参数,也希望匹配,可以加个可选符 “?”
/search/:words?
import Search from "@/views/Search";
const router = new VueRouter({
routes: [
{ path: "/search/:words?", component: Search },
],
});
4、路由进阶
1. 路由重定向
- 问题:网页打开, url 默认是 / 路径,未匹配到组件时,会出现空白
- 说明:重定向 → 匹配path后, 强制跳转path路径
- 语法:
{ path: 匹配路径, redirect: 重定向到的路径 }
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home'},
],
});
2. 404
- 作用:当路径找不到匹配时,给个提示页面 “@/views/NotFound”
<template>
<div>
<h1>404 Not Found</h1>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
- 位置:配在路由最后
- 语法:
path: "*"
(任意路径) – 前面不匹配就命中最后这个
import NotFound from "@/views/NotFound"
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home'},
{ path: "/home", component: Home },
{ path: "/search/:words?", component: Search },
{ path: '*', component: NotFound }
],
});
3. 模式设置
- hash路由(默认)例如: http://localhost:8080/#/home
- history路由(常用)例如: http://localhost:8080/home (以后上线需要服务器端支持)
const router = new VueRouter({
mode: "history",
routes: [
{ path: "/", redirect: "/home" },
{ path: "/home", component: Home },
{ path: "/search/:words?", component: Search },
{ path: "*", component: NotFound },
],
});
5、编程式导航
1. 通过路径跳转
- path 路径跳转 (简易方便):
this.$router.push({path: '路由路径' })
this.$router.push('/search')
this.$router.push({
path: '/search'
})
2. 通过路由名字跳转
- name 命名路由跳转 (适合 path 路径长的场景):
this.$router.push({ name: '路由名'})
const router = new VueRouter({
mode: "history",
routes: [
{ name: "/search", path: "/search/:words?", component: Search },
],
});
this.$router.push({
name: '/search'
})
3. 路径跳转query传参
- 传参:
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input v-model="inputValue" type="text">
<button @click="goSearch">搜索一下</button>
</div>
</div>
</template>
<script>
export default {
name: "FindMusic",
data() {
return {
inputValue: "",
}
},
methods: {
goSearch() {
// this.$router.push(`/search?key=${this.inputValue}`)
this.$router.push({
path: "/search",
query: {
key: this.inputValue,
},
});
},
},
};
</script>
<style>
</style>
- 获取参数:
$route.query.参数名
<p>搜索关键字: {{ $route.query.key }} </p>
4. 路径跳转动态路由传参
- 传参:
this.$router.push('/路径/参数值')
methods: {
goSearch() {
this.$router.push(`/search/${this.inputValue}`)
},
},
- 配置动态路由
import Home from "@/views/Home";
import Search from "@/views/Search";
const router = new VueRouter({
routes: [
{ path: "/search/:words?", component: Search },
],
});
- 获取参数:
$route.params.参数名
<p>搜索关键字: {{ $route.params.words }} </p>
5. 路由名字跳转query传参
- 传参:
this.$router.push({ name: '路由名字', query: { 参数名1: '参数值1',参数名2: '参数值2' }})
methods: {
goSearch() {
this.$router.push({
name: "/search",
query: {
key: this.inputValue,
},
});
},
},
- 获取参数:
$route.query.参数名
<p>搜索关键字: {{ $route.query.key }} </p>
6. 路由名字跳转态路由传参
- 传参:
this.$router.push({ name: '路由名字', params: {参数名: '参数值', }})
methods: {
goSearch() {
this.$router.push({
name: "/search",
params: {
words: this.inputValue,
},
});
},
- 配置动态路由
import Home from "@/views/Home";
import Search from "@/views/Search";
const router = new VueRouter({
routes: [
{ path: "/search/:words?", component: Search },
],
});
- 获取参数:
$route.params.参数名
<p>搜索关键字: {{ $route.params.words }} </p>
6、组件缓存
- 问题:从面经 点到 详情页,又点返回,数据重新加载了 → 希望回到原来的位置
- 原因:路由跳转后,组件被销毁了,返回回来组件又被重建了,所以数据重新被加载了
- 解决:利用 keep-alive 将组件缓存下来
1. keep-alive
- keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
- keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。
2. 优点
- 在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,
- 减少加载时间及性能消耗,提高用户体验性。
3. 属性
- include : 组件名数组,只有匹配的组件会被缓存
- exclude : 组件名数组,任何匹配的组件都不会被缓存
- max : 最多可以缓存多少组件实例
<template>
<div class="h5-wrapper">
<!-- include="组件名" 组件名:组件name,没有配置name匹配文件名 -->
<keep-alive include="keepArr">
<router-view></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
name: "h5-wrapper",
data() {
return {
keepArr: ["LayoutPage"],
};
},
};
</script>
4. 生命周期函数
- activated 当组件被激活(使用)的时候触发 → 进入这个页面的时候触发
- deactivated 当组件不被使用的时候触发 → 离开这个页面的时候触发
- 组件缓存后就不会执行组件的 created, mounted, destroyed 等钩子了
- 所以其提供了actived 和 deactived钩子,帮我们实现业务需求。
activated () {
console.log('actived 激活 → 进入页面');
},
deactivated() {
console.log('deactived 失活 → 离开页面');
}
八、vuex
1、vuex概述
1. Vue介绍
- vuex 是一个 vue 的 状态管理工具,状态就是数据。
- 大白话:vuex 是一个插件,可以帮我们管理 vue 通用的数据 (多组件共享的数据)
- 购物车数据
- 个人信息数据
2. 场景
- 某个状态 在 很多个组件 来使用 (个人信息)
- 多个组件 共同维护 一份数据 (购物车)
3. 优势
- 共同维护一份数据,数据集中化管理
- 响应式变化
- 操作简洁 (vuex提供了一些辅助函数)
2、构建 vuex 环境
- 安装 vuex:
npm install vuex@3 --legacy-peer-deps
- 新建vuex 模块文件:新建 store/index.js 专门存放 vuex
- 创建仓库:Vue.use(Vuex) 创建仓库 const store = new Vuex.Store()
- main.js 导入挂载:在 main.js 中导入挂载到 Vue 实例上
3、state 状态
1. 提供数据
- State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储。
- 在 state 对象中可以添加我们要共享的数据。
// 存放 vuex 相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
// 插件安装
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store({
// state 状态, 即所有组件共享的数据, 类似于 vue 组件中的 data
state: {
title: '大标题',
count: 100
}
})
// 导出
export default store
2. 使用数据
- 通过 store 直接访问:
- 模板中:
{{ $store.state.xxx }}
- 组件逻辑中:
this.$store.state.xxx
- JS模块中:
store.state.xxx
- 模板中:
<template>
<div id="app">
<h1>
根组件 <br>
- {{ $store.state.title }} <br>
- {{ $store.state.count }}
</h1>
</div>
</template>
<script>
export default {
name: 'app',
created () {
console.log(this.$store.state.count)
}
}
</script>
<style>
</style>
3. mapState
- mapState是辅助函数,帮助我们把 store中的数据 自动 映射到 组件的计算属性中
- 导入 数组方式 mapState:
import { mapState } from 'vuex'
- 数组方式 mapState 引入state:
mapState(['count'])
- 展开运算 符映射:
computed: { }
- 导入 数组方式 mapState:
<template>
<div id="app">
<h1>
根组件 <br>
- {{ title }} <br>
- {{ count }}
</h1>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'app',
components: {
Son1,
Son2
},
computed: {
...mapState(['count', 'title'])
},
}
</script>
<style>
</style>
4、mutations
1. 单向数据流
- 目标: 明确 vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label>{{ count }}</label>
<br>
<button @click="handleAdd">值 + 1</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Son1Com',
computed: {
...mapState(['count'])
},
methods: {
handleAdd () {
// 错误代码
// this.$store.state.count++
}
}
}
</script>
<style lang="css" scoped>
.box{
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>
- 通过 strict: true 可以开启严格模式
// 存放 vuex 相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
// 插件安装
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store({
strict: true,
// state 状态, 即所有组件共享的数据, 类似于 vue 组件中的 data
state: {
title: '大标题',
count: 100
}
})
// 导出
export default store
2. 修改数据
- 目标:掌握 mutations 的操作流程,来修改 state 数据。 (state数据的修改只能通过 mutations )
- 定义 mutations 对象,对象中存放修改 state 的方法
const store = new Vuex.Store({
strict: true,
// 1. 通过 state 提供所有组件共享的数据
state: {
title: '大标题',
count: 100
},
// 2. 通过 mutations 提供修改数据的方法
mutations: {
// 所有 mutations 函数的第一个参数都是 state
addCount (state) {
state.count += 1
},
addFive (state) {
state.count += 5
}
}
})
- 组件中提交调用 mutations:
this.$store.commit('addCount')
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label>{{ count }}</label>
<br>
<button @click="handleAdd">值 + 1</button>
<button @click="handleAddFive">值 + 5</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Son1Com',
computed: {
...mapState(['count'])
},
methods: {
handleAdd () {
// 错误代码
// this.$store.state.count++
this.$store.commit('addCount')
},
handleAddFive () {
this.$store.commit('addFive')
}
}
}
</script>
<style lang="css" scoped>
</style>
3. mutations 传参
- 目标:掌握 mutations 传参语法
- 提供 mutation 函数 (带参数 - 提交载荷 payload ):只能传递一个参数,多个参数需要包装成一个对象
const store = new Vuex.Store({
strict: true,
// 1. 通过 state 提供所有组件共享的数据
state: {
title: '大标题',
count: 100
},
// 2. 通过 mutations 提供修改数据的方法
mutations: {
// 所有 mutations 函数的第一个参数都是 state
addCount (state, num) {
state.count += num
}
}
})
- 页面中提交调用 mutation可以传递参数的
this.$store.commit( 'xxx', 参数 )
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label>{{ count }}</label>
<br>
<button @click="handleAdd(1)">值 + 1</button>
<button @click="handleAdd(5)">值 + 5</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Son1Com',
computed: {
...mapState(['count'])
},
methods: {
handleAdd (n) {
this.$store.commit('addCount', n)
}
}
}
</script>
<style lang="css" scoped>
.box{
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>
4. mutations 双向绑定
- 更新方法
const store = new Vuex.Store({
state: {
count: 100
},
mutations: {
changeCount (state, newValue) {
state.count = newValue
}
}
})
- 双向绑定
<template>
<div id="app">
<h1>
根组件 <br>
- {{ title }} <br>
- {{ count }}
</h1>
<input :value="count" @input="handleInput" type="text">
</div>
</template>
<script>
import Son1 from './components/Son1.vue'
import Son2 from './components/Son2.vue'
import { mapState } from 'vuex'
export default {
name: 'app',
computed: {
...mapState(['count'])
},
methods: {
handleInput (e) {
const num = +e.target.value
this.$store.commit('changeCount', num)
}
}
}
</script>
<style>
</style>
5. mapMutations
- mapMutations 和 mapState很像,它是把位于mutations中的方法提取了出来,映射到组件methods中
<template>
<div class="box">
<h2>Son2 子组件</h2>
从vuex中获取的值:<label>{{ count }}</label>
<br />
<button @click="subCount(1)">值 - 1</button>
<button @click="subCount(5)">值 - 5</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
name: 'Son2Com',
computed: {
...mapState(['count'])
},
methods: {
// handleSub (num) {
// this.$store.commit('subCount', num)
// }
...mapMutations(['subCount'])
}
}
</script>
<style lang="css" scoped>
</style>
5、actions
- 目标:明确 actions 的基本语法,处理异步操作。
- 需求: 一秒钟之后, 修改 state 的 count 成 666。
- 说明:mutations 必须是同步的 (便于监测数据变化,记录调试)
1. 提供 action 方法
const store = new Vuex.Store({
strict: true,
// 1. 通过 state 提供所有组件共享的数据
state: {
count: 100
},
// 2. 通过 mutations 提供修改数据的方法
mutations: {
changeCount (state, newValue) {
state.count = newValue
}
},
// 3. action 处理异步
actions: {
// context 上下文,为分模块可以当场 store 仓库
changeCountAction (context, num) {
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
}
})
2. dispatch 调用
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label>{{ count }}</label>
<br>
<button @click="handleChange">1秒后修改为666</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Son1Com',
computed: {
...mapState(['count'])
},
methods: {
handleChange () {
this.$store.dispatch('changeCountAction', 666)
}
}
}
</script>
<style lang="css" scoped>
</style>
3. mapActions
- mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中
<template>
<div class="box">
<h2>Son2 子组件</h2>
从vuex中获取的值:<label>{{ count }}</label>
<br />
<button @click="subCount(1)">值 - 1</button>
<button @click="subCount(5)">值 - 5</button>
<button @click="changeCountAction(888)">1秒后修改为888</button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
name: 'Son2Com',
computed: {
...mapState(['count'])
},
methods: {
// handleSub (num) {
// this.$store.commit('subCount', num)
// }
...mapMutations(['subCount']),
// handleChange () {
// this.$store.dispatch('changeCountAction', 666)
// },
...mapActions(['changeCountAction'])
}
}
</script>
<style lang="css" scoped>
</style>
6、getters
- 目标:掌握核心概念 getters 的基本语法 (类似于计算属性)
- 说明:除了state之外,有时我们还需要从state中派生出一些状态,这些状态是依赖state的,此时会用到getters
- 例如:state中定义了list,为 1-10 的数组,组件中,需要显示所有大于5的数据
1. 提供数据
const store = new Vuex.Store({
state: {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},
// 4. getters 类似于计算属性
getters: {
// 第一个参数都是 state,必须有返回值
filterList (state) {
return state.list.filter(item => item > 5)
}
}
})
2. 使用数据
<template>
<div class="box">
<div>{{ $store.getters.filterList }}</div>
</div>
</template>
<script>
export default {
name: 'Son1Com',
}
</script>
<style lang="css" scoped>
</style>
3. mapGetters
<template>
<div class="box">
<h2>Son2 子组件</h2>
从vuex中获取的值:<label>{{ count }}</label>
<br />
<button @click="subCount(1)">值 - 1</button>
<button @click="subCount(5)">值 - 5</button>
<button @click="changeCountAction(888)">1秒后修改为888</button>
<br>
<div>{{ filterList }}</div>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
name: 'Son2Com',
computed: {
// mapState、mapGetters 映射属性
...mapState(['count']),
...mapGetters(['filterList'])
},
methods: {
// mapMutations、mapActions 映射方法
...mapMutations(['subCount']),
...mapActions(['changeCountAction'])
}
}
</script>
<style lang="css" scoped>
</style>
7、分模块
- 目标:掌握核心概念 module 模块的创建
- 由于 vuex 使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时, store 对象就有可能变得相当臃肿。(当项目变得越来越大的时候,Vuex会变得越来越难以维护)
1. 新建模块
- user 模块:store/modules/user.js
// user 模块
const state = {
userInfo: {
name: 'erer',
age: 18
},
score: 88
}
const mutations = {}
const action = {}
const getters = {}
export default {
state,
mutations,
action,
getters
}
- setting 模块:store/modules/setting.js
// setting 模块
const state = {
theme: 'light',
desc: 'test'
}
const mutations = {}
const action = {}
const getters = {}
export default {
state,
mutations,
action,
getters
}
2. 挂载模块
import Vue from 'vue'
import Vuex from 'vuex'
// 引入模块
import user from './moduels/user'
import setting from './moduels/setting'
// 插件安装
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store({
// 5. modules 模块
modules: {
user,
setting
}
})
// 导出
export default store
3. 访问 state
- 目标:掌握模块中 state 的访问语法
- 尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的 state 中,属性名就是模块名
- 直接通过模块名访问 $store.state.模块名.xxx
<div class="box">
<!-- 测试访问模块中的数据 -->
<div>{{ $store.state.user.userInfo.name }}</div> <!-- erer -->
<div>{{ $store.state.setting }}</div> <!-- "theme": "light", "desc": "test" -->
</div>
- 通过 mapState 映射:默认根级别的映射
mapState([ 'xxx' ])
<template>
<div class="box">
<div>{{ user.userInfo.name }}</div> <!-- erer -->
<div>{{ setting }}</div> <!-- { "theme": "light", "desc": "test" } -->
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Son2Com',
computed: {
...mapState(['user', 'setting']),
},
}
</script>
<style lang="css" scoped>
</style>
- 通过 mapState 映射:子模块的映射
mapState('模块名', ['xxx'])
- 需要开启命名空间
// user 模块
const state = {
userInfo: {
name: 'erer',
age: 18
},
score: 88
}
const mutations = {}
const action = {}
const getters = {}
export default {
namespaced: true,
state,
mutations,
action,
getters
}
<template>
<div class="box">
<div>{{ userInfo }}</div> <!-- { "name": "erer", "age": 18 } -->
<div>{{ theme }}</div> <!-- light -->
<div>{{ desc }}</div> <!-- test -->
</div>
</template>
<script>
import { mapState} from 'vuex'
export default {
name: 'Son2Com',
computed: {
// mapState、mapGetters 映射属性
...mapState('user', ['userInfo']),
...mapState('setting', ['theme', 'desc']),
},
}
</script>
<style lang="css" scoped>
</style>
4. 访问 getters
- 目标:掌握模块中 getters 的访问语法
- 直接通过模块名访问
$store.getters['模块名/xxx ']
// user 模块
const state = {
userInfo: {
name: 'erer',
age: 18
},
score: 88
}
const mutations = {}
const action = {}
const getters = {
upperCaseName (state) {
return state.userInfo.name.toUpperCase()
}
}
export default {
namespaced: true,
state,
mutations,
action,
getters
}
<div class="box">
<div>{{ $store.getters['user/upperCaseName'] }}</div> <!-- ERER -->
</div>
- 通过 mapGetters 映射:默认根级别的映射
mapGetters([ 'xxx' ])
- 通过 mapGetters 映射:
子模块的映射 mapGetters('模块名', ['xxx'])
- 需要开启命名空间
<template>
<div class="box">
<!-- 访问模块中的 getters -->
<div>{{ upperCaseName }}</div>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
name: 'Son2Com',
computed: {
...mapGetters('user', ['upperCaseName'])
},
}
</script>
<style lang="css" scoped>
</style>
5. 访问 mutations
- 目标:掌握模块中 mutation 的调用语法
- 注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
- 直接通过 store 调用
$store.commit('模块名/xxx ', 额外参数)
// user 模块
const state = {
userInfo: {
name: 'erer',
age: 18
},
score: 88
}
const mutations = {
updateUserInfo (state, newInfo) {
state.userInfo = newInfo
}
}
const action = {}
const getters = {}
export default {
namespaced: true,
state,
mutations,
action,
getters
}
<template>
<div class="box">
<div>{{ $store.state.user.userInfo.name }}</div>
<div>{{ $store.state.user.userInfo.age }}</div>
<!-- 访问模块中的 mutations -->
<button @click="changeUserInfo()">更新个人信息</button>
</div>
</template>
<script>
export default {
name: 'Son1Com',
methods: {
// 访问模块中的 mutations
changeUserInfo () {
this.$store.commit('user/updateUserInfo', {
name: '尔尔',
age: 30
})
}
}
}
</script>
<style lang="css" scoped>
</style>
- 通过 mapMutations 映射:默认根级别的映射
mapMutations([ 'xxx' ])
- 通过 mapMutations 映射:子模块的映射
mapMutations('模块名', ['xxx'])
- 需要开启命名空间
<template>
<div class="box">
<div>{{ userInfo.name }}</div>
<div>{{ userInfo.age }}</div>
<!-- 访问模块中的 mutations -->
<button @click="updateUserInfo({ name: '尔尔',age: 28 })">更新个人信息</button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
name: 'Son2Com',
computed: {
...mapState('user', ['userInfo'])
},
methods: {
...mapMutations('user', ['updateUserInfo'])
}
}
</script>
<style lang="css" scoped>
</style>
6. 访问 actions
- 目标:掌握模块中 action 的调用语法 (同理 - 直接类比 mutation 即可)
- 注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
- 直接通过 store 调用
$store.dispatch('模块名/xxx ', 额外参数)
// user 模块
const state = {
userInfo: {
name: 'erer',
age: 18
},
score: 88
}
const mutations = {
updateUserInfo (state, newInfo) {
state.userInfo = newInfo
}
}
const actions = {
updateUserInfoSecend (context, newInfo) {
setTimeout(() => {
context.commit('updateUserInfo', newInfo)
}, 1000)
}
}
const getters = {}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
<template>
<div class="box">
<!-- 访问模块中的 actions -->
<div>{{ $store.state.user.userInfo.name }}</div>
<div>{{ $store.state.user.userInfo.age }}</div>
<button @click="changeUserInfoSecond()">1s后更新个人信息</button>
</div>
</template>
<script>
export default {
name: 'Son1Com',
methods: {
// 访问模块中的 actions
changeUserInfoSecond () {
this.$store.dispatch('user/updateUserInfoSecend', {
name: '尔尔',
age: 30
})
}
}
}
</script>
<style lang="css" scoped>
</style>
- 通过 mapActions 映射:默认根级别的映射
mapActions([ 'xxx' ])
- 通过 mapActions 映射:
mapActions('模块名', ['xxx'])
- 需要开启命名空间
<template>
<div class="box">
<!-- 访问模块中的 actions -->
<div>{{ userInfo.name }}</div>
<div>{{ userInfo.age }}</div>
<button @click="updateUserInfoSecend( { name: '尔尔',age: 28 } )">1s后更新个人信息</button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
name: 'Son2Com',
computed: {
...mapState('user', ['userInfo']),
},
methods: {
...mapActions('user', ['updateUserInfoSecend'])
}
}
</script>
<style lang="css" scoped>
</style>
8、案例
1. 子模块
- user 模块:store/modules/user.js
// user 模块
const state = {
userInfo: {
name: 'erer',
age: 18
}
}
const mutations = {
updateUserInfo (state, newInfo) {
state.userInfo = newInfo
}
}
const actions = {
updateUserInfoSecend (context, newInfo) {
setTimeout(() => {
context.commit('updateUserInfo', newInfo)
}, 1000)
}
}
const getters = {
upperCaseName (state) {
return state.userInfo.name.toUpperCase()
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
2. 创建仓库
// 存放 vuex 相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
import user from './moduels/user'
// 插件安装
Vue.use(Vuex)
// 创建仓库
const store = new Vuex.Store({
strict: true,
// 1. 通过 state 提供所有组件共享的数据
state: {
title: '大标题',
count: 100,
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},
// 2. 通过 mutations 提供修改数据的方法
mutations: {
// 所有 mutations 函数的第一个参数都是 state
addCount (state, num) {
state.count += num
},
subCount (state, num) {
state.count -= num
},
changeCount (state, newValue) {
state.count = newValue
}
},
// 3. actions 处理异步
actions: {
// context 上下文,为分模块可以当场 store 仓库
changeCountAction (context, num) {
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
},
// 4. getters 类似于计算属性
getters: {
// 第一个参数都是 state,必须有返回值
filterList (state) {
return state.list.filter(item => item > 5)
}
},
// 5. modules 模块
modules: {
user
}
})
// 导出
export default store
3. 导入挂载
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from '@/store/index'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
4. 直接访问
<template>
<div class="box">
<h2>访问根仓库中的信息</h2>
<!-- 访问 state -->
<div>{{ $store.state.count }}</div> <!-- 100 -->
<div>{{ $store.state.list }}</div> <!-- [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] -->
<hr>
<!-- 访问 getters -->
<div>{{ $store.getters.filterList }}</div> <!-- [ 6, 7, 8, 9, 10 ] -->
<hr>
<!-- 访问 mutations -->
<button @click="handleAdd(1)">值 + 1</button>
<button @click="handleAdd(5)">值 + 5</button>
<hr>
<!-- 访问 actions -->
<button @click="handleChange">1秒后修改为666</button>
<hr>
<h2>访问模块中的信息</h2>
<!-- 访问模块中的 state -->
<div>{{ $store.state.user.userInfo.name }}</div> <!-- erer -->
<div>{{ $store.state.user.userInfo }}</div> <!-- { "name": "erer", "age": 18 } -->
<hr>
<!-- 访问模块中的 getters -->
<div>{{ $store.getters['user/upperCaseName'] }}</div> <!-- ERER -->
<hr>
<div>{{ $store.state.user.userInfo.name }}</div> <!-- erer -->
<div>{{ $store.state.user.userInfo.age }}</div> <!-- 18 -->
<!-- 访问模块中的 mutations -->
<button @click="changeUserInfo()">更新个人信息</button>
<hr>
<div>{{ $store.state.user.userInfo.name }}</div> <!-- erer -->
<div>{{ $store.state.user.userInfo.age }}</div> <!-- 18 -->
<!-- 访问模块中的 actions -->
<button @click="changeUserInfoSecond()">1s后更新个人信息</button>
</div>
</template>
<script>
export default {
name: 'Son1Com',
methods: {
handleAdd (n) {
// 访问 mutations
this.$store.commit('addCount', n)
},
handleChange () {
// 访问 actions
this.$store.dispatch('changeCountAction', 666)
},
changeUserInfo () {
// 访问模块中的 mutations
this.$store.commit('user/updateUserInfo', {
name: '尔尔',
age: 30
})
},
changeUserInfoSecond () {
// 访问模块中的 actions
this.$store.dispatch('user/updateUserInfoSecend', {
name: '尔尔',
age: 30
})
}
}
}
</script>
<style lang="css" scoped>
</style>
5. 子模块映射
<template>
<div class="box">
<h2>访问根仓库中的信息</h2>
<!-- 访问 state -->
<div>{{ count }}</div> <!-- 100 -->
<div>{{ list }}</div> <!-- [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] -->
<hr>
<!-- 访问 getters -->
<div>{{ filterList }}</div> <!-- [ 6, 7, 8, 9, 10 ] -->
<hr>
<!-- 访问 mutations -->
<button @click="subCount(1)">值 - 1</button>
<button @click="subCount(5)">值 - 5</button>
<hr>
<!-- 访问 actions -->
<button @click="changeCountAction(888)">1秒后修改为888</button>
<hr>
<h2>访问模块中的信息</h2>
<!-- 访问模块中的 state -->
<div>{{ user.userInfo.name }}</div> <!-- erer -->
<div>{{ userInfo }}</div> <!-- { "name": "erer", "age": 18 } -->
<hr>
<!-- 访问模块中的 getters -->
<div>{{ upperCaseName }}</div> <!-- ERER -->
<hr>
<div>{{ userInfo.name }}</div> <!-- erer -->
<div>{{ userInfo.age }}</div> <!-- 18 -->
<!-- 访问模块中的 mutations -->
<button @click="updateUserInfo({ name: '尔尔',age: 28 })">更新个人信息</button>
<hr>
<div>{{ userInfo.name }}</div> <!-- erer -->
<div>{{ userInfo.age }}</div> <!-- 18 -->
<!-- 访问模块中的 actions -->
<button @click="updateUserInfoSecend({ name: '尔尔',age: 28 })">1s后更新个人信息</button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
name: 'Son2Com',
computed: {
// mapState、mapGetters 映射属性
...mapState(['count', 'list']),
...mapGetters(['filterList']),
// 模块中的映射属性:mapState、mapGetters
...mapState(['user']),
...mapState('user', ['userInfo']),
...mapState('setting', ['theme', 'desc']),
...mapGetters('user', ['upperCaseName'])
},
methods: {
// mapMutations、mapActions 映射方法
...mapMutations(['subCount']),
...mapActions(['changeCountAction']),
// 模块中的映射方法:mapMutations、mapActions
...mapMutations('user', ['updateUserInfo']),
...mapActions('user', ['updateUserInfoSecend'])
}
}
</script>
<style lang="css" scoped>
</style>