通俗重制系列--Vue2基础教程

598 阅读15分钟

image.png

起手式

完整版同时包括编译器(compiler) 和 运行时(runtime)将模板字符串编译为 JavaScript 渲染函数(render函数)的代码 运行时的功能包括创建 Vue 实例、渲染并处理虚拟 DOM 等,它包括除了编译器的其他所有功能\

两个版本的区别

Vue完整版Vue只包含运行时版
特点有compiler没有compiler
视图写在HTML里,或者写在template选项里写在render函数里,用h创建标签
cdn引入vue.jsvue.runtime.js
webpack引入需要配置alias默认使用
vue@cli引入需要额外配置默认使用

那究竟应该使用哪一个版本呢?

1、 对于用户来说,非完整版 (即runtime版)体积小,用户体验好,但只支持h函数

2、 对于程序员来说,只能写h函数的话,开发体验不好,如果有compiler, 开发者就能写更直观更语义化的HTML标签和template, 所以我们需要一个compiler

3、 vue-loader就可以引入compiler, 把vue文件里的HTML标签和template 会在构建时预编译成 h函数,这样用户和开发者都高兴

template 和 render 的用法

// 需要编译器
new Vue({
  template: '<div>{{ hi }}</div>'
})

// 不需要编译器
new Vue({
  render (h) {
    return h('div', this.hi)
  }
})

template标签和JS里的template

//vue文件中的template标签
  <template>
      <div id="app">      
          {{n}}
          <button @click="add">+1</button>   
     </div> 
  </template>

//js中的template
    template : `
        <div id="app">      
          {{n}}
          <button @click="add">+1</button>   
        </div> 
    `

render函数:

