vue概述sd
在官方文档中,有一句话对Vue的定位说的很明确:
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
Vue在我的理解下,其实很像mvvm架构中,对vm的实行。在mvvm架构中,viewModel是负责把view和model关联起来,把model的数据同步到view显示出来,把view的修改同步回model。在Vue的data属性中可设定相关变量,并把这些变量和Dom进行关联,达到修改data中属性即可修改Dom的功能 ( model(data) --> Vue --> view(dom) )。在Dom上,通过触发绑定方法,对相关数据进行修改 ( view(dom) --> Vue --> model(data) )。按这个思路来理解Vue,可以大概猜想出Vue有些功能为什么要被设计出来。
- 插值:快速实现data和view的快速绑定,
- 指令:对插值的补充,作用于模板上,用来实现模板的重复生成(v-for),动态显示(v-show, v-if),属性绑定(v-bind),事件绑定(v-on),对表单的增强(v-model)
- 计算属性和观察属性:对插值和指令的补充,动态响应更新数据达到动态更新Dom的功效
- 过滤:对插值和v-bind指令的补充,对要绑定到Dom上的数据进行过滤
- 组件:Vue最强大的功能,对viewModel的实现,template就是view层,script就是model层
这些内容组成了Vue的骨架,掌握以上内容基本就能上手开发。下面我就利用上一章的例子把这些内容详细的介绍下(我按照我的理解打乱了官方的介绍顺序)
1. 插值
<template>
<div class="wrap">
<p>{{info}}</p>
</div>
</template>
<script>
export default {
data () {
return {
info: 'Hello world!'
}
}
}
</script>
文本插值
处于data中返回值的info就是一个文本插值,这时如果修改info的值,对应DOM(p标签)的就会发生改变
上面data必须是函数的写法,是因为这是在一个组件内部,如果使用对象形式的写法,这个组件在经过多个实例化后,实际上是在共享一个data对象,更改任一组件data中的值,就会影响所有组件,所以组件内的data必须是函数,这样每个组件就形成独立的函数作用域,彼此不冲突。
属性插值
如果想把data中的值绑定成为html元素的属性,需要使用v-bind指令(简写方式是 : ),代码改造如下:
<p v-bind:title="info">{{info}}</p>
这个绑定值可以是普通的html属性,也可以是自定义属性
<p v-bind:data-cusData="info">{{info}}</p>
这时如果看最终渲染的页面,其效果是忽略cusData的大写,只显示为cusdata
<p data-cusdata="Hello vue!">Hello vue!</p>
如果这里是设置常用的class,id,title属性是没什么问题,但如果是自定义属性比如data-cusData这种类型的数据,就会被强制转为data-cusdata
如果要绑定多个属性值可以使用对象的形式
<p v-bind='{id: elemId, class: elemClass, "data-cusData": cusData}'>{{info}}</p>
Data中设置修改如下
data () {
return {
info: 'Hello vue!',
elemId: 'pId',
elemClass: 'pClass',
cusData: 'hello world'
}
}
因为class和style是我们常用的属性值,Vue针对这两个属性做了特殊处理
2. 样式
1):class绑定对象语法
改造代码如下
<p class=‘static’ :class='{red: redFlag, font: fontFlag }'>{{info}}</p>
:class是v-bind:class的简写
对应data修改如下:
{
...
redFlag: true,
fontFlag: false
}
页面渲染结果如下:
<p class="static red">Hello vue!</p>
如例子显示,绑定对象的形式可以动态切换多个class,同时也支持与普通class共存
当绑定对象要进行多个class值的设定,沿用上面的写法会显得繁琐,我们可以把绑定的对象换成一个计算属性,在计算属性中动态的设定返回值。
<p class='static' :class='classObj'>{{info}}</p>
在计算属性中设定相关值
computed: {
classObj () {
let classStr = '';
if (this.redFlag) {
classStr += 'red';
}
if (this.fontFlag) {
classStr += 'font';
}
return classStr;
}
}
渲染结果如上,除了上面的写法,也可以结合下面介绍的绑定数组语法,把上面的例子再简化些
<p :class='[classObj, "static"]'>{{info}}</p>
2) :class绑定数组语法
代码如下:
<p :class="[basicClass, redClass]">{{info}}</p>
data下数据修改如下:
{
...
basicClass: 'f20 mb20',
redClass: 'red'
}
这样最终渲染如下:
<p class="f20 mb20 red">Hello vue!</p>
在数组语法中,可以使用三元运算符控制某个元素class的添加和隐藏
代码改造如下:
<p :class="[showInfo ? basicClass : '', redClass]">{{info}}</p>
data数据修改如下:
{
...
showInfo: false,
...
}
这时渲染的最早结果如下:
<p class="red">Hello vue!</p>
如果把showInfo设为true,渲染结果和上例一致
官方文档这里还介绍了对组件的class绑定处理,不过我觉得这样做其实会破坏组件的封装型,就算要添加或者移除某个class,最好还是通过props传值在组件内部自行处理
3) :style绑定对象语法
:style的绑定对象语法和:class绑定对象语法使用方法一样,只是在写css属性名要注意下
<p :style='{color: redColor, "font-size": fontSize, backgroundColor: bgColor }'>{{info}}</p>
data数据修改如下:
{
...
redColor: 'red',
fontSize: '16px',
bgColor: '#FF0'
...
}
如例css的属性名,可以改写成驼峰形式(backgroundColor),或者使用引号包裹起来("font-size")
当使用对象语法进行多个css属性设置时,可以使用计算属性进行绑定
<p :style='styleObj'>{{info}}</p>
计算属性设置如下
computed: {
styleObj () {
return `color: ${this.redColor}; fontSize: ${this.fontSize}; backgroundColor: ${this.bgColor}`;
}
}
渲染结果如下:
<p style="color: red; font-size: 16px; background-color: rgb(255, 255, 0);">Hello vue!</p>
上面设定样式的方式如果只是绑定单个对象是没问题的,但如果要使用数组的形式绑定多个对象就无法生效(解决方法参看下面)。
4) :style绑定数组语法
使用:class绑定数组使用方法见下例:
<p :style='[colorStyle, fontStyle, backgroundColor]'>{{info}}</p>
这个时候要保证对应数组对象是对象的形式(上例是字符串形式):
这个例子中绑定对象的值分别在data和计算属性中设定,只是做演示,表示这个绑定值,可以是不同的来源值进行混合
在data中设定colorStyle
{
...
colorStyle: {
color: 'red'
}
...
}
在计算属性中设定fontStyle和backgroundColor
computed: {
fontStyle () {
return {fontSize: this.fontSize}
},
backgroundColor () {
return {backgroundColor: this.bgColor}
}
}
这时的渲染结果和上例是一致的,但如果你把其中一个值,改成字符串形式
fontStyle () {
return `fontSize: ${this.fontSize};`
}
这时你会发现Vue会忽略当前值的设定,所以在使用:style形式时,最好按对象形式返还设定的style。
3.指令
我对指令的理解就是:指令是使模板具备了逻辑处理的能力,是对插值的一种补充,因为指令的存在才使得数据层和Dom层具备了相互绑定的能力。
按官方api,vue的指令如下:
v-text
v-html
v-show
v-if
v-else
v-else-if
v-for
v-on
v-bind
v-model
v-pre
v-cloak
v-once
1)v-text和v-bind
v-text实现文本绑定的能力,期望值是string
<p v-text="info"></p>
<p>{{info}}</p>
上面两种写法渲染后的结果一致,通常我们会使用第二种写法,比较简洁。这个指令期望的值是string,但如果绑定的值是一个对象,就会原样把对象输出。
<p>{{infoObj}}</p>
在data中设定一个infoObj
{
...
infoObj: {
msg: 'Hello vue!'
}
...
}
这时页面渲染为
<p>{
"msg": "Hello vue!"
}</p>
如果你进行下面的设定
infoObj: true, // => true
或
infoObj: 3 > 2, // => true
或
infoObj: Math.random(), // => 渲染为一个不确定的随机数
或
infoObj: 2 + 3, // => 5
所以这个指令会对传入的值进行一个转换,转换成string。事例中该指令的绑定值,我只使用了data下的值,但实际上这个值还可以是计算属性。
computed: {
infoObj () {
return 'Hello vue by computed!'
}
}
对页面进行渲染时,页面的展示数据分为静态数据和动态数据,一般情况下静态数据存放在data属性下,动态数据通过计算属性进行返回(上例只是个样式,正常情况下计算属性会包含相关逻辑处理,相关会在计算属性那部分讲解,这里就不展开了)
v-bind用来动态的绑定一个或者多个属性
在属性插值部分已经对v-bind指令做了比较详细的介绍,有一些特殊点要特别说下:
绑定值可以是一个表达式
<p v-bind:title="info + ' 这时行内添加的信息'">{{info}}</p>
这时页面渲染结果为
<p title="Hello vue! 这时行内添加的信息">Hello vue!</p>
这是一个比较有用的特性,在开发过程中我们很容易碰到根据数据的不同展示不同的样式(比如符合某个条件时文案颜色变红)
<p :class='[this.login ? "red" : "", "static", elemClass]'>{{info}}</p>
在data中我们假定一个login的字段
{
...
login: true
...
}
这时页面就会渲染为
<p class="red static pClass">Hello vue!</p>
如例中假设,比如我现在有个接口返回用户是否登陆,拿到接口返回数据,我们就可以根据接口返回的相关字段,动态的设定某一处的class。
通过修饰符.prop绑定DOM属性
如果在绑定属性后使用.prop修饰符,会忽略其他对该值的设定,强制使用设定的属性值
<p class="testProp" v-bind:text-content.prop="info" v-bind:className.prop="elemId">测试数据</p>
这时页面会渲染为
<p class="pId">Hello vue!</p>
如果删掉一个prop值
<p class="testProp" v-bind:text-content="info" v-bind:className.prop="elemId">测试数据</p>
对应的绑定值就会变成一个普通的属性值
<p class="pId" text-content="Hello vue!">测试数据</p>
2) v-html
该指令是用来直接输出HTML内容
<div v-html="html"></div>
在data中设定如下值
{
...
html: '<p>Hello vue!</p>',
...
}
这时页面的渲染结果为
<div><p>Hello vue!</p></div>
如果使用v-text会原样把字符串内容输出
<div v-text="html"></div>
渲染为
<div><p>Hello vue!</p></div>
在官方文档中强调这种写法很容易引起XSS攻击,不能把这个能力开放给普通用户,在实际开发中,我也没碰到什么地方需要使用该指令。。。
3)v-show
根据表达式之真假值(这个值可以是data中的值,也可以是计算属性,后续不再赘述),切换元素的 display CSS 属性。
<p v-show="showFlag">{{info}}</p>
在data中设定showFlag的值
{
...
showFlag: true,
...
}
这时页面渲染为
<p>Hello vue!</p>
如果showFlag设为false,页面渲染为
<p style="display: none;">Hello vue!</p>
v-show指令使用时有如下内容需要注意:
- v-show不支持<template>元素,因为template标签的元素的内容不会被渲染,所以对该标签使用v-show去改变其的display属性是没有意义的。
- v-show不支持和v-else一起使用,v-show只管当前元素的显示与否
4)v-if
该指令会根据表达式的值动态渲染元素。元素及其包含的指令/组件在切换期间被销毁并重新构建。如果是作用在<template>元素上,则其内部元素内容会根据表达式的值动态建立或者销毁。(这点是与v-show的不同点,v-show只是改变display的值,v-if则会进行动态的建立或者销毁)
<template v-if="showFlag"><p>{{info}}</p></template>
渲染为
<p>Hello vue!</p>
5)v-else和v-else-if
对v-if的补充,必须紧跟着v-if之后,否则无法生效
<p v-if="type === 'A'">A</p>
<p v-else-if="type === 'B'">B</p>
<p v-else-if="type === 'C'">C</p>
<p v-else>D</p>
在data中设定type一个随意的值
{
...
type: 'E',
...
}
这时页面渲染为
<p>D</p>
由于v-if会对元素进行重建或者销毁,而Vue在渲染时会尽可能复用已有元素,针对普通元素这个指令使用起来是没问题的,但针对一些表单元素就有问题了。
<template v-if="type === 'A'"><input placeholder="A"/></template>
<template v-else-if="type === 'B'"><input placeholder="B"/></template>
<template v-else-if="type === 'C'"><input placeholder="C"/></template>
<template v-else><input placeholder="D"/></template>
<button @click="changeType">切换Type的值</button>
@click是v-on:click的简写(这个是用来绑定事件),在methods中添加相关方法
methods: {
changeType () {
let [random, type] = [Math.random(), ''];
if (random > 0.8) {
type = 'A';
} else if (random > 0.6) {
type = 'B';
} else if (random > 0.2) {
type = 'C';
} else {
type = 'D';
}
this.type = type;
}
},
页面渲染为
<input placeholder="D">
<button>切换Type的值</button>
点击button,会发现input元素的内容会发生改变,但如果你向input输入一个值,这时你再点击button,虽然input元素会变,但是已经输入的内容却不会改变,类似的还有textarea标签。
如果认为这是一个问题,可以使用Vue提供的防重的方式,使用key添加一个唯一的关键值。做以下改造
<template v-if="type === 'A'"><input key="A" placeholder="A"/></template>
<template v-else-if="type === 'B'"><input key="B" placeholder="B"/></template>
<template v-else-if="type === 'C'"><input key="C" placeholder="C"/></template>
<template v-else><input key="D" placeholder="D"/></template>
<button @click="changeType">切换Type的值</button>
这时如果输入内容,再点击切换input就会完全重新渲染,不过之前输入的内容也会被清除
6)v-for
v-for指令根据一组数组的选项列表进行渲染,通常我们只会在需要展示列表的部分使用该指令。
数据源是一个对象组成的数组
<ul>
<li v-for="item in students">姓名:{{item.name}}--年龄:{{item.age}}</li>
</ul>
在data下设定students的值
{
...
students:[
{name:'Tom', age:24},
{name:'Jim', age:22},
{name:'Kate', age:21}
],
...
}
渲染结果为
<ul>
<li>姓名:Tom--年龄:24</li>
<li>姓名:Jim--年龄:22</li>
<li>姓名:Kate--年龄:21</li>
</ul>
从这个例子,我们可以看出该指令是在当前元素进行循环渲染,根据条件展示数据,该指令还可以接受一个参数表示数组的排序(从0开始)
<ul>
<li v-for="(item, $index) in students">{{$index}}.姓名:{{item.name}}--年龄:{{item.age}}</li>
</ul>
渲染为
<ul>
<li>0.姓名:Tom--年龄:24</li>
<li>1.姓名:Jim--年龄:22</li>
<li>2.姓名:Kate--年龄:21</li>
</ul>
数据源是一个对象
<ul>
<li v-for="value in tomInfo">{{value}}</li>
</ul>
在data中添加相关数据
{
...
tomInfo:{
age: 24,
gender: 'man',
address: '北二环前门里'
},
...
}
渲染为
<ul>
<li>24</li>
<li>man</li>
<li>北二环前门里</li>
</ul>
此时如果指令接受的第二个参数表示的是对象key值
<ul>
<li v-for="(value, key) in tomInfo">{{key}} : {{value}}</li>
</ul>
渲染为
<ul>
<li>age : 24</li>
<li>gender : man</li>
<li>address : 北二环前门里</li>
</ul>
在数据源是对象的情况下,该指令还可以接受第三个参数,表示序号
<ul>
<li v-for="(value, key, $index) in tomInfo">{{$index}}. {{key}} : {{value}}</li>
</ul>
渲染为
<ul>
<li>0\. age : 24</li>
<li>1\. gender : man</li>
<li>2\. address : 北二环前门里</li>
</ul>
上面是我们假定的一个例子,通常我们是从后端拿到数据然后再在前端展示,而后端给到前端的数据通常是按照JSON格式给出,key值都是英文,这时前端就要对key值进行一个转为中文的操作,我们现在要对已经处理过的数据在进行简单处理(逻辑并不复杂),这时我们就需要使用过滤器(本部分只简单介绍)
在filters(这是组件内的写法)下添加相关过滤器
filters: {
keyCheck (val) {
let cnVal = '';
if (val === 'age') {
cnVal = '年龄';
} else if (val === 'gender') {
cnVal = '性别';
} else if (val === 'address') {
cnVal = '家庭住址';
}
return cnVal;
}
}
页面内容进行如下改造
<ul>
<li v-for="(value, key, $index) in tomInfo">{{$index}}. {{key | keyCheck}} : {{value}}</li>
</ul>
注意{{key | keyCheck}}就是过滤器的使用方法,页面渲染为
<ul>
<li>0\. 年龄 : 24</li>
<li>1\. 性别 : man</li>
<li>2\. 家庭住址 : 北二环前门里</li>
</ul>
关于v-for使用:key提升性能的解释
在Vue的官方文档中介绍v-for指令时,提到使用key值提升渲染性能,具体原因官方没有详细介绍,只说Vue在进行渲染时进行的是“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
参考如下实例,有助于理解这段话:
<div v-for="(item, $index) in listInfo" :title="item.id">
<input :placeholder="item.value"/>
</div>
<button @click="addList">添加新的列表内容</button>
在data和methods下添加如下内容
{
...
listInfo: [
{id: 'listA', value: 'A'},
{id: 'listB', value: 'B'},
{id: 'listC', value: 'C'},
{id: 'listD', value: 'D'}
]
...
}
methods: {
addList () {
this.listInfo.splice(1, 0, {id: 'listE', value: 'E'})
}
},
这时页面渲染为
<div title="listA"><input placeholder="A"></div>
<div title="listB"><input placeholder="B"></div>
<div title="listC"><input placeholder="C"></div>
<div title="listD"><input placeholder="D"></div>
<button>添加新的列表内容</button>
如果在几个输入框中输入随意文字,然后再点击按钮,这时我们发现渲染结果为
<div title="listA"><input placeholder="A"></div>
<div title="listE"><input placeholder="E"></div>
<div title="listB"><input placeholder="B"></div>
<div title="listC"><input placeholder="C"></div>
<div title="listD"><input placeholder="D"></div>
<button>添加新的列表内容</button>
渲染的信息如我们所预期的一致,但是我们刚刚输入的文字输入框却没有按预期的下移,比较明显的被直接复用,这也就是官方文档中提到的如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,不过在这个例子中这种Vue的默认处理方式是不符合我们的预期,所以这时要使用key值来标记每个节点,方便Vue对数据进行重新排序
<div v-for="(item, $index) in listInfo" :title="item.id" :key="item.id">
<input :placeholder="item.value"/>
</div>
<button @click="addList">添加新的列表内容</button>
渲染结果同上没有变化,但是如果你这时再输入框输入文字,然后再点击按钮时,Dom就会按预期的排序
在这里会有个容易忽略的问题,如果你把key的绑定值换成数组序号的$index值
:key="$index"
Vue会忽略这个key的设定,还是按照未设定key时的方式进行渲染,原因(以下是个人理解,可能根本原因并不是这样)是此时key值绑定的是数组的序号值(数字是不可变值),虽然可以通过addList方法触发listInfo值的更新,但是无法触发key绑定值的更新,Vue会把新加的数据当作普通数据,采取"就地复用"的策略(也就是官方文档中提到的确保它在特定索引下显示已被渲染过的每个元素),直接更新Dom而不对Dom进行重新排序,所以我们做如下修改,来验证我们的想法
<div v-for="(item, $index) in listInfo" :title="list[$index]" :key="list[$index]">
<input :placeholder="item.value"/>
</div>
<button @click="addList">添加新的列表内容</button>
在data中设定list值
{
...
list: [1, 2, 3, 4],
...
}
同时在修改addList方法,触发list数组的更新
addList () {
this.listInfo.splice(1, 0, {id: 5, value: 'E'});
this.list.splice(1, 0, 5);
}
这时如果进行相关操作,就会发现和上面方法(:key="item.id")呈现一致,在上例中key的绑定值是一个数组对象,同时在addList方法中更新数组数据(如果不进行更新,而是在list中提前定义好,一样无法触发Dom的重新排序)
触发v-for的数据更新
v-for的数据源如果是数组时,我们可以使用触发数组更新的方法,来触发v-for的重新渲染
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
copyWithin()
fill()
以上的方法都会触发原始数组的更新,数组更新自然就会触发v-for的重新渲染,数组的其他方法比如filter,concat,slice,every,map,find,findIndex等方法都不回直接修改原始数组,数组不更新,v-for也就不会重新渲染,如果需要对数组使用到非更新方法,可以把处理后的数组直接赋值给原始数组
其他特殊情况
Vue没有实现对以下内容变化的检测(按官方说法是受javascript限制,后续单独讲解):
检测数组的变化
- 利用索引直接设置数组值:arr[0] = newValue;
- 修改数组长度:arr.length = newLength;
用下面例子演示解决方法:
<div v-for="(item, $index) in listInfo" :title="item.id" :key="item.id">
<input :placeholder="item.value"/>
</div>
<button @click="editList">修改list内容</button>
在methods添加editList方法:
methods: {
editList () {
// this.listInfo[0] = {id:1, value:'AA'};
this.$set(this.listInfo, 0, {id:1, value:'AA'});
}
}
this.$set是在methods方法内的使用方式,如果你不在其中使用可以使用Vue.set的形式(保证Vue的存在)
你可以尝试使用直接设置数组值(注释部分的代码),这时会发现页面并不会改变,使用Vue.set会让Vue检测到数组的变化,还可以使用数组的splice方法让数组更新
this.listInfo.splice(0, 1, {id:1, value:'AA'});
如果这时你使用
this.listInfo.length = 2;
设置数组长度,视图不会更新,解决方法一样可以调用splice方法
this.listInfo.splice(2);
检测对象的变化
- Vue不能检测对象属性的添加或删除
事例如下:
<ul>
<li v-for="(value, key, $index) in tomInfo">{{$index}}. {{key | keyCheck}} : {{value}}</li>
</ul>
<button @click="addSchool">添加学校信息</button>
在methods方法中添加addSchool方法
methods: {
addSchool () {
this.tomInfo.school = '深圳大学';
console.log(this.tomInfo);
}
}
点击页面按钮,我们在控制台可以看到属性是添加到指定数据上,但页面视图却没有重新渲染,解决方法同样使用Vue.set方法
addSchool () {
this.$set(this.tomInfo, 'school', '深圳大学');
console.log(this.tomInfo);
}
这时视图如期望变化,与数组的splice方法类似,针对对象也可以使用Object.assign方法把更新后的对象重新赋值
addSchool () {
this.tomInfo = Object.assign({}, this.tomInfo, {school: '深圳大学'});
console.log(this.tomInfo);
}
使用Object.assign的好处就是可以同时添加多个属性值
this.tomInfo = Object.assign({}, this.tomInfo, {school: '深圳大学', class: '软件工程'});
一个需要特别注意的地方:v-for的优先级比v-if的优先级高
<li v-for="(value, key, $index) in tomInfo" v-if="$index > 1">{{$index}}. {{key | keyCheck}} : {{value}}</li>
因为v-for优先级高于v-if,所以这个会先进行循环,然后再判断是否符合条件进行展示
7) v-on
该指令是要来监听DOM事件,并在触发时运行指定代码(使用方法是v-on:事件名=“事件”)
<p @click="editInfo">{{info}}</p>
相关事件是放到methods下,@click是v-on:click的简写方式
methods: {
editInfo () {
this.info = 'info值被修改';
}
}
这时点击页面就会发现页面数据发生改变,事件可以接收参数来获取原生DOM事件信息
editInfo (event) {
this.info = 'info值被修改';
console.log(event);
}
进行点击,在控制台会输出原生DOM事件信息
<p @click="editInfo('修改info值')">{{info}}</p>
绑定事件时可以接收参数传值
editInfo (msg) {
this.info = msg;
}
这时如果还要输出DOM事件信息,就要在传参数时传一个特殊参数$event
<p @click="editInfo('修改info值', $event)">{{info}}</p>
同一个元素可以绑定多个事件
<p @mouseover="mouseOver" @mouseout="mouseOut" @click="editInfo('修改info值', $event)">{{info}}</p>