MVVM模式
MVVM是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对 View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定
一、基础用法
1. 模板语法和指令
普通常用:
v-cloak //vue 未解析之前 一般配合用 [v-cloak] {display: none;}
v-bind 或者缩写 <img :src="src" alt="" />
v-on 或者缩写 <button @click="greet">Greet</button>
v-text <span v-text="msg"></span> || <span>{{msg}}</span>
//v-text 权重比{{}}高
v-html <div v-html="html"></div>
v-ref //
<child :ref="head"></child> //给子组件一个ref命名
vm.$refs.head //父组件可通过该方法读取修改子组件的属性 和调用子组件的方法
//显不显示 也可以搭配v-else使用
v-show
<div v-show="show">aaa</div>
<div v-else>bbb</div>
//宣不渲染 存不存在 条件
v-if v-else-if v-else
<div v-if="status"></div>
v-model //表单元素,双向绑定
<input type="text" v-model="mess"/>
v-pre //不编译,当字符串输出
<span v-pre>{{mess}}</span>
v-once //内容解析一次,当改变值再改变也不映射修改
<span v-once>{{mess}}</span>
循环v-for Array | Object | Number | String
这个单独拿出来写 更清晰:
一般jsfor in是遍历key,而for of遍历value
一类:
data = {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
data = [{},{},{}]
<div v-for="(value, index) in data">
<span>{{value}}</span>
<span>{{index}}</span>
</div>
二类:
data={
"key1":{
"aa":"1","bb":"2"
},
"key2":{
"aa":"2","bb":"4"
}
}
<div v-for="(item,key,index) of data">
<span>{{item}}</span>
<span>用of就能访问到 key1|key2 :{{key}}</span>
<span>{{index}}</span>
</div>
2. 样式绑定
绑定 class
对象:
<div :class="{classNam1: 1 == 1, className2: 1 == 2}"></div>
单个:
<div :class="classObject"></div>
数组:
<div :class="[class1, class2, 'className3', active ? 'className4' : '']"></div>
对应的data
data: {
class1: 'className1',
class2: 'className2',
active: true
}
绑定再方法上:
<div :class="classObjectComputed"></div>
computed: {
classObjectComputed: function(){
return{
className1: true,
className2: true
}
}
}
---------------------------------------------------
绑定style
在对象当中,CSS 的属性名要用驼峰式表达:fontSize 解析成 font-size
对象:
<div :style="{color: color, fontSize: fontSize, backgroundColor: '#ccc'}"></div>
data: {
color: 'red',
fontSize: '12px'
}
数组:
<div :style="[styleObject, {backgroundColor: '#ccc'}]"></div>
data: {
styleObject: {
color: 'red',
fontSize: '12px'
}
}
3. 事件修饰符 .**
<!--阻止事件冒泡.stop 等同于event.stopPropagation()-->
<div id="div1" class="stop" @click.stop="event1(1)">
<!--使用事件捕获模式.capture-->
<div id="div4" class="stop" @click.capture="event1(4)">
<!--事件只作用本身.self,类似于已阻止事件冒泡-->
<div id="div7" class="stop" @click.self="event1(7)">
<!--阻止浏览器默认行为.prevent 等同于event.preventDefault()-->
<a href="https://m.baidu.com" target="_blank" @click.prevent="prevent">dk's github</a>
<!--只作用一次.once-->
<a href="https://m.baidu.com" target="_blank" @click.once="prevent">dk's github</a>
<!--修饰符可以串联.click.prevent.once-->
<a href="https://m.baidu.com" target="_blank" @click.prevent.once="prevent">dk's github</a>
二、Vue实例化时基本属性
1. 实例元素 el
实例化在哪个容器里 如果有多个相同的实例元素则只有第一个起效。
##挂载方式一:
<div id="app"></div>
var vm = new Vue({
el:"#app" //挂载方式一:常用方式
})
##挂载方式二:
<div id="app"></div>
vm.$mount("#app"); //挂载方式二: 手动进行挂载
console.log(vm.$el) // 可以通过实例获取实例元素
2. 数据对象 data
M(Model) 数据模型层 映射到V层。
(1) 实例创建之后,vm.$data可访问原始数据对象。Vue实例也代理了 data 对象上所有的 property,因此访问vm.a等价于访问vm.$data.a
。
(2) 以_或$开头的 property不会被 Vue 实例代理,可使用例如vm.$data._property
的方式访问这些 property。
(3) 当一个组件被定义,data必须声明为返回一个初始数据对象的函数
,使之创建独立作用域,多次调用同个组件时是个全新副本数据对象不相互影响。
(4) 注意,如果你为data property 使用了箭头函数,则this
不会指向这个组件的实例
关于视图不更新怎么解决?
方法一: vm.set()
对象用法:vm.$set(Object, key, value)
数组用法:vm.$set(Array, index, Value)
1.对于对象,如果要给对象添加新的属性,数据变化 视图没变化。
Vue在初始化实例时进行双向数据绑定,使用Object.defineProperty()对属性遍历添加 getter/setter 方法,所以setter属性必须在 data 对象上存在时才能进行setter过程,触发视图响应。此时需要用到$set
视图不更新:this.dataform.username = '123'; // 直接赋值 在视图上不显示
视图更新:this.$set(this.dataform, 'username', '123'); //改用 $set 更新可以在视图上显示
2.对于数组,由于 JavaScript 的限制,Vue不能检测以下变动的数组。
//以下两操作均无法触发视图更新。其余操作正常,另外如果用到splice删除后引起长度变化 注意
#视图不更新:this.arr[index] = val;
#视图更新:this.$set(this.arr, index, val);
#视图不更新:this.arr.length = 2;
#视图更新:this.arr.splice(2);
//对于清空数组推荐
this.arr = [];
方法二:vm.forceUpdate()
可是如果我们不想利用$set
去设置,非要按照我们第一种方式去写,可以实现么?答案是可以的,就是利用$forceUpdate
了,因为你修改了数据,但是页面层没有变动,说明数据本身是被修改了,但是vue没有监听到而已,用$forceUpdate
就相当于按照最新数据给渲染一下。
change: function(index) {
this.arr[index] = '9';
this.$forceUpdate();
},
clearLen: function() {
this.arr.length = 2;
this.$forceUpdate();
}
3. 事件处理器 methods
元素可以通过事件进行绑定事件。
注意,不应该使用箭头函数来定义 method 函数
(例如 plus: () => this.a++)。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.a 将是 undefined。
4. 计算属性 computed
主要是针对 data 的属性进行操作,this的指针默认指向实例vm。
可以像绑定普通属性一样在模板中绑定计算属性
<div id="example">
<p>原本值: "{{ basicNum }}"</p>
<p>计算后的值: "{{ countNum }}"</p>
</div>
<script>
var vm = new Vue({
el: '#example',
data: {
basicNum: 0
},
computed: {
// 计算属性的 getter
countNum: function () {
return Math.floor(this.basicNum*999/100);
}
}
})
</script>
// 设basicNum = 3 >> countNum = 29
// 设countNum = 28 报错 因为计算属性默认情况下不支持set设值
默认只有getter没有setting,只能读取不能修改。 需要支持修改的话 要手动加上去 like this:
<div id="app">
<!--fullName.get 只被调用一次-->
<p>{{fullName}}</p>
<p>{{fullName}}</p>
<p>{{fullName}}</p>
<!--每次点击都会调用 changeName-->
<input type="button" value="changeName" @click="changeName('Vue')">
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
firstName:'DK',
lastName: 'Lan',
newName: ''
},
computed: {
fullName:{
get: function(){
return this.firstName + '.' + this.lastName
},
set: function(newValue){
this.firstName = newValue
}
}
},
methods: {
changeName: function(txt){
this.newName = txt;
//如果在这里改变 this.fullName 的值,则会再次自动触发对应的 getter
}
}
})
</script>
5. 监听器 watch
当该属性发生改变的时候,自动触发,此项使用不当会影响性能,慎用
<div id="example">
<p>输入的值: <input v-model="basicNum"></p>
<p>计算后的值: "{{ countNum }}"</p>
</div>
<script>
var vm = new Vue({
el: '#example',
data: {
basicNum: 0
},
watch: {
// 侦听 data属性 这个属性一旦变化
basicNum: function () {
return Math.floor(this.basicNum*999/100);
}
}
})
//补充 computer 和 watch 还都支持对象的写法。
vm.$watch('obj', {
// 深度遍历
deep: true,
// 立即触发
immediate: true,
// 执行的函数
handler: function(val, oldVal) {}
})
</script>
computed与watch区别
- 相同:
computed和watch都起到监听/依赖一个数据,并进行处理的作用 不同:
1.computed 创建新的属性, watch 监听 data 已有的属性
2.computed 会产生依赖缓存
3.当 watch 监听 computed 时,watch 在这种情况下无效,仅会触发 computed.setter{ computed: { a: { get: function(){ return ''; }, set: function(newVal){ //会触发此项 console.log('set val %s', newVal); } } }, watch: { a: function(){ //不会被触发 console.log('watch'); } } }
所以一般来说需要依赖别的属性来动态获得值的时候可以使用computed
,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用watch
6. 过滤器 filters
(1) 介绍
<div id="app">
<!-- 用法一:在双花括号中 -->
{{ money | monerFilter }}
<!-- 用法二:在表达式中 版本要求2.1.0+ -->
<div v-bind:id="rawId | formatId"></div>
</div>
<script>
//定义方法一:vue选项中定义本地的过滤器
var vm = new Vue({
el: '#example',
filters: {
capitalize: function (value) {
return "¥"+ value
}
}
})
//定义方法二:在vue实例化之前全局定义过滤器
Vue.filter('formatId', function (value) {
return value + "_total"
})
new Vue({
// ...
})
</script>
(2) 可调用多个过滤器: {{msg | filterA | filterB}}
<template>
<div>
{{msg | filterA | filterB}} <!-- //可调用多个过滤器 -->
</div>
</template>
<script>
export default {
data () {
return {
msg: '5' //数据
}
},
filters: {
filterA(val) {
return val - 0 // 返回值
},
filterB(val) {
return val + 12 // 返回值
}
}
}
</script>
(3) 允许传入多个参数: {{msg|filterA(a,b,c)}}
<template>
<div>
{{msg | filterA('转换结果',false)}}
</div>
</template>
<script>
export default {
data () {
return {
msg: '5' //数据
}
},
filters: {
filterA(val) {
console.log(arguments); // ["5","转换结果",false] msg是第一个值 后面是传入的值
let [a,b,c] = arguments;
return c ? Number(a).toFixed(2) : `${b}: ${a}` // 返回值
}
}
}
</script>
7. 生命周期
(1) vue 生命周期:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed
var app = new Vue({
el: '#app',
data: {
msg: "你好,我是初始值"
},
beforeCreate: function() {
console.log(this.$el,this.$data,this.msg);
// this.$el >> undefined ; this.$data >> undefined ; this.msg >> undefined
},
created: function() {
console.log(this.$el,this.$data,this.msg);
// this.$el >> undefined ; this.$data >> 有值 ; this.msg >> 已被初始化 "你好,我是初始值"
},
beforeMount: function() {
console.log(this.$el,this.$data,this.msg);
// this.$el >> 有值 ; this.$data >> 有值 ; this.msg >> 已被初始化 "你好,我是初始值"
// <p>{{msg}}</p> 页面调用的东西依然是代码状态
},
mounted: function() {
console.log(this.$el,this.$data,this.msg);
// this.$el >> 有值 ; this.$data >> 有值 ; this.msg >> 已被初始化 "你好,我是初始值"
// <p>你好,我是初始值</p> 已挂载 页面调用的东西渲染出数据
},
// 某个地方修改值后触发beforeUpdate、updated;如 vm.msg = '你好,我更新了'
beforeUpdate: function() { //数据已更新调用,发生在虚拟 DOM 打补丁之前,DOM 未更新时。
console.log(this.$el,this.$data,this.msg);
// this.$el >> 有值 ; this.$data >> 有值 ; this.msg >> "你好,我更新了"
console.log(this.$el.innerHTML); //"你好,我是初始值"
console.log(this.$el); //展开和updated一样都是更新后的 因为当点开下面的箭头展开具体内容时,显示的是该指针指向对象的当前内容,因此看来,两个都一样。
},
updated: function() {
console.log(this.$el,this.$data,this.msg);
// this.$el >> 有值 ; this.$data >> 有值 ; this.msg >> "你好,我更新了"
console.log(this.$el.innerHTML); //'你好,我更新了'
console.log(this.$el); //更新后的
},
//例如触发 app.$destroy(); 销毁vm实例与DOM之间的关联
//beforeDestroy钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。
//destroyed后,DOM所有东西仍然存在,实例指示的所有东西都会解绑定,所有的事件监听器会被移除, 不过后续就不再受vue控制。
beforeDestroy: function() {
console.log(this.$el,this.$data,this.msg);
},
destroyed: function() {
console.log(this.$el,this.$data,this.msg);
}
})
(2) 组件还有两个生命周期函数
activated,deactivated
export default {
name: "MyFamily",
components:{
FamilyItem
},
data(){
},
//在创建vue对象时,当html渲染之前就触发;created()只会触发一次;
created() {
console.log('进来执行了');
},
//每次进入当前存在activated()函数的页面时就触发;
activated(){
console.log('activated');
},
deactivated(){
console.log('deactivated');
}
}
(3) 第一次页面加载会触发哪几个钩子?beforeCreate、created、beforeMount、mounted
(4) DOM 渲染在哪个周期中就已经完成?
DOM 渲染在 mounted 中就已经完成了,但 mounted 不会保证所有的子组件也都一起被挂载。如希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick
mounted: function () {
this.$nextTick(function () {
// do something
})
}
8. 自定义指令 directives
除了默认内置的指令 (v-model
和v-show
),Vue允许注册自定义指令,可分全局指令和局部指令。
全局指令
<div id="app">
<!--使用自定义的指令 v-myGlobalDir-->
<input type="text" value="" v-myGlobalDir />
</div>
<script>
// 注册指令名称不用写前缀 v-
// 参数 element:使用指令的元素
Vue.directive('myGlobalDir', function(element){
//默认触发钩子函数的 inserted
element.value = "世界和平";
element.focus();
})
var vm = new Vue({
el: '#app'
})
</script>
局部指令
<div id="app">
<!--使用自定义的指令 v-privateDir-->
<input type="text" value="" v-privateDir />
</div>
<script>
var vm = new Vue({
el: '#app',
directives: {
//注册指令名称不用写前缀 v-
// 参数 element:使用指令的元素
privateDir: function(element){
element.style.background = '#ccc';
element.value = "世界和平";
}
}
})
</script>
指令的钩子函数
钩子函数可以理解成是指令的生命周期
bind
:指令第一次绑定到元素时调用。可用于初始化。inserted
:被绑定元素插入父节点时调用update
:被绑定元素所在的模板更新时调用。componentUpdated
:指令所在组件完成一次更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
<div id="app">
<!--使用自定义的指令 v-demo-->
<input type="text" v-model="text" v-demo="{color:'red'}">
</div>
<script>
Vue.directive('demo', {
//先于 inserted 触发,只调用一次 可用于初始化
bind: function(element, binding, vnode){
console.log('bind');
element.style.color = binding.value.color
},
//被绑定元素插入父节点时调用 后于 bind 触发
//参数 element: 使用指令的元素; 参数 binding: 使用指令的属性对象; 参数 vnode: 整个 Vue 实例
inserted: function(element, binding, vnode){
console.log('inserted');
},
//被绑定元素所在的模板更新时调用,而不论绑定值是否变化
update: function(element, binding, vnode){
console.log('update');
},
//被绑定元素所在模板完成一次更新周期时调用。
componentUpdated: function(element, binding, vnode){
console.log('componentUpdated');
}
})
var vm = new Vue({
el: '#app',
data:{
text: '钩子函数'
}
})
</script>
案例:自定义日期控件
<div id="app">
<!--直接在 jQuery 环境下使用 datepicker 插件-->
<input type="text" id="datepicker" data-date-format="yyyy-mm-dd"/>
<!--使用 Vue 自定义指令 v-datepicker-->
<input type="text" v-datepicker data-date-format="yyyy-mm-dd"/>
<input type="button" value="保存" @click="save">
<span>{{dataform.birthday}}</span>
</div>
//在没有使用 Vue 前,datepicker 插件在 jQuery 的环境下是这样使用
$('#datepicker').datepicker();
//使用 Vue 自定义指令 v-datepicker
Vue.directive('datepicker', function(element, binding, vnode){
// data = dataform.birthday
$(element).datepicker({
language: 'zh-CN',
pickTime: false,
todayBtn: true,
autoclose: true
}).on('changeDate', function(){
//由于不是手动在 input 输入值,所以双向绑定 v-model 无效
//所以需要手动改变实例的数据模型
var data = $(element).data('model');
if(data){
// datas = ['dataform', 'birthday']
var datas = data.split('.');
//context = vm
var context = vnode.context;
//循环属性自动添加
datas.map((ele, idx) => {
//最后一个属性就直接赋值
if(idx == datas.length - 1){
context[ele] = element.value
} else {
//动态添加属性
context = context[ele]
}
})
}
})
})
var vm = new Vue({
el: '#app',
data: {
dataform: {}
},
methods: {
save: function(){
//使用 $set 更新 dataform
//更多 $set 的使用在下面继续介绍
this.$set(this.dataform)
}
}
})
三、 组件
1. 组件注册使用
(1).组件命名两个选择:
短横线分隔命名 eg:<my-component-name>
;
驼峰命名 eg:<MyComponentName>
;
(2)组件data必须是个函数并return返回,强迫创建一个独立作用域,就算组件多次复用不相互影响。
局部组件
<div id="app">
<!--组件的使用-->
<private-component></private-component>
</div>
//组件的定义 Vue.component(组件名称, {template})
var vm = new Vue({
el: '#app',
components:{
'private-component': {
template: '<h1>局部组件</h1>'
}
}
})
最终渲染的效果
<div id="app">
<h1>局部组件</h1>
</div>
2.插槽
Vue 组件默认是覆盖渲染,为了解决这一问题,Vue 提出了 slot 分发内容,留给父组件注入内容。
//父组件
<div id="app">
<component1>
<h1>Sam</h1>
<h1>Lucy</h1>
</component1>
</div>
//组件留有slot
Vue.component('component1', {
template: `
<div>
<h1>Tom</h1>
<slot></slot>
</div>`
})
>>渲染出来效果
<div id="app">
<component1>
<h1>Tom</h1>
<h1>Sam</h1> //注入
<h1>Lucy</h1> //注入
</component1>
</div>
具名slot
如果要将组件里面不同的子元素放到不同的地方,那就为子元素加上一个属性 slot="名称",然后在组件定义的时候用名称对应位置 ,其它没有 slot 属性的子元素将统一分发到 里面
//父组件
<h1 slot="lucy">Lucy</h1> //在名字为lucy的slot注入内容
//组件内部
<slot name="lucy"></slot>
缩写
(v-slot:
) 替换为字符#
。例如v-slot:header
可以被重写为#header
:
3.动态和异步组件
(1)动态组件 is
<div id="app" style="display: none;">
//切换动态组件
<input type="button" value="changeLight" @click="changeLight" />
<br/>
//渲染读取动态组件
<p :is="show"></p>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
show: 'red',
},
methods:{
changeLight: function(){
this.show = this.show == 'red' ? 'green' : 'red';
}
},
components: {
red: {
template: '<h1>Red</h1>'
},
green: {
template: '<h1>Green</h1>'
}
}
})
</script>
<keep-alive>
组件实例在第一次创建的时候被缓存下来<!-- 失活的组件将会被缓存!--> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
(2) 异步组件
Vue.component('async-example', function (resolve, reject)
resolve 加载成功
reject 加载失败
<template>
<div>
组件一 延迟300毫秒,从服务器加载
组件二 不延迟从服务器加载
<template v-if="loadComponent">
<child></child>
<child2></child2>
</template>
<button @click="toggle">点击异步加载组件</button>
</div>
</template>
<script>
import Vue from 'vue';
const child = Vue.component('child', function (resolve) {
setTimeout(function () {
require(['./child.vue'], resolve)
}, 3000);
});
const child2 = Vue.component('child2', function (resolve) {
require(['./child2.vue'], resolve)
});
export default{
data: function () {
return {
loadComponent: false
};
},
components: {
child,
child2,
},
methods: {
toggle:function () {
this.loadComponent = !this.loadComponent;
}
}
}
</script>
4.组件间的通讯
(1)父传子
父组件在引入子组件时上带属性过去,子组件props接收。
(2)子传父
父组件在引入子组件时v-on订阅监听对应的数据,子组件通过$.emit数据发送给父
(3)兄弟传兄弟
引入公共bus文件
(4)引入插件vueX托管状态
5.动画和过度效果
(1) 设置name, 然后和切换参数搭配设置。
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter: 定义进入过渡的开始状态。在元素被插入之前生效。
v-enter-active: 定义进入过渡生效时的状态。
v-enter-to: 进入过渡的结束状态。
v-leave: 定义离开过渡的开始状态。
v-leave-active: 定义离开过渡生效时的状态。
v-leave-to: 离开过渡的结束状态。
<transition name="fade">
<p v-if="show">hello</p>
</transition>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s; //直接写
//animation: bounce-in .5s; 或者在css动画里写
}
.fade-enter, .fade-leave-to /\* .fade-leave-active below version 2.1.8 \*/ {
opacity: 0; //直接写
//animation: bounce-in .5s; 或者在css动画里写
}
@keyframes bounce-in {
0% {transform: scale(0)}
100% {transform: scale(1)}
}
(2) 自定义过渡的类名
enter-class 定义进入过渡的开始状态。在元素被插入之前生效。
enter-active-class 定义进入过渡生效时的状态。
enter-to-class 进入过渡的结束状态。
leave-class 定义离开过渡的开始状态。
leave-active-class 定义离开过渡生效时的状态。
leave-to-class 离开过渡的结束状态。
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
直接调用了Animate.css的动画,也可以写自己动画。
(3) JavaScript钩子
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
----------------------
methods: {
beforeEnter: function (el) {},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter: function (el, done) { done() },
afterEnter: function (el) {},
enterCancelled: function (el) {},
beforeLeave: function (el) {},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave: function (el, done) { done() },
afterLeave: function (el) {},
leaveCancelled: function (el) {} // leaveCancelled 只用于 v-show 中
}
参考资料:
官网:https://cn.vuejs.org/
学习笔记: https://github.com/Wscats/vue...
过渡:https://segmentfault.com/q/10...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。