VUE介绍
- Vue的特点
- 构建用户界面,只关注
View
层 - 简单易学,简洁、轻量、快速
- 渐进式框架
- 构建用户界面,只关注
- 框架VS库
库,是一封装好的特定方法的集合,提供给开发者使用,库没有控制权,控制权在使用者手中;
代表:jQuery、underscore、util
框架,框架顾名思义就是一套架构,会基于自身的特点向用户提供一套相当完整的解决方案,而且控制权的在
框架本身,使用者要找框架所规定的某种规范进行开发
代表:backbone、angular、vue
理解渐进式
所谓的渐进式,可以一步一步、有阶段性的使用Vue
,不是必须在一开始把所有的东西都用上。
- 声明式的渲染(Declarative Rendering)
- 组件系统(Components System)
- 客户端路由器(vue-router)
- 大规模的状态管理(vuex)
-
构建工具(vue-cli)
Vue的两个核心点
- 响应式的数据绑定
当数据发生改变 -> 视图自动更新
忘记操作DOM
这回事,而是专注于操作数据 -
可组合的视图组件
把视图按照功能,切分若干基本单元;
组件可以一级一级组合成整个应用,形成了倒置的组件树;
使用组件的好处:可维护、可重用、可测试;
启动应用
- 全局的构造函数
new Vue({选项对象})
- 选项对象
告诉Vue你要做什么事情
el
: 告诉Vue管理的模板范围。css selector || element
data
: 数据对象,会被实例代理,转成getter/setter
形式。
Object
定义好的数据可以直接在模板中使用 - 插值
1.使用双大括号(“Mustache”语法)做文本插值
2.可以写任意表达式
3.属性名、条件运算符、数据方法调用
声明式渲染
- 声明式
只需要声明在哪里where 做什么what,而无需关心如何实现how - 命令式
需要以具体代码表达在哪里where做什么what,如何实现how - 声明式渲染理解
-
DOM
状态只是数据状态的一个映射; - 所有的逻辑尽可能在状态的层面去进行,就是说,所以逻辑去操作数据,不要想着去操作
DOM
; - 当状态改变了,
view
会被框架自动更新到合理的状态;
-
指令directive
- 理解指令
- 是一种特殊的自定义行间属性,以
v-
开头 - 将数据和
DOM
做关联,当表达式的值改变时,响应式的作用在视图 - 预期的值为
javascript
表达式
-
指令初体验
v-bind:
动态的绑定数据。简写为:
指令学习
DOM结构渲染说明
DOM
结构为模板,先把DOM
结构渲染出来,Vue
会找到这个模板,进行解析,绑定值绑定在指定的位置,重新替换原来的DOM
结构。
-
v-text
:更新元素的textContent
,可代替{{}}
-
v-html:
更新元素的innerHTML
不使用这个指令,插入的html
结构不作为模板编译,作为文本显示;
不要插入不安全的html
结构; -
v-cloak:
隐藏未编译的 Mustache 标签直到实例准备完毕 -
v-once:
只渲染一次,随后数据改变将不再重新渲染,视为静态内容,用于优化更新性能
列表渲染
-
v-for
作用:对一组数据循环生成对应的结构
语法:
循环数组:v-for=“item,index in 数组”
循环数组:v-for=“value,key,index in 对象”
v-for
:使用v-for循环对象时,拿到的下标是按照object.keys()
的顺序来的; -
key值
对渲染的列表的结构采用“就地复用”的策略,也就说当数据重新排列数据时,会复用已在页面渲染好的元素,不会移动DOM
元素来匹配数据项的顺序,这种模式是高效的,改变现有位置的结构的数据即可;
需要提供一个key
值,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素。
条件判断
-
v-if:
根据表达式的值的真假条件渲染元素和移出元素;(true
渲染,false
不渲染)
-v-else
:成功走if
,不成功走else
,与js
中的if else
逻辑相同
注意:v-if和v-else
必须要连起来用,不能单独出现,会报错,并且,两者之间不可以有间隔 -
v-show:
根据表达式的值的真假条件,切换元素的CSS
属性display
属性;
区别:
初始页面根据条件判断是否要渲染某一块的模板,使用v-if
频繁的切换模板的显示隐藏,使用v-show
自定义指令
需要对普通 DOM
元素进行底层操作,这时候就会用到自定义指令。
注册方法:
全局注册(Vue.directive
)与局部注册(directives:{}
):
钩子函数
bind
:只调用一次,指令第一次绑定到元素时调用
inserted
:被绑定元素插入父节点时调用
update
:更新时调用
componentUpdated
: 更新完毕调用
unbind
:只调用一次,指令与元素解绑时调用,就是当元素被移除DOM
的时候就会触发这个函数
钩子函数参数
-
el
:指令所绑定的元素,可以用来直接操作DOM
-
binding
:可以看成是一个配置自定义指令的一些属性的对象;
binding对象中的一些属性说明-
name
:指令名,不包括v-
前缀 -
value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。 -
oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。 -
expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。 -
arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。 -
modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
-
-
vnode
:Vue
编译生成的虚拟节点。 -
oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
自定义指令小练习
- input自动获取焦点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>input自动获取焦点</title>
</head>
<body>
<div id="app">
<!-- v-focus:这就是下面自定义的指令 -->
<input type="text" v-focus>
</div>
<script src="../vue.js"></script>
<script>
//全局写法
// Vue.directive('focus',{
// inserted(el,binding){
// el.focus();
// }
// });
new Vue({
el:'#app',
//局部写法
directives:{
focus:{
inserted(el,binding){
console.log(binding)
el.focus();
}
}
}
});
</script>
</body>
</html>
- 点击按钮显示下拉列表,点击随意位置隐藏下拉列表
事件系统
v-on:
可以用v-on
指令监听DOM
事件,简写为@
;事件处理函数
事件处理函数写在methods
中;
在模板中不传参,只写上函数名字,函数第一个参数事件对象作为事件处理函数的第一个参数传入的;
在模板中传参,需要手动在模板中使用$event
传入事件对象;
事件处理函数(methods)中的this
都指向根实例(Vue);事件修饰符
方法只有纯粹的数据逻辑,而不是去处理DOM
事件细节。
语法:v-on:
事件名.修饰符=”处理函数|表达式|处理函数执(tu)行”.stop(取消冒泡)、.prevent(阻止默认行为)、.capture(阻止捕获)、.self(在自己身上触发)、.once(事件只执行一次)
按键修饰符.enter、.tab、.delete 、.esc、.space、.up、.down、.left、.right
简单的控制显示隐藏案例:
<div id="app">
<!--点击按钮,来改变display的值(true/flase),这里写的是表达式-->
<button @click="display = !display">按钮1</button>
<!--
1.事件指令写方法函数名,这个方法名要在vuer的methods中定义;
2.如果方法中需要传参,就可以直接写clickFn(str),这里写法和js的函数写法一样,下面方法中接收就可以了-->
<button @click="clickFn">按钮2</button>
<!--通过上面按钮点击时,改变的display的值,使用指令v-show实现下面div的显示和隐藏-->
<div v-show="display">点击显示隐藏</div>
</div>
<script>
let vm = new Vue({
el:'#app',
data:{
//通过创建一个状态,通过改变状态实现想要得到的效果;
display:true
},
methods:{ //在模板中使用的方法,都统一写在这里
clickFn(){
console.log('我是按钮2执行的方法')
}
}
});
</script>
双向数据绑定
Vue
将数据对象和DOM
进行绑定,彼此之间互相产生影响,数据的改变会引起DOM
的改变,DOM
的改变也会引起数据的变化;
可交互的表单元素
input(text、checkbox、radio) textarea select
v-model 是个语法糖,数据绑定在value上,监听了oninput事件
1.在表单元素上创建双向数据绑定;
2.会根据控件类型自动选取正确的方法来更新元素交互的值;
3.负责监听用户的输入事件以更新数据;
注意:
1.会忽略所有表单元素的value、checked、selected
特性的初始值;
2.将Vue
实例的数据作为数据来源;
3.需要在数据对象中声明初始值;
双向数据绑定示意
- 上图流程说明:数据(Model)通过VM(ViewModel)绑定到视图(View),然后用户进行交互,交互后的数据又通过
VM
中绑定的Model
,改变Model
中的数据;
MVVM模式说明:
M=Model
V=view
VM=vue实例
数据通过VM
层绑定在视图上,视图上有交互,改变了数据,改变之后会重新渲染;
响应式原理
把一个普通的 JavaScript
对象传给Vue
实例的data
选项 Vue
将遍历此对象所有的属性,并使用 Object.defineProperty
把这些属性全部转为 getter/setter
Vue
内部会对数据进行劫持操作,进而追踪依赖,在属性被访问和修改时通知变化;
具体步骤:
step1:需要observe
的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter
和getter
这样的话,给这个对象的某个值赋值,就会触发setter
,那么就能监听到了数据变化
step2:compile
解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
step3:Watcher
订阅者是Observer
和Compile
之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()
方法
3、待属性变动dep.notice()
通知时,能调用自身的update()
方法,并触发Compile
中绑定的回调,则功成身退。
step4:MVVM
作为数据绑定的入口,整合Observer
、Compile
和Watcher
三者,通过Observer
来监听自己的model
数据变化,通过Compile
来解析编译模板指令,最终利用Watcher
搭起Observer
和Compile
之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input)
-> 数据model
变更的双向绑定效果。
Object.defineProperty
作用:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性;(通俗意思就是给对象定义属性的一个方法)
语法:
Object.defineProperty(obj, prop, descriptor)
;-
参数:
obj
要在其上定义属性的对象 prop
要定义或修改的属性的名称
descriptor
:描述对象,将被定义或修改的属性·(prop)·描述符;
-
数据描述对象
configurable
: 是否可以删除目标属性。默认为false
enumerable
: 此属性是否可以被枚举(遍历)。默认为false
value
: 该属性对应的值,默认为 undefined
writable
: 属性的值是否可以被重写。默认为false
-
存取器描述(getter、setter)
getter
:是一种获得属性值的方法(当获取值的时候触发的方法);
setter
:是一种设置属性值的方法(设置值的时候触发的方法)
可以写configurable
、enumerable
不能写value
、writable
注意:这个方法可以新添加属性也可以对原有的属性进行配置;
setter
方法中,在设置值时,会传一个参数newValue
,这个是当前改变的值,newValue
是跟着设置的值而改变的;
给一个对象定义属性的小例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>对象定义属性</title>
</head>
<body>
<script>
/*
普通定义方法
以下添加的属性默认特性:可删除,可改写,可遍历
*/
let obj ={a:1};
obj.b = 10;//给obj这个对象添加一个b属性,并赋值一个10
console.log(obj);//{a:1,b:10}
/*
Object.defineProperty(obj,property,描述对象/访问器(存取器(getter、setter)))定义方法
通过描述对象中的属性,可以设置添加的属性,是否可以删除,改写,遍历等等一系列的控制;
第三个参数是一个对象,里面可以是描述对象,也可以是存取器(getter和setter)
*/
//以下为描述对象例子
let obj2 = {a:1};
Object.defineProperty(obj2,'b',{
value:'我是b属性的值',
//这里只是简单写个小实例
writable:true // 属性是否可以被改写,默认是false为不可以,这里设置成true
});
//改写前
console.log(obj2);//{a:1,b:'我是b属性的值'}
obj2.b = 99;//这里进行改写
//改写后
console.log(obj2);//{a:1,b:'99'}
//以下为存取器例子
let obj3 = {a:1};
let val = 1000; //这个变量就是设置成了动态的,一般常用的都是这种动态的
Object.defineProperty(obj3,'b',{
get(){//获取时触发的函数
console.log('获取了');
return val; //返回的就是要获取的值
},
set(newValue){//设置值时触发的函数
console.log('设置了');
console.log(newValue);
val = newValue; //把改变的值赋值给指定变量
}
});
obj3.b //这里触发的就是get中的函数
obj3.b = 100 //这里触发的就是set中的函数,newValue就是100,
</script>
</body>
</html>
简单的数据劫持小例子:
- object.defineProperty劫持:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用object.defineProperty劫持</title>
</head>
<body>
<button id="btn">改变改变</button>
<div id="d1"></div>
<script>
//获取元素
let btn = document.querySelector('#btn');
let d1 = document.querySelector('#d1');
//准备数据
let data ={
title:'我是初始值'
};
//对要改变的数据进行劫持
observer(data);
//初始化赋值
d1.innerHTML = data.title;
btn.onclick = function(){
//按钮点击后,改变数据中的值,这时,通过上面的数据劫持函数中的处理,
//就可以直接改变页面中的数据了;
data.title = "改变了";
};
//用来劫持数据
function observer(data){
//循环data数据中的每一个属性,再在每一个属性中添加get和set方法
// Object.keys()返回的是对象所有属性的数组
Object.keys(data).forEach(function(item){
definedReactive(data,item,data[item])
});
}
/*
给数据中的属性添加get set方法
obj:要添加属性的对象
property:要操作的属性
value:要操作的值
*/
function definedReactive(obj,property,value){
Object.defineProperty(obj,property,{
get(){
//在获取值函数中,直接返回要显示的值
return value;
},
set(newValue){
value = newValue;//将最新的值,赋值给value
//这里的d1.innerHTML是直接写死了,正常开发这里应该是动态的
d1.innerHTML = newValue;
}
});
}
</script>
</body>
</html>
- proxy劫持:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>proxy劫持</title>
</head>
<body>
<button id="btn">设置改变</button>
<div id="d1"></div>
<script>
//获取元素
let btn = document.querySelector('#btn');
let d1 = document.querySelector('#d1');
//准备数据
let data = {
title:'我是初始值'
};
//数据劫持函数,这个函数会返回一个新的对象数据,要把返回的数据再赋值给data数据中
data = observer(data);
//初始化div显示的数据
d1.innerHTML = data.title;
btn.onclick = function(){
data.title = '改变了'
};
function observer(data){
/*
这里使用ES6中的proxy,对象代理,会返回一个新的对象
新对象中会包括被代理对象中的所有属性与值,再进行其它的操作;
比起之前使用Object.defineProperty省略了循环对象属性那一步,
这个方法,只要代理了,就会自动得到所有属性不需要循环;
Proxy(obj,{})
obj:要代理的对象;{}:这里面写要实现的代理方法
*/
let newData = new Proxy(data,{
//这里需要给每一个属性都添加get和set方法
/*
get(target,key):拦截(代理)对象属性的读取
target:就是obj这个原始数据中所有的数据;
key:数据对象中的key名,下面的monitor调用哪个属性,
*/
get(target,key){
//target就是data的数据
return target[key]
},
/*
set(target,key,value):拦截对象设置属性
target:原始数据对象; key:要修改的属性;value:要修改的值;
*/
set(target,key,value){
//把要改变的值赋值给对应的属性的值
target[key] = value;
//这里也是写死的,正常开发d1.innerHTML是动态的变量
d1.innerHTML = value;
}
});
//proxy返回的是一个新的对象,返回的对象就是新的数据对象,所以最后把这个对象返回出去
return newData;
}
</script>
</body>
</html>
响应的数据变化
data
对象中的数据都会被转换为getter/setter
,所以当数据发生变化时,自动更新在页面中;
如果没有在vue对象的
data`属性中初始化变量的值,那么这个变量就不是响应的(不能检测属性的变化)
-
Vue.set( target, key, value )
设置对象的属性。如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新。这个方法主要用于避开Vue
不能检测属性被添加的限制 - 参数说明:
target
:要添加属性的对象;
key
:要添加的属性名;
value
:要添加的属性值;
这个方法返回的是设置的值; -
vm.$set( target, key, value )
:实例上的方法 - 参数说明:与上面的
Vue.set()
方法参数一样; - 替换对象
Object.assign()
注意:注意对象不能是Vue
实例,或者Vue
实例的根数据对象;
数组的变异方法
在vue
实例中,可以使用this.
的方式直接调取数据的一些常用方法,如:
push()、pop()、shift()、unshift()、splice()、sort()、reverse()
-
以上这些变异方法的注意事项:
1.都可以改变原数组,因为这些方法vue
做为了变异方法,其它的数组原有方法都没有变异;
2.不能使用下标改值,比如:
this.arr[0]=10; //这些写会没有效果,是不可以改的,如果想改变,可以使用splice()来改
;
比如:concat
这个方法是不可以改变原数组的,因为不是变异方法;
3.不能使用length改变数组,比如:
this.arr.length=1;//可以使用 splice()来改变
或者也可以通过重新改变数组;
*注意:这些方法都是vue
变异的方法,不是原生的;都是vue
改写过的,为了更方便的使用。所以vue
提供了观察数组的变异方法,使用这些方法将会触发视图更新;
计算属性
- 计算属性定义在
computed
中,它不是方法,属性的值是函数的返回值; - 把对处理数据的逻辑抽离在计算属性中,使得模板更加轻量易读;
- 计算属性的值会被缓存,并根据依赖的数据变化而重新计算;
计算属性语法
key:function(){}
//key的值是函数的返回值,内部调用这个函数;
//所以计算属性会被放在实例上,通过this.可以找到对应的计算属性;
//计算属性中的this都指向实例
计算属性与methods的区别
- 计算属性只有在数据改变了才会执行,
methods
只要调用就执行; - 数据如果频繁更新改变,使用
methods
就比较合适;反之使用计算属性;
计算属性的使用
- 取值: 触发
get
函数,默认设置的函数 - 赋值: 触发
set
函数
watch观察
利用watch
观察 Vue
实例上的数据变动,键是需要观察的表达式,值是对应回调函数
- 回调函数会接受两个参数:
newValue:
变化的最新值
oldValue:
变化之前的值
具体写法:
//效果是:点击按钮,message值变成123
<input type="button" value="我要变变变" @click="message=123"/>
<p>{{message}}</p>
let vm = new Vue({
el:'.app',
data:{
message:'hello word!',//单层数据
name:{//多层数据
zhangsan:{
age:12
}
}
}
});
/*
通过watch可以监控到data数据中属性改变的值;
vm对象下直接就有$watch()的方法,传两个参数;
第一个参数:要改变的属性名(或者是路径);
说明:直接写属性名相当于数据只有一层,当遇到多层数据,比如对象里面套对象这类时,就需要写路径了;
第二个参数:数据改变后监控到值的回调;
在这个回调函数中传两个参数newvalue,oldvalue:
顾名思义,一个是改变后的新值,一个是改变前的旧值;
*/
vm.$watch('message',function(newvalue,oldvalue){ //这是单层数据监控
console.log('改变了')
}');
vm.$watch('name.zhangsan.age',function(newvalue,oldvalue){ //这是多层数据监控,针对性的监控某一个值
console.log('改变了')
}');
//总结:总的来说,以上多层监控时,哪个属性改变监控哪个。
/*
一般实际项目中,watch都是写到new Vue里面的,直接写成
wacth:{
message(newvalue,oldvalue){ //单层监控
console.log('改变了')
},
'name.zhangsan.age'(newvalue,oldvalue){//多层监控
console.log('改变了')
}
}
*/
- 深度监控
handler(){}
deep:true
//true就是表示深度监控
注意:由于watch
在监控时,会遍历对象中的所有属于,而遍历是非常耗费性能的,所以不易监控太深的数据,在使用要特别的注意选择;
具体写法
let vm = new Vue({
el:'.app',
data:{
name:{//多层数据
zhangsan:{
age:12
}
}
},
watch:{ //这里要监控name这个对象的改变,只要name里面的任何数据有了改变,就要监控到;
//这种写法在页面第一次加载时,不会自动调用下面的监控
'name':{
handler(newvalue,oldvalue){
console.log('有属性发生改变')
},
deep:true
}
//下面这种写法是立即调用,就是在页面第一次加载时,就调用监控,当真正发生改变时,依旧会执行
'name':{
handler(newvalue,oldvalue){
console.log(''改变了)
},
//可以联着写,既可以加载就执行,也可以深度监控
immediate:true,
deep:true
}
}
});
methods观察
定义一些方法,将被混入到 Vue
实例中
- 使用method
可以直接通过VM
实例访问这些方法,或者在指令表达式中使用,方法中的this
自动绑定为Vue
实例
组件化开发
组件可以将UI页面切分成一些单独的、独立的单元,整个页面就是这些单元组合而成的,只需要关注构建每个单独的单元,每个组件包含自己的结构、逻辑和样式,这样既减少了逻辑复杂度,又能实现组件的复用性。当不需要某个组件或替换组件时,而不影响整个应用的运行。
- 组件开发的好处:
降低耦合度、提高可维护性、可复用性便与协作开发;
全局注册组件
- 分为三步骤:创建组件构造器、注册组件和使用组件
-
Vue.extend
(选项对象)//当页面中需要多个vue实例时,就可以使用extend来实现 <div id='app'>{{message}}</div> <div id='index'>{{message}}</div> //写法与new Vue一样,对象中使用的方法也是全部都一样,除了不可以写el; //子构造器 let ex = Vue.extend({ data(){ return { message:'hello extend' } } }); //这里的ex返回的是一个实例,所以想要调用$mount方法必须要new //执行方法:extend必须要使用$mount()来实现挂载; new ex().$mount('#index') let vm = new Vue({ data(){ return { message:'hello extend' } } }); vm.$mount('#app'); //手动挂载
Vue.extend():返回构造器实例
- 和根实例的选项对象一样,不同的是没有
el
; -
data
必须是一个工厂方法,返回对象; -
template
定义组件的模板; - 通过
extend
来实现一个组件
组件说明:首先组件就是一个函数;
<div id='app'>
//组件是以标签的形式执行的,也可以把注册好的组件看成是自定义的标签
<custom-ex></custom-ex>
</div>
//创建一个组件的函数
let ex = Vue.extend({
data(){
return {
message:'hello extend'
}
},
template:`
//注意这里的message不是new Vue里面的,是extend子构造器中的,与Vue完全是独立的。
<div>{{message}}</div>
`
});
//将上面的函数注册成一个组件,这里使用的方法是注册一个全局组件,传两个参数,第一个参数是组件的名字(id名),第二个参数是组件的函数(可以直接是函数,也可以是函数名)
Vue.component('custom-ex',ex);
let vm = new Vue({
el:'#app',
data(){
return {
message:'hello extend'
}
}
});
全局注册组件
Vue.component( id, [definition] )
id:
字符串
definition:
构造器实例或对象
如果为对象,内部会自动调用 Vue.extend
在Vue
实例范围内使用注册组件的id
为标签名
全局组件的定义与注册
说明:下面案例通过Vue.component
('组件名',{选项对象})可以直接定义组件,第二个参数直接提供一个对象,vue
内部会自动调用Vue.extend()
,然后把这个返回的对象传到第二个参数中;
组件的选项对象没有el
属性;
<div id="app">
<custom-ex></custom-ex>
</div>
//这样就是一个简单的组件,注册组件里面的template必须要加上,否则会报错;
Vue.component('custom-ex',{
template:`<div>我是组件</div>`
});
let vm = new Vue({
el:'#app'
});
局部注册组件
- 选项对象的
components
属性注册局部组件,只能在所注册的作用域模板中使用;
{components:{组件id:实例构造器 | 对象 } }
局部组件就是在哪里定义,在哪里使用;
局部组件写法
<div id="app">
<custom-cc></custom-cc>
</div>
<script>
let vm = new Vue({
el:'#app',
components:{
//这个就是局部组件,局部组件的作用域只在new Vue的这个模板的范围中使用,
//不可以用在其它地方,比如放到其它组件中是不可以的,会报错;
'costom-cc':{
template:`<div>我是局部组件</div>`
}
}
});
</script>
-
组件中data的注意
-
data
必须是一个函数,函数中返回一个新对象,如果是对象形式会警告必须是函数; - 这样做防止组件引用同一个对象,导致数据相互影响;
-
html
模板
放在一对template
标签中,在组件中使用标签的id
放在一对script
标签中,标签的type
要设置为text/x-template
组件命名约定和模板
- 注册组件命名:
kebab-case
(短横线分隔命名)
camelCase
(驼峰式命名)
PascalCase
(单词首字母大写命名)
不能使用html
规定的标签名 - 使用组件命名:
kebab-case
(短横线分隔命名)
prop
prop
上的属性都会放在当前组件的实例上面
props:['test']
,可以是多个,也可以是对象形式的;
prorp的验证引用官网API:
Vue.component('my-component', {
props: {
// 基础的类型检查 (null
匹配任何类型)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组且一定会从一个工厂函数返回默认值
default: function () {
return { message: 'hello' }
} },// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
一个简单的组件小练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>news</title>
</head>
<body>
<div id="app">
<!--循环新闻数据,将对应数据传到组件中-->
<news-cutom v-for="item,index in newsData" :data="item"></news-cutom>
</div>
<script src="../vue.js"></script>
<script>
/*
注册组件,这里使用的是全局的
*/
Vue.component('news-cutom',{
/*组件通过props接收*/
props:['data'],
/*将接收的数据显示到页面中*/
template:`
<div>
<h1>{{data.title}}</h1>
<ul>
<li v-for="item,index in data.news">{{item}}</li>
</ul>
</div>`
});
//新闻数据
let newsList = [
{title:'娱乐',news:['娱乐新闻1','娱乐新闻2','娱乐新闻3']},
{title:'体育',news:['体育新闻1','体育新闻2','体育新闻3']},
{title:'视频',news:['视频新闻1','视频新闻2','视频新闻3']}
];
let vm = new Vue({
el:'#app',
data:{
newsData:newsList
}
});
</script>
</body>
</html>
父子组件之间的通信
使用在一个组件模板中的组件,称之为子组件。
- 组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据,采用单向绑定的方式,避免子组件直接修改父组件产生副作用;
- 但是有时候需要父子组件之间需要相互通信:父组件可能要给子组件传递数据,子组件则可能要将它内部发生的事情告知父组件。
- 通过
props
将父组件的数据传递给子组件; - 触发子组件的某个事件时,可以通知到父组件,那么在子组件中发布一个自定义事件,父组件监听自定义事件;
- 组件实例
.$emit
(自定义事件名,参数);
父组件传递数据示意图
子组件发布事件示意图
三层组件之间的通信(父传子,子传父)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>多层组件通信练习</title>
</head>
<body>
<!--下面写一个三层组件之间的通信(父传子,子传父)-->
<div id="app">
<h2>我是根组件</h2>
{{vmData}}
<!--将根组件的数据传给第2层,再传给第3层,数据显示在第3层的组件中-->
<coustom1 :coudata1="vmData"></coustom1>
</div>
<script src="../vue.js"></script>
<script>
/*
根组件传过来的数据,接收也是一层一层接
使用props接收,先第2层接收,再第3层接收然后显示到第3层
*/
Vue.component('coustom1',{
props:['coudata1'],
template:`
<div>
<h2>我是第2层组件</h2>
<p>第2层组件收到的数据:{{coudata1}}</p>
<span>下面是第3层组件</span>
<coustom2 :coudata2="coudata1" @changeFn="changeFn"></coustom2>
</div>
`,
//通过自定义事件的方法,实现子组件向父组件通信
methods:{
//父组件中写自定义的事件,如果有参数传过来,这里可以直接接收传过来的参数
//从页实现父组件收到子组件的值
changeFn(){
console.log('监听到了第3层组件');
}
}
});
/*
这里接收第2层传过来的cou1Data的数据
*/
Vue.component('coustom2',{
props:['coudata2'],
template:`
<div>
<h2>我是第3层组件</h2>
<p>第3层组件收到的数据:{{coudata2}}</p>
</div>
`,
mounted(){
//父组件使用$emit方法传值,第二个参数传需要传的值,
// 这里测试没有用到参数
this.$emit('changeFn')
}
});
//vm就是所有组件中的父级,也就是根组件
let vm = new Vue({
el:'#app',
data:{
vmData:'我是根组件的数据'
}
});
</script>
</body>
</html>
多层组件嵌套之间通信
方法有很多种:
- 1.组件由外到内或者由内到外层层的传数据,这样做,如果是较少的几层,还可以使用,当遇到多层,十层二十层时这个时候就会显的非常的繁琐,而且很容易出错;
- 2.当遇到非常多层,并且好多组件的数据是相互引用的,这时就可以使用一个叫
VueX
(状态管理器)来处理这种情况; - 3.当遇到第三层组件直接向第一层组件通信时,可以用一个叫做事件总线
(event-bus)
的东西的来解决;
//事件总线使用方法:
let vm = new Vue();
//监听event事件
vm.$on('event',function(){});
//发布这个事件
vm.$emit('event')
组件间的改值
父组件可以通过props
向子组件传,但是子组件不可以向父组件传,这里就有一个单向数据流的概念;在组件中在使用props
只是从外向内传;
如何子组件修改父组件的数据呢?
解决方法两种:.sync修饰符
与v-model
;
-
sync方法
.sync修饰符
:在2.0被移除后,发现还是需要双向绑定这种需求的存在,所以从2.3.0重新引入后,就作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的v-on监听器。
.sync
具体使用案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>.sync练习</title>
</head>
<body>
<div id="app">
<h1>父级:{{message}}</h1>
<!--.sync是编译时的语法糖-->
<costum :title.sync="message"></costum>
<!--
下面是编译的写法,这里是自定义事件,名字自己取;
解释:动态绑定了要改变的数据message,再监听自定义事件,并传参数val,将val赋值给要改变的数据message
-->
<costum :title="message" @customFn="val => message = val"></costum>
</div>
<script src="../vue.js"></script>
<script>
/*
总结sync使用步骤:
1.在<costum>组件标签中绑定并监听要改变的数据message,写法::title.sync="message";
2.将组件标签中的title通过props传到组件中;
3.给组件中的按钮加点击事件;
4.在点击事件中,通过this.$emit('update:title','子级改变了父级!!!'),来更新同步改变的值;
注意:在监听.sync的自定义事件中,第一个参数是固定写法:update:要同步的属性名;
第二个参数,要改变的值,可以是动态也可以写死
*/
Vue.component('costum',{
props:{
title:{
type:String,
default:'子级的数据'
}
},
template:`<div><button @click="changeFn">点击改变父级数据</button></div>`,
methods:{
changeFn(){
this.$emit('update:title','子级改变了父级!!!');
}
}
});
new Vue({
el:'#app',
data:{
message:'父级的数据'
}
});
</script>
</body>
</html>
-
v-model方法
v-model
:自定义事件可以用来创建自定义的表单输入组件,使用v-model
来进行数据双向绑定,它也是编译的语法糖;
在组件上使用v-model
在子组件中使用value
来接收v-model
的值;再调用emit监听来监听input
来改变对应的值;
那么问题来了,如果当需要的这个value
名被组件占用,或者emit
中要监听的自定义事件input
也被占用,怎么办?
解决办法:
Vue.component('custom',{
//在组件中的model对象中,可以改变v-model接收值的名字。
//这里相当于改了个别名
model:{
prop:'modelTest', //要改变的属性名
event:'testFn' //要监听的事件名
}
});
v-model
具体使用案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>v-model练习</title>
</head>
<body>
<div id="app">
<h1>父级:{{message}}</h1>
<!--v-model是编译的语法糖-->
<custom v-model="message"></custom>
<!--
编译写法
原理:动态绑定要改变的数据message,监听input事件,并将value值赋值给要改变的message
-->
<custom :vlaue="message" @input="value=>message=value"></custom>
</div>
<script src="../vue.js"></script>
<script>
/*
v-model使用步骤:
1.直接在子组件标签中写v-model='要改变的数据名';
2.在子组件按钮中添加点击事件;
3.在点击事件中,使用this.$emit监听input事件,并输入要改变的数据;
这里注意,在v-model的监听中,第一个参数要输入input,第二个参数输入要改变的数据;
*/
Vue.component('custom',{
props:{
title:{
type:String,
default:'子级的数据'
}
},
template:`<div>{{title}} <p><button @click="changeFn">点击改变父数据</button></p></div>`,
methods:{
changeFn(){
this.$emit('input','子级改变了父级~~~~');
}
}
});
new Vue({
el:'#app',
data:{
message:'父级的数据'
}
});
</script>
</body>
</html>
使用插槽分发内容
-
编译作用域
父组件模板的内容在父组件作用域内编译;
子组件模板的内容在子组件作用域内编译; -
插槽的作用
将父组件中写在子组件一对标签内的结构混合在子组件模板中,这个过程称之为内容分发。使用特殊的<slot>
元素作为原始内容的插槽; -
单个插槽(无命名插槽)
如果子组件中没有一对slot
标签,写在子组件标签对的内容会被丢弃;
子组件中有slot
标签,子组件标签对的内容会整体替换在slot
标签位置;
如:<slot></slot> //这个就是默认的插槽,只要组件标签中写内容都会替换slot标签中的内容
slot
标签内的内容被视作备用内容; -
具名插槽(有命名的插槽)
可以使用name
来配置如何分发内容;
没有name
的slot
被视为默认插槽;
如:
//有名字插槽的使用方法
<div id="app">
<custom>
<!--替换指定名字,就是用slot="对应名字"-->
<p slot="list">我是要替换的内容p标签</p>
</custom>
</div>
<slot name="list"><span>我是组件中默认内容<span></slot> //当有名字时,就可以指定替换某个名字插槽的内容
-
批量替换插槽
如:
<div id="app">
<custom>
<!--使用template的方法如下-->
<template slot="list">
<p>123</p>
<p>123</p>
<p>123</p>
</template>
</custom>
</div>
/*当需要替换li时,就可以使用组件中提供的
template标签
*/
<slot name="list">
<ul>
<li>默认的</li>
<li>默认的</li>
</ul>
</slot>
-
作用域插槽(slot-scope)
说明:2.1.0新增的,在这个版本中只能局限于<template>
标签上使用,在2.5.0+ 版本后,能在任意元素或组件中使用了;
作用:可以在父组件中显示子组件的数据;
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>slot-scope使用练习</title>
</head>
<body>
<div id="app">
{{ms1}}
<hr>
<costum>
<!--slot-scope="props":数据都存在props中,得到的是子组件数据的对象-->
<ul slot-scope="props">
<li>{{props.msg1}}</li>
<li>{{props.msg2}}</li>
</ul>
</costum>
</div>
<script src="../vue.js"></script>
<script>
//注册子组件
Vue.component('costum',{
data(){
return {
cosData:'我是子组件的数据'
}
},
/*
:msg1="cosData" :msg1属性绑定的子组件data中的动态数据;
msg2="子组件的属性":msg2属性绑定的是写死的数据;
只要在slot标签中写key=value格式的值,都会存到props中,保存的是对象格式,
所以上面就可以使用slot-scope标签中使用props去获取子组件中相应的值;
*/
template:`<div><slot :msg1="cosData" msg2="子组件的属性">{{cosData}}</slot></div>`
});
let vm =new Vue({
el:'#app',
data:{
ms1:'我是父组件的数据'
}
});
</script>
</body>
</html>
使用插槽分发内容示意图
封装通用的组件
说明:以下三个所需要的API不是绝对的,具体还要看实现的业务需求;
- Vue 组件的 API 来自三部分——
prop
、事件和插槽
-
Prop
允许外部环境传递数据给组件; - 事件允许从组件内触发通过外面;
-
slot
插槽允许外部环境将额外的内容组合在组件中;
封装Modal组件
1.组件API
- 设置的
props:
modalTitle
提醒信息 默认为 '这是一个模态框';
show
控制显示隐藏,需要双向绑定; - 定制模板:
slot
为modal-content
定制提醒信息模板;
slot
为modal-footer
定制底部模板; - 监控内部变化:
事件名on-ok:
点击确定触发
事件名on-cancel:
点击取消触发
2.组件代码
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<style>
p,h4{
margin:0;
}
.model{
width: 500px;
background-color: #fff;
border: 1px solid rgba(0,0,0,.2);
border-radius: 6px;
box-shadow: 0 3px 9px rgba(0,0,0,.5);
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
z-index: 111;
}
.model-header {
padding: 15px;
border-bottom: 1px solid #e5e5e5;
}
.model-content div {
padding: 20px;
}
.model-footer {
padding: 15px;
text-align: right;
border-top: 1px solid #e5e5e5;
}
.btn {
padding: 5px 15px;
}
.mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(55,55,55,.6);
height: 100%;
z-index: 100;
}
</style>
</head>
<body>
<!--
弹窗的组件,一般弹窗都是渲染在body下,那么这时就需要操作底层的html(model组件结构),
在页面渲染时放在body下,这就需要用到的自定义指令;
-->
<div id="app">
<button @click="show=true">显示弹窗</button>
<!--使用关心的内部事件-->
<!--
v-model="show": 这里使用了v-model的方法来实现数据的双向绑定,
让子组件可以改变show这个变量
v-transform-body:自定义指令,作用将组件插入到body中,需要注意的是,自定义指令也是可以根据对应的值来判断是否要执行,默认是true;
-->
<model ok-value="OK" cancel-value="不要"
@gogo="okChangeFn" @nono="cancelChangeFn"
v-model="show" v-transform-body>
<!--header部分-->
<h1 slot="header">我是一个超大的标题</h1>
<!--这里就是改变弹框的context部分-->
<template slot="content">
<p>报名器</p>
<form action="">
<p>姓名:<input type="text"></p>
</form>
</template>
</model>
</div>
<script src="../vue.js"></script>
<script>
/*
model是一个通用的弹框组件,需要以下这些内容:
1.定制数据(数据都写在props中)
title:标题数据
okValue:确认按钮
cancelValue:取消按钮
2.关心的内部事件(组件对外发布相关事件)
okValue的点击事件
cancelValue的点击事件
3.定制结构(在组件中的template属性中,通过slot实现)
content:内容结构
下面就根据这些要求来写这个组件
*/
Vue.component('model',{
//定制的数据都是放在props中
props:{
title:{
type:String,
default:'默认标题'
},
okValue:{
type:String,
default:'确认'
},
cancelValue:{
type:String,
default:'取消'
},
value:{//显示隐藏model框的标识
type:Boolean,
default:false
}
},
/*
1.将写好的props中的数据,都绑定到组件结构中,就可以在组件标题上自定义数据了
2.在methods中发布确认和取消按钮的事件,事件名为:gogo,nono
3.定制header、content、footer三部分的结构,使用slot标签来表示可以更改
v-show="value":这个是用来隐藏弹窗的
v-model是用value来接收,所以props中要写value
*/
template:`
<div class="model-example" v-show="value">
<div class="mask"></div>
<div class="model">
<div class="model-header">
<slot name="header"><h4>{{title}}</h4></slot>
</div>
<div class="model-content">
<slot name="content">在这里添加内容</slot>
</div>
<div class="model-footer">
<slot name="footer">
<input class="btn" type="button" :value="okValue" @click="okFn"/>
<input class="btn" type="button" :value="cancelValue" @click="cancelFn"/>
</slot>
</div>
</div>
</div>
`,
methods:{
/*在组件中发布关心的按钮事件*/
okFn(){
this.$emit('gogo');//确认按钮事件
//改变父组件传来的show变量的值,让弹窗隐藏
this.$emit('input',false)
},
cancelFn(){
this.$emit('nono');//取消按钮事件
//改变父组件传来的show变量的值,让弹窗隐藏
this.$emit('input',false)
}
}
});
/*
model框默认是隐藏的,当点击按钮或者触发一个其它事件的时候才会显示出来;
逻辑:
1.在父级组件中定义一个show的变量,默认为false隐藏
2.当触发某个事件时,由子级组件改变这个变量来实现显示与隐藏;
那么这里涉及到了数据的双向绑定,可以使用.sync或v-model来实现;
*/
let vm = new Vue({
el:'#app',
data:{
show:false //显示、隐藏的标识
},
methods:{
/*父级这里写要自定义的行为*/
okChangeFn(){
//这里可以写任何点击确认按钮后,要做的事情
//alert('测试,我是改变的确认按钮');
},
cancelChangeFn(){
//alert('测试,我是改变的取消按钮');
}
},
//创建一个自定义指令
directives:{
transformBody:{
inserted(el){
//将元素插入到body中
document.body.appendChild(el)
}
}
}
});
</script>
</body>
</html>
生命周期函数
一个组件从开始到最后消亡所经历的各种状态,就是一个组件的生命周期。Vue
在执行过程中会自动调用生命周期钩子函数,只需要提供这些钩子函数即可钩子函数的名称都是Vue
中规定好的;
- 创建阶段:
beforeCreate
:数据劫持之前被调用,无法访问methods,data,computed
上的方法或数据;
created
:实例已经创建完成之后被调用。但挂载阶段还没开始,$el
属性目前不可见。常用于ajax
发送请求获取数据; - 挂载阶段:
beforeMount
:在挂载开始之前被调用;
mounted
:vue
实例已经挂载到页面中,可以获取到el
中的DOM
元素,进行DOM
操作; - 更新阶段:
beforeUpdate
:更新数据之前调用;
updated
:组件 DOM 已经更新; - 销毁阶段:
beforeDestroy
:实例销毁之前调用。在这一步,实例仍然完全可用;
destroyed
:Vue
实例销毁后调用;
小练习
一.数据驱动的选项卡小练习:
/*
知识要点:
1.控制class:
v-bind:class="{class名字:表达式}",根据表达式的布尔值,决定这个元素是否要添加这个class,true 添加,false不添加;
2.控制style:
v-bind:style="{display:这里可以写三元运算}" ;
当然在控制style中可以同时添加多个属性,使用逗号隔开,带单位的正常加上单位;
说明:
这个案例主要通过创建变量itemIndex,在按钮点击时,改变itemIndex的值。
通过改变itemIndex的值来判断按钮与选项块的显示与隐藏, 从而实现数据驱动选项卡;
*/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>选项卡4</title>
<style>
#app div{width:200px;height:100px;background: #f00;}
.cur{background:#ff0;}
.fn-hide{display: block}
</style>
</head>
<body>
<div id="app">
<!--当前点击的index等于itemIndex就加上cur这个class-->
<button
v-for="item,index in data"
:class="{cur:itemIndex===index}"
@click="itemFn(index)">{{item.title}}
</button>
<!--判断当前index等于itemIndex,display就为block,否则就是none-->
<div v-for="item,index in data"
:style="{display:itemIndex===index ? 'block' : 'none'}">
<p v-for="item,index in item.newListS">{{item.list}}</p>
</div>
</div>
<script src="../vue.js"></script>
<script>
//准备好要显示的数据
let data = [
{title:'新闻',newListS:[{list:'新闻1'},{list:'新闻2'},{list:'新闻3'}]},
{title:'娱乐',newListS:[{list:'娱乐1'},{list:'娱乐2'},{list:'娱乐3'}]},
{title:'体育',newListS:[{list:'体育1'},{list:'体育2'},{list:'体育3'}]}
];
let vm = new Vue({
el:'#app',
data:{
data,
itemIndex:0 //用于储存当前按钮的下标
},
methods:{
itemFn(index){
//在按钮点击时,将当前传过来的下标值,赋值给itemIndex变量
this.itemIndex = index;
}
}
});
</script>
</body>
</html>
二.好友列表小练习
//好友列表大致思路和选项卡是相同的,不同的是里面会有列表收回的交互;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>好友列表</title>
<style>
body,div,h2,ul,li{padding:0;margin:0;list-style: none}
[v-cloak]{display: none}
#app {width:200px; margin: 100px auto 0}
#app div{margin-bottom: 3px;}
h2{text-align:center;background: #ff6700;color:#fff;}
h2:hover{cursor: pointer}
ul{background: #96c6d7;text-align: center;display: none;}
ul li{height:30px;}
.cur{background:#c6d796}
</style>
</head>
<body>
<div id="app" v-cloak>
<div v-for="titleItem,index in nameData">
<h2 @click="titleItemFn(index)"
:class="{cur:showIndex===index}">{{titleItem.title}}</h2>
<!--判断ul是否显示-->
<ul :style="{display:showIndex===index ? 'block' : 'none'}">
<li v-for="nameItem,index2 in titleItem.qqNameS">{{nameItem.name}}</li>
</ul>
</div>
</div>
<script src="../../vue.js"></script>
<script>
let data =[
{title:'我的好友',qqNameS:[{name:'好友1'},{name:'好友2'},{name:'好友3'}]},
{title:'我的家人',qqNameS:[{name:'家人1'},{name:'家人2'},{name:'家人3'}]},
{title:'黑名单',qqNameS:[{name:'黑名单1'},{name:'黑名单2'},{name:'黑名单3'}]}
];
let vm = new Vue({
el:'#app',
data:{
nameData:data,
showIndex:-1 //为-1表示ul不显示出来
},
methods:{
titleItemFn(i){
//判断如果点击的元素下标与showIndex相等,那就表示下拉列表是已经显示出来的。
if(this.showIndex === i){
//这时就需要把showIndex设置为-1,这样下拉列表就会隐藏回去
this.showIndex = -1;
}else{
//否则就是没显示出来的,就将当前点击的元素下标,赋值给showIndex变量
this.showIndex = i;
}
}
}
});
</script>
</body>
</html>
三.音乐全选与单选小练习
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
body,ul,li {margin: 0;padding: 0;list-style: none;}
.wrap{ width: 500px;
margin: 113px auto 0;
border: 10px solid #f00;}
.list-head li div {margin: 0;}
.list-head li {padding: 0 20px;}
.list li span {height: 100%;font: 14px/36px "微软雅黑";flex: auto;text-align: center;}
.list li div, .select .selectAll {float: left;height: 12px;position: relative;top: 11px;margin-right: 20px;font: 14px/12px arial;cursor: pointer;}
.list li {height: 36px;padding: 0 30px;box-sizing: border-box;color: #000000;display: flex;flex-direction: row;}
.list{ background: #ff7917;}
.list-body li:nth-child(even) {background: yellow;}
.list-body li:nth-child(odd) {background: #fff;}
.list-body li:hover {background: #0f0;}
.list-body li.checkedColor {background: #0f0;}
</style>
<script src="../../vue.js"></script>
</head>
<body>
<div class="wrap" id="app">
<div class="baidu">
<ul class="list list-head">
<li>
<div>
<input type="checkbox" v-model="isCheckedAll"/>全选
</div>
<span>歌单</span>
<span>歌手</span>
<span>专辑</span>
</li>
</ul>
<ul class="list list-body">
<!--checkedColor 选中样式-->
<li v-for="item,index in nameData">
<div><input type="checkbox" v-model="item.checked"></div>
<span>{{item.name}}</span>
<span>{{item.song}}</span>
<span>{{item.album}}</span>
</li>
</ul>
<div class="select">
<span class="selectAll">
<span>统计:</span>
</span>
<div class="others">
<span><em></em>歌手有:{{nameData.length}}位</span>
<span><em></em>专辑有{{albums}}张</span>
</div>
</div>
</div>
</div>
<script>
//数据中的checked用于标识是否选中
let data = [
{
id:Date.now()+Math.random(),
name: '萧亚轩',
song: '代言人',
checked: true,
album: 10
},
{
id:Date.now()+Math.random(),
name: '蔡依林',
song: '说爱你',
checked: false,
album: 10
},
{
id:Date.now()+Math.random(),
name: '王菲',
song: '红豆',
checked: false,
album: 10
},
{
id:Date.now()+Math.random(),
name: '周杰伦',
song: '等你下课',
checked: false,
album: 10
}
];
let vm = new Vue({
el:'#app',
data:{
nameData:data
},
computed:{
//isCheckedAll:是用来控制全选按钮的,所以需要有setter
isCheckedAll:{
get(){
//过滤所有checked值为true,并且为true的长度要与数据长度相等,就表示全部都选中了
// 简便写法
// return this.nameData.filter( item => item.checked ).length === this.nameData.length
return this.nameData.filter(item =>{
return item.checked===true
}).length === this.nameData.length;
},
set(newValue){
//set中的newValue是当前属性的值
//把这个值赋值给checked属性,就可以控制全选了。
return this.nameData.forEach(item=>item.checked = newValue)
}
},
albums(){//专辑总数
//这是传统的写法
/*let num = 0;
this.nameData.forEach(item=>{
num +=item.album;
});
return num;*/
//下面使用reduce方法来写
return this.nameData.reduce((item1,item2)=>{
return {
n:item1.n + item2.album
}
},{n:0}).n;
/*
使用说明:
reduce传一个函数参数,可以说是一个累加器,通过str1与str2相加,可以得到累计的数
参数说明:
reduce,会传入两个参数,第一个参数是函数,函数中有两个参数,分别是初始值str1,从数组中拿的第一个值str2;
第二个参数(n),是设置的初始值,这里输入几就是从几开始;初始值可以写成一个对象格式,也可以是一个数字;
返回的是计算后的数;
语法:
//普通数字写法
let arr = [1,2,3,4]
arr.reduce(function(str1,str2){
return str1+str2
},n);
//数组中是对象格式的写法
var o1 = [{n:1,m:2},{n:2,m:3}];
o1.reduce(function(str1,str2){
return { //这里可以返回一个对象
n: item1.n + item2.n,
m: item1.m + item2.m,
}
},{n:0,m:1});//这里的初始值参数也可以是一个对象
*/
}
}
});
</script>
</body>
</html>
四.商品筛选小练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品筛选</title>
<style type="text/css">
body {
font-size: 14px;
font-family: "Lantinghei SC Extralight",Arial;
}
ul {
padding: 0;
margin: 0;
list-style: none;
}
a {
text-decoration: none;
}
img {
vertical-align: top;
}
#wrap {
width: 788px;
height: 260px;
margin: 100px auto;
padding: 50px 120px 175px;
background: #c6d796;
}
#section {
width: 788px;
height: 260px;
-moz-box-shadow: 0 0 4px rgba(0,0,0,.2);
box-shadow: 0 0 4px rgba(0,0,0,.2);
}
#choose {
width: 788px;
height: 50px;
margin: 0 auto;
background: #a6d796;
line-height: 50px;
text-indent: 21px;
}
#type {
height: 210px;
background: #718c00;
padding: 17px 0 16px 28px;
}
#type li {
height: 44px;
color: #4d4d4d;
line-height: 44px;
}
#type a {
margin: 0 12px 0 11px;
color: #000;
}
#choose mark {
position: relative;
display: inline-block;
height: 24px;
line-height: 24px;
border: 1px solid #28a5c4;
color: #28a5c4;
margin: 12px 5px 0;
background: none;
padding: 0 30px 0 6px;
text-indent: 0;
}
#choose mark a {
position: absolute;
top: 3px;
right: 2px;
display: inline-block;
width: 18px;
height: 18px;
background: #28a5c4;
color: #fff;
line-height: 18px;
font-size: 16px;
text-align: center;
}
.active {
color:#0000ff !important
}
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<!--
本页功能描述:
1.选择手机商品的4种参数,并显示到'你的选择'中,并有选中后的样式;
2.每种品牌手机的参数,只能选择一个(4行参数分别是单选);
3.在'你的选择'中显示的信息,必须要按照品牌--网络这样的顺序,不可以改变顺序;
4.即使删除其中的参数,也必须要按照要求3中的顺序;
-->
<div id="wrap" v-cloak>
<h1>随便调随便选,不要钱!!!</h1>
<section id="section">
<nav id="choose">
你的选择:
<!--循环goods中的数据-->
<mark v-for="goodsItem,key in goods">{{goodsItem}}
<!--delGoodFn():删除选中商品-->
<a href="javascript:;" @click="delGoodFn(key)">x</a>
</mark>
</nav>
<ul id="type">
<!--循环所有title的数据-->
<li v-for="listItem,listIndex in listData">
<span>{{listItem.title}}</span>
<!--
对每一个商品的操作说明:
1.循环names的数据;
2.选中商品时,需要加class为active;
3.给每一个商品加点击事件
-->
<a href="javascript:;"
v-for="namesItem,namesIndex in listItem.names"
:class="{active:listItem.titleIndex === namesIndex}"
@click="namesItemFn(listItem,namesIndex,namesItem,listIndex)"
>
{{namesItem}}
</a>
</li>
</ul>
</section>
</div>
<script src="js/vue.js"></script>
<script>
//准备好的商品数据
let titleData = [
{
title: '品牌',
names: ["苹果", "小米", "锤子", "魅族", "华为", "三星", "OPPO", "vivo", "乐视", "360", "中兴", "索尼"]
},
{
title: '尺寸',
names: ["3.0英寸以下", "3.0-3.9英寸", "4.0-4.5英寸", "4.6-4.9英寸", "5.0-5.5英寸", "6.0英寸以上"]
},
{
title: '系统',
names: ["安卓 ( Android )", "苹果 ( IOS )", "微软 ( WindowsPhone )", "无", "其他"]
},
{
title: '网络',
names: ["联通3G", "双卡单4G", "双卡双4G", "联通4G", "电信4G", "移动4G"]
}
];
let vm = new Vue({
el:'#wrap',
data:{
listData:titleData, //挂载商品数据
goods:{} //这里使用对象形式储存选择后的商品,显示到mark标签中。格式为{0:val,1:val...}
},
methods:{
/*
【添加商品方法】
参数说明:
listData:当前点击的商品数据对象
index:当前类型商品的索引
namesItem:当前点击的商品
listIndex:每一行的下标
*/
namesItemFn(listData,index,namesItem,listIndex){
//给每个商品对象中添加一个titleIndex属性,值是对应的索引;用于记录选中商品的位置
this.$set(listData,'titleIndex',index);
//给goods对象数据,格式为:key值放当前所有行的下标,value放当前点击的商品数据
//key值放当前商品所在行下标,目的是一行商品只能选一个,不可以多选,这样可以通过key的唯一性来实现单选
this.$set(this.goods,listIndex,namesItem);
},
/*
【删除商品方法】
key:当前商品下标,因为这里的goods存的key,就是当前商品的下标,所以这里直接使用Key值即可
*/
delGoodFn(key){
//根据当前删除商品的下标,删除goods对象中对应的商品
this.$delete(this.goods,key);
//这行代码处理当删除商品后,选中商品的样式要去掉
this.$set(this.listData[key],'titleIndex',-1);
}
}
});
</script>
</body>
</html>
五.todosList小练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>todos</title>
<link rel="stylesheet" type="text/css" href="css/base.css">
<link rel="stylesheet" type="text/css" href="css/index.css">
<style>
[v-cloak]{display: none}
</style>
</head>
<body>
<section class="todoapp" v-cloak>
<div>
<header class="header" >
<h1>todos</h1>
<input class="new-todo" placeholder="请输入内容"
@keyUp.13="addTodo()"
v-model="title"
/>
</header>
<section class="main" v-show="listData.length">
<!--全选按钮-->
<input class="toggle-all" type="checkbox" v-model="isCheckedAll"/>
<ul class="todo-list">
<!--
completed:选中的样式
editing:编辑中的样式
-->
<li :class="{completed:item.checked,editing:editIndex===index}"
v-for="item,index in filterList">
<div class="view">
<!--v-model="item.checked" 直接通过v-model绑定数据是否为选中-->
<input class="toggle" type="checkbox" v-model="item.checked"/>
<!--编辑todo-->
<label @dblclick="editTodo(index)">{{item.title}}</label>
<!--删除按钮-->
<button class="destroy" @click="delTodo(index)"></button>
</div>
<!--
编辑时要显示的输入框,需要将当前数据再显示到这个框中
ref="editDom" 给这个input加了一个名字,方法dom查找,这个是vue中提供的方法
@blur="editEnd(item)" :失去焦点完成编辑
@keyup.13="editEnd(item)":按回车也可以完成编辑
@keyup.esc="editCancel(item)":取消编辑,把对应的数据传过去
-->
<input class="edit" v-model="item.title" ref="editInput"
@blur="editEnd(item)" @keyup.13="editEnd(item)"
@keyup.esc="editCancel(item)"
/>
</li>
</ul>
</section>
<footer class="footer" v-show="listData.length">
<span class="todo-count">
<strong>{{checkedLen}}</strong>
<span>条未选中</span>
</span>
<ul class="filters">
<!--判断hash值是否与数据中的hash一样-->
<li><a href="#/all" :class="{selected:hash==='all'}">All</a></li>
<li><a href="#/active" :class="{selected:hash==='active'}">Active</a></li>
<li><a href="#/completed" :class="{selected:hash==='completed'}">Completed</a></li>
</ul>
</footer>
</div>
</section>
</body>
<script src="../vue.js"></script>
<script>
/*
1. 增删改查数据,要使用localStorage做数据持久化
2. 根据hash不同,过滤渲染的数据
a. 全部任务
b. 完成的任务
c. 未完成的任务
3. 自己设计数据结构
*/
//页面一加载先获取存储的todoData,没数据就返回一个空数组
let data = JSON.parse(localStorage.getItem('todoData')) || [];
//将三种hash值放到对象中过滤
let filterListData = {
all(list){//所有状态
return list;
},
active(list){//未选中
return list.filter(item => !item.checked);
},
completed(list){//已选中
return list.filter(item => item.checked);
}
};
let vm = new Vue({
el:".todoapp",
data: {
listData: data,
title:'',//内容变量
editIndex:-1, //编辑数据的下标变量
beforeTitle:'', //编辑前的数据
hash:'all' //默认状态
},
computed:{
//过滤listData数据
filterList(){
return filterListData[this.hash](this.listData);
},
//过渡checked是否都是true,都是true,就自动显示全选
isCheckedAll:{
get(){
return this.listData.every(item => item.checked===true)
},
set(newValue){
this.listData.forEach((item)=>{
item.checked = newValue;
})
}
},
//未选中条数
checkedLen(){
return this.listData.filter((item)=>{
return item.checked === false
}).length;
}
},
//监控listData中的数据,只要有改变就创建localStorage
watch:{
//深度监控listData数据
listData:{
handler(){
//当listData对象中数据发生改变时,就创建本地存储
return localStorage.setItem('todoData',JSON.stringify(this.listData));
},
deep:true
}
},
//methods上面的方法,也会挂在实例上
methods:{
addTodo(){//添加方法
if(this.title.trim() === '') return;
this.listData.push({
title:this.title,
id:Date.now()+Math.random(),
checked:false
});
//添加后清空输入框
this.title=''
},
delTodo(index){//删除方法
this.listData.splice(index,1)
},
editTodo(index){//编辑方法
/*
编辑逻辑:
1.编辑方法是在双击某条数据时,会显示输入框,这个是通过class样式来控制的,
所以,这里使用的方法是,通过变量,保存当前点击数据的下标,来判断是否添加输入框的class名;
2.在双击后,输入框需要获取焦点;
编辑遇到的问题:
直接在方法里写获取焦点是没有效果的,因为编辑的dom是动态判断显示的,页面加载后,编辑的输入框
并没有更新完,就直接获取了,所以这个时候是找不到输入框的;
这里处理的办法使用的是,Vue.nextTick();dom更新后才触发的方法;
*/
//编辑的时候,保存一下编辑前的数据
this.beforeTitle = this.listData[index].title;
//重新渲染视图
this.editIndex = index;
//编辑输入框获取焦点
this.$nextTick(()=>{
//获取输入框
//这里通过this.$refs.(ref的名字),可以获取到有ref属性的元素,具体使用方法和注意事项,请查API
this.$refs.editInput[index].focus();
})
},
//完成编辑
editEnd(item){
//判断是否为空,为空就删除数据
if( !item.title.trim()){
/*
如果是空,就调删除方法,删除数据,这里传的参数是一个下标,
所以这里使用了findIndex()es6新方法,来找到对应的下标;
*/
this.delTodo(this.listData.findIndex((dataItem) =>{
dataItem === item;
}));
}
//通过改变editIndex变量为-1,去掉编辑样式
this.editIndex = -1;
//编辑完后,清空编辑前的数据
this.beforeTitle =''
},
//取消编辑
editCancel(item){
//通过改变editIndex变量为-1,去掉编辑样式
this.editIndex = -1;
//把编辑前保存的数据,赋值给当前数据
item.title = this.beforeTitle;
}
}
});
/*
通过获取hash值的方法,来实现,all,active,completed 三种类型的选项
大概思路:
1.页面加载后,先拿到hash值;
2.判断hash值的三种情况(存在的hash,hash为空,不存在的hash);
3.逻辑,hash为这和不存在的hash,都默认是all
*/
//改变hash值,触发的事件
window.onhashchange=function(){
//hash值
let hash = window.location.hash;
//判断三种情况
if(hash){//hash值存在
//获取hash值
hash =hash.slice(2);
//判断hash值是否为空,如果为空,就返回all
hash = filterListData[hash] ? hash : 'all';
}else{//不存在
hash = 'all';
}
// 改变实例下的过滤条件
vm.hash = hash;
};
//先执行hash事件
window.onhashchange();
</script>
</html>