//不完整版在js中构建视图
  render(h){ 
       return h('div', [this.n,h('{on:{click:this.add}’,'+1'])
   }

//不完整版使用vue-loader

//先创建一个demo.vue文件,在里面构建视图
    import demo from "./demo.vue"
     new Vue({
       el: "#app",
       render(h) {
         return h(demo)
       }
     })

options选项

new Vue() 这就是构造一个Vue的实例。

image.png

这个实例会根据你给的选项得出一个对象(vm),vm封装了这个DOM对象以及对应的所有操作,不管是事件绑定还是数据的读写、DOM更新,全部都由vm这个对象负责。你只需要去调用它的API就好了。

原型:对象.__proto__===其构造函数.prototype 推出vm.__proto__===Vue.prototype

函数也是对象,所以Vue函数对象的内存图如上。

函数.__proto__===Function.prototype推出Vue.__proto__===Function.prototype

问题一: 初始化时可以写些什么对象进去(options)?

问题二: vm自己有哪些属性?

问题三: Vue函数本身有哪些属性?

问题四: 每个函数都有个属性叫prototype,同时每个对象都有个属性叫__proto__。假设Vue.prototype对应的对象的地址是#419,那请问这个#419里面有哪些属性呢?

问题五: Vue.prototype.__proto__= ?

options的五类属性

  • DON: el,template,render,rebderError
  • 生命周期钩子函数:beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,activated,deactivated,beforeDestroy,destroyed,erroCaptured。
  • 资源:directives,filters,components
  • 组合:parent,mxins,extends,provide,inject

第1类属性 数据(Data):

data 数据 
props 属性
computed 计算属性 //被计算出来的
methods 方法,用来定义方法的
watch 观察 //当data变化时做某些事情就用watch
propsData //很少用,单元测试会用

方法和函数的区别?

1.概念:方法是属于面向对象概念,函数属于数学概念。

在面向对象里叫方法,有对象才有方法,方法依附于对象即对象.方法,比如说obj.sayhi()sayhi就叫方法也是函数,一般叫方法。 如果sayhi()这样写就叫函数

在数学里叫函数。

2.都指同一个东西

function(p1,p2){
  return 
}

第2类属性 DOM:

el 挂载点 //你要用你的模版替换页面上的哪一块,你的挂载点
template //你的HTML内容。着重讲语法v-if、v-for
render  渲染 //⚠️注意template和render只能二选一!
//template是给完整版用的,render是给非完整版用的。一起用必然有一个会失效!
renderError //很少用

第3类属性 生命周期钩子:

生命周期:Vue组件在页面中插入一个<div>监听它的事件,然后用户点击按钮时变化。可以切入的点叫做钩子。

beforeCreate  创建之前
created  创建之后
beforeMount
mounted 挂在之后
beforeUpdate
updated 更新之后
activated
deactivated
beforeDestroy
destroyed 失败之后
errorCaptured //很少用

image.png

第4类属性 资源

directives 指令
filters 过滤器 //尽量不去用,用methods代替
components 组件 
//如果要在一个文件引用另一个Vue的文件就用组建。Demo.vue就叫组件,英文名叫components。

第5类属性 组合:

parent //很少用
mixins 混入
extends 扩展
provide 提供
inject 注入

入门属性

1.el 挂载点

可以用$mount代替

你要用你的模版替换页面上的哪一块,可以用$mount代替。组件或者实例的挂载点。

index.html
<div id="app"> {{n}} </div>

main.js
new Vue({ 
  el: '#app',
  render(h) {
    return h(Demo)
  }
})
/*可以用$mount替代:*/
new Vue({ 
  render: h => h(Demo)
}).$mount('#app')

//const vm = new Vue({
//  render: h => h(Demo)
//})
//vm.$mount('#app')

总结

特点1.名字要保持一致

特点2.如果在<div>内加内容,那么多半用户是看不见的。js加载完后会把hello干掉。

特点3.可以用$mount替代。挂载基本等同于replace或append

2.data 内部数据

组件的定义只接受函数。

用vue完整版来示例

main.js
console.log(window.Vue)
// import Vue from 'vue' 删掉,这次不从npm引入,直接使用全局的Vue
const Vue = window.Vue
Vue.config.productionTip = false

new Vue({ //实例
  data: { //内部数据,只支持函数
    n: 0
  },
  template: `
  <div class="red">
    {{n}}
    <button @click="add">+1</button>
  </div>
  `,
  methods: {
    add() { //add要先定义好,不然会报错
      this.n += 1
    }
  }
}).$mount('#app')

Bug: Vue的data有bug,后面讲"数据响应式"时会说。

为什么data必须是函数?

如果你是个组件,比如Demo组件,引入Demo组件import Demo from './Demo.vue'

//Demo.vue
export default {
    data(){ //vue-loader写文件时,data必须是函数
      return {
        n:0
      }
    },
}

Demo实际上是对象,Vue会自动把Demo传给new Vue(Demo)

假设如果有两个组件共用内部数据data,当其中一个改变时另一个也会变,因为它们引用的是同一个data。函数会阻止两个组件共用data的问题。

main.js
render:h=>h(x,[h(Demo),h(Demo)])

3.methods 方法

事件处理函数或者是普通函数

add必须写在methods里面,如果写到外面会报错。 在这里插入图片描述

main.js
//new Vue({  错误
//  add() { this.n += 1 },
//  methods: {
//  }
//})

事件处理函数: 写到一个@click或keypress或者任何事件作为它的处理函数

普通函数method代替filter。

main.js
new Vue({ //实例
  data: {
    n: 0,
    array:[1,2,3,4,5,6,7,8,9]
  },
  template: `
    <div class="red">
      {{n}}
      <button @click="add">+1</button>
      <hr>
      {{filter(array)}} //2'filter()
    </div>
   `,
   methods: {
    add() {
      this.n += 1
    },
  filter(array) {
    return array.filter(i => i % 2 === 0)
    }
//filter() {
  //return this.array.filter(i => i % 2 === 0)
  //}
}).$mount('#app')

在这里插入图片描述

bug: methods第2种用法,用来主动在模版里调用。这种调用特点是每次渲染都会重新调用。就算毫无意义跟之前是相同的结果它也会执行。

4.components 组件

使用Vue组件,注意大小写

如果要在一个文件引用另一个Vue的文件就用组件。Demo.vue就叫组件(components)。

const vm=new Vue({...})vm是Vue实例或Vue对象

这个不能叫做组件,它使用"其它的Vue实例"的时候,"其它的Vue实例"才是组件。

如何使用组件?

首先要创建组件,组件的3种引入形式

1' 创建一个.vue文件(推荐)。

这个文件就是Vue组件,比如Demo.vue,然后引入该文件

使用组件

说明要用的组件是frank,然后就可以在template里面写frank。

main.js
import Demo from './Demo.vue' //引入Demo文件

new Vue({ //实例
  components: { //说明要用的组件是frank
    frank: Demo //名字:值,Demo组件
  //Demo:  Demo //es6语法可以简写为Demo
  },
  template: `
  <div class="red">
    <frank/>
  </div>
  `,
}).$mount('#app')  

Demo.vue
<template>
    <div class="red">
       fuck
    </div>
</template>

在这里插入图片描述

优先使用第1种,其它2种不够模块化。

2' 用js的方式

不要components,直接声明全局的Demo2。

main.js
Vue.component('Demo2', {
  template: `
    <div> demo2 </div>
  `
})
new Vue({ 
template: `
  <div class="red">
    <Demo2/>
  </div>
  `,
}).$mount('#app')

在这里插入图片描述

你是入口就是实例,被别人用的就是组件。

3' 前2种的结合

保留js对象,又保留components

main.js
Vue.component('Demo2', {
  template: `
    <div> demo2 </div>
  `
})
new Vue({ 
  components: {
    fuck: {
      template: `
      <div> demo3 </div>
    `
    }
  },
 template: `
  <div class="red">
     <fuck/>
  </div>
  `,

在这里插入图片描述

fuck也可以有data

  fuck: {
      data() { //组件data必须用函数
        return { n: 0 }
      },
      template: `
      <div> fuck's n:{{n}} </div>
    `
    }

fuck对象里面的写法,跟外面的options是完全一样的。

在这里插入图片描述

什么是组件?

组件:可以组合的物件就叫组件。比如手臂、腿就是人的组件

组件可以认为是实例中的实例。

注意大小写

1.文件名最好全小写,因为有些古老的操作系统,比如window10可能不能识别大小写,防止2个大小写文件重名。

2.组件首字母最好大写。

在这里插入图片描述

5.四个钩子

1.created 实例出现在内存中

2.mounted 实例出现在页面中

3.updated 实例更新了

4.destroyed 实例消亡了

1.2.3.created、mounted、updated

new Vue({ //实例
  created() {
    //debugger
    console.log("这玩意出现在内存中")
  },
  mounted() {
    //debugger
    console.log("这玩意出现在页面中")
  },
   updated() {
    console.log("更新了") //点击+1按钮后显示更新了
    console.log(this.n) //每次拿到的n都是最新的
  },
}).$mount('#app')

可以通过debugger验证实例是否出现在页面:n和button没加载出来说明出现在内存,加载出来证明出现在页面。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.destroyed 实例消亡了

步骤

逻辑:让一个组件出现又消失

1.src新建文件demo2.vue

把目前的实例变组件:main.jsnew Vue({ //实例 })的实例剪切到demo2.vue<script>里。别忘了把template内容也移到<template>里。

2.创建实例

//main.js
import Demo from './demo2.vue'

new Vue({ //实例
  components: { Demo },
  data: { //自己new Vue就不是组件,所以data可以是对象
    visible: true
  },
  template: `
    <div>
      <button @click="toggle">toggle</button>
      <hr>
      <Demo v-if="visible===true"/>
    </div>
  `,
  methods: {
    toggle() {
      this.visible = !this.visible //把visible变为反值,实现按钮的切换
    }
  }
}).$mount('#app')

3.监听destroyed

  destroyed(){
    console.log("已经消亡了")
  }

每次toggle后n将重新初始化为0。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6kq9dGAP-1648651132320)(media/16259421256477/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202022-03-30%20%E4%B8%8B%E5%8D%887.21.06.png)]

知识点

1.渲染页面: render函数

render: h => h(Demo) //更简单
//等价于
components: { Demo },
  template: `
    <Demo/>
  `,

2.v-if什么时候出现

new Vue({ 
  components: { Demo },
  data: { //自己new Vue就不是组件,所有data可以是对象
    visible: true
  },
  template: `
      <Demo v-if="visible===true"/>
  `
}).$mount('#app')

3.实例 VS 组件

实例就是main.js,代码特征new Vue({ }),data可以是对象、函数。 实例需要导入组件demo.vue实例包含组件,如果实例是爸爸,那组件就是流落在外的儿子。

main.js
import Demo from './demo.vue' //导入组件`demo.vue`

new Vue({ 
  data: { //data可以是对象
    visible: true
  },
}).$mount('#app')
复制代码

组件就是新建的demo.vue,代码特征3个标签<template>、<script>、<style scoped>,data必须是函数。 可以认为是实例中的实例。

demo.vue
//组件
<template> //html
</template>

<script> //js
export default {
 data(){ //vue-loader写文件时,data必须是函数 }
    }
</script>

<style scoped> //css
</style>

4.函数和方法的区别?

函数(function) 是可以执行的javascript代码块,由javascript程序定义或javascript实现预定义。函数可以带有实际参数或者形式参数,用于指定这个函数执行计算要使用的一个或多个值,而且还可以返回值,以表示计算的结果。

方法(method) 是通过对象调用的javascript函数。也就是说,方法也是函数,只是比较特殊的函数。假设有一个函数是fn,一个对象是obj,那么就可以定义一个method。方法和对象相关,函数和对象无关。

方法和函数大致上是相同的,但有两个主要的不同之处:

(1)方法中的数据是隐式传递的。

(2)方法可以操作类内部的数据(请记住,对象是类的实例化–类定义了一个数据类型,而对象是该数据类型的一个实例化)

6.props 外部数据

外部数据是由外部来传值的(值是字符串),也叫外部属性

1' 传字符串message="n"

2' 传变量 :message="n" 传入this.n数据

3' 传函数:fn="add" 传入this.add函数

1' 传字符串 message="n"

步骤

(1)新建文件demo3.vue

props从外部接收message,这个message会自动绑到this上。Vue允许省掉this。

//demo3.vue
<template>
    <div class="red">
        这里是demo3的内部
        {{message}} //{{this.message}}this可省
    </div>
</template>

<script>
  export default {
   //声明:props:[属性名]
     props:['message'] //从外部接收message,这个message会自动绑到this上
  }
</script>
<style scoped>
  .red{ color: red; }
</style>

(2)使用props外部数据

main.js
import Demo from './demo3.vue'

new Vue({ //实例
components: { Demo },
 template: `
    <div>
      <Demo message="你好 props"/> //传值:在组件后加key value
    </div>
  `,
}).$mount('#app')

message="字符串"

在这里插入图片描述

2' 传变量 :message="n" 传入this.n数据

加空格和冒号" :", 注意Demo后有空格!

// main.js
import Demo from './demo3.vue'

new Vue({ //实例
 components: { Demo },
  data: {
    n:0
  },
   template: `
    <div>
      <Demo :message="n"/> <!--传变量(数据) -->
    </div>
  `,
}).$mount('#app')

空格:message="JS(变量)"

在这里插入图片描述

3' 传方法(函数):fn="add" 传入this.add函数

1.添加第2个参数fn

demo3.vue
<template>
    <div class="red">
        这里是demo3的内部
        {{message}}  
        <button @click="fn">call fn</button>
    </div>
</template>
<script>
export default {
    props:['message','fn']
  //从外部接收message、fn,会自动绑到this上
}
</script>

<style scoped>
.red{ color: red; }
</style>

2.接收方法

main.js
import Demo from './demo3.vue'

new Vue({ //实例
  components: { Demo },
  data: { //实例的data可以是对象
    visible: true,
    n: 0
  },
   template: `
    <div>
      {{n}}
      <Demo :fn="add"/> <!--传JS变量(数据) --> 
    </div>
  `, 
  methods: {
    add() {
      this.n += 1
       }, 
  }
}).$mount('#app') 

空格:message="JS(方法)"

在这里插入图片描述

把n回传给里面的儿子,得到的是最新的n。

main.js
template: `
  <div>
    {{n}}
    <Demo :message="n" :fn="add"/>
  </div> 
`,

在这里插入图片描述

对Vue 数据响应式的理解

getter、setter

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter

image.png 总结一下:

  1. 任何一个 Vue Component 都有一个与之对应的 Watcher 实例。
  2. Vue 的 data 上的属性会被添加 getter 和 setter 属性。
  3. 当 Vue Component render 函数被执行的时候, data 上会被 触碰(touch), 即被getter 方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有 data。(这一过程被称为依赖收集)
  4. data 被改动时(主要是用户操作), 即被setter 方法会被调用, 此时 Vue 会去通知所有依赖于此 data 的组件去调用他们的 render 函数进行更新
let obj0 = {
  姓: "高", 名: "圆圆", age: 18
};

// 需求一,得到姓名

let obj1 = {
  姓: "高", 名: "圆圆", 姓名() {
    return this.姓 + this.名;
  }, age: 18
};

console.log("需求一:" + obj1.姓名());
// 姓名后面的括号能删掉吗?不能,因为它是函数
// 怎么去掉括号?

// 需求二,姓名不要括号也能得出值

let obj2 = {
  姓: "高", 名: "圆圆", get 姓名() {
    return this.姓 + this.名;
  }, age: 18
};

console.log("需求二:" + obj2.姓名);

// 总结:getter 就是这样用的。不加括号的函数,仅此而已。

// 需求三:姓名可以被写

let obj3 = {
  姓: "高", 名: "圆圆", get 姓名() {
    return this.姓 + this.名;
  }, set 姓名(xxx) {
    this.姓 = xxx[0]
    this.名 = xxx.slice(1)
  }, age: 18
};

obj3.姓名 = '高媛媛'

console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)

// 总结:setter 就是这样用的。用 = xxx 触发 set 函数

image.png

Object.defineProperty()

Vue是通过 JS 标准内置对象方法 Object.defineProperty 来设定将data中普通的属性n转化为getter、setter方法的属性n的。

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。

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

参数

obj:要在其上定义属性的对象。

prop:要定义或修改的属性的名称。

descriptor:将被定义或修改的属性描述符。

返回值: 被传递给函数的对象。

Object.defineProperty() - JavaScript | MDN (mozilla.org)

let data0 = {
  n: 0
}

// 需求一:用 Object.defineProperty 定义 n
let data1 = {}

Object.defineProperty(data1, 'n', {
  value: 0
})

console.log(`需求一:${data1.n}`)

// 总结:这煞笔语法把事情搞复杂了?非也,继续看。

// 需求二:n 不能小于 0
// 即 data2.n = -1 应该无效,但 data2.n = 1 有效

let data2 = {}

data2._n = 0 // _n 用来偷偷存储 n 的值

Object.defineProperty(data2, 'n', {
  get() {
    return this._n
  },
  set(value) {
    if (value < 0) return
    this._n = value
  }
})

console.log(`需求二:${data2.n}`)
data2.n = -1
console.log(`需求二:${data2.n} 设置为 -1 失败`)
data2.n = 1
console.log(`需求二:${data2.n} 设置为 1 成功`)

// 抬杠:那如果对方直接使用 data2._n 呢?
// 算你狠

image.png

proxy() 代理

// 需求三:使用代理

let data3 = proxy({data: {n: 0}}) // 括号里是匿名对象,无法访问

function proxy({data}/* 解构赋值,别TM老问 */) {
  const obj = {}
  // 这里的 'n' 写死了,理论上应该遍历 data 的所有 key,这里做了简化
  // 因为我怕你们看不懂
  Object.defineProperty(obj, 'n', {
    get() {
      return data.n
    },
    set(value) {
      if (value < 0) return
      data.n = value
    }
  })
  return obj // obj 就是代理
}

// data3 就是 obj
console.log(`需求三:${data3.n}`)
data3.n = -1
console.log(`需求三:${data3.n},设置为 -1 失败`)
data3.n = 1
console.log(`需求三:${data3.n},设置为 1 成功`)

// 杠精你还有话说吗?
// 杠精说有!你看下面代码
// 需求四

let myData = {n: 0}
let data4 = proxy({data: myData}) // 括号里是匿名对象,无法访问

// data3 就是 obj
console.log(`杠精:${data4.n}`)
myData.n = -1
console.log(`杠精:${data4.n},设置为 -1 失败了吗!?`)

// 我现在改 myData,是不是还能改?!你奈我何
// 艹,算你狠

// 需求五:就算用户擅自修改 myData,也要拦截他

let myData5 = {n: 0}
let data5 = proxy2({data: myData5}) // 括号里是匿名对象,无法访问

function proxy2({data}/* 解构赋值,别TM老问 */) {
  // 这里的 'n' 写死了,理论上应该遍历 data 的所有 key,这里做了简化
  // 因为我怕你们看不懂
  let value = data.n
  Object.defineProperty(data, 'n', {
    get() {
      return value
    },
    set(newValue) {
      if (newValue < 0) return
      value = newValue
    }
  })
  // 就加了上面几句,这几句话会监听 data

  const obj = {}
  Object.defineProperty(obj, 'n', {
    get() {
      return data.n
    },
    set(value) {
      if (value < 0) return//这句话多余了
      data.n = value
    }
  })

  return obj // obj 就是代理
}

// data3 就是 obj
console.log(`需求五:${data5.n}`)
myData5.n = -1
console.log(`需求五:${data5.n},设置为 -1 失败了`)
myData5.n = 1
console.log(`需求五:${data5.n},设置为 1 成功了`)


// 这代码看着眼熟吗?
// let data5 = proxy2({ data:myData5 })
// let vm = new Vue({data: myData})

// 现在我们可以说说 new Vue 做了什么了

image.png 结论:Vue会遍历传入的data对象所有属性,并使用Object.defineProperty把这些属性全部转为getter/setter,这样就生成一个新的对象全权负责数据——就是实例化的Vue对象vm。这样vm会成为data 的代理,对 data 的所有属性进行监控,当数值发生改变的时候,vue就调用render函数重新渲染视图。

image.png

数据响应式

当你创建一个实例时

const vm = new Vue({data:{n: 0}})
  • vue 会让 vm 成为 myData 的代理。
  • vue 会对 myData 的所有属性进行监控。
  • 示例

1、在 data 中添加属性

对于一般的对象来说,可以在 data 中预先把所有可能用到的属性全部写出来,这样并不需要新增属性,只需要改它。 也可以通过其他方法来添加属性。 在了解以上原理后,我们来了解 Vue 提供的一个 API:

Vue.set(object, key, value)
或
this.$set(object, key, value)

image.png

2、对数组的方法

vue对数组进行了改变,给数组加了一层原型,在其中Vue修改了7个方法覆盖了之前数组原型的7个方法。调用这些Vue新定义的方法时,在这些新方法里Vue会加上对新添的元素的监听(相当于进行了set操作),把新数据也进行代理,这样vue就能重新监测到数组的变化了更新UI操作 具体的七个变更方法:

  • push()(在数组结尾处)向数组添加一个新的元素
  • pop()方法从数组中删除最后一个元素
  • shift()会删除首个数组元素,并把所有其他元素“位移”到更低的索引
  • unshift()(在开头)向数组添加新元素,并“反向位移”旧元素
  • splice()拼接,可用于向数组添加新项
  • sort()
  • reverse()

computed 和 watch的区别

computed

对比

  1. 不用 computed 筛选男女
  2. 用 computed 筛选男女

这个我有过总结 computed 和 watch的区别

模板、指令与修饰符

Vue 模板、指令与修饰符

进阶构造属性

Vue 进阶属性

directives、mixins、extends、provide、inject

directives 指令

内置指令

  • v-if、v-for、v-show、v-html

自定义指令

一、 声明一个全局指令

Vue.directive('x', directiveOptions)

二、 声明一个局部指令

new Vue({
    ...,
    directives: {
        "x": directiveOptions
    }
})

directiveOptions

五个函数属性

  • bind(el, info, vnode, oldVnode) - 类似 created
  • inserted(参数同上) - 类似 mounted
  • update(参数同上) - 类似 updated
  • componentUpdated(参数同上) - 用的不多
  • unbind(参数同上) - 类似 destroyed

缩写

指令的作用

主要用于 DOM 操作

  • Vue 实例/组件用于数据绑定、事件监听、DOM 更新
  • Vue 指令主要目的就是原生 DOM 操作

减少重复

  • 如果某个 DOM 操作你经常使用,就可以封装为指令
  • 如果某个 DOM 操作比较复杂,也可以封装为指令

mixin 混入

类比

  • directives 的作用是减少 DOM 操作的重复
  • mixins 的作用是减少 data、methods、钩子的重复

image.png 代码

技巧

  • 选项智能合并 混入
  • Vue.mixin 全局混入

extends 继承、扩展

减少重复

  • 遇到与 mixins 同样的需求
  • Vue.extend 或 options.extends
const MyVue = Vue.extend({
    data(){return {name:'', time:undefined}}
    created(){
        if(!this.name){console.error(no name!)}
        this.time = new Date()
    },
    beforeDestroy(){
        const duration = (new Date()) - this.time
        console.log(`${this.name}存活时间${duration}`)
    }
})
  • 然后就可以使用 new MyVue(options)

provide 和 inject

使用

  • provideObject | () => Object
  • injectArray<string> | { [key: string]: string | Symbol | Object }
  • provide 是祖先组件向子孙后代注入一个依赖
  • inject 是让后代注入祖先提供的依赖

image.png

示例

示例

总结

  • 作用:大范围的 data 和 method 等共用
  • 注意:不能只传值不传方法,因为值是被复制给 provide的
  • 可以传引用但不推荐,容易失控

表单与v-model

基本用法

input / textarea / checkbox / radio / select / form

  • input-文本:
<input v-model="message" placeholder="edit me"> 
<p>Message is: {{ message }}</p>
  • textarea-多行文本:
<span>Multiline message is:</span> 
<p style="white-space: pre-line;">{{ message }}</p> 
<br> 
<textarea v-model="message" placeholder="add multiple lines"></textarea>
  • checkbox-复选框:
<input type="checkbox" id="checkbox" v-model="checked"> 
<label for="checkbox">{{ checked }}</label>
  • 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: '' 
    } 
})
  • select-选择框:

    • 单选时:
<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: '' 
    } 
})

