框架设计模式
MVC
- Model - 模型,数据
- View - 视图
- Controller - 控制器
- View传送指令到Controller
- Controller完成业务逻辑后,要求Model改变状态
- Model将新的数据发送到View,用户得到反馈
- 所有的通信都是单向的
MVP
- Model - 模型,数据
- View - 视图
- Presenter - 控制器MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向(双向通信)
- View 与 Model 不发生联系,都通过 Presenter 传递
MVVM
- Model - 模型,数据
- View - 视图
- VM - ViewModel
- 双向绑定,即View的变化传个VM,VM变化也传给View
概述
- Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用
- Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合
- Vue现行两大版本:vue2.x 、 vue3.x
- Vue2.x 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性(Object.defineProperty())
- Vue2.x 底层使用 Object.defineProperty() 来实现数据劫持,Vue3.x 底层使用 Proxy 来实现数据代理
- Vue底层是监听DOM节点变化,找出变化的节点,打上标记,只将标记的节点修改
安装
- 使用 <script> 直接引入:
- 使用 <script src=“”> 方式引入,会添加一个全局变量 Vue
- 两种版本:
- 开发版本: 包含完整的警告和调试模式 vue.js
- 生产版本: 压缩混淆,不包含调试模式信息 vue.min.js
- npm
- vue-cli
基本用法
Vue设计思路
- 使用与HTML标签一致的方式来描述DOM元素<div></div>
- 使用与HTML标签一致的方式来描述属性<div id=“app”></div>
- 使用:或v-bind来描述动态绑定的属性<div :id=“dynamicId”></div>
- 使用@或v-on来描述事件<div @click=“handler”></div>
- 使用与HTML一致的方式描述层级结构<div><span></span></div>
声明式渲染
- 简洁的模板语法(类似模板字符串)来声明式地将数据渲染进 DOM 的系统
- 渲染后DOM与数据是响应式关联的,当数据发生改变,DOM也会发生相应改变
- data 对象中的所有的 property 加入到 Vue 的响应式系统中,但只有当实例被创建时就已经存在于 data 中的 property 才是响应式的
<div id="app">
{
{message}}
</div>
<script src="../libs/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
message:'hello Vue.js'
},
})
</script>
渲染器
- 拿到数据后Vue渲染过程
- Vue底层思路是这样的,但还有其他要处理的东西,比如改变里面的children浏览器并非重新渲染,而是Vue先标记处改变的点,然后浏览器再按标记渲染页面
<script>
const vnode={
tar:'div',
props:{
onclick:()=>{
alert('hello world')}
},
children:'click me'
}
function render(vnode,container){
const el=document.createElement(vnode.tar)
for(let key in vnode.props){
if(/^on/.test(key)){
el.addEventListener(
key.substr(2).toLowerCase(),
vnode.props[key]
)
}
}
if(typeof vnode.children == 'string'){
el.appendChild(document.createTextNode(vnode.children))
}else if(Array.isArray(vnode.children)){
vnode.children.forEach(child =>render(child,el))
}
container.appendChild(el)
}
render(vnode,document.body)
</script>
理解响应式数据
- 响应式数据可以理解为实时更新,当data中数据改变时,页面也做出相应改变
- 通过Proxy代理实现,改变数据时set会触发,而获取数据时get会触发
<script>
const bucket=new Set()
const data={
text:"hello world"}
const obj=new Proxy(data,{
get(target,key){
bucket.add(effect)
return target[key]
},
set(target,key,newVal){
target[key] = newVal
bucket.forEach(fn => fn())
return true
}
})
function effect(){
document.body.innerText=obj.text
}
effect()
setTimeout(()=>{
obj.text='hello vue3'
},1000)
</script>
Vue中的methods
- 在 methods 中定义的方法内部,this 通常指向的是当前创建出的 Vue 实例对象本身(不要使用箭头函数,虽然不会报错)
- 在我们命名中尽量不要使用$和_因为Vue中自身对象采用这种命名规则
<div id="app">
{
{message}}
{
{method1()}}
{
{method2()}}
{
{method3()}}
</div>
<script src="../libs/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
message:'hello Vue.js'
},
methods:{
method1:function (){
console.log("method1")
return "method1"
},
method2(){
console.log("method2")
return "method2"
},
method3:()=>{
console.log("method3")
return "method3"
},
}
})
/*网页输出为hello Vue.js method1 method2 method3*/
Vue中的指令
- Vue中的指令都是以v-开头
- 语法v-指令名称:参数.修饰符
文本指令
- v-text
new Vue({
el:'#app',
data:{
message:'hello Vue.js'
}
})
- <div v-text=“message”></div>hello Vue.js
- <div>{
{message}}</div>hello Vue.js
- 类似于原生中的innerText,普通文本会对里面的HTML标签进行解析,浏览器渲染的是文本格式数据
- v-html
- <div v-html=“message”></div>一个文本输入框
- 类似于原生JS中innerHTML会解析出来里面的HTML标签元素
条件渲染:满足条件的节点即显示,否则隐藏
- v-show
- v-if
- v-else-if
- v-else
- v-show与v-if区别
- v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
- v-if是惰性的,当初始条件为假时,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块
- v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换
- v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销
- 当非常频繁切换时,用v-show,运行条件很少改变时,用v-if
- 示例
<body>
<div id="app">
<button @click="status = !status">显示与隐藏</button>
<div v-show="status">v-show显示与隐藏</div>
<div v-if="status">v-if显示与隐藏</div>
</div>
<script src="../libs/vue.js"></script>
<script>
new Vue({
el:'#app',
data:{
status:true,
}
})
</script>
列表渲染:通常是对数组进行遍历
- v-for
- 在进行列表渲染时,尽量为渲染的每一项绑定 key 属性
- 不推荐v-if与v-for一起使用,如果一起使用注意他们的执行先后顺序
- vue2.x:v-for 的优先级高于 v-if
- vue3.x:v-if 的优先级高于 v-for
- 实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<div v-for="n in 3">{
{n}}-渲染内容</div>
<div v-for="(item,index) in message">当前:{
{item}}-索引:{
{index}}</div>
<div v-for="(value,key,index) in person" >值:{
{value}} -key:{
{key}}- 索引:{
{index}}</div>
</div>
<script src="../libs/vue.min.js"></script>
<script>
new Vue({
el:'#app',
data:{
message:[34,45,64,34,66],
person:{
id:10,
name:'jack',
age:20
}
}
})
</script>
</body>
</html>
事件处理
- v-on:可简写为 @
- 在内联 JavaScript 语句中调用方法,当在事件处理程序调用时需要显式传递参数时,通常使用这种方式
属性绑定
- v-bind:可简写为 :
- 动态地绑定一个或多个 attribute,或一个组件 prop 到表达式
- 在绑定 class 或 style attribute 时,支持其它类型的值,如数组或对象
- 在绑定 prop 时,prop 必须在子组件中声明。可以用修饰符指定不同的绑定类型
- 没有参数时,可以绑定到一个包含键值对的对象。注意此时 class 和 style 绑定不支持数组和对象
表单处理
- v-model:双向绑定
- 对于不同的表单事件
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件
- v-model只是封装的语法糖,下面两种都都能实现双向绑定
<div id="app">
<div>{
{message}}</div>
<input type="text" :value="message" @input="handleInput">
/*两个单向绑定拼成双向绑定*/
<div>{
{message1}}</div>
<input type="text" v-model="message1">
</div>
<script src="../libs/vue.js"></script>
<script>
new Vue({
el:'#app',
data:{
message:'hello Vue.js',
message1:'heloo vue.js1'
},
methods:{
handleInput(event){
this.message=event.target.value
}
}
})
</script>
插槽
- v-slot
- 在父组件中使用template并写入对应的slot值来指定该内容在子组件中现实的位置,没有对应值的其他内容会被放到子组件中没有添加name属性的slot中
其它
- v-pre:跳过这个元素和它的子元素的编译过程,可以用来显示原始 Mustache 标签
- <div v-pre>{
{product}}</div>
- v-cloak:这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
- v-once:一次
- 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<div id="app">
<div v-cloak>{
{product}}</div>
<div v-pre>{
{product}}</div>
</div>
<script src="../libs/vue.min.js"></script>
<script>
setInterval(function (){
new Vue({
el:'#app',
data:{
product:'电视机'
}
})
},1000)
</script>
</body>
</html>
示例:购物车
- 效果
- 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
table{
width: 100%;
height: 100%;
}
tr{
height: 30px;
}
.active{
background: pink;
}
</style>
</head>
<body>
<div id="app">
<h1>购物车</h1>
/*判断购物车中有无商品,没有显示div有则显示table*/
<div v-if="cart.length ===0">暂无商品,请添加</div>
<table v-else>
<tr>
<td><input type="checkbox" :checked="checkedall" @change="checkedAllStatus"></td>
<td>商品id</td>
<td>商品信息</td>
<td>是否选中</td>
<td>商品价格</td>
<td>商品数量</td>
<td>商品总价</td>
<td>删除</td>
</tr>
<tr v-for="item in cart" :class="{active:item.checked}">
<td><input type="checkbox" v-model:checked="item.checked"></td>
<td>{
{item.id}}</td>
<td>{
{item.title}}</td>
<td>{
{item.checked?"是":"否"}}选中</td>
<td>{
{item.price}}</td>
<td>{
{item.num}}</td>
<td>{
{(item.price*item.num).toFixed(2)}}</td>
<td v-on:click="delProduct(item.id)">删除</td>
</tr>
</table>
<div><span>总计:{
{numTotal}}件,</span><span>已选{
{numStatic}}件 </span>合计:{
{total}}<button @click="clearCart">清除所有</button></div>
<h1>推荐商品</h1>
/*渲染商品列表*/
<ul id="">
<li v-for="item in products">
{
{item.id}}-{
{item.title}}-{
{item.price}}-<button v-on:click="addToCart(item)">加入购物车</button>
</li>
</ul>
<div>
<input type="text" autofocus placeholder="输入商品名" v-model.trim="addName" ref="input" ><br>
<input type="text" placeholder="输入价格" v-model.trim="addPrice" @keydown.enter="addProduct"><br>
<button @click="addProduct">添加商品</button>
</div>
</div>
/*引入Vue*/
<script src="../libs/vue.js"></script>
<script>
const _products=new Array(6).fill(null).map(function (item,index,arry){
let name;
switch (index){
case 0:
name="苹果";
break;
case 1:
name="香蕉";
break;
case 2:
name="菠萝";
break;
case 3:
name="葡萄";
break;
case 4:
name="梨子";
break;
case 5:
name=" 猕猴桃";
break;
}
return {
id:++index,
title:'商品'+name,
price:(Math.random()*100).toFixed(2)
}
})
let index=6;
const vm=new Vue({
el:'#app',
data:{
products:_products,
cart:[],
addName:'',
addPrice:'',
},
computed:{
total(){
return this.cart.reduce((sum,item)=>{
return item.checked?sum += item.price*item.num:sum
},0).toFixed(2)
},
checkedall(){
return this.cart.every(item => item.checked)
},
numTotal(){
return this.cart.length
},
numStatic(){
return this.cart.reduce((s,item)=>{
return item.checked? ++s :s
},0)
},
},
methods:{
checkedAllStatus(event){
let checkedAll=event.target.checked;
this.cart.forEach(item => item.checked=checkedAll)
},
addToCart(item){
const pro=this.cart.find(curr=> curr.id ===item.id)
if(pro){
pro.num++
}else {
this.cart.push({
...item,
num:1,
checked:true,
})
}
},
delProduct(id){
this.cart=this.cart.filter(item => id !== item.id)
},
addProduct(){
this.$refs.input.focus()
if(this.addName === '' || this.addPrice === ''){
return
}
this.products.push({
id:++index,
title:'商品'+this.addName,
price:this.addPrice,
num:1,
checked:true
})
this.addName='';
this.addPrice='';
},
clearCart(){
this.cart=[]
}
}
})
</script>
</body>
</html>