在如今的前端行业,选择一门框架进行开发不仅仅能大幅提升开发效率而且可以更好的使得项目方便管理,维护,而前端框架中主流的则分为:React,Angular和Vue,其中Vue在这两年发展的非常快,据2019年的统计,前端工具使用中,仅次于React和Jquery,因为为了更好的学习Vue,特此作为学习记录,本文基于vue2,因此文中的信息不涉及vue3的内容,vue3也是基于2的版本上进行的更迭,使用上不会有大的变动。
官网的解释是:一套用于构建用户界面,以数据驱动的渐进式框架,我理解的渐进式的概念是:没有强制性,需要做什么,就加什么,比如仅仅需要一个类似于jq一样的js库,那么只需要把这部分加进来就可以了,如果需要控制路由,那么把路由加进来就可以了,如果又需要同步数据,那么只需要加vuex就可以了;
而且,相对于react和angular,vue的学习曲线最平滑,学习的难度最渐进,没有一下子难度跨度过大的部分,这部分设计也是相当友好的;
和传统开发相比,vue完全颠覆了开发模式,视图的变化只跟数据挂钩,数据有变化,那么对应的dom就产生变化,这种方式和jQuery有本质上的区别,jq是拿到数据后,手动获取DOM并进行操作,而vue是响应式的,只要数据变化,那么视图也将发生变化;
举个例子:假如现在有个题目,需要在指定dom下创建一个具有xxx内容的div;
jQuery:
创建了一个div,之后从服务器获取div内容,将内容插入到div中,之后将div再插入到指定div里,这里DOM的创建,操作,插入,均需要手动完成,大致代码如下
let btn = '';
$('#div').append(btn)
$('#div1').html('你好');
Vue:
在vue中,将视图与数据分离,视图的显示与否取决于是否有数据,比如,预先在html的部分设定好div,div的显示与不显示则根据数据有还是没有,至于dom的创建还是不创建则完全交给vue处理,大致代码如下
<template>
<div v-if=''>你好</div>
</template>
两种方式:一种类似于jq,直接在html中通过
//开发环境
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
//生产环境
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
另一种,通过vue-cli脚手架安装,这种方式适合整个项目都使用vue开发时使用,在脚手架中可以安装配套的一些功能,比如vuex,router甚至是css预处理器等等;
当然做项目的话推荐vue-cli这种方式安装使用,因为做项目其实很难避免掉不使用vue的全家桶,具体这种方式的按钮就不介绍了,网上具体安装步骤的教程一堆,下例简单的说一下;
vue-cli的安装是依赖于node.js的,因此必须先安装node,在官网下载并安装node.js,安装好之后,打开“终端”,
进入官网,找到“命令行工具”根据提示安装,或者直接输入以下:
sudo npm install --global vue-cli //进行安装脚手架,mac系统
//--global和-g都是全局配置,代表在全局安装vue-cli,否则只会在当前文件夹内安装
//卸载vue-cli
sudo npm uninstall vue-cli -g
安装完之后,输入命令vue --version,可以查看当前vue的版本号
注意,为了方便管理新建的项目最好cd到指定的目录下,不然新建的项目默认会创建在当前目录
cd 指定初始化的项目文件夹路径
vue init webpack 项目名
初始化成功后
cd 安装的目录
//启动项目
npm run dev
在vue-cli 3.x之后的版本,修改了创建的方式
vue create 项目名
//之后跟2.x的版本一样需要选择一些安装以及配置
npm run serve //运行项目
npm run build //回车,自动进行项目的打包
很多时候不需要从头开始创建项目,公司都是维护已有的项目,在已有的项目上添加新功能:
//进入项目的根目录,或者说进入有package.json的目录
cd 路径
//执行安装
npm install
最常见的方式就是“双大括号”,比如:
//msg是在vue中设定的变量名
<span>{{msg}}span>
另外在双大括号中页支持单表达式
{{number+1}}
{{number===1? 'Yes':'No'}}
{{msg.splite('').reverse().join('')}}
//如果是复杂流程控制则不会生效,比如
{{ if (ok) { return message } }}
vue中有很多内置的指令,这些指令当作属性放置在html中的DOM元素上,不同的指令会将不同的结果作用到绑定的DOM上
给指定元素绑定属性,也可以通过缩写,:绑定,比如
<a v-bind:href='url'>链接a>
//缩写
<a :href='url'>链接a>
意思是:给a标签动态绑定了一个href属性,属性的值是一个变量url,而url的定义则是在vue中定义的,每次url值的变化都是响应式的动态的作用到a标签上,如果url的值是1,那么点击就会链接到1,如果动态改变了url的值为2,那么此时点击就会链接到2,链接的不再是1;
任何属性都可以改为通过v-bind绑定, 其作用是将该属性的值由静态变为动态,值不再是一个具体的字符串,或者布尔属性等具体值,而变成了一个变量,变量的定义则在vue中定义,这样的结果就会灵活很多,值可以是在不同条件下响应式的生成对应的值,不需要每一次都通过:获取DOM->操作DOM属性这种方式修改;
在属性绑定中有几个特殊绑定,比如class,其值为一个对象,对象的键名为类名,值是一个布尔值,其作用是如果值是false,那么这个类名将不会被渲染到DOM上,如果是true,那么这个类名将被添加到class中,比如
//active是写在css中的类名,值isActive是一个布尔值
<div v-bind:class="{ active: isActive }" class='title'></div>
如果isActive是false,那么最终在渲染在DOM中的结果是
<div class='title'></div>
如果isActive是true,那么最终在渲染在DOM中的结果是
<div class='title active'></div>
另外,绑定的值不一定要写在dom中,也可以是一个参数,但这个参数的结构必须遵守规则,例如
<div v-bind:class="classObj" class='title'></div>
这个classObj可以是定义在data中的对象,也可以是计算属性返回的结果等,但这个值的格式必须是一个对象或者数组,如果是对象,对象名必须是类名,值必须是一个布尔值,如果是数组,那么数组的每一项都必须是类名,而此时的类名的值会默认为true,也就代表所有的类名都会被渲染到dom上,除此之外,如果这个值的类型不是规定的格式,那么这个值将不会被vue识别;
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
//最终渲染结果
<div class="active text-danger"></div>
或者
<div v-bind:class="classObject"></div>
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
或者和v-bind:class类似
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
给指定元素绑定事件,绑定后如果在DOM上触发了绑定事件,那么将执行对应的代码,绑定也可以通过缩写的方式绑定,缩写为@,比如:
<div v-on:click='event()'>点击div>
//缩写
<div @click='event()'>点击div>
意思是:v-on是指令,代表给给div绑定了一个事件,冒号后面跟随着的是js中原生的具体事件,比如点击事件click,双击事件dbclick,最后引号内的是触发事件后执行的函数;
在JS中,有一些默认事件,比如在函数中调用event.preventDefault()阻止默认事件,和event.stopPropagation()阻止冒泡,这种需求是非常常见的,为了更好的让方法只执行数据逻辑,而不是去处理DOM,因此vue提供了一些事件修饰符来解决,通过修饰符进一步对事件进行约束;
在事件类型后面加点开头的修饰作为执行该事件修饰符,具体使用比如:给div绑定了一个点击事件,但是这个点击事件要阻止冒泡,可以这么些
<div v-on:click.stop='doSomething'>div>
v-on:,这个执行绑定指令,代表要给div绑定一个事件;
click,绑定的这个事件具体是一个单击事件;
.stop,给click这个单击事件添加了一个事件修饰符,代表这个单击事件停止冒泡到更上层DOM;
doSomething,单击后执行的函数名叫做doSomething;
具体事件修饰符如下
<a v-on:click.stop="doThis">a>
<form v-on:submit.prevent="onSubmit">form>
<a v-on:click.stop.prevent="doThat">a>
<form v-on:submit.prevent>form>
<div v-on:click.capture="doThis">...div>
<div v-on:click.self="doThat">...div>
<a v-on:click.once="doThis">a>
<div v-on:scroll.passive="onScroll">...div>
和事件修饰符同理,v-on能绑定的不仅仅是click这种点击事件,还包括了对键盘上的键盘进行监听,键盘修饰符则是为键盘事件被触发是增加的一些修饰:
<input v-on:keyup.enter="submit">
<input v-on:keyup.page-down="onPageDown">
<input v-on:keyup.tab="dothis">
<input v-on:keyup.delete="dothis">
<input v-on:keyup.esc="dothis">
<input v-on:keyup.space="dothis">
<input v-on:keyup.up="dothis">
<input v-on:keyup.down="dothis">
<input v-on:keyup.left="dothis">
<input v-on:keyup.right="dothis">
<input v-on:keyup.ctrl="dothis">
<input v-on:keyup.alt="dothis">
<input v-on:keyup.shift="dothis">
<input v-on:keyup.meta="dothis">
另外修饰符是可以连续使用的,比如:
<input v-on:keyup.alt.67="clear">
<div v-on:click.ctrl="doSomething">Do somethingdiv>
上面的keyup事件必须是alt和c键同时松开时才会触发clear函数
v-if指令用户条件性的将绑定DOM渲染出来,比如
//只有当awesome的值为true的时候,h1标签才会被渲染
//如果awesome的值为false,那么这个h1将不会被渲染进DOM树
<h1 v-if="awesome">Vue is awesome!</h1>
于js相似,既然有了if,那么必定有else-if和else,所以对应的指令为v-else-if和****v-else
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else-if="type === 'C'">C</div>
<div v-else>Not A/B/C</div>
上例中的v-if例子就是一个最完整的v-if条件渲染,如果type的值是A,那么就会渲染A这个div,之后的B,C,Not A/B/C这个3个div将不会被渲染,会被vue忽略掉,type的值是B,C或其他则是同理,这4个div只会根据条件渲染其中的一个,剩下的3个将不会被渲染进DOM树
使用方式和v-if一致,后面的值是一个布尔值,区别在于v-show如果是false那么就相当于在css中将display属性设置成none,虽然在页面上不可见,但是实际上是存在DOM树里面的,如果值是true则会显示在页面上
//如果ok的值是true,那么h1标签会被显示,否则则会隐藏
<h1 v-show="ok">Hello!h1>
一般来说,v-if 有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
列表渲染,也就是指令v-for,该指令基于一个数组或对象来渲染一个列表,具体例子如下:
假设现在有一个长度为100的数组,数组中的每一项都是一个对象,对象上有一个属性message,其值是一个string类型,现在我们在ul中生成100个li,每个li中的文字对应message的值
使用v-for的写法如下
<ul id="example-1">
<li v-for="(item,index) in items" :key="item.message">
{{ item.message }}
li>
ul>
注意:这边:key是vue为了更好的进行内部排序而绑定的标志,绑定的值的要求是必须本次循环中是唯一的,不能有重复,不然如果一旦li的顺序有所变动,vue在内部就不知道哪个对应哪个
如果遍历的目标是一个对象,那么例子如下:
<ul id="v-for-object" class="demo">
<li v-for="(value, name) in object" :key='name'>
{{ name }}: {{ value }}
li>
ul>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
结果:
遍历对象时,上例中有两个参数,第一个属性也就是value对应对象的值,第二属性name对应对象的键值;
当然,如果遍历对象时,还有第三个参数index,其值是当前遍历对象的索引
双向数据绑定,可以用 v-model 指令在表单 、 及 等元素上创建双向数据绑定。绑定后,如果元素上手动输入会直接影响data中的值,而data中的值发生变化,那么元素上的显示也会发生变化
比如:
<input v-model="message">
<p>Message is: {{ message }}p>
new Vue({
el: '#v-for-object',
data: {
message:''
}
})
现在给input绑定了一个v-model指令,其值是在data中定义的message变量,此时,如果我们在input中输入:你好,那么在data中的message属性的值将同步被更新为“你好”,只会因为p元素中也绑定了message,所以p元素中的message位置也将同步修改为“你好”;
如果,这时有一个按钮,按钮的作用是将触发一个函数,函数会将message的值清空,点击按钮后,message的值被清空了,这是input输入框中的值将会被清空,p元素message的位置也会被清空;
v-model本质上是一个语法糖,在Vue中是通过给dom元素同时绑定v-bind:value和v-on:input事件达到最终的效果的;它在内部对不同的表单组件做了识别,针对不同的表单组件实现不同的方案;
示例:现在自定义了一个输入框组件,其内部包含了一个input和一个label,在其他组件引用后,在组件上绑定了v-model,那么在组件内部的input如何接收v-model上的内容呢?
//自定义组件使用
<input-component v-model='formResult.phone'></input-component>
//组件内部
<template>
<div class='form-input-style' :class='borderShow'>
<label>{{this.label}}</label>
<input @input="$emit('input',$event.target.value)" :value="value">
</div>
</template>
<script>
export default {
props:{
value:String
}
};
function isBoolean(val){
return Object.prototype.toString.call(val) === '[object Boolean]';
}
</script>
<style scoped lang='scss'>
</style>
**
在这两个元素上v-model的用法差不多,就是和上面的那个例子一样,其绑定后,在元素内的输入会同步修改data中的属性值,而data中的属性值一旦发生便会,也会同步修改元素中输入框内的值,这是一个双向的影响
单个复选框,绑定到布尔值:
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}label>
那么选中时,值为true,未选中时,值为false;
多个复选框,绑定到同一个数组:
<div id='example-3'>
<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>
new Vue({
el: '#example-3',
data: {
checkedNames: []
}
})
那么,选中的复选框的value值将被加入数组,未选中时其value值将被从数组移除;
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">Onelabel>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Twolabel>
<br>
<span>Picked: {{ picked }}span>
div>
new Vue({
el: '#example-4',
data: {
picked: ''
}
})
v-model将会把选中的单选框的value值赋值给picked,换句话说picked的值就是当前被选中的单选框的value值
单选时和单选按钮相似,其绑定的变量的值就是当前选择框选中的value值;多选时绑定到一个数组和复选框相似,其数组内的值就是当前全部被选中的项的value值
首先明确一点,计算属性是一个属性,而不是方法,因此调用的时候不能有括号,在vue中存在一个函数,叫做computed,也叫做计算属性,这个属性专门用于复杂逻辑的处理,比如,从服务器上获取了一堆属性值,这堆值没办法直接使用,需要通过一系列的处理才能使用(具体例子如:请求得到的数据中用户的名字,联系方式,地址,邮箱都是不同的键值对,而在html里需要合并成一个完整的字符串显示),那么就可以在计算属性中处理完了,在放入html中
另外,当计算属性中的某个值发生变化时,计算属性的结果将重新计算,并且页面将被重新渲染,比如现在定一个一个计算属性name,而这个name中有两个值:姓和名,一旦其中某一个发生了变化,那么计算属性name将会被重新执行,然后所有使用到name的地方也将被连带这重新执行渲染
注意:如果某个计算属性在页面中没有被使用到,那么此时计算属性的变更并不会引起页面的渲染
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
例子中,reversedMessage的值依赖于message,那么当message的值发生变化时,计算属性reversedMessage将会重新计算执行;
计算属性是有缓存的,也就是说,如果其中的值并没有发生变化,那么即使多次被访问,其值并不会重新运行计算,它会将之前计算好的结果给返回出去,只有在其值发生变化的时候,才会重新运行计算结果;
因此注意,下例这个值是不会更新的,其值就是首次创建时返回的值
computed: {
now: function () {
return Date.now()
}
}
侦听属性,watch,该属性用于监测某些属性,比如需要监测某个值是否发生了变化,因为一旦某个值发生了变化,那么带来的结果就会有一系列的响应,侦听属性的函数名,就是属性名,其函数体内的方法就是一旦侦听的属性发生了变化,那么函数体内的代码就会执行;
ar vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
//这里的firstName,就是data中的firstyName,代表侦听的属性就是firstyName
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
//这里的lastName,就是data中的lastName,代表侦听的属性就是lastName
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
这里,watch中里设定了监听了data中的两个属性:firstName和lastName,一旦data中的fistName或者lastName的值发生变化,那么对应在watch中的对应的方法就会被执行;
到这里,是不是觉得计算属性computed和侦听属性watch有点像,区别在于:
另外侦听属性很容易被滥用,比如上例,其实可以用计算属性重写
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
每个Vue实例在被创建的过程中都会经过一系列的初始化过程,在这些过程中Vue会进行数据监听,编译模版,实例挂载到DOM等等一系列操作,在这个过程中,Vue为用户在不同阶段都预留了一个函数接口,这些接口函数会在不同的阶段被执行,这些接口也叫做生命周期钩子函数;
注意:钩子函数可以用es6的写法,也可以用es5的写法,但是****唯独不可以用箭头函数,因为生命周期的钩子函数中的this指向的是vue的实例,而箭头函数是没有自己的this的;
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
//最终会打印:a is 1
该阶段运行在实例创建前的状态 ,**此时的el和data尚未被创建,因此值都是undefined;**这个阶段可以除了整个系统的Loading
el就是我们挂载的根节点元素,比如下例,创建的vue实例就是挂载在id为app的DOM元素上的,当然也可以将#app改为.app,那么就代表挂载的对象是类名为app的DOM元素;
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
该阶段在实例被创建后就立即调用,此时的data中已经有数据了,并且可以运行方法,使用计算数据和侦听属性之类的了,但是该阶段挂载还没有开始,因此el是undefined;
通常在这个阶段,通过axios等将数据请求下来,存储至vuex或者data中;
该阶段在实例被挂载开始前调用;不过通常该阶段能做的事在created()中也能做;这个钩子函数其实不常用;
该阶段在实例挂载后被调用,这是el被创建的(虚拟DOM)替换了,注意的是mounted不会保证所有的自组件也都被一起挂载,如果你希望等到整个视图都渲染完毕,可以在mounted的内部调用vm.$nextTick
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
在mounted()之前,页面上所有的数据都是通过占位符占位的,而mounnted之后占位符的内容发生更新,数据被填充到页面上;
该钩子函数会在页面上数据更新前调用,在这个阶段适合在更新前访问现有的DOM;
该钩子函数会在页面上数据更新之后调用,这个阶段DOM已经更新,适合处理依赖新DOM的操作,然而在绝大多数情况下,根据DOM或者说根据数据需要改变现有状态,通常使用计算属性或者侦听属性比较好;
同样,updated不会保证所所有的子组件页都被一起重绘,如果希望等到所有的视图都重绘完毕,那么可以在updataed里使用vm.$nextTick
updated: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been re-rendered
})
}
实例销毁前调用,在这一步,实例仍然完全可用,可以在这个钩子函数内清理事件,计时器或者取消订阅操作等;
实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
比如现在在该组件上有一个定时器,希望离开组件时定时器被清除,那么就可以在beforeDestroy()或者destroyed()这两个钩子函数中的一个设置清除定时器,因为当执行beforeDestroy()或者destroyed()这个两个钩子函数时,就代表着离开当前组件了;
组件是Vue中最为强大的功能之一,它可以将HTML元素进行封装重用,这样整个页面其实就被组件化了,页面上类似的元素部分可以进行剥离,然后组件化,这样既可以减少代码量,也方便对相同的部分进行统一管理,一旦需要修改那么只需要修改一处便可以将多处进行同时修改;
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: ''
})
上例是在Vue实例上添加(也可以说是注册)了一个名为button-counter的组件,注册之后,那么在Vue上的任意地方可以进行任意次数的使用,并且因为data是通过函数返回的形式定义的数据,因此多个组件之间的数据并不相互影响,而且每个组件都有自己的data,computed,watch,methods以及生命周期,具体使用例子如下:
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
通过template标签创建,比如
<template id="myCom2">
<div>这是template构建的组件div>
template>
注意:所有元素都必须包含在一个元素中,或者说组件的根组件有且只有有个;
通过上例,相信对组件有了一个简单的了解,组件的注册分为两种:全局注册和局部注册;
这两者的区别在于,全局组件一旦注册,那么就可以在根实例下任意一个地方使用,但是这种注册往往是不够理想的,设想下,如果你全局注册了一个组件,在通过像webpack这样的构建系统时,即使你全局注册的组件没有被使用到,webpack仍然会将组件进行打包,这样最终的打包结果代码量就会无意义的增大;
局部注册则不同,当制作完局部组件之后,那么如果哪个组件需要使用,就通过components进行局部注册,这样组件可以在当前组件中被使用了;
通过Vue.component()这个方法给全局注册一个组件
Vue.component('my-component-name', { /* ... */ })
第一个参数是一个字符串,代表注册组件的名字,第二个参数是组件代码;
全局组件通常只用来注册一些非常常用的基础组件,比如:自定义的按钮,自定义的输入框,自定义的icon等等;
局部注册则是需要在当前组件中注册
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
}
}
首先通过import将组件引入,之后在当前的components中注册,这样注册后就可以在当前组件中使用了;
通过vue-cli搭建的项目,更多的使用的是局部注册的方式引入组件,使用组件;
因为HTML对大小写不敏感,因此强烈建议组件的命名遵循W3C的规范,也就是:字母全部小写且必须包含一个连字符;
可以这样注册
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
}
}
但是使用的适合则写成
<component-a>component-a>
父组件上定义一个属性,子组件通过prop接收属性,具体如下
父组件示例
//静态属性
<component-a name='oliver'></component-a>
如果是静态属性,那么直接在组件上写上属性名和属性值,但实际情况除了UI库,很少会有静态属性直接写在组件上,更多是动态值传递给子组件,这个时候就用到了v-bind指令
//动态属性
//:name是v-bind:name的缩写
<component-a :name='username'></component-a>
通过v-bind指令给name绑定了一个变量username
注意:假如组件上只有绑定了属性,没有绑定属性值,比如下例,只有绑定了name,没有为name绑定任何值,那么在子组件中获得的值默认为boolean类型,且值为true
<component-a name></component-a>
子组件示例
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
}
}
}
在子组件中通过一个对象props接收父组件传递过来的属性,该对方的键名就是父组件上的属性名
值的注意的是,值的传递是单向数据流,也就是说,父组件值的变化会实时更新到子组件的props中,但是子组件的变化不会影响到父组件,这意味着不应该在子组件去修改props中的值,如果在子组件中需要监听props的值,并且当值改变时需要有一系列的操作,那么这是应该使用计算属性等方式重新计算组件,例如下面两种示例
1.props传递可一个初始值,子组件希望将这个值保存到本地
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
2.props传递了一个原始值,子组件希望通过这个原始值计算出了一个新的值供组件使用
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
在子组件中,以某些事件为契机,触发后通过**$emit()**方法将消息和数据传递到父组件,在父组件中,通过v-on将事件接收,比如
子组件
<button v-on:click='updata'>传递</button>
methods:{
updata(){
this.$emit('msg',data)
}
}
在子组件中有一个点击事件,点击后,出发了$emit(),第一个参数是定义的名字,父组件中通过这个名字接收数据,第二个参数就是需要传递的数据;
父组件
<component-a v-on:msg='getData'></component-a>
methods:{
getData(msg){
console.log(msg)
}
}
getData中的形参msg就是子组件传递上来的数据data;
通过标签动态加载,动态组件可以更大的帮助用户去复用代码,而不是重新构造
<component v-bind:is='currentComponent'>component>
computed:{
currentComponent(){
return this.ab?LifeCycleA : FOR;
}
}
通过判断this.ab的值选择当前的的位置是加载组件LifeCycleA还是组件FOR
另外通过这种方式动态加载组件会重新卸载,加载组件,如果在组件内部有什么修改,切换后修改是不会被保存的,因此,如果需要保存,Vue提供了一种内置的解决办法:,将这个标签包裹component标签,例如
<keep-alive>
<component v-bind:is='currentComponent'>component>
keep-alive>
包裹后,会去缓存当前的组件实例,因此如果当前的组件再一次进入页面,那么Vue将不会去重新创建渲染进页面,而是从缓存中将该页面取出来,插入页面中;
vue上面的原文是:Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 元素作为承载分发内容的出口。
换句话说,也就是定义的内容会被自动加载替换到slot这个元素中,拿个实际场景为例,很多后台管理页面,注册,登录,密码找回使用的是同一套背景模版,区别在于上面的输入框不同,那么实际上开发的时候背景模版只需要单独封装成一个组件,并且预留一个作为接口,之后分别在登录,注册,密码找回中引入,实际上登录注册密码找回的代码就仅仅是输入部分的编写设计
例子:
navigation-link是一个基础组件,标签内部的"Your Profile"将会替换组件内部的标签
<navigation-link url="/profile">
Your Profile
</navigation-link>
navigation-link组件内部
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
同样上例中"Your Profile"部分可以是文字,也可以是HTML代码,如果是HTML代码,HTML代码将整体替换组件内部的部分
另外标签内部是可以有内容的,它仅仅只会在插槽没有被使用到的时候才会被渲染,成为**“后备内容”**,
<button type="submit">
<slot>Submit</slot>
</button>
<submit-button></submit-button>
如果标签内部没有写入内容,那么button上就会有默认文字"Submit",如果有文字,那么会显示文字内容;
如果一个组件内需要有多个插槽,那么就需要给不同的插槽进行命名,这样,指定的内容可以通过指令插入到指定的插槽内,另外如果没有命名实际上vue会给他一个默认添加一个名字default,例如
<div class="container">
<header>
<slot name="header">slot>
header>
<main>
//虽然没有添加name,但是实际上vue会给它添加一个name='default'属性,默认插槽有且只有一个
<slot>slot>
main>
<footer>
<slot name="footer">slot>
footer>
div>
通过给插槽添加属性name来确定为具名插槽,在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令(缩写#),并以 v-slot 的参数的形式提供其名称:
<base-layout>
//v-sort:header 等同于 #header
<template v-slot:header>
<h1>Here might be a page titleh1>
template>
<p>A paragraph for the main content.p>
<p>And another one.p>
<template v-slot:footer>
<p>Here's some contact infop>
template>
base-layout>
最终的渲染结果:
<div class="container">
<header>
<h1>Here might be a page titleh1>
header>
<main>
<p>A paragraph for the main content.p>
<p>And another one.p>
main>
<footer>
<p>Here's some contact infop>
footer>
div>
vue提供了几种内置动画供切换时使用,简单的说,就是当触发v-if和v-show等切换时,为了使得界面更加友好,增加了一些渐入渐出的动画;
Vue 提供了 transition 的封装组件,具体示例如下:
<div id="demo">
<button v-on:click="show = !show">
Toggle
button>
<transition name="fade">
<p v-if="show">hellop>
transition>
div>
通过使用标签,将需要动画的部分包裹起来,之后,当内容显示/隐藏的时候,会触发动画,具体的动画类型则是通过name告诉Vue,上例中的动画类型是fade,另外动画基于css
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
上面的css是控制渐入渐出的动画持续时间
动画种类还是比较丰富的,具体参考官网;
Vue允许用户自己封装类似于v-if,v-for的指令,通过**directive()**函数,具体示例如下:
//全局注册一个指令
Vue.directive("demo",{})
注意的是,注册的指令使用时默认需要加上v-,也就是上面注册的是demo,但是使用的时候是v=demo
//使用方式类似于v-if等指令
<template>
<div v-demo></div>
</template>
那么时候适合自己封装vue指令呢?
为了保证methods方法只有纯粹的数据逻辑,当methods中存在操作DOM/BOM的逻辑时,就可以考虑是否可以抽象成一个自定义指令;
同样,指令封装中有很多钩子函数,用于对这个指令的描述,具体如下
Vue.directive("demo",{
// 只调用一次,指令第一次绑定到元素时调用
// 在这里可以进行一次性的初始化设置
bind: function(el, binding, vnode){},
// 被绑定元素插入父节点时调用
// 要注意的是,该方法只能保证父节点存在,但不能保证父节点已经被插入到文档中
inserted: function(el, binding, vnode){},
// 所在的组件的VBode更新时调动,
// 但是可能发生在其子 VNode 更新之前
// 指令的值可能发生了改变, 也可能没有但是可以通过比较更新前后的值来忽略不必要的模版更新
update: function(el, binding, vnode, oldVnode){},
//指令所在的组件的 VNode 以及其子 VNode 全部更新后调用
componentUpdate: function(el, binding, vnode, oloVode){},
// 只调用一次,指令与元素解绑时调用
unbind: function(el, binding, vnode){}
})
每个钩子函数中都有其固定的形参,具体意思如下:
**el:**指令所绑定的元素,可以用来直接操作 DOM。
**binding:**一个对象,包含以下 property:
vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
案例:实现一个v-resize指令,要求如下:
//全局注册一个v-resize指令
Vue.directive("resize", {
inserted(el, binding) {
//获取绑定的值,也就是绑定的函数
const callback = binding.value;
//获取有没有后缀
const direction = binding.arg;
//获取有没有修饰符
const modifiers = binding.modifiers;
//通过后缀判断是监听宽度还是监听高度
const result = () => {
return direction === "vertical" ? window.innerHeight : window.innerWidth;
};
window.addEventListener("resize", () => callback(result()));
//判断是否有.quiet修饰符
if (!modifiers || !modifiers.quiet) {
callback(result());
}
el._onResize = callback;
},
unbind(el) {
if (!el._onResize) return;
window.removeEventListener("resize", el._onResize);
delete el._onResize;
}
});