多选时:

<div id="example-6"> 
    <select v-model="selected" multiple style="width: 50px;"> 
        <option>A</option> 
        <option>B</option> 
        <option>C</option> 
    </select>
    <br> 
    <span>Selected: {{ selected }}</span> 
</div>

new Vue({ 
    el: '#example-6', 
    data: { 
        selected: [] 
    } 
})
  • form:
 <form @submit.prevent="onSubmit">
  <label>
    <span>用户名</span>
    <input type="text" v-model="user.username" />
  </label>
  <label>
    <span>密码</span>
    <input type="text" v-model="user.password" />
  </label>
  <button type="submit">登录</button>
</form>
    
new Vue({ 
      name: "App",
  data(){
    return {
      user:{
        username: "",
        password: ""
      },
    }
  },
  methods:{
    onSubmit()
  },
  components: {},
})

修饰符

.lazy / .number / .trim

  • v-model.lazy,焦点失去时生效
  • v-model.number,只接收数字
  • v-model.trim,两头空格去掉

v-model

  • 默认利用名为 value 的 prop 和名为 input 的事件

  • 等价于 <input type="text" :value="user.username" @input="user.username = $event.target.value">

  • 双向绑定:v-model会绑定一个变量,在变量变化的时候 UI 会变化,用户改变 UI 的时候,数据也会改变

  • v-model 是 v-bind:value 和 v-on:input 的语法糖

  • 自定义:v-on:input="$event",原生:v-on:input="$event.target.value"

  • 监听的事件

    • input 事件,键盘、鼠标、任何输入设备的输入
    • change 事件,只在 input 是去焦点时触发 (v-model.lazy)

