通俗重制系列--Vue2基础教程
起手式
完整版
同时包括编译器(compiler) 和 运行时(runtime)将模板字符串编译为 JavaScript 渲染函数(render函数)的代码 运行时的功能包括创建 Vue 实例、渲染并处理虚拟 DOM 等,它包括除了编译器的其他所有功能\
两个版本的区别
Vue完整版 | Vue只包含运行时版 | |
特点 | 有compiler | 没有compiler |
视图 | 写在HTML里,或者写在template选项里 | 写在render函数里,用h创建标签 |
cdn引入 | vue.js | vue.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的实例。
这个实例会根据你给的选项得出一个对象(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 //很少用
第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.js
中new 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。
知识点
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
- 任何一个 Vue Component 都有一个与之对应的 Watcher 实例。
- Vue 的
data
上的属性会被添加 getter 和 setter 属性。 - 当 Vue Component
render
函数被执行的时候,data
上会被触碰(touch)
, 即被读
,getter
方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有data
。(这一过程被称为依赖收集) 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 函数
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 呢? // 算你狠
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 做了什么了
数据响应式
当你创建一个实例时
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)
2、对数组的方法
vue对数组进行了改变,给数组加了一层原型,在其中Vue修改了7个方法覆盖了之前数组原型的7个方法。调用这些Vue新定义的方法时,在这些新方法里Vue会加上对新添的元素的监听(相当于进行了set操作),把新数据也进行代理,这样vue就能重新监测到数组的变化了更新UI操作 具体的七个变更方法:
push()
(在数组结尾处)向数组添加一个新的元素pop()
方法从数组中删除最后一个元素shift()
会删除首个数组元素,并把所有其他元素“位移”到更低的索引unshift()
(在开头)向数组添加新元素,并“反向位移”旧元素splice()
拼接,可用于向数组添加新项sort()
reverse()
computed 和 watch的区别
computed
对比
这个我有过总结computed 和 watch的区别
模板、指令与修饰符
进阶构造属性
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
缩写
- directiveOptions 在某些条件下可以缩写为函数,自定义指令 — Vue.js (vuejs.org)
指令的作用
主要用于 DOM 操作
- Vue 实例/组件用于数据绑定、事件监听、DOM 更新
- Vue 指令主要目的就是原生 DOM 操作
减少重复
- 如果某个 DOM 操作你经常使用,就可以封装为指令
- 如果某个 DOM 操作比较复杂,也可以封装为指令
mixin 混入
类比
- directives 的作用是减少 DOM 操作的重复
- mixins 的作用是减少 data、methods、钩子的重复
技巧
- 选项智能合并 混入
- 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
使用
- provide:
Object | () => Object
- inject:
Array<string> | { [key: string]: string | Symbol | Object }
- provide 是祖先组件向子孙后代注入一个依赖
- inject 是让后代注入祖先提供的依赖
示例
总结
- 作用:大范围的 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 动画原理
#文档 过渡 & 动画
轮播组件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属性并设置animation或transition来明确声明你需要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模板、指令与修饰符