Vue
1. Vue 定义
Vue (类似于 view) 是一套用于构建用户界面的渐进式框架,Vue 被设计为可以自底向上逐层应用,Vue是mvvm模式的。
2. Vue API
2.1 全局配置
2.2 全局API
2.2.1 filter 过滤器
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示。
<div id="app">
<ul>
<li
v-for="goods in goodsList"
:key="goods.id"
>
<!-- 在双花括号中 -->
<h5>{{goods.title | toUpper}}</h5>
<!-- 竖线之后写过滤器的名字,就会把前面值传递给过滤器,
过滤器处理之后再作为插值表达式来显示 -->
<p>{{goods.price | toFix}}</p>
</li>
</ul>
</div>
补充:
<!-- 在 `v-bind` 中 待理解-->
<li v-bind:id="rawId | formatId"></li>
const app = new Vue({
el: '#app',
data: {
goodsList: [
{
id: 1,
title: '罗技鼠标',
price: 9.9999999999
}
]
},
filters: {
// 定义一个过滤器,接收要过滤的值,返回过滤的结果
toFix (val) {
return val.toFixed(2)
},
toUpper (val) {
return val.toUpperCase()
}
}
})
过滤器是 JavaScript 函数,因此可以接收参数。如:
{{ message | filterA('arg1', arg2) }}
filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,
普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
2.3 选项 / 数据
2.3.1 data
类型:Object | Function
data => Vue实例中的数据对象,对象必须是纯粹的对象 (含有零个或多个的 key/value 对)。
注意:data
属性使用了箭头函数,则 this
不会指向这个组件的实例,不过你仍然可以将其实例作为函数的第一个参数来访问。
组件的data必须是一个方法。因为每个组件的数据是独立的,使用方法可以保证不共享,在data里return一个对象。复用组件时,data必须是个函数(function)
var data = { a: 1 }
// 直接创建一个实例
var vm = new Vue({
data: data
})
vm.a // => 1
vm.$data === data
2.3.2 methods 事件处理器
类型:{ [key: string]: Function }
methods是用来写方法的地方,可以在其他地方调用这里的方法。methods 将被写到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this
自动绑定为 Vue 实例。
注意:不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++
)。理由是箭头函数绑定了父级作用域的上下文,所以 this
将不会按照期望指向 Vue 实例,this.a
将是 undefined。
var vm = new Vue({
data: { a: 1 },
methods: {
plus: function () {
this.a++
}
}
})
vm.plus() // 调用方法
vm.a // 输出 2
2.3.3 computed 计算属性
类型:{ [key: string]: Function | { get: Function, set: Function } }
计算属性将被写到 Vue 实例中。所有 get 和 set 的 this 上下文自动地绑定为 Vue 实例。计算属性每次的计算结果都会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。
注意:一个计算属性如果使用了箭头函数,则 this
不会指向这个组件的实例,不过你仍然可以将其实例作为函数的第一个参数来访问。
var vm = new Vue({
data: { a: 1 },
computed: {
// 仅读取
aDouble: function () {
return this.a * 2
},
// 读取和设置
aPlus: {
get: function () {
return this.a + 1
},
set: function (v) {
this.a = v - 1
}
}
}
})
vm.aPlus // => 2
vm.aPlus = 3 // 设置计算结果为 3
vm.a // 所以输出a => 2
vm.aDouble // => 4
2.3.4 watch 侦听属性
类型:{ [key: string]: string | Function | Object | Array }
watch 用于监听(观察)某个属性是否发生改变,发生改变就做出相应的操作来响应这个数据变化。一个对象,键是需要观察的表达式,值是对应回调函数、方法名或者包含选项的对象。Vue 实例将会在实例化时调用 $watch()
,遍历 watch 对象的每一个属性是否发生数据变化,并做出响应。
注意:不应该使用箭头函数来定义 watcher 函数 (例如 searchQuery: newValue => this.updateAutocomplete(newValue)
)。理由是箭头函数绑定了父级作用域的上下文,所以 this
将不会按照期望指向 Vue 实例,this.updateAutocomplete
将是 undefined。
<div id="app">
<label>姓:<input type="text" v-model="xing"></label><br>
<label>名:<input type="text" v-model="ming"></label><br>
<label>姓名:<input type="text" v-model="name"></label><br>
</div>
const app = new Vue({
el: '#app',
data: {
xing: '',
ming: '',
name:''
},
watch: {
// 监听值的修改
// 只要值发生了改变,都会执行这个方法
xing (nVal, oVal) {
console.log(nVal, oVal)
// 姓发生了改变
this.name = nVal + this.ming
},
ming (nVal) {
this.name = this.xing + nVal
},
name (nVal) {
this.xing = nVal.slice(0, 1)
this.ming = nVal.slice(1)
}
}
})
2.3.5 props
类型:Array<string> | Object
props 可以是数组或对象,用于接收来自父组件的数据。props 可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义验证和设置默认值。
基于对象的语法使用以下选项:
-
type
: 校验数据类型。可以是下列原生构造函数中的一种:String
、Number
、Boolean
、Array
、Object
、Date
、Function
、Symbol
、任何自定义构造函数、或上述内容组成的数组。 -
default
:any
prop 指定一个默认值。 -
required
:Boolean
定义该 prop 是否是必填项。 -
validator
:Function
自定义验证函数会将该 prop 的值作为唯一的参数代入。
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
props 使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
<!-- 在 html 中是 post-title 的 -->
<blog-post post-title="hello!"></blog-post>
Vue.component('blog-post', {
// 在 JavaScript 中是 postTitle 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
props 是单向数据流, 所有的prop都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。所以props可用于组件传参(父 => 子)。
2.4 特殊特性
2.4.1 ref
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs
对象上。如果用在子组件上,引用就指向组件实例。
在普通的 DOM 元素上使用,引用指向的就是 DOM 元素。
<div id="app">
<input type="text" ref="add" value="hello">
</div>
new Vue ({
el: '#app',
data: {
},
methods: {
// 取到input这个DOM元素
this.$refs.add.focus()
}
})
3. Vue的基础使用
3.1 引入Vue
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
或者保存 Vue.js 到本地,本地调用也可
3.2 声明式渲染
声明式渲染 就是利用插值表达式进行数据渲染
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:
<div id="app">
{{ message }} <!-- 插值表达式 -->
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
输出:Hello Vue!
注意:此时数据和 DOM 已经被建立了关联,所有东西都是响应式的。如:修改 app.message 的值,输出信息也会更改。
3.3 Vue 实现双向绑定的原理(插值表达式的原理):
Vue的双向绑定是基于defineProperty实现的。流程如下:
注意:defineProperty是用来给一个对象定义属性的。基于defineProperty的get/set实现
3.4 模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。
3.4.1 插值表达式
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:
<div id="app">
{{ message }} ——插值表达式
</div>
注:插值表达式里面代表是javascript表达式。所以字符要加引号
<div id="app">
{{isHandsome}} <br>
{{1+1}} <br>
{{isHandsome && 'you are right'}} <br>
{{1 + 1 !== 2 ? '你算对了' : '回去读小班'}} <br>
</div>
3.4.2 template标签
template标签,HTML5提供的新标签,更加规范和语义化 ;可以把列表项放入template标签中,然后进行批量渲染。
<template id="tem">
<div id="app">
<h1 id="title">hello world!</h1>
</div>
</template>
打开网页,会发现在浏览器并没有渲染出任何信息,这是因为template标签内容天生不可见,设置了display:none;属性。
var tem =document.getElementById("tem");//获取template标签
var title = tem.content.getElementById("title"); //在template标签内部内容,必须要用.content属性才可以访问到
console.log(title); //找到h1
template标签中的 元素是被当做一个不可见的包裹元素,主要用于分组的条件判断和列表渲染。
3.5 Vue 指令
Vue.js的指令 (Directives) 是带有 v-
前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for
是例外情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
3.5.1 v-text
v-text 更新元素文本内容, 让元素的文本内容显示属性的内容。
<div id="app">
<!-- 插值表达式不会解析html字符串,当作普通文本在渲染 -->
{{str}} 输出结果:<b>hello</b>
<!-- 指令,指令里面是javascript 所以str要加引号-->
<p v-text="str"></p> 输出结果:<b>hello</b>
<!-- 由于v-text里面必须放javascript,所以会把内容当然属性或者方法去读取,因此下面这行代码会报错 -->
<p v-text="你真帅"></p> 输出结果:报错
<!-- 当指令和插值表达式同时存在的时候指令生效 -->
<p v-text="'你真帅'">{{str}}</p> 输出结果:你真帅
</div>
const app = new Vue({
el: '#app',
data: {
str: '<b>hello</b>'
}
})
3.5.2 v-html
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html
指令:
<div id="app">
<!-- v-html能解析html字符串 -->
<p v-html="str"></p> 输出结果:加粗的 hello
</div>
3.5.3 条件渲染 v-if(else) / v-show
v-if(else)
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true 值的时候被渲染。
v-if
是通过是否渲染来决定显示隐藏。v-show
是一定会渲染,通过display样式来决定是否显示隐藏。
<div id="app">
<div v-if="false">这是一个弹框1</div> false 不渲染显示
<div v-if="1 + 1 === 2">这是一个弹框2</div> true 渲染显示
</div>
也可以用 v-else
添加一个“else 块”,v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。v-else指令与v-if或者v-show同时使用,v-if条件不成立则会显示v-else内容。
<div id="app">
<!-- false, 渲染else;true,渲染if -->
<div v-if="isModelShow">这是一个弹框4</div>
<!-- v-else只作用与上一个兄弟元素的v-if -->
<div v-else>这事跟弹框4相反的一个显示</div>
</div>
const app = new Vue({
el: '#app',
data: {
isModelShow: false
}
})
v-show
用于根据条件展示元素的选项是 v-show
指令。用法跟v-if
大致一样:
<div id="app">
<div v-show="true">这是一个弹框5</div> 显示
<div v-show="1 + 1 === 3">这是一个弹框6</div> 隐藏
</div>
注意:v-show
不支持 <template>
元素,也不支持 v-else
。
v-if
与v-show
的区别:
v-show
不管条件是否成立,都会渲染html,而v-if
只有条件成立才会渲染。
v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地切换元素的 CSS 属性 display
。
非常频繁地切换显示隐藏,则使用 v-show
较好;反之,则使用 v-if
较好。
3.5.4 v-for 列表渲染 (循环)
v-for
指令根据一组数组的选项列表进行渲染。v-for
指令需要使用 item in items
形式的特殊语法,items
是源数据数组并且 item
是数组元素迭代的别名。
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
输出结果: ·Foo
·Bar
v-for
可以循环字符串 、数字、对象、数组等。
v-for
也可以循环数字。在这种情况下,它将重复多次模板。
<div id="app">
<span v-for="n in 10">{{ n }} </span>
</div>
例:v-for
循环对象,可以遍历 索引、键名、属性。
<div id="app">
<!-- 给每一个循环动态绑定一个唯一的key,这个key一般是一条数据的id,或者name,title之类的唯一标识 -->
<div v-for="(value, key, index) in person" v-bind:key="obj.id">
{{ index }}.{{ key }}: {{ value }}
</div>
<!-- 循环data数据,得到索引和元素值,循环渲染当前标签 -->
<div v-for="(value, key) in person" :key="like.id">
{{ key }}: {{ value }}
</div>
</div>
new Vue({
el: '#app',
data: {
person: {
name: '小明',
age: 20,
gender: '男'
}
}
})
3.5.5 v-model 表单输入绑定
v-model
指令用于表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text 和 textarea 元素使用
value
属性和input
事件;checkbox 和 radio 使用
checked
属性和change
事件;-
select 字段将
value
作为 prop 并将change
作为事件。1. input文本应用
<!-- 输入框的v-model指令负责绑定value --> <!-- 双向绑定,用户在input里输入值会自动绑定到data上 --> <div id="app"> <input type="text" v-model="username"> {{username}} </div>
const app = new Vue({ el: '#app', data: { username: 'zhangsan' } })
2.多行文本 textarea
<span>Multiline message is:</span> <p style="white-space: pre-line;">{{ message }}</p> <br> <textarea v-model="message" placeholder="add multiple lines"></textarea>
3.复选框 checkbox
单个复选框,绑定到布尔值 true 选中,false 未选中 <!-- v-model使用在checkbox上的时候代表多选框的选中状态,绑定的是checked属性 --> <input type="checkbox" id="checkbox" v-model="checked"> <label for="checkbox">{{ checked }}</label>
多个复选框,绑定到同一个数组: <!-- v-model可以使用一个数组来绑定多选按钮,选中的value值就会存在这个数组里 --> <div id='example-3'> <input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label> <br> <span>Checked names: {{ checkedNames }}</span> </div>
new Vue({ el: '#example-3', data: { checkedNames: [] } })
4.单选按钮 radio
<div id="example-4"> <input type="radio" id="one" value="One" v-model="picked"> <label for="one">One</label> <br> <input type="radio" id="two" value="Two" v-model="picked"> <label for="two">Two</label> <br> <span>Picked: {{ picked }}</span> </div>
new Vue({ el: '#example-4', data: { picked: '' } })
5.下拉菜单 select
<!-- v-model用在select上,绑定的就是选中的那个option的value --> <div id="example-5"> <select v-model="selected"> <option disabled value="">请选择</option> <option>A</option> <option>B</option> <option>C</option> </select> <span>Selected: {{ selected }}</span> </div>
new Vue({ el: '...', data: { selected: '' } })
3.5.6 v-clock
v-clock
指令可以解决使用插值表达式页面闪烁问题(解决页面卡顿给用户带来不好体验)。
方法:将该指令加在html标签中时,可以在该文件中加style属性为display:none。
[v-cloak] {
display: none;
}
<!-- 加上v-cloak这个指令以后,在vue实例化之前div身上就会有v-cloak这个属性,实例化之后这个属性就会被移除
-->
<div id="app" v-cloak>
{{msg}}
</div>
const app = new Vue({
el: '#app',
data: {
msg: 'hello world'
}
})
3.5.7 v-bind 属性绑定
v-bind
用于动态地绑定一个或多个特性,v-bind用来绑定一个属性,属性值作为JavaScript去执行。
可以在其名称后面带一个参数,中间放一个冒号隔开,这个参数通常是HTML元素的特性(attribute),如v-bind: class 、v-bind: src 等。 注意:class可以和v-bind:class同时存在,叠加渲染。
1. 绑定属性 (如:href、src)
<div id="app">
<a v-bind:href = "baiduLink">百度一下</a>
<!-- v-bind 简写为 : -->
<a :href = "baiduLink">百度一下</a>
<img :src="imgUrl" alt="">
</div>
const app = new Vue({
el: '#app',
data: {
baiduLink:'https://www.baidu.com/',
imgUrl:'https://ss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=1488861817,1113726833&fm=202'
}
})
2. 绑定 style - Class
.box {
width: 200px;
height: 200px;
background: red;
margin-bottom: 10px;
}
.ac {
background: green;
}
<div id="app">
<!-- 绑定class名称 -->
<div :class="'box'"></div>
<div :class="className"></div>
<!-- 可以绑定一个对象,通过isAc的boolean来决定ac是否存在 -->
<div :class="{ac: isAc}"></div>
<!-- 普通class和绑定class可以共存,最后把两部分叠加渲染 -->
<div class="box" :class="{ac: isAc}"></div>
<!-- 这个是最复杂的用法,绑定一个数组,数组元素可以直接是class名称就能直接绑定,如果另外一个class根 据数据决定,那就再写一个对象 -->
<div :class="[className, {ac: isAc}]"></div>
<p :style="style">Lorem ipsum dolor</p>
</div>
const app = new Vue({
el: '#app',
data: {
className: 'box',
isAc: false,
style: {
width: '100px',
height: '100px',
color: 'red'
}
}
})
3.5.8 v-on 事件监听
v-on
指令用于监听 DOM 事件,并在触发时运行一些 JavaScript 代码。例如给button添加点击事件
<button v-on:click="show">
<!-- v-on:click指令可以简写为@click,修改代码:-->
<button @click="show">
许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称。
<div id="app">
<button v-on:click="onNumDecrease">decrease</button>
{{num}}
<button @click="onNumAdd(12, $event)">Add</button>
</div>
const app = new Vue({
el: '#app',
data: {
num: 1
},
methods: {
// 定义当前vue实例的方法
onNumDecrease (e) {
// 事件处理函数在v-on监听的时候不写括号,函数的第一个参数就是事件对象
console.log(e)
// this.$data.num
this.num--
},
onNumAdd (n, e) {
// 事件处理函数在v-on监听的时候写了括号,那就可以传参,而且传一个特殊变量$event就是事件对象
console.log(n)
console.log(e)
}
}
})
3.5.9 事件修饰符
事件处理程序中经常需要处理默认事件等,为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。事件修饰符监听事件的同时就阻止默认事件等。
常用事件修饰符有:.stop 、 .prevent 、 .capture 、.self 、.once 、.passive 。
.stop 防止事件冒泡
冒泡事件:嵌套两三层父子关系,然后所有都有点击事件,点击子节点,就会触发从内至外 子节点 => 父节点的点击事件。
<!-- 阻止单击事件继续传播 阻止冒泡 -->
<a v-on:click.stop="doThis"></a>
.prevent 阻止默认事件
.prevent
等同于JavaScript的event.preventDefault()
,用于取消默认事件。
<!-- 提交事件不再重载页面 阻止默认提交-->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 点击右键不再出现菜单 阻止右键菜单-->
<p @contextmenu.prevent="onCM">Lorem ipsum dolor</p>
.capture 捕获事件
捕获事件:嵌套两三层父子关系,然后所有都有点击事件,点击子节点,就会触发从外至内 父节点 => 子节点的点击事件
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
.self 触发事件
.self
只会触发自己范围内的事件,不会包含子元素。
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
注意:
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,
用 v-on:click.prevent.self 会阻止所有的点击,
而 v-on:click.self.prevent 只会阻止对元素自身的点击。
.once 只执行一次点击
如果我们在@click
事件上添加.once
修饰符,只要点击按钮只会执行一次。
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
.passive 新增
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
这个 .passive
修饰符能够提升移动端的性能。
注意:不要把 .passive
和 .prevent
一起使用,因为 .prevent
将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive
会告诉浏览器你不想阻止事件的默认行为。
3.5.10 v-on:keyup 按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符。可以使用按键名,也可使用按键码。
常用按键名:enter 、tab 、delete 、esc 、space 、left 、up 、right 、down
常用按键码:13(enter ) 、 37-40(左上右下)
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<!-- 按下 enter,提交表单 -->
<input @keyup.enter="submit">
<!-- 按下 enter,提交表单 -->
<input v-on:keyup.13="submit">
4. Vue 组件
4.1 组件概念
组件是可复用的 Vue 实例,且带有一个名字。我们可以在一个通过 new Vue
创建的 Vue 根实例(父组件)中,把这个组件作为自定义元素来使用。
data 方法
一个组件的 data 选项必须是一个函数,函数体内 return一个对象。因为组件里的数据是独立的,使用方法可以保证不共享,其他地方不能调用这个data的数据。
data () {
return {
}
}
4.2 组件注册
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。
4.2.1 全局注册
用 Vue.component
来创建的组件,叫做全局组件,全局组件可以在全局内使用。全局组件可以用在任何新创建的 Vue 根实例 (new Vue
) 的模板(包括组件树中的所有子组件的模板)中。
创建方法:
// Vue.component 的第一个参数是组件名,第二个参数是一个对象
Vue.component('my-component-name', {
// ... 选项 ...
})
// 组件名可以驼峰命名,也可以中线连接(在HTML中使用组件时驼峰命名需变为 my-component-name)
Vue.component('MyComponentName', { /* ... */ })
范例:
<div id="app">
<hello-world></hello-world>
</div>
<script>
// 注册全局组件
Vue.component('HelloWorld', {
template: '<div>{{msg}}</div>',
// 组件的data必须是一个方法,因为每个组件的数据是独立的,使用方法可以保证不共享,return一个对象
data () {
return {
msg: 'hello'
}
}
})
const app = new Vue({
el: '#app',
data: {
msg1: 'hello'
}
})
</script>
4.2.2 局部注册
局部注册组件就只能在当前实例里面使用,局部注册的组件在其他子组件中不可用。
创建方法:
// 局部注册组件
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
// 局部注册的组件在其他子组件中不可用
'component-a': ComponentA
},
// ...
}
// new 一个Vue实例
new Vue({
el: '#app',
components: {
// 在父组件中使用子组件 A B
'component-a': ComponentA,
'component-b': ComponentB
}
})
范例:
<div id="app">
<!-- 这里打印出 hello world -->
<hello-world></hello-world>
<!-- 下面这个地方不能解析,因为这个组件是app1局部的 -->
<hello-vue></hello-vue>
</div>
<div id="app1">
<!-- 这里打印出 hello world -->
<hello-world></hello-world>
<!-- 这里打印出 hello vue -->
<hello-vue></hello-vue>
</div>
<script>
Vue.component('HelloWorld', {
template: '<div>hello world</div>'
})
// 定义一个组件
const HelloVue = {
template: '<h2>hello Vue</h2>'
}
const app = new Vue({
el: '#app',
data: {
}
})
const app1 = new Vue({
el: '#app1',
components: {
// 这里用到解构赋值
HelloVue
}
})
</script>
注意:注册的组件大驼峰命名,在html
里面使用这个组件的时候需要把大写变为小写,然后使用中线连接。
每个template
里面一般都有一个div进行外层包裹。
4.3 组件传递数据
4.3.1 通过 Prop 向子组件传递数据(父 => 子)
props:
处理数据传输,使用这个组件的时候把父组件定义的msg
传递给组件内部,在组件内部通过props来接收, 组件内部的props可以直接作为data使用。
当以绑定的方式传递,这个时候会把app
实例中的data的数据传递过去。
注意:每个子组件都必须在父组件里进行注册。
示例:
<div id="app">
<!-- 打印出 hello component -->
<hello-world msg="hello component"></hello-world>
<!-- 打印出 hello -->
<hello-world :msg="msg1"></hello-world>
</div>
<script>
const HelloWorld = {
template: '<div>{{msg}}</div>',
props: ['msg']
}
const app = new Vue({
el: '#app',
data: {
msg1: 'hello'
},
components: {
HelloWorld
}
})
</script>
如果要传递数字或boolean需要绑定,否则传递过去后会解析成字符串。
props配置属性时,可以校验数据类型,是否必须传 required: true;或者default:0 默认值。
示例:
<!-- 传递数字或者boolean需要绑定,否则传递过去以后解析成字符串 -->
<hello-world msg="hello component" :num="3"></hello-world>
<!-- props中配置属性 -->
props: {
msg: {
type: String,
required: true // 这里代表必传
},
num: {
type: Number,
default: 0 // 这里代表不传时,默认为0
},
isCompleted: Boolean
}
}
props绑定驼峰方式要改写成中线;html
属性名用中线连接,js
使用驼峰。
示例:
<div id="app">
<!-- html属性名用中线 -->
<hello-world :user-name="userName"></hello-world>
</div>
<script>
const HelloWorld = {
template: '<div>{{userName}}</div>',
// 这里的javascript使用驼峰
props: ['userName']
}
const app = new Vue({
el: '#app',
data: {
userName: 'xiaoming'
},
components: {
HelloWorld
}
})
</script>
4.3.2 通过$emit 向父组件传递数据(子 => 父)
$emit
emit 监听子组件事件
由于props
是单向的数据流,只能单向响应父组件向子组件的数据传递。不能响应子组件向父组件的数据传递。
所以如果需要子组件向父组件进行数据响应,就得使用$emit这个自定义属性,利用这个自定义属性发出一个emit事件,事件命名要用中线连接而不用驼峰。
然后在父组件中监听子组件的自定义事件,当子组件emit
这个事件的时候,父组件的这个方法就会响应执行;
父组件中能响应子组件对数据的修改,并且函数方法的第一个参数就是自定义事件传递过来的参数。
注意:子组件的props
一般不允许修改。
示例:
<div id="app">
<!-- 监听 num-add 这个事件 父组件响应事件的方法onParentAddNum-->
<hello-world :num="num" @num-add="onParentAddNum"></hello-world>
{{num}}
</div>
<script>
const HelloWorld = {
template: '<div>{{num}}<button @click="onAddNum">Add</button></div>',
props: ['num'],
methods: {
onAddNum () {
// 自定义emit事件 事件名 num-add
this.$emit('num-add', {
num: this.num + 1
})
}
}
}
const app = new Vue({
el: '#app',
data: {
num: 1
},
methods: {
// 监听事件 响应方法
onParentAddNum (data) {
this.num = data.num
}
},
components: {
HelloWorld
}
})
</script>
4.3.3 通过 bus 向兄弟组件传递数据(兄 => 弟)
event bus 事件总线
事件总线就是利用空的Vue实例来进行两个兄弟组件之间的数据传输 。
方法:
创建一个空的Vue实例 const bus = new Vue();
在第一个兄弟组件中使用bus触发一个emit自定义事件 bus.$emit;
-
在第二个兄弟组件中使用$bus.on进行监听(这个方法写在created(){}中);
解释:created() 这里的代码是Vue实例创建之后执行的
在点击的时候就会触发这个事件,第二个兄弟组件就能响应这个事件;
第二个兄弟组件一上来就需要监听事件总线的事件,响应第一个兄弟组件里通过bus给它的emit的这个事件,就可以接收到传递给它的参数
data
。
示例:
<div id="app">
<ge></ge>
<di></di>
</div>
<script>
const bus = new Vue()
const ge = {
template: '<div><span @click="onDawodi">打我弟</span></div>',
methods: {
onDawodi () {
//
bus.$emit('wuwuwu', {
kusheng: 'yinyinyin'
})
}
}
}
const di = {
template: '<div><span>{{ku}}</span></div>',
data () {
return {
ku: ''
}
},
created () {
// 初始化 就监听 bus 事件总线
// 触发 bus.$emit 的点击事件 onDawodi,马上响应 wuwuwu 这个自定义事件
// 接收传递过来的data,把data的数据赋给当前data方法中的对象
// 数据传输成功
bus.$on('wuwuwu', (data) => {
this.ku = data.kusheng
})
}
}
const app = new Vue({
el: '#app',
components: {
ge,
di
}
})
</script>
4.4 template 标签
可以在html
中写一个template
标签,并给这个标签起一个id,在创建组件的时候使用template关联这个id;在这个标签里就放DOM元素。
在DOM元素div
(id = "app")标签中写入组件标签,在组件的标签中对父组件的数据进行绑定。
示例:
<!-- html 'app' 中使用组件 product -->
<div id="app">
<product
:price="price"
:title="title"
:src="src"
></product>
</div>
<!-- 创建组件 product 的template -->
<template id="product">
<div>
<div class="img">
<img :src="src" alt="">
</div>
<p>{{title}}</p>
<p>¥<span>{{price}}</span></p>
</div>
</template>
<script>
// 定义一个子组件Product
const Product = {
template: '#product',
props: {
src: String,
title: String,
price: Number
}
}
// Vue实例
const app = new Vue({
el: '#app',
data: {
title: '这是最贵的小姐姐',
price: 99,
src: 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2151614847,425289411&fm=111&gp=0.jpg'
},
// 在实例中使用子组件 Product
components: {
Product
}
})
</script>
4.5 动态组件
动态组件就是在不同组件之间进行动态切换,这在实际项目中是常见的。
实现方法:
可以通过 Vue 的 <component>
元素加一个特殊的 is
特性来实现,这个is
特性可以绑定父组件中data数据,然后通过点击时改变这个数据来达到动态切换。
示例:
<template id="guonei">
<div>华为手机要升级了</div>
</template>
<template id="guoji">
<div>apple 倒闭了</div>
</template>
<div id="app">
<div>
<span @click="news = 'guonei'">国内新闻</span>
<span @click="news = 'guoji'">国际新闻</span>
</div>
<!-- 这里直接使用component标签和它的is特性 -->
<component :is="news"></component>
</div>
<script>
const guonei= {
template: '#guonei'
}
const guoji= {
template: '#guoji'
}
const app = new Vue({
el: '#app',
data: {
news: 'guoji'
},
components: {
guonei,
guoji
}
})
</script>
4.6 slot 插槽
slot
(插槽)放在template
标签中占位置,在使用组件的时候,去决定是否需要某个DOM元素,需要的话就写在组件内部(如下图<hello>标签),在template
标签里面的slot
标签就会被这个dom元素自动替换。
当写多个slot
时,需要写上name
属性,使用时添加属性 slot="slot标签的name值"就可以对应起来了。
示例:
<template id="hello">
<div>
<slot name="a"></slot>
商品名称
<slot name="b"></slot>
</div>
</template>
<div id="app">
<hello>
<!-- slot 的值一一对应 slot标签的name值 -->
<b slot="b">包邮</b>
<b slot="a">打九折</b>
</hello>
</div>
<script>
const Hello = {
template: '#hello'
}
const app = new Vue({
el: '#app',
components: {
Hello
}
})
</script>
4.7 过渡 & 动画
transtion
4.7.1 概述
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。
包括以下工具:
- 在 CSS 过渡和动画中自动应用 class
- 可以配合使用第三方 CSS 动画库,如 Animate.css
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
4.7.2 方法
4.7.2.3 animate.css 动画库
参考网站:https://daneden.github.io/animate.css/?
这个是常用的外部工具,里面有很多动画效果,简单易用。使用时只需将文件引入,然后结合自定义过渡类名将属性名改为此工具中对应的属性名,就可以达到相应的动画效果了。
4.7.2.4 过渡模式
当同时生效的进入和离开的过渡不能满足所有要求,这时 Vue 提供了 过渡模式。
-
in-out
:新元素先进行过渡,完成之后当前元素过渡离开。 -
out-in
:当前元素先进行过渡,完成之后新元素过渡进入。
4.7.2.5 列表过渡
transition
过渡只能作用于一个元素;多个元素需要过渡时(列表过渡),需要使用 transition-group
组件,其他的和一般过渡一致。
4.8 Vue 生命周期(lifecycle )
所有的生命周期钩子自动绑定 this
上下文到实例中,因此可以直接访问数据,对属性和方法进行运算。
注意:不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos()
)。
图示:[图片上传失败...(image-ebc822-1566829451674)]
生命周期总体来说有四段:
-
create创建vue实例
beforecreate:创建之前
created:创建之后
-
mount 将挂载
mount 解析实例中的数据,通过插值表达式把数据渲染到DOM元素上(挂载到DOM元素上)。
beforemount:挂载之前
mounted:挂载之后
-
update更新
在updated处,根据虚拟DOM更新真实DOM(做数据更新)
beforeupdate:创建之前
updated:创建之后
-
destroy销毁
beforedestroy:创建之前
destroyed:创建之后
注意:ajax
数据请求一般在 created
或者 beforemount
去发送。
4.9 虚拟DOM
基于真实dom
,构建的一个虚拟的dom
,数据操作时是对虚拟dom
进行操作。它有一个脏检查,用来检查旧的数据和新的数据有什么区别,然后对旧的数据进行更新。
原理:
根据真实DOM构建一个虚拟DOM(js对象)。
当真实DOM要修改的时候;会先修改虚拟DOM;然后用修改之后的虚拟DOM跟真实的DOM进行脏检查(比对)。
-
把修改过后的那一部分重新渲染到真实DOM上。
注意:脏检查运用的算法 — diff算法
好处:虚拟DOM可以避免多次的DOM操作,提高性能。(频繁请求DOM影响性能)
查询 super() 用法
5. router 路由
网址 :https://router.vuejs.org/zh/installation.html
5.1 使用router的步骤
1. 写一个 router.js 文件 引入需要配置的组件,再使用router ;
得到一个router的实例,并导出 ;通过routers字段配置路由;
跳转的路由路径,给组件起个名字,对应的组件
// 定义路由,导出路由
// 1. 引入核心模块(包)
import Vue from 'vue'
import Router from 'vue-router'
// 2. 引入需要配置的组件
import Home from './views/Home.vue'
import About from './views/about.vue'
// 3. 使用 router (类似于中间件)
Vue.use(Router)
// 4. new 一个router实例,并导出
export default new Router({
// 5. 通过routes字段配置路由
routes: [
{
path: '/', // 跳转的路由路径
name: 'home', // 给组件取个名字
component: Home // 这个路由对应的组件
},
{
path: '/about',
name: 'about',
component: About
}
]
})
2. main.js 引入router.js
实例化vue的时候把router配置进来,我们#app这个实例中就可以使用配置好的router了。
import Vue from 'vue'
import App from './App.vue'
// 引入了 router.js
import router from './router'
Vue.config.productionTip = false
new Vue({
// 实例化Vue 的时候吧router配置进来,我们#app这个实例里就能使用配置好的 router了
router,
render: h => h(App)
}).$mount('#app')
3. App.vue
<router-link to="组件名"/>会渲染成a标签,to就相当于href,就是要跳转的那个组件的path
<router-view/>必须的,组件跳转的显示位置,即点击home这儿就显示home组件
<template>
<div id="app">
<div id="nav">
<!-- router-link默认会渲染成 a标签,to 相当于 href,就是要跳转的那个组件的path -->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<!-- router-view 必须写的!组件跳转的显示位置 作用:显示跳转组件的位置-->
<!-- 点击home这儿就显示home组件。即点击谁就显示相对应的组件 -->
<router-view/>
</div>
</template>
嵌套路由:
在router.vue文件中配置路由时,当一个组件中还有其他组件视图,就需要进行路由嵌套。在当前配置路由的地方加上children属性,在其中配置子路由。
5.2 router 组件跳转
一级路由
组件之间需要跳转时,就要用到路由来进行解决(利用路由进行跳转)。
二级路由
=> 动态路由 + 传参 ,实现子组件跳转
路由嵌套,
传参方式: 传参方式: 1. to="/list/man" 这是通过path进行路由传参。只能传一个参数
传参方式: 2. :to="{ name: 'category', params: {cateName: 'woman'}}" 这是通过绑定 to 后,给这个对象传组件名称,通过组件名来跳转,params 也是个对象,可以实现传多个参数。start这个参数未在路由内配置,但是能够被接收,这个叫做隐式传参。
3. :to="{ name: 'Category', params: {cateName: 'kid', start: 0}, query: { end: 200 }} query传参,通过 ? 连接参数,显示在url上。
跳转方式: router-link + to ,声明式
router.push({ path: '/' } }) 在js中编写, 编程式 (如:返回首页)
router-view 命名视图
重定向
点击该路由,跳转到其他页面(非该路由对应页)去。
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
重定向也可以是一个命名路由
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
也可以是一个方法,动态返回重定向目标
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
5.3 导航守卫
5.3.1 全局配置导航守卫
在每一次导航发生改变(路由跳转)之前,都会进入这个钩子函数,在这个函数里只有调用了 next 才会跳转。一般用于全局的权限验证。
// router.beforeEach
// 注册一个全局导航守卫
const router = new VueRouter({ ... })
router.beforeEach((to, from, next)=> {
在进入 to 之前进行登录(权限)验证
if(to.){
} else {
}
})
// router.afterEach
// 注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身
router.afterEach(()=> {
if(){
} else {
}
})
()中有三个参数,分别是:to from next
解释: from表示当前路由路径
to表示即将跳转至的路由路劲
next()需要跳转时,必须调用
注意:beforeEach
指路由跳转之前,这时this
指向undifend
在每一次导航发生之前都会进入这个钩子函数,在这个函数里只有调用了next才会跳转,一般用于全局 的权限验证(比如登录)。判断当前要跳转的那个组件是否需要权限;再验证是否已登录,没有登录就中断当前导航,进入新的导航;不需要权限验证就直接进入当前导航。
补充:
to和from是将要进入和将要离开的路由对象,路由对象指的是平时通过this.$route获取到的路由对象。
next:Function 这个参数是个函数,且必须调用,否则不能进入路由(页面空白)。
next() 进入该路由。
next(false): 取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)。
-
next 跳转新路由,当前的导航被中断,重新开始一个新的导航。
我们可以这样跳转:next('path地址')或者next({path:''})或者next({name:''})
且允许设置诸如 replace: true、name: 'home' 之类的选项
以及你用在router-link或router.push的对象选项。
5.3.2 路由独享导航守卫
beforeEnter()
参数:to from next (同上)
// 路由配置上直接定义 beforeEnter 守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
5.3.3 组件内的守卫
路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
-
beforeRouteUpdate
(2.2 新增) beforeRouteLeave
beforeRouteEnter
守卫 不能 访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
在下列组件外层套一个组件。因为这个是组件内的守卫。
<!-- 进入之前 -->
<!-- 第一次进入使用 beforeRouteEnter 只是在进入时调用一次 -->
beforeRouteEnter (to, from, next){
// 在渲染该组件的对应路由被 confirm 前调用
// 不能获取组件实例 this
// 因为守卫执行前,组件实例还没有被创建
// 在next 里可以传递一个回调函数,这个回调函数的第一个形参就是this,即VM = this
next(vm => {
})
}
<!-- 修改更新 之后每次修改更新都用 beforeRouteUpdate -->
beforeRouteUpdate (to, from, next){
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
}
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
5.4 路由懒加载
路由懒加载 => 能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,更加高效。
问题:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。
解决方法:
结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
1.通过注释语法,可以把添加相同注释语法的组件打包到一个js 文件里。
7. Vuex
Vuex 的状态存储是响应式的。Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态。全局状态管理Vuex.Store是一个单例
状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
“单向数据流”理念的简单示意:
多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
7.1 vuex使用
7.1.1 安装
npm install vuex --save
// 引入
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
7.1.2 创建 store
安装 Vuex 之后,让我们来创建一个 store。创建过程直截了当——仅需要提供一个初始 state 对象和一些 mutation
// 模块化环境中
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
新建一个store目录---创建index.js---编写内容state、getters、actions、mutations、modules
提交一个mutation,这个mutation在store的实例中定义的 state只能在mutation中mutate
只能传一个参数,可以传对象或数组。
通过 store.state
来获取状态对象,以及通过 store.commit
方法触发状态变更:
store.commit('increment')
7.2 核心概念
state 数据源
mutation 修改数据,提交mutation修改
getter state 中派生出一些状态,相当于 store 的计算属性,根据state计算
-
action 类似mutation,mutation中只能写同步;action可以包含任意异步操作,action提交的是mutation,而不是直接变更状态
参数:context,可以理解为store,通过commit提交mutation context.commit('方法')
如果需要通过异步去修稿state,那么就需要在action中写异步代码,异步返回的时候再提交mutation,进行数据处理 。
mapState 返回一个对象,可以将其映射到computed中
...mapState([‘count’])
...mapMutations(['count'])
7.3 规则
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
8. 生命周期 !!!
8.1 Vue的生命周期
create 创建
mount 挂载
update 更新
-
destroy 销毁
在前面加上 before ,表示在 * *之前; 过去时态,表示在 * * 之后
8.2 导航守卫的生命周期
全局守卫:
- router.beforeEach 全局前置守卫 进入路由之前
- router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用
- router.afterEach 全局后置钩子 进入路由之后
路由组件内的守卫:
- beforeRouteEnter 进入路由前
- beforeRouteUpdate (2.2) 路由复用同一个组件时
- beforeRouteLeave 离开当前路由时
8.3 keep-alive 生命周期
Vue提供了一个内置组件keep-alive
来缓存组件内部状态,避免重新渲染,文档网址
<keep-alive>
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
keep-alive
大多数使用场景就是这种。
<keep-alive>
<component :is="view"></component>
</keep-alive>
// 或者
<keep-alive>
<router-view></router-view>
</keeo-alive>
生命周期:
在被keep-alive
包含的组件/路由中,会在原有的Vue生命周期前提下,多出两个生命周期的钩子:activated
与 deactivated
。
- activated在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。
- deactivated:组件被停用(离开路由)时调用
使用了keep-alive就不会调用 beforeDestroy (组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了。
这个钩子可以看作
beforeDestroy
的替代。
MVVM
1 mvvm模式
MVVM是Model-View-ViewModel的简写,即模型-视图-视图模型。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。这些事是 ViewModel
自动处理的,viewmodel 可以在取出 Model 的数据同时帮忙处理 View 中需要涉及的业务逻辑。
它有两个方向:一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。
2 MVVM模式的组成部分
-
模型 (model)
【模型】指的是后端传递的数据。模型 是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。
-
视图 (view)
-
视图模型 (viewModel)
【视图模型】mvvm模式的核心,主要处理业务逻辑和获取数据,它是连接view和model的桥梁。视图模型 是暴露公共属性和命令的视图的抽象。
MVVM
没有MVC模式的控制器,也没有MVP模式的presenter
,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器之间进行通信。 -
绑定器
声明性数据和命令绑定隐含在MVVM模式中。在Microsoft解决方案堆中,绑定器是一种名为XAML的标记语言。绑定器使开发人员免于被迫编写样板式逻辑来同步视图模型和视图。在微软的堆之外实现时,声明性数据绑定技术的出现是实现该模式的一个关键因素。
ViewModel是Vue.js的核心,它是一个Vue实例。Vue和React都是MVVM模式的框架。
3 MVVM优点
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点:
1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xaml代码。
4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
4 MVVM模式的框架
- Vue
- React
5 MVC与MVVM区别
- MVVM是MVC是改进版,MVVM中ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller。MVVM实现的是业务逻辑组件的重用。
- 使用MVC的目的就是将M和V的代码分离。‘MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。