Vue笔记
基础
声明式渲染
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: "#app",
data: {
message: "hello"
}
});
异步向data新增对象
<div id="app">
<!-- 可以不在data里声明并且在dom里写上去,然后用js异步声明 -->
<div v-if="showflag">
{{ info }}
</div>
</div>
var app = new Vue({
el: "#app",
data: {
showflag: false
},
mounted() {
this.info = "hello";
this.showflag= true; // dom可以访问到
this.test(); // 方法可以访问到
},
methods: {
test() {
console.log(this.info);
}
}
});
一次性插值(v-once)
<div id="app">
<div v-once> {{ a }} </div> <!-- 永远输出 1 -->
<div> {{ b }} </div>
</div>
var app = new Vue({
el:"#app",
data: {
a: 1,
b: 2
},
mounted() {
this.a = 3 // 不会影响dom中 v-once 标记的数据
}
});
解析为HTML/文本
<div id="app">
{{message}} <!-- 被解析为文本 -->
<span v-html="htmlmessage"></span> <!-- 被解析为HTML -->
</div>
var app = new Vue({
el:"#app",
data:{
message:"<p style='border:1px solid red'>hello</p>",
htmlmessage:"<p style='border:1px solid red'>world</p>"
}
});
app.htmlmessage="<p style='border:1px solid red'>hello</p>"; //v-html指令也支持响应
使用js表达式
可以在{{ }}
或v-bind
或v-html
里使用表达式,注意不可以调用自定义方法,因为它是运行在沙盒中的,只能访问白名单内的系统方法
<div id="app" v-bind:title="num+5"> <!-- v-bind里使用 -->
{{message+" world"}}
{{parseInt(num)}} <!-- 调用系统方法 -->
{{ok ? "Yes" : "No"}} <!-- 三元表达式 -->
<span v-html="num+5"></span> <!-- v-html里使用 -->
</div>
动态参数
<div id="app">
<!-- attr1 作为 JS表达式传入,value1 作为 data属性传入 -->
<input type="button" v-bind:[attr1]="value1" />
<!-- 注意 Attr2在 html中会被解析为小写,应尽量避免属性名大写 -->
<input type="button" v-bind:[Attr2]="value2" />
<!-- attr3 作为 JS表达式传入,注意值不能动态设置 -->
<input type="button" v-on:[attr3]="sc" />
<!-- 表达式示例,注意,根据 html语法,表达式不能使用空格、单引号等 -->
<input type="button" v-on:[ok?type1:type2]="sj" />
</div>
var app=new Vue({
el:"#app",
data:{
attr1:"value",
value1:"button1",
attr2:"title",
value2:"button2",
attr3:"click",
ok:false,
type1:"click",
type2:"dblclick"
},
methods:{
sc:function(){
console.log(event.target.tagName);
},
sj:function(){
console.log(this.ok?this.type1:this.type2);
}
}
});
v-bind指令
基本格式
<div id="app" v-bind:title="biaoti"></div>
<!-- !不仅支持所有系统属性,用户自定义的属性也可以设置 -->
<!-- 缩写形式:<div id="app" :title="biaoti"></div> -->
var app = new Vue({
el:"#app",
data:{
biaoti:"this is title"
}
});
布尔型属性特性
复习:<div hidden="hidden">
表示隐藏元素,不隐藏则不加这个属性(只有这一个方式,赋其他值照样隐藏)
Vue能够自动判断这类属性,当绑定的值为"Truthy(真值)"
时,会删除这个属性,否则不会
<div id="app" v-bind:hidden="isHidden">
hello
</div>
var app = new Vue({
el:"#app",
data:{
isHidden:false //app不会显示
}
});
需要注意Vue内的Truthy(真值)
和假值
与实际略有不同:载自:MDN web docs
//浏览器默认Truthy(真值)
if (true)
if ({})
if ([])
if (42)
if (-42)
if (3.14)
if (-3.14)
if ("foo")
if (new Date())
if (Infinity)
if (-Infinity)
//以上都支持,且Vue增加以下真值
if ("")
if (0)
if (-1)
//浏览器默认假值
if (false)
if (null)
if (undefined)
if (0) //不支持
if (0n) //不支持
if (NaN)
if ('') //不支持
if ("") //不支持
if (``) //不支持
if (document.all)
v-is(动态标签名)
动态指定标签/组件名
<div id="app">
<!-- 动态指定标签名,这里的标签名最好是component(官方文档是这样写的,可能是因为一般用于组件?,但其他地方也能用) -->
<!-- 点击后标变成div -->
<component v-bind:is="current" @click="current='div'">current tag is {{current}}</component>
<!-- 正经的用法:动态组件名,上面的用法不是很正经 -->
<component v-bind:is="currentTab" @click.native="currentTab='tab-main'"></component>
</div>
Vue.component("tab-header",{
template:'<div>header component</div>'
});
Vue.component("tab-main",{
template:'<div>main component</div>'
});
Vue.component("tab-bottom",{
template:'<div>bottom component</div>'
});
var app=new Vue({
el:"#app",
data:{
current:"a",
currentTab:"tab-header"
}
});
属性和数据获取方式
//获取data数据
app.message;
//获取Vue属性
//错误示例:app.el ==> notdefined
app.$el === document.getElementById("app"); //true
app.$data === data; //true
Vue生命周期
//示例
var data={
message:"hello"
};
var app = new Vue({
el:"#app",
data:data,
created:function(){ //created 阶段执行
console.log("message="+this.message); //输出 hello
}
});
计算属性和侦听器
基本格式
<div id="app">
<div id="demo">{{fullName}}</div>
</div>
var app=new Vue({
el:"#app",
data: {
firstName: 'Foo',
lastName: 'Bar'
//注意这里不能声明fullName
},
computed:{
fullName:function(){
return this.firstName+" "+this.lastName;
}
}
});
缓存特性
计算属性只有相关响应式依赖发生改变时它们才会重新求值
<div id="app">
<!-- 方法 -->
{{ m1() }} <!-- 运行后显示 1567954020656 -->
{{ ys() }} <!-- 延时一会儿 -->
{{ m1() }} <!-- 运行后显示 1567954020712 -->
<!-- 计算属性 -->
{{ m2 }} <!-- 运行后显示 1567954020712 -->
{{ ys() }} <!-- 延时一会儿 -->
{{ m2 }} <!-- 运行后显示 1567954020712 -->
</div>
var app=new Vue({
el:"#app",
methods:{
m1:function(){
return Date.now();
},
ys:function(){
for(var i=0;i<100;i++){
var abc=1.23456*2.34567;
console.log(abc);
}
}
},
computed:{
m2:function(){
return Date.now();
}
}
});
set方法(覆盖缓存)
若要重新执行上一小节的m2
方法,覆盖缓存,需要用到set
方法
var app=new Vue({
el:"#app",
data:{
dateTime:null //声明m2的dateTime
},
methods:{
m1:function(){
return Date.now();
},
ys:function(){
for(var i=0;i<100;i++){
var abc=1.23456*2.34567;
}
}
},
computed:{
m2:{
get:function(){
this.dateTime=Date.now();
return this.dateTime;
},
//设置m2=?后会执行这个函数,然后重新执行get方法并缓存
set:function(newValue){ //newValue是传来的值,用不到可以不写这个参数
this.dateTime=Date.now();
}
}
}
});
//点击document更新m2
addEventListener("click",function(){
app.m2=""; //参数,因为没有需要传入参数,所以随便传(null、undefined、1...)
console.log(app.m2);
});
侦听器
v-model
用来给表单提供双向数据绑定,会智能判断对象来提供不同的属性和事件(例如为text
提供input
事件和value
属性)
<div id="app">
<input type="text" v-model="textContent"><br/>
</div>
var app=new Vue({
el:"#app",
data:{
textContent:""
},
watch:{ //侦听事件,发生后执行相应函数
textContent:function(){
console.log("侦听到textContent值被改变");
}
},
created:function(){ //创建时执行一次
console.log("请输入");
}
});
class和style绑定
对象格式
class
<div id="app">
<!-- 可以和普通的class并存 -->
<div class="default"
:class="{bgRed:isBgRed,'colorBlue':isColorBlue}">text</div>
<!-- 对象名是绑定的类名,值是data中的一个布尔值 -->
</div>
.default{font-size:25px;}
.bgRed{background:red;}
.colorBlue{color:blue;}
var app=new Vue({
el:"#app",
data:{
isBgRed:true, //表示添加
isColorBlue:false //表示不添加
}
});
style
和class相同类似:
<div id="app">
<div :style="{border:redBorder,backgroundColor:blackBG,'font-size':big+'px'}">text</div>
<!-- 驼峰式写法 非驼峰需加引号 可使用表达式 -->
</div>
... ...
data:{
redBorder:"1px solid red",
blackBG:"#888",
big:50
}
... ...
计算属性绑定class
这是一个常用且强大的模式:
<div id="app">
<div class="default" :class="vueClass">text</div>
</div>
var app=new Vue({
el:"#app",
data:{
isBgRed:true,
isColorBlue:false
},
computed:{
vueClass:function(){
return {
bgRed:this.isBgRed ? true : false,
colorBlue:this.isColorBlue ? true : false
}
}
}
});
通常,style也结合计算属性使用
数组格式
class
<div id="app">
<div class="default"
:class="['bgRed',isColorBlue ? colorBlue : '']">text</div>
<!-- 可以用三元运算符实现是否添加,直接写则表示始终添加 -->
</div>
var app=new Vue({
el:"#app",
data:{
isColorBlue:true,
colorBlue:"colorBlue"
}
});
style
<div id="app">
<div :style="[baseStyle,smallSize]">text</div>
<!-- 必须是对象类型 -->
</div>
... ...
data:{
baseStyle:{
background:"#888",
color:"#fff",
fontSize:"50px"
},
smallSize:{
width:"140px",
height:"80px",
textAlign:"center"
}
}
... ...
组件中绑定
例如声明了一个添加有class的组件
Vue.component('c1', {
template:"<div class='default'></div>"
})
使用时,新添加的class不会覆盖原class
<c1 class="redColor"></c1>
<!-- class = redColor default -->
<c1 :class="{redColor:isRedColor}">
<!-- 同样支持数据绑定class -->
style多重值
可以为style绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:
<!-- 优先倒序使用,若支持则使用,若不支持则检测上一个 -->
<div :style="{display:['-webkit-flex', '-o-flex', 'flex']}">text</div>
事件处理
基本格式
<div id="app">
<!-- 执行简单的js代码 -->
<input type="button" value="按钮" v-on:click="clickNum++" /> {{ clickNum }}
<!-- 执行方法 -->
<input type="button" value="按钮" v-on:click="m1" />
<!-- 执行带参方法 -->
<input type="button" value="按钮" v-on:click="m2(msg)" />
<!-- 带参方法加事件对象 -->
<input type="button" value="按钮" v-on:click="m2(msg,$event)" />
<!-- 缩写形式:<input @click="m1" /> -->
</div>
var app = new Vue({
el:"#app",
data:{
msg:"hello",
clickNum:0
},
methods:{
m1:function (){
console.log(event.target.tagName); //带有一个event(事件对象)参数 快捷链接:https://blog.csdn.net/yjl15517377095/article/details/96839606#event_141
},
m2:function (message){
console.log(message);
},
m3:function (message,e){
console.log(message+" "+e.target.tagName);
}
}
});
事件修饰符
阻止跳转/冒泡
<div id="app">
<!-- event.stopPropagation 响应本次事件 阻止事件继续冒泡 可以不写参数 也可以写方法名 -->
<a href="https://www.baidu.com" v-on:click.stop="">跳转</a>
<!-- event.preventDefault 阻止本次事件 事件继续冒泡 也可以只写修饰符 -->
<a href="https://www.baidu.com" v-on:click.prevent>跳转</a>
<!-- 修饰符串联 相当于return false; -->
<a href="https://www.baidu.com" v-on:click.stop.prevent>跳转</a>
</div>
dom事件流
<div id="app">
<div v-on:click="sc(1)" style="width:100px;height:100px;background: #007BFF;">
<div v-on:click="sc(2)" style="width:75px;height:75px;background: #138496;">
<div v-on:click="sc(3)" style="width:50px;height:50px;background: #E0A800;">d3
</div>d2
</div>d1
</div>
</div>
<!-- 函数功能为:sc(n){console.log(n)} -->
默认为冒泡阶段触发,点击3,结果为: 3 2 1
<!-- 可以设置某个事件在捕获阶段触发 -->
<div v-on:click="sc(1)" style="width:100px;height:100px;background: #007BFF;">
<div v-on:click.capture="sc(2)" style="width:75px;height:75px;background: #138496;">
<div v-on:click.capture="sc(3)" style="width:50px;height:50px;background: #E0A800;">
点击3,结果为:2 3 1
<!-- Vue还多了一个功能:只当在event是当前元素自身时触发处理函数,否则不触发 -->
<div v-on:click="sc(1)" style="width:100px;height:100px;background: #007BFF;">
<div v-on:click.self="sc(2)" style="width:75px;height:75px;background: #138496;">
<div v-on:click="sc(3)" style="width:50px;height:50px;background: #E0A800;">
点击3,结果为:3 1
,没触发2
点击2,结果为:2 1
,2被触发
这些修饰符都能够互相串联,例如:
v-on:click.self.prevent
…等
官方说 “使用修饰符时,顺序很重要,相应的代码会以同样的顺序产生”,但实际测试没影响,可能存在偶然性和兼容性
addEventListener参数三
<!-- 事件只被触发一次,然后 removeEventListener -->
<input v-on:click.once="sc" type="button" value="按钮" />
<!-- 忽略.prevent -->
<div v-on:scroll.passive></div>
键盘按键修饰符
事件类型
<div id="app">
<!-- 键盘弹起 -->
<input type="text" v-on:keyup="submit" />
<!-- 键盘按下 -->
<input type="text" v-on:keydown="submit" />
<input type="text" v-on:keypress="submit" />
</div>
var app=new Vue({
el:"#app",
methods:{
submit(){
console.log("你按下了:"+event.key);
}
}
});
keyCode
keyCode 的事件用法已经被废弃了并可能不会被最新的浏览器支持。
<!-- keyCode形式 -->
<input type="text" @keyup.13="submit" />
<!-- keyCode别名形式,详情看表 -->
<input type="text" @keyup.enter="submit" />
最好用别名,Vue已经处理好了keyCode兼容性问题
值 | 备注 |
---|---|
enter | |
tab | |
delete | 含退格 和删除 ,event具体值不同 |
esc | |
space | |
up / down / left / right | 上下左右 |
page-up / page-down | |
home / end | |
pause | 暂停 |
control | ctrl键 |
* | 乘 |
+ | 含小键盘+ 和shift+= ,event具体值不同 |
- | 含小键盘- 和大键盘- ,event具体值不同 |
组合键
<!-- ctrl+a event返回的是a -->
<input type="text" @keyup.ctrl.a="submit" />
<!-- ctrl+alt+a event返回的是a -->
<input type="text" @keyup.ctrl.alt.a="submit" />
<!-- alt和shift -->
<input type="text" @keyup.alt.a="submit" />
<input type="text" @keyup.shift.a="submit" />
<!-- 注意其他键不能组合,会分别触发 a和b的keyup事件 -->
<input type="text" @keyup.a.b="submit" />
串联
<!-- 按下a/b/c都会触发,并返回对应event -->
<input type="text" @keyup.a.b.c="submit" />
自定义别名
//新建自定义别名
Vue.config.keyCodes.huiche=13;
//更改已存在别名keyCode
Vue.config.keyCodes.enter=65;
exact修饰符
<!-- 按下ctrl+shift+a也能触发 -->
<input type="text" @keyup.ctrl.a="submit" />
<!-- 仅按下ctrl+a时触发 -->
<input type="text" @keyup.ctrl.exact.a="submit" />
<!-- 注意:仅针对组合键 -->
鼠标按键修饰符
<!-- 左键单击 -->
<div @click.left="submit"></div>
<!-- 滚轮键单击 -->
<div @click.middle="submit"></div>
<!-- 右键单击 -->
<div @click.right="submit"></div>
条件渲染
If,elseIf,else
v-if
和v-else-if
和v-else
顺序不能乱,否则不能识别
<div id="app">
<!-- if -->
<div v-if="age<22">学生</div>
<!-- if elseIf else -->
<div v-if="age<10">儿童</div>
<div v-else-if="age<45">青年</div>
<div v-else>中年</div>
<!-- if else -->
<div v-if="age<25">无脱发</div>
<div v-else>二级脱发</div>
</div>
age=20 ==> 学生 青年 无脱发
template元素条件渲染用法
注意:和原生标签不同,放在
Vue App
内的template
会被去除<template>
标签,内部当作普通html内容,所以默认是显示的,还要注意Vue不会编译模板内的style标签
<!-- isHidden是一个布尔值数据,若`v-if`为`真值`则显示,否则不显示(移除整个`template`) -->
<template v-if="isHidden">
<div>
<p>hello</p>
</div>
</template>
复用特性
示例
<div id="app">
<template v-if="ok">
<label>name</label>
<input placeholder="输入name">
</template>
<template v-else>
<label>age</label>
<input placeholder="输入age">
</template>
<input type="button" @click="qiehuan" value="切换" />
</div>
var app = new Vue({
el:"#app",
data:{
ok:true
},
methods:{
qiehuan:function(){
this.ok=!this.ok;
}
}
});
按下按钮时两个template
会互相切换,从渲染上看,两个模板内容高度相同,若完全重新渲染会造成不必要的浪费,所以Vue会尽可能复用这些元素,例如示例代码:
. | label | input |
---|---|---|
正常 | 创建label,更改innerText | 创建input,创建属性,设置属性 |
Vue复用 | 更改innerText | 设置属性 |
所以在切换时不会消除上次输入的内容,这是因为复用造成的
管理复用(key)
<!-- ... -->
<input placeholder="输入name" key="name">
<!-- ... -->
<input placeholder="输入age" key="age">
<!-- ... -->
只需要在不需要复用的地方指定不同的key
值就可以避免复用,同时label
还是复用的
v-show
v-show
会切换元素的display
,来达到显示/隐藏
<div id="app">
<div v-show="isShow"></div>
</div>
v-show和v-if区别
v-show
在运行时就直接渲染元素,然后根据用户指示操作元素的display=none/block
v-if
在运行时若为假值
则不会渲染元素,直到为真值
时才进行渲染,且当再次切换真/假值
时会适当的进行销毁和重建v-if
在切换时开销比v-show
大,因此,切换频繁的元素建议用v-show
,不频繁或需要销毁/重建的元素用v-if
列表渲染
循环数组
var app = new Vue({
el:"#app",
data:{
students:[
{id:1,name:"sam",age:18},
{id:2,name:"tom",age:21}
]
}
});
item in items
格式:
<div id="app">
<ul>
<li v-for="student in students">
{{student.name+" - "+student.age}}
</li>
</ul>
</div>
<!-- 渲染结果为:
<li>sam - 18</li>
<li>tom - 21</li>
-->
(item,index) in items
格式:
<div id="app">
<ul>
<!-- 索引从0开始,参数名任意,顺序不可变 -->
<li v-for="(student,index) in students">
{{index+" - "+student.name+" - "+student.age}}
</li>
</ul>
</div>
可以用of
替代in
,因为它更接近js迭代器的语法:
<li v-for="student of students">
注意事项
列表渲染是响应式的,但由于js限制,无法检测到以下行为:
//列表不会检测到这些行为,因此不会改变
app.students[0]="{id:1,name:"jack",age:18}"; //已更改值但列表没有重新渲染
app.students.length=1; //已经删掉多余值但列表没有重新渲染
问题1解决方案:
//Vue.set方法
Vue.set(app.students,0,{id:1,name:"jack",age:18}); //全局方法
app.$set(app.students,0,{id:1,name:"jack",age:18}); //实例方法,两个均可
//因为Vue对数组的变异方法进行了包裹,所以这些方法将会触发视图更新
//splice方法 (第0个开始,删掉1个,插入的新数据 )
app.students.splice(0,1,{id:1,name:"jack",age:18});
问题2解决方法:
//splice方法 (第0个开始,删掉1个)
app.students.splice(0,1);
变异方法和非变异方法
官方文档传送门
- Vue对数组的变异方法进行了包裹,所以这些方法将会触发视图更新
- 非变异方法返回的是一个新数组,将新数组替换旧数组,也能达到触发视图更新的目的
- 在新数组替换旧数组时仍采用了复用技术
过滤数组
计算属性
<div id="app">
<ul>
<li v-for="student in getBigStudent">
{{student}}
</li>
</ul>
</div>
var app = new Vue({
el:"#app",
data:{
students:[
{name:"sam",age:18},
{name:"tom",age:21},
{name:"jack",age:19}
]
},
computed:{
//获取年龄大于18的学生
getBigStudent:function(){
return this.students.filter(function(student){
return student.age>18;
})
}
}
});
方法
更改以下部分即可:
"student in getBigStudent" ⇒ "student in getBigStudent()"
computed ⇒ methods
循环对象
<div id="app">
<ul>
<!-- (student) (student,name) (student,name,index) 三种格式均可 -->
<li v-for="(student,name,index) of students">
{{index+":"+name+" - "+student.name+" - "+student.age}}
</li>
</ul>
</div>
<!-- 渲染结果为:
<li>0:s1 - sam - 18</li>
<li>1:s2 - tom - 20</li>
-->
var app = new Vue({
el:"#app",
data:{
students:{
s1:{id:1,name:"sam",age:18},
s2:{id:2,name:"tom",age:20}
}
}
});
vue在遍历对象时按
Object.keys()
结果进行遍历(但不能保证任何情况都一致),因此顺序不一定和data内书写顺序相同
注意事项
由于js限制,无法检测到以下行为:相关问题传送门
app.students.s3={id:3,name:"tom",age:25};
解决方案:
//Vue.set方法
Vue.set(app.students,"s3",{id:3,name:"tom",age:25}); //全局方法
app.$set(app.students,"s3",{id:3,name:"tom",age:25}); //实例方法,两个均可
当需要添加/设置多个属性时,你可能会这样:Object.assign传送门
复习:Object.assign(target,...source)
//直接把Source给app.students
Object.assign(app.students,{
s1:{id:1,name:"sam",age:19},
s3:{id:3,name:"jack",age:21}
});
请不要这样,应该这样:(不理解为什么要这样)
//把source和app.students给{}来创建一个新对象,然后再给app.students
app.students=Object.assign({},app.students,{
s1:{id:1,name:"sam",age:19},
s3:{id:3,name:"jack",age:21}
});
维护状态(复用问题)
示例:若在文本框输入值后点击更新值按钮,由于复用策略,文本框原本的值不会重新被渲染
<div id="app">
<div v-for="item in items">
{{item}}:<input type="text" />
</div>
<input type="button" value="chageValue" @click="chageValue" />
</div>
var app = new Vue({
el:"#app",
data:{
items:{
0:"name",
1:"age"
}
},
methods:{
chageValue:function(){
this.items[0]="userName";
}
}
});
使用key禁止复用策略:此时将会重新渲染整个for,注意key应该是字符型或数值型且是唯一的
<div v-for="item in items" :key="item">
整数循环
v-for
可以接受一个整数:
<div id="app">
<span v-for="i in 10"> {{i}} </span>
<!-- ⇒ 1 2 3 4 5 6 7 8 9 10 -->
</div>
在template中使用
在template
中同样也可以正常使用
<div id="app">
<template v-if="isShow" v-for="i in 10">
{{i}}
</template >
</div>
v-for和v-if同时使用
vue书写风格不推荐v-for和v-if同时使用
v-for
的优先级比v-if
高,意味着每一个节点都会运行v-if
,这个特性适合只渲染部分节点,示例:
<div id="app">
<ul>
<li v-for="todo in 10" v-if="todo<5">
{{todo}} <!-- ⇒ 1 2 3 4 -->
</li>
</ul>
</div>
可以把v-if
放到v-for
上一级来提升v-if
的优先级:
<div id="app">
<ul v-if="false"> <!-- 直接在这里控制整个for是否渲染 -->
<li v-for="todo in 10">
{{todo}}
</li>
</ul>
</div>
表单输入绑定
基础用法和值绑定
v-model实现表单的双向绑定,它会根据元素类型自动判断需要绑定的事件和属性
text,textarea
text
和textarea
元素使用value
属性和input
事件
<div id="app">
<input type="text" v-model="textMessage"/> {{textMessage}}
<!-- 等价于:
<input type="text" :value="textMessage" @input="textMessage=$event.target.value"/>
-->
<textarea v-model="textareaMessage"></textarea> {{textareaMessage}}
</div>
var app = new Vue({
el:"#app",
data:{
textMessage:"",
textareaMessage:""
}
});
checkbox
checkbox
和radio
使用checked
属性和change
事件
单选
<div id="app">
<input type="checkbox" v-model="checkboxMessage"/>
{{checkboxMessage}}
</div>
var app = new Vue({
el:"#app",
data:{
checkboxMessage:false //初始值会影响到html绑定的表单元素
//单选也可以用数组,返回的是value数组
}
});
多选
<div id="app">
<input type="checkbox" name="g1" value="tom" v-model="checkboxMessage"/><label>tom</label>
<input type="checkbox" name="g1" value="sam" v-model="checkboxMessage"/><label>sam</label>
<input type="checkbox" name="g2" value="jack" v-model="checkboxMessage"/><label>jack</label>
你的选择:{{checkboxMessage}}
</div>
var app = new Vue({
el:"#app",
data:{
checkboxMessage:[] //!必须是数组返回的才是选择项value数组,否则返回true/false,且会影响到其他项
}
});
注意:上面3个元素的input
的name
虽不同,但在Vue app
内无视name
属性,作为同一组对待
值绑定
单选情况下,返回的只有true/false
,但可以将一个值绑定到动态属性上
<div id="app">
<input type="checkbox" v-model="isCheck" true-value="yes" false-value="no"/>
{{isCheck}}
</div>
var app = new Vue({
el:"#app",
data:{
isCheck:"yes" //初始值同样会影响到html绑定的表单元素
}
});
此时yes
相当于checked=true
,反之为false
radio
<div id="app">
<input type="radio" value="yes" v-model="radioMessage"/><label>yes</label>
<input type="radio" value="no" v-model="radioMessage"/><label>no</label>
你的选择:{{radioMessage}}
<!-- 返回选定元素的value值 -->
</div>
var app = new Vue({
el:"#app",
data:{
radioMessage:""
}
});
值绑定
还可以绑定变量实现动态值
<div id="app">
<input type="radio" v-model="isCheck" :value="sport.a"/>football
<input type="radio" v-model="isCheck" :value="sport.b"/>basketball
{{isCheck}}
</div>
var app = new Vue({
el:"#app",
data:{
isCheck:"basketball",
sport:{
a:"football",
b:"basketball"
}
}
});
select
select
字段将value
作为 prop 并将change
作为事件
单选
<div id="app">
<select v-model="select">
<option value="aaa">a</option>
<option>b</option>
<option>c</option>
</select>
{{select}}
<!-- 返回选定元素的value值(没有则返回元素内文本) -->
</div>
var app = new Vue({
el:"#app",
data:{
select:""
}
});
多选(shift/ctrl)
此时返回的是选择项value数组
<!-- ... -->
<select multiple v-model="select">
<!-- ... -->
var app = new Vue({
el:"#app",
data:{
select:[] //可以是""或其他,和checkbox的多选不一样,不是必须数组
}
});
值绑定
<div id="app">
<select v-model="isCheck">
<!-- 绑定的值类型任意,不一定必须是字符串,例如这里绑定对象类型 -->
<option :value="sports">sports</option> <!-- ==> {a:...,b:...} -->
<option>food</option>
</select>
{{isCheck}}
</div>
var app = new Vue({
el:"#app",
data:{
isCheck:"",
sports:{
a:"run",
b:"football"
}
}
});
修饰符
.lazy(input->change)
可以将input
事件触发转为change
事件触发
<div id="app">
<input type="text" v-model.lazy="message" />
{{message}} <!-- 只有change事件触发才会做出响应 -->
</div>
.number
对于type=number
的input
元素,即使类型为number
,但仍会返回string
类型数据,使用.number
来解决返回类型问题
<div id="app">
<input type="number" v-model.number="message"/>
{{typeof(message)+":"+message}} <!-- 输入值后返回的将是 number:your number -->
</div>
.trim
去除首尾空格,在触发事件时文本框
和绑定数据
将同步去除首尾空格
<div id="app">
<input type="text" v-model.trim="message"/>
{{message}}
</div>
组件注册
基本格式
- app声明必须在使用的组件下面
- data必须是一个函数,这样可以保证复用组件之间数据互不影响
<div id="app">
<counter></counter>
<counter></counter>
</div>
Vue.component("counter",{
template:'<input type="button" :value="taps+n" @click="n++"/>',
data:function(){
return{
taps:"点击了(次):",
n:0
}
}
});
var app = new Vue({
el:"#app"
});
组件名
//短横线分隔式
ji-shu-qi
//驼峰式
JiShuQi
在使用时两种方式定义的组件都必须以下形式,否则不识别:
<ji-shu-qi></ji-shu-qi>
Vue
书写风格建议组件名全小写,且需有短横线
分隔,这样可以和标准HTML
节点区分开
全局/局部注册
全局注册
例如基本格式小节的声明方式就是全局注册,全局注册的组件可以在不声明的情况下用于任何vue app,且组件间能够互相嵌套
在vue app内局部注册
<div id="app">
<ji-shu-qi></ji-shu-qi>
</div>
//局部注册用var声明
var jiShuQi={
template:'<input type="button" :value="n" @click="n++"/>',
data:function(){
return{
n:0
}
}
};
var app = new Vue({
el:"#app",
components:{ //在此注册要使用的组件,否则未注册的组件不能使用
"ji-shu-qi":jiShuQi
//标签名 : 变量名
}
});
嵌套局部注册的组件
<div id="app">
<taps-ji-shu-qi></taps-ji-shu-qi>
</div>
var tapsJiShuQi={
template:'<div>你点击了(次):<ji-shu-qi></ji-shu-qi></div>',
components:{ //注册局部组件 并使用它 ↑
"ji-shu-qi":jiShuQi
}
};
var app = new Vue({
el:"#app",
components:{ //被嵌套的组件不用再次注册
"taps-ji-shu-qi":tapsJiShuQi
}
});
父子组件通信(自定义事件)
基础示例($emit)
有时子组件需要触发父组件的某个事件,可以用$emit
配合侦听器
实现
Vue.component("son",{
template:`
<div class="border px-3 py-3 mx-3 my-3">
<!-- 1、$emit方法可以触发父组件(input的父组件是son)中的指定事件 -->
<!-- 这里指定的@click是真正触发的事件,后面的是自定义事件 -->
<input type="button" value="changeFather's name" @click="$emit('son-click')"/>
</div>
`
});
Vue.component("father",{
template:`
<div>
<!-- 2、触发父组件(father)的指定事件 -->
<son @son-click="$emit('father-click')"></son>
</div>
`
});
var app = new Vue({
el:"#app",
data:{
fatherName:"jack"
}
});
<div id="app">
<!-- 3、最终此处监听到事件,修改data -->
<father :name="fatherName" @father-click="fatherName='tom'"></father>
{{ fatherName }}
</div>
注意事项:
- 组件不能指定
原始事件
,例如:<father @click="funcion"></father>
,若指定了也会被当作自定义事件
- 所以
自定义事件
名称可以和原始事件
重名 - 若要触发
原始事件
,可以用.native
修饰符启用:
<!-- 此时单击father可以触发click事件,同时即使自定义事件名同名也互不影响 -->
<father :name="fatherName" @click.native="fatherName='sam'" @father-click="fatherName='tom'"></father>
官方文档说
.native的原始事件
和自定义监听器
冲突的话.native
监听器将静默失败,实际测试并没有:传送门
- 监听器的名字不支持驼峰式/短横线式互转
带参通信($event)
Vue.component("son",{
template:`
<div class="border px-3 py-3 mx-3 my-3">
<!-- $emit( , ) 带参 -->
<input type="button" value="changeFather's name" @click="$emit('son-click','tom')"/>
</div>
`
});
Vue.component("father",{
template:`
<div>
<!-- $event用来接收参数,这里把参数再次传给父组件 -->
<son @son-click="$emit('father-click',$event)"></son>
</div>
`
});
传给表达式:
<div id="app">
<father :name="fatherName" @father-click="fatherName=$event"></father>
{{ fatherName }}
</div>
传递给方法:
<div id="app">
<father :name="fatherName" @father-click="changeName"></father>
{{ fatherName }}
</div>
/* ... ... */
methods:{ //第一个参数就是传过来的值,名字任意
say:function(name){
this.fatherName=name;
}
}
/* ... ... */
父子组件v-model通信
众所周知:v-model等价于… 传送门,所以,可以:
Vue.component("test",{
template:`
<!-- 接收初始值 自定义事件,这里的$event不是通讯传参那个,是真的event -->
<input :value="value" @input="$emit('input',$event.target.value)"/>
`,
props:["value"]
});
var app = new Vue({
el:"#app",
data:{
message:"hello"
}
});
<div id="app">
<test :value="message" @input="message=$event"></test>{{message}}
</div>
<!-- Vue为父子组件通讯专门设置了: -->
<div id="app">
<test v-model="message"></test>{{message}}
</div>
<!-- 所以可以简写,注意传过来的
$event.target.value是input,例如checkbox是selected,不要忘了,不是都是value -->
.sync修饰符
在使用带参通信时推荐使用update:myPropName
的格式,sync
可以简化这种格式:
<div id="app">
<!-- 和上面都一样,就是监听器名字改成 update:myPropName 的形式 -->
<!-- <test :name="post.name" @update:name="post.name=$event"></test>{{post.name}} -->
<!-- 可以简化为: -->
<test :name.sync="post.name"></test>{{post.name}}
</div>
Vue.component("test",{
template:`
<div>
<input type="button" value="change" @click="$emit('update:name','tom')"/>
{{name}}
</div>
`,
props:["name"]
});
var app=new Vue({
el:"#app",
data:{
post:{
name:"jack",
age:20
}
}
});
多事件多值简写(.sync结合v-bind)
当需要传多个变量的不同事件时,可以结合v-bind
使用:
<div id="app">
<!-- 正常要写这么多: -->
<!-- <test :name.sync="post.name" :age.sync="post.age"></test>{{post}} -->
<!-- 简写后: 和props的$attrs功能差不多呀 -->
<test v-bind.sync="post"></test>{{post}}
</div>
Vue.component("test",{
template:`
<div>
<input type="button" value="change" @click="$emit('update:name','tom')" @mouseover="$emit('update:age',18)"/>
{{name}}{{age}}
</div>
`,
props:["name","age"]
});
实例事件($on,once,off)
有时候需要在Vue应用外写一个Vue应用的方法和触发它的事件,可以用实例事件
<div id="app">
<p>count={{count}}</p>
<add-button @addcount="count++"></add-button>
<!-- Vue应用内触发 -->
</div>
<!-- 实例事件-1:应用外的一个按钮 -->
<input type="button" value="reduce" onclick="reduce()"/>
Vue.component("add-button",{
template:`
<input type="button" value="add" @click="$emit('addcount')"/>
`
})
var app=new Vue({
el:"#app",
data:{
count:0
},
methods:{
add:function(){
this.count++;
}
}
})
// 实例事件-2:点击后通过$emit触发指定事件
function reduce(){
app.$emit("reduceAppCount");
}
//实例事件-3:通过$on指定事件内容
//事件发生一次触发一次
app.$on(/"reduceAppCount",function(){
this.count--;
})
/*仅能触发一次
app.$once("reduceAppCount",function(){
this.count--;
})*/
/*关闭事件,关闭后不会再触发
app.$on("reduceAppCount",function(){
this.count--; //触发一次
this.$off("reduceAppCount"); //关闭
})*/
父子组件通信-2
子访问根($root
) And 子访问父($parent
)
<div id="app">
<father></father>
</div>
Vue.component("father",{
//只要是子组件都可以访问到$root,不限于第一代子组件
template:'<son></son>',
data:function(){
return{
message:"fatherData",
}
},
computed:{
newMessage:function(){
return this.message+'_computed'
}
},
methods:{
say:function(){
console.log("fatherMethod")
}
}
})
Vue.component("son",{
template:'<p @click="appSay">{{appMessage}},{{appNewMessage}},{{fatherMessage}},{{fatherNewMessage}}</p>',
data:function(){
return{
appMessage:this.$root.message, //访问根元素的data
appNewMessage:this.$root.newMessage, //访问根元素的计算属性
fatherMessage:this.$parent.message, //访问父元素的data
fatherNewMessage:this.$parent.newMessage //访问父元素的计算属性
}
},
methods:{
appSay:function(){
this.$root.say(); //访问根元素的方法
this.$parent.say(); //访问父元素的方法
}
}
})
var app=new Vue({
el:"#app",
data:{
message:"rootData"
},
methods:{
say:function(){
console.log("rootMethod")
}
},
computed:{
newMessage:function(){
return this.message+'_computed'
}
}
});
父访问子($refs)
<div id="app">
<base-form></base-form>
</div>
Vue.component("base-form",{
template:`
<!-- 单击时更改子组件value值,鼠标移上去时触发子组件方法 -->
<div class="base-form" @click="changeValue" @mouseover="changeFocus">
<!-- 把这个子组件存进refs里并命名 -->
name:<base-input ref="usernameInput"></base-input>
</div>
`,
methods:{
changeValue:function(){
//读取$refs中的usernameInput
//可以访问子组件的数据
this.$refs.usernameInput.value="tom";
},
changeFocus:function(){
//可以访问子组件的方法
//总之就和子组件内部一样,随便操作
this.$refs.usernameInput.getFocus();
}
}
})
Vue.component("base-input",{
template:'<input type="text" ref="input" :value="value"/>',
data:function(){
return{
value:"sam",
}
},
methods:{
getFocus:function(){
this.$refs.input.focus();
}
}
})
后代访问前代(依赖注入)
使用实例对象provide
和inject
实现依赖注入
<div id="app">
<father></father>
</div>
Vue.component("father",{
template:'<son></son>',
})
Vue.component("son",{
//后代对象可以访问到前代提供的方法和数据
template:'<p @click="say">root name is {{name}}</p>',
//在这里指定需要接收的数据/方法
inject:["name","say"]
})
var app=new Vue({
el:"#app",
data:{
name:"app"
},
methods:{
say:function(){
console.log("my name is "+this.name)
}
},
//包含一个后代组件都可以访问到的数据/方法对象
provide:function(){
return{
name:this.name,
say:this.say
}
}
});
Prop
基本格式
<div id="app">
<father :name="name"></father>
</div>
Vue.component("grandson",{
template:'<p>{{name}}</p>',
props:["name"]
});
Vue.component("son",{
template:'<p><grandson :name="name"></grandson></p>',
props:["name"]
});
Vue.component("father",{
template:'<p><son :name="name"></son></p>',
props:["name"]
});
var app = new Vue({
el:"#app",
data:{
name:"jack"
}
});
$attrs修饰符
$attrs
相当于自动把所有props
传给下一个,免去手动传递了,替换son
和father
内容如下:
Vue.component("son",{
template:'<p><grandson v-bind="$attrs"></grandson></p>'
});
Vue.component("father",{
template:'<p><son v-bind="$attrs"></son></p>'
});
prop单向特性
Vue中几乎所有都是双向绑定
,但要注意prop
:
父子的prop
都是单向下行绑定
,父级prop的更新会流动到子组件中,反之则不行,这样可以防止子组件意外改变父组件
示例
<div id="app">
<son :name="fatherName"></son>
my name is {{ fatherName }}
<!-- 父->子特性:点击按钮修改data,prop参数会向流向子元件,并更新子元件对应的prop参数 -->
<input type="button" value="change my name" @click="fatherName='sam'"/>
</div>
Vue.component("son",{
template:`
<div>
father's name is {{name}}
<!-- 子->×父特性:子元件更新prop参数,只会更新本身,不会流向父组件 -->
<!-- 可以这样直接修改prop来改变自身,但Vue会警告,最好不要这样做 -->
<input type="button" value="change father's name" @click="name='tom'"/>
</div>
`,
props:["name"]
});
var app = new Vue({
el:"#app",
data:{
fatherName:"jack"
}
});
prop传递初始值
直接修改prop
的做法在Vue里是不被允许的
若要用于初始值(只传过来一次,后续的修改互不影响)的传递,可以存入data
:
Vue.component("son",{
template:`
<div>
<!-- 父组件修改prop参数仍会流向子组件,但不会影响到data里的fatherName -->
father's history name is {{name}}
<!-- 修改自身data不会影响prop参数 -->
father's realTime name is {{fatherName}}
<input type="button" @click="fatherName='sam'"/>
</div>
`,
props:["name"],
data:function(){
return{ //将prop参数存入data里,使用时当作自身数据使用
fatherName:this.name
}
}
});
prop传递原始值
若要用于传递原始值且需要进行转换(父组件修改prop仍可影响到子组件),可以用计算属性
:
根据计算属性双向绑定
的特性,父组件修改了prop,会使子组件重新计算,所以子组件的加工后的值也会随之变化
Vue.component("son",{
template:`
<div>
father's name is {{fatherName}}
</div>
`,
props:["name"],
computed:{ //prop参数name发生改变时这里会重新计算
fatherName:function(){ //处理原始值
return this.name.trim().toUpperCase();
}
}
});
prop类型验证
<div id="app">
<blue-p
:number-content="post.numberContent"
:string-content="post.stringContent"
...省略...
:diy-content="post.diyContent"
></blue-p>
</div>
Vue.component("blueP",{
template:`
<p>
<strong>基础类型:</strong><br/>
{{ typeof(numberContent)+":"+numberContent }}<br/>
{{ typeof(stringContent)+":"+stringContent }}<br/>
{{ typeof(booleanContent)+":"+booleanContent }}<br/>
{{ typeof(arrayContent)+":"+arrayContent }}<br/>
{{ typeof(objectContent)+":"+objectContent }}<br/>
<strong>多个可能的类型:</strong><br/>
{{ typeof(multipleTypeContent1)+":"+multipleTypeContent1 }}<br/>
{{ typeof(multipleTypeContent2)+":"+multipleTypeContent2 }}<br/>
<strong>修饰参数:</strong><br/>
<em>必填</em><br/>
{{ typeof(mustContent)+":"+mustContent }}<br/>
<em>默认值</em><br/>
{{ typeof(defaultContent1)+":"+defaultContent1 }}<br/>
{{ typeof(defaultContent2)+":"+defaultContent2 }}<br/>
{{ typeof(defaultContentObject)+":"+defaultContentObject }}<br/>
<strong>自定义验证</strong><br/>
{{ typeof(diyContent)+":"+diyContent }}<br/>
<strong>自定义type</strong><br/>
{{ typeof(diyTypeContent)+":"+diyTypeContent }}<br/>
</p>
`,
props:{
//基础类型
"number-content":Number,
"string-content":String,
"boolean-content":Boolean,
"array-content":Array,
"object-content":Object,
//多个可能的类型
"multiple-type-content-1":[String,Boolean],
"multiple-type-content-2":[String,Boolean],
//修饰参数-必填
"must-content":{
type:String,
required:true
},
//修饰参数-默认值
"default-content-1":{
type:Number,
default:15
},
"default-content-2":{
type:Number,
default:15
},
"default-content-object":{
type:Object,
default:function(){ //必须是工厂函数模式
return{
name:"sam",
age:20
}
}
},
//自定义验证
"diy-content":{
validator:function(value){
//为number类型且大于18
return typeof(value)=="number" && value>18;
}
},
//自定义type
"diy-type-content":Student
}
});
var app = new Vue({
el:"#app",
data:{
post:{
//基础类型
numberContent:12,
stringContent:"hello",
booleanContent:true,
arrayContent:[1,2,3],
objectContent:{name:"sam",age:20},
//多个可能的类型
multipleTypeContent1:true,
multipleTypeContent2:"hello",
//修饰参数-必填
mustContent:"hello",
//修饰参数-默认值
defaultContent1:20,
//自定义验证
diyContent:19
//自定义Type
diyTypeContent:new Student("sam",15)
}
}
});
一次传入所有属性
对象属性可以一次性传入,html
代码可改为:
<div id="app">
<blue-p v-bind="post"></blue-p>
</div>
需要注意post
里的参数名称必须和props
里的参数名相同(驼峰式/短横线式任意)
非prop特性
示例
正常情况下prop
的传递情况:
<div id="app">
<test message="world"></test>
</div>
Vue.component("test",{
//$props['message'] 等同于 message ,注意props属性不能用 $attrs['message']
template:"<p>hello {{ $props['message'] }}</p>",
props:["message"]
})
结果 ⇒ <p>hello message</p>
Vue
还具有非prop
特性:
<div id="app">
<test message="world"></test>
</div>
Vue.component("test",{
//非prop属性必须用 $attrs['message']
template:"<p>hello {{ $attrs['message'] }} </p>"
})
结果 ⇒ <p message="world">hello world </p>
该组件的根元素被
vue
继承了属性,正常的prop
则不会继承属性
替换/合并特性
<div id="app">
<test message="world" style="background:#eee" class="border"></test>
</div>
Vue.component("test",{
template:"<p message='bbb' style='font-size:20px'>hello</p>"
})
结果 ⇒ <p message="world" style="font-size:20px;background:#eee" class="border">hello</p>
一般属性被替换,
style
,class
属性被智能合并
阻止继承
给组件添加inheritAttrs:false
可以阻止继承,但不会影响style
和class
<div id="app">
<test message="world" message2="aaa" style="background:#eee" class="border"></test>
</div>
Vue.component("test",{
template:"<p message='bbb' style='font-size:20px'>hello {{ $attrs['message'] }}</p>",
inheritAttrs:false
})
结果 ⇒ <p message="bbb" style="font-size:20px;background:#eee" class="border">hello world</p>
未被替换 style和class不受影响
配合$attrs过滤props
<div id="app">
<father :name="name" :age="20"></father>
</div>
Vue.component("son",{
//age会传不过来,所以只有name可用
template:'<p>{{name}} {{age}}</p>',
props:["name","age"]
});
Vue.component("father",{
//传递所有参数到子组件
template:'<p><son v-bind="$attrs"></son></p>',.,
//禁止继承
inheritAttrs:false,
//禁止继承的属性
props:["age"]
});
var app = new Vue({
el:"#app",
data:{
name:"jack"
}
});
结果 ⇒ jack //age被过滤了
插槽
基础例子
<test>
<p>html标签内容</p>普通内容
</test>
template:`
<div>
<slot></slot>
</div>
`
渲染结果为:插槽内容替换了<slot>
标签,且支持html标签
和组件
<div>
<p>html标签内容</p>普通内容
</div>
默认值
<div id="app">
<test></test>
<test>456</test>
</div>
Vue.component("test",{
template:`
<div>
<slot>123</slot>
</div>
`
})
渲染结果:
<!-- 采用默认值 -->
<div>
123
</div>
<!-- 替换掉默认值 -->
<div>
456
</div>
具名插槽
<div id="app">
<test>
<template v-slot:header>this is header.</template>
<!-- defalut指向组件中省略name属性的slot -->
<!-- <template v-slot:default>this is header.</template> -->
<!-- 简写格式:可以不写template标签 -->
this is header.
<template v-slot:bottom>this is bottom.</template>
</test>
</div>
Vue.component("test",{
template:`
<div>
<slot name="header"></slot>
<!-- 有一个可以不写template标签就可用的slot,这个slot的名为default -->
<!-- <slot name="default"></slot> -->
<!-- 简写格式:可以省略name -->
<slot></slot>
<slot name="bottom"></slot>
</div>
`
})
和v-bind
缩写是:
,v-on
缩写是@
一样,插槽名
也支持缩写,是#
作用域
<div id="app">
<!-- 此处的message(fatherMessage)是父组件(相对子组件)作用域 -->
<test>{{message}}</test>
<test></test>
</div>
Vue.component("test",{
template:`
<div>
<!-- 此处的message(childMessage)是子组件(相对父组件)作用域 -->
<slot>{{message}}</slot>
</div>
`,
data:function(){
return{
message:"childMessage"
}
}
})
var app=new Vue({
el:"#app",
data:{
message:"fatherMessage"
}
})
父访问子
仅default插槽
<div id="app">
<!-- 获取子组件的default插槽的参数对象 -->
<!-- <test v-slot:default="messages">{{messages}}</test> -->
<!-- 是default的话可以省略指定名字 -->
<test v-slot="messages">{{messages}}</test>
</div>
Vue.component("test",{
template:`
<div>
<!-- 把message和message2打包为对象给父组件 -->
<slot :message="message" :message2="message2"></slot>
</div>
`,
data:function(){
return{
message:"childMessage",
message2:"chilaMessage2"
}
}
})
渲染结果:
{ "message": "childMessage", "message2": "chilaMessage2" }
多插槽访问语法格式
上面的例子只有一个default
插槽,若有多个插槽,需要注意格式:
<div id="app">
<test>
<!-- 多插槽下禁止将v-slot写在根标签,但仍支持简写default -->
<template v-slot="mainMessages">{{mainMessages}}</template>
<template v-slot:header="messages">{{messages}}</template>
</test>
</div>
Vue.component("test",{
template:`
<div>
<slot :mainMessage="mainMessage"></slot>
<slot name="header" :message="message" :message2="message2"></slot>
</div>
`,
data:function(){
return{
message:"childMessage",
message2:"chilaMessage2",
mainMessage:"childmainMessage"
}
}
})
解构赋值结合插槽内容
<div id="app">
<test>
<!-- 这里的slot将被赋予一个对象: -->
<!-- { "message": "sam", "message2": 19 } -->
<!-- 浏览器如果支持es6的结构赋值,可以: -->
<template v-slot="{message='aaa',message2:age,message3='汉族'}">{{message+age+message3}}</template>
<!-- 注意是这个v-slot=""被解构赋值 -->
<!-- {message,message2:age,message3='汉族'}={message:"childMessage",message2:19} -->
</test>
</div>
Vue.component("test",{
template:`
<div>
<slot :message="message" :message2="message2"></slot>
</div>
`,
data:function(){
return{
message:"sam",
message2:19
}
}
});
var app=new Vue({
el:"#app"
});
支持动态插槽名
动态参数传送门,插槽名也支持动态参数:
<div id="app">
<test>
<template v-slot:[slotname]>setter text.</template>
</test>
</div>
Vue.component("test",{
template:`
<div>
<slot name="header">header default text.</slot>
<slot>main default text.</slot>
</div>
`
});
var app=new Vue({
el:"#app",
data:{
slotname:"default"
}
});
缓存和异步组件
缓存组件(keep-alive)
在多标签页面中可能需要缓存页面的状态,这时候需要<keep-alive>
标签来缓存不活动的组件
<div id="app">
<input type="button" v-for="tab in tabs" :value="tab" @click="cureentTabs=tab"/>
<!-- 尝试切换几个标签,component内的内容会被缓存,不会重新渲染 -->
<keep-alive> <!-- 在需要缓存的地方包裹上keep-alive标签 -->
<component :is="currentTabsComputed"></component>
</keep-alive>
</div>
Vue.component("tab-header",{
template:'<div>header component.<input /></div>'
});
Vue.component("tab-main",{
template:'<div>main component.<input /></div>'
});
Vue.component("tab-bottom",{
template:'<div>bottom component.<input /></div>'
});
var app=new Vue({
el:"#app",
data:{
tabs:["header","main","bottom"],
cureentTabs:"header"
},
computed:{
currentTabsComputed:function(){
return 'tab-'+this.cureentTabs
}
}
});
异步组件(需要学习webpack)
<div id="app">
<!-- 在页面加载完毕的2s后异步渲染 -->
<test></test>
</div>
//resolve表示成功,reject表示失败,变量名任意
Vue.component("test",function(resolve,reject){
setTimeout(function(){ //等5秒
resolve({ //成功就返回这个
template:'<p>aaa</p>'
})
},2000);
})
组件模板其他格式
内联模板
子组件加上inline-template
属性,组件的template
可以内联到DOM
中:
<div id="app">
<test inline-template> <!-- 加这个属性 -->
<!-- begin 和app里的一样,就是位置变了 -->
<div class="test">
<span>username:{{username}}</span>
<span>content:{{content}}</span>
</div>
<!-- end -->
</test>
</div>
Vue.component("test",{
data:function(){
return{
username:"sam",
content:"hello"
}
}
})
X模板
可以把模板外置到script
标签内,只需要给script
标签指定类型和id
,并将id
传给模板属性
<div id="app">
<test></test>
</div>
<script type="text/x-template" id="x-template">
<div class="test">
<span>username:{{username}}</span>
<span>content:{{content}}</span>
</div>
</script>
Vue.component("test",{
template:"#x-template",
data:function(){
return{
username:"sam",
content:"hello"
}
}
})
低开销静态组件(v-once)
有时候一个组件里都是静态内容,如果每次都渲染会浪费cpu,可以用v-once
设置首次渲染后缓存起来,但注意这个组件只能是静态的了。最好不要用,除非渲染真的慢
Vue.component("test",{
template:`
<div class="test" v-once>
<!-- 加上v-once,此时若单击span,不会触发任何事件,这个组件已经是静态的了 -->
<span @click="username='aaa'">username:{{username}}</span>
</div>
`,
data:function(){
return{
username:"sam"
}
}
})
继承(混入)
继承方式
有一个phone
和computer
对象(Vue组件):
var phone={
data:function(){
return{
name:"phone1"
}
},
created:function(){
console.log("aaa");
},
methods:{
call:function(){
console.log(this.name+" calling...");
}
}
};
var computer={
data:function(){
return{
name:"computer1"
}
},
created:function(){
console.log("bbb");
},
methods:{
comput:function(){
console.log(this.name+" computing...");
}
}
};
Vue.extend
//使用extend方法继承Vue组件
var intePhone=Vue.extend({
mixins:[phone,computer], //继承的组件名称
//格式和Vue组件格式都一样
created:function(){
console.log("ccc");
},
data:function(){
return{
name:"intePhone"
}
}
});
var intePhone=new intePhone();
intePhone.call();
intePhone.comput();
Vue应用内继承
<div id="intePhoneApp"></div>
var intePhoneApp=new Vue({
mixins:[phone,computer],
el:"#intePhoneApp",
created:function(){
console.log("ccc");
},
data:{
name:"intePhoneApp"
}
});
intePhoneApp.comput();
全局继承
表示所有Vue应用,组件…都将继承指定组件
Vue.mixin({
mixins:[phone,computer],
created:function(){
console.log("ccc");
},
data:{
name:"intePhoneApp"
}
});
Vue应用:
<div id="app"></div>
var app=new Vue({
el:"#app"
});
app.comput();
Vue组件:
<div id="app">
<test></test>
</div>
Vue.component("test",{
template:"<p @click='computByFather'>comput</p>",
methods:{
computByFather:function(){
this.comput(); //继承了父组件
}
}
});
var app=new Vue({
el:"#app"
});
覆盖规则
同名data和同名钩子函数
- 同名data:在
mixins
数组内,后者覆盖前者,然后继承者覆盖数组内最后者;例如上面的例子中,computer
覆盖phone
,最终应用覆盖computer
。 - 同名钩子函数:参与继承/被继承所有组件的钩子函数将会组合为一个数组,依次执行(
mixins
数组内前后依次执行, 然后执行继承者),例如上面的例子中,created
函数的执行结果为:aaa bbb ccc
; - 同名钩子函数:例如
methods
之类的钩子函数,同名子函数采用同data
一样的覆盖规则。
自定义选项和覆盖规则
//1、全局自定义选项如何处理
Vue.mixin({
created:function(){
//示例:自定义选项info的处理语句
//创建时输出info选项值
var info=this.$options.info;
if(info){
console.log(info);
}
}
});
//2、自定义选项覆盖规则
var animate={
info:{name:"animate",age:1}
};
var dog=Vue.extend({
info:{name:"dog"},
mixins:[animate]
});
var d1=new dog();
//结果:{name:"dog"}
//无论是什么类型,都进行覆盖
//3、自定义"自定义选项"覆盖规则
var animate2={
info:{name:"animate",age:1}
};
var dog2=Vue.extend({
info:{name:"dog"},
mixins:[animate]
});
//Vue.config.optionMergeStrategies是一个存放覆盖规则的对象
Vue.config.optionMergeStrategies.info=function(toVal,fromVal){
//相当于默认的覆盖规则,还可以写其他自定义的规则
//fromVal:被覆盖者 toVal:覆盖者
if(!fromVal){return toVal}
if(!toVal){return fromVal}
}
var d2=new dog2();
//结果:{name:"dog"}
//和默认一样的规则
自定义指令
注册自定义指令
全局注册
<div id="app">
<!-- 打开页面获得焦点 -->
<input v-focus/>
</div>
Vue.directive("focus",{ //全局有效
inserted:function(el){ //钩子函数:插入时触发
el.focus(); //使用对象获得焦点
}
});
var app=new Vue({
el:"#app"
});
局部注册
var app=new Vue({
el:"#app",
directives:{ //仅在当前app内有效
focus:{
inserted:function(el){
el.focus();
}
}
}
});
钩子函数
钩子函数
bind和inserted
bind
执行是在dom
加载之前,所以函数中读取不到父元素,适合做初始化工作inserted
执行是在dom
加载完后,所以函数中可以读取到父元素,适合正式开始写处理语句
<div id="app">
<input type="input" v-focus/>
</div>
var app=new Vue({
el:"#app",
directives:{
focus:{
bind:function(el){ //指令首次绑定到元素时调用
console.log("bind的父节点:"+el.parentNode); //null
},
inserted:function(el){ //被绑定元素插入父节点时调用
el.focus(); //比如这句放在这里比较合适,不能放在bind里,放这里表示加载完后该元素获得焦点
console.log("inserted的父节点:"+el.parentNode); //可读取到
}
}
}
});
update和componentUpdated
update
执行是在所在组件的VNode
更新时调用(也可能是在其后),指令的值可能发生了变化,也可能没有componentUpdated
执行是在所在组件的VNode
及其子VNode
更新后调用,指令的值都已被更新
<div id="app">
<p v-test :name="name" @click="changeName"/>{{name}}</p>
</div>
var app=new Vue({
el:"#app",
directives:{
test:{
update:function(el){
console.log("update");
console.log("innerHTML:"+el.innerHTML); //==>sam 有些值是更新时的
console.log("name:"+el.getAttribute("name")); //==>tom 有些值是更新后的
},
componentUpdated:function(el){
console.log("componentUpdated");
console.log("innerHTML:"+el.innerHTML); //==>tom 都是更新后的
console.log("name:"+el.getAttribute("name")); //==>tom
}
}
},
data:{
name:"sam"
},
methods:{
changeName:function(){
this.name="tom";
}
}
})
unload
<div id="app">
<p v-test v-if="ok" @click="unload">unload</p>
</div>
var app=new Vue({
el:"#app",
directives:{
test:{
unbind:function(el){
console.log("destory");
}
}
},
data:{
ok:true
},
methods:{
unload:function(){
this.ok=false
}
}
})
钩子函数简写(update+bind)
<div id="app">
<p v-test @click="unload">{{name}}</p>
</div>
var app=new Vue({
el:"#app",
directives:{
test:function(){
console.log("update/bind");
}
},
data:{
name:"tom"
},
methods:{
unload:function(){
this.name="sam"
}
}
})
/*全局写法
Vue.directive("test",function(){
console.log("update/bind");
});*/
钩子函数参数
<div id="app">
<input value="button" v-button:style.preset="1+0"/>
</div>
var app=new Vue({
el:"#app",
directives:{
button:{
//注意:可修改el,但不要修改binding
//名字任意,顺序不任意
inserted:function(el,binding,vnode,oldVnode){
console.log("指令所绑定的DOM元素:"+el); //==><input .../>
console.log("指令名(不带V-):"+binding.name); //==>button
console.log("指令值:"+binding.value); //==>1
//console.log("旧指令值(仅用于两个更新钩子):"+binding.oldValue);
console.log("字符串形式的表达式:"+binding.expression); //==>"1+0"
console.log("传给指令的参数(String):"+binding.arg); //==>style,若是多个则用:分隔,如a:b:c
console.log("传给指令的修饰符(Object):"+binding.modifiers); //==>{preset:true}
console.log("Vue编译生成的虚拟节点:"+vnode); //==>VNode{tag:"input",data:{...},children:undefined...}
//console.log("旧虚拟节点(仅用于两个更新钩子):"+oldVnode);
//示例:
el.setAttribute("type","button");
// 有style参数 且 有preset修饰符 且 值等于1
if(binding.arg.indexOf("style")!=-1 && binding.modifiers.preset && binding.value==1){
el.style="border:2px solid #0af;background:#0bf;color:#fff";
}
}
}
}
});
动态指令参数值
<div id="app">
<input value="button" v-red:[arg]/>
</div>
var app=new Vue({
el:"#app",
directives:{
red:{
bind:function(el,binding){
var arg=binding.arg;
el.style[arg]="red";
}
}
},
data:{
//动态指定参数,注意这里的值仅在渲染时用一下,不会绑定数据,后期操作互不影响
arg:"border-color"
}
});
渲染函数
单元素和多元素格式
<div id="app">
<anchored-heading :level="2">aa</anchored-heading> <!-- <h2>aa</h2> -->
<many-element></many-element> <!-- /<div> <a>aaa</a> <a>bbb</a> </div> -->
</div>
//渲染函数和普通组件结构都一样,就是template换成了render,由静态渲染换成了js动态渲染
Vue.component('anchored-heading', { //单元素格式
render:function(ce){
//语法:createElement(elementName,innerHTML)
return ce("h"+this.level,this.$slots.default)
},
props:["level"]
})
Vue.component('many-element', { //多元素格式
render:function(ce){
return ce("div",[ //根元素
ce("a","aaa"), //aaa,bbb两个子元素
ce("a","bbb")
]);
}
})
createElement参数
上面的例子的语法为:
createElement(elementName,innerHTML)
innerHTML
参数可以还可以是一个对象,对象参数解释如下:
<div id="app">
<rendering></rendering>
</div>
Vue.component("test",{
template:`<p :title="prop1" @click="$emit('aaa')"><slot text="hello"></slot></p>`,
props:["prop1"]
});
Vue.component('rendering', {
render:function(createElement){
var t=this; //注意this.$refs等需要在这里读
return createElement("test",{ //创建一个组件类型的元素
props:{ //prop
prop1:"test"
},
attrs:{ //属性
attr1:"attr1"
},
/*domProps:{ //DOM属性
innerHTML:"test" //和上面例子的第二个参数作用一样
},*/
class:{ //类和样式智能识别
a:false,
b:true
},
style:{
fontSize:"20px",
color:"#08f"
},
nativeOn:{ //原生事件
click:function(){
console.log("原生事件click");
}
},
on:{ //处理组件类型标签的自定义事件或给普通类型的标签添加原生事件
aaa:function(){
console.log("自定义事件aaa");
}
},
directives:[ //要在该组件上添加的自定义指令
{
name:"size", //binding.name
value:"30", //binding.value
arg:"px" //binding.arg
//binding.expression,binding.modifiers
//binding的属性都可以在这里直接用
}
],
scopedSlots:{ //填充子组件的插槽
/*default:function(){ //直接填
return `
<a>link</a>
`
}*/
/*default:function(props){ //还可以读取子组件插槽属性
return props.text
}*/
default:props => props.text //js的=>函数,等同于上面那个
},
//key:"唯一标识,不可重复",
//ref:"类似html表单的name,例如有两个ref等于xb,则$refs.xb是一个ref=xb的所有元素的数组"
})
},
directives:{
size:{ //上面用到的自定义指令
inserted:function(el,binding,vnode,oldVnode){
el.style.fontSize=binding.value+binding.arg;
}
}
}
})
上面例子是用一个组件作为标签名,普通html
标签除了on
,scopedSlots
等组件专有属性外,其他参数都可使用
createElement
创建的元素简称VNode
,是一个虚拟DOM,记录节点的描述信息,用来追踪如何改变真实DOM
VNode唯一性
<div id="app">
<test>title1</test>
</div>
这样违反了VNode
唯一性(可正常渲染,但不符合规范):
Vue.component('test', {
render:function(createElement){
var pp=createElement("p","hi");
return createElement("div",[
pp,pp
])
}
})
可以采用工厂函数实现:
Vue.component('test', {
render:function(createElement){
return createElement("div",[
Array.apply(null,{length:20}).map(function(){
return createElement("p","hi")
})
])
}
})
涉及知识:
Array.map(function(item,index,array){})
/*遍历数组*/
Array.apply(null,{length:20})
/*声明20个undefined变量
0:undefined ... 19:undefined*/
Array.apply(20)
/*仅声明一下占用大小,所以用这个无法遍历
length:20*/
渲染函数实现template功能
v-if和v-for
Vue.component('test', {
render:function(createElement){
if(this.students.length){
return createElement("ul",this.students.map(function(item,index,array){
return createElement("li",item.name+" "+item.age)
}))
}else{
return createElement("p","no students")
}
/*实现了template中的:
<ul v-if="students.length">
<li v-for="item in items">{{item.name+" "+item.age}}</li>
</ul>
<p v-else>no students</p>
*/
},
data:function(){
return{
students:[
{name:"sam",age:20},
{name:"tom",age:21},
{name:"jack",age:21}
]
}
}
})
v-model
<div id="app">
<test @in="testvalue=$event"></test>{{testvalue}}
</div>
Vue.component('test', {
render:function(createElement){
var t=this;
return createElement("input",{
on:{
input:function(e){
t.$emit("in",e.target.value)
}
}
},t.value) //参数3等同domProps的innerHTML
}
})
var app=new Vue({
el:"#app",
data:{
testvalue:""
}
})
事件修饰符
addEventListener参数3
原生js传送门
Vue事件处理修饰符传送门
修饰符 | 前缀 |
---|---|
.capture | ! |
.passive | & |
.once | ~ |
.capture.once 或.once.capture | ~! (不可调换符号位置!~ |
格式:
on:{
"~!click":function(){} //事件类型因为有特殊字符,所以作为字符串传
}
其他事件都没有修饰符,只能用原生js实现
插槽
<div id="app">
<test>
<template v-slot:header>this is header.</template>
main
</test>
</div>
单个元素直接用this
Vue.component('test', {
render: function (createElement){
return createElement('div',this.$slots.default) //==>main
}
})
多个元素用需在外面引用this
Vue.component('test', {
render: function (createElement){
var self=this;
return createElement('div',[
createElement('p',self.$slots.header),
createElement('p',self.$slots.default)
])
}
})
如果不是在createElement
方法里直接写需要写为以下形式:
Vue.component('test', {
render: function (createElement){
var self=this;
return createElement('div',[
createElement('p',self.$slots.header),
createElement('p',{
domProps:{
innerHTML:self.$slots.default[0].text
}
})
])
}
})
作用域插槽
<div id="app">
<test>
<template v-slot="messages">{{messages}}</template>
<template v-slot:header="messages2">{{messages2}}</template>
</test>
</div>
Vue.component('test', {
render: function (createElement){
return createElement('div',[
this.$scopedSlots.default({
mainMessage:"mainMessage"
}),
this.$scopedSlots.header({
message:"message",
message2:"message2"
})
])
}
/*相当于Vue组件template中的:
template:`
<div>
<slot :mainMessage="mainMessage"></slot>
<slot name="header" :message="message" :message2="message2"></slot>
</div>
`*/
})
函数式组件
<div id="app">
<test name="sam" age="18" @click="say">
<template v-slot:default><p @click="say">default text</p></template>
<!-- 插槽里的事件作为输出的独立内容可以正常响应 -->
<template v-slot:header>header text</template>
aaa
</test>
</div>
//函数式组件没有响应式数据(this),如data、props等无法访问
//函数式组件没有上下文,所以渲染更快
Vue.component('test', {
functional:true, //设置为函数式组件
render:function(createElement,context){ //函数式组件不能响应数据,但提供了context参数
console.log(context.listeners);
return createElement("div",[
//插槽
createElement("p",context.slots().default), //this.$slots.default,slots()是函数所以要加括号
createElement("p",context.scopedSlots.header()), //this.$slots.header,暴露传入的插槽对象,和上面功能一样
createElement("p",context.data.scopedSlots.header()), //同上
//父子
createElement("p",context.children), //所有子节点VNode集合,指定名称的插槽(template标签)不会被计入,注意未指定名称的插槽会被计入(不含标签)
createElement("p",context.parent.$el.id), //parent:父节点,<div id="app">元素的id
//数据
//context里的data不是指组件的data,是传递给组件的整个数据对象,例如上面的scopedSlots和attrs
createElement("p",context.props.name), //this.name
createElement("p",context.data.attrs.age), //所有attrs,若有props参数,则props内已声明的参数不会在attrs中,若没有则所有参数都在attrs中;例本例attrs中只有age没有name
//事件
//函数式组件不能响应test上的事件(例如例题中的@click="say"不能响应)
createElement("p",{
on:{ //单独元素的事件可以响应
click:function(){
context.listeners.click(); //通过listeners对象获取test上的事件
}
}
},"click"),
createElement("p",context.injections.text) //访问inject,传送门:https://blog.csdn.net/yjl15517377095/article/details/100985342#_418
])
},
props:["name"], //函数式组件中可以省略props,因为可以自动添加;注意在template组件中仍不可省略
inject:["text"]
})
var app=new Vue({
el:"#app",
methods:{
say:function(){
console.log("hello");
}
},
provide:function(){
return{
text:"ni hao"
}
}
});
传递属性/事件特性
<div id="app">
<!-- 传递整个data使声明式函数不需要native修饰符就可以响应原生事件 -->
<test name="sam" age="18" @click="say"></test>
<test2 name="sam" age="18" @click.native="say"></test2>
</div>
Vue.component('test', {
functional:true,
render:function(createElement,context){
/*函数式组件不会渲染任何属性(包括style,class):<p></p>
函数式组件仍可在data里读取到:
return createElement("p",context.data.attrs.age)
所以可以传递整个data,实现和普通组件一样的效果:<p age="18"></p>*/
return createElement("p",context.data,"test")
//把data(包括事件监听器和数据)整个传递下去是非常透明的,甚至原生事件都不需要native修饰符
},
props:["name"]
});
Vue.component('test2', {
//普通组件不会渲染存在于props的属性,其他正常渲染:<p age="18"></p>
template:'<p>test2</p>',
props:["name"]
});
var app=new Vue({
el:"#app",
methods:{
say:function(){
console.log("hello");
}
}
});
过滤器
- 用管道符表示要使用的过滤器
- 局部和全局过滤器重名时优先使用局部过滤器
<div id="app">
<!-- 在{{}}中使用 -->
{{message | capitalize}}
<!-- 在v-bind中使用 -->
<p :title="message | uppercase">text</p>
<!-- 过滤器串联,由前至后依次过滤,先全小写再首字母大写 -->
{{'HELLO' | lowercase | capitalize}}
<!-- 带参过滤器 -->
{{'hello' | capitalize_diy(1)}}
</div>
//全局声明
Vue.filter("uppercase",function(value){
return value.toUpperCase()
});
Vue.filter("lowercase",function(value){
return value.toLowerCase()
});
Vue.filter("capitalize_diy",function(value,n){ //指定要大写的字符位置
return value.slice(0,n) + value.charAt(n).toUpperCase() + value.slice(n+1)
});
var app=new Vue({
el:"#app",
data:{
message:"hello"
},
//局部声明
filters:{
capitalize:function(value){ //首字母大写
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
});
Vuex
axios同步
下面两个写法效果相同:
getTodos(){
axios({
url: 'http://jsonplaceholder.typicode.com/todos'
}).then(response => {
console.log(response.data);
})
}
async
同步写法
async getTodos({ commit }){
var todos = await axios.get('http://jsonplaceholder.typicode.com/todos');
console.log(todos);
}
state,getters,actions,mutations
新建一个存放状态管理的文件夹store
(名字任意),新建一个index.js
(index表示默认的状态管理入口,引入时只需要写到文件夹路径,起其他名字则需要写到文件名)
import Vue from 'vue'
import Vuex from 'vuex'
import todos from './modules/todos'
Vue.use(Vuex)
export default new Vuex.Store({
modules:{
todos
}
})
在modules
(任意)创建todos
(任意,按用途起名字)
import axios from 'axios';
// 相当于 data
const state = {
todos:[]
};
// 相当于 computed(计算属性)
const getters = {
allTodos:state => state.todos
};
// 与后台交互,获取 json数据,传给 mutations
const actions ={
async getTodos({ commit }){
var todos = await axios.get('http://jsonplaceholder.typicode.com/todos');
console.log(todos);
this.commit('setTodos', todos);
}
};
// 操作 actions传来的数据,赋给 state
const mutations = {
setTodos(state, todos){
state.todos = todos
}
};
export default{
state, // state <==> state:state
getters,
actions,
mutations
};
不直接把
todos
的东西放在index.js
中是为了便于分离各个状态,便于管理
状态的读取和操作
<template>
<div class="todos">
{{ allTodos }}
</div>
</template>
<script>
// 需要哪个就映射哪个
import { mapGetters,mapActions } from 'vuex';
export default {
name: 'todos',
// 简便写法
methods: mapActions(['getTodos']),
// 正常写法(前面加 ...)
computed: {
...mapGetters(['allTodos'])
},
created(){
this.getTodos();
}
}
</script>