关于Vue

简介

是一种渐进式JavaScript框架,Vue被设计为可以自底向上逐层应用

MVC模式(后端概念)
M:model 数据模型(数据保存)
V:View 视图 (用户界面)
C:controller 控制器(业务逻辑)

MVVM模式(前端概念)
M:model 数据模型(数据保存,处理数据业务逻辑)
V:View 视图 (用户界面,展示数据,与用户交互)
VM:View Model 数据模型和视图的桥梁

MVVM模式最大特点是支持数据的双向传递:M -> VM ->V,V -> VM -> M

Vue采用MVVM模式:
被控制的区域:View
Vue实例对象:View Model
实例对象中的data:Model

可在浏览器中安装插件 Vue-devtools 来配合开发使用
github网址:https://github.com/vuejs/vue-devtools

兼容性:
Vue不支持IE8及以下的版本(Vue使用的IE8无法模拟的ES5特性)

官网:https://cn.vuejs.org/

完整的学习Vue:HTML+CSS,JavaScript,CSS3,HTML5,第三方库,网络通信,ES6+(包括ES2015,ES2018,...),webpack,模块化(常见的有CommonJS,ES6 Module,AMD,CMD,UMD),包管理器,CSS预编译器

Vue特点:渐进式,组件化,响应式

Vue的应用场景:前台的部分页面和后台的全部页面

搭建Vue项目的脚手架:Vue-cli,webpack-simple,手动搭建

引入Vue.js

script标签引入:

CDN引入: (生产环境版本,优化了尺寸和速度)     
          (开发环境版本,包含了有帮助的命令行警告)
     

声明式渲染

Vue实例对象:
let vue=new Vue({  //创建一个Vue实例对象
    el:'xxx(id名,class名...)',  //告诉Vue的实例对象,将来需要控制界面的哪个区域
    data:{  //告诉Vue实例对象,被控制区域的数据是什么
        ...
    },
})

e.g.
{{ message }}
var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } }) 注入:配置对象中的部分内容会被提取到vue实例中:data,methods,... 注入的两个目的: 1. 完成数据响应式:vue是怎么知道数据被更改了呢?(vue2.0是通过Object.defineProperty方法完成数据响应式。vue3.0是通过Class Proxy完成的数据响应式) Object.defineProperty无法感知属性的新增和删除等。Class Proxy解决了这些缺陷。 2. 绑定this:this指向vue实例

有关虚拟DOM树

为提高渲染效率,vue会把模板编译成为虚拟DOM树,然后再生成真实的DOM
当数据更改时,将重新编译成虚拟DOM树,然后对前后两棵树进行对比,仅将差异部分反映到真实DOM,这样既可最小程度改动真实DOM,提升页面效率

image.png

对于Vue而言,提升效率重点是两方面:
1. 减少新的虚拟DOM生成
2. 保证对比之后,只有必要的节点有变化

Vue提供多种方式生成虚拟DOM树:
1. 在挂载的元素内部直接书写,此时将使用元素的outerHTML作为模板
2. 在template配置中书写
3. 在render配置中用函数直接创建虚拟节点树,此时完全脱离模板,将省略编译步骤:render(h){return h('元素节点','xxx ')},render(h){return h('元素节点',[h('子节点','xxx')])}
以上步骤从上到下,优先级逐渐提升

虚拟节点树必须是单根的

将生成的真实DOM树,放置到某个元素位置,称为挂载
挂载的方式:
1. 通过el:'css选择器'进行配置
2. 通过vue实例.$mount('css选择器')进行配置

Vue指令

1. v-if,v-else,v-else-if:条件语句
    