使用 Ant Design of Vue

使用组件

$ npm i --save ant-design-vue

/* 完整引入 Antd 组件 */
import Vue from 'vue'
import App from './App.vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'

Vue.config.productionTip = false;
Vue.use(Antd)

new Vue({
  render: h => h(App),
}).$mount('#app')

/* 局部导入组件 */
import { Button, message } from 'ant-design-vue'

Vue.use(Button)

VueRouter

Vue-Router 前端路由实现的思路

Vue 动画原理

#文档 过渡 & 动画

轮播组件slides
轮播难点在于最末位到首位的切换方式,在讲轮播之前需要讲下动画。 Vue动画支持很多种不同的方式。

Vue动画方式1 - CSS transition

Vue提供了transition组件

HTML
//先引入Vue(bootCDN)
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.5.17/vue.min.js"></script>

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
//1.写`<transition>`
  <transition name="fade"> 
    <p v-if="show">hello</p>
  </transition>
</div>

CSS
//2.写类
.fade-enter-active, .fade-leave-active {
  transition: all 2s;
  }
.fade-enter, .fade-leave-to {
  opacity: 0;
  width:100px
  }
//3.设置初始值
p{ 
  border:1px solid red;
  width:300px
  }
  
JS
new Vue({
  el: '#demo',
  data: { show: true }
})

