Vue笔记

VUE介绍

  • Vue的特点
    1. 构建用户界面,只关注View
    2. 简单易学,简洁、轻量、快速
    3. 渐进式框架
  • 框架VS库
    库,是一封装好的特定方法的集合,提供给开发者使用,库没有控制权,控制权在使用者手中;
    代表:jQuery、underscore、util
    框架,框架顾名思义就是一套架构,会基于自身的特点向用户提供一套相当完整的解决方案,而且控制权的在
    框架本身,使用者要找框架所规定的某种规范进行开发
    代表:backbone、angular、vue

理解渐进式

所谓的渐进式,可以一步一步、有阶段性的使用Vue,不是必须在一开始把所有的东西都用上。

  1. 声明式的渲染(Declarative Rendering)
  2. 组件系统(Components System)
  3. 客户端路由器(vue-router)
  4. 大规模的状态管理(vuex)
  5. 构建工具(vue-cli)


    Vue笔记_第1张图片
    image.png

Vue的两个核心点

  1. 响应式的数据绑定
    当数据发生改变 -> 视图自动更新
    忘记操作DOM这回事,而是专注于操作数据
  2. 可组合的视图组件
    把视图按照功能,切分若干基本单元;
    组件可以一级一级组合成整个应用,形成了倒置的组件树;
    使用组件的好处:可维护、可重用、可测试;


    Vue笔记_第2张图片
    视图组件

启动应用

  • 全局的构造函数
    new Vue({选项对象})
  • 选项对象
    告诉Vue你要做什么事情
    el: 告诉Vue管理的模板范围。 css selector || element
    data: 数据对象,会被实例代理,转成getter/setter形式。
    Object 定义好的数据可以直接在模板中使用
  • 插值
    1.使用双大括号(“Mustache”语法)做文本插值
    2.可以写任意表达式
    3.属性名、条件运算符、数据方法调用

声明式渲染

  • 声明式
    只需要声明在哪里where 做什么what,而无需关心如何实现how
  • 命令式
    需要以具体代码表达在哪里where做什么what,如何实现how
  • 声明式渲染理解
    1. DOM状态只是数据状态的一个映射;
    2. 所有的逻辑尽可能在状态的层面去进行,就是说,所以逻辑去操作数据,不要想着去操作DOM;
    3. 当状态改变了,view会被框架自动更新到合理的状态;
      Vue笔记_第3张图片
      过程图

指令directive

  • 理解指令
  1. 是一种特殊的自定义行间属性,以v-开头
  2. 将数据和DOM做关联,当表达式的值改变时,响应式的作用在视图
  3. 预期的值为javascript表达式
  • 指令初体验
    v-bind:动态的绑定数据。简写为

指令学习

DOM结构渲染说明

DOM结构为模板,先把DOM结构渲染出来,Vue会找到这个模板,进行解析,绑定值绑定在指定的位置,重新替换原来的DOM结构。

  • v-text:更新元素的 textContent,可代替{{}}
  • v-html:更新元素的 innerHTML
    不使用这个指令,插入的html结构不作为模板编译,作为文本显示;
    不要插入不安全的html结构;
  • v-cloak:隐藏未编译的 Mustache 标签直到实例准备完毕
  • v-once:只渲染一次,随后数据改变将不再重新渲染,视为静态内容,用于优化更新性能

列表渲染

  • v-for
    作用:对一组数据循环生成对应的结构
    语法:
    循环数组:v-for=“item,index in 数组”
    循环数组:v-for=“value,key,index in 对象”
    v-for:使用v-for循环对象时,拿到的下标是按照object.keys()的顺序来的;
  • key值
    对渲染的列表的结构采用“就地复用”的策略,也就说当数据重新排列数据时,会复用已在页面渲染好的元素,不会移动 DOM 元素来匹配数据项的顺序,这种模式是高效的,改变现有位置的结构的数据即可;
    需要提供一个key值,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素。

条件判断

  • v-if:根据表达式的值的真假条件渲染元素和移出元素;(true渲染,false不渲染)
    -v-else:成功走if,不成功走else,与js中的if else逻辑相同
    注意:v-if和v-else必须要连起来用,不能单独出现,会报错,并且,两者之间不可以有间隔
  • v-show: 根据表达式的值的真假条件,切换元素的 CSS 属性 display属性;
    区别:
    初始页面根据条件判断是否要渲染某一块的模板,使用v-if
    频繁的切换模板的显示隐藏,使用v-show

