vue 是一个 构建用户界面的渐进式的 javascript 框架
渐进式:逐渐增强,vue不强求你一次性在网站中运用学习所有的语法,可以学一点用一点
库:本质上是一些方法的集合。每次调用方法,实现一个特定的功能 如 axios
框架:是一套完整的解决方案。框架实现了大部分的功能,我们需要按照框架的规则写代码 如 vue
Vue是一个MVVM的框架 (MVVM:一种软件架构模式)
是一种简化用户界面事件驱动的编程方式
M:model数据模型(ajax获取到的数据)
V:view视图(页面)
VM:ViewModel 视图模型 (操作视图+模型) 是mvvm的核心,将数据和实图进行双向绑定
MVVM通过 数据双向绑定 让数据自动地双向同步
V(修改视图) -> M(数据自动同步)
M(修改数据) -> V(视图自动同步)
一方面修改,另一方面跟着修改,不用手动的操作dom元素
之前的思想,原生 dom驱动。无论修改什么页面内容,先找对象,操作dom。
现在的思想,vue 数据驱动。想更新视图,直接操作数据即可。数据变化,视图自动更新
vue组件化思想
组件化:也就是vue页面 一个组件会包含(HTML+CSS+JS) ,完整的页面可以拆分成多个组件
组件化的优点:
容易维护
便于复用(HTML+CSS+JS)
@vue/cli 脚手架的使用
传统开发模式:基于html/css/js文件开发 vue
工程化开发方式:在webpack环境中开发vue。(推荐,学习+项目)
vue脚手架
@vue/cli 也叫 vue脚手架, @vue/cli 是vue官方提供的一个全局命令工具
这个命令可以帮助我们快速的创建一个vue项目的基础架子
vue脚手架的好处
开箱即用
零配置(不用你配webpack)
内置babel等工具
vue脚手架的基本使用
全局安装:(一次安装之后直接创建文件)
npm i @vue/cli -g 或 yarn global add @vue/cli查看vue版本:
vue --version初始化一个vue项目:
vue create 项目名(不能用中文)-
启动项目, 打包项目
npm run serve npm run build
我们在项目无法找到webpack.config.js文件,因为vue把它隐藏了
如果需要覆盖webpack的配置,可以修改vue.config.js文件,覆盖webpack配置
脚手架里的主要文件的作用
node_modules – 下载的第三方的包
public/index.html – 运行的浏览器网页
src/main.js – webpack打包的入口
src/App.vue – vue页面的入口
package.json – 依赖包列表文件
public/index.html不用动,提供一个最基础的页面
src/main.js不用动, 渲染了App.vue组件
src/App.vue默认有很多的内容,可以全部删除
assets 文件夹 和 components 中的可以直接删除
.vue文件
单文件组件:一个 .vue 文件就是一个组件
安装插件 (语法高亮)vetur
一个单文件组件由三部分组成
template: 结构 (有且只能一个根元素)
script: js逻辑
style: 样式-
让组件支持 less
(1) style标签, lang=“less”,开启less功能(2) 装包: yarn add less-loader@7.2.1 less -D
vue插值表达式
vue提供数据
通过 data 属性可以提供数据, data属性必须是一个函数
这个函数需要返回一个对象,这个对象就代表vue提供的数据
export default {
data(){
return {
name : "张三"
}
}
}
使用插值表达式,可以在模板中渲染数据
<template>
<div>
{{ name }}
</div>
</template>
插值表达式: 小胡子语法 {{ }}
作用: 使用 data 中的数据渲染视图(模板)
支持基本语法, 支持三元运算符 变量 对象.属性 方法
注意点:
(1)使用数据在 data 中必须存在
(2)能使用表达式,但是不能使用语句 if for ...
(3)不能在标签属性中使用 {{ }} 插值
vue指令
vue指令: 特殊的 html 标签属性, 特点: v- 开头
每个 v- 开头的指令, 都有着自己独立的功能, 将来vue解析时, 会根据不同的指令提供不同的功能
vue指令-v-bind
说明:插值表达式不能用在html的标签属性上,想要动态的设置html标签属性,需要使用v-bind指令
作用: 动态的设置html的标签属性
语法: v-bind:属性名="值"
简写: :属性名="值"
<template>
<!-- template中只有一个根标签 -->
<div>
<!-- vue 的指令 v-bind: 开头 -->
<input type="text" v-bind:value = "value">
<br>
<input type="text" :value ="value">
<br>
<a v-bind:href = 'url'>点击跳转1</a>
<br>
<a :href = 'url'>点击跳转2</a>
</div>
</template>
<script>
export default {
name : 'app',
// data必须是一个函数,并且必须要返回一个对象,数据就在返回的对象里
data(){
return{
value : '请输入姓名',
url : 'https://www.jianshu.com/'
}
}
}
</script>
vue指令-v-on
作用:注册事件
语法:
v-on:事件名=“要执行的少量代码"
v-on:事件名=“methods中的函数名"
-
v-on:事件名=“methods中的函数名(实参)"
注意:事件处理函数在methods中提供
简写:v-on 可以 简写 成 @
<template>
<!-- template中只有一个根标签 -->
<div>
<!-- 注册事件 vue指令 v-on:事件名 = ‘要执行的代码’ -->
<!-- 简写:v-on 可以 简写 成 @ -->
<h3>{{ money }}</h3>
<!-- 点击加10 -->
<button v-on:click = 'money+=10'>+10</button>
<!-- 简写 -->
<button @click = 'money+=10'>+10简写</button>
<br>
<!-- 点击加100,使用方法 -->
<button v-on:click = 'addmoney'>+100</button>
<!-- 加100简写 -->
<button @click = 'addmoney'>+100简写</button>
<br>
<!-- 多个不同的按钮调用方法 -->
<button @click = 'moneys(200)'>+200</button>
<button @click = 'moneys(500)'>+500</button>
<button @click = 'moneys(1000)'>+1000</button>
<button @click = 'moneys(10000)'>+10000</button>
</div>
</template>
<script>
export default {
name : 'app',
// data必须是一个函数,并且必须要返回一个对象,数据就在返回的对象里
data(){
return{
money : 10
}
},
// 事件处理函数在methods中提供
methods: {
// 在methods中引用data里的变量需要使用this
addmoney(){
this.money += 100
},
// 有参数的方法可以根据需求给多个标签调用
moneys(num){
this.money += num
},
}
}
</script>
vue中阻止默认事件
正常阻止默认事件
语法:事件对象.preventDefault()
vue中获取事件对象
(1) 没有传参, 通过形参接收 e
(2) 传参了, 通过$event指代事件对象 e
<template>
<div>
<!-- 阻止事件默认行为 -->
<!-- 没有参数时 -->
<a href="https://www.jianshu.com/" @click = 'fn'>没有参数</a>
<br>
<br>
<!-- 有参数时 -->
<a href="https://www.jianshu.com/" @click="fns(10,$event)">有参数</a>
</div>
</template>
<script>
methods: {
// 没有传参时,直接设置事件对象,然后阻止默认行为
fn(e){
e.preventDefault()
console.log(e)
},
// 有传参时,最后一个参数是事件对象,但是传参必须传 $event 固定语法 否则报错
fns(num,e){
e.preventDefault()
console.log(num)
}
}
}
</script>
修饰符
事件修饰符
事件修饰符:vue提供事件修饰符,可以快速阻止默认行为或阻止冒泡
.prevent 阻止默认行为, .stop 阻止冒泡
.once 程序运行期间,只能触发一次事件处理函数
<template>
<!-- template中只有一个根标签 -->
<div>
<!-- 阻止事件默认行为 事件名.prevent -->
<a href="https://www.baidu.com" @click.prevent = 'fn'>点击跳转页面</a>
<!-- 事件修饰符 事件名.stop 阻止事件冒泡-->
<div @click = 'father'>父亲
<div @click.stop = 'son'>孩子</div>
</div>
</div>
</template>
<script>
export default {
// data必须是一个函数,并且必须要返回一个对象,数据就在返回的对象里
data(){
return{
}
},
// 事件处理函数在methods中提供
methods: {
fn(){
console.log('阻止跳转')
},
father(){
console.log('父亲')
},
son (){
console.log('儿子')
}
}
}
</script>
按键修饰符
在监听键盘事件时,我们经常需要判断详细的按键。可用按键修饰符
@keyup.enter – 只要按下回车才能触发这个键盘事件函数
@keyup.esc 监听返回键
vue中内置有 .delete .enter .esc .tab 等
<template>
<!-- template中只有一个根标签 -->
<div>
<!-- 按键修饰符 -->
<input type="text" placeholder="用户名" @keyup.esc = 'down'>
<!-- 设置按键弹起时,只有在设置的输入框获取焦点时按才有效 -->
<input type="password" placeholder="密码" @keyup.enter = 'fn'>
<button>登录</button>
</div>
</template>
<script>
export default {
name : 'app',
// data必须是一个函数,并且必须要返回一个对象,数据就在返回的对象里
data(){
return{
}
},
// 事件处理函数在methods中提供
methods: {
down (){
console.log('esc 返回')
},
fn () {
console.log('enter弹起')
}
}
}
</script>
案例 :逆转世界
当点击时字符串翻转
使用: 插值表达式
<template>
<!-- template中只有一个根标签 -->
<div>
<!-- 逆转世界 -->
<!-- 先静态后动态 -->
<h1>{{ msg }}</h1>
<button @click="reverseWorld">逆转世界</button>
</div>
</template>
<script>
export default {
data(){
return {
msg : 'Hello,World!'
}
},
methods : {
reverseWorld (){
// 1.先将字符串分割 split
const arr = this.msg.split('')
// 2.再将分割形成的数组倒转 reverse
arr.reverse()
// 3.最后将倒转的数组重新拼接成字符串然后赋值给msg
this.msg = arr.join('')
}
}
}
</script>
盒子的显示隐藏
v-show 和 v-if 可以控制盒子的显示隐藏
-
v-show
语法: v-show="布尔值" (true显示, false隐藏)原理: 实质是在控制元素的 css 样式,
display: none;
-
v-if
语法: v-if="布尔值" (true显示, false隐藏)原理: 实质是在动态的创建 或者 删除元素节点
应用场景:
1. 如果是频繁的切换显示隐藏, 用 v-show (v-show, 只是控制css样式,而v-if, 频繁切换会大量的创建和删除元素, 消耗性能)
-
如果是不用频繁切换, 要么显示, 要么隐藏的情况, 适合于用 v-if
(v-if 是惰性的, 如果初始值为 false, 那么这些元素就直接不创建了, 节省一些初始渲染开销)
-
vue指令 - v-else 和 v-else-if
<template>
<!-- template中只有一个根标签 -->
<div>
<!-- 显示隐藏 v-show v-if 值都为布尔值 true显示 false 隐藏-->
<!-- v-show 的值为false时,就是往其标签的样式上加display = none -->
<div v-show="age >= 18">成年人的世界</div>
<div v-show="age < 18">未成年人的世界</div>
<!-- v-if 的值为true时 创建一个新的标签,为false时就删除 -->
<div v-if="age >= 18">我想要</div>
<div v-if="age < 18">孤勇者</div>
<!-- v-if v-else-if v-else 用法和js中的if判断一样 -->
<div v-if="num < 60">不及格</div>
<div v-else-if="num < 80">及格</div>
<div v-else-if="num < 90">优秀</div>
<div v-else-if="num < 100">顶尖</div>
<div v-else>金色传说</div>
</div>
</template>
<script>
export default {
data(){
return {
age : 16,
num : 12
}
},
methods : {
}
}
</script>
v-model
v-model作用:给表单元素使用, 双向数据绑定
数据变化了, 视图会跟着变
-
视图变化了, 数据要跟着变
语法: v-model='值'
vue指令 - v-model - 处理其他表单元素
例如:
select 绑定的是选中的选项值
checkbox 绑定的是是否选中 的一个布尔值
textarea 绑定的是value属性 用户输入的值
注意:v-model 会 忽略掉表单元素原本的value, checked等初始值
v-model修饰符
语法: v-model.修饰符="Vue数据变量"
.number 转数字,以parseFloat转成数字类型
.trim 去除首尾空白字符
.lazy 在change时触发而非inupt时
<template>
<!-- template中只有一个根标签 -->
<div>
<!-- v-model 修饰符 -->
<!-- .number转数字,以浮点型转成数字类型然后赋值给变量 -->
<input type="text" v-model.number="age" placeholder="数字类型">
<br>
<!-- .trim 将用户输入的数据前后空格去除然后赋值给变量 -->
<input type="text" v-model.trim="add" placeholder="去除前后空格">
<br>
<!-- .lazy 在输入框失去焦点时才触发,将用户输入的数据赋值给变量 -->
<input type="text" v-model.lazy="sall" placeholder="失去焦点时触发">
</div>
</template>
<script>
export default {
data(){
return {
age : "",
add : "",
sall : "",
}
}
}
</script>
vue指令-v-text和v-html
作用:更新元素的innerText/innerHTML
语法:
v-text="值"
v-html="值"
区别:
v-text 不解析标签
v-html 解析标签
使用v-text和v-html的标签元素中不应写内容,会被覆盖
<template>
<!-- template中只有一个根标签 -->
<div>
<!-- v-text和 v-html -->
<!-- v-text 相当于 innerText textContent -->
<!-- v-html相当于 innerHTML -->
<!-- v-text 只能解析变量中的文本,会将变量的数据以字符串的方式显示在标签元素中 -->
<!-- 使用v-text和v-html之后 标签元素中的插值会被覆盖 -->
<h1 v-text="msg">{{666}}</h1>
<!-- v-html 可以解析标签 ,会将解析的html标签显示在标签元素中 -->
<h1 v-html="msg"></h1>
</div>
</template>
<script>
export default {
data(){
return {
msg : "<button>按钮</button>"
}
}
}
</script>
综合案例--文章标题编辑
<template>
<div>
<!-- 标题部分 -->
<!-- 2.当点击编辑时,标题和编辑隐藏,编辑内容显示 给编辑绑定点击事件 -->
<!-- 3.当点击编辑时,标题隐藏 -->
<h1 class="title" v-show="!show">
{{ title }}
<span class="tag">{{ option }}</span>
<!--5.点击编辑的时候,要初始化表单显示 -->
<a class="edit-btn" @click="fn">编辑</a>
</h1>
<!-- 编辑内容 -->
<!-- 1.在最开始时,下面编辑框会隐藏 -->
<div v-show="show">
<div class="input-group">标题:<input type="text" v-model="edtitle" /></div>
<div class="input-group">
频道:<select v-model="edoption">
<option value="前端">前端</option>
<option value="测试">测试</option>
<option value="Java">Java</option>
</select>
</div>
<div class="operation">
<!-- 4.点击取消或者确认回到原来的状态 -->
<button class="cancel" @click="show = false">取消</button>
<button class="confirm" @click="ret">确认</button>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
show : false,
title : '从入门到崩溃',
option : "前端",
edtitle : '',
edoption : '',
}
},
methods: {
// 点击编辑的事件
fn(){
this.show = true
// 将title的值赋值给编辑输入框
this.edtitle = this.title
this.edoption = this.option
},
// 点击确认要执行的
ret(){
this.show = false
// 点击确认之后将将修改后的edtitle赋值给title
this.title = this.edtitle
this.option = this.edoption
}
}
};
</script>
<style>
.title {
display: flex;
align-items: center;
}
.tag {
margin-left: 8px;
padding: 3px 6px;
border-radius: 8px;
background-color: green;
font-size: 0.6em;
color: #fff;
}
.edit-btn {
margin-left: 32px;
font-size: 0.8em;
color: lightskyblue;
}
.input-group {
margin-top: 16px;
font-size: 28px;
}
.input-group input,
.input-group select {
width: 300px;
border-color: #ddd;
font-size: 28px;
}
.operation {
margin-top: 16px;
}
.cancel {
margin-left: 85px;
background-color: #ddd;
}
.cancel,
.confirm {
padding: 8px 24px;
border: 0;
border-radius: 4px;
}
.confirm {
margin-left: 16px;
background-color: #006699;
color: #fff;
}
</style>
v-for
作用:可以遍历 数组 或者 对象 或者数字,用于渲染结构 可以遍历循环结构
遍历数组语法:
v-for="item in 数组名"
v-for="(item, index) in 数组名"
遍历对象语法:
v-for = "(value, key) in 对象名"
遍历数字
v-for = "item in 数字"
v-for优先级高于v-if
如果同时出现v-for和v-if,无论判断条件是否成立,都会执行一遍v-for循环,这样浪费性能,所以要尽可能的避免两者一起使用。
v-for就地更新
vue会尽量复用上一次渲染的dom
当需要重新渲染时,vue会先对比缓存在内存里的的dom结构,然后只更新变化的结构
新旧DOM产生后对比, 然后决定是否复用真实DOM/更新内容
Vue会尽可能的就地(同层级,同位置), 对比虚拟dom,复用旧dom结构,进行差异化更新
可以复用旧的dom结构,更新高效
虚拟dom
html 渲染出来的 真实dom树,是个树形结构(复杂)。每个标签,都只是树的某个节点
虚拟dom:本质就是 保存节点信息, 描述真实dom的 JS 对象
一个缓存在内存的js对象,可以用最少的属性结构,描述真实的dom
可以更高效的对比变化的地方,只更新改变的地方,减少dom操作
v-for中的key属性
在没有提供key的时候或是使用索引作为key的值时就使用就地更新
在使用key时,就根据key的值进行对比,提示对比和更新性能
key的值的要求是:唯一不重复的字符串或数字
key值 : 有id用id 没有用索引
可以配合虚拟DOM提高更新的性能
动态修改样式
style
用 v-bind 动态设置标签的 style 行内样式
语法 :style="对象/数组"
对象
:style = '{属性名:"属性值"}' // 属性值带引号
数组
:style = '[]' // 数组里可以是多个对象,多个值
样式的属性名带横线
1.使用小驼峰命名
2.给属性名加上引号
class
用 v-bind 动态设置标签的 class 类名
语法 :class="对象/数组"
对象:如果键值对的值为true,那么就有这个类,否则没有这个类
对象中可以使用表达式判断
类名有横线 : 加引号
数组:数组中所有的类,都会添加到盒子上
数组中的所有元素都要加上引号
v-bind 对于类名操作的增强, 注意点 :class 不会影响到原来的 class 属性
计算属性
计算属性默认情况下只能获取,不能修改。
要给计算属性赋值,就必须写计算属性的完整写法!
作用: 根据一些数据算出来的属性 (用的时候不加括号)
声明在computed对象中 computed和data methods 同级
简写 : 声明为一个函数
computed : {
age(){
}
}
完整写法:
computed : {
age: {
get() {
// 获取计算属性的值
const date = new Date();
return date.getFullYear() - this.year;
},
set(newAge) {
// 更新计算属性的值
// 本质上就是更新变量的值 改变之后触发set属性
const date = new Date();
this.year = date.getFullYear() - newAge;
},
},
}
set里的参数是age的状态或数据,当age更新时触发set,然后执行set里面的代码,将新的数据更新给变量,然后又触发get
侦听器
watch: 可以侦听到 data/computed 属性值的改变
简写
watch:{
被侦听的属性名(newVal,oldVal){
newVal 是新值
oldVal 是旧值
}
完整写法
可以用于监听数据变化,然后同步到本地
// 侦听数据的变化
// 侦听器完整写法 相当于input时间
// 可以侦听复杂数据类型 也可以立即执行
watch: {
list: {
// immediate: true, 立即执行一次
deep: true, // 深度侦听 常用于数组或对象
handler(newVal) { // 数据变化触发的函数
console.log(newVal.name);
},
},
},
};
组件
组件概念
组件是可复用的 Vue 实例, 封装标签, 样式和JS代码
组件化 :封装的思想,把页面上 可重用的部分
封装为 组件
,从而方便项目的 开发 和 维护
一个页面, 可以拆分成一个个组件,一个组件就是一个整体
每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)
前端组件化开发的好处:
1.提高了 复用性和灵活性
2.提升了 开发效率 和 后期可维护性
组件的注册使用
App.vue 是根组件, 这个比较特殊, 是最大的一个根组件。其实里面还可以注册使用其他小组件
使用组件的四步:
1.创建组件, 封装要复用的标签, 样式, JS代码
2.引入组件
3.注册组件
全局注册 – main.js中 – 语法如图
局部注册 – 某.vue文件内 – 语法如图
4.使用组件:组件名当成标签使用即可
<组件名></组件名>
注意点:组件名不能和内置的html名同名
创建组件
创建一个vue文件
封装要复用的结构 ,样式 , 和js代码
引入组件
import 组件对象 from vue文件路径
import {组件对象} from vue文件路径
当导入部分或导入非原组件默认导出的组件时,需要加大括号
导入默认导出的组件或者将整个模块作为单一的对象进行导入,不需要加大括号,该模块的所有导出都会作为对象的属性存在
注册组件
全局注册
Vue component('组件名',组件对象)
局部注册
export default {
components : {
'组件名' : 组件对象
}
}
使用组件
<组件名></组件名>
组件名的命名方式
在进行组件的注册时,定义组件名的方式有两种
- 注册使用短横线命名法,例如 hm-header 和 hm-main
使用时 <hm-button> </hm-button>
Vue.component('hm-button',HmButton)
- 注册使用大驼峰命名法,例如 HmHeader 和 HmMain
使用时 <HmButton> </HmButton>
和 <hm-button> </hm-button>
都可以
Vue.component('HmButton',HmButton)
推荐1: 定义组件名时, 用大驼峰命名法, 更加方便
推荐2: 使用组件时,遵循html5规范, 小写横杠隔开(可选)
组件在 开发者工具中 显示的名字,可以通过name进行修改
在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称
<script>
export default {
name : 'HmButton'
}
</script>
name写在要组册的组件中
注册可以全局注册或者局部注册
Vue.component(HmButton.name,HmButton)
组件之间的样式冲突 scoped
默认情况下,写在组件中的样式会 全局生效
,因此很容易造成多个组件之间的样式冲突问题。
全局样式
: 默认组件中的样式会作用到全局局部样式
: 可以给组件加上 scoped 属性, 可以让样式只作用于当前组件
scoped原理
(1)当前组件内标签都被添加 data-v-hash值 的属性
(2)css选择器都被添加 [data-v-hash值] 的属性选择器
最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到
scoped会在元素上添加唯一的属性(data-v-x形式),css编译后也会加上属性选择器,从而达到限制作用域的目的
组件通信--父传子
父组件给子组件传值
被引入的组件是子组件
父组件 : App.vue
子组件:被引入的组件
父传子语法:
父组件通过给子组件加属性传值
<Son price="100" title="不错" :info="msg"></Son>-
子组件中, 通过props属性接收
props: ['price', 'title', 'info']
父组件
<template>
<div>
<h1>父组件</h1>
<a-package :strs="str"></a-package>
</div>
</template>
<script>
import A from "./components/a-package.vue";
export default {
components: {
"a-package": A,
},
data() {
return {
str: "这是父组件传给子组件的值",
};
},
};
</script>
<style>
</style>
子组件
<template>
<div>
<h1>{{ strs }}</h1>
</div>
</template>
<script>
export default {
props: ["strs"],
};
</script>
<style>
</style>
注意点:
1.父传子之前需要先将子组件引入父组件中使用
2.给子组件传值的属性等于值需要写在子组件在父组件的标签中
3.子组件需要使用props接收父组件传过来的值,传过来的值要用引号
4.props中接收的是写在父组件中子组件标签中的属性 ,所对应的值可以直接在子组件中使用,但不能改变
v-for遍历组件
父组件
<template>
<div>
<h1>v-for遍历组件</h1>
<a-package
v-for="item in list"
:key="item.id"
:username="item.name"
:ages="item.age"
></a-package>
</div>
</template>
<script>
import A from "./components/a-package.vue";
export default {
components: {
"a-package": A,
},
data() {
return {
list: [
{ id: 1, name: "唐僧", age: 20 },
{ id: 1, name: "悟空", age: 600 },
{ id: 1, name: "八戒", age: 1800 },
{ id: 1, name: "沙僧", age: 1500 },
],
};
},
};
</script>
<style>
</style>
子组件
<template>
<div>
<h1>{{ username }}</h1>
<h2>{{ ages }}</h2>
</div>
</template>
<script>
export default {
props: ["username", "ages"],
};
</script>
<style>
</style>
单向数据流
在vue中需要遵循单向数据流原则: (从父到子的单向数据流动, 叫单向数据流)
父组件的数据变化了,会自动向下流动影响到子组件
子组件不能直接修改父组件传递过来的 props, props是只读的
组件通讯--子传父
子传父的基本语法:
- 子组件可以通过
this.$emit('自定义事件名', 参数1, 参数2, ...)
触发事件的同时传参的 - 父组件可以给子组件注册对应的自定义事件
- 父组件并提供对应的函数接收参数
子组件触发事件之后的方法给父组件传值
this.$emit('ok',参数)
父组件在子组件标签上监听该事件,当子组件触发该事件时,调用方法
<子组件 @ok = 'fn'></子组件>
<script>
export default {
methods: {
fn(e){
console.log(e)
}
}
}
</script>
当子组件传参只有一个时,父组件监听的事件触发的方法默认第一个就是子组件的传参
当父组件也有传参时,$event则是子组件的默认传参
<子组件 @ok = 'fn(item,$event)'></子组件>
<script>
export default {
methods: {
fn(item,e){
console.log(item)
console.log(e)
}
}
}
</script>
props校验
props 是父传子, 传递给子组件的数据, 为了提高 子组件被使用时 的稳定性, 可以进行props校验, 验证传递的数据是否符合要求
默认的数组形式, 不会进行校验, 如果希望校验, 需要提供对象形式的 props
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
v-model语法糖
语法糖:v-model本质上是 value属性和input事件的一层包装
v-model的作用:提供数据的双向绑定
1.数据发生了改变,页面会自动变 v-bind:value
2.页面输入改变 , 数据会自动变化 v-on:input
v-model流程
1.先是在父组件中声明数据
2.然后在父组件中的子组件的标签使用v-model 等于 数据
3.子组件在props中接收父组件传过来的参数必须为value
4.在子组件中使用value 然后在触发事件时将要改变的数据作为参数传给父组件 传参定义的事件默认为input
5.父组件因为使用v-model ,默认监听input事件,所有传过来的数据默认修改 ,然后传给子组件
以上是v-model实现组件数据的双向绑定的步骤
父组件
<template>
<div>
<a-package v-model="num"></a-package>
</div>
</template>
<script>
import A from "./components/a-package.vue";
export default {
data() {
return {
num: 10,
};
},
components: {
"a-package": A,
},
};
</script>
<style>
</style>
子组件
<template>
<div>
<button @click="$emit('input', value - 1)">--</button>
{{ value }}
<button @click="$emit('input', value + 1)">++</button>
</div>
</template>
<script>
export default {
props: ["value"],
};
</script>
<style>
</style>
注意:
当子组件传数据给父组件时
在script中使用 this.$emit('自定义事件名',参数)
在template中不需要使用this
ref和$refs
作用:利用 ref 和 $refs 可以用于 获取 dom 元素, 或者组件实例
1.获取dom标签
给要获取的标签添加ref属性
然后通过this.$refs.属性值 ,获取目标标签
<h1 ref='my'><h1>
console.log(this.$refs.my)
2.获取组件实例
给Demo组件目标组件, 添加ref属性-名字随意
恰当时机, 通过 this.$refs.xxx 获取组件对象, 可调用组件对象里方法等
<组件名 ref= 'my'></组件名>
this.$refs.my.方法或属性
$nextTick
$nextTick:等DOM更新后, 才会触发执行此方法里的函数体
methods:{
this.show = true
this.$nextTick(() =>{
console.log(this.$refs.my)
})
}
小案例
点击按钮,显示输入框获取焦点
<template>
<div>
<input type="text" v-show="show" ref="inp" />
<button v-show="!show" @click="fn">点击显示</button>
</div>
</template>
<script>
export default {
data() {
return {
show: false,
};
},
methods: {
fn() {
this.show = !this.show;
console.log(this.$refs.inp);
this.$nextTick(() => {
this.$refs.inp.focus();
});
},
},
};
</script>
<style>
</style>
动态组件
动态组件是可以改变的组件,可以解决多组件同一位置,切换显示的问题
基本语法
component 组件(位置) + is 属性 (哪个组件)
修改 is 属性绑定的值 => 切换组件
准备被切换的 3个组件, 并引入注册
准备变量来承载要显示的"组件名"
设置挂载点<component>, is属性设置要显示的组件 ( component + is )
点击按钮 – 修改name变量里的"组件名" ( 修改 is 的值)
父组件
<template>
<div>
<button @click="name = 'a-package'">老虎</button>
<button @click="name = 'b-package'">🦌</button>
<button @click="name = 'c-package'">猫</button>
<!-- <a-package></a-package>
<b-package></b-package>
<c-package></c-package> -->
<component :is="name"></component>
</div>
</template>
<script>
import A from "./components/a-package.vue";
import B from "./components/b-package.vue";
import C from "./components/c-package.vue";
export default {
components: {
"a-package": A,
"b-package": B,
"c-package": C,
},
data() {
return {
name: "a-package",
};
},
};
</script>
<style>
</style>
子组件中分别是三种对应按钮的图片
当点击对应的按钮时显示对应的图片
注意:当使用动态组件时,不要再使用子组件标签
只需要切换:is的值就可以切换组件
使用v-if 的显示隐藏也可以达到类似的效果
自定义指令
自定义指令:自己定义指令, 封装dom操作,扩展额外功能
局部注册
color是指令名
// 局部注册自定义指令
directives: {
color: {
// 当指令所在的标签插入到dom树中,下面方法会被执行
inserted(dom) {
dom.style.color = "red";
},
},
},
全局注册
bold是指令名
// 全局注册自定义指令
Vue.directive("bold", {
inserted(dom) {
dom.style.fontSize = "50px";
},
});
自定义指令传参
语法:在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值
通过 binding.value 可以拿到指令值,指令值修改会触发 update 函数
自定义指令主要是对dom的操作来做一些额外操作
App.vue
<template>
<div>
<p v-color="red" v-bold="'100px'">自定义指令</p>
<p v-color v-bold>自定义指令</p>
<p v-color v-bold>自定义指令</p>
<button @click="fn">点击</button>
</div>
</template>
<script>
export default {
data() {
return {
red: "red",
blue: "blue",
pink: "pink",
};
},
// 局部注册自定义指令
directives: {
color: {
// 当指令所在的标签插入到dom树中,下面方法会被执行
inserted(dom) {
dom.style.color = "red"; // value = red
},
// update 在指令的值改变时触发,将修改后的值传参然后重新赋值修改
// binding.value是传给自定义指令的值
update(dom, binding) {
dom.style.color = binding.value;
},
},
},
methods: {
fn() {
this.red = "green";
},
},
};
</script>
<style>
</style>
main.js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// 全局注册自定义指令
Vue.directive("bold", {
inserted(dom) {
dom.style.fontSize = "50px";
},
update(dom, binding) {
dom.style.fontSize = binding.value;
},
});
new Vue({
render: (h) => h(App),
}).$mount("#app");
keep-alive--缓存组件
keep-alive是缓存组件,不会重复创建销毁
<button @click="names = 'a-name'">a-a</button>
<button @click="names = 'b-name'">b-b</button>
<keep-alive>
<component :is="names"></component>
</keep-alive>
两个生命周期
export default {
//在缓存激活的时候触发
activated() {
console.log("b激活了");
},
//在缓存失活的时候触发
deactivated() {
console.log("b失活了");
},
};
插槽
默认插槽
插槽基本语法:
- 组件内用<slot></slot>占位
- 使用组件时<组件名></组件名>夹着的地方, 传入标签替换slot
父组件
<template>
<div>
<slot-a>
<img
src="https://ts3.cn.mm.bing.net/th?id=OIP-C.S3QOsgnlzrpbpAdOXF4YMAHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=1.25&pid=3.1&rm=2"
alt=""
/>
</slot-a>
</div>
</template>
<script>
import slotA from "./components/slot-a.vue";
export default {
components: {
"slot-a": slotA,
},
};
</script>
<style>
</style>
子组件
<template>
<div>
<h1>插槽</h1>
<slot></slot>
<p>这是通过插槽传递结构</p>
</div>
</template>
<script>
export default {};
</script>
<style>
</style>
插槽 - 后备内容(默认值)
插槽后备内容:封装组件时,可以为预留的 <slot>
插槽提供后备内容(默认内容)
语法: 在<slot>标签内放置内容, 作为默认显示内容
效果:
外部使用组件时,不传东西,则slot会显示后备内容 (slot标签内的结构)
外部使用组件时,传东西了,则slot整体会被换掉
<slot>
<p>这是默认内容</p>
</slot>
插槽 - 具名插槽
一个组件内有多处,需要外部传入标签,进行定制
语法:
多个slot使用name属性区分名字 <slot name="content"></slot>
template配合v-slot:名字来分发对应标签,根据对应的名字传给不同的插槽对应的结构或内容
v-slot:可以简化成#
父组件
<template>
<div>
<slot-a>
<template #header>
<p>这是插槽的标题</p>
</template>
<template #body>
<p>这是插槽的内容</p>
</template>
<template #footer>
<p>这是插槽的底部</p>
</template>
</slot-a>
</div>
</template>
<script>
import slotA from "./components/slot-a.vue";
export default {
components: {
"slot-a": slotA,
},
};
</script>
<style>
</style>
子组件
<template>
<div>
<p>
<slot name="header"><button>标题</button></slot>
</p>
<p>
<slot name="body"><button>内容</button></slot>
</p>
<p>
<slot name="footer"><button>底部</button></slot>
</p>
</div>
</template>
<script>
export default {};
</script>
<style>
</style>
插槽 - 作用域插槽
作用域插槽: 定义 slot 插槽的同时, 是可以传值的。给插槽上可以绑定数据,将来使用组件时可以用
基本步骤:
1.给slot标签以添加属性的方式传值
2.所有添加的属性都会被添加到一个对象中,然后传给父组件的对应的地方
3.在template中 ,接收子组件传过来的对象
匿名的就是 v-slot =‘自定义的对象名’
具名的就是 v-slot= 插槽名 = ‘自定义的对象名’
匿名插槽传参
父组件
<template>
<div>
<!-- 接收子组件传过来的对象 -->
<slot-a v-slot="obj">
<!-- 子组件插槽传过来的值 obj:{user:{name:'张三',age:18}} -->
<h1>{{ obj.user.name }}</h1>
<h1>{{ obj.user.age }}</h1>
</slot-a>
</div>
</template>
<script>
import slotA from "./components/slot-a.vue";
export default {
components: {
"slot-a": slotA,
},
};
</script>
<style>
</style>
子组件
<template>
<div>
<slot :user="user"></slot>
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: "张三",
age: 18,
},
};
},
};
</script>
<style>
</style>
具名插槽传参
父组件
<template>
<div>
<slot-a>
<template #head="obj">
<p>{{ obj.title }}</p>
</template>
<template #body="obj">
<p>{{ obj.content }}</p>
</template>
</slot-a>
</div>
</template>
<script>
import slotA from "./components/slot-a.vue";
export default {
components: {
"slot-a": slotA,
},
};
</script>
<style>
</style>
子组件
<template>
<div>
<slot name="head" :title="user.title">标题</slot>
<slot name="body" :content="user.content">内容</slot>
</div>
</template>
<script>
export default {
data() {
return {
user: {
title: "这是一个标题",
content: "这是内容",
},
};
},
};
</script>
<style>
</style>
生命周期
vue组件生命周期:从创建 到 销毁 的整个过程就是 – Vue实例的 - 生命周期
生命周期-钩子函数概述
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行
作用: 特定的时间点,执行特定的操作
比如: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据
分类: 三大阶段,8个方法
- 组件
初始化阶段
的生命周期函数 - 组件
运行阶段
的生命周期函数 - 组件
销毁阶段
的生命周期函数
八大生命周期钩子函数:
1.beforeCreate:data数据初始化之前,组件还没有数据
2.created: data数据初始化之后,可以获取到组件的数据
3.beforeMount:DOM渲染之前,DOM还没渲染
4.mounted:DOM渲染之后,可以操作DOM了
5.beforeUpdate: 数据更新,DOM更新前
6.updated: 数据更新,DOM更新后
7.beforeDestroy: 组件销毁前
8.destroyed: 组件销毁后
路由
单页面应用程序
SPA - Single Page Application
单页面应用(SPA): 所有功能在一个html页面上实现 例如网易云音乐
(多页面应用程序MPA)
优点:
不整个刷新页面,每次请求仅获取需要的部分,用户体验更好
数据传递容易, 开发效率高
缺点:
开发成本高(需要学习专门知识 - 路由)
首次加载会比较慢一点。不利于seo
路由
路由就是路径地址和组件的映射关系
vue的路由是 vue-router ,是vue官方提供的一个插件,专门负责出来路由
其中v3 对应的是vue2.0
v4 对应的是vue3.0
vue的组件分类说明
组件分类: .vue文件分2类, 一个是页面组件, 一个是复用组件
src/views文件夹
页面组件 - 页面展示 - 配合路由用
src/components文件夹
复用组件 - 展示数据 - 常用于复用
配置路由的步骤
1.下包
npm i vue-router@3 // @后面是对应的版本
2.引入路由
import VueRouter from "vue-router";
3.创建组件
4.引入组件
5.导入 注册路由
Vue.use(VueRouter); //使用Vue注册之前要先引入Vue
6.定义路由
const routes = [
{
path: "/head-name", // 地址
component: headName, // 组件
},
]
7.实例化路由
routes这个是固定的 属性名 改变后路由地址改变无法跳转页面
const router = new VueRouter({
routes, //缩写
});
8.导出路由
export default router;
9.在main.js中引入路由,然后挂载到vue实例上面去
// 引入路由
import router from "./router/index";
new Vue({
// 将路由挂载到vue实例上
router, //简写
render: (h) => h(App),
}).$mount("#app");
10.在App.vue上使用 <router-view></router-view> 来渲染路由
声明式导航
组件router-link 替代 a标签,能跳转,能高亮
vue-router提供了一个全局组件 router-link
router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
router-link提供了声明式导航高亮的功能(自带类名)
<template>
<div>
<router-link to="/head-name">1</router-link><span>|</span>
<router-link to="/body-name">2</router-link><span>|</span>
<router-link to="/foot-name">3</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
.router-link-active {
color: red;
}
</style>
声明式导航 - 两个类名
router-link 会自动给当前导航添加两个类名
router-link-active: 激活的导航链接 模糊匹配
可以匹配开始一致的路径
router-link-exact-active: 激活的导航链接 精确匹配
只能匹配完全一致的路径
可以修改默认高亮的类名
const router = new VueRouter({
routes,
linkActiveClass = 'aa'
linkExactActiveClass = 'bb'
});
声明式导航 - 跳转传参
跳转路由时, 可以给路由对应的组件内传值
在router-link上的to属性传值, 语法格式如下
/path?参数名=值 当参数对页面不是必须的,通常放在问号后面
/path/值 – 需要路由对象提前配置 path: “/path/:参数名” 参数对应页面来说是必须的,就将参数嵌套在url里面
对应页面组件接收传递过来的值
在对应的组件中创建后可以接收到
this.$route 代表当前的页面信息
查询参数
this.$route.query.参数名 问号后面
this.$route.params.参数名 嵌套url
1.问号后面非必要传参
<!-- 对象属性,path是要跳转的路径,query是问号后面的参数 -->
<router-link :to="{ path: '/bar', query: { name: 'jack', age: 18 } }">bar</router-link>
在对应页面可以查到对应的参数信息
created() {
console.log(this.$route.query.name);
},
2.内嵌url 对页面必要性传参
当需要传递内嵌到路径里的参数的时候
/路径/:参数名 来声明内嵌式参数 在定义路径和组件的映射关系中
路由文件
{
path: "/foot-name/:id",
component: footName,
},
App.vue
<router-link :to="`/foot-name/${user.id}`">3</router-link>
对应的组件可以查到传入的参数
export default {
created() {
console.log(this.$route.params.id);
},
};
想要用对象的方式传参, path不能和params一起传参 ,如果非要用对象的方式传参,需要给对应的路由取一个名字,用name和params一起传参
{
name: "aa",
path: "/body-name/:id",
component: bodyName,
},
<router-link :to="{ name: 'aa', params: { id: 66 } }">2</router-link>
对应组件一样可以查看传的参数
export default {
created() {
console.log(this.$route.params.id);
},
};
重定向
重定向:匹配path后, 强制跳转path路径
网页打开url默认hash值是/路径
redirect是设置要重定向到哪个路由路径
// 重定向
{
path: "/", // 默认根组件
// 当为根地址是重定向为新的地址
redirect: "/head-name",
},
vue路由--404
404:当找不到路径匹配时,给个提示页面
路由最后, path匹配*(任意路径) – 前面不匹配就命中最后这个
// 模糊匹配要放在任意一个
{
// *代表任意路径
path: "*",
//组件使用路由先要引用
component: noneName,
},
路由模式设置 - 修改路由,在地址栏的模式
hash路由例如: http://localhost:8080/#/home
history路由例如: http://localhost:8080/home (以后上线需要服务器端支持)
const router = new VueRouter({
routes, //缩写
// 路由模式
mode: "history", // 这就是设置路由模式
});
编程式导航
编程式导航:用JS代码来进行跳转
this.$router 有push方法,可以推入页面,压下上一个页面,也可以传参
1.可以直接传路径
2.可以传对象,里面有路径和参数 path和query,或者name和params (path和params不能在一起)
<button @click="$router.push('/head-name')">编程式导航</button>
编程式导航 - 路由传参
query传:$route.query.xxx 接收
params传:$route.params.xxx 接收
区别:
params传参:是在内存中传参,刷新会丢失
query传参:是在地址栏传参,刷新还在
<button @click="$router.push({ path: '/head-name', query: { age: 55 } })">
<button @click="$router.push({ name: 'aa', params: { id: 2983193 } })">
过滤器--vue3.0已经废弃
和methods方法一样,filters
只能用在插值表达式和动态绑定属性中,用 | 表明
<template>
<div>
<h1>{{ str | split | res | join }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
str: "123456",
};
},
filters: {
split(str) {
return str.split("");
},
res(str) {
return str.reverse();
},
join(str) {
return str.join("");
},
},
};
</script>
<style>
</style>