步骤

第1步.在html里写<transition>

第2步.在css里写.fade开头的一系列类

最后给需要的属性添加初始值

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 ,则v-是这些类名的默认前缀。如果你使用了 <transition name="fade">,那么v-enter会替换为fade-enter

文档 过渡的类名

在进入/离开的过渡中,会有6个class切换:

v-enter-active、v-leave-active表示过渡时间有多长,一般会合并写这2个类,因为动的都是一样的。 v-enter、v-leave-to表示进入和退出状态,这2个类也会合写。剩下2个一般不用。

在这里插入图片描述

p经历的过程:

一开始p就是这个类,但是由于目前是隐藏的show: false,所以需要enter进入DOM,进入DOM的过程可以详细的去控制。

v-enter控制进入时的开始的状态,v-enter-to控制进入时的结束的状态,fade-enter-active控制它如何去添加补间动画,一般不需要加v-enter-to因为结束状态应该就是它原本状态p,没必要加。 等动画结束,就会把这3个类v-enter、fade-enter-active、v-enter-to都删掉,恢复到原始状态p

p由于目前是隐藏的所以需要enter进入DOM,进入DOM的过程可以详细的进行控制。开始是红色,然后变成黑色,过程持续3s。动画结束后enter被删掉,恢复到原始的白色