自定义指令

需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
注册方法:
全局注册(Vue.directive)与局部注册(directives:{}):
钩子函数
bind:只调用一次,指令第一次绑定到元素时调用
inserted:被绑定元素插入父节点时调用
update:更新时调用
componentUpdated: 更新完毕调用
unbind:只调用一次,指令与元素解绑时调用,就是当元素被移除DOM的时候就会触发这个函数
钩子函数参数

  • el:指令所绑定的元素,可以用来直接操作 DOM
  • binding:可以看成是一个配置自定义指令的一些属性的对象;
    binding对象中的一些属性说明
    • name:指令名,不包括v-前缀
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为 { foo: true, bar: true }
  • vnodeVue 编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点,仅在updatecomponentUpdated 钩子中可用。

自定义指令小练习

  • input自动获取焦点



    
    input自动获取焦点


    
  • 点击按钮显示下拉列表,点击随意位置隐藏下拉列表
 

事件系统

  • v-on:可以用v-on 指令监听 DOM 事件,简写为@ ;

  • 事件处理函数
    事件处理函数写在methods中;
    在模板中不传参,只写上函数名字,函数第一个参数事件对象作为事件处理函数的第一个参数传入的;
    在模板中传参,需要手动在模板中使用$event传入事件对象;
    事件处理函数(methods)中的this都指向根实例(Vue);

  • 事件修饰符
    方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
    语法:v-on:事件名.修饰符=”处理函数|表达式|处理函数执(tu)行”.stop(取消冒泡)、.prevent(阻止默认行为)、.capture(阻止捕获)、.self(在自己身上触发)、.once(事件只执行一次)
    按键修饰符.enter、.tab、.delete 、.esc、.space、.up、.down、.left、.right

简单的控制显示隐藏案例:

点击显示隐藏

双向数据绑定

Vue将数据对象和DOM进行绑定,彼此之间互相产生影响,数据的改变会引起DOM的改变,DOM的改变也会引起数据的变化;

  • 可交互的表单元素
    input(text、checkbox、radio) textarea select

  • v-model 是个语法糖,数据绑定在value上,监听了oninput事件
    1.在表单元素上创建双向数据绑定;
    2.会根据控件类型自动选取正确的方法来更新元素交互的值;
    3.负责监听用户的输入事件以更新数据;
    注意:
    1.会忽略所有表单元素的value、checked、selected特性的初始值;
    2.将Vue实例的数据作为数据来源;
    3.需要在数据对象中声明初始值;

双向数据绑定示意

Vue笔记_第4张图片
示意图
  • 上图流程说明:数据(Model)通过VM(ViewModel)绑定到视图(View),然后用户进行交互,交互后的数据又通过VM中绑定的Model,改变Model中的数据;
Vue笔记_第5张图片
流程图

MVVM模式说明:

M=Model
V=view
VM=vue实例
数据通过VM层绑定在视图上,视图上有交互,改变了数据,改变之后会重新渲染;

响应式原理

把一个普通的 JavaScript 对象传给Vue实例的data 选项 Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter
Vue内部会对数据进行劫持操作,进而追踪依赖,在属性被访问和修改时通知变化;
具体步骤:
step1:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 settergetter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
step2compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
step3Watcher订阅者是ObserverCompile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
step4MVVM作为数据绑定的入口,整合ObserverCompileWatcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起ObserverCompile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

Object.defineProperty

  • 作用:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性;(通俗意思就是给对象定义属性的一个方法)

  • 语法:Object.defineProperty(obj, prop, descriptor)

  • 参数:

obj要在其上定义属性的对象 prop 要定义或修改的属性的名称
descriptor :描述对象,将被定义或修改的属性·(prop)·描述符;

  • 数据描述对象

configurable: 是否可以删除目标属性。默认为false
enumerable : 此属性是否可以被枚举(遍历)。默认为false
value : 该属性对应的值,默认为 undefined
writable : 属性的值是否可以被重写。默认为false

  • 存取器描述(getter、setter)

getter:是一种获得属性值的方法(当获取值的时候触发的方法);
setter:是一种设置属性值的方法(设置值的时候触发的方法)
可以写configurableenumerable
不能写valuewritable
注意:这个方法可以新添加属性也可以对原有的属性进行配置;
setter方法中,在设置值时,会传一个参数newValue,这个是当前改变的值,newValue是跟着设置的值而改变的;

给一个对象定义属性的小例子:



    
    对象定义属性