xxxxxxx
xxxxxxx
xxxxxxx
2. v-show:显示(改变display)
xxxxxxx
3. v-for:循环(数组,对象,数字,字符)
{{ item.xxx }}
...
...
...
var app = new Vue({ el: '#app', data: { items:['1','2','3'], items1:[ { name:'xx', age:'xx' }, { name:'xx', age:'xx' }, ], object:{ name:'xxx', age:'xx', work:'xxx', married:'xx' } } }) v-for注意点: 1. v-for在渲染元素时,会先查看缓存中有没有需要渲染的元素 2. 若缓存中没有需要渲染的元素,就会创建一个新的放到缓存中。 若缓存中有需要渲染的元素,就不会创建一个新的,而是直接复用原有的 3. 在vue中只要数据发生了改变,就会自动重新渲染 可在for循环中绑定一个唯一的标识以防重新渲染新数据时发生错误:
...
4. v-text和v-html:更新元素的 textContent和innerHTML => {{msg}}
5. v-on:event 绑定事件(缩写@) / var app = new Vue({ el: '#app', methods:{ add(){...}, aa(n,e){...} } }) 事件event上的修饰符: .stop - 调用 `event.stopPropagation()`阻止冒泡发生 .prevent - 调用 `event.preventDefault()`阻止默认事件发生 .capture - 让默认情况下是事件冒泡变成事件捕获 .self - 只有当事件是从侦听器绑定的元素本身触发时才触发回调 .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调 .native - 监听组件根元素的原生事件 .once - 只触发一次回调 .left - (2.2.0) 只当点击鼠标左键时触发 .right - (2.2.0) 只当点击鼠标右键时触发 .middle - (2.2.0) 只当点击鼠标中键时触发 .passive - (2.3.0) 以 `{ passive: true }` 模式添加侦听器 部分用法: 6. v-model:在表单控件或者组件上创建双向绑定 var app = new Vue({ el: '#app', data:{ num:2 } }) 此时输入框的文本值为2 7. v-bind:给元素属性绑定数据。动态地绑定一个或多个 attribute,或一个组件 prop 到表达式(缩写::) (绑定属性) (内联字符串拼接)
(class的绑定) ... 一些修饰符: .prop - 作为一个 DOM property 绑定而不是作为 attribute 绑定。 .camel - (2.1.0+) 将 kebab-case attribute 名转换为 camelCase。(从 2.1.0 开始支持) .sync (2.3.0+) 语法糖,会扩展成一个更新父组件绑定值的 `v-on` 侦听器。 8. v-pre:跳过这个元素和它的子元素的编译过程(原样输出) {{ this will not be compiled }} 9. v-cloak:这个指令保持在元素上直到关联实例结束编译(渲染完成后显示) 当网络较慢,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码。我们可以使用 v-cloak 指令来解决这一问题 若和CSS`[v-cloak] { display: none }` 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕 css:[v-cloak] { display: none; } HTML:
{{ message }}
10. v-once:只渲染元素和组件一次 This will never change: {{msg}} 11. v-slot:插槽。提供具名插槽或需要接收 prop 的插槽。(缩写:#) //组件调用时 //书写组件时 //组件调用
//书写组件时 12. 绑定类名: 想要在style中找对应的类并绑定到元素中::class=['xxx',...]
可用三目运算符实现按需绑定类名:
可通过对象来决定是否要绑定类名:
可在Vue实例对象的data中添加一个对象,含需要绑定的类名值:
data{ obj:{ 'xxx':true, 'xx':false, 'xx':true, ... } } 13. 绑定样式: 和绑定类名类似(若属性名中有'-',则必须用''把属性名引起来)
也可好像绑定类名一样,在data定义一个对象(含属性名和属性值):
若有多个对象,则把其放进数组中:

特殊属性

key:该属性可以干预diff算法,在同一层级,key值相同的节点会进行比对,key值不同的节点则不会
可应用到的场景:
1. 切换登录方式(应用key不会只改变label,会让label改动,旧节点移除,新节点产生)
2. 循环生成的节点中,给予每个节点唯一且稳定的key值
...

Vue自定义指令

自定义全局指令:
Vue.directive('指令名',(el,binding,vnode)=>{});
其中各参数的解析:
el:指令所绑定的元素,可以用来直接操作 DOM
binding:一个对象,包含以下 property:name(指令名,不包括 `v-` 前缀)
                                     value(指令的绑定值,例如:`v-my-directive="1 + 1"` 中,绑定值为2)
                                     oldValue(指令绑定的前一个值,仅在 `update` 和 `componentUpdated` 钩子中可用。无论值是否改变都可用)
                                     expression(字符串形式的指令表达式。例如 `v-my-directive="1 + 1"` 中,表达式为 `"1 + 1"`)
                                     arg(传给指令的参数,可选。例如 `v-my-directive:foo` 中,参数为 `"foo"`)
                                     modifiers(一个包含修饰符的对象。例如:`v-my-directive.foo.bar` 中,修饰符对象为 `{ foo: true, bar: true }`)
vnode:Vue 编译生成的虚拟节点

e.g. 
Vue.directive('changecolor',(el,binding,vnode)=>{el.style.background=binding.value}) 一个指令定义对象提供的几个钩子函数: bind:function(){}:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置 inserted:function(){}:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中) update:function(){}:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下) componentUpdated:function(){}:指令所在组件的 VNode 及其子 VNode 全部更新后调用 unbind:function(){}:只调用一次,指令与元素解绑时调用。 Vue.directive('xxx',{ bind:function(){}, //指令被绑定到元素上时执行 inserted:function(){}, //绑定指令的元素被添加到父元素上时调用 update:function(){}, componentUpdated:function(){}, unbind:function(){} }) 自定义局部指令: new Vue({ el:'xxx(要求在此元素中局部应用)', directives:{ '指令名':{ bind:function(el,obj){ .... } } } })

Vue 扩展实例构造器

Vue.set(target, propertyName/index, value)

解决不能通过索引设置数组的值的问题

e.g. 写一个方法来改变数组arr中的第二个值
    methods:{
        change:function(){
            //this.arr[1]='x';(错误写法)
              Vue.set(this.arr,1,'x');(正确写法)
         };
      }

Vue 定义方法(函数)methods

methods:{}  (用来存储(定义)一些监听事件的回调函数(方法))
new Vue({
    methods:{
        xx(){
            ...
        }
    }
})

Vue 计算属性 computed

computed:{}  (专门用于定义计算属性的)
new Vue({
    computed:{
        //仅访问器
        xx(){
            return ...
        }
        //访问器+设置器
        xxx:{
            get(){  //得到值
                return ...
            },
            set(val){  //设置值
                ...
            }
        }
    }
})

computed(计算属性)和methods(方法)的区别:
计算属性可以赋值,而方法不行
计算属性会进行缓存,如果依赖不变,则直接使用缓存结果,不会重新计算
凡是根据已有数据计算得到新数据的无参数函数,都应尽量写成计算属性,而不是方法

特点:只要计算返回的结果没有发生变化,那么计算属性就只会被执行一次
应用场景:由于计算属性会将返回的结果缓存起来,所以如果返回的数据不经常发生变化,那么使用计算属性(computed)的性能会比使用函数(methods)的性能高

Vue 过滤器

过滤器和函数,计算属性一样也是用来处理数据的
但过滤器一般用于格式化插入的文本数据
过滤器只能在插值语法和v-bind中使用
过滤器可连续使用

过滤器的定义:
自定义全局过滤器:
Vue.filter('过滤器名',过滤器处理函数function(value){...  return xxx}) (默认情况下处理数据的函数要接收一个参数,就是当前要被处理的数据)
自定义局部过滤器:
new Vue({
    el:'xxx(绑定要局部使用的元素区域)'
    filters:{
        '过滤器名':function(value){...}
    }
})

过滤器的使用:(Vue会把要处理的数据交给指定的过滤器处理,把处理之后的结果插入到指定的元素中)
{{处理的数据 | 过滤器名}}
/
过滤器连续使用:
{{处理的数据 | 过滤器名1 | 过滤器名2 | ...}}
可在过滤器中添加参数:
{{处理的数据 | 过滤器名('xxx')}}
e.g. 用过滤器对时间进行格式化
{{time | formatDate('yyyy-mm-dd')}}
Vue.filter('formatDate',function(value,fmstr){ let date=new Date(value); let year=date.getFullYear(); let month=date.getMonth() + 1 + ''; let day=date.getDate() + ''; let hour=date.getHours() + ''; let minute=date.getMinutes() + ''; let second=date.getSeconds() + ''; if(fmstr && fmstr === 'yyyy-mm-dd'){ return `${year}-${month.padStart(2,'0')}-${day.padStart(2,'0')}`; } return `${year}-${month.padStart(2,'0')}-${day.padStart(2,'0')}-${hour.padStart(2,'0')}-${minute.padStart(2,'0')}-${second.padStart(2,'0')}`; }) new Vue({ el:'#app', data:{ time:Date.now(); } })

Vue 过渡动画

将需要执行动画的元素放到transition组件中

对于使用css属性来过渡动画的:
当transition组件中的元素显示时会自动查找 .v-enter/.v-enter-active/.v-enter-to 类名
当transition组件中的元素隐藏时会自动查找 .v-leave/.v-leave-active/.v-leave-to 类名
动画初始化的设置(第一次进入时就有动画):可通过给transition添加appear属性
给多个不同的元素指定不同的动画:可通过gettransition指定name的方式来指定'进入之前/进入之后/进入过程中',离开之前/离开之后/离开过程中'对应的类名来实现不同的元素执行不同的过渡动画

过渡动画的实现:
1. 利用css属性:


    
多个transition同时使用:
2. 在属性中声明javascript钩子函数:
new Vue({ methods:{ beforeEnter(el){ el.style.opacity='0'; ... }, enter(el,done){ //动画执行完毕后一定要调用done回调函数,否则后续的afterEnter钩子函数不会被执行 //若是通过JS钩子函数来实现过渡动画,那必须在动画执行过程中加上el.offsetWidth/el.offsetHeight el.offsetWidth; //el.offsetHeight; el.style.transition='all 3s'; //若想要第一次进入时就有动画,可设置setTimeout setTimeout(function(){ done(); },0); ... }, //若enter中调用了done(),就会执行enterAfter enterEnter(el){ el.style.opacity='1'; el.style.marginTop=100+'px'; ... }, } }) 3. 配合使用第三方 JavaScript 动画库Velocity.js 引入Velocity.js的路径: new Vue({ methods:{ enter(el,done){ //Velocity(对应的元素,属性设置,过渡时长) Velocity(el,{opacity:1,...},3000); } } }) 4. 自定义类名动画: 可以通过以下 attribute 来自定义过渡类名: enter-class enter-active-class enter-to-class (2.1.8+) leave-class leave-active-class leave-to-class (2.1.8+) 5. 配合使用第三方 CSS 动画库Animate.css 引入Animate.css的路径: transition里只能放一个标签元素,想要在transition里面放多个标签元素(多个元素有过渡效果):可利用 使用transition-group时默认会把内容放到span中,要想放到指定的标签元素里,可用:

Vue 组件

每个组件包含:功能(JS代码),内容(模块代码),样式(CSS代码)

全局组件:
1. 创建组件构造器
   let xxx = Vue.extend({
                template:`...`; //在创建组件指定组件的模板时,模板只能有一个根元素(可用一个div包裹)
             }) 
             
2. 注册已经创建好的组件
    Vue.component('组件名',xxx(上面组件构造器的名字))
    或
    Vue.component('组件名',{
        //data通过函数返回的原因:每创建一个新组件,都会调用一次这个方法将这个方法返回的数据和当前创建的组件绑定在一起,有效避免了数据混乱
        data(){
            return{...}
        }
        template:`...`,
        methods:{
            xxx(){...}
        },
        props:[...]
    })

3. 使用注册好的组件
   <组件名>

局部组件:(推荐使用)
new Vue({
    el:'xxx(绑定局部区域)',
    components:{
        '组件名':{
            template:`...`,
            ...
        }
    }
})


组件的属性props:
<组件名 :p1='a' :p2='b'> //传递属性
Vue.component('组件名',{
    props:['p1','p2'], //定义属性
    template:'
{{p1}},{{p2}}
', }) //props包含的一些设置 props:{ 属性名:{ type:xxx, //约束该属性的类型是xxx required:xxx, //约束该属性是否必须要传递 default:xxx //默认值(若返回的默认值是对象,则要写成函数,把对象作为返回结果。如default:function(){return {}}或default:()=>({})) validator:function(){...} //自定义验证函数 } } 在组件中,属性是只读的,绝对不可以更改,叫做单向数据流 组件的切换: 可通过v-if实现组件切换
Vue.component('temone',{ template:'#temone' }) Vue.component('temtwo',{ template:'#temtwo' }) 动态组件:
//可保存组件的状态
组件动画: 单个组件就使用transition 多个组件就使用transition-group 和上面的元素过渡动画一样 不过默认情况下进入动画和离开动画是同时执行的,如果想要一个做完后再做另一个,则需要指定动画模式(mode='out-in') 父子组件: 局部组件就是典型的父子组件 子组件只能在父组件中使用 以下为定义方式: 第一种: new Vue({ el:'xxx(绑定局部区域)', components:{ '父组件名':{ template:`...`, components:{ '子组件名':{ tenplate:'...' } } ... } } }) 第二种: Vue.component('父组件名',{ template:'...', components:{ '子组件名':{ tenplate:'...' } } }) 父子组件的数据传递: 父组件传递数据给子组件: 第一步:在父组件中通过v-bind传递数据:v-bind:自定义接收名称='要传递的数据' 第二步:在子组件中通过props接收数据:props:['自定义接收名称'] Vue.component('父组件名',{ template:'...', data(){ return { name:'ckx', } } components:{ '子组件名':{ tenplate:'...', props:['parentName(自定义的接收名称)',...] } } }) 兄弟组件之间不能直接传递数据,如果兄弟组件间要传递数据,那么必须借助父组件(但这方法很麻烦,可用Vuex简化) 普通的实现方法: 1. 父亲给儿子传递一个方法 2. 在儿子中修改数据 3. 儿子中修改完数据,调用父亲传递过来的方法,并且将修改后的数据通过此方法传递给父亲 4. 在父亲的方法中保存最新数据 Vue.component('父组件名',{ template:'...', data(){ return { num:0, } }, methods:{ change(newCount){ this.num=newCount; } }, components:{ '子组件名1':{ tenplate:'...', data(){ return { count:0, } }, methods:{ add(){ this.count++; this.$emit('parentchange',this.count); } } }, '子组件名2':{ tenplate:'...', props:['parentnum'] }, } }) 父子组件的方法传递: 第一步:在父组件中通过v-on传递方法:v-on:自定义接收名称='要传递的方法' 第二步:在子组件中自定义一个方法,再在自定义方法中通过this.$emit('自定义接收名称'):触发传递过来的方法 Vue.component('父组件名',{ template:'...', methods:{ aaa(){...} } components:{ '子组件名':{ tenplate:'...', methods:{ xxx(){ this.$emit('parent-method('自定义接收名称')',...(函数参数)); //获取此参数时可用$event } } } } })

aa.jpg

组件的命名:
组件名的命名方式有两种:
短横线分隔命名(xx-xx-xx)
首字母大写命名(XxxXxxx)
注意点:
1. 注册组件时使用了'驼峰命名',那么在使用时需要转换成'短横线分割命名'
    注册时:myFather -> 使用时:my-father
2. 传递参数时如果想使用'驼峰命名',那么就必须写'短横线分割命名' 
    传递时:parent-name='name' -> 接收时:props['parentName']
3. 传递方法时不能使用'驼峰命名',只能用'短横线分割命名'
    @parent-say='say' -> this.$emit('parent-say')
    
    
组件中数据和方法的多级传递:
Vue中儿子想要使用爷爷的数据,则要一层一层往下传递
Vue中儿子想要使用爷爷的方法,则要一层一层往下传递





组件中的插槽:
使用子组件时给子组件动态的添加内容,那么必须使用插槽(相当于把slot比作一个坑,然后我们来填坑)
Vue2.6之后使用v-slot代替slot
插槽可以指定名称,默认情况下若没有指定名称,则称之为匿名插槽
匿名插槽的特点:有多少个匿名插槽,则填充的数据就会被拷贝几份(推荐只用一个匿名插槽)

<子组件名 :parent-name1(自定义的接收名称)='parent-name'  @parent-method1('自定义接收名称')='xxx'>
    
填坑的内容
具名插槽: 通过插槽的name属性给插槽指定名称 使用时可通过slot='name'方式指定当前内容用于替换哪一个插槽 <子组件名 :parent-name1(自定义的接收名称)='parent-name' @parent-method1('自定义接收名称')='xxx'>
填坑的内容
v-slot(缩写#)的用法: 作用域插槽: 带数据的插槽(让父组件在填充子组件插槽内容时也能使用子组件的数据) 应用场景:子组件提供数据,父组件决定如何渲染 使用: 在slot中通过v-bind:数据名称='数据名称'方式暴露数据 在父组件中通过