CSS 过渡

html
<div id="example-1">
  <button @click="show = !show"> Toggle render </button>
  <transition name="slide-fade"> //滑出
    <p v-if="show">hello</p>
  </transition>
</div>

CSS
/* 可以设置不同的进入和离开动画,设置持续时间和动画函数 */
.slide-fade-enter-active { 
  transition: all 3s ease;//滑出淡入不是线性3s
}
.slide-fade-leave-active { 
  transition: all 1s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter, .slide-fade-leave-to {
//fade-leave-to"淡出的结束状态"就是"淡入开始的状态",fade-enter对应fade-leave-to
  transform: translateX(10px);//淡入那一瞬间的位置
  opacity: 0;
}

JS
new Vue({
    el: '#example-1',
    data: { show: true }
})

Vue动画方式2 - CSS animation

html
<div id="example-2">
  <button @click="show = !show">Toggle show</button>
  <transition name="bounce"> //bounce类的前缀
    <p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
  </transition>
</div>

CSS
.bounce-enter-active { 
  animation: bounce-in .5s;
}
.bounce-leave-active { 
  animation: bounce-in .5s reverse; //bounce-in
}
@keyframes bounce-in { //bounce-in
  0% {transform: scale(0);}
  50% {transform: scale(1.5);}
  100% {transform: scale(1);} //结束时恢复正常状态
}

JS
new Vue({
  el: '#example-2',
  data: { show: true }
})

keyframes语法

@keyframes spin { 
  0% {opacity: 0;}
  100% {opacity: 1;}
}
使用
.fade-enter-active{
  animation:spin 1s reverse;
}

类名

可以通过以下 attribute 来自定义过渡类名:
enter-class、enter-active-class、enter-to-class、
leave-class、leave-active-class、leave-to-class

也可以结合第三方CSS动画库 Animate.css 提供了很多动画效果。

Animate.css

bootCDN选择animate.min.css 示例

html
<link href="https://cdn.bootcdn.net/ajax/libs/animate.css/3.5.2/animate.min.css" rel="stylesheet">

<div id="example-3">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition
    enter-active-class="animated tada" 
  //animated后接的就是动画效果,格式animated xxx
    leave-active-class="animated bounce" 
  >
    <p v-if="show">hello</p>
  </transition>
</div>

JS
new Vue({
  el: '#example-3',
  data: {show: true}
})

CSS 不需要写CSS

同时使用过渡和动画

在一些场景中,你需要给同一个元素同时设置两种过渡动效,比如 animation很快的被触发并完成了,而transition效果还没结束。在这种情况中,你就需要使用type属性并设置animationtransition来明确声明你需要Vue监听的类型。

显性的过渡持续时间

有的时候需要手动设置淡入淡出时间

<transition :duration="1000">...</transition> //:duration="1000"

JavaScript 钩子

文档 JS钩子

可以在attribute中声明JS钩子,你可以用这些钩子知道当前动画处在哪个阶段。

Vue动画方式3 - JS 操作动画

velocity是一个非常著名的用JS操作动画的库,推荐用这个做动画。

bootCDN选择velocity.min.js,最好用版本1.2.3的。

<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

<div id="example-4">
  <button @click="show = !show">
    Toggle
  </button>
  <transition
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    v-bind:css="false"
  >
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>

JS
new Vue({
  el: '#example-4',
  data: {
    show: false
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
      el.style.transformOrigin = 'left'
    },
    enter: function (el, done) {
      Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
      Velocity(el, { fontSize: '1em' }, { complete: done })
    },
    leave: function (el, done) {
      Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
      Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
      Velocity(el, {
        rotateZ: '45deg',
        translateY: '30px',
        translateX: '30px',
        opacity: 0
      }, { complete: done })
    }
  }
})