简单的数据劫持小例子:

  • object.defineProperty劫持:



    
    使用object.defineProperty劫持


    
    
  • proxy劫持:



    
    proxy劫持


    
    

响应的数据变化

data对象中的数据都会被转换为getter/setter,所以当数据发生变化时,自动更新在页面中;
如果没有在vue对象的data`属性中初始化变量的值,那么这个变量就不是响应的(不能检测属性的变化)

  • Vue.set( target, key, value )
    设置对象的属性。如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新。这个方法主要用于避开 Vue不能检测属性被添加的限制
  • 参数说明:
    target:要添加属性的对象;
    key:要添加的属性名;
    value:要添加的属性值;
    这个方法返回的是设置的值;
  • vm.$set( target, key, value ):实例上的方法
  • 参数说明:与上面的Vue.set()方法参数一样;
  • 替换对象Object.assign()
    注意:注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象;

数组的变异方法

vue实例中,可以使用this.的方式直接调取数据的一些常用方法,如:
push()、pop()、shift()、unshift()、splice()、sort()、reverse()

  • 以上这些变异方法的注意事项:

1.都可以改变原数组,因为这些方法vue做为了变异方法,其它的数组原有方法都没有变异;
2.不能使用下标改值,比如:
this.arr[0]=10; //这些写会没有效果,是不可以改的,如果想改变,可以使用splice()来改;
比如:concat这个方法是不可以改变原数组的,因为不是变异方法;
3.不能使用length改变数组,比如:
this.arr.length=1;//可以使用 splice()来改变或者也可以通过重新改变数组;

*注意:这些方法都是vue变异的方法,不是原生的;都是vue改写过的,为了更方便的使用。所以vue提供了观察数组的变异方法,使用这些方法将会触发视图更新;

计算属性

  • 计算属性定义在computed中,它不是方法,属性的值是函数的返回值;
  • 把对处理数据的逻辑抽离在计算属性中,使得模板更加轻量易读;
  • 计算属性的值会被缓存,并根据依赖的数据变化而重新计算;

计算属性语法

    key:function(){}
   //key的值是函数的返回值,内部调用这个函数;
   //所以计算属性会被放在实例上,通过this.可以找到对应的计算属性;
    //计算属性中的this都指向实例

计算属性与methods的区别

  • 计算属性只有在数据改变了才会执行,methods只要调用就执行;
  • 数据如果频繁更新改变,使用methods就比较合适;反之使用计算属性;

计算属性的使用

  • 取值: 触发get函数,默认设置的函数
  • 赋值: 触发set函数

watch观察

利用watch观察 Vue 实例上的数据变动,键是需要观察的表达式,值是对应回调函数

  • 回调函数会接受两个参数:
    newValue:变化的最新值
    oldValue:变化之前的值
具体写法:
    //效果是:点击按钮,message值变成123
    
    

{{message}}

let vm = new Vue({ el:'.app', data:{ message:'hello word!',//单层数据 name:{//多层数据 zhangsan:{ age:12 } } } }); /* 通过watch可以监控到data数据中属性改变的值; vm对象下直接就有$watch()的方法,传两个参数; 第一个参数:要改变的属性名(或者是路径); 说明:直接写属性名相当于数据只有一层,当遇到多层数据,比如对象里面套对象这类时,就需要写路径了; 第二个参数:数据改变后监控到值的回调; 在这个回调函数中传两个参数newvalue,oldvalue: 顾名思义,一个是改变后的新值,一个是改变前的旧值; */ vm.$watch('message',function(newvalue,oldvalue){ //这是单层数据监控 console.log('改变了') }'); vm.$watch('name.zhangsan.age',function(newvalue,oldvalue){ //这是多层数据监控,针对性的监控某一个值 console.log('改变了') }'); //总结:总的来说,以上多层监控时,哪个属性改变监控哪个。 /* 一般实际项目中,watch都是写到new Vue里面的,直接写成 wacth:{ message(newvalue,oldvalue){ //单层监控 console.log('改变了') }, 'name.zhangsan.age'(newvalue,oldvalue){//多层监控 console.log('改变了') } } */
  • 深度监控
    handler(){}
    deep:true //true就是表示深度监控
    注意:由于watch在监控时,会遍历对象中的所有属于,而遍历是非常耗费性能的,所以不易监控太深的数据,在使用要特别的注意选择;
具体写法
    let vm = new Vue({
        el:'.app',
        data:{
          name:{//多层数据
                 zhangsan:{
                      age:12
                  }
            }
        },
        watch:{ //这里要监控name这个对象的改变,只要name里面的任何数据有了改变,就要监控到;
            //这种写法在页面第一次加载时,不会自动调用下面的监控
            'name':{
                handler(newvalue,oldvalue){
                        console.log('有属性发生改变')
                },
                deep:true
            }
           //下面这种写法是立即调用,就是在页面第一次加载时,就调用监控,当真正发生改变时,依旧会执行
            'name':{
                  handler(newvalue,oldvalue){
                          console.log(''改变了)
                  },
            //可以联着写,既可以加载就执行,也可以深度监控
                  immediate:true,
                  deep:true
            }
        }
    });

methods观察

定义一些方法,将被混入到 Vue 实例中

  • 使用method
    可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用,方法中的this 自动绑定为 Vue 实例

组件化开发

组件可以将UI页面切分成一些单独的、独立的单元,整个页面就是这些单元组合而成的,只需要关注构建每个单独的单元,每个组件包含自己的结构、逻辑和样式,这样既减少了逻辑复杂度,又能实现组件的复用性。当不需要某个组件或替换组件时,而不影响整个应用的运行。

  • 组件开发的好处:
    降低耦合度、提高可维护性、可复用性便与协作开发;

全局注册组件

  • 分为三步骤:创建组件构造器、注册组件和使用组件
  • Vue.extend(选项对象)
       //当页面中需要多个vue实例时,就可以使用extend来实现
       
    {{message}}
    {{message}}
    //写法与new Vue一样,对象中使用的方法也是全部都一样,除了不可以写el; //子构造器 let ex = Vue.extend({ data(){ return { message:'hello extend' } } }); //这里的ex返回的是一个实例,所以想要调用$mount方法必须要new //执行方法:extend必须要使用$mount()来实现挂载; new ex().$mount('#index') let vm = new Vue({ data(){ return { message:'hello extend' } } }); vm.$mount('#app'); //手动挂载
Vue.extend():返回构造器实例
  • 和根实例的选项对象一样,不同的是没有el
  • data必须是一个工厂方法,返回对象;
  • template定义组件的模板;
  • 通过extend来实现一个组件
    组件说明:首先组件就是一个函数;
      
//组件是以标签的形式执行的,也可以把注册好的组件看成是自定义的标签
//创建一个组件的函数 let ex = Vue.extend({ data(){ return { message:'hello extend' } }, template:` //注意这里的message不是new Vue里面的,是extend子构造器中的,与Vue完全是独立的。
{{message}}
` }); //将上面的函数注册成一个组件,这里使用的方法是注册一个全局组件,传两个参数,第一个参数是组件的名字(id名),第二个参数是组件的函数(可以直接是函数,也可以是函数名) Vue.component('custom-ex',ex); let vm = new Vue({ el:'#app', data(){ return { message:'hello extend' } } });
全局注册组件

Vue.component( id, [definition] )
id: 字符串
definition:构造器实例或对象
如果为对象,内部会自动调用 Vue.extend
Vue实例范围内使用注册组件的id为标签名

Vue笔记_第6张图片
步骤图

全局组件的定义与注册

说明:下面案例通过Vue.component('组件名',{选项对象})可以直接定义组件,第二个参数直接提供一个对象,vue内部会自动调用Vue.extend(),然后把这个返回的对象传到第二个参数中;
组件的选项对象没有el属性;

    
//这样就是一个简单的组件,注册组件里面的template必须要加上,否则会报错; Vue.component('custom-ex',{ template:`
我是组件
` }); let vm = new Vue({ el:'#app' });

局部注册组件

  • 选项对象的components属性注册局部组件,只能在所注册的作用域模板中使用;
    {components:{组件id:实例构造器 | 对象 } }
    局部组件就是在哪里定义,在哪里使用;
局部组件写法
      
  • 组件中data的注意

  1. data必须是一个函数,函数中返回一个新对象,如果是对象形式会警告必须是函数;
  2. 这样做防止组件引用同一个对象,导致数据相互影响;
  • html模板
    放在一对template标签中,在组件中使用标签的id
    放在一对script标签中,标签的type要设置为text/x-template

组件命名约定和模板

  • 注册组件命名:
    kebab-case (短横线分隔命名)
    camelCase (驼峰式命名)
    PascalCase (单词首字母大写命名)
    不能使用html规定的标签名
  • 使用组件命名:
    kebab-case (短横线分隔命名)

prop

prop上的属性都会放在当前组件的实例上面
props:['test'],可以是多个,也可以是对象形式的;

prorp的验证引用官网API:

Vue.component('my-component', {
props: {
// 基础的类型检查 (null 匹配任何类型)
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
}
}
}
})

一个简单的组件小练习




    
    news


父子组件之间的通信

使用在一个组件模板中的组件,称之为子组件。

  • 组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据,采用单向绑定的方式,避免子组件直接修改父组件产生副作用;
  • 但是有时候需要父子组件之间需要相互通信:父组件可能要给子组件传递数据,子组件则可能要将它内部发生的事情告知父组件。
  • 通过props将父组件的数据传递给子组件;
  • 触发子组件的某个事件时,可以通知到父组件,那么在子组件中发布一个自定义事件,父组件监听自定义事件;
  • 组件实例.$emit(自定义事件名,参数);
    Vue笔记_第7张图片
    通信流程

父组件传递数据示意图

Vue笔记_第8张图片
数据示意图

子组件发布事件示意图

Vue笔记_第9张图片
事件示意图
三层组件之间的通信(父传子,子传父)



    
    多层组件通信练习




    

我是根组件

{{vmData}}

多层组件嵌套之间通信

方法有很多种:

  • 1.组件由外到内或者由内到外层层的传数据,这样做,如果是较少的几层,还可以使用,当遇到多层,十层二十层时这个时候就会显的非常的繁琐,而且很容易出错;
  • 2.当遇到非常多层,并且好多组件的数据是相互引用的,这时就可以使用一个叫VueX(状态管理器)来处理这种情况;
  • 3.当遇到第三层组件直接向第一层组件通信时,可以用一个叫做事件总线(event-bus)的东西的来解决;
//事件总线使用方法:
let vm = new Vue();
//监听event事件
vm.$on('event',function(){}); 
//发布这个事件
vm.$emit('event')

组件间的改值

父组件可以通过props向子组件传,但是子组件不可以向父组件传,这里就有一个单向数据流的概念;在组件中在使用props只是从外向内传;
如何子组件修改父组件的数据呢?
解决方法两种:.sync修饰符v-model

  • sync方法
    .sync修饰符:在2.0被移除后,发现还是需要双向绑定这种需求的存在,所以从2.3.0重新引入后,就作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的v-on监听器。

.sync具体使用案例




    
    .sync练习


    

父级:{{message}}

  • v-model方法
    v-model:自定义事件可以用来创建自定义的表单输入组件,使用v-model来进行数据双向绑定,它也是编译的语法糖;
    在组件上使用v-model在子组件中使用value来接收v-model的值;再调用emit监听来监听input来改变对应的值;
    那么问题来了,如果当需要的这个value名被组件占用,或者emit中要监听的自定义事件input也被占用,怎么办?
    解决办法:
Vue.component('custom',{
      //在组件中的model对象中,可以改变v-model接收值的名字。
       //这里相当于改了个别名
      model:{
          prop:'modelTest', //要改变的属性名
          event:'testFn' //要监听的事件名
      }
});

v-model具体使用案例




    
    v-model练习


    

父级:{{message}}

使用插槽分发内容

  • 编译作用域
    父组件模板的内容在父组件作用域内编译;
    子组件模板的内容在子组件作用域内编译;
  • 插槽的作用
    将父组件中写在子组件一对标签内的结构混合在子组件模板中,这个过程称之为内容分发。使用特殊的 元素作为原始内容的插槽;
  • 单个插槽(无命名插槽)
    如果子组件中没有一对slot标签,写在子组件标签对的内容会被丢弃;
    子组件中有slot标签,子组件标签对的内容会整体替换在slot标签位置;
    如: //这个就是默认的插槽,只要组件标签中写内容都会替换slot标签中的内容
    slot标签内的内容被视作备用内容;
  • 具名插槽(有命名的插槽)
    可以使用name来配置如何分发内容;
    没有nameslot被视为默认插槽;
    如:
//有名字插槽的使用方法

我是要替换的内容p标签

我是组件中默认内容 //当有名字时,就可以指定替换某个名字插槽的内容
  • 批量替换插槽
    如:
/*当需要替换li时,就可以使用组件中提供的 template标签 */
  • 默认的
  • 默认的
  • 作用域插槽(slot-scope)
    说明:2.1.0新增的,在这个版本中只能局限于