- A+
Vue基础.js
目录:
点击文字跳转,点击背景或三角展开
使用Vue的好处:
文档传送门:Vue官网
语法:
el:挂载点
-
el设置Vue实例的挂载的元素
-
Vue会管理el选中的及其内部的后代元素(不用js或jq操作不了挂载点之外的)
-
可以使用选择器(dom也可以),但推荐使用id选择器
-
可以选择使用其他双标签,但不能是html和body(id或类选择器等则也可以选单标签)
注:在methods中,想用dom就可以this.$el(等价于document.getElementById("app"))
data:数据对象
-
Vue中用到的数据都定义在data中
-
data中可以写复杂类型的数据
-
渲染复杂类型的数据时,遵守js的语法即可
computed:计算属性
- 用于预处理data中的属性
- 内容都是方法,相较于methods而言,其方法大多为名词
- 除非方法内的data值发生改变,会再次执行方法,否则方法只执行一次。执行后会将值进行缓存,下次调用直接从缓存中获取
- 例如用插值表达式使用时,是不需要在方法名后加
()
进行使用的
示例:
<div id="app"> {{fullName}} 他花了 {{totalMoney}} 巨资充实了自己 </div> <script> var app = new Vue({ el: '#app', data: { finalName: "猫巷", lastName: "Number One", books: [{ id: 1, name: "Thinking In Java", price: 40 }, { id: 2, name: "On Java", price: 30 }, { id: 3, name: "深入理解计算机原理", price: 20 }, { id: 4, name: "现代操作系统", price: 10 } ] }, computed: { fullName: function() { return this.finalName + " " + this.lastName }, totalMoney: function() { let totalMoney = 0; for (let i = 0; i < this.books.length; i++) { totalMoney += this.books[i].price; } return totalMoney; } } });
结果:
以上computed里的两个方法是简写,每个计算属性的方法都有一对getter和setter,取值时调用get()
,赋值时调用set()
。具体如下:
computed: { fullName: { get: function(){ return this.finalName + " " + this.lastName }, set: function(newValue){ // newValue为此fullname的新赋的值 // set方法通常都不写,因为computed为计算属性。因此通常也被称为只读属性 } }, fullName: { get: function(){ let totalMoney = 0; for (let i = 0; i < this.books.length; i++) { totalMoney += this.books[i].price; } return totalMoney; }, set: function(newValue){ // newValue为此fullname的新赋的值 // set方法通常都不写,因为computed为计算属性。因此通常也被称为只读属性 } } }
methods:定义函数
- Vue中用到的方法都定义在methods中,且大多为动词形式
- 写法与普通方法无异,遵守js的语法即可
- 相较于computed,没有缓存,因此效率与computed相比较低
指令
插值表达式
挂载完对应元素后,可通过{{message}} 取出data中的message值
插值表达式之间支持简单Js,例如:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> </head> <body> <div id="app"> {{5+5}}<br> {{ ok ? 'YES' : 'NO' }}<br> {{ message.split('').reverse().join('') }} <div v-bind:id="'list-' + id">菜鸟教程</div> </div> <script> new Vue({ el: '#app', data: { ok: true, message: 'RUNOOB', id : 1 } }) </script> </body> </html>
效果如下:
10 YES BOONUR 菜鸟教程 (div的id为list-1)
v-once:只展示初始值
使用v-once后,在渲染数据后,修改data的值,将不会再进行响应式更新。即只展示最初始的值
<div id="app"> <span v-once>{{message}}</span> </div> <script> new Vue({ el: "#app", data:{ message: "hello vue" } }) </script>
注意:v-once 后面不需要加
=""
v-pre:不解析语法
等价于<pre></pre>
标签,例如:
<div id="app"> <span v-pre>{{message}}</span> </div> <script> new Vue({ el: "#app", data:{ message: "hello vue" } }) </script>
此时页面展示不是hello vue
,而是{{message}}
v-cloak:等待vue解析完数据后再显示页面
浏览器解析html文件是从上往下,有时候js代码卡住,用户就会先看见还未渲染的数据,如{{message}}
,而不是data中的hello vue
,这体验是不好的。v-cloak原理是,加上v-cloak属性的元素,默认会看不见,等待vue渲染完毕后就会删除所有v-cloak属性,然后就自动展示出数据了。
v-text:设置文本值
- 设置双标签内的文本值
- 会覆盖原双标签里的文本值
- 内部可写表达式,也会识别为文本
v-html:设置innerHtml
- 设置双标签的innerHtml值
- 需要在内部写html语句采用v-html,只需要纯文本则用v-text。v-html写的会被解析为html语句
v-if :判断
根据 if 表达式后的值(true 或 false )来决定是否插入此元素
<div id="app"> <span v-if="flag"> <label>账号:</label> <input type="text" id="account" placeholder="请输入账号"> </span> <span v-else> <label>邮箱:</label> <input type="text" id="emails" placeholder="请输入邮箱"> </span> <button type="button" @click="flag = !flag">切换登录</button> </div> <script type="text/javascript"> new Vue({ el:"#app11", data:{ flag : true } }) </script>
点击按钮效果如图:
注意:表单里的值之所以没清空,是因为Vue底层采用的
虚拟Dom
,元素Dom结构极为相似,且是对立情况出现的情况下,Vue底层会渲染同一个的虚拟Dom到页面上,再比较所渲染元素的具体区别(属性值等),进行页面的渲染。
虚拟Dom
浏览器解析Dom时,会将元素直接渲染到页面上。而Vue、React等底层采用了虚拟Dom,元素会先被Vue渲染到虚拟Dom中,在渲染到页面的同时会比较是否有可重复利用的元素,如果有 则渲染时直接复制原虚拟Dom的元素,再根据具体区别(属性值等)进行修改。这么做大大提升了页面渲染的效率。
总结:
- 获取监听变化后生成的虚拟节点树
- 与上一次虚拟DOM节点树进行比较
- 找到差异的部分,渲染到真实的DOM节点上面
- 更新视图
特殊情况需要解决虚拟Dom的复用时
上述例子,切换表单明显需要清空表单内的值,此时虚拟Dom带来了反面效果,可通过添加key属性来解决。
- key值相同,代表可以复用
- key值不同,代表不能被复用
示例:
<div id="app11"> <span v-if="flag"> <label>账号:</label> <input type="text" id="account" placeholder="请输入账号" key="username"> </span> <span v-else> <label>邮箱:</label> <input type="text" id="emails" placeholder="请输入邮箱" key="email"> </span> <button type="button" @click="flag = !flag">切换登录</button> </div>
上述例子label
没写key,所以可被复用,input
二者的key不同,即不可复用。在点击按钮时,input内的值自然就被清空了
v-show:是否展示此元素
-
当显示条件为true时:
v-if
与v-show
效果相同 -
当显示条件为false时:
v-if
不渲染dom元素,而v-show仍然渲染;渲染时会加上行内样式:style = "display: none"
当数据要在显示和隐藏间频繁切换时,使用v-show
当数据只有一次切换或根据条件及进行渲染时使用v-if
v-for:循环
常用场景:
-
数组集合循环
<div id="app10"> <ul> <!-- 1.item为值,index为索引 2.就写一个默认就为item,即是值 3.括号可省略但不建议 --> <li v-for="(item,index) in arr">{{item}}==={{index}}</li> </ul> </div> <script type="text/javascript"> new Vue({ el: "#app10", data: { arr: [1,2,3,4,5] }, }) </script>
结果:
-
遍历Json形式的数据(超好用)
<div id="app10"> <ul> <!-- 1.注意value和key的顺序是反的,先写的始终是值value,后写的是索引index或键key 2.其他点同上 --> <li v-for="(value,key) in person">{{key}}==={{value}}</li> </ul> </div> <script type="text/javascript"> new Vue({ el: "#app10", data: { person: { id:1001, username:"admin", password:"123" } }, }) </script>
结果:
-
json集合(两者结合)
<div id="app10"> <table cellspacing="1" border="1"> <tr> <th>编号</th> <th>id</th> <th>产品名称</th> <th>产品价格</th> </tr> <tr v-for="(product,index) in persons"> <td>{{ index + 1 }}</td> <td v-for="value in product">{{ value }}</td> </tr> </table> </div> <script type="text/javascript"> new Vue({ el: "#app10", data: { persons: [ {id: 1001, proName: "手机", proMoney: "123"}, {id: 1002, proName: "平板", proMoney: "1090"}, {id: 1003, proName: "电脑", proMoney: "2222"}, {id: 1004, proName: "摄像机", proMoney: "3041"}, ] }, }) </script>
结果:
注:获取对象索引还可以:
v-for="(value, key, index) in Person"
哦
注意事项
1. 关于提高性能
官方推荐我们在使用v-for时,给对应的元素或组件加上:key
,即绑定key属性。且绑定的属性值为当前元素(item)
原因:这么做其实与其虚拟Dom的Diff算法有关
Diff算法:Diff算法的作用是用来计算出 Virtual DOM (即虚拟Dom) 中被改变的部分,然后针对该部分进行原生DOM操作,而不用重新渲染整个页面
案例:
页面使用ul-li标签展示数组arr=[A,B,C,D,E]
,此时往第三个位置插入个F元素(即创建新元素)。
此时高效率做法应是直接往第三个位置插入F元素①,而虚拟Dom采用的是极低效率的“将C变成F,D变成C,E变成D,创建新li标签且值为E“。数据更新后,采用Diff算法渲染时会”判断不同的数量,再一个一个更替不同的地方“②。此做法会提升消耗,降低效率。
此时,若在遍历时加入key属性,且与单个遍历对象item
一一绑定,遍历时虚拟Dom会优先将key
与item
对应的值进行绑定渲染,再将新创建的(无对应key值)元素进行插入,即可达到①的效果
用代码总结就是:<ul><li v-for="item in arr" :key="item"> </li></ul>
注意:
- 绑定index是没用的,因为新增或删除元素时,index会发生变化,此时固定的item对应的index就会发生改变。
- 绑定item的前提是保证
arr
数组中的元素是唯一(不重复的)
2. 关于响应布局实时渲染
对于数组,调用push、pop、shift、unshift、splice、sort、reverse
等方法,都可以达到响应式布局。
但是arr[2] = "aaa"
这种直接通过索引下标修改元素的,Vue是没有监听的,因此不会实时渲染到页面上。
通常解决方式有两种:
arr.splice(2, 1, "aaa");
Vue.set(arr, 2, "aaa")
, 即Vue(要修改的对象,要修改的索引值,修改后的值)
v-on:事件绑定
- 指令作用为:为元素绑定事件
- 事件名不需要写on (v-on:click)
- 指令可简写成@(@click)
- 绑定的方法定义在methods中
- 方法内部可通过this访问定义在data中的属性
当绑定的方法没有参数时,括号可加可不加。
this中有很多$开头的属性。如$event表示原生dom的事件。this.$el = document.getElementById("app")
使用
@click
后想要获取点击本事件本体,有两种方式: a.
this.$refs.ref
可获得el绑定的所有带有ref属性的标签,再通过下标即可获取对应元素。 b. 通过
event.currentTarget
获取本身标签元素this
。
事例:绑定methods中的方法名
<div id="example-2"> <!-- `greet` 是在下面定义的方法名,不写括号时,默认方法的`第一个参数`可接收原生event事件--> <button v-on:click="greet">Greet</button> </div> <script> var example2 = new Vue({ el: '#example-2', data: { name: 'Vue.js' }, // 在 `methods` 对象中定义方法 methods: { greet: function (event) { // `this` 在方法里指向当前 Vue 实例 alert('Hello ' + this.name + '!') // `event` 是原生 DOM 事件 if (event) { alert(event.target.tagName) } } } }) </script>
事件数量:
Dom有的事件Vue基本都有,总的事件如图:
修饰符
修饰符是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault(),即阻止元素的默认行为。event.stopPropagation()阻止事件冒泡等。常用的修饰符有以下几种:
.stop
- 阻止冒泡.prevent
- 阻止默认事件.capture
- 阻止捕获.self
- 只监听触发该元素的事件.once
- 只触发一次.left
- 左键事件.right
- 右键事件.middle
- 中间滚轮事件
事例
<!-- 阻止单击事件继续传播 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 只有修饰符 --> <form v-on:submit.prevent></form> <!-- 添加事件监听器时使用事件捕获模式 --> <!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 --> <div v-on:click.capture="doThis">...</div> <!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 即事件不是从内部元素触发的 --> <div v-on:click.self="doThat">...</div> <!-- 点击事件将只会触发一次 --> <a v-on:click.once="doThis"></a> <!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 --> <!-- 而不会等待 `onScroll` 完成 --> <!-- 这其中包含 `event.preventDefault()` 的情况 --> <!-- 这个 .passive 修饰符尤其能够提升移动端的性能 --> <div v-on:scroll.passive="onScroll">...</div>
注:
- 使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用
v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击。- 不要把
.passive
和.prevent
一起使用,因为.prevent
将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive
会告诉浏览器你不想阻止事件的默认行为。
v-bind:动态绑定
v-bind 被用来响应地更新 HTML 属性,可简写为一个冒号:
。
例子:
1. 用以挂载data中的数据
<div id="app"> <pre> <a v-bind:href="url">菜鸟教程</a> </pre> </div> <script> new Vue({ el: '#app', data: { url: 'http://www.runoob.com' } }) </script>
注意:插值表达式无法直接赋值标签的属性值。
2. 切换class属性
可根据布尔值来确定是否拼接class属性
固定的class可直接在前面定义,即 class="class1",因为:class是拼接上去的,不会覆盖原来
a. 传入数组,根据布尔值进行是否拼接class属性
<html> <head> <meta charset="utf-8"> <title>v-bind</title> </head> <body> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> <div id="app"> <div v-bind:class="{'active': isActive,'line':isLine}"> v-bind:class 指令 </div> </div> <script> new Vue({ el: '#app', data: { isActive: true, isLine:false } }); </script> </body> </html>
效果如图:
上述例子等价于:
<html> <head> <meta charset="utf-8"> <title>v-bind</title> </head> <body> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> <div id="app"> <!-- 也可以指向method或computed --> <div v-bind:class="getClasses()"> v-bind:class 指令 </div> </div> <script> new Vue({ el: '#app', data: { isActive: true, isLine: false }, methods: { getClasses: function() { return { 'active': this.isActive, 'line': this.isLine }; } } }); </script> </body> </html>
b. 与v-model结合使用,达到切换class效果
<html> <head> <meta charset="utf-8"> <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title> </head> <style> .class1{ background: #444; color: #eee; } </style> <body> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> <div id="app"> <label for="r1">修改颜色</label><input type="checkbox" v-model="use" id="r1"> <br><br> <div v-bind:class="{'class1': use}"> v-bind:class 指令 </div> </div> <script> new Vue({ el: '#app', data:{ use: false } }); </script> </body> </html>
效果: ,点击选择框,下方div背景会跟随选择框的状态改变
c. 传入数组,批量绑定属性
<div id="app"> <div class="test1" v-bind:class="[test2, test3]"> v-bind:class 指令 </div> </div> <script> new Vue({ el: '#app', data: { test2: "class2", test3: "class3" } }); </script>
结果:
3. 动态绑定style
实例:
<div id="app"> <!-- 格式: :style="key(属性名): value(属性值)" --> <h2 :style="{fontSize: '5px'}">动态绑定style</h2> <h2 :style="{fontSize: finalSize+'px'}">动态绑定style</h2> </div> <script> var app = new Vue({ el: '#app', data: { finalSize: 5 } }); </script>
结果:
同上,也可以传入数组,进行批量设值
v-model:数据双向绑定
用来在 input
、select
、textarea
、checkbox
、radio
等表单控件元素上创建双向数据绑定,根据表单上的值,自动更新绑定的元素的值
input示例
<div id="app"> <p>{{ message }}</p> <input v-model="message"> </div> <script> new Vue({ el: '#app', data: { message: 'Runoob!' } }) </script>
radio示例
<div id="app"> <label> <input type="radio" value="男" v-model="sex" />男 </label> <label> <input type="radio" value="女" v-model="sex" />女 </label><br> 性别是: {{ sex }} </div> <script> new Vue({ el: "#app", data: { sex: "女" } }) </script>
结果:
checked示例
<div id="app"> <input type="checkbox" value="篮球" v-model="hobbies">篮球 <input type="checkbox" value="足球" v-model="hobbies">足球 <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球 <input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球 <p>你的爱好有:{{ hobbies}}</p> </div> <script> new Vue({ el: "#app", data: { hobbies:['篮球', '足球'] } }) </script>
结果:
值绑定
示例:
<div id="app"> <label v-for="item in hobbies" :for="item"> <input type="checkbox" :id="item" :value="item" v-model="myHobbies" /> </label> <p>你的爱好有:{{ myHobbies}}</p> </div> <script> new Vue({ el: "#app", data: { hobbies: ['篮球', '足球', '乒乓球', '羽毛球', '保龄球', '高尔夫球'], myHobbies:[] } }) </script>
结果:
修饰符
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步。你可以添加 lazy
修饰符,从而转为在 change
事件_之后_进行同步:
<!-- 在“change”时而非“input”时更新 即敲下回车或表单失焦后触发,而不是敲一下触发一次事件,降低事件使用频率 --> <input v-model.lazy="msg">
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符(用户只能输入数字类型):
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number"
时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat()
解析,则会返回原始的值。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
<input v-model.trim="msg">
过滤器
用作一些常见的文本的格式化,并且多个过滤器可串联{{ message | filterA | filterB }}
格式:
<!-- 在两个大括号中 --> {{ message | capitalize }} <!-- 在 v-bind 指令中 --> <div v-bind:id="rawId | formatId"></div>
示例
插值表达式以及class属性的过滤(首个字母大写):
<!-- 过滤器 --> <div id="app9"> <div v-bind:class="clsTest|clsTest"> {{message | toLowerCase}} </div> </div> <script type="text/javascript"> new Vue({ el:"#app9", data:{ message:"runoob!", clsTest:"class" }, filters:{ toLowerCase:function(value){ return value.charAt(0).toUpperCase() + value.substr(1) }, clsTest:function(cls){ return cls+"1"; } } }) </script>
结果为:
<div class="class1">Runoob</div>
过滤器是 JavaScript 函数,因此可以接受参数:
{{ message | filterA('arg1', arg2) }}
Vue的生命周期
整体流程图:
文字解析
Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
Vue一共有8中生命周期(4种状态的前后):分别为:
- 创建Vue实例前 ---beforeCreate
- 创建Vue实例后 ---created
- 挂载前 ---beforeMount
- 挂载后 ---mounted
- 数据更新前 ---beforeUpdate
- 数据更新后 ---update
- 实例销毁前 ---beforeDestroy
- 实例销毁后 ---destroyed
其中,数据的渲染在mounted阶段渲染完毕。
每个过程对应一个事件钩子(callHook()),对应一个函数的执行,如 挂载前会执行beforeMount()方法
实例解析
<div id="app11"> {{ message }} </div> <script type="text/javascript"> var app = new Vue({ el: "#app11", data: { message: "Hello Vue.js!" }, beforeCreate: function () { showDate("vue实例创建前",this); }, created: function () { showDate("vue实例创建后",this); }, beforeMount: function () { showDate("挂载前",this); }, mounted: function () { showDate("挂载后",this); }, beforeUpdate: function () { showDate("数据更新前",this); }, updated: function () { showDate("数据更新后",this); }, beforeDestroy: function () { showDate("vue实例销毁前",this); }, destroyed: function () { showDate("vue实例销毁后",this); } }); /** 展示真实dom结构 **/ function realDom() { console.log("真实dom结构:"+document.getElementById("app11").innerHTML) } /** 展示data的数据以及将要挂载的对象 **/ function showDate(process,obj) { console.log(process); console.log("data数据:"+obj.message); console.log("挂载的对象:"); console.log(obj.$el); realDom(); console.log('-------------------') console.log('-------------------') } app.message="good" app.$destroy(); </script>
结果:
- 当app.message="good"作用,app.$destroy()注释时:
vue实例创建前 data数据:undefined 挂载的对象: undefined 真实dom结构: {{ message }} ------------------- ------------------- vue实例创建后 data数据:Hello Vue.js! 挂载的对象: undefined 真实dom结构: {{ message }} ------------------- ------------------- 挂载前 data数据:Hello Vue.js! 挂载的对象: <div id="app11"> {{ message }} </div> 真实dom结构: {{ message }} ------------------- ------------------- 挂载后 data数据:Hello Vue.js! 挂载的对象: <div id="app11"> good </div> 真实dom结构: Hello Vue.js! <!-- 注:此时这里挂载对象为“good”是因为message已经被赋值,但挂载上去的仍是“Hello Vue.js!”,原因是因为赋值一定会有一个值的变化的过程,而这个过程在Vue中发生在Mounted阶段,即挂载后 --> ------------------- ------------------- 数据更新前 data数据:good 挂载的对象: <div id="app11"> good </div> 真实dom结构: Hello Vue.js! ------------------- ------------------- 数据更新后 data数据:good 挂载的对象: <div id="app11"> good </div> 真实dom结构: good ------------------- -------------------
- 当app.$destroy()作用,app.message="good"注释时:
vue实例创建前 data数据:undefined 挂载的对象: undefined 真实dom结构: {{ message }} ------------------- ------------------- vue实例创建后 data数据:Hello Vue.js! 挂载的对象: undefined 真实dom结构: {{ message }} ------------------- ------------------- 挂载前 data数据:Hello Vue.js! 挂载的对象: <div id="app11"> {{ message }} </div> 真实dom结构: {{ message }} ------------------- ------------------- 挂载后 data数据:Hello Vue.js! 挂载的对象: <div id="app11"> Hello Vue.js! </div> 真实dom结构: Hello Vue.js! ------------------- ------------------- vue实例销毁前 data数据:Hello Vue.js! 挂载的对象: <div id="app11"> Hello Vue.js! </div> 真实dom结构: Hello Vue.js! ------------------- ------------------- vue实例销毁后 data数据:Hello Vue.js! 挂载的对象: <div id="app11"> Hello Vue.js! </div> 真实dom结构: Hello Vue.js! <!-- 之所以销毁前后与之前没变化,是因为destroy只是销毁了vue对象,然而vue已渲染、产生的效果是不会消失的。 --> ------------------- -------------------
Vue的Ajax异步请求:axios
通用写法格式:
// 发送 POST 请求 axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' }.then(function(){...}).catch(function(){...}) });
在axios中的this无法指代vue实例,拿取不到vue中data的数据。因此可在axios定义上方定义类似:
var _this = this;
来获取vue实例对象。然后获得user对象可通过_this.user
获取
GET 请求
axios.get('/user?ID=12345') .then(function (response) { // then方法等价于success方法,Ajax正确执行后的回调函数 console.log(response); }) .catch(function (error) { // catch方法等价于error方法,Ajax执行出现异常后的回调函数 console.log(error); }); // 上面的请求也可以这样做(get的参数必须加params) axios.get('/user', { params: { ID: 12345 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
POST 请求
// post请求不需要加params,直接花括号里加Json数据 axios.post(‘/user’, { firstName: ‘Fred’, lastName: ‘Flintstone’ }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); <!-- =========== 另一种例子 ============== --> var app = new Vue({ el: "#app1", data: { user: { username: "", password: "" } }, methods: { reg: function () { var _this = this; axios.post('/sshTest/user/save',_this.user).then(function (response) { console.log(response); }).catch(function (message) { console.log(message); }) } } })
多个并发请求
function getUserAccount() { return axios.get(‘/user/12345’); } function getUserPermissions() { return axios.get(‘/user/12345/permissions’); } // axios.all()方法里传入Ajax方法的数组 axios.all([getUserAccount(), getUserPermissions()]) .then(axios.spread(function (acct, perms) { // 两个请求现在都执行完成 }));
axios(config) 写法
// 发送 POST 请求 axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } });
使用 application/x-www-form-urlencoded 请求头
import qs from 'qs'; const data = { 'bar': 123 }; const options = { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, data: qs.stringify(data), url, }; axios(options);
Vue组件
入门案例
<div id="app"> <button-counter></button-counter> </div> <script> // 定义一个名为 button-counter 的新组件 Vue.component('button-counter', { data() { return { count: 0 } }, template: '<button @click="count++">我点击了 {{ count }} 次</button>' }) /* 以上也可以等价于以下 // 创建组件构造器对象 const cpn = Vue.extend({ data() { return { count: 0 } }, template: '<button @click="count++">我点击了 {{ count }} 次</button>' }); // 组件注册 Vue.component('button-counter',cpn) */ new Vue({ el: '#app' }) </script>
效果如图: ,点击按钮则count+1. 且在页面上是看不到button绑定的事件的。
注意:
- 例中的 ”button-counter" 即为组件名,自定义组件名遵循规则 (字母全小写且必须包含一个连字符)。这会避免和当前以及未来的 HTML 元素相冲突。
- 组件是可以重复使用的,组件内的data相互独立互不影响
- data必须是个函数,而不能是对象(Vue的规定,以此来保证每个组件的data独立)
- data函数的返回是个对象(即要
{}
包裹)
组件名
定义组件名的方式有两种:
使用 kebab-case(推荐)
Vue.component('my-component-name', { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>
。
使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时,在JS中引用两种命名法都可以使用。但是,HTML不区分大小写,因此只能使用 kebab-case 。
模板分离
在实际开发中,直接写template='<div>...</div>'
一个是可读性不好,一个是没有复用性,还有就是在写字符串时许多开发工具并不会语法提示。因此就有了模板分离:将HTML字符串用特殊标签包住后,直接写在其内部,引用其id给template
即可直接使用
<div id="app"> <my-button></my-button> </div> <template id="test"> <div> <input type='text'> <input type='button' value="模板"> <input type='checkbox' checked="checked">模板显示 </div> </template> <script> Vue.component('MyButton', { // 引用其模板的id template: test }) new Vue({el: '#app'}) </script>
效果:
- 可用
<template></template>与<script type="text/x-template" id="test"></script>
进行包裹,效果一样,更推荐前者- 模板不用写在Vue实例所挂载的内部
组件注册
全局注册
Vue.component('my-component-name', { // ... 选项 ... })
这些组件是全局注册的。它们在注册之后可以用在任何新创建的 Vue 实例 (new Vue
) 的模板中。比如:
Vue.component('component-a', { /* ... */ }) Vue.component('component-b', { /* ... */ }) Vue.component('component-c', { /* ... */ }) new Vue({ el: '#app' }) <div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div>
在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。
局部注册
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
在这些情况下,可以通过一个普通的 JavaScript 对象来定义组件。然后在 Vue实例内用components
选项中定义你想要使用的组件:
<div id="app"> <!-- 标签名不区分大小写,因此必须采用kebab-case风格 --> <component-a></component-a> </div> <script> // JS定义对象 var ComponentA = { data() { return { count: 0 } }, template: '<button @click="count++">我点击了 {{ count }} 次</button>' } new Vue({ el: '#app', components: { // 等价于 "CompunentA" : ComponentA // 注意,全局注册的组件不能写在这,会报ComponentA is not define ComponentA } }) </script>
对于 components
对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。例如,如果希望 ComponentA
在 ComponentB
中可用,则需要这样写:
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }
或者如果你通过 Babel 和 webpack 使用 ES6 模块,那么代码看起来更像:
import ComponentA from './ComponentA.vue' export default { components: { ComponentA }, // ... }
在 ES6 中,在对象中放一个类似
ComponentA
的变量名其实是ComponentA: ComponentA
的缩写,即这个变量名同时是:
- 用在模板中的自定义元素的名称
- 包含了这个组件选项的变量名
组件通讯--父传子
Props
写在props
中的值,接收数据通过组件上的同名属性接收,使用方式与在data中无异(因此可使用v-bind进行数据绑定)。
示例:
<div id="app"> <!-- posts为Vue实例中data的数据 v-bind:post,:arr绑定post,arr属性值,通过props传入。使用时与从data中取值无异 在组件上使用vue实例的方法要加.native,声明是本页面的方法,否则只能调用Vue中的方法 --> <component-a v-for="post in posts" @click.native="vueFun(post.id)" :post="post" :arr="[1,2,3,4,5]"></component-a> </div> <script> var ComponentA = { data() { return { count: 0 } }, props: ["post","arr"], template: '<ul><li @click.stop="componentFun(post.title)" v-for="(item, index) in arr">{{item}} {{post.title}} {{index + count}}</li></ul>', methods: { componentFun(obj) { console.log(obj) } } } new Vue({ el: '#app', components: { ComponentA }, data: { posts: [ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ] }, methods: { vueFun(obj) { showData(obj) } } }) function showData(obj) { console.log(obj) } </script>
结果:,点击后
data与props用法相似,组件私有的数据、可读可写的写在data中。只读不可写的写在props中
Props的命名
- 由于HTML是大小写不敏感的,使用标签的属性统一采用小写。
- 对于Props中驼峰命名的数据,需要使用其等价的 kebab-case (短横线分隔命名)
建议:在JS中采用驼峰命名,属性使用kebab-case(短横线分隔命名)
Props的类型限定
简略写法(数组):
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
限定类型写法(对象):
props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor }
使用错误类型时页面不会报错,但控制台会报错。
注意
[]
与{}
。简略写法必须加引号''
,限定类型写法可省略。
Props语法
Vue.component('my-component', { props: { // 基础的类型检查 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 } } } })
null
和undefined
会通过任何类型验证
Props类型检查的种类
type
可以是下列原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
额外的,type
还可以是一个自定义的构造函数,并且通过 instanceof
来进行检查确认。例如,给定下列现成的构造函数:
function Person (firstName, lastName) { this.firstName = firstName this.lastName = lastName }
你可以使用:
Vue.component('blog-post', { props: { author: Person } })
来验证 author
prop 的值是否是通过 new Person
创建。
Props的单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告(Avoid mutating a prop directly since the value will be overwritten whenever the parent component)。
在两种情况下,我们很容易忍不住想要去修改prop中数据:
- Prop作为初始值传入后,子组件想把它当作局部数据来用;
- Prop作为原始数据传入,由子组件处理成其它数据输出。
对这两种情况,正确的应对方式是:
- 在子组件中定义一个局部变量,并用prop的值初始化它:
props: ['initialCounter'], data: function () { // this可调用props中的属性值 return { counter: this.initialCounter } }
- 定义一个计算属性,处理prop的值并返回:
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
在JavaScript中对象和数组是引用类型,指向同一个内存空间,如果prop是一个对象或数组,在子组件内部改变它会影响父组件的状态
非Props的Attribute属性
当在组件上写有Attribute属性,却在props中没有进行定义(接收数据),即为非Props的Attribute属性。
因为显式定义的 prop 适用于向一个子组件传入信息,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。
实例:
<div id="app"> <component-b test type="text" class="componentClass" style="margin-left: 50px;"> </component-b> </div> <script> var ComponentB = { template:'<input type="date" class="myClass" style="width : 500px">' } </script>
结果:
test
可以是任意属性,可以是hiden、disabled
等具有实际意义属性,或是test
这种自定义属性。- 当组件与模板根元素有相同属性
type
时,组件的属性值会覆盖模板根元素的属性值。class
与style
比较特殊,不会覆盖原来的属性值,而是拼接上去。- 如果不想让组件上的属性影响到模板根元素,可设置禁用Attribute继承
禁用Attribute继承
当不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false
。
<div id="app"> <component-a type='text' ></component-a> </div> <script> var ComponentA = { template:'<input class="myClass" type="date" style="width : 500px">', // 不继承任何组件上传来的属性 inheritAttrs: false } </script>
结果:
type=date
不会被type=text
覆盖inheritAttrs
对class与style无效,仍然会继续拼接
$attrs
作用:在指定元素位置传递attribute属性。
使用v-bind='$attrs'
将非Props的Attribute属性绑定到指定的元素上,而不是只能绑定在根元素上
父组件想传值给曾子组件时,如果使用inheritAttrs
,子组件会有很多无关的属性在组件上。此时禁用子组件的Attribute继承后,使用$attrs
在子组件上使用即可达到传值效果。
inheritAttrs
是会覆盖相同的属性值的,但$attrs
是不会覆盖的(value会覆盖?) -lyx todo
<body> <div id="app"> <component-a type='submit' width="20px"></component-a> </div> <script> var ComponentB = { template: ` <div> <!-- 在此位置使用type='submit',width因为已经有了,$attrs无法覆盖原属性值,因此width仍然是30 --> <input width="30px" v-bind="$attrs" value="测试"> </div> `, inheritAttrs: false } var ComponentA = { components:{ ComponentB }, template: ` <div> <input class="myClass" type="date" style="width : 500px"> <!-- 在组件上使用v-bind='$attrs' --> <component-b v-bind="$attrs"></component-b> </div>`, // 不继承任何组件上传来的属性 inheritAttrs: false } new Vue({el:"#app",components:{ComponentA}}) </script>
结果:
$listener:事件监听器
<div id="app"> A{{msg}} <my-button :msg="msg" @todo="handleClick"></my-button> </div> <script> let vm = new Vue({ el: '#app', data: { msg: '100' }, methods: { handleClick() { console.log('点击事件') } }, components: { 'MyButton': { // 这里的v-on='$listeners' 与 v-bind='$attrs'异曲同工 template: `<div>B<my-radio v-on="$listeners"></my-radio></div>`, components: { 'MyRadio': { template: `<div @click="$listeners.todo">C</div>`, } }, created() { console.log(this.$listeners) } }, } }) </script>
注:
爷孙组件传递规则,同$attrs传递规则一样,逐级传递
与
v-bind='$attrs'
一样,直接使用v-on='$listeners'
也可以把所有原生事件附加在指定氧元素上。自定义事件由于不知道处罚方式(event = click or blur 等),所以无法触发。触发指定指定的事件有两种情况:
<my-button @todo="handleClick" @click='clickFun'></my-button>
触发原生事件:此时要在附加的元素上添加
@click="$listeners.click"
即可添加事件注意:click为触发的事件event
触发自定义事件:
@click="$listeners.todo"
,@event='$listeners.点击事件名'
组件通讯--子传父
$emit
格式:$emit.("父组件方法名", 传给父组件的值)
。
作用:可触发指定父组件的方法,父组件会监听子组件,而触发条件就是$emit
。
我们开发一个子组件,有时他需要与父级组件沟通,如:通知父组件数据变化、根据子组件给父组件赋值等。
示例:
<div id="app"> <div :style="{ fontSize: finalSize + 'em' }"> <!-- 自定义事件 --> <component-a @sendvalue='getChildValue' @big='finalSize += 0.5' :key="post.id" :post="post" :finalSize="finalSize"></component-a> <div> {{childValue}} </div> </div> </div> <script> var ComponentB = { data() { return { son: -1 } }, template: ` <div> <button @click='sendToChild(1)'>点击传值1</button> </div> `, methods: { sendToChild(value) { this.$emit('sendParent', value) } } } var ComponentA = { props: ['post'], components: { ComponentB }, template: ` <div class="blog-post"> <button @click="$emit('big')"> {{post.name1}} </button> <component-b @sendParent='sendPatent'></component-b> <div v-html="post.content"></div> </div> `, methods: { sendPatent(value) { this.$emit('sendvalue', value) } } } new Vue({ el: "#app", components: { ComponentA }, data: { post: { id: 1, name1: "文本变大", name2: "子组件传递值给父组件", age: 13, content: "测试文本" }, obj: "test", finalSize: 1, childValue: 0 }, methods: { getChildValue(value) { this.childValue = value; } } }) </script>
点击之前: 点击之后:
自定义事件名
不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。例如:我们监听这个事件
this.$emit('myEvent')
则监听这个名字的 kebab-case 版本是不会有任何效果的:
<!-- 没有效果 --> <my-component v-on:my-event="doSomething"></my-component>
并且 v-on
事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent
将会变成 v-on:myevent
——导致 myEvent
不可能被监听到。
因此,推荐始终使用 kebab-case 的事件名。
父子组件的双向数据绑定
表单
有时候需要在组件上使用v-model,输入修改子组件的表单input的同时修改父组件的data值。
示例:
<div id="app"> <my-input v-model="message"></my-input>{{message}} </div> <script> var MyInput = { props:["value"], template:` <div> <input :value="value" @input="$emit('input',$event.target.value)"> </div> `, inheritAttrs:false } new Vue({ el:"#app", components:{ MyInput }, data:{ message: "测试文本" } })
原理:
v-model
本质上可分解为v-bind:value
以及@input
事件,在组件上使用v-model
没有什么差别,差别在于:使用子组件时,@input
事件不再是默认的改变自己this.value = $event.target.value
,而是修改调用父类的input
方法,并将$event.target.value
赋值给message
自定义组件的v-model -lyx? todo
一个组件上的 v-model
默认会利用名为 value
的 prop 和名为 input
的事件,但是像单选框、复选框等类型的输入控件可能会将 value
attribute 用于不同的目的。model
选项可以用来避免这样的冲突:
不写:checked='select' 没有初始化,且子组件的checked值始终为false,即使复选框被选中也为false
<div id="app2"> {{select}} <my-checkbox v-model='select' :checked='select'></my-checkbox> <input type="checkbox" v-model="select" /> </div> <script> var MyCheckbox = { model: { props: "checked", event: "change" }, props: { checked: Boolean }, template: ` <div><input type='checkbox' :checked='checked' @change='$emit("change",$event.target.checked)'>111</div> `, inheritAttrs: false } new Vue({ el: "#app2", data: { select: true }, components: { MyCheckbox }, inheritAttrs: true }) </script>
.sync修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。但是,子组件不能直接修改父组件的值,通过子父组件的数据双向绑定也可以达到效果,但是十分繁琐,在Vue中提供了.sync
的语法糖 -lyx todo
<div id="app"> <div style="width: 309px;height: 200px;border: 1px solid black;"> <my-button :child-show.sync="show" v-show="show"></my-button> </div> </div> <script> let vm = new Vue({ el: '#app', data: { show: true } components: { 'MyButton': { props: ["child-show"], template: `<div> <button @click.stop='$emit("update:childshow",false)'> test </button> </div>` } } }) </script>
当我们用一个对象同时设置多个
prop
双向绑定的时候,也可以将这个.sync
修饰符和v-bind
配合使用:<my-button v-bind.sync="doc"></my-button>
插槽
插槽,也就是slot,是组件的一块HTML模板。这块模板显示不显示、以及怎样显示由父组件来决定;但是插槽显示的位置却由子组件自身决定
实际上,插槽最核心的两个问题在这里就点出来了,是显示不显示和怎样显示。
具名插槽:带名字的插槽
实例:
<div id="app"> <my-button> <!-- 等价于<template #parents> --> <template v-slot:parents> <div> <input value='我是父类的插槽内容'> </div> </template> </my-button> </div> <template id="temp1"> <div> <div class="child"> 我是子类的插槽内容 </div> <slot name='parents'></slot> </div> </template> <script> Vue.component('MyButton', { template: temp1 }) new Vue({el: '#app'}) </script>
效果:
当组件渲染的时候,
<slot></slot>
将会直接被替换为组件双标签内的内容。插槽内可以包含任何模板代码,包括 HTML,甚至可以是其他的组件。
<slot></slot>
标签无name属性时,默认name='default'
,会渲染出组件起始标签内的所有未设置v-sloat
的节点以及v-sloat:default
的节点。这类插槽也称作:默认插槽/匿名插槽/单个插槽跟
v-on
和v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符#
。例如
v-slot:header
可以被重写为#header
注:采用
#
缩写时,default
不可省略除了
v-slot:header
及#header
外,还有slot='header'
也是一样的,但是已被废弃,了解即可
作用域插槽:带数据的插槽
作用域插槽,与前一种插槽相比,区别就是携带数据
<!-- 匿名插槽 --> <slot></slot> <!-- 具名插槽 --> <slot name="up"></slot> <!-- 作用域插槽 --> <slot name="up" :data="data"></slot>
实例:
...待更新中
后备内容
有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。
<div id="app"> <my-button> <template #slot1> <div> <input class='parent' value='我是父类的插槽内容1'> </div> </template> <template> <div> <input class='parent' value='我是父类的插槽内容2'> </div> </template> </my-button> </div> <template id="temp1"> <div> <slot name='slot1'>一个slot='sloat1'的插槽都没有</slot> <slot name='slot2'>一个slot='sloat2'的插槽都没有</slot> </div> </template> <script> Vue.component('MyButton', { template: temp1 }) new Vue({el: '#app'}) </script>
结果:
注:后备内容也同样支持HTML语法
后续内容待更新。。。