VUE2
vue快速上手
vue概念
- Vue是一套**构建用户界面 ** 的 渐进式 框架,主要用于构建用户界面和前端交互
举个例子:
想象一下,你正在搭建一个房子,Vue 就像是一套工具箱,里面包含了各种工具,比如锤子、锯子、螺丝刀等。这些工具可以让你更快、更轻松地完成房子的建造。
创建vue实例
1)准备容器
2)引包 (官网) - 开发版本 / 生产版本
3)创建 Vue 实例 new Vue()
4)指定配置项 el data => 渲染数据
el 指定挂载点,选择器指定控制的是哪个盒子
data 提供数据
<body>
<!--
创建Vue实例,初始化渲染
1. 准备容器 (Vue所管理的范围)
2. 引包 (开发版本包 / 生产版本包) 官网
3. 创建实例
4. 添加配置项 => 完成渲染
-->
<!-- 不是Vue管理的范围 -->
<div class="box2">
box2 -- {{ count }}
</div>
<div class="box">
box -- {{ msg }}
</div>
-----------------------------------------------------
<!-- Vue所管理的范围 -->
<div id="app">
<!-- 这里将来会编写一些用于渲染的代码逻辑 -->
<h1>{{ msg }}</h1>
<a href="#">{{ count }}</a>
</div>
<!-- 引入的是开发版本包 - 包含完整的注释和警告 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
// 一旦引入 VueJS核心包,在全局环境,就有了 Vue 构造函数
const app = new Vue({
// 通过 el 配置选择器,指定 Vue 管理的是哪个盒子
el: '#app',
// 通过 data 提供数据
data: {
msg: 'Hello 传智播客',
count: 666
}
})
</script>
</body>
插值表达式
- 我们可以用插值表达式渲染出Vue提供的数据
- 利用表达式进行插值,渲染到页面中
- 语法格式:{{ 表达式 }}
<body>
<!--
插值表达式:Vue的一种模板语法
作用:利用 表达式 进行插值渲染
语法:{{ 表达式 }}
注意点:
1. 使用的数据要存在
2. 支持的是表达式,不是语句 if for
3. 不能在标签属性中使用 {{ }}
-->
<div id="app">
<p>{{ nickname }}</p>
<p>{{ nickname.toUpperCase() }}</p>
<p>{{ nickname + '你好' }}</p>
<p>{{ age >= 18 ? '成年' : '未成年' }}</p>
<p>{{ friend.name }}</p>
<p>{{ friend.desc }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
nickname: 'tony',
age: 18,
friend: {
name: 'jepson',
desc: '热爱学习 Vue'
}
}
})
</script>
</body>
- 错误用法:
1.在插值表达式中使用的数据 必须在data中进行了提供
<p>{{ hobby }}</p>//如果在data中不存在 则会报错
2.支持的是表达式,而非语句,比如:if for ...
<p>{{ if }}</p>
3.不能在标签属性中使用 {{ }} 插值 (插值表达式只能标签中间使用)
<p title="{{ nickname }}">我是p标签</p>
响应式特性
数据改变,视图会自动更新→使用Vue开发专注于业务核心逻辑
<body>
<div id="app">
{{ msg }}
{{ count }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 响应式数据 → 数据变化了,视图自动更新
msg: '你好,黑马',
count: 100
}
})
// data中的数据,是会被添加到实例上
// 1. 访问数据 实例.属性名
// 2. 修改数据 实例.属性名 = 新值
</script>
</body>
开发者工具
安装配置:
vue指令
常用指令
指令(Directives)是 Vue 提供的带有 v- 前缀 的 特殊 标签属性
内容渲染指令
用来辅助开发者渲染 DOM 元素的文本内容
1)v-text(类似innerText)
语法:<p v-text="uname">hello</p>
,将 uame 值渲染到 p 标签中
2)v-html(类似 innerHTML)
语法:<p v-html="intro">hello</p>
,将 intro 值渲染到 p 标签中
<div id="app">
<h2>个人信息</h2>
// 既然指令是vue提供的特殊的html属性,所以咱们写的时候就当成属性来用即可
<p v-text="uname">姓名:</p>
<p v-html="intro">简介:</p>
</div>
<script>
const app = new Vue({
el:'#app',
data:{
uname:'张三',
intro:'<h2>这是一个<strong>非常优秀</strong>的boy<h2>'
}
})
</script>
条件渲染指令
用来辅助开发者按需控制 DOM 的显示与隐藏
1)v-show
语法: v-show = “表达式” 表达式值为 true 显示, false 隐藏
原理:切换 display:none 控制显示隐藏
场景:频繁切换显示隐藏的场景
2)v-if
语法: v-if= “表达式” 表达式值 true显示, false 隐藏
原理:基于条件判断,是否创建 或 移除元素节点
场景:不频繁切换的场景
<body>
<!--
v-show底层原理:切换 css 的 display: none 来控制显示隐藏
v-if 底层原理:根据 判断条件 控制元素的 创建 和 移除(条件渲染)
-->
<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/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
flag: false
}
})
</script>
</body>
3)v-else、v-else-if
<body>
<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/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
gender: 2,
score: 95
}
})
</script>
</body>
事件绑定指令
为DOM注册事件时使用
注册事件=添加监听+提供处理逻辑
语法:v-on:事件名 = “处理函数”
简写:@事件名
<body>
<div id="app">
<button @click="fn">切换显示隐藏</button>
<h1 v-show="isShow">黑马程序员</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app4 = new Vue({
el: '#app',
data: {
isShow: true
},
methods: {
fn () {
// 让提供的所有methods中的函数,this都指向当前实例
// console.log('执行了fn', app.isShow)
// console.log(app3 === this)
this.isShow = !this.isShow
}
}
})
</script>
</body>
属性绑定指令
动态设置html的标签属性 比如:src、url、title
语法:**v-bind:**属性名=“表达式”
简写: :属性名=“表达式”
<img v-bind:src="url" />
= <img :src="url" />
(v-bind可以省略)
<body>
<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/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
imgUrl: './imgs/10-02.png',
msg: 'hello'
}
})
</script>
</body>
列表渲染指令
使用v-for列表渲染指令,辅助开发者基于一个数组来循环渲染一个列表结构
语法:
v-for = “(item, index) in arr” :key=“唯一值”
item→数组中的每一项, index →每一项的索引
key=“唯一值”,给列表项添加的唯一标识
<div id="app">
<h3>小黑的书架</h3>
<ul>
<li v-for="(item,index) in booksList":key="item.id">
<span>{{item.name}}</span>
<span>{{item.author}}</span>
<button @click="del(item.id)">删除</button>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/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) {
// console.log('删除', id);
// filter:根据条件,保留满足条件的对应项,得到一个新数组
// 不会改变原数组,指挥将收集的放在一个新的数组
// this.booksList.filter(item => item.id !== id)
this.booksList = this.booksList.filter(item => item.id !== id)
}
}
})
</script>
双向绑定指令V-model
即数据改变视图自动更新;视图变化数据自动更新
作用:给表单元素(input、radio、select)使用,双向绑定数据
语法:v-model=“变量”
<body>
<div id="app">
<!--
v-model 可以让数据和视图,形成双向数据绑定
(1) 数据变化,视图自动更新
(2) 视图变化,数据自动更新
可以快速[获取]或[设置]表单元素的内容
-->
账户:<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/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>
</body>
补充指令
指令修饰符
通过“.”指明一些指令后缀 →不同的后缀封装了不同的处理操作 —> 简化代码
1)按键修饰符:@keyup.enter
—>当点击enter键的时候才触发
<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/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: ''
},
methods: {
fn (e) {
// if (e.key === 'Enter') {
// console.log('键盘回车的时候触发', this.username)
// }
console.log('键盘回车的时候触发', this.username)
}
}
})
</script>
2)v-model修饰符:
v-model.trim
—>去除首位空格
v-model.number
—>转数字
3)事件修饰符:
@事件名.stop —> 阻止冒泡
@事件名.prevent —>阻止默认行为
@事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为
<div id="app">
<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>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
age: '',
},
methods: {
fatherFn () {
alert('老父亲被点击了')
},
sonFn (e) {
// e.stopPropagation()
alert('儿子被点击了')
}
}
})
</script>
v-bind对样式增强的操作
1)操作class:针对 class 类名 和 style 行内样式 进行控制
语法::class = “对象/数组”
对象:适用一个类名,来回切换
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
<div class="box" :class='{red:true,big:true}'>dimple</div>
数组:适用批量添加或删除类
<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>
<div class="box" :class="['red','big']">dimple</div>
<div id="app">
<div class="box" :class="{ red: true, big: true }">dimple</div>
<div class="box" :class="['red', 'big']">dimple</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
2)操作style:
语法::style = “样式对象”
适用场景:某个具体属性的动态设置
<div id="app">
<div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></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应用于其他表单元素
输入框 input:text ——> value
文本域 textarea ——> value
复选框 input:checkbox ——> checked
单选框 input:radio ——> checked
下拉菜单 select ——> value
...
<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>
自我描述:
<textarea v-model="desc"></textarea>
<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: "2",
cityId: '102',
desc: ""
}
})
</script>
计算属性与侦听器
computed计算属性与methods计算属性
1)computed计算属性:封装了一段对于数据的处理,求得一个结果
2)methods计算属性:给Vue实例提供一个方法,调用以处理业务逻辑
<div id="app">
<h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
<h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
<h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
<h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</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>礼物总数:{{ totalCountFn() }} 个</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: {
totalCountFn () {
console.log('methods方法执行了')
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>
两者比较:
计算属性具有缓存特性,再次使用时直接读取缓存
methods没有缓存特性,太麻烦了
3)计算属性完整写法
计算属性默认的简写→只能读取访问,无法修改
如果要修改→需使用完整写法
<div id="app">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<span>{{ fullName}}</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.fullName = '吴青峰'
}
},
computed: {
// 简写→获取,没有配置设置的逻辑
// fullName() {
// return this.firstName + this.lastName
// }
// 完整写法
fullName: {
// 1.当fullname计算数学被获取求值时,执行get(有缓存,先读缓存)
// 会将返回值作为求值的结果
get() {
return this.firstName + this.lastName
},
// 2.当fullname计算数学,被修改赋值时,执行set
// 修改的值,传给set方法的形参
set(value) {
// console.log(value);
// console.log(value.slice(0, 1));
// console.log(value.slice(1));
/*
slice(start:开始位置的索引,end:结束位置的索引,但不包含该索引位置的元素)→截取
2.splice(index,0,插入的项)→删除
*/
this.firstName = value.slice(0, 1)
this.lastName = value.slice(1)
}
}
}
})
</script>
watch侦听器
监视数据变化,执行一些业务逻辑或异步操作
1)简单写法:简单类型数据直接监视
<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">mela</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: ''
}
},
// 具体讲解:(1) watch语法 (2) 具体业务实现
watch: {
// 该方法会在数据变化时调用执行
// newValue新值, oldValue老值(一般不用)
// words (newValue) {
// console.log('变化了', newValue)
// }
'obj.words' (newValue) {
console.log('变化了', newValue)
}
}
})
</script>
2)完整写法:添加额外配置项
- deep:true 对复杂类型进行深度监听
- immdiate:true 初始化 立刻执行一次
watch: {// watch 完整写法
对象: {
deep: true, // 深度监视
immdiate:true,//立即执行handler函数
handler (newValue) {
console.log(newValue)
}
}
}
生命周期
简介
一个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('created 响应式数据准备好之后', this.count)
// this.数据名 = 请求回来的数据
// 可以开始发送初始化渲染的请求了
},
// 2. 挂载阶段(渲染模板)
beforeMount () {
console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
},
mounted () {
console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
// 可以开始操作dom了
},
// 3. 更新阶段(修改数据 → 更新视图)
beforeUpdate () {
console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
},
updated () {
console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML)
},
// 4. 卸载阶段
beforeDestroy () {
console.log('beforeDestroy, 卸载前')
console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
},
destroyed () {
console.log('destroyed,卸载后')
}
})
</script>
工程化开发
开发Vue的两种方式
-
核心包传统开发模式:基于html / css / js 文件,直接引入核心包,开发 Vue。
-
工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue
-
工程化配置优点:提高编码效率,比如使用JS新语法、Less/Sass、Typescript等通过webpack都可以编译成浏览器识别的ES3/ES5/CSS等
-
问题:
1)webpack配置不简单
2)雷同的基础配置
3)缺乏统一的标准
- 为了解决以上问题,所以我们需要一个工具,生成标准化的配置
脚手架Vue CLI
是Vue官方提供的一个全局命令工具,可以帮助我们快速创建一个开发Vue项目的标准化基础架子。
使用步骤:
全局安装(只需安装一次即可) 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)
项目目录介绍
重点关注:main.js 入口文件、App.vue App根组件 、index.html 模板文件
运行流程:
组件
组件化开发
一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为
优点:便于维护,利于复用 → 提升开发效率
组件分类:普通组件、根组件
如下图,可按照模块对组件进行划分
根组件APP.VUE
- 整个应用最上层的组件,包裹所有普通小组件
语法高亮插件:
组件构成:
1)template:结构 (有且只能一个根元素)
2)script: js逻辑
3)style: 样式 (可支持less,需要装包)
让组件支持less
(1) style标签,lang=“less” 开启less功能
(2) 装包: yarn add less less-loader -D 或者npm i less less-loader -D
<template>
<!-- vue2中有且只能用一个根元素 -->
<div class="app">
<div class="box" @click="fn"></div>
</div>
</template>
<script>
// 导出的是当前组件的配置项
// 里面可以提供data(特殊) methods computed watch生命周期
export default {
created(){
console.log('我是created');
},
methods:{
fn(){
alert('你好')
}
}
}
</script>
<style lang="less">
/* 让style支持less
1.给style加上lang="less"
2.按照依赖包,less less-loader
npm i less less-loader -D
*/
.app{
width: 400px;
height: 400px;
background-color:pink;
.box{
width: 100px;
height: 100px;
background-color: blue;
}
}
</style>
普通组件注册-局部注册
只能在注册的组件内使用
步骤:
1)创建 .vue 文件 (三个组成部分)
2)在使用的组件内导入并注册
命名:大驼峰命名法, 如 HmHeader
// 导入需要注册的组件
import 组件对象 from '.vue文件路径'
import HmHeader from './components/HmHeader'
export default { // 局部注册
components: {
'组件名': 组件对象,
HmHeader:HmHeaer,
HmHeader
}
}
如下图,APP.VUE由三个组件构成
HmHeader.vue
<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>
HmMain.vue
<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>
HmFooter.vue
<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>
App.vue
<template>
<div class="app">
<HmHeader></HmHeader>
<HmMain></HmMain>
<HmFooter></HmFooter>
</div>
</template>
<script>
import HmHeader from './components/HmHeader'
import HmMain from './components/HmMain'
import HmFooter from './components/HmFooter'
export default { // 局部注册
components: {
//'组件名': 组件对象,
// HmHeader:HmHeaer,
HmHeader,
HmMain,
HmFooter
}
}
</script>
普通组件注册-全局注册
全局注册的组件,在项目的任何组件中都能使用
步骤:
1)创建.vue组件(三个组成部分)
2)main.js中进行全局注册
语法:Vue.component(‘组件名’, 组件对象)
// 文件核心作用:导入app.vue,基于app.vue创建结构渲染index.html
// 1.导入vue核心包
import vue from 'vue'
// 2.导入app.vue的跟组件
import App from './App.vue'
// 编写导入的代码,往代码的顶部编写(规范)
import HmButton from './components/HmButton'
Vue.config.productionTip = false
// 进行全局注册→在所有的组件范围内都能直接使用
// createApp(App).component(组件名,组件对象)
vue.component('HmButton', HmButton)
// 3.基于APP创建结构渲染index.html
new Vue({
render: h => h(App),
}).$mount('#app')
举例,如需要给 HmHeader,HmMain, HmFooter三部分中展示一个通用按钮
HmButton.Vue
<template>
<button class="hm-button">通用按钮</button>
</template>
<script>
export default {
}
</script>
<style>
.hm-button {
height: 50px;
line-height: 50px;
padding: 0 20px;
background-color: #3bae56;
border-radius: 5px;
color: white;
border: none;
vertical-align: middle;
cursor: pointer;
}
</style>
组件的样式冲突解决
1.全局样式:默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响
2.局部样式:给组件加上scoped 属性→让样式只作用于当前组件
<style scoped>
/*
1.style中的样式 默认是作用到全局的
2.加上scoped可以让样式变成局部样式
组件都应该有独立的样式,推荐加scoped(原理)
-----------------------------------------------------
scoped原理:
1.给当前组件模板的所有元素,都会添加上一个自定义属性
data-v-hash值
data-v-5f6a9d56 用于区分开不通的组件
2.css选择器后面,被自动处理,添加上了属性选择器
div[data-v-5f6a9d56]
*/
div{
border: 3px solid blue;
margin: 30px;
}
</style>
scoped原理:
当前组件内标签都被添加data-v-hash值 的属性
css选择器都被添加 [data-v-hash值] 的属性选择器
data必须是一个函数
是为了保证每个组件实例,维护独立的一份数据对象
每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象
export default {
// data必须是一个函数,
data() {
return {
count: 999,
}
},
}
组件通信
实现组件与组件之间的数据传递
组件的数据是独立的,无法直接访问其他组件的数据→需采用组件通信
解决方法:父子关系、非父子关系
父子通信流程
1)父组件通过 props 将数据传递给子组件
- 给子组件以添加属性的方式传值
- 子组件内部通过props接收
- 模板中直接使用 props接收的值
父组件App.vue
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 1.给组件标签,添加属性方式 赋值 -->
<Son :title="myTitle"></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">
<!-- 3.直接使用props的值 -->
我是Son组件 {{title}}
</div>
</template>
<script>
export default {
name: 'Son-Child',
// 2.通过props来接受
props:['title']
}
</script>
<style>
</style>
2)子组件利用 $emit 通知父组件修改更新
$emit触发事件,给父组件发送消息通知
父组件监听$emit触发的事件
提供处理函数,在函数的性参中获取传过来的参数
子组件Son.vue
<template>
<div class="son" style="border: 3px solid #000; margin: 10px">
我是Son组件 {{ title }}
<button @click="changeFn">修改title</button>
</div>
</template>
<script>
export default {
name: 'Son-Child',
props: ['title'],
methods: {
changeFn() {
//1. 通过this.$emit() 向父组件发送通知
this.$emit('changTitle','今天也开心')
},
},
}
</script>
<style>
</style>
父组件App.vue
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 2.父组件对子组件的消息进行监听 -->
<Son :title="myTitle" @changTitle="handleChange"></Son>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
myTitle: '学前端,就来黑马程序员',
}
},
components: {
Son,
},
methods: {
// 3.提供处理函数,提供逻辑
handleChange(newTitle) {
this.myTitle = newTitle
},
},
}
</script>
<style>
</style>
Props
Props是组件上注册的一些 自定义属性
作用:向子组件传递数据
特点:可以传递任意数量、任意类型的prop
props校验:为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误
props: {
校验的属性名: {
type: 类型, // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
},
props&data、单向数据流
区别:
data 的数据是自己的 → 随便改
prop 的数据是外部的 → 不能直接改,要遵循 单向数据流
单向数据流
父级props 的数据更新,会向下流动,影响子组件→这个数据流动是单向的
<template>
<div class="base-count">
<button @click="handleSub">-</button>
<span>{{ count }}</span>
<button @click="handleAdd">+</button>
</div>
</template>
<script>
export default {
// 1.自己的数据随便修改 (谁的数据 谁负责)
// data () {
// return {
// count: 100,
// }
// },
// 2.外部传过来的数据 不能随便修改
props: {
count: {
type: Number,
},
},
methods: {
handleSub() {
this.$emit('changeCount', this.count - 1)
},
handleAdd() {
this.$emit('changeCount', this.count + 1)
},
},
}
</script>
<style>
.base-count {
margin: 20px;
}
</style>
非父子通信(拓展)
event bus事件总线
步骤
创建一个都能访问的事件总线 (空Vue实例)
import Vue from 'vue'
const Bus = new Vue()
export default Bus
A组件(接受方),监听Bus的 $on事件
created () {
Bus.$on('sendMsg', (msg) => {
this.msg = msg
})
}
B组件(发送方),触发Bus的$emit事件
Bus.$emit('sendMsg', '这是一个消息')
代码示例:
EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
BaseA.vue(接收方)
<template>
<div class="base-a">
我是A组件(接受方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = 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 @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>
BaseB.vue(接收方)
<template>
<div class="base-c">
我是C组件(接受方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
})
},
}
</script>
<style scoped>
.base-c {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
provide&inject
1.父组件 provide提供数据
<template>
<div class="app">
我是APP组件
<button @click="change">修改数据</button>
<SonA></SonA>
<SonB></SonB>
</div>
</template>
<script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {
provide() {
return {
// 简单类型 是非响应式的
color: this.color,
// 复杂类型 是响应式的
userInfo: this.userInfo,
}
},
data() {
return {
color: 'pink',
userInfo: {
name: 'zs',
age: 18,
},
}
},
methods: {
change() {
this.color = 'red'
this.userInfo.name = 'ls'
},
},
components: {
SonA,
SonB,
},
}
</script>
<style>
.app {
border: 3px solid #000;
border-radius: 6px;
margin: 10px;
}
</style>
2.子/孙组件 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>
进阶语法
v-model
原理
其本质为语法糖,即为value属性
和 input事件
的合写
- 数据变,视图跟着变 :value
- 视图变,数据跟着变 @input
- $event 用于在模板中,获取事件的形参
<template>
<div id="app" >
<input v-model="msg1" type="text">
//不能直接写e,要通过$event→模板中获取事件
<input :value="msg2" @input="msg2 = $event.target.value" type="text">
</div>
</template>
v-model简化代码
v-model其实就是 :value和@input事件的简写
子组件:props通过value接收数据,事件触发 input
<select :value="value" @change="handleChange">...</select>
props: {
value: String
},
methods: {
handleChange (e) {
this.$emit('input', e.target.value)
}
}
父组件:v-model直接绑定数据
<BaseSelect v-model="selectId"></BaseSelect>
.sync修饰符
可以实现 子组件 与 父组件数据 的 双向绑定→子组件可以修改父组件传过来的props值
封装弹框类的基础组件时 → visible
属性 true显示 false隐藏
本质:.sync修饰符 就是 :属性名 和 @update:属性名 合写
父组件
//.sync写法
<BaseDialog :visible.sync="isShow" />
--------------------------------------
//完整写法
<BaseDialog
:visible="isShow"
@update:visible="isShow = $event"
/>
子组件
props: {
visible: Boolean
},
this.$emit('update:visible', false)
ref和$refs
利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例
语法:
1.目标组件 – 添加 ref 属性
<div ref="chartRef">我是渲染图表的容器</div>
2.通过 this.$refs.xxx, 获取目标组件,就可以调用组件对象里面的方法
(注意:之前只用document.querySelect(‘.box’) 获取的是整个页面中的盒子)
mounted () {
console.log(this.$refs.chartRef)
}
Vue异步更新 & $nextTick
要求:编辑标题, 编辑框自动聚焦
语法:
1.点击编辑,显示编辑框
让编辑框,立刻获取焦点
this.isShowEdit = true //显示输入框
this.$refs.inp.focus() //获取焦点
$nextTick:等 DOM 更新后, 才会触发执行此方法里的函数体
语法: this.$nextTick(函数体)
注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例
this.$nextTick(() => {
this.$refs.inp.focus()
})
自定义指令
- 内置指令:v-html、v-if、v-bind、v-on… ,可以直接使用
- 自定义指令:v-focus、v-loading、v-lazy…
基本语法
1)全局注册
//在main.js中
// 1.全局注册指令
Vue.directive('focus', {
// inserted会在指令所在的元素,被插入到页面中时触发
inserted(el) {
// el 就是指令所绑定的元素
// console.log(el);
el.focus()
}
})
2)局部注册
//在Vue组件的配置项中
directives:{
// 指令名:指令配置项
focus:{
inserted(el){
el.focus()
}
}
}
3)使用:当页面加载时,让元素将获得焦点
<input v-focus ref="inp" type="text">
指令的值
语法
1.在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值
<div v-color="color">我是内容</div>
2.通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数
directives: {
color: {
inserted (el, binding) {
el.style.color = binding.value
},
update (el, binding) {
el.style.color = binding.value
}
}
}
插槽
默认插槽
让组件内部的一些 结构 支持 自定义
组件内容部分,不希望写死,希望使用时候自定义
基本语法:
- 组件内需要定制的结构部分,改用slot占位
- 使用组件时, MyDialog标签内部, 传入结构替换slot
- 给插槽传入内容时,可以传入纯文本、html标签、组件
MyDialog.vue
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<!-- 1.在需要定制的位置使用slot占位 -->
<slot></slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
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>
具名插槽
一个组件有多处结构需要外界传入标签,但默认插槽只能定制一个位置,这时可采用具名插槽
语法:
多个slot使用name属性区分名字
<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>
template配合v-slot:名字来分发对应标签
<template>
<div>
<MyDialog>
<!-- 需要通过template标签包裹需要分发的结构,包成一个整体 -->
<template v-slot:head>
<div>我是大标题</div>
</template>
<template v-slot:content>
<div>我是内容</div>
</template>
<template #footer>
<button>取消</button>
<button>确认</button>
</template>
</MyDialog>
</div>
</template>
作用域插槽
给插槽可以绑定数据,进行传值→可用来封装表格组件
使用步骤
-
给 slot 标签, 以 添加属性的方式传值
<slot :id="item.id" msg="测试文本"></slot>
-
所有添加的属性, 都会被收集到一个对象中
{ id: 3, msg: '测试文本' }
-
在template中, 通过
#插槽名= "obj"
接收,默认插槽名为 default<MyTable :list="list"> <template #default="obj"> <button @click="del(obj.id)">删除</button> </template> </MyTable>
MyTable.vue
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr 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>
</style>
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) {
// console.log(row);
alert(`姓名:${row.name}; 年纪:${row.age}`)
}
},
components: {
MyTable
}
}
</script>
路由
路由入门
基本使用
Vue中路由:路径和组件的映射关系
根据路由就能知道不同路径,应该匹配渲染哪个组件
VueRouter的使用(5+2)
5个基础步骤
-
下载 VueRouter 模块到当前工程,版本3.6.5
yarn add [email protected]
-
main.js中引入VueRouter
import VueRouter from 'vue-router'
-
安装注册
Vue.use(VueRouter)
创建路由对象
const router = new VueRouter()
注入,将路由对象注入到new Vue实例中,建立关联
new Vue({ render: h => h(App), router:router }).$mount('#app')
上述5步完成之后,便可看到浏览器路由地址变为/#/的形式→项目路由已被Vue-Router管理
2个核心步骤
1.创建需要的组件(view目录),配置路由规则
2.配置导航,配置路由出口(路径匹配的组件显示的位置)
main.js
import Vue from 'vue'
import App from './App.vue'
// 路由的使用步骤 5 + 2
// 5个基础步骤
// 1. 下载 v3.6.5
// 2. 引入
// 3. 安装注册 Vue.use(Vue插件)
// 4. 创建路由对象
// 5. 注入到new Vue中,建立关联
// 2个核心步骤
// 1. 建组件(views目录),配规则
// 2. 准备导航链接,配置路由出口(匹配的组件展示的位置)
import Find from './views/Find'
import My from './views/My'
import Friend from './views/Friend'
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 },
]
})
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router
}).$mount('#app')
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>
组件存放目录
组件分类(分类存放,更易维护):
页面组件(src/views文件夹)-页面展示-配合路由使用
复用组件(src/components文件夹)-展示数据-封装复用
路由的封装抽离
所有的路由配置都在main.js中不太合→将路由模块抽离处理,拆分模块,利于维护
路由进阶
声明式导航
1)导航链接: router-link (取代 a 标签)
要实现导航高亮效果,使用a标签太麻烦,vue-router 提供了一个全局组件 router-link 来取代a
语法:
能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #
能高亮,默认就会提供高亮类名,可以直接设置高亮样式
<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>
2)两个类名
使用router-link跳转后→当点击链接时,默认添加两个class值→ router-link-exact-active
和router-link-active
router-link-active:模糊匹配(用的多)→to=“/my” 可以匹配 /my /my/a /my/b …→只要是以/my开头的路径 都可以和 to="/my"匹配到
router-link-exact-active:精确匹配→to=“/my” 仅可以匹配 /my
3)自定义类名
router-link的两个高亮类名 太长了,可以自己定制
在创建路由对象时,额外配置两个配置项, linkActiveClass
和linkExactActiveClass
// 创建了一个路由对象
const router = new VueRouter({
routes: [
...
],
linkActiveClass: 'active', // 配置模糊匹配的类名
linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})
4)跳转传参
两种传参方式:
- 查询参数传参 (比较适合传多个参数)
语法格式如下
1.跳转:<router-link to="/path?参数名=值"></router-link>
2.获取:$router.query.参数名
2.动态路由传参 (优雅简洁,传单个参数比较方便)
1.配置动态路由:path: "/path/参数名"
2.跳转:to="/path/参数值"
3.获取:$route.params.参数名
路由重定向
问题:网页打开时,url默认是/路径,未匹配到组件时,会出现空白
解决方法:重定向→ 匹配 / 后, 强制跳转 /home 路径
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search }
]
})
export default router
路由404
当路径找不到匹配时,应该给个提示页面→配在路由最后面
语法:path: “*” (任意路径) – 前面不匹配就命中最后这个
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search },
{ path: '*', component: NotFound }
]
})
export default router
路由模式
hash路由(默认) 例如: http://localhost:8080/#/home
lhistory路由(常用) 例如: http://localhost:8080/home (以后上线需要服务器端支持,开发环境webpack给规避掉了history模式的问题)
const router = new VueRouter({
//一旦采用了history模式,地址栏就没有#了,需要后台配置访问规则
mode:'histroy', //默认是hash
routes:[]
})
编程式导航-两种路径跳转方式
编程式导航:用JS代码来进行跳转
- path 路径跳转(简易方便)
//简单写法
this.$router.push('路由路径')
//完整写法
this.$router.push({
path: '路由路径'
})
2.name 命名路由跳转 (适合 path 路径长的场景)
路由规则,必须配置name配置项
// 2.通过命名路由的方式跳转,比较适合长路径
{ name: '路由名', path: '/path/xxx', component: XXX },
通过name来进行跳转
this.$router.push({
name: '路由名'
})
编程式导航-两种路径跳转传参
- 查询参数传参、动态路由传参
- 两种跳转方式,对于两种传参方式都支持:
- path 路径跳转传参; name 命名路由跳转传参
1)path路径跳转传参(query传参)
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
this.$router.push({
path: '/路径',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
2)path路径跳转传参(动态路由传参)
this.$router.push('/路径/参数值')
this.$router.push({
path: '/路径/参数值'
})
3)name 命名路由跳转传参 (query传参)
this.$router.push({
name: '路由名字',
query: {
参数名1: '参数值1',
参数名2: '参数值2'
}
})
4)name 命名路由跳转传参 (动态路由传参)
this.$router.push({
name: '路由名字',
params: {
参数名: '参数值',
}
})
VueCli 自定义创建项目
1.安装脚手架 (已安装)
npm i @vue/cli -g
2.创建项目
vue create exp-mobile
- 选项
Vue CLI v5.0.8
? Please pick a preset:
Default ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
> Manually select features 选自定义
-
手动选择功能
-
选择vue的版本
3.x
> 2.x
- 是否使用history模式
- 选择css预处理
- 选择eslint的风格 (eslint 代码规范的检验工具,检验代码是否符合规范)
- 比如:const age = 18; => 报错!多加了分号!后面有工具,一保存,全部格式化成最规范的样子
- 选择校验的时机 (直接回车)
-
选择配置文件的生成方式 (直接回车)
-
是否保存预设,下次直接使用? => 不保存,输入 N
- 等待安装,项目初始化完成
- 启动项目
npm run serve
ESlint代码规范修复
- eslint会自动高亮错误显示
- 通过配置,eslint会自动帮助我们修复错误
配置
// 当保存的时候,eslint自动帮我们修复错误
"editor.codeActionsOnSave": {
"source.fixAll": true
},
// 保存代码,不自动格式化
"editor.formatOnSave": false
注意:eslint配置文件必须在根目录下,插件才能才能生效。打开项目必须以根目录打开,一次打开一个项目
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
官方文档:https://vuex.vuejs.org/zh/
Vuex是一个插件,可以帮我们管理 Vue 通用的数据 (多组件共享的数据)
共同维护一份数据,数据集中化管理
例如:购物车数据 个人信息数
基本使用
1.安装:vuex是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装
yarn add vuex@3 或者 npm i vuex@3
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()
// 导出仓库
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
created(){
console.log(this.$store)
}
核心概念(state、mutation、actions、getters)
state状态
State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储
1)创建Store数据源,提供唯一公共数据源
// 创建仓库 store
const store = new Vuex.Store({
// state 状态, 即数据, 类似于vue组件中的data,
// 区别:
// 1.data 是组件自己的数据,
// 2.state 中的数据整个vue项目的组件都能访问到
state: {
title: '大标题',
count: 101
}
})
2)访问数据
通过 store 直接访问→{{ $store.state.count }}
获取 store:
1.Vue模板中获取 this.$store
2.js文件中获取 import 导入 store
模板中: {{ $store.state.xxx }}
组件逻辑中: this.$store.state.xxx
JS模块中: store.state.xxx
在模板中使用
<h1>state的数据 - {{ $store.state.count }}</h1>
组件逻辑中使用
<h1>state的数据 - {{ count }}</h1>
// 把state中数据,定义在组件内的计算属性中
computed: {
count () {
return this.$store.state.count
}
}
js文件中使用
//main.js
import store from "@/store"
console.log(store.state.count)
辅助函数- mapState
通过辅助函数mapState 映射计算属性→{{ count }}
将store中的数据映射到组件的计算属性中
使用步骤:
1导入mapState (mapState是vuex中的一个函数)
import { mapState } from 'vuex'
2采用数组形式引入state属性
mapState(['count'])
3利用展开运算符将导出的状态映射给计算属性
computed: {
...mapState(['count'])
}
<div> state的数据:{{ count }}</div>
mutations
用于同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化)
基础使用
1定义 mutations 对象,对象中存放修改 state 的方法
2组件中提交调用 mutations
const store = new Vuex.Store({
state: {
count: 0
},
// 定义mutations
mutations: {
// 方法里参数 第一个参数是当前store的state属性
// payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷
addCount (state) {
state.count += 1
}
}
})
3组件中提交 mutations
this.$store.commit('addCount')
mutations传递参数
1提供函数
mutations: {
// 所有mutation函数,第一个参数都是state
addCount (state, n) {
// 修改数据
state.count += n
},
changeTitle (state, newTitle) {
state.title = newTitle
}
},
2提交mutation
handleAdd (n) {
// 严格模式(有利于初学者检查代码,上线时需要关闭)
// 错误代码(vue默认不会检测,检测需要成本)
// this.$store.state.count++
// console.log(this.$store.state.count)
// 应该通过mutation核心概念,进行修改数据
// 需要提交调用mutation
//
// console.log(n)
// 调用带参数的mutation函数
this.$store.commit('addCount', n)
},
changeFn () {
this.$store.commit('changeTitle', '黑马')
}
多参数传参,可传递一个对象
this.$store.commit('addCount', {
count: n,
msg: '哈哈'
})
辅助函数mapMutations
它和mapState类似,把mutations中的方法提取出来
//1.从Vuex中按需导入 mapMutations 函数
import { mapMutations } from 'vuex'
//2.将指定的mutations函数,映射为当前组件的 methods 方法
methods: {
...mapMutations(['addCount'])
}
此时,即可调用
<button @click="addCount">值+1</button>
actions
用于异步更新
基础使用
1提供actions 方法
mutations: {
changeCount (state, newCount) {
state.count = newCount
}
}
// 3.处理异步,不能直接操作state,还是需要commit mutation
actions: {
// context上下文(此处未分模块,可以当成store仓库)
// context.commit('changeCount', 额外参数)
changeCountAction (context, num) {
// 这里时settimeou模拟异步,以后大部分是发送请求
setTimeout(() => {
context.commit('changeCount', num)
}, 1000)
}
},
2页面中 dispatch 调用
handleChange () {
// 调用action
this.$store.dispatch('changeCountAction', 666)
}
辅助函数mapActions
mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中
Son2.vue
import { mapActions } from 'vuex'
methods: {
...mapActions(['changeCountAction'])
}
//mapActions映射的代码 本质上是以下代码的写法
//methods: {
// changeCountAction (n) {
//直接通过 this.方法 就可以调用
// this.$store.dispatch('changeCountAction', n)
// },
//}
getters
可以对Store中的数据进行加工处理形成新的数据,类似于Vue的计算属性
基础使用
1定义getters
getters: {
// getters函数的第一个参数是 state
// 必须要有返回值
filterList: state => state.list.filter(item => item > 5)
}
2使用getters
<div>{{ $store.getters.filterList }}</div>
辅助函数mapGetters
1通过 store 访问 getters
computed: {
...mapGetters(['filterList'])
}
2通过辅助函数 mapGetters 映射
<div>{{ filterList }}</div>
模块module(进阶语法)
若使用单一状态数,应用的所有状态会集中到一个比较大的对象。
因此有了Vuex的模块化
语法:
定义两个模块 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 {
namespaced:true
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
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)"
综合案例-购物车
创建项目
脚手架新建项目 (注意:勾选vuex)
vue create vue-cart-demo
构建vuex-cart模块
1.新建 store/modules/cart.js
export default {
namespaced: true,
state () {
return {
list: []
}
},
}
2.挂载到 vuex 仓库上 store/cart.js
import Vuex from 'vuex'
import Vue from 'vue'
import cart from './modules/cart'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
cart
}
})
export default store
准备后端接口服务环境
- 安装全局工具 json-server (全局工具仅需要安装一次)
yarn global add json-server 或 npm i json-server -g
2.代码根目录新建一个 db 目录
3.将资料 index.json 移入 db 目录
4.进入 db 目录,执行命令,启动后端接口服务 (使用–watch 参数 可以实时监听 json 文件的修改)
json-server --watch index.json
请求动态渲染数据
请求获取数据存入 vuex, 映射渲染
- 安装 axios
yarn add axios
2.准备actions 和 mutations
mutations: {
updateList (state, newList) {
state.list = newList
},
updateCount (state, obj) {
// 根据id找到对应的对象,更新count属性即可
const goods = state.list.find(item => item.id === obj.id)
goods.count = obj.newCount
}
},
actions: {
// 请求方式:get
// 请求地址:http://localhost:3000/cart
async getList (context) {
const res = await axios.get('http://localhost:3000/cart')
// console.log(res)
context.commit('updateList', res.data)
},
// 请求方式:patch
async updateCountAsync (context, obj) {
// 将修改更新同步到后台服务
// console.log(obj)
await axios.patch(`http://localhost:3000/cart/${obj.id}`, {
count: obj.newCount
})
// 将修改更新同步到vuex
context.commit('updateCount', {
id: obj.id,
newCount: obj.newCount
})
}
},
3.App.vue
页面中调用 action, 获取数据
import { mapState } from 'vuex'
export default {
name: 'App',
components: {
CartHeader,
CartFooter,
CartItem
},
created () {
this.$store.dispatch('cart/getList')
},
computed: {
...mapState('cart', ['list'])
}
}
4.动态渲染
<!-- 商品 Item 项组件 -->
<cart-item v-for="item in list" :key="item.id" :item="item"></cart-item>
cart-item.vue
<template>
<div class="goods-container">
<!-- 左侧图片区域 -->
<div class="left">
<img :src="item.thumb" class="avatar" alt="">
</div>
<!-- 右侧商品区域 -->
<div class="right">
<!-- 标题 -->
<div class="title">{{item.name}}</div>
<div class="info">
<!-- 单价 -->
<span class="price">¥{{item.price}}</span>
<div class="btns">
<!-- 按钮区域 -->
<button class="btn btn-light">-</button>
<span class="count">{{item.count}}</span>
<button class="btn btn-light">+</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CartItem',
props: {
item: Object
},
methods: {
}
}
</script>
修改数量
1.注册点击事件
<!-- 按钮区域 -->
<button class="btn btn-light" @click="onBtnClick(-1)">-</button>
<span class="count">{{item.count}}</span>
<button class="btn btn-light" @click="onBtnClick(1)">+</button>
2.页面中dispatch action
onBtnClick (step) {
const newCount = this.item.count + step
if (newCount < 1) return
// 发送修改数量请求
this.$store.dispatch('cart/updateCount', {
id: this.item.id,
count: newCount
})
}
3.提供action函数
async updateCount (ctx, payload) {
await axios.patch('http://localhost:3000/cart/' + payload.id, {
count: payload.count
})
ctx.commit('updateCount', payload)
}
4.提供mutation处理函数
mutations: {
...,
updateCount (state, payload) {
const goods = state.list.find((item) => item.id === payload.id)
goods.count = payload.count
}
},
底部总价展示
1.提供getters
getters: {
// 商品总数量 累加count
total (state) {
return state.list.reduce((sum, item) => sum + item.count, 0)
},
// 商品总价 累加count* price
totalPrice (state) {
return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
}
}
2.动态渲染
<template>
<div class="footer-container">
<!-- 中间的合计 -->
<div>
<span>共 {{total}} 件商品,合计:</span>
<span class="price">¥{{totalPrice}}</span>
</div>
<!-- 右侧结算按钮 -->
<button class="btn btn-success btn-settle">结算</button>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'CartFooter',
computed: {
...mapGetters('cart', ['total', 'totalPrice'])
}
}
</script>
VUE3
Vue2 选项式 API vs Vue3 组合式API
<script>
export default {
data(){
return {
count:0
}
},
methods:{
addCount(){
this.count++
}
}
}
</script>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const addCount = ()=> count.value++
</script>
特点:
1.代码量变少
2.分散式维护变成集中式维护
VUE3优势:
使用create-vue搭建Vue3项目
1)create-vue是Vue官方新的脚手架工具,底层切换到了 vite
2)执行如下命令安装create-vue
npm init vue@latest
3)熟悉项目和关键文件
组合式API
setup选项
1)写法
<script>
export default {
setup(){
},
beforeCreate(){
}
}
</script>
2)执行机制(在beforeCreate钩子之前执行)
3)复杂写法:在setup函数中写的数据和方法需要在末尾以对象的方式return,才能可模版使用
// setup执行时机,比beforeCreat还要早
// 2.setup函数中,获取不到this(this是undefind)
// 3.数据和函数需要在setup最好return,才能模板中应用
// 问题:每次都要return,太麻烦
<script>
export default {
setup(){
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
// 必须return才可以
return {
message,
logMessage
}
}
}
</script>
4)语法糖写法:script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句
<script setup>
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
</script>
组合式API-reactive和ref函数
1)reactive:接受对象类型数据的参数传入并返回一个响应式的对象
<script setup>
// 导入
import { reactive } from 'vue'
// 执行函数 传入参数 变量接收
const state = reactive({
msg:'this is msg'
})
const setSate = ()=>{
// 修改数据更新视图
state.msg = 'this is new msg'
}
</script>
<template>
{{ state.msg }}
<button @click="setState">change msg</button>
</template>
2)ref:接收简单类型或者对象类型的数据传入并返回一个响应式的对象
//在原有传入数据的基础之上包了一层对象,包成了复杂类型
//注意点:脚本中,访问我们的数据,需要通过.value
//template中,.value不需要加
<script setup>
// 1.导入
import { ref } from 'vue'
//2. 执行函数 传入参数 变量接收
const count = ref(0)
const setCount = ()=>{
// 修改数据更新视图必须加上.value
count.value++
}
</script>
<template>
<button @click="setCount">{{count}}</button>
</template>
3)区别:
- reactive不能处理简单类型的数据
- ref参数类型支持更好,但是必须通过.value做访问修改
- ref函数内部的实现依赖于reactive函数
组合式API-computed
基本与VUE2保持一致,组合式API下只修改了API写法
<script setup>
//1.导入
import { ref, computed } from 'vue'
// 声明数据
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])
//2.执行函数 变量接收 在回调参数中return计算值
const computedList = computed(() => {
return list.value.filter(item => item > 2)
})
const addFn = () => {
list.value.push(666)
}
</script>
<template>
<div>
<div>原始数据: {{ list }}</div>
<div>计算后的: {{ computedList }}</div>
</div>
<button @click="addFn">+1</button>
</template>
组合式API-watch
侦听一个或多个数据的变化,数据变化时执行回调函数
1)侦听单个数据
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
})
</script>
2)侦听多个数据:第一个参数可以改写成数组的写法
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('cp')
// 2. 调用watch 侦听变化
watch([count, name], ([newCount, newName],[oldCount,oldName])=>{
console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName])
})
</script>
3)immediate(立即执行)
<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
},{
immediate: true
})
</script>
- deep(深度侦听)
// 4.deep 深度监视,默认watch为浅层监视
// const 数据为简单类型,则直接监视
// 复杂类型,监视不到
<script>
const userInfo = ref({
name:'zs',
age:21
})
const setUserInfo = () => {
userInfo.value.age++
}
watch(userInfo,(newValue) => {
console.log(newValue)
}, {
deep: true
})
/ 5.对于对象中的单个属性,进行监视
watch(() => userInfo.value.age, (newValue, oldValue) => {
console.log(newValue, oldValue)
} )
</script>
<template>
<div>{{ userInfo }}</div>
<button @click="setUserInfo">修改userInfo</button>
</template>
组合式API - 生命周期函数
1)基本使用
//1.导入生命周期函数
import {onMounted } from 'vue'
//2.执行生命周期函数 传入回调
onMounted(() => {
//自定义逻辑
})
2)执行多次:按照顺序执行
// 如果有些代码需要在mounted生命周期中执行
onMounted(() => {
console.log('mounted生命周期函数')
})
// 写成函数的调用方式,可以调用多次,并 不会冲突,先调用的先执行
onMounted(() => {
console.log('mounted生命周期函数111')
})
组合式API - 父子通信
1)父传子
- 父组件中给子组件绑定属性
- 子组件内部通过props选项接收数据
2)子传父
- 父组件中给子组件标签通过@绑定事件
- 子组件内部通过 emit 方法触发事件
组合式API - 模版引用
通过 ref标识 获取真实的 dom对象或者组件实例对象
1)基本使用
- 调用ref函数生成一个ref对象
- 通过ref标识绑定ref对象到标签
<script setup>
import {ref} from 'vue'
//1.调用ref函数生成一个ref对象
const h1Ref = ref(null)
</script>
<template>
<!-- 2.通过ref标识绑定ref对象 -->
<h1 ref=“h1Ref”>我是dom标签h1<h1>
</template>
2)defineExpose
语法糖下组件内部的属性和方法不开放给父组件
可通过defineExpose编译宏指定哪些属性和方法允许访问
组合式API - provide和inject
顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
1)跨层传递普通数据
顶层组件通过 provide
函数提供数据
底层组件通过 inject
函数提供数据
2)跨层传递响应式数据
在调用provide函数时,第二个参数设置为ref对象
3)跨层传递方法:顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据
APP.VUE
<script setup>
import CenterCom from '@/components/center-com.vue'
import { provide, ref } from 'vue'
// 1.跨层传递普通数据
provide('theme-color', 'pink')
// 2.跨出传递响应式数据
const count = ref(100)
provide('count',count)
setTimeout(() => {
count.value = 500
}, 2000)
// 3.跨层级传递数据 =》 给子孙后代传递可以修改的数据
provide('changeCount', (newCount) => {
count.value = newCount
})
</script>
<template>
<div>
<h1>我是顶层组件</h1>
<CenterCom></CenterCom>
</div>
</template>
bottom.vue
<script setup>
import { inject } from 'vue'
const themeColor = inject('theme-color')
const count = inject('count')
const changeCount = inject('changeCount')
const clickFn = () => {
changeCount(1699)
}
</script>
<template>
<div>
<h3>我是底层组件 - {{ themeColor }} - {{ count }}</h3>
<button @click="clickFn">更新count</button>
</div>
</template>
vue3新特性
defineOptions
问题:
1.在script setup 之前,要定义 props, emits→添加一个与setup平级的属性即可
2.在script setup 之后, setup 属性已没有→无法添加与其平级的的属性
3.引入了 defineProps 与 defineEmits 两个宏→只解决了 props 与 emits 这两个属性
4.若要定义组件的name或其它自定义属性,还是得回到原始用法——再添加一个script标签→非常奇怪,有两个script标签
解决办法:
Vue 3.3 中新引入了 defineOptions 宏→用来定义 Options API 的任意选项
<script setup>
defineOptions({
name:'Foo',
inheritAttrs: false,
//...更多自定义属性
})
</script>
defineModel
<script setup>
const modelValue = defineModel()
modelValue.value++
</script>
若要生效,还需配置vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true
}
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Pina
简介
Pinia 是 Vue 的最新状态管理工具 ,是 Vuex 的 替代品
pinia基础使用
1.定义store
2.组件使用store
getters实现
直接使用 computed函数进行模拟→需要使用时把getters return出去
//数据(state)
const count = ref(0)
//getters
const doubleCount = computed(() => count.value*2)
action异步实现
异步action函数的写法和组件中获取异步数据的写法完全一致
//异步action
const getList = async () => {
const res = await axios.request<接口数据类型>({})
}
storeToRefs工具函数
使用storeToRefs函数可以辅助保持数据(state + getter)的响应式解构
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>
Pinia的调试
Vue官方的 dev-tools 调试工具 对 Pinia直接支持,可以直接进行调试
Pinia持久化插件
官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/
- 安装插件 pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate
2.使用 main.js
import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))
3.配置 store/counter.js
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
...
return {
count,
doubleCount,
increment
}
}, {
persist: true
})