一. Vue
一个可以独立完成前后端分离式web项的轻量级的JavaScript框架
三大主流框架之一:Angular React Vue
Vue可以完全脱离服务器端,以前端代码复用的方式渲染整个页面:组件开发
二. Vue实例成员
1. el: 挂载点 data:数据 methods:管理方法 delimiters:分隔符,解决前后台不分离语言冲突问题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> <style> .box{ background-color: orange; } </style> </head> <body> <div id="app"> <div class="box" v-on:click="pClick">测试</div> <br> <div class="box" v-on:mouseover="pOver">悬浮测试</div> </div> </body> <script src="js/vue.js"></script> <script> new Vue({ el:'#app', data:{ }, methods:{ pClick (){ alert('box被点击了') }, pOver (){ alert('鼠标悬浮了?') } } }) </script> </html>
2. computed:计算属性 一个变量监听多个变量,并且渲染到页面中
1)内部书写方法, 管理监听多个变量的方法
2)方法名 可以直接作为变量被渲染,值为方法的返回值
3)内部只要有一个变量有变化都会被监听,且方法名对应的值都会被影响【依方法的逻辑】
4)computed用来解决一个变量值依赖多个变量值
两个案例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <div id="app"> <p>姓<input type="text" v-model="first_name"></p> <p>名<input type="text" v-model="last_name"></p> <p> 全名:{{full_name}}</p> </div> </body> <script src="js/vue.min.js"></script> <script> new Vue({ el:'#app', data:{ first_name:'', last_name:'', }, computed:{ full_name (){ return this.first_name + " " + this.last_name; } } }) </script> </html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <div id="app"> <input type="text" v-model="a_val"> <input type="text" v-model="b_val"> <input type="text" v-model="c_val"> {{c}} </div> </body> <script src="js/vue.min.js"></script> <script> new Vue({ el:'#app', data:{ a_val:'', b_val:'', c_val:'', }, computed:{ c () { //将要监听的变量放在c方法中 this.a_val; this.b_val; this.c_val; // console.log('该方法被调用了') return this.a_val + this.b_val + this.c_val } } }) </script> </html>
3.watch:监听属性
1. watch内部书写方法,该方法名就是绑定的属性名,监听绑定的属性必须要提前初始化值
2. 方法名代表的属性值发生改变,绑定的方法就会被自动触发/调用,
3. 如full_name属性值发生变化了,就触发该方法。如可以转跳或向后台发送数据
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <div id="app"> <p> 姓名<input type="text" v-model="full_name"> </p> </div> </body> <script src="js/vue.min.js"></script> <script> new Vue({ el:'#app', data:{ full_name:"", first_name:"", }, watch:{ full_name (){ console.log('我被调用了'); } } }) </script> </html>
4.delimiters:处理差值表达式与后台模板语言冲突问题,详见文本指令补充
三. 指令
v-text、v-html、v-html、v-once | v-on | v-bind、v-model | v-if、v-else-if、v-else | v-show
这些指令都是书写在标签的头部,没有Vue环境都是自定义属性;有Vue环境就是Vue语法
直接等于一个变量;
v-text
v-html【可以解析html代码】
v-html
v-once
v-if
v-else-if
v-else 【前面两个满足剩下的】
v-model 【绑定value, 单选项框、独立单选框绑定true与false、多选框绑定数组】
1.简写: @ = v-on: | : = v-bind
2. 条件指令,方法methods提供;数据data提供
v-if 页面消失时不渲染、即不显示页面结构; v-if、v-else-if、v-else: 后面跟条件表达式
v-show 页面消失时以display:none 方式处理
3. 属性指令:v-bind绑定普通属性就一个变量,绑定style就是多个变量,key、value形式
v-bind绑定给类属性
4.循环指令:v-for ="ele in 容器变量" 【案例1=> todolist案例】
数组的循环
<!--传统点0索引取值-->
<!--for循环,谁要多个,就在哪里写for循环-->
<!--可以取索引,数据在前,索引在后,在Vue中重要的数据在前-->
<!--注:key属性是vue的属性,表示该标签在内存中建立缓存的依据-->
<!--b为普通属性,cc就是普通字符串-->
<!--a为绑定的属性,msg为变量,必须在data中定义-->
案列1,
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>循环指令</title> <style> ul{ list-style: none; } /*[v-cloak]{*/ /*display: none;*/ /*}*/ </style> </head> <body> <div id="app" v-cloak> <!--数组的循环--> <div>{{arr}}</div> <hr> <!--传统点0索引取值--> <ul> <li>{{ arr[0] }}</li> <li>{{ arr[1] }}</li> <li>{{ arr[2] }}</li> </ul> <hr> <!--for循环,谁要多个,就在哪里写for循环--> <ul> <li v-for="i in arr">{{i}} </li> </ul> <hr> <!--可以取索引,数据在前,索引在后,在Vue中重要的数据在前--> <ul> <li v-for="(s, i) in arr">第{{i+1}}个:{{s}}</li> </ul> <hr> <!--注:key属性是vue的属性,表示该标签在内存中建立缓存的依据--> <!--b为普通属性,cc就是普通字符串--> <!--a为绑定的属性,msg为变量,必须在data中定义--> <ul> <li v-for="(s, i) in arr" :key="s+i" b="cc" :a="msg">第{{i+1}}个:{{s}}</li> </ul> </div> </body> <script src="js/vue.min.js"></script> <script> new Vue({ el:'#app', data:{ arr:['aaa', 'bbb', 'ccc'] }, }) </script> </html>
对象的循环
<!--对象循环只拿val-->
<!--key、value都取,value在前,key在后-->
<!--对象也可以取索引,默认从0开始-->
<!--列表里套字典,不可以放变量,直接放[{}, {}, {}]-->
对象循环案例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>对象循环指令</title> </head> <body> <div id="app"> <ul> <!--对象循环只拿val--> <li v-for="(v) in dict">{{ v}}</li> </ul> <hr> <ul> <!--value在前,key在后--> <li v-for="(v, k) in dict">key:{{k}},值:{{v}}</li> </ul> <hr> <ul> <!--对象也可以取索引,默认从0开始--> <li v-for="(v, k, index) in dict">key:{{k}},值:{{v}},第{{ index }}</li> </ul> <hr> <!--列表里套字典--> <p v-for="stu in students"> <span v-for="(val, ke, i) in stu"> <b v-if="i != 0">|</b> <b>{{ ke }}:{{ val }}</b> </span> </p> </div> </body> <script src="js/vue.min.js"></script> <script> new Vue({ el: '#app', data: { dict: { 'name': 'jason', 'gender': 'male', 'age': 17 }, students: [ { 'name': 'owen', 'gender': '哇奥', 'age': 27 }, { 'name': 'jason', 'gender': '哇塞', 'age': 21 }, { 'name': 'egon', 'gender': '我去', 'age': 30 },] } }) </script> </html>
todolist案例
1) 数据为空直接结束
2)数据添加到留言数组中
this.msg.push(this.msg_val); // 尾增
this.msg.unshift(this.msg_val); //首增
3)清空输入框
4)操作数组
// this.msg.splice(index, count,arg); // 从第几位开始,操作多少位,可变长的新数据
// count:0 新增 arg
// count:1,2 修改 成arg
// arg:0 删除
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>todolist</title> <style> li:hover{ cursor:pointer; color: red; } </style> </head> <body> <div id="app"> <input type="text" v-model="msg_val" > <button @click="send_msg">留言</button> <ul> <li v-for="(v, i) in msg" @click="del_msg">{{v}}</li> </ul> </div> </body> <script src="js/vue.min.js"></script> <script> new Vue({ el:"#app", data:{ msg:['第一条', '第二条'], msg_val:"" }, methods:{ send_msg (){ // 1) 数据为空直接结束 if (!this.msg_val) return; // 2)数据添加到留言数组中 // this.msg.push(this.msg_val); // 尾增 this.msg.unshift(this.msg_val); //首增 // 3)清空输入框 this.msg_val='' }, del_msg (index){ // this.msg.splice(index, count,arg); // 从第几位开始,操作多少位,可变长的新数据 // count:0 新增 arg // count:1,2 修改 成arg // arg:0 删除 this.msg.splice(index, 1) } } }) </script> </html>
5. 文本相关指令:v-text v-html v-once ==>三者后面绑定的都是变量名,【加引号就是普通字符串】 变量名必须在data中定义
{{ msg }} 插值表达式 相当于是标签的text, 必须由Vue实例的data提供, 否则报not defined
补充:差值表达式与后台模板语言冲突问题?【前后台不分离时,如由django的试图函数加载,导致前后台数据加载冲突】
解决方式:符号格式化, 非delimiters中的符号不会识别,会以普通字符串处理
delimiters:['[{', '}]'] | delimiters:[ '${', '}$' ]
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <div id="app"> <!--不是delimiters定义的符号,不会被识别--> {{msg}} <!--会被vue识别--> <p>[{ msg }]</p> </div> </body> <script src="js/vue.min.js"></script> <script> new Vue({ el:'#app', data:{ msg:"信息" }, delimiters:['[{', '}]'] }) </script> </html>
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
v-text:
<p v-text='msg'>原文本</p> 原文本会被msg替换 ,就是文本
<p v-text='"msg"'>123</p> 加引号就是普通字符串
v- html :
可以解析标签,非标签就解析为字符串,原文本会被变量替换
<p v-html="htmlMsg" @click="changeMsg">1230000</p>
methods:{
changeMsg:function () {
this.htmlMsg = "<i>htmlMsg被点击后的</i>"
}
}
v-once:
默认为初始值,不会被改变
<p v-once="htmlMsg">{{htmlMsg}}</p>
四、前台数据库 存取支持点或[ ] 语法
window.localStorage; //永久存储仓库【基于服务器的】
window.sessionStorage //临时存储仓库 【浏览器关闭,数据就消失】
Cookies 有过期时间的存储仓库
localStorage
存: localStorage['name'] = 'owen'
localStorage.name = 'owen'
取 localStorage.name
清空 localStorage.clear()
sessionStorage
存 window.sessionStorage.name ='egon'
取 console.log(window.sessionStorage['name'])
清空 sessionStorage.clear()
优化todolist:
1. 数据库中有值就存入,没有值就创建一个空数组, 数据库中数据存入默认是以字符串形式存入,所以要将字符串split为数组
msg: localStorage.msg ? localStorage.msg.split(',') :[],
2. 数据删除后,需同步到数据库
localStorage.msg = this.msg;
更新后代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>todolist</title> <style> li:hover{ cursor:pointer; color: red; } </style> </head> <body> <div id="app"> <input type="text" v-model="msg_val" > <button @click="send_msg">留言</button> <ul> <li v-for="(v, i) in msg" @click="del_msg">{{v}}</li> </ul> </div> </body> <script src="js/vue.min.js"></script> <script> new Vue({ el:"#app", data:{ // msg:['第一条', '第二条'], msg: localStorage.msg ? localStorage.msg.split(',') :[], msg_val:"" }, methods:{ send_msg (){ // 1) 数据为空直接结束 if (!this.msg_val) return; // 2)数据添加到留言数组中 // this.msg.push(this.msg_val); // 尾增 this.msg.unshift(this.msg_val); //首增 //同步到数据库 localStorage.msg = this.msg; // 3)清空输入框 this.msg_val='' }, del_msg (index){ // this.msg.splice(index, count,arg); // 从第几位开始,操作多少位,可变长的新数据 // count:0 新增 arg // count:1,2 修改 成arg // arg:0 删除 this.msg.splice(index, 1); // 删除后同步到数据库 localStorage.msg = this.msg; } } }) </script> </html>
五、组件 【每个组件均由 html模板、css样式、js逻辑组成】、根组件 | 局部组件 | 全局组件
根组件:new Vue({}) | el作为template | data 直接绑定{}
局部组件与全局组件:只有一个根标签 | 必须明确自己的 html(template)、css、js | data是一个函数,函数的返回值是{}
局部组件:let localTag = {template:``, data (){ return {}}, methods:{ func(){}}, } | 必须在父组件注册才能使用 | js驼峰对应的-链接语法
全局组件:Vue.component(' ', {相当于是一个局部组件}) | 无需注册 | js驼峰对应的-链接语法
1.一个new Vue创建的是vue实例,一个实例就是一个vue组件
2. new 定义的对象实例称之为根组件【实际开发中一个页面只有一个根组件】
3. 两个根组件之间数据的是相互隔离的,可以将new出来的实力赋值给变量,然后利用原生的js来处理,
template:·· 抄写真实DOM: 用引号包裹,【样式、事件逻辑都一样可以包含】
注:挂载点是必须的(作为虚拟DOM渲染替换的依据),挂载点可以读取挂载点的内容及所有结构,作为根组件自己的template,所以根组件无需书写template。
挂载点为什么是必须的? 即使有了自己的template,还是需要指定替换页面中的哪个部分。
相当于:Vue组件将挂载点的内容读到内存中,然后加载自己的数据,加载完后再用组件的虚拟DOM替换挂载点对应的真实DOM
结论:1.挂载点是必须的,作为虚拟DOM渲染替换的依据。
2.挂载点读取挂载点中的内容及结构可以作为组件自己的template, 所以template不是必须的。
案例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <div id="app01"> {{msg}} </div> <div id="app02"> {{msg}} </div> </body> <script src="js/vue.min.js"></script> <script> let app01 = new Vue({ el:'#app01', data:{ msg:"我是app01msg", c:'red', // template中的C }, // template:'<ul>{{msg}}</ul>' //替换挂载点对应的真实DOM template:` <div id="app01" :style="{color: c}" @click="action"> {{msg}} </div> ` , //抄写真实DOM:用引号包裹, methods:{ action (){ alert(this.msg) } } }); let app02 = new Vue({ el:'#app02', data:{ msg:"我是app02msg" } }); // console.log(app01.msg) // app01.msg = app02.msg //根组件间通过原生js在外组件外界进行通信 </script> </html>
4. 根组件内部可以注册使用n个子组件, 子组件必须拥有自己的 html模板、css样式、js逻辑。
5. 创建局部组件 => 在根组件中注册 => 解析{} 中的vue语法 => 组件形成
注:new出来的就是根组件, 定义一个对象【 let box= {...}】 局部组件
Vue中取名建议小驼峰,html标签中标签名忽略大小写,都会默认成小写,这样Vue中的名字与HTML就对不上了【例】:
错误的: Vue中的 html
components:{ <boxTag></boxTag>
boxTag: boxTag
}
建议:1. 都用小写加下划线
2. 或者js引号包裹:‘box-tag’ html: box-tag
3. Vue中建议用小驼峰,Html中支技用中线隔开语法,对应着就是JS中的驼峰语法
components中: boxTag html中:box-Tag
6.局部组件data必须要是一个函数,return一个字典,因为每个组件必须要有一套自己的数据,【data的数据还必须是字典】
类似闭包函数,每执行一次外部函数,内部函数就会产生一个局部名称空间
闭包案例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 闭包案例 def outer(): def inner(): num=1 return inner print(inner.__dict__, id(inner)) obj1 = outer() obj2 = outer() {} 2137236028272 {} 2137236028408
Veu局部组件案例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>局部组件</title> <style> .box { width: 200px; text-align: center; border: 1px solid black; border-radius: 10px; overflow: hidden; float: left; } .box img { width: 100%; } .box:hover span { cursor: pointer; color: red; } </style> </head> <body> <div id="app"> <box-tag></box-tag> <box-tag></box-tag> </div> </body> <script src="js/vue.js"></script> <script> let boxTag = { //局部组件 //html模板 template: ` <div class="box"> <img src="img/333.jpg" alt=""> <p> <span @click="btnClick">点击了下{{ num }}</span> </p> <p>美人</p> </div> `, data() { // 组件执行一次就会产生一个局部名称空间,来存放自己的一套数据 return { num:0 } }, methods: { btnClick() { this.num++ } } }; new Vue({ el: '#app', components: { // 'box-tag': boxTag boxTag, } }) </script> </html>
六、组件通信
1. 父传子,数据在父组件 => 父组件模板写子组件标签 =>子组件标签自定义属性绑定数据变量
父组件提供数据 | 子组件通props绑定属性 | 子组件拿到属性就拿到父组件的变量值, props中可以看成是一个反射,可以直接拿到对应的变量值
父传子案例一:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>父传子</title> </head> <body> <div id="app"> <sub-tag :a="msg" ></sub-tag> <!--<sub-tag></sub-tag>--> </div> </body> <script src="js/vue.js"></script> <script> let subTag ={ //子组件通过实例成员props获取自身自定义属性,通过插值表达式反射拿到对应的变量的值 props:['a'], template:` <div> <h1>{{ a }}</h1> </div> `, // data (){ // return { // msg:'123' //该数据必须由父组传进来,不可以写死 // } // }, }; new Vue({ el:'#app', components:{ subTag, }, data:{ msg:"父级数据" }, }) </script> </html>
父传子案例二:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> <style> .box{ width: 200px; border-radius: 10px; border: 2px solid black; text-align: center; float: left; overflow: hidden; } .box img{ width: 100%; } .box:hover { cursor: pointer; color: red; } </style> </head> <body> <div id="app"> <sub-tag v-for="box_obj in boxTag" :box-tag="box_obj"></sub-tag> </div> </body> <script src="js/vue.js"></script> <script> let subTag = { props:['boxTag'], template:` <div class="box"> <img :src="boxTag.img_url" alt=""> <span @click="action">点了{{num}}下</span> <p> <span>{{ boxTag.img_title}}</span> </p> </div>> `, data (){ return { num:0 } }, methods:{ action (){ this.num ++ }, }, }; let back_data = [ { img_url:'img/222.jpg', img_title:'美1'}, {img_url:'img/333.jpg', img_title:'美2'}, {img_url:'img/444.jpg', img_title:'美3'}, ]; new Vue({ el:'#app', components:{ subTag, }, data:{ boxTag:back_data, }, }) </script> </html>
2. 子传父, 数据在子组件 => 父组件模板写子组件标签 => 子组件内部通过事件发送数据给外部,子组件与父组件链接的桥梁子组件标签
子组件提供数据 | 子组件 $emit('自定义事件', 数据们) | 父组件模板中的子组件标签绑定自定义事件 | 自定义事件的方法由父组件实现 | 实现的方法的参数就是子组件的数据
@send_pag2 子组件的自定义事件、 recv_page2 父组件的变量事件
<view-tag v-if="page == 'tag1'" @send_pag2="recv_page2"></view-tag>
子传父案例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>父子组件</title> <style> body, h1{ margin: 0; } </style> <style> .header_tag{ height: 120px; background-color: orange; } .body{ height: 800px; background-color: papayawhip; } .footer{ height: 120px; background-color: cyan; } span:hover{ cursor: pointer; color: red; } </style> </head> <body> <div id="app"> <view-tag v-if="page == 'tag1'" @send_pag2="recv_page2"></view-tag> <view-tag2 v-else-if="page == 'tag2'" @send_pag2="recv_page2"></view-tag2> </div> </body> <script src="js/vue.js"></script> <script> let headerTag ={ template:` <div class="header_tag"> <h1 style="text-align: center; line-height: 80px">头</h1> <span @click="changPage('tag1')" >免费</span> <span @click="changPage('tag2')" >轻课</span> </div> `, methods:{ changPage (page) { this.$emit('send_data', page) }, }, }; let footerTag ={ template:` <div class="footer"> <h1 style="text-align: center; line-height: 120px">尾</h1> </div> `, }; let viewTag = { template:` <div> <header-tag @send_data="rev_sub"></header-tag> <div class="body" style="background-color: green"></div> <footer-tag></footer-tag> </div> `, components:{ headerTag, footerTag, }, methods:{ rev_sub (val){ this.$emit('send_pag2', val) }, }, }; let viewTag2 = { template:` <div> <header-tag @send_data="rev_sub"></header-tag> <div class="body" style="background-color: blue"></div> <footer-tag></footer-tag> </div> `, components:{ headerTag, footerTag, }, methods:{ rev_sub (val){ this.$emit('send_pag2', val) }, }, }; new Vue({ el:'#app', components:{ viewTag, viewTag2, }, data:{ page:'tag1' }, methods:{ recv_page2 (val){ console.log(val); this.page = val; } } }) </script>