Vue是一套用于构建用户界面的渐进式框架。
渐进式就跟这个图片一样,开发可以根据需求,逐渐递增所要的方式,但每个方式有不是依靠行特别强
Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
库是一个模块,而Vue是一套架构,会基于自身特点向用户提供一套相当完整的解决方案,而且控制权在框架本身;对项目的侵入性较大,使用者要按照框架所规定的某种特定规范进行开发,项目如果需要更换框架,则需要重新架构整个项目。
响应式的数据绑定:当数据发生改变,视图可以自动更新,可以不用关心dom操作,而专心数据操作
可组合的视图组件:把视图按照功能切分成若干基本单元,组件可以一级一级组合整个应用形成倒置组件树,可维护,可重用,可测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./js/vue.global.js"></script>
<style>
.box {
width: 200px;
height: 200px;
background-color: darksalmon;
display: none;
}
div.show {
display: block;
}
</style>
</head>
<body>
<div id="app">
{{message}}
<h2 @click="isShow()">{{title}}</h2>
<div class="box" :class="{show}" style="color: #333; background-color: antiquewhite" :style="{width:'200px', height:h}">
<ul>
<li v-for="item in articles.slice(0,5)" :title="item.content">
<p>{{item.title}}</p>
<span>{{item.content}}</span>
</li>
</ul>
</div>
</div>
</body>
<script>
//Vue2
var vm = new Vue({
el: '#app',
data: {
num: 0,
},
methods: {
……
},
});
//Vue3
const app = Vue.createApp({
data() {
return {
h: '500px',
message: 'this is a test',
title: 'Vue demo',
show: true,
articles: [
{title:'Google',content:'Vue 的核心库只关注视图层'},
{title:'souhu',content:'Vue 的核心库只关注视图层'},
{title:'Google',content:'Vue 的核心库只关注视图层'},
{title:'Google',content:'Vue 的核心库只关注视图层'},
{title:'Google',content:'Vue 的核心库只关注视图层'},
{title:'Google',content:'Vue 第三方库或既有项目整合'},
{title:'xifang',content:'第三方库或既有项目整合'},
{title:'baidu',content:'第三方库或既有项目整合'},
{title:'baidu',content:'第三方库或既有项目整合'},
{title:'baidu',content:'第三方库或既有项目整合'}
]
}
},
methods: {
isShow() {
this.show = !this.show;
}
}
}).mount("#app")
</script>
</html>
生命周期
:事物从诞生到消亡的整个过程
vue生命周期
:Vue 实例从创建到销毁的过程,vue生命周期钩子
:在达到某一阶段时去触发的函数
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的,生命周期函数中的this
指向是vm
或 组件实例对象。
它可以总共分为8个阶段:
创建前/后, 挂载前/后,更新前/后,销毁前/销毁后
create
阶段:vue实例被创建
mount
阶段: vue实例被挂载到真实DOM节点
update
阶段:当vue实例里面的data数据变化时,触发组件重新渲染
destroy
阶段:vue实例被销毁
beforeCreate(创建前)
表示实例完全被创建出来之前,vue 实例的挂载元素$el和数据对象 data 都为 undefined,还未初始化。
created(创建后)
数据对象 data 已存在,可以调用 methods 中的方法,操作 data 中的数据,但 DOM 未生成,$el
未存在 。
beforeMount(挂载前)
vue 实例的 $el
和 data
都已初始化,挂载之前为虚拟的 DOM节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中。data.message
未替换。
mounted(挂载后)
vue 实例挂载完成,data.message
成功渲染。内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了。实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,DOM 渲染在 mounted 中就已经完成了。
beforeUpdate(更新前)
当 data 变化时,会触发beforeUpdate方法 。data 数据尚未和最新的数据保持同步。
updated(更新后)
当 data 变化时,会触发 updated 方法。页面和 data 数据已经保持同步了。
beforeDestory(销毁前)
组件销毁之前调用 ,在这一步,实例仍然完全可用。
destoryed(销毁后)
组件销毁之后调用,对 data 的改变不会再触发周期函数,vue 实例已解除事件监听和 dom绑定,但 dom 结构依然存在。
常用的生命周期方法:
mounted()
:初始化操作,发送ajax请求, 启动定时器、绑定自定义事件、订阅消息等异步任务
beforeDestroy()
: 做收尾工作, 清除定时器、解绑自定义事件、取消订阅消息等
关于销毁Vue实例:
销毁后借助Vue开发者工具看不到任何信息
销毁后自定义事件会失效,但原生DOM事件依然有效
一般不会在beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了。
Vue生命周期流程图
vue生命周期在真实场景下的业务应用:
Vue-CLI 是Vue官方提供的脚手架工具
Vue脚手架指的是vue-cli,它是一个专门为单页面应用快速搭建繁杂的脚手架,它可以轻松的创建新的应用程序而且可用于自动生成vue和webpack的项目模板。
利用vue-cli脚手架来构建Vue项目需要先安装Node.js和NPM环境。
.
|-- build // 项目构建(webpack)相关代码
| |-- build.js // 生产环境构建代码
| |-- check-version.js // 检查node、npm等版本
| |-- dev-client.js // 热重载相关
| |-- dev-server.js // 构建本地服务器
| |-- utils.js // 构建工具相关
| |-- webpack.base.conf.js // webpack基础配置
| |-- webpack.dev.conf.js // webpack开发环境配置
| |-- webpack.prod.conf.js // webpack生产环境配置
|-- config // 项目开发环境配置
| |-- dev.env.js // 开发环境变量
| |-- index.js // 项目一些配置变量
| |-- prod.env.js // 生产环境变量
| |-- test.env.js // 测试环境变量
|-- node_modules //所需要依赖资源
|-- src // 源码目录
| |-- assets //存放资产文件,如静态资源
| |-- components // vue公共组件
| |-- router //存放路由js文件,用于页面的跳转
| |-- App.vue // 页面入口文件
| |-- main.js // 程序入口文件,加载各种公共组件
|-- static // 静态文件,比如一些图片,json数据等
| |-- data // 群聊分析得到的数据用于数据可视化
|-- .babel.config.js // bale的配值文件
|-- .editorconfig // 定义代码格式
|-- .gitignore // git上传需要忽略的文件格式
|-- README.md // 项目说明
|-- favicon.ico
|-- index.html // 入口页面
|-- package.json // 项目基本信息
|-- package-lock.json //包版本控制文件
|-- vue.config.js // vue可选的配值文件
.
文件名 | 说明 |
---|---|
dist | 存放使用npm run build 打包的项目文件 |
node_modules | 存放项目的依赖包 |
public | 存放静态文件。里面包含了几个文件: index.html是一个模板文件 |
src | 这里是我们要开发的目录,基本上要做的事情都在这个目录里。里面包含了几个目录及文件:assets: 放置一些资源文件,比如图片、字体等资源。components: 目录里面放了一个组件文件,可以不用。App.vue: 项目入口文件,我们也可以直接将组件写这里,而不使用 components 目录。main.js: 项目的核心文件(入口文件)。 |
package.json | 模块基本信息项目开发所需要模块,版本,项目名称 |
package-lock.json | 是在 npm install时候生成一份文件,用以记录当前状态下实际安装的各个npm package的具体来源和版本号 |
babel.config.js | 是一个工具链,主要用于在当前和较旧的浏览器或环境中将ECMAScript 2015+代码转换为JavaScript的向后兼容版本 |
gitignore | git上传需要忽略的文件格式 |
vue.config.js | 保存vue配置的文件,可以用于设置代理,打包配置等 |
README.md | 项目说明 |
输入npm run dev命令来启动项目
运行成功后在浏览器输入:http://localhost:8080
//原生JS
<div id="msg">div>
<script type="text/javascript">
var msg = 'Hello World'
var div = document.querySelector('#msg');
div.innerHTML = msg
script>
<div id="msg">div>
<script type="text/javascript" src="js/jquery.js">script>
<script type="text/javascript">
var msg = 'Hello World';
$('#msg').html(msg);
script>
//script 引入
<script src="js/vue.js">script>
//CDN 引入
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js">script>
//生产版本
<script src="https://cdn.jsdelivr.net/npm/[email protected]">script>
Vue的基本使用步骤:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>title>
head>
<body>
<div id="app">
<div>{{num}}div>
<div><button v-on:click="handle">点击button>div>
div>
<script type="text/javascript" src="js/vue.js">script>
<script type="text/javascript">
//创建vue实例 vm (ViewModel)
var vm = new Vue({
el: '#app',
data: {
num: 0,
},
methods: {
// ES6 的对象字面量方法简写允许我们省略对象方法之后的冒号及function关键字
// handle:function(){
// this.num++;
// }
handle() {
this.num++;
},
},
});
script>
body>
html>
el
:指定当前Vue实例为哪个标签服务(值可以是CSS选择器或者DOM元素),el有2种写法:
(1) new Vue时候配置el属性。
const vm = new Vue({
el:'#root', //第一种写法
data:{
msg:' '
}
})
vm.$mount(’#root’)
指定el的值。const vm = new Vue({
data:{
msg:' '
}
})
vm.$mount('#root') //第二种写法 */
data
用于存储数据(值是一个对象或函数),数据供el
所指定的标签使用,data有2种写法:
(1) 对象式
data:{
msg:' '
}
(2) 函数式
data(){
return{
msg:' '
}
}
如何选择:目前data的哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
methods
: 该属性用于在Vue对象中定义方法。
Vue模板语法包括两大类:
功能:用于解析标签体内容
双大括号表达式{{xxx}}
xxx 是js 表达式,可以直接读取到 data 中的所有区域,也可以调用对象的方法和计算属性
{{ number + 1 }}
{{ ok ? ‘YES’ : ‘NO’ }}
{{message.split(‘’).reverse().join(‘’)}}
功能:用于解析标签(包括:标签熟悉、标签体内容、绑定事件…)
指令(以v-xxx
开头的自定义标签属性)【很多】
(在{{}}和v-指令进行数据绑定时,支持js单个表达式)
{{msg}}v-once
数据执行一次性插值,数据改变时,插值处内容不更新,不响应
v-once
的应用场景:以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
{{msg}}v-pre
,内容原封不动的展示,跳过其所在节点的编译过程。
v-text
,向其所在的节点中渲染文本内容。
v-text
与插值语法的区别:v-text
会替换掉节点中的内容,{{xx}}
则不会。
v-html
,可以输出html代码
v-html
与插值语法的区别:
(1). v-html
会替换掉节点中所有的内容,{{xxx}}
则不会。
(2). v-html
可以识别html结构。
严重注意:v-html
有安全性问题!!!!
(1). 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2). 一定要在可信的内容上使用v-html
,永不要用在用户提交的内容上!
data:{
msg:'test message',
title:`Title
`
}
<div id="app">
<div>{{msg}}div>
<div v-text='msg'>div>
<div v-html='msg1'>div>
<div v-pre>{{msg}}div>
div>
var vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue',
msg1: 'HTML
'
}
});
v-cloak
应用场景:解决插值表达式存在的问题——“闪烁”
渲染普通文本有2种方式:{{}}
与v-text
<div id="app">{{msg}}div>
<div id="app" v-text="msg">div>
new Vue({
el: '#app',
data: {
msg: '欢迎Vue!'
}
})
“闪动”的意思:
在使用{{}}
展示或更新页面数据时:当网速比较慢时,会出现一个不好的过度现象,会让用户先看到我们的表达式(上面页面中的{{msg}}
),然后才看到data中的值(欢迎Vue!)------->即所谓的闪烁问题!
如何解决该问题:
使用v-cloak
指令,不让未经解析的html模板显示在页面
使用v-cloak
指令,然后为其设置css样式display:none;
即上述代码可修改为:
ref : 为某个元素注册一个唯一标识, vue对象通过$refs属性访问这个元素对
v-cloak
指令的用法:
[v-cloak]{
display: none;
}
<div id="app" v-cloak>{{msg}}div>
原理: 先通过样式隐藏内容,然后在内存中进行值的替换,替换好之后再显示最终的结果。
v-cloak
指令本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak
属性
说明: 但有时添加完毕后变量仍会显示(即闪烁问题没解决),这是怎么回事呢?原来是 v-cloak
的display属性被优先级别高的样式覆盖所导致,所以最好再添加一个 !important ,将其优先级设置为最高,防止被其他优先级高的dispaly:none样式所覆盖。
[v-cloak]{
display: none !important;
}
自定义指令:内置指令不满足需求,需要自己定义指令使用
需求1:定义一个v-big
指令,和v-text
功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind
指令,和v-bind
功能类似,但可以让其所绑定的input元素默认获取焦点。
一、定义语法
//写法一:
new Vue({
directives:{指令名:回调函数}
})
//写法二:
new Vue({
directives{指令名:配置对象}
})
实例1
<div id="root">
<h2>当前的n值是: <span v-text = 'n'>span>h2>
<h2>放大10倍后的n值是:<span v-big='n'>span>h2>
<button @click="n++">n+1button>
<input type="text" v-fbind:value = 'n'>
div>
const vm = new Vue({
el: '#root',
data: {
n: 12
},
directives: {
//写法一:回调函数写法
//el,指令所绑定的元素(真实DOM). binding,一个对象,包含以下属性:name:指令名,不包括 v- 前缀。value:指令的绑定值 arg:传给指令的参数,
big(el, binding) {
console.log(el, binding,this) //注意,此处的this是window
el.innerText = binding.value * 10;
},
fbind(el, binding){
onsole.log(el, binding)
el.value = binding.value
el.focus() //不能获取焦点
},
//写法二:配置对象写法 在不使用inserted钩子函数时可以使用方法一简写
big: {
//指令与元素成功绑定时(一上来)
bind(el, binding) {
el.innerText = binding.value * 10;
},
//指令所在元素被插入页面时
inserted(el, binding) {},
//指令所在的模板被重新解析时
update(el, binding) {
el.innerText = binding.value * 10;
},
},
fbind: {
bind(el, binding) {
el.value = binding.value
console.log('bind')
},
inserted(el, binding){
el.focus() //有效
console.log('inserted')
},
update(el, binding) {
el.value = binding.value
console.log('update')
}
},
},
},
})
big函数何时会被调用?
1.指令与元素成功绑定时
2.指令所在的模板被重新解析时
//方法一
Vue.directive(指令名,配置对象)
//方法二
Vue.directive(指令名,回调函数)
Vue.directive('big',function(el, binding){
console.log(el, binding,this) //注意,此处的this是window
el.innerText = binding.value * 10;
})
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(el, binding) {
el.value = binding.value
console.log('bind')
},
//指令所在元素被插入页面时
inserted(el, binding){
el.focus()
console.log('inserted')
},
//指令所在的模板被重新解析时
update(el, binding) {
el.value = binding.value
console.log('update')
}
})
b. 所有的钩子函数的参数都有以下
v-my-directive="1 + 1”
中,绑定值为 2。{ foo:true, bar: true }
<div v-demo="{ color: "white',text: "hello!' }">div>
<script>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // white
console.log(binding.value.text) // "hello!"
script>
v-debounce
自定义指令来实现二、配值对象中常用的3个回调
三、备注:
指令定义时不加v-,但使用时要加v-;
指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。写法如下:
<span v-big-number='n'>span>
directives:{'big-number'(el, binding){……}}
插值{{}}
只能用在模板内容中,用于动态内容绑定
如果希望元素的属性也可以动态绑定,需要通过v-bind
指令
<a v-bind:href="url"> ... a>
<a :href="url"> ... a>
<a :[key]="url"> ... a>
<template>
<h2 title="this is a test">{{msg}}h2>
<h2 v-bind:title="msg">{{msg}}h2>
<h2 :title="info">{{info}}h2>
<img :src="imgsrc" width="100" height="100" alt="">
<a :href="url">百度a>
template>
<script>
const data = {
msg: 'this is a test',
info: 'new info',
imgsrc: 'https://v3.vuejs.org/logo.png',
title: 'Title
',
url: 'http://www.baidu.com'
}
export default {
name: 'App',
data() {
return data
}
}
script>
class 样式有四种用法(字符串,数组,对象,方法)
<template>
<div :class="[one,two]">box4div>
<div :class="active">box5div>
<div :class="{one:isOne, two:isTwo}">box6div>
<div :class="{demo:demo}">box6div>
<div :class="{demo}">box6div>
<div :class="getStyleArr()">box7div>
<div :class="getStyleObject()">box8div>
template>
<script>
const data = {
one: 'one',
two: 'two',
active: ['one','two'],
isOne: true,
isTwo: false,
demo: true
}
export default {
name: 'App',
data() {
return data
},
methods() {
getStyleArr() {
return [this.one,this.two];
},
getStyleObject() {
return {
one: this.isOne,
two: this.isTwo
}
}
script>
<style scoped>
.one {
background-color: rebeccapurple;
font-weight: bold;
}
.two {
background-color: #42b983;
}
.demo {
background-color: #333;
}
style>
有种方法,一种数组语法、一种是对象语法
<template>
<div :style="['font-size:100px','background:red']">box1div>
<div :style="[fontSize, bgColor]">box2div>
<div :style="['font-size:'+size+'px','background:'+color]">box3div>
<div :style="{fontSize: '15px', 'background-color':'yellow'}">box9div>
<div :style="{'font-size': '15px', 'background-color':'yellow'}">box9div>
template>
<script>
const data = {
fontSize: 'font-size:50px',
bgColor: 'background-color:green',
size: 90,
color: 'yellow'
}
export default {
name: 'App',
data() {
return data
}
script>
**使用:**在computed属性对象中定义计算属性的方法,在页面中使用{{方法名}}
来显示计算的结果。
原理:底层借助了Objcet.defineproperty()方法提供的getter
和setter
。
get有什么作用?
当读取computed中的方法时,get就会被调用,且返回值就作为该方法的值
get函数什么时候执行?
(1) 初次读取时会执行一次。
(2) 当依赖的数据发生改变时会被再次调用。
优势: computed计算属性有缓存的功能,计算属性在处理一些复杂逻辑时是很有用的。
<template>
<div>
<h3>{{name}} - {{slogen}}h3>
<h3>{{name +' - ' + slogen}}h3>
<h3>{{getTitle()}}h3>
<h3>{{title}}h3>
div>
template>
<script>
const data = {
name: '张三',
slogen: '新的世界'
}
export default {
name: 'App',
data() {
return data
},
computed: {
title: {
get() {
console.log('get computed')
return this.name+ ' - '+this.slogen;
}
}
},
methods: {
getTitle() {
console.log('get methods')
return this.name+ ' - '+this.slogen;
}
}
}
script>
如果有多个数据
<h3>{{getTitle()}}h3>
<h3>{{getTitle()}}h3>
<h3>{{getTitle()}}h3>
<h3>{{title}}h3>
<h3>{{title}}h3>
<h3>{{title}}h3>
运行查看控制台
methods里的getTitle()是使用多少次方法就调用多少次。
而computed有缓存的作用,只计算一次,computed里的title依赖于name和sologen,只要name和slogen没有变化,这个两个属性值就一直保存在缓存中,若更改,则相应title绑定也会更新。
计算属性默认只有getter,需要时也提供一个setter
计算属性完整写法
// ...
computed: {
fullName: {
// getter 可以省略,
//当fullName所依赖的firstNameh和lastName发生改变时会被再次调用
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
现在在运行 vm.fullName = 'John Doe'
时, setter
会被调用, vm.firstName
和 vm.lastName
也会被对应更新。
计算属性常用简写(只考虑读取,不考虑修改的情况下)
computed: {
fullName() {
return this.firstName + '-' + this.lastName;
}
}
例子
总价:<small>¥</small>{{totalPrice}}
.....
const data = {
books: [
{id:1, name:'JS大红本第一版',price:120},
{id:1, name:'JS大红本第二版',price:130},
{id:1, name:'JS大红本第三版',price:150},
{id:1, name:'JS大红本第四版',price:190}
]
}
export default {
name: 'App',
data() {
return data
},
//计算属性
computed: {
totalPrice: {
get() {
//汇合
return this.books.reduce((s,n)=> s+=n.price, 0)
}
}
}
}
Vue.js提供了一个方法 $watch
, 它用于观察 Vue 实例上的数据变动。当一些数据需要根据其它数据变化时。
监听属性watch: 监听具体数据变化,当数据属性变化时, 回调函数handler自动调用, 在函数内部进行计算
写法:
(1)在vue实例vm中传入watch配置来监视指定的属性
(2)通过vm对象的 $watch()
应用场景: 数据变化时执行异步或开销较大(比较耗时)的操作
注意: 监听的属性必须在vm中存在,才能进行监听
思考下面例子:
<div id="demo">{{ fullName }}div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
//完整写法
//xxx为vm实例中存在且被监听的属性
/*
xxx: {
immediate: true, //初始化时让handler调用一下
//handler(固定函数) 什么时候调用?当函数中的数据发生改变时
handler(newValue, oldValue) {....},
},
*/
//简写 (当只需要handler属性时)
firstName(val) {
this.fullName = val + ' ' + this.lastName
},
lastName(val) {
this.fullName = this.firstName + ' ' + val
}
}
})
//还有另外一种写法,但是需要保证vm实例已创建
// vm.$watch 正常写法
vm.$watch('fistName', {
immediate: true,
deep: true,
handler(newValue ,oldVelue) {
this.fullName = val + ' ' + this.lastName
}
})
//vm.$watch 简写
vm.$watch('fistName', function(newValue ,oldVelue){
this.fullName = val + ' ' + this.lastName
}
})
上面代码是命令式的和重复的。跟计算属性对比:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
})
计算属性的方法更好点。
深度监听:
(1) Vue中的watch默认不监测对象内部值的改变,只检测第一层。
(2) 配置deep:true
可以监测对象内部值改变,可以检测多层。
备注:
(1). Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2). 使用watch
时根据数据的具体结构,决定是否采用深度监视。
var vm = new Vue({
el: '#demo',
data: {
numbers:{
a:1,
b:2
}
},
watch: {
//监视多级结构中某个属性的变化
'numbers.a':{
handler() {……}
},
//监视多级结构中所有属性的变化
numbers: {
deep: true,
handler() {……}
}
}
})
computed
:计算属性,依赖其他属性,当其他属性改变的时候,下一次获取computed值时也会改变,computed
的值会有缓存
watch
:监听属性,监听具体数据变化,当数据属性变化时, 回调函数handler
自动调用, 在函数内部进行计算
methods
: 该属性用于在Vue对象中定义方法。
computed
与watch
区别:
computed
能完成的功能,watch
都可以完成。watch能完成的功能,computed
不一定能完成,例如:watch
可以进行异步操作。computed
计算属性computed
在大多数情况下更合适,但当需要在数据变化时执行异步或开销较大的操作时,使用watch
更适用。
computed
与methods
区别:
计算属性是基于它们的依赖进行缓存,如果多次使用时,计算属性只会调用一次,性能上计算属性明显比methods
好,如果依赖改变则重新缓存,而方法不缓存
在前端开发中,需要经常和用户交互
绑定事件监听器指令:v-on
缩写: @ (语法糖)
参数: $event
(获取事件对象)
注意:
@click="demo”
和@click="demo($event)"
效果一致,但后者可以传参
<a v-on:click="doSomething"> ... a>
<a @click="doSomething"> ... a>
<a @[event]="doSomething"> ... a>
<template>
<div>
msg = {{msg}}<br>
<input type="text" v-model="msg"><br><br>
num = {{num}}<br>
//<button @click="num--">-button>
<button @click="sub">-button>
<input type="text" size="3" v-model="num">
//<button @click="num++">+button>
<button @click="add">+button>
div>
template>
<script>
const data = {
msg: 'this is a test',
num: 0,
max: 10,
min: 0
}
export default {
name: 'App',
data() {
return data
},
methods: {
add() {
if (this.num >= this.max) {
this.num = this.max;
} else {
this.num++;
}
},
sub() {
if (this.num <= this.min) {
this.num = this.min;
}else {
this.num--;
}
}
}
}
script>
<style>
.....
style>
效果
.stop
阻止事件冒泡.self
当事件在该元素本身触发时才触发事件.capture
添加事件侦听器是,使用事件捕获模式.prevent
阻止默认事件.once
事件只触发一次.enter
只有在 key
是 Enter
时调用.passive
滚动事件的默认行为 (即滚动行为) 将会立即触发
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
.enter
只有在 key
是 Enter
时调用.delte
(捕获“删除”和“退格”键).esc
退出.space
空格.tab
换行 (特殊,必须配合keydown去使用).up
上 .down
下 .left
左 .right
右系统修饰键(用法特殊):ctrl、alt、shift、meta
(1). 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。比如ctrl+s 然后释放s
(2). 配合keydown使用:正常触发事件。
也可以使用keyCode去指定具体的按键(不推荐),但注意要转为kebab-case(短横线命名)
Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
//keyup 按键松开触发
<input @keyup.enter='submit'>
<input @keyup.delete='handle'>
<div id="app">
<form action="">
<div>
用户名: <input type="text" @keyup.delete='clearContent' v-model='uname'>
div>
<div>
密码:<input type="text" @keyup.enter='handleSubmit' v-model='pwd'>
div>
<div>
<input type="button" @click='handleSubmit' value="提交">
div>
form>
div>
//……
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
uname: '',
pwd: '',
age: 0
},
methods: {
clearContent(){
// 按delete键的时候,清空用户名
this.uname = '';
},
handleSubmit(){
console.log(this.uname,this.pwd)
}
}
});
script>
//……
除了系统的修饰符,还有自定义按键修饰符
规则:自定义按键修饰符名字是自定义的,但是对应的值必须是按键对应event.keyCode值
<div id="app">
<input type="text" @keyup.f1='handle' v-model='msg'>
div>
<script type='text/javascript'>
//a的ACSII值为65
Vue.config.keyCodes.f1 = 65
const app = new Vue({
el: "#app",
data: {
msg: ''
},
methods: {
handle: function (event) {
console.log(event.keyCode);
}
}
})
script>
<button @click="sub('sub',$event)">-button>
<input type="text" size="3" v-model="num">
<button @click="add">+button>
add(e) {
console.log(e); //没传参,获取得到事件对象
if (this.num >= this.max) {
this.num = this.max;
} else {
this.num++;
}
},
sub(p) {
console.log(p,e); //传参了,一个为’sub',一个为事件对象
if (this.num <= this.min) {
this.num = this.min;
}else {
this.num--;
}
}
<div @click="one()" class="box1">
<div @click="two()" class="box2">
<button @click="three()">按钮button>
div>
div>
<script>
.....
export default {
name: 'App',
data() {
return data
},
methods: {
one() {
console.log('one');
},
two() {
console.log('two');
},
three() {
console.log('three');
}
}
}
script>
<style scoped>
.box1 {
width: 150px;
height: 150px;
background-color: #42b983;
}
.box2 {
width: 100px;
height: 100px;
background-color: rebeccapurple;
}
style>
点击按钮,事件冒泡
若要停止事件冒泡,则
可串行使用
<div @click.self.stop="two()" class="box2">
<button @click="three()">按钮button>
div>
v-if
和 v-show
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-show
就简单得多——不管初始条件是什么,元素总是会被渲染并保留在 DOM 中,并且只是简单地基于 CSS 进行切换(display)
v-if和v-show的区别
v-if
指令是直接销毁和重建DOM达到让元素显示和隐藏的效果
v-show
指令是通过修改元素的display属性让其显示或者隐藏
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
条件分支 v-if v-else
多条件分支 v-if v-else-if v-else
注意:v-if
可以和v-else-if v-else
一起使用,但要求结构不能被打断
<h1 v-if="awesome">Vue is awesome!h1>
<h1 v-else>Oh no h1>
<button @click="isShow = !isShow">显示/隐藏button>
<h1 v-if="isShow">Vue is awesome!h1>
<h1 v-show="isShow">Oh no h1>
显示状态
隐藏状态
遍历指令:v-for
遍历数组 v-for=”(item, [index]) in 数组”
遍历对象 v-for=”(value, [key], [index]) in 对象”
vue中列表循环需加:key="唯一标识"
唯一标识可以是item里面id index等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM,使用diff算法的处理方法,对操作前后的dom树同一层的节点进行对比,一层一层对比
<div id="app">
<ul>
<p>遍历数组p>
<li v-for="(item,index) in list" :key="item">{{ index+1}} - {{ item }}li>
ul>
<ul>
<p>遍历对象p>
<li v-for="(item,key,index) in obj" :key="item">{{index+1}} - {{ key}} - {{ item }}li>
ul>
<ul>
<p>遍历数组对象p>
<li v-for="(item,index) in books" :key="item.id">{{ index+1 }} - {{ item.name }} - {{item.price}}li>
ul>
<ul>
<p>遍历字符串p>
<li v-for="(char,index) in str" :key="item.id">{{ char }} - {{ index }}li>
ul>
div>
Vue.createApp({
data() {
return {
str: 'hello',
list: ['Java','Python','C/C++','PHP','Vue'],
obj: {
name: '百度',
url: 'http://www.baidu.com',
slogen: 'get it'
},
books: [
{id:1, name:'Foo',price:20},
{id:2, name:'Bar',price:25},
{id:3, name:'Zun',price:50},
{id:4, name:'Dom',price:40},
]
}
}
}).mount('#app')
选中样式应用
<div id="app">
<ul>
<p>遍历数组对象p>
<li :class="{active:item.active}" @mouseenter="over(index)" v-for="(item,index) in books" :key="item.id">{{ index+1 }} - {{ item.name }} - {{item.price}}li>
ul>
div>
Vue.createApp({
data() {
return {
books: [
{id:1, name:'Foo',price:20, active: false},
{id:2, name:'Bar',price:25, active: false},
{id:3, name:'Zun',price:50, active: false},
{id:4, name:'Dom',price:40, active: false},
]
}
},
methods: {
over(index) {
for (let i in this.books) {
if (index == i)
this.books[index].active = true;
else
this.books[i].active = false;
}
}
}).mount('#app')
原始JS实现功能
:数据 => 真实DOM
Vue实现功能
:数据 => 虚拟DOM(内存中的数据)=> 真实DOM
虚拟DOM
可以理解为虚拟节点,是一个用来描述真实DOM结构的js对象。
Diff算法
是用于比较新旧虚拟节点之间差异的一种算法,每个虚拟节点都有一个唯一标识key,通过对比新旧节点的key来判断节点是否改变,将两个节点不同的地方存储在patch对象中,最后利用patch记录的消息局部更新DOM。
Key
主要用在虚拟Dom算法中,每个虚拟节点都有一个唯一标识Key,通过对比新旧节点的key来判断节点是否改变,可以大大提高渲染效率。
面试题: React vue 中的 key
有什么作用? ( key
的内部原理)
虚拟DOM 中 key
的作用: key
是 虚拟DOM 中对象的标识,当数据发生变化时, Vue 会根据新数据生成新的虚拟DOM,随后 Vue 进行新 虚拟DOM 与旧虚拟DOM 的差异比较,比较规则如下
对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若新节点中内容没变(和旧节点一样), 直接复用之前的旧节点生成真实DOM
②.若新节点中内容变了(和旧节点不一样), 则生成新的真实DOM,随后替换掉页面中之前旧虚拟DOM生成的真实DOM
(2). 旧虚拟DOM中未找到与新虚拟DOM相同的key,则创建新的真实DOM,随后渲染到到页面。
用index
作为key
可能会引发的问题:
(1).若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ,这时界面效果没问题, 但渲染效率低。
(2).如果结构中还包含输入类的DOM,会产生错误DOM更新,使界面有问题
开发中如何选择key
:
(1).最好使用每条数据的唯一标识作为key
, 比如id、手机号、身份证号、学号等唯一值。
(2).如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index
作为key
是没有问题的。
由下图可知,第一次有三项数据渲染为真实DOM,第二次渲染中的数据比第一次多了一项,此时Vue使用Diff算法按照规则在虚拟DOM中依次对比每一项数据,发现前三项数据key和内容都相同,则复用第一次的真实DOM,最后一项数据没有则需渲染新数据为真实DOM,提高了渲染效率。
列表过滤:可以使用watch也可以使用计算属性,使用计算属性更加简单方便一点
<div id="root">
<h2>人员列表h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="(p,index) of filPerons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
li>
ul>
div>
<script type="text/javascript">
Vue.config.productionTip = false
//用watch实现
/*
new Vue({
el:'#root',
data:{
keyWord:'',
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
filPerons:[]
},
watch:{
keyWord:{
//初始化自动调用handler,输入框为空,handler的val为空字符串
immediate:true,
//'abcd'.indexOf('') =0 一个字符串indexOf空字符串结果为0,filPerons就获取到了persons所有值
handler(val){
this.filPerons = this.persons.filter((p)=>{
return p.name.indexOf(val) !== -1
})
}
}
}
}) */
//#endregion
//用computed实现
new Vue({
el:'#root',
data:{
keyWord:'',
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
]
},
computed:{
filPerons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
}
}
})
</script>
<div id="root">
<h2>人员列表h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<button @click="sortType = 2">年龄升序button>
<button @click="sortType = 1">年龄降序button>
<button @click="sortType = 0">还原顺序
<ul>
<li v-for="(p,index) of filPerons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
li>
ul>
<script type="text/javascript">
Vue.config.productionTip = false
//用computed实现
new Vue({
el:'#root',
data:{
keyWord:'',
sortType: 0, //0代表原本, 1代表升序, 2代表降序
persons: [
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
},
computed: {
filPerons() {
const arr = this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
// 判断下是否需要排序
if (this.sortType) {
arr.sort((p1, p2) => {
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
})
}
return arr
}
}
})
</script>
更新时的一个问题
this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}
更改data数据,Vue不监听,模板不改变
<div id="root">
<h2>人员列表h2>
<button @click="updateMei">更新马冬梅的信息button>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
li>
ul>
div>
<script type="text/javascript">
Vue.config.productionTip = false
//用computed实现
new Vue({
el:'#root',
data:{
keyWord:'',
persons: [
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
},
methods: {
updateMei() {
// this.persons[0].name = '马老师' //奏效
// this.persons[0].age = 50 //奏效
// this.persons[0].sex = '男' //奏效
this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} // 不奏效
this.persons.splice(0,1, {id:'001',name:'马老师',age:50,sex:'男'}) //奏效
}
}
})
</script>
而this.persons.splice(0,1, {id:'001',name:'马老师',age:50,sex:'男'})
却有效果,原因解释如下:
let data= {
name: 'JJ boy',
address: 'BeiJing',
childs: {
ch1: {
name: 'Bebond',
age: 20
},
ch2: {
name: 'Mary',
age: 25
}
}
}
// 创建一个监视的实例对象,用于监视data中的属性变化
const obs = new Observer(data)
console.log(obs)
//准备一个vm实例对象
let vm = {}
vm._data = data = obs
function Observer(obj) {
//汇总对象中所有属性形成一个数组
const keys = Object.keys(obj)
//遍历
keys.forEach(k => {
Object.defineProperty(this, k, {
get(){
return obj[k]
},
set(val) {
console.log('$(k)被改了,去解析模板,生成虚拟DOM……')
obj[k] = val
}
})
});
}
控制台,data的数据修改了,vm._data数据属性同样修改
但是上诉代码的有个缺点,没有考虑到data
里有对象,对象里还有属性的情况,上述的例子没法为对象里的属性匹配getter
和setter
,如下图
而Vue中会为所有对象属性匹配getter
和setter
,即vue会监视data
中所有层次的数据
//这样声明,可查看控制台
const vm = new Vue({
el: '#root',
data: {
name: 'JJ boy',
address: 'BeiJing',
hobby: ['h1','h2','h3'],
childs: {
ch1: {
name: 'Bebond',
age: 20
},
ch2: {
name: 'Mary',
age: 25
}
}
},
})
data
中所有层次的数据。setter
实现监视,且要在new Vue时就传入要监测的数据。Vue.set(target, propertyName/index, value)
Vm.$set(target, propertyName/index, value)
target
:追加属性的目标key
: 追加的属性value
:追加属性的值Vue.set()
和vm.$set()
不能给Vue实例对象或是Vue实例的根数据对象(data)添加属性//监测对象数据
const vm = new Vue({
el: '#root',
data: {
name: 'JJ boy',
address: 'BeiJing',
hobby: ['h1','h2','h3'],
childs: {
ch1: {
name: 'Bebond',
age: 20
},
ch2: {
name: 'Mary',
age: 25
}
}},
methods: {
// 这种方式追加的属性是响应式的
addSex() {
Vue.set(this.childs.ch1, 'sex', 'man')
//或是
this.$set(this.childs.ch1, 'sex', 'man')
}
}
})
push()、pop()、unshift()、shift()、splice()、sort()、reverse()
Vue.set()
和vm.$set()
变更数组变更方法:
- push / pop: 末尾添加、删除,改变原数组, 返回添加之后新数组的长度或删除的这个值
- unshift / shift: 头部添加、删除,改变原数组,返回添加之后新数组的长度或删除的这个值
- sort/ reverse: 排序、反转,改变原数组
- splice(start开始的位置, number删除/更改的个数, 替换的值): 一般用于删除或更改数组中的元素,返回删除或更改元素组成的数组,改变原数组
还有一些非变更方法,不会改变原数组,总是返回一个新数组,如:filter()、concat()、slice()
非变更方法:
- filter(item => true(满足条件为true))返回的是满足条件的一个新数
- concat: 连接数组,不影响原数组, 浅拷贝
- slice(start开始的索引, end结束的索引): 返回截断后的新数组,不改变原数组
当使用非变更方法时,用一个含有相同元素的数组去替换原来的数组是非常高效的操作,例如:
//h !== '抽烟'的元素组成一个新数组
this.student.hobby = this.student.hobby.filter((h) => {
return h !== '抽烟';
});
通过数组索引修改数组,无法响应
通过变更数组的方法来监测数组数据,具有响应式
所以,前面的例子使用this.persons.splice(0,1, {id:'001',name:'马老师',age:50,sex:'男'})
有响应效果
除了变更方法,还可以使用前面提到的**Vue.set()
和vm.$set()
来修改数组
例子
<div id="root">
<h1>学生信息h1>
<button @click="student.age++">年龄+1岁button><br />
<button @click="addSex">添加性别属性,默认值为:男button><br />
<button @click="student.sex='女' ">修改性别为女button> <br />
<button @click="addFriend">在列表首位添加一个朋友button> <br />
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三button><br />
<button @click="addHobby">添加一个爱好button> <br />
<button @click="updateFirstHobby">修改第一个爱好为:开车button> <br />
<button @click="removeSmoke">过滤掉爱好中的抽烟button> <br />
<h3>姓名:{{student.name}}h3>
<h3>年龄:{{student.age}}h3>
<h3 v-if="student.sex">性别:{{student.sex}}h3>
<h3>爱好:h3>
<ul>
<li v-for="(p,index) in student.hobby" :key="index">
{{p}}
li>
ul>
<h3>朋友们:h3>
<ul>
<li v-for="(f,index) in student.friends " :key="index">
{{f.name}}--{{f.age}}
li>
ul>
div>
const vm = new Vue({
el: '#root',
data: {
student: {
name: 'JJ boy',
age: 18,
hobby: ['抽烟', '喝酒', '烫头'],
friends: [
{ name: 'jerry', age: 35 },
{ name: 'tony', age: 36 },
],
}
},
methods: {
addSex() {
this.$set(this.student, 'sex', '男')
},
addFriend() {
this.student.friends.unshift({name:'Mary', age: 10})
},
updateFirstFriendName() {
this.student.friends[0].name = '张三'
},
addHobby() {
this.student.hobby.push('打游戏')
},
updateFirstHobby(){
this.$set(this.student.hobby, 0, '开车')
},
removeSmoke(){
this.student.hobby = this.student.hobby.filter(h=>{
return h !== '抽烟'
})
}
}
})
v-model
指令
本质: 它负责监听用户的输入事件,从而更新数据,并对一些极端场景进行一些特殊处理。同时,v-model
会忽略所有表单元素的value、checked、selected特性的初始值,它总是将vue实例中的数据作为数据来源。 然后当输入事件发生时,实时更新vue实例中的数据。
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
value
属性 和 input
事件;checked
属性 和 change
事件;value
作为 prop 并将 change
作为事件。特点: 数据不仅能从 data 流向视图,还能从视图流向 data
实现原理:
<input v-bind:value="message" v-on:input="message = $event.target.value" />
v-model
组件上的 v-model
默认会利用名为 value 的 prop 和名为 input 的事件。 v-model
用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
1. v-bind
绑定一个value属性
2. v-on
指令给当前元素绑定input事件
v-model
的修饰符号:
.lazy
懒加载修饰符.number
修饰符让其转换为 number 类型.trim
修饰符可以自动过滤掉输入框的首尾空格使用v-model时要切记:
v-model
绑定的值不能是props
传过来的值,因为props
是不可以修改的!
props
传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
自定义组件:
自定义组件使用v-model
,有以下操作:
1. 接收一个value prop
2. 触发input事件,并传入新值
<input v-model="parentData">
<input v-bind:value="parentData" v-on:input="parentData = $event.target.value">
<input :value="parentData" @input= "parentData = $event.target.value">
@input
是对输入事件的一个监听,:value="parentData"
是将监听事件中的数据放入到input。
在自定义组件中
<my-component v-model="inputValue">my-component>
<my-component v-bind:value="inputValue" v-on:input="inputValue = argument[0]"> my-component>
这个时候,inputValue
接受的值就是input事件的回调函数的第一个参数,所以在自定义组件中,要实现数据绑定,还需要$emit
去触发input
的事件。
this.$emit('input', value)
v-model
不仅可以给input赋值还可以获取input中的数据,而且数据的获取是实时的,因为语法糖中是用@input
对输入框进行监听的。
<input type="text" v-model="msg"><br>{{msg}}
单向绑定
<input type="text" :value="msg"><br>
{{msg}}
<input type="text" :value="msg" @input="msg=$event.target.value"><br>
{{msg}}
<label for="one">
<input type="radio" id="one" value="男" v-model="sex">男
label>
<label for="two">
<input type="radio" id="two" value="女" v-model="sex">女
label>
<br> sex: {{sex}}
Vue.createApp({
data() {
return {
sex: '男'
}
}
}).mount("#app")
<input type="checkbox" v-model="checked" id="checkbox">
<label for="checkbox">{{checked}}label>
Vue.createApp({
data() {
return {
checked: false
}
}
}).mount("#app")
多个复选框
<div id="app">
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jacklabel>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">Johnlabel>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mikelabel>
<br />
<span>Checked names: {{ checkedNames }}span>
div>
Vue.createApp({
data() {
return {
checkedNames: []
}
}
}).mount("#app")
<div id="app">
<select v-model="selected">
<option disabled value="">Please select oneoption>
<option>Aoption>
<option>Boption>
<option>Coption>
select>
<span>Selected: {{ selected }}span>
div>
Vue.createApp({
data() {
return {
selected : ''
}
}
}).mount("#app")
选择框多选时
<select v-model="selected" multiple>
<option>Aoption>
<option>Boption>
<option>Coption>
select>
<br />
<span>Selected: {{ selected }}span>
用v-for
渲染的动态选项
<div id="app">
<select v-model="selected">
<option v-for="op in options" :value="op.value" :key="op.value">{{op.name}}option>
select>
<span>Selected: {{ selected }}span>
div>
Vue.createApp({
data() {
return {
selected: 'zs',
options: [
{name:'张三',value:'zs'},
{name:'王五',value:'ww'},
{name:'李四',value:'ls'},
{name:'梅芳',value:'mf'},
]
}
}
}).mount("#app")
**定义:**对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
//全局过滤器
Vue.filter(filterName, function(value[,arg1,arg2,...]){
// 进行一定的数据处理
return newValue
})
//局部过滤器
new Vue{
filters:{
filterName(value){
return newValue
}
}
}
//使用方法
<div>{{myData | filterName}}div>
<div>{{myData | filterName(arg)}}div>
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据
日期格式化例子
<body>
<div id="demo">
<h2>显示格式化的日期时间h2>
<p>{{date}}p>
<p>完整版:{{date | dateString}}p>
<p>年月日:{{date | dateString('YYYY-MM-DD')}}p>
<p>时分秒:{{date | dateString('HH:mm:ss')}}p>
div>
<script src="../js/vue.js">script>
//引入时间格式化插件
<script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/moment.js">script>
<script>
Vue.filter('dateString', function(value, format='YYYY-MM-DD HH:mm:ss'){
return moment(value).format(format);
});
new Vue({
el: '#demo',
data: {
date: new Date()
}
})
script>
body>
处理时间的库 moment 体积较大 dayjs 轻量级
<body>
<div id="root">
<h2>显示格式化后的时间h2>
<h3>现在是:{{fmtTime}}h3>
<h3>现在是:{{getFmtTime()}}h3>
<h3>现在是:{{time | timeFormater}}h3>
<h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}h3>
<h3 :x="msg | mySlice">尚硅谷h3>
div>
<div id="root2">
<h2>{{msg | mySlice}}h2>
div>
body>
<script type="text/javascript" src="../js/vue.js">script>
<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.0/dayjs.min.js">script>
<script type="text/javascript">
Vue.config.productionTip = false;
//全局过滤器
Vue.filter('mySlice', function (value) {
return value.slice(0, 4);
});
new Vue({
el: '#root',
data: {
time: 1621561377603, //时间戳
msg: '你好',
},
computed: {
fmtTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss');
},
},
methods: {
getFmtTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss');
},
},
//局部过滤器
filters: {
timeFormater(value, str = 'YYYY年MM月DD日 HH:mm:ss') {
// console.log('@',value)
return dayjs(value).format(str);
},
},
});
new Vue({
el: '#root2',
data: {
msg: 'hello!',
},
});
script>
body>
<template>
<div>
<div v-if="cartlist.length <= 0">您没有选择的商品,购物车为空,<a href="#">去购物a>div>
<table v-else>
<caption><h1>购物车h1>caption>
<tr>
<th>th>
<th>编号th>
<th>商品名称th>
<th>商品价格th>
<th>购买数量th>
<th>操作th>
tr>
<tr v-for="(item,index) in cartlist" :key="item.id">
<td><input type="checkbox" v-model="item.checked">td>
<td>{{item.id}}td>
<td>{{item.name}}td>
<td><small>¥small>{{item.price.toFixed(2)}}td>
<td>
<button @click="item.count--" :disabled="item.count <= 1 ">-button>
{{ item.count}}
<button @click="item.count++">+button>
td>
<td><a href="#" @click.prevent="del(index)">删除a>td>
tr>
<tr>
<td colspan="3" align="right">总价td>
<td colspan="3">{{ totalPrice }}td>
tr>
table>
div>
template>
<script>
export default {
name: 'App',
data() {
return {
cartlist: [
{id:1, checked:true, name:'《活着》', price:80, count:1},
{id:2, checked:true, name:'《岛上世界》', price:40, count:1},
{id:3, checked:true, name:'《权力属于有自制力的人》', price:50, count:1},
{id:4, checked:true, name:'《朝花夕拾》', price:120, count:1},
{id:5, checked:true, name:'《完美世界》', price:99, count:1},
{id:6, checked:true, name:'《无间道》', price:39, count:1},
]
}
},
computed: {
totalPrice: {
get() {
let sum = 0;
for (let book of this.cartlist) {
if (book.checked)
sum += book.count * book.price;
}
return '¥'+sum.toFixed(2);
}
}
},
methods: {
del(index) {
this.cartlist.splice(index,1)
}
}
}
script>
<style scoped>
table {
width: 600px;
border: 1px solid #333;
border-collapse: collapse;
}
th {
background-color: #d2d2d2;
}
td, th {
bord`在这里插入代码片`er: 1px solid #333333;
padding: 10px;
}
style>
传统方法编写应用问题
组件方法编写应用
模块
a. 理解: 向外提供特定功能的 is 程序,一般就是一个is 文件
b. 为什么: js 文件很多很复杂
c. 作用: 复用、简化js 的编写,提高js 运行效率
组件
a. 定义: 用来实现局部功能的代码和资源的集合(html/css/js/image…)
b. 为什么: 一个界面的功能很复杂
c. 作用: 复用编码,简化项目编码,提高运行效率
模块化
当应用中的 is 都以模块来编写的,那这个应用就是一个模块化的应用
组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
非单文件组件: 一个文件中包含有 n 个组件
单文件组件: 一个文件中只包含有 1 个组件
Vue中使用组件的三大步骤:
Vue.extend(options)创建
,其中 options
和 new Vue(options)
时传入的 options
几乎一样,但也有点区别el
不要写,因为最终所有的组件都要经过一个 vm
的管理,由 vm
中的 el
才决定服务哪个容器data
必须写成函数,避免组件被复用时,数据存在引用关系,(若data为对象不是函数,那么创建出来的实例保持的都是对同一个对象的引用(同一数据的引用地址复制了多份,但它们还是指向相同的数据),其中一个组件更改了data数据,则另外的组件data数据也会更改)//data为对象形式
let data = {
a: 1,
b: 2,
};
const x1 = data;
const x2 = data;
x1.a = 99;
console.log(x1.a); //99
console.log(x2.a); //99
//data为函数形式
function data1() {
return {
a: 1,
b: 2,
};
}
const x3 = data1();
const x4 = data1();
x3.a = 99;
console.log(x3.a); //99
console.log(x4.a); //1
组件命名方式:
一个单词组成
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool(在使用Vue脚手架的情况下可用)
组件名尽可能回避HTML中已有的元素名称,例如h2、H2,也可以使用name配置项指定组件在开发者工具中呈现的名字
const school = Vue.extend({
template: `
学校名称:{{schoolName}}
学校地址:{{address}}
`,
data() {
return {
schoolName: '尚硅谷',
address: '北京昌平'
}
}
new Vue()
的时候options
传入components
选项Vue.component('组件名','组件')
使用组件//全局注册
Vue.component('school', school)
//局部注册
const app = new Vue({
el:"#app",
components:{//局部组件创建
//'school': school
school
}
})
调用组件
(在使用Vue脚手架的情况下可用)
会导致后续组件不能渲染const school = Vue.extend(options)
可简写为 const school = options
,因为父组件 components
引入的时候会自动创建组件案例
<body>
<div id="root">
<hello>hello>
<hr>
<h1>{{msg}}h1>
<hr>
<school>school>
<hr>
<student>student>
div>
<div id="root2">
<hello>hello>
div>
body>
<script type="text/javascript">
//第一步:创建school组件
const school = Vue.extend({
template: `
学校名称:{{schoolName}}
学校地址:{{address}}
`,
data() {
return {
schoolName: '尚硅谷',
address: '北京昌平'
}
},
methods: {
showName() {
alert(this.schoolName)
}
},
})
//第一步:创建student组件
const student = Vue.extend({
template: `
学生姓名:{{studentName}}
学生年龄:{{age}}
`,
data() {
return {
studentName: '张三',
age: 18
}
}
})
//第一步:创建hello组件
const hello = Vue.extend({
template: `
你好啊!{{name}}
`,
data() {
return {
name: 'Tom'
}
}
})
//第二步:全局注册组件
Vue.component('hello', hello)
//创建vm
new Vue({
el: '#root',
data: {
msg: '你好啊!'
},
//第二步:注册组件(局部注册)
components: {
school,
student
}
})
new Vue({
el: '#root2',
})
script>
<body>
<div id="root">
div>
body>
<script type="text/javascript">
//定义school的student子组件
const student = Vue.extend({
name: 'student',
template: `
学生姓名:{{name}}
学生年龄:{{age}}
`,
data() {
return {
name: '尚硅谷',
age: 18
}
}
})
//定义school子组件
const school = Vue.extend({
name: 'school',
template: `
学校名称:{{name}}
学校地址:{{address}}
`,
data() {
return {
name: '尚硅谷',
address: '北京'
}
},
//注册组件(局部)
components: {
student
}
})
//定义hello子组件
const hello = Vue.extend({
template: `{{msg}}
`,
data() {
return {
msg: '欢迎来到尚硅谷学习!'
}
}
})
//定义app父组件
const app = Vue.extend({
template: `
`,
components: {
school,
hello
}
})
//创建vm
new Vue({
template: ' ',
el: '#root',
//注册组件(局部)
components: {
app
}
})
script>
关于VueComponent
Vue.extend()
函数生成的
或<school>
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行new VueComponent(options)
Vue.extend
,返回的都是一个全新的VueComponent(因为Vue.extend
在vue内部是函数,data使用函数式是一个道理,保证每个模板的数据是相互独立的)VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)
Vue的实例对象,以后简称为vm
<body>
<div id="root">
<school>school>
<hello>hello>
div>
body>
<script type="text/javascript">
//定义school组件
const school = Vue.extend({
name: 'school',
template: `
学校名称:{{name}}
学校地址:{{address}}
`,
data() {
return {
name: '尚硅谷',
address: '北京'
}
},
methods: {
showName() {
console.log('showName', this)
}
},
})
//定义hello组件下 test子组件
const test = Vue.extend({
template: `atguigu`
})
//定义hello组件
const hello = Vue.extend({
template: `
{{msg}}
`,
data() {
return {
msg: '你好啊!'
}
},
components: {
test
}
})
//创建vm
const vm = new Vue({
el: '#root',
components: {
school,
hello
}
})
script>
这样组件实例对象vc就可以访问到Vue原型上的属性和方法(本来VueComponent原型对象的__ proto __
应该指向Object的原型对象,vue强行更改的)
组件实例对象就是小型的实例对象vm,但它没有el
配置对象
每一个构造函数都有原型对象prototype
,把所有不变的的方法都直接定义在原型对象上,然后从构造函数中new出来的实例对象就可以共享这些方法。
实例对象都会有__proto__
属性,指向构造函数的原型对象prototype
,之所以实例对象可以使用构造函数原型对象的属性和方法,就是因为对象有__proto__
属性的存在。
构造函数.prototype === 实例对象.__ proto __
VueComponent.prototype.__proto__ === Vue.protot ype
<body>
<div id="root">
<school>school>
div>
body>
<script type="text/javascript">
Vue.prototype.x = 99
//定义school组件
const school = Vue.extend({
name: 'school',
template: `
学校名称:{{name}}
学校地址:{{address}}
`,
data() {
return {
name: '尚硅谷',
address: '北京'
}
},
methods: {
showX() {
console.log(this.x)//99
}
},
})
//创建一个vm
const vm = new Vue({
el: '#root',
data: {
msg: '你好'
},
components: {
school
}
})
//定义一个构造函数
/* function Demo(){
this.a = 1
this.b = 2
}
//创建一个Demo的实例对象
const d = new Demo()
console.log(Demo.prototype) //显式原型属性
console.log(d.__proto__) //隐式原型属性
console.log(Demo.prototype === d.__proto__)
//程序员通过显式原型属性操作原型对象,追加一个x属性,值为99
Demo.prototype.x = 99
console.log('@',d) */
script>
传统组件的问题与解决方案
问题:
1.全局定义的组件必须保证组件的名称不重复
2.字符串模板缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的
3.不支持 CSS 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
4.没有构建步骤限制,只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器(如:Babel)
解决方案:
针对传统组件的问题,Vue 提供了一个解决方案 —— 使用 Vue 单文件组件。
单文件组件:一个文件只包含1个组件
单文件组件的组成结构
template
组件模板区域script
组件交互区域(业务逻辑)style
组件样式区域// 模板
<template>
template>
<script>
// 这里用于定义Vue组件的业务逻辑
export default {
// 私有数据
data: () { return {} },
// 处理函数
methods: {}
// ... 其它业务逻辑
}
script>
// 加scoped 组件私有
<style scoped>
/* 这里用于定义组件的样式 */
style>
App.vue
<template>
<div>
<span style="color: blueviolet">App.Vuespan>
div>
<HelloWorld>HelloWorld>
template>
<script>
import HW from './components/newHelloWorld'
export default {
name: 'App',
data() {
return {
}
},
components: {
//若键值名称相同,则写一个即可
HelloWorld: HW
}
}
script>
<style scoped>
style>
newHelloWorld.vue
<template>
<div>
<h1>{{msg}}h1>
div>
template>
<script>
export default {
name: "newHelloWorld",
data() {
return {
msg: 'Hello World!!!!'
}
}
}
script>
<style scoped lang='scss'>
/*scoped 可设置样式只在当前组件使用,不往下传递给子组件*/
style>
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
//或者
new Vue({
template: ` `
el:"#app",
components:{App}
})
index.html
<div id="app">div>
<script src="../../vue.js">script>
<script src="./main.js">script>
关于不同版本的Vue
vue.js与vue.runtime.xxx.js(main.js中引入的运行版)的区别:
vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template
这个配置项,需要使用render
函数接收到的createElement
函数去指定具体内容。render
函数和 template
一样都是创建 html 模板的
new Vue({
el:"#app",
//template: ` `
//template: `APP信息
`
render: createElement => {
return createElement('h1','APP信息')
}
//等价于
render: q=>q('h1','APP信息')
//下面这行代码会解释,完成这个功能:将App组件放入容器中
render: h=>h(App)
})
父组件 App.vue
<template>
<section class="conn">
<header class="header">
<my-header>my-header>
header>
<div class="main">
<div class="content">
<my-main>my-main>
div>
<div class="siderbar">
<my-sider-bar>my-sider-bar>
div>
div>
<footer class="footer">footer>
section>
template>
<script>
import MyHeader from "@/components/MyHeader";
import MySiderBar from "@/components/MySiderBar";
import MyMain from "@/components/MyMain";
export default {
name: 'App',
//子组件声明
components: {
MyHeader,
MyMain,
MySiderBar
}
}
script>
<style scoped lang="scss">
$w:600px;
$color1:#ccc;
$color2:#888;
html,body {
margin: 0;
padding: 0;
}
.conn {
width: $w;
background-color: $color1;
height: 500px;
margin: 0 auto;
}
.header {
width: $w;
height: 80px;
background-color: $color2;
}
.main {
width: 100%;
height: 300px;
background-color: yellow;
}
.footer {
width: 100%;
height: 100px;
background-color: green;
}
.content {
width: 70%;
height: 300px;
float: left;
background-color: rebeccapurple;
}
.siderbar {
width: 30%;
height: 300px;
float: left;
background-color: aqua;
}
style>
MyHeader.vue
<template>
<div>
<h1>{{msg}}h1>
div>
<my-conn>my-conn>
<my-bar>my-bar>
template>
<script>
import MyConn from "@/components/childComp/MyConn";
import MyBar from "@/components/childComp/MyBar";
export default {
name: "MyHeader",
data() {
return {
msg: 'Hello World!!!!'
}
},
//子组件
components: {
MyBar,
MyConn
}
}
script>
<style scoped>style>
MyMain.vue
<template>
<my-conn>my-conn>
template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
components: {
MyConn
}
}
script>
<style scoped>style>
MySiderBar.vue
<template>
<my-bar>my-bar>
<my-bar>my-bar>
<my-bar>my-bar>
template>
<script>
import MyBar from "@/components/childComp/MyBar";
export default {
name: "MySiderBar",
components: {
MyBar
}
}
script>
<style scoped>style>
MyConn.vue
<template>
<div class="myconn">
{{mess}}
div>
template>
<script>
export default {
name: "MyConn",
data() {
return {
mess: 'this is main test'
}
}
}
script>
<style scoped>
.myconn {
width: 90%;
height: 150px;
background-color: brown;
margin: 10px;
}
style>
MyBar.vue
<template>
<div class="mybar">
bar
div>
template>
<script>
export default {
name: "MyBar"
}
script>
<style scoped>
.mybar {
width: 50px;
height: 50px;
margin: 10px;
background-color: cornflowerblue;
}
style>
props
让组件接收外部传来的数据
props传递数据原则:单向数据流,只能父传子
这里age前加 :
,通过v-bind
使得里面的18是数字 <!-- 子组件 -->
//第一种方式(只接收)最常用
props:['name','age']
//第二种方式(限制类型)
props:{name:String, age:Number}
//第三种方式(限制类型、限制必要性、指定默认值)
props:{
name:{
type:String, //类型
required:true, //必要性
default:'张三' //默认值
}
}
//以对象形式列出所有 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
Prop
是你可以在组件上注册的一些自定义attribute
。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property
一个组件默认可以拥有任意数量的 prop
,任何值都可以传递给任何prop
。
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
传递的属性值时,属性名父组件和子组件最好一样,传递MyTitle,子组件就使用MyTitle
v-bind
是不支持使用驼峰标识的,例如cUser
要改成c-User
。
备注: props
是只读的,Vue底层会监测你对props
的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props
的内容到data
中一份,然后去修改data
中的数据。
传递单值例子:
App.vue传递给MyMain.vue一个msg值和title值
v-bind
来动态传递 prop
<my-main msg="hello" :title="msg">my-main>
......
data() {
return {
msg:'this is app data msg'
}
}
MyMain.vue从props
接收,会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样。
<template>
<my-conn>my-conn>
{{msg}} {{title}}
template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
//从上一层组件传过来的一个值,接收一个值
props: ['msg','title'],
components: {
MyConn
}
}
script>
<style scoped>style>
传递数组例子
App.vue
<my-main msg="hello" :title="msg" :article="article">my-main>
......
data() {
return {
msg:'this is app data msg',
article: ['one','two','three']
}
}
MyMain.vue
<template>
<my-conn>my-conn>
{{msg}} {{title}}
<br>
<span v-for="(item,index) in article" :key="index">{{item}}<br>span>
template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
//就要采用对象的方法,写法与数组方式不同
props: {
msg: {
type: String,
default:'#####' //设置缺省值,若无传值,等同于在data处声明一个msg:'####'一样
},
title: {
type: String,
required: true //表明该属性值必传,否则报错
},
article: {
type: Array,
default () { //Vue2的写法
return ['aaa','bbb','ccc']
},
// default: ['aaa','bbb','ccc'] Vue3支持的写法
}
},
components: {
MyConn
}
}
script>
<style scoped>style>
props
可以多层传递,MyMain可以传递给MyConn,写法一样
MyConn.vue
<template>
<div class="myconn">
<p>conn contentp>
<span v-for="(item,index) in article" :key="index">{{item}}<br>span>
div>
template>
<script>
export default {
name: "MyConn",
props: {
article: {
type: Array
}
}
}
script>
<style scoped>
.myconn {
width: 90%;
height: 150px;
background-color: brown;
margin: 10px;
}
style>
MyMain.vue
<template>
<my-conn :article="article">my-conn>
{{msg}} {{title}}
<br>
<span v-for="(item,index) in article" :key="index">{{item}}<br>span>
template>
而App.vue里声明的article属性,一旦有变化,MyMain和MyConn就会随之变化
或
<!-- App.vue父组件 -->
// 在父组件中给子组件 xxx为自定义事件 getStudentName为回调函数(在父组件中)
<Student @xxx="getStudentName" />
//若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
<Student @xxx.once="getStudentName" />
...
methods: {
//回调函数
getStudentName(name) {
this.studentName = name;
},
},
this.$refs.demo.$on('事件名',方法)
//通过ref给Student组件打标识
<Student ref="student" />
...
methods: {
//回调函数
getStudentName(name) {
this.studentName = name;
},
},
mounted() {
//通过$refs获取Student组件
//在获取到的Student组件上绑定自定义事件xxx getStudentName为回调函数
this.$refs.student.$on("xxx", this.getStudentName); //$on当...时
//若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
this.$refs.student.$once("xxx", this.getStudentName);
},
once
修饰符,或$once
方法this.$emit(xxx,data)
<!-- Student.vue子组件 -->
<button @click="sendStudentName">把学生名给App</button>
...
methods: {
sendStudentName() {
//触发Student组件实例身上的xxx自定义事事件
this.$emit("xxx", this.name);
}
},
this.$off(‘xxx’)
this.$off('studentEvent') //只使用解绑一个自定义事件
this.$off(['studentEvent','studentEvent2']) //解绑多个自定义事件
this.$off() //所有自定义的事件都解绑
@click.native="show"
上面绑定自定义事件,即使绑定的是原生事件也会被认为是自定义的,需要加 native
,加了后就将此事件给组件的根元素 this.$refs.xxx.$on('事件名,回调函数)
绑定自定义事件时,回调函数要么配置在 methods 中,要么用箭头函数,否则 this
指向会出问题例子
src/App.vue
<template>
<div id="app">
<h1>{{msg}}, APP父组件——学校是: {{schoolName}}h1>
<h1>{{msg}}, APP父组件——学生姓名是: {{studentName}}h1>
<School :getSchoolName="getSchoolName" />
<Student ref="student" @click.native="show" />
div>
template>
<script>
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
name: 'App',
components: {
School, Student
},
data() {
return {
msg: '你好啊',
studentName: '',
schoolName: ''
}
},
methods: {
getSchoolName(name) {
this.schoolName = name
},
getStudentName(name,...params) {
console.log('App接收学生数据-----',name, params);
this.studentName = name
},
test(value) {
console.log('studentEvent2被触发了-----',value);
},
show() {
alert('show')
}
},
mounted() {
// console.log('多次');
//方法二:使用refs绑定自定义事件
// this.$refs.student.$on('studentEvent', this.getStudentName)
//$once单次触发
// this.$ref.student.$once('studentEvent', this.getStudentName)
//可以把函数写在这里,但是必须写成箭头函数的形式,因为箭头函数没有自己this,那么箭头函数会往外找this(找到mounted,则找到vm实例对象)
//如果写成普通函数形式,this指向student的vc实例对象,则实例对象中没有studentName
this.$refs.student.$on('studentEvent', (name,...params)=>{
console.log('App接收学生数据-----',name, params);
this.studentName = name
})
}
}
script>
<style>
#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>
子组件src/components/Student.vue
<template>
<div class="student">
<h2>学生姓名:{{ name }}h2>
<h2>性别:{{ sex }}h2>
<h2>当前求和为: {{ number }}h2>
<button @click="add">点我number++button>
<button @click="sendStudentName">把学生名给Appbutton>
<button @click="unbind">解绑事件button>
<button @click="death">销毁当前Student组件的实例(vc)button>
div>
template>
<script >
export default ({
name: "Student",
data() {
return {
name: "张三",
sex: '男',
number: 0
};
},
methods: {
add() {
console.log('add调用了');
this.number++
},
sendStudentName() {
//触发Student组件实例身上的xxx自定义事事件
this.$emit('studentEvent', this.name)
// this.$emit('studentEvent2', this.name)
},
unbind() {
this.$off('studentEvent') //只使用解绑一个自定义事件
// this.$off(['studentEvent','studentEvent2']) //解绑多个自定义事件
// this.$off() //所有自定义的事件都解绑
},
death() {
this.$destroy(); //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
}
}
})
script>
<style scoped>
.student {
background-color: pink;
padding: 5px;
margin-top: 30px;
}
style>
子组件src/components/School.vue
<template>
<div class="school">
<h2>学校名:{{ name }}h2>
<button @click="sendSchoolName">把学校名给Appbutton>
div>
template>
<script >
export default ({
name: "School",
data() {
return {
name: "XXXX大学",
};
},
props: ['getSchoolName'],
methods: {
sendSchoolName() {
this.getSchoolName(this.name)
}
}
})
script>
<style scoped>
.school {
background-color: rgb(112, 255, 188);
padding: 5px;
margin-top: 30px;
}
style>
子组件MyConn.vue
子组件调用父组件可以采用$parent
<template>
<div class="myconn">
<button @click="changenum">++button>
<br>
div>
template>
<script>
export default {
name: "MyConn",
methods: {
changenum() {
this.$parent.add();
}
}
}
script>
<style scoped>
......
style>
父组件MyMain.vue
父组件声明了一个add()
<template>
<div style="width: 200px;height: 50px;background-color: yellow">父组件的count:{{count}}div>
template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
components: {
MyConn
},
data() {
return {
count:0
}
},
methods: {
add() {
this.count ++;
}
}
}
script>
<style scoped>style>
若是孙子组件想要访问爷爷组件的方法,也用$parent
但是要打两个$parent
,也可直接直接使用$root
孙子组件MyConn.vue
methods: {
changenum() {
this.$parent.add();
console.log(this.$parent.count) //访问MyMain.vue的count
console.log(this.$parent.$parent.msg) //访问App.vue的msg
this.$parent.$parent.appmet() //访问App.vue的appmet方法
//等价于
this.$root.appmet()
}
}
$children
或 $refs
ref
被用来给元素或子组件注册引用信息(id的替代者
或
this.$refs.xxx
父组件MyMain.vue
<template>
<button @click="subson" ref="btn">让子组件-1 button>
<my-conn ref="child">my-conn>
template>
<script>
import MyConn from "@/components/childComp/MyConn";
export default {
name: "MyMain",
components: {
MyConn
},
data() {
return {
count:0
}
},
methods: {
subson() {
console.log('父组件的subson()');
this.$refs.child.sub() //MyConn组件的实例对象vc
console.log(this.$refs.btn) //真实DOM元素
}
}
}
script>
<style scoped>style>
子组件MyConn.vue
<template>
<div class="myconn">
子组件的num:{{num}}
div>
template>
<script>
export default {
name: "MyConn",
data() {
return {
num: 0
}
},
methods: {
sub() {
this.num--;
}
}
}
script>
<style scoped>
....
style>
一种可以在任意组件间通信的方式,本质上就是一个对象,它必须满足以下条件
$on
$emit
$off
方法去绑定、触发和解绑事件使用步骤
1.定义全局事件总线
//main.js
new Vue({
el: '#app',
render: h => h(App),
//安装全局事件总线 beforeCreate创建实例前
beforeCreate() {
//组件实例对象vc可以访问到Vue原型上的属性和方法,往Vue原型上添加$bus属性 $bus为傀儡
//那么子组件可以使用$bus,而$bus值为vm(this) 因为vm可以调用$on $emit这些方法
Vue.prototype.$bus = this;
},
});
A组件
methods(){
demo(data){......}
}
......
//mounted():初始化操作,绑定自定义事件
//xxx为自定义事件 this.demo为回调函数
mounted() {
this.$bus.$on('xxx',this.demo)
//还可以使用箭头函数来,这一部分可以看前面5.3.2.2 子组件给传递父组件数据 $emit部分的第7点
this.$bus.$on('xxx',(data)=>{……})
}
//使用完之后 beforeDestroy解绑自定义事件
beforeDestroy() {
this.$bus.$off("xxx");
},
发送数据:
B组件
this.$bus.$emit('xxx',数据)
beforeDestroy
钩子中,用$off
去解绑当前组件所用到的事件。beforeDestroy() {
//解绑当前组件用到的事件
this.$bus.$off("hello");
},
案例:兄弟组件传值(Student => School)
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
School.vue
<template>
<div class="school">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
methods: {
demo(data) {
console.log("我是School组件,收到了数据", data);
},
},
mounted() {
//在School组件给傀儡绑定自定义事件hello,借助傀儡身上的$on方法获取数据
this.$bus.$on("hello", this.demo);
},
beforeDestroy() {
//解绑当前组件用到的事件
this.$bus.$off("hello");
},
};
</script>
<style scoped>
.school {
background-color: skyblue;
padding: 5px;
}
</style>
Student.vue
template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
消息订阅与发布 (pubsub) 消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信使用步骤
npm i pubsub-js
import pubsub from 'pubsub-js
methods: {
demo(msgName, data) {
...
},
},
mounted() {
//订阅消息 每一次订阅都会产生一个id
//msgName代表数据名xxx data代表接收的数据
this.pubId = pubsub.subscribe("xxx", this.demo);
},
beforeDestroy() {
//通过id取消订阅
pubsub.unsubscribe(this.pubId);
},
methods: {
sendStudentName() {
//发布消息
pubsub.publish("hello", this.name);
},
},
beforeDestroy
钩子中,使用 pubsub.unsubscribe(pid)
取消订阅案例:兄弟组件传值(Student => School)
School.Vue
<template>
<div class="school">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
import pubsub from "pubsub-js";
export default {
name: "School",
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
methods: {
demo(msgName, data) {
console.log("我是School组件,收到了数据", msgName, data);
},
},
mounted() {
//订阅消息 每一次订阅都会产生一个id
//msgName代表数据名hello data代表接收的数据
this.pubId = pubsub.subscribe("hello", this.demo);
},
beforeDestroy() {
//通过id取消订阅消息
pubsub.unsubscribe(this.pubId);
},
};
</script>
<style scoped>
.school {
background-color: skyblue;
padding: 5px;
}
</style>
Student.Vue
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
import pubsub from "pubsub-js";
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
//发布消息
pubsub.publish("hello", this.name);
},
},
};
</script>
<style lang="less" scoped>
.student {
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将
元素作为承载分发内容的出口。
插槽: 让父组件可以向子组件指定位置插入 html 结构,也是一种组件间通信的方式,适用于父组件===>子组件
特性:插槽可以实现组件的扩展性 , 抽取共性, 保留不同
分类: 默认插槽、具名插槽、作用域插槽
插槽slot是把父组件把数据渲染完了,再插到子组件里
//父组件中
<Category>
<div>html结构1div>
Category>
//子组件中
<template>
<div>
<slot>插槽默认内容……slot>
div>
template>
<template>
<div class="mybar">
<h6>{{title}}h6>
<slot>slot>
div>
template>
<script>
export default {
name: "MyBar",
data() {
return {
title: 'title'
}
}
}
script>
<style scoped>
.mybar {
width: 80px;
height: 80px;
margin-bottom:10px;
background-color: cornflowerblue;
}
style>
父组件MySiderBar.vue
<template>
<my-bar>
<button>提交button>
my-bar>
<my-bar>
<a href="#">提交a>
my-bar>
<my-bar>
<p>提交文本p>
my-bar>
template>
<script>
import MyBar from "@/components/childComp/MyBar";
export default {
name: "MySiderBar",
components: {MyBar}
}
script>
<style scoped>style>
插槽slot
还可以包含任何模板代码,包括 HTML,即设置默认值,若父组件中的不提供任何插槽内容时:
<my-bar>my-bar>
<my-bar>my-bar>
<my-bar>my-bar>
而子组件为一个插槽设置具体的后备 (也就是默认的) 内容,它会在没有提供内容的时候被渲染
<div class="mybar">
<h6>{{title}}h6>
<slot><button>提交button>slot>
div>
多个插槽,若没有设置插槽名称,会把所有的slot都替换掉,我们需要把slot插槽设置名称(具名插槽),按名字指定替换哪个slot
v-slot:
简写为 #
v-slot:header
可以被简写为 #header
出口会带有隐含的名字“default”。v-slot
只能添加在
上//父组件中
<Category>
<template slot="center">
<div>html结构1div>
template>
<template v-slot:footer>
<div>html结构2div>
template>
Category>
//子组件中
<template>
<div>
<slot name='center'>插槽默认内容……slot>
<slot name='footer'>插槽默认内容……slot>
div>
template>
子组件 MyBar.vue
<template>
<div class="mybar">
<h6>{{title}}h6>
<slot name="one"><button>提交button>slot><br>
<slot name="two">firstslot><br>
<slot>secondslot>
div>
template>
父组件MySiderBar.vue
<template>
<my-bar>my-bar>
<my-bar>
<template v-slot:one>
<a href="#" >替换文本a>
template>
<template v-slot:default>
<a href="#" >更新文本a>
template>
my-bar>
<my-bar> my-bar>
template>
有时让插槽内容能够访问子组件中才有的数据是很有用的。当一个组件被用来渲染一个项目数组时,这是一个常见的情况,我们希望能够自定义每个项目的渲染方式。
我们在子组件里的
元素绑定为属性绑定在
元素上的 attribute 被称为插槽 prop
//子组件
<slot name="up" :data="data">slot>
<script>
export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
},
}
script>
父组件往子组件插模板的情况,那到底插一套什么样的样式呢,这由父组件的html+css共同决定,但是这套样式里面的内容呢?
作用域插槽绑定了一套数据,父组件可以拿来用。于是,情况就变成了这样:
样式父组件说了算,但内容可以显示子组件插槽绑定的。
我们可以使用scope
属性
scope
用于父组件往子组件插槽放的 html 结构接收子组件的数据
理解: 数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定(games 数据在 Category 组件中,但使用数据所遍历出来的结构由 App 组件决定)
<Category>
<template scope="scopeData">
根据需求设定不一样的样式
template>
Category>
子组件 Categoru.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot :games="games" msg="hello">我是默认的一些内容</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
}
},
}
</script>
<style scoped>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: orange;
}
video{
width: 100%;
}
img{
width: 100%;
}
</style>
父组件 App.vue
<template>
<div class="container">
<Category title="游戏">
<template scope="atguigu">
<ul>
<li v-for="(g,index) in atguigu.games" :key="index">{{g}}li>
ul>
template>
Category>
<Category title="游戏">
<template scope="{games}">
<ol>
<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}li>
ol>
template>
Category>
<Category title="游戏">
<template slot-scope="{games}">
<h4 v-for="(g,index) in games" :key="index">{{g}}h4>
template>
Category>
div>
template>
<script>
import Category from './components/Category'
export default {
name:'App',
components:{Category},
}
script>
<style scoped>
.container,.foot{
display: flex;
justify-content: space-around;
}
h4{
text-align: center;
}
style>
要想使 user和sex可用于父级提供的 slot 内容,我们可以添加一个
元素并将其绑定为属性
绑定在
元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot
来定义我们提供的插槽 prop 的名字:
<template>
<div class="mybar">
<slot name="one"><button>提交button>slot><br>
<slot name="two" :sex="sex">性别:男slot><br>
<slot :user="user">名字:{{ user.name }}slot>
div>
template>
<script>
export default {
name: "MyBar",
data() {
return {
title: 'title',
user: {name:'zhangsan'},
sex: '男'
}
}
}
script>
父组件MySiderBar.vue
<my-bar>
<template v-slot:one>
<a href="#" >替换文本a>
template>
<template v-slot:two="setSex">
{{ setSex.sex}}
template>
<template v-slot:default="newuser">
<a href="#" >{{ newuser.user.name }}a>
template>
my-bar>
mixin
混入//定义混合
export const mixin = {
methods: {
showName() {
alert(this.name);
},
},
};
Vue.mixin(xxx)
mixins:['xxx']
备注:
src/mixin.js
export const mixin = {
methods: {
showName() {
alert(this.name);
},
},
mounted() {
console.log('mixin混入对象的mounted')
}
};
export const hunhe2= {
data() {
return {
x:100,
y:200
}
}
};
全局混入
src/main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
import {mixin} from './mixin'
//全局混入
//Vue.mixin(mixin)
//Vue.mixin(hunhe)
//创建vm
new Vue({
el:'#app',
render: h => h(App)
})
局部混入
src/compoents/School.vue子组件
<template>
<div class="demo">
<h2 @click="showName">学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
import { mixin,hunhe } from "../mixin";
export default {
name: 'School',
data() {
return {
name: "尚硅谷",
address: "北京",
x: 666
};
},
mixins: [mixin, hunhe], //局部混入
};
</script>
<style></style>
src/compoents/Student.vue
<template>
<div class="demo">
<h2 @click="showName">学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
</div>
</template>
<script>
import { mixin,hunhe } from "../mixin";
export default {
name: 'Student',
data() {
return {
name: "张三",
sex: "男",
};
},
mixins: [mixin, hunhe], //局部混入
};
</script>
<style></style>
src/App.vue
<template>
<div >
<School/>
<hr>
<Student/>
</div>
</template>
<script>
import School from "./components/School"
import Studentfrom "./components/Student"
export default {
name: 'App',
components: {School, Student}
};
</script>
<style></style>
Vue.use()
//定义插件
对象.install = function (Vue, options) {
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入(合)
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.myMethod = function () {...}
Vue.prototype.myProperty = xxxx
}
实例
src/plugin.js
export default {
install(Vue, x, y, z) {
console.log(x,y,z)
//全局过滤器
Vue.filter("mySlice", function (value) {
return value.slice(0, 4);
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = () => {
alert("你好啊");
}
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(el, binding) {el.value = binding.value},
//指令所在元素被插入页面时
inserted(el, binding){el.focus() },
//指令所在的模板被重新解析时
update(el, binding) {el.value = binding.value }
})
//定义混入
Vue.mixin({
data() {return {x:100,y:200}}
})
},
};
src/main.js
//引入Vue
import Vue from "vue";
//引入App
import App from "./App.vue";
//引入插件
import plugins from "./plugins";
//使用插件
Vue.use(plugins,1,2,3);
//创建vm
new Vue({
el: "#app",
render: (h) => h(App),
});
src/compoents/School.vue子组件
<template>
<div class="demo">
<!-- 用了插件里的过滤器 -->
<h2 @click="showName">学校名称:{{ name | mySlice }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="test">点我测试一个hello方法</button>
</div>
</template>
<script>
export default {
name: 'School',
data() {
return {
name: "尚硅谷",
address: "北京",
x: 666
};
},
methods: {
test() {this.hello()}
}
};
</script>
<style></style>
src/compoents/Student.vue
<template>
<div class="demo">
<h2 @click="showName">学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<input type="text" v-fbind:value="name">
</div>
</template>
<script>
import { mixin,hunhe } from "../mixin";
export default {
name: 'Student',
data() {
return {
name: "张三",
sex: "男",
};
}
};
</script>
<style></style>
scoped
样式:让样式在局部生效,防止冲突。
<style scoped></style>
应用场景举例:鼠标点击编辑按钮时,span标签消失,同时input输入框显示,并且输入框已经自动获取焦点,失去焦点时就显示编辑按钮
分析:当需要浏览器重绘重排的时候,是需要时间的,当点击按钮时Input还没有渲染出来,此时如果的编辑按钮里的方法里直接写focus(),可以发现是不起作用的,input没有何来焦点?
实现:
visibility
,确定输入框的展示状态,默认为falsechangeVisibility
方法,并给button
绑定点击事件@click="changeVisibility"
,实现点击按钮展示输入框,给输入框绑定失去焦点事件@blur="handleBlur"
input
和button
元素,绑定条件渲染指令v-show
,当visibility=false
时展示按钮,反之展示输入框v-show
来绑定条件<template>
<div>
<label for="input">
<span v-show="!visibility">{{title}}span>
<input type="text" ref="inputRef" v-show="visibility" @blur="handleBlur($event)">
label>
<button @click="changeVisibility" v-show="!visibility">编辑button>
div>
template>
<script>
export default {
data() {
return {
visibility: false,
title: 'testTitle'
}
},
methods: {
changeVisibility() {
this.visibility = !this.visibility // 切换 visibility,控制输入框的显示状态
//定时器+ref引用
setTimeout(() => {
this.$refs.inputRef.focus()
}, 0);
//nexTick
this.$nextTick(()=>{
this.$refs.inputRef.focus()
})
},
handleBlur(e) {
this.visibility = !this.visibility
if(!e.target.value.trim()) return alert('输入不能为空')
/*
传递数据操作……
*/
}
}
}
script>
自动获取对话框焦点
ref
引用this.$refs.inputRef.focus()
获取输入框的焦点,inputRef
是创建输入框时添加的ref
引用)。但是方法定义在组件渲染之前,因此直接在方法中添加,效果无法实现。这是由于浏览器执行到this.$refs.inputRef.focus()
时,input
元素还没有被渲染到页面上,此是的DOM还不存在指定的input
元素,可以设置定时器,推迟焦点的获取setTimeout(() => {
this.$refs.inputRef.focus()
}, 0);
设置一个0ms的定时器,当浏览器执行到定时器时,会将定时器内部的函数放入延迟队列中,当定时器的等待事件结束后,会将函数放入消息队列的末尾,消息队列的执行按照先进先出原则,当前面的任务执行完成后,浏览器会自动执行this.$refs.inputRef.focus()
实现焦点的获取
$nextTick
方法, 将获取焦点推迟到下一个 DOM 更新周期this.$nextTick(()=> {
this.$refs.inputRef.focus()
})
组件的this.$nextTick(cb)
方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。
通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。
this.visibility
发生变化时,都会执行一次更新,增加服务器的负担 updated() {
if (this.visibility){
this.$refs.inputRef.focus()
}
}
当组件处于updated
时,页面已经根据最新的数据渲染完成了,此时我们执行this.$refs.inputRef.focus()
就可以正常获取输入框的焦点
小技巧:
一堆数据用数组,每一个数据中的属性太多用对象
数据在哪里,操作数据的方法就在哪里
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
浏览器端通过 sessionStorage
和localStorage
属性来实现本地存储机制。
相关API:
xxxxxStorage.setItem(‘key’, ‘value’)
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxxxStorage.getItem(‘person’)
该方法接受一个键名作为参数,返回键名对应的值。
xxxxxStorage.removeItem(‘key’)
该方法接受一个键名作为参数,并把该键名从存储中删除。
xxxxxStorage.clear()
该方法会清空存储中的所有数据。
备注:
SessionStorage
存储的内容会随着浏览器窗口关闭而消失。
LocalStorage
存储的内容,需要手动清除才会消失,关闭浏览器不会消失
xxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem
的返回值是null
。
JSON.parse(null)
的结果依然是null
。
<h2>localstorageh2>
<button onclick="saveDate()">点我保存数据button><br/>
<button onclick="readDate()">点我读数据button><br/>
<button onclick="deleteDate()">点我删除数据button><br/>
<button onclick="deleteAlIDate()">点我清空数据button><br/>
<script>
let person = (name:"JoJo", age:20]
function saveDate(){
localstorage.setItem( 'msg',"localstorage')
localstorage.setItem('person,JSON.stringify(person)
}
function readDate() {
console.log(localstorage.getitem('msg')
const person = localstorage.getItem('person')
console.log(JSON.parse(person)
}
function deleteDate(){
localstorage.removeitem('msg')
localstorage.removeitem('person')
}
function deleteAllDate(){
localstorage.clear()
}
script>
<h2>sessionStorageh2>
<button onclick="saveDate()">点我保存数据button><br/>
<button onclick="readDate()">点我读数据button><br/>
<button onclick="deleteDate()">点我删除数据button><br/>
<button onclick="deleteAlIDate()">点我清空数据button><br/>
<script>
let person = (name:"JoJo", age:20]
function saveDate(){
sessionStorage.setItem( 'msg',"sessionStorage')
sessionStorage.setItem('person,JSON.stringify(person)
}
function readDate() {
console.log(sessionStorage.getitem('msg')
const person = sessionStorage.getItem('person')
console.log(JSON.parse(person)
}
function deleteDate(){
sessionStorage.removeitem('msg')
sessionStorage.removeitem('person')
}
function deleteAllDate(){
sessionStorage.clear()
}
script>
1.过渡与动画作用
在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名
2.图示
v-enter
进入的起点v-enter-active
进入过程中v-enter-to
进入的终点v-leave
离开的起点v-leave-active
离开过程中v-leave-to
离开的终点3.2 使用
包裹要过度的元素,并配置 name
属性,此时需要将上面样式名的 v
换为 name
3.3 要让页面一开始就显示动画,需要添加 appear
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!h1>
transition>
3.4 备注:若有多个元素需要过渡,则需要使用
,且每个元素都要指定key
值
<transition-group name="hello" appear>
<h1 v-show="isShow" key="1">你好啊!h1>
<h1 v-show="isShow" key="2">尚硅谷!h1>
transition-group>
案例
App.vue
<template>
<div>
<Test />
<Test2 />
<Test3 />
</div>
</template>
<script>
import Test from "./components/Test";
import Test2 from "./components/Test2";
import Test3 from "./components/Test3";
export default {
name: "App",
components: { Test, Test2, Test3 },
};
</script>
<style>
</style>
Test.vue
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!h1>
transition>
div>
template>
<script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
script>
<style scoped>
h1{
background-color: orange;
}
.hello-enter-active{
animation: atguigu 0.5s linear;
}
.hello-leave-active{
animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
style>
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏button>
<transition-group name="hello" appear>
<h1 v-show="isShow" key="1">你好啊!h1>
<h1 v-show="isShow" key="2">尚硅谷!h1>
transition-group>
div>
template>
<script>
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
script>
<style scoped>
h1 {
background-color: orange;
}
/* 进入的起点 离开的终点 */
.hello-enter,
.hello-leave-to {
transform: translateX(-100%);
}
/* 进入的整个过程 离开的整个过程 */
.hello-enter-active,
.hello-leave-active {
transition: 0.5s linear;
}
/* 进入的终点 离开的起点 */
.hello-enter-to,
.hello-leave {
transform: translateX(0);
}
style>
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏button>
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
>
<h1 v-show="!isShow" key="1">你好啊!h1>
<h1 v-show="isShow" key="2">尚硅谷!h1>
transition-group>
div>
template>
<script>
import "animate.css";
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
script>
<style scoped>
h1 {
background-color: orange;
}
style>