Vue动画方式4 - 多元素动画

一.多个元素的过渡 文档

示例1:淡出淡入
<div id="example-4">
  <transition name="fade" mode="out-in">
    <button key="on" v-if="status==='off'" @click="status='on'">on</button>
    <button key="off" v-else @click="status='off'">off</button>
  </transition>
</div>

JS
new Vue({
    el: '#example-4',
    data: { status: 'on'},
})    

CSS
.fade-enter-active,.fade-leave-active {
  transition: all 1s;
}
.fade-enter {
  opacity: 0;
}
.fade-leave-to {
  opacity: 0;
}

示例2:先进再出
<transition name="fade" mode="in-out">
CSS
.fade-enter-active,.fade-leave-active {
  transition: all 1s;
}
.fade-enter {
  opacity: 0;
  transform:translateX(100px)
}
.fade-leave-to {
  opacity: 0;
  transform:translateX(-100px)
}
#example-4{
  position:relative;
}
button{
 position:absolute;
}
JS同上略

示例3:实现轮播效果
<transition name="fade">
CSS
#example-4{
  position:relative;
  padding:100px;
}
JS同上略

必须要加key才有动画,不然它不知道你要变什么,它会以为两个button是一个。当两个标签一样的时候一定要加key
同时生效的进入和离开的过渡不能满足要求,所以Vue提供了过渡模式:

out-in:当前元素先进行过渡,完成之后新元素过渡进入。
in-out(用的少):淡出的时候往左移
mode="out-in/in-out"是为了解决2个元素占用位置冲突的问题。

二.多个组件的过渡
就是用<component>绑定is属性
只需要使用动态组件

//实际上就是tab切换
<div id="transition-components-demo">
  <transition name="component-fade" mode="out-in">
    <component v-bind:is="view"></component>
  </transition>
</div>

JS
new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a' //view是啥,就是哪个组件的名字
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})

CSS
.component-fade-enter-active, .component-fade-leave-active {
  transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to {
  opacity: 0;
}

适用场景:如果你有10个组件在一个地方显示,想在切的时候有动画。就用<component :is="view">组件,如果你想让第一个组件出现就让view: 'v-a',view等于第几个组件,就默认显示它。不需要加key。

Vue动画5 - 列表动画(常用)

文档列表过渡

<div id="list-demo" class="demo">
  <button v-on:click="add">Add</button>
  <button v-on:click="remove">Remove</button>
  <transition-group name="list" tag="p"> 
    <span v-for="item in items" v-bind:key="item" class="list-item">
      {{ item }}
    </span>
  </transition-group>
</div>

CSS
.list-item {
  display: inline-block;
  margin-right: 10px;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to
/* .list-leave-active for below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(30px);
}

JS
new Vue({
  el: '#list-demo',
  data: {
    items: [1,2,3,4,5,6,7,8,9],
    nextNum: 10
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
  }
})

知识点
1.name="list"list就是css的前缀
2.<transition-group> 组件
使用场景:要想用v-for实现同时渲染整个列表,这时候就可以使用<transition-group>组件。

该组件特点:
(1)默认为一个<span>,也可以通过 tag attribute更换为其他元素。

<transition-group name="list" tag="p">
  <span v-for="item in items" v-bind:key="item" class="list-item">
    {{ item }}
  </span>
</transition-group>

tag='p'tag取值是p,<transition-group>就会将自身替换为p标签。tag取值是什么,<span>就会被什么标签包围。不写tag就默认是<span>

在这里插入图片描述

(2)过渡模式不可用,因为我们不再相互切换特有的元素。 mode=“out-in"不能用,,<transition-group>不支持mode

(3)内部元素总是需要提供唯一的 key attribute 值。 内部元素<span>必须写keyv-bind:key="item"

(4)CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。

for循环如何做动画?
<transition-group> + tag="div" + v-for循环的模版(span/div)
注意:不能加其它的标签,for循环模版外只能是<transition-group>

总结

在这里插入图片描述

Previous模板、指令与修饰符