vue.js基础
Vue.js是一个轻量级MVVM框架。vue.js的核心思想是数据驱动+组件化。
1.vue实例
<script src="../lib/vue.js"></script>
<body>
<div id="app">
你的名字是:{{name}}
</div>
</body>
// 创建一个vue实例
<script>
var app = new Vue({
el: '#app', //让vue实例去接管id="app"元素里面的内容。
data: { //存放数据。
name: 'A',
}
});
</script>
挂载点,模板,vue实例的关系
<div id="app">
</div> <!--这个div是vue实例的挂载点,vue只会去处理挂载点下面的内容-->
<!--挂载点内部的内容就是模板,模板可以放在挂载点内,也可以放在vue实例template属性中-->
<script>
//vue实例
new Vue({
el: '#app',
template: '<h1>hello,{{name}}</h1>',
data: {
name: 'mingjie'
}
})
</script>
Vue实例的生命周期函数(面试题*)
生命周期函数就是vue实例在某一个时间点会自动执行的函数,用户可以在不同阶段添加自己的代码。
vue实例从开始创建、初始化数据、编译模板、挂载 DOM、数据变化时更新 DOM 、销毁等一系列过程,称之为 Vue 的生命周期。
创建前后:
beforeCreate: 创建实例之前的钩子,vue实例的挂载元素$el和数据对象data都为undefined,也就是说数据没有初始化,DOM也没生成。
created:实例创建完成后的钩子,数据对象data也完成了初始化,但是dom还没生成,挂载属性$el不存在
载入前后:
beforeMount :编译模板将编译完成的html挂载到对应的虚拟dom时的钩子,但是此时页面还没有渲染出数据
mounted:编译好的html挂载到页面完成后所执行的事件钩子函数。此时页面上已经渲染出了数据。
更新前后
beforeUpdate:(更新前) 当数据发生变化,更新渲染视图之前的钩子函数。
updated:(更新后) 在由于数据更改导致的更新渲染视图之后的钩子函数。
销毁前后:
beforeDestroy: 在实例销毁之前调用。实例仍然完全可用。
detroyed: 在实例销毁之后调用。此时该实例与其他实例的关联已经被清除,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
当使用keep-alive时,会多一个生命周期函数activated:当页面重新被显示时被执行。
题目:vue 项目获取数据(用axios 发送ajax请求获取数据) 是要在created中比较好还是在mounted中?
建议放在created里。
created:实例创建完成后的钩子,此时数据完成了初始化,但dom还未生成,挂载属性$el不存在,页面上还未渲染数据。
mounted:编译好的html挂载到页面完成后所执行的事件钩子函数。此时页面上已经渲染出了数据。
如果在mounted钩子函数中请求数据可能导致页面闪屏问题,其实就是加载时机问题,放在created里会比mounted触发早一点,如果在页面挂载完之前请求完成的话就不会看到闪屏了。
2.指令
v-text,v-html
使用v-html之后,会覆盖子元素,有xss风险!!!
<div id="app">
<!--<h1>{{name}}</h1>--> hello,mingjie
<!--<h1 v-text="name"></h1>--> hello,mingjie
<h1 v-html="name1"></h1> 斜体
//v-text和v-html的内容都是一个变量,指向元素的内容
</div>
<script>
new Vue({
el: '#app',
data: {
name: 'hello,mingjie',
name1:'<i>斜体<i>'
}
})
</script>
v-on(@)
绑定事件
-
给一个元素同时绑定多个事件
<button v-on="{mouseenter: onEnter,mouseleave: onOut}">点我</button><!--mouseenter表示事件名称,onEnter是当事件发生的时候执行的方法-->
-
event事件传参
1.event.target
指向引起触发事件的元素,而
event.currentTarget则是事件绑定的元素,只有被点击的那个目标元素的
event.target才会等于
event.currentTarget。2.如果事件没有参数,那么event会自动传过去;如果事件传了自定义参数,那么event需要使用$event传过去
<div @click="handleClick1()"></div> <div @click="handleClick2(2, $event)"></div> methods: { handleClick1(event){ console.log(event) console.log(event.currentTarget) // 1. event是原生的Event对象 // 2. 事件被绑定到了当前元素 }, handleClick2(val, event){ console.log(val, event) } }
-
事件修饰符
//submit事件要禁止掉浏览器的默认方式,提交事件不再重载页面。 @submit.prevent="onSubmit" //阻止单击事件继续冒泡传播。 @click.stop = "" //添加事件监听器时使用事件捕获模式,即内部元素触发的事件先在此处理,然后再交由内部元素处理 @click.capture="" // 只当在event.target是当前元素自身时触发处理函数,即事件不是从内部元素触发的 @click.capture="doThis" //点击事件将只会触发一次 @click.once=""
-
按键修饰符
//只有在key是回车键时触发onKeyenter方法。 <form v-on:keyup.enter="onKeyenter"> <input type="text"> <button type="submit">提交</button> </form>
v-bind(:)
绑定元素属性
<a :href="url" :class="'btn ' + klass">点我</a>
data: {
url: 'http://www.baidu.com',
klass: 'btn-primary',
}
绑定样式
-
class的对象绑定
<style> .active { color: red; } </style> <a :class="{active:isActive}">点我</a> <!--active:表示添加的类,isActive:表示什么情况下添加 --> data(){ return { isActive: true, } }
-
class的数组绑定
<div :class="[activeClass,errorClass]"></div> //渲染为: <div class="active text-danger"></div> data() { return { activeClass: 'active', errorClass: 'text-danger' } }
-
内联样式的对象绑定
<div id="app"> <div :style="styleObj">hello world</div> </div> data() { return { styleObj: { color: "red", fontSize: '16px' } } },
v-model(表单渲染)
双向数据绑定:vue实例中的data变了会使视图层的dom变化,视图层的dom变化也会使数据层变化。
v-model用于的元素
-
- input
<div> <input type="text" v-model="name"> <span>你的名字是:{{name}}</span> </div> data() { return { name: 'mingjie', } }
-
- textarea
<textarea v-model="article" cols="30" rows="10"></textarea> data(){ return { article: `refs erfs ef aefew ef ahf adsfjh asdf hakd jfh aks fcsaer acd` } }
-
-
select
提交的是value
<div>你要到哪儿去</div> <select v-model="dest" multiple> //multiple表示可多选。 <option value="1">深圳</option> <option value="2">广州</option> <option value="3">郑州</option> </select> {{dest}} data(){ return { dest: [] } }
-
-
4.radio
提交的是value
<label> 男 <input v-model="gender" type="radio" value="male"> </label> <label> 女 <input v-model="gender" type="radio" value="female"> </label> {{gender}} data(){ return { gender: 'female', } }
-
5.checkbox
提交的是value
<label> JACK <input v-model="gender" type="checkbox" value="JACK"> </label> <label> MIKE <input v-model="gender" type="checkbox" value="MIKE"> </label> <label> Michilea <input v-model="gender" type="checkbox" value="michilea"> </label> {{gender}} data(){ return { gender: ['JACK'], } }
修饰符
v-model.lazy: 惰性更新,blur时才更新。其实是触发了change事件,change事件在不同的表单组件表现不同。
v-model.trim: 把用户输入后的值前后的空格去掉。
v-model.number: 用于价钱/年龄等,把填入的10转化为数字类型10.
v-if
<div id="app">
<div v-if="show">
用户名: <input key="username"/> //key是为了防止复用。
</div>
<div v-else>
邮箱: <input key="email"/>
</div>
<!--<div v-show="show">hello world</div>-->
</div>
<script>
new Vue({
el: '#app',
data: {
show: true,
},
})
</script>
v-if和v-show的区别(面试题)
v-if是通过控制dom节点的存在与否来控制元素的显示与隐藏;v-show是通过设置DOM元素的display样式,block为显示,none为隐藏;
性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
v-for
循环渲染
key的重要性:唯一的key值可以提高循环性能,必须有key值,但key值不能是random和index,必须是和业务相关的id。
v-if和v-for不能在同一个元素中一起使用:因为v-for的优先级比v-if高,那么每次循环都要判断一次,一般将v-if放在v-for的父元素上。
数组的渲染
<ul>
<li v-for="(item,index) in foodList" :key="item.id">
</li>
</ul>
对象的渲染
<div id="app">
<div v-for="(value,key,index) in userInfo" :key="key">
</div>
</div>
3.计算属性computed
computed有缓存,依赖的data不变则不会重新计算。
<div id="app">
<p>{{firstname}}<p>
<p>{{lastname}}</p>
<p>{{fullname1}}</p>
<input type="text" v-model="fullname2"></div>
</div>
<script>
var vm =new Vue({
el: '#app',
data: {
firstname: 'dell',
lastname: 'wang'
},
computed:{
fullname1 (){
return this.firstname+' '+this.lastname;
},
// 因为v-model绑定的是fullname2,双向数据绑定,所以必须有get和set方法。
fullName2: {
get (){
return this.firstName + ' ' + this.lastName;
},
//computed属性不仅可以写get方法,通过其他的值算出一个新值,
// 还可以写set值,通过设置一个值去改变它相关联的值,进而引起重新计算。
set (value) {
var arr = value.split(" ");
this.firstName = arr[0];
this.lastName = arr[1];//computed属性在当它依赖的值发生变化时,就会重新计算。
},
}
},
})
>vm.fullname = "mike wang"
mike wang
4.侦听器watch
侦听器的作用: 监听某个数据的变化,一旦数据变化,就会执行侦听器中的业务逻辑。
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
引用类型需要监听对象中的对象,所以需做到深度监听,但引用类型的深度监听也是拿不到oldVal值。
<div id="app">
<div>{{count}}</div>
</div>
<script>
new Vue({
el: '#app',
data: {
count: 0,
info:{
infoo:{
city: '北京'
}
}
},
watch: {
count(newVal,oldVal){
console.log(newVal,oldVal)// 值类型,可正常拿到oldVal
},
// 实现引用类型的深度监听
info: {
handler(newVal,oldVal){
console.log(newVal,oldVal)//引用类型,拿不到oldVal。因为指针相同,此时已经指向了新的val。
},
deep: true
}
}
})
</script>
组件
组件间的通信(面试题*)
在vue的根实例中使用vue组件,vue的根实例相当于父组件,被使用的组件相当于子组件。
父子通信
父组件通过属性的方式向子组件传递参数。
- 父组件通过属性来向子组件传递数据,子组件通过props属性接收父组件的数据。
- vue是单数据流,子组件不能修改父组件传过来的数据。(子组件可以先拷贝,再修改。)
<div id="app">
//父组件通过属性传递参数。
<blog-post v-for="post in posts" :key="post.id" :title="post.title"></blog-post>
</div>
props: {
title: {
// 参数校验
type: String,
default: 'default value',
validator: function (value) {
return (value.length > 5)
}
}
},
子组件通过事件触发的形式向父组件传递数据。
-
子组件通过$emit向外触发事件,并且传递值,父组件监听子组件触发的这个事件并处理。
-
组件上绑定原生事件
在事件后加修饰符.native。
<todo-item v-for="(item,index) in list" @click.native="handleClick"></todo-item>
非父子组件间通信
-
通过自定义事件的形式。(发布订阅模式或者观察者模式)
eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。
var bus = new Vue();//在全局作用域下使用一个空的Vue实例作为中央事件总线(bus)。 bus.$emit('change',this.self_content); //一个组件通过bus.$emit向外触发事件 bus.$on('change',function(msg){ that.self_content = msg; })//在另一个组件mounted的钩子函数中通过bus.$on监听来自bus实例的事件,做出处理
-
使用工具vuex
组件使用注意
-
data定义**(面试题*)**
组件中的data和vue根实例中的data不一样,它不能是一个对象,而应该是一个函数。
因为组件和根实例不同,每一个组件都是可多次复用的
vue
实例,组件不管被复用了多少次,组件中的data
数据都应该是相互隔离,互不影响的。组件中的
data
写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data
,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据,这样每个组件实例化的对象就会有所区别。而如果写成对象形式,会使得所有组件实例共用一份data
,引用类型造成一个变了全都会变的结果。data(){ return { ... } } //data是一个函数,他的返回值是对应的数据。
-
reference
在vue中操作dom,通过ref获取dom对象。
<div id="app"> <counter ref="one" @change="handleChange"> </counter> <counter ref="two" @change="handleChange"> </counter> <div>{{total}}</div> </div> methods: { handleChange: function(){ console.log(this.$refs.two); } }
-
vue组件样式穿透
在vue开发过程中,可能会遇到修改element ui组件样式的时候,无效的问题,在网页检查页面元素的时候发现自己写的样式不生效,原因是因为中scoped的问题导致,所以我们需要用到样式穿透:
stylus:使用>>>
外层类 >>> 想要修改的类名 { 要修改的样式 }
sass和less:/deep/
外层类 /deep/ 想要修改的类名 { 要修改的样式 }
通用:::deep
::v-deep 想要修改的类名 { 要修改的样式 }
vue高级特性(*****)
1. 自定义v-model的实现
如颜色选择器的实现
<p>{{name}}</p>
<!-- <input v-model="name"> -->
<!-- 自定义v-model -->
<custom-v-model v-model="name"/>
<template>
<input type="text" :value="text1" @input="$emit('change', $event.target.value)">
<!-- 1. 上面的input使用了 :value,而不是v-model
2. 上面的change和model.event要对应起来。
3. text1属性对应起来。
-->
</template>
<script>
export default {
name: 'CustomVModel',
model: {
prop: 'text1', // 对应props text
event: 'change'
},
props: {
text1: String,
default(){
return ''
}
}
}
</script>
2. $nextTick(面试题*)
- vue是异步渲染
- 异步渲染:data改变后,DOM不会立刻渲染,且页面渲染时会将data的多次修改做整合,多次data修改只会渲染一次。
- n e x t T i c k : 在 异 步 渲 染 中 , nextTick:在异步渲染中, nextTick:在异步渲染中,nextTick会在DOM渲染之后被触发,以获取最新的DOM节点
methods: {
addItem(){
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// const ulElem = this.$refs.ul1
// console.log(ulElem.childNodes.length) // 拿不到最新的dom节点。
// 异步渲染,$nextTick待DOM渲染完再回调,这样就可以拿到最新的dom节点
this.$nextTick(() => {
const ulElem = this.$refs.ul1
console.log(ulElem.childNodes.length)
})
}
}
3. slot插槽(****)
作用:父组件往子组件中插一些东西,如dom节点。
基本使用
<!-- 父组件 -->
<slot-demo :url="website.url">
<!-- 父组件向子组件中插入的内容 -->
{{website.title}}
</slot-demo>
<!-- SlotDemo组件 -->
<template>
<a :href="url">
<!-- 子组件中slot的书写,父组件插入的内容子组件通过slot标签接收 -->
<slot>
默认内容(即父组件没设置内容时显示)
</slot>
</a>
</template>
作用域插槽
有时让父组件中的插槽内容能够访问到子组件中才有的数据是很有用的。
例如,在子组件<current-user>
中:
<span>
<slot>{{ user.lastName }}</slot>
</span>
我们可能想在父组件的插槽内容中换掉备用内容,用名而非姓来显示。如下:
<current-user>
{{ user.firstName }}
</current-user>
然而上述代码不会正常工作,因为只有<current-user>
组件可以访问到 user,而我们提供的内容是在父级渲染的。
为了让 user 在父级组件的插槽内容中可用,我们可以将 user 作为<slot>
元素的一个 attribute 绑定上去:
<!-- 子组件current-user -->
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
绑定在 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps。
具名插槽
<!--父组件 -->
<named-slot>
<template v-slot:header>
<h1>将插入到header slot中</h1>
</template>
<p>将插入到main slot中,即未命名的slot</p>
<template v-slot:footer>
<h1>将插入到footer slot中</h1>
</template>
</named-slot>
<!--NamedSlot组件 -->
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
4. 动态组件
用法
适用于需要根据数据去动态渲染的场景,即组件类型不确定的场景。
<div v-for="(value, key) in newsData" :key="key">
<component :is="value.type"/>
</div>
data (){
return {
newsData: {
1: {
type: 'text'
},
2: {
type: 'text'
},
3: {
type: 'img'
},
}
}
}
5. 异步加载组件
import()函数 按需加载,异步加载大组件
<template>
<div>
<p>{{name}}</p>
<custom-v-model v-model="name"/>
<!-- 异步组件 -->
<form-demo v-if="showFormDemo"></form-demo>
<button @click="showFormDemo = true">show form demo</button>
</div>
</template>
<script>
// 同步加载组件,首页组件全部打包
import CustomVModel from './CustomVModel.vue'
export default {
name: 'AdvanceUse',
components: {
CustomVModel,
FormDemo: () => import('./FormDemo') // 异步加载组件,按需加载,优化首页性能
},
data (){
return {
name: '爽约',
showFormDemo: false,
}
}
}
</script>
6. keep-alive
缓存组件
适用于频繁切换,不需要重复渲染的场景,如tab切换。
7. mixin
适用于多个组件有相同的逻辑时需要抽离出来的场景。
mixin并不是完美的解决方法,vue3.0提出的Composition API旨在解决这些问题。
<script>
import myMixin from './mixin.js'
export default {
mixins: [myMixin], //可以添加多个,会自动合并起来
}
</script>
<!--mixin.js -->
export default {
data() {
return {
city: '北京'
}
},
mounted() {
console.log('mounted')
},
methods: {
handleClick(){
console.log(this.name)
}
}
}
动画
CSS动画
过渡动画效果
利用css3中的transition以及vue自动为元素添加删除类来实现过渡效果。
<style>
.fade-enter {opacity: 0}
.fade-enter-active {transition: opacity 3s;}
.fade-leave-to {opacity: 0}
.fade-leave-active {transition: opacity 2s;}
</style>
<div id="app">
<transition name="fade"> <!--transition中包裹的元素有动画效果-->
<div v-if="show">hello world</div>
</transition>
<button @click="handleClick">切换</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
show: true,
},
methods: {
handleClick: function(){
this.show = !this.show;
}
}
})
</script>
使用animate.css库(keyfrom动画效果)
<link rel="stylesheet" href="./animate.css">
<transition name="fade"
appear
enter-active-class="animated swing" <!--入场动画-->
leave-active-class="animated shake" <!--出场动画-->
appear-active-class="animated swing"> <!--一开始刷新就有的动画-->
<div v-if="show">hello world</div>
</transition>
同时使用过渡和动画效果
<link rel="stylesheet" href="./animate.css">
<style>
.fade-enter {opacity: 0}
.fade-enter-active {transition: opacity 3s;}
.fade-leave-to {opacity: 0}
.fade-leave-active {transition: opacity 2s;}
</style>
<div id="app">
<transition
//type="transition"//既有transition动画,又有keyfrom动画,以transition动画时长为准。
//:duration="10000" //自定义时长
:duration="{enter: 5000,leave: 3000}" //自定义时长,包括入场和出场
name="fade"
appear
enter-active-class="animated swing fade-enter-active"
leave-active-class="animated shake fade-leave-active"
appear-active-class="animated swing">
<div v-if="show">hello world</div>
</transition>
JS动画
Vue中使用js的钩子来实现js动画效果
使用velocity.js动画库实现js动画效果
<div id="app">
<transition
name="fade"
@before-enter="handleBeforeEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter"
>
<!--在元素显示之前通过@before-enter触发handleBeforeEnter事件,并将控制的div元素作为参数传递进去-->
<div v-if="show">hello world</div>
</transition>
<button @click="handleClick">切换</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
show: true,
},
methods: {
handleClick: function(){
this.show = !this.show;
},
handleBeforeEnter: function (el) {
el.style.opacity = 0;
},
handleEnter: function (el, done) {
Velocity(el, {opacity: 1}, {duration: 1000,complete:done})
//动画执行完之后自动触发complete,表示这个函数执行结束。元素入场完毕。
},
handleAfterEnter: function (el) {
alert('动画结束');
}
}
})
</script>
Vue中多个元素或组件之间的过渡动画
<div id="app">
<transition name="fade" mode="out-in"> <!--先隐藏再显示-->
<component :is="type"></component>
<!--<div v-if="show" key="hello">hello world</div>
<div v-else key="bye">Bye world</div>-->
</transition>
<button @click="handleClick">切换</button>
</div>
<script>
Vue.component('child',{
template: '<div>child</div>'
});
Vue.component('child-one',{
template: '<div>child-one</div>'
});
var vm = new Vue({
el: '#app',
data: {
/* show: true,*/ type: 'child',
},
methods: {
handleClick: function(){
/*this.show = !this.show;*/
this.type = this.type === 'child'? 'child-one':'child';
},
}
})
</script>
列表的过渡动画
类似与单个元素的过渡动画
<div id="app">
<transition-group>
<div v-for="(item, index) of list" :key="item.id">
{{item.title}}--{{item.id}}
</div>
</transition-group>
<button @click="handleClick">add</button>
</div>
动画封装
<div id="app">
<fade :show="show">
<div>hello world</div>
</fade>
<fade :show="show">
<h1>bye world</h1>
</fade>
<button @click="handleClick">切换</button>
</div>
<script>
//将全部的动画封装在这个组件中。
Vue.component('fade',{
props: ['show'],
template: `<transition
name="fade"
@before-enter="handleBeforeEnter"
@enter="handleEnter">
<slot v-if="show"></slot>
</transition>`,
methods: {
handleBeforeEnter: function (el) {
el.style.color = 'red';
},
handleEnter: function (el, done) {
setTimeout(()=>{
el.style.color = 'green'
},3000);
done();
},
}
})
var vm = new Vue({
el: '#app',
data: {
show: true,
},
methods: {
handleClick: function(){
this.show = !this.show;
}
}
})
</script>