1. MVVM 模式
1.1 MVC 模式
MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。
- Model (模型) : 代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
- View(视图) : 代表模型包含的数据的可视化。
- Controller(控制器) :控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。
1.2 MVC 模式实现
创建一个作为模型的 Student 对象。StudentView 是一个把学生详细信息输出到控制台的视图类,StudentController 是负责存储数据到 Student 对象中的控制器类,并相应地更新视图 StudentView。MvcDemo 作为演示类。
- 创建模型
public class Student {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 创建视图
public class StudentView {
public void printStudentDetails(String studentName,String studentId){
System.out.println("Student: ");
System.out.println(" Name: " + studentName);
System.out.println(" Id: " + studentId );
}
}
- 创建控制器
public class StudentController {
private Student model;
private StudentView view;
public StudentController(Student model, StudentView view) {
super();
this.model = model;
this.view = view;
}
public void setStudentName(String name){
model.setName(name);
}
public String getStudentName(String name){
return model.getName();
}
public void setStudentId(String id){
model.setId(id);
}
public String getStudentId(String name){
return model.getId();
}
public void updateView(){
view.printStudentDetails(model.getName(), model.getId());
}
}
- 使用 StudentController 方法来演示 MVC 设计模式的用法。
public class MvcDemo {
public static void main(String[] args) {
Student model = retrieveStudentDatabase();
StudentView view = new StudentView();
StudentController controller = new StudentController(model,view);
controller.updateView();
}
private static Student retrieveStudentDatabase(){
Student student = new Student();
student.setId("2018303858");
student.setName("邹小胖");
return student;
}
}
- 输出页面
1.3 MVVM模式
MVVM(Model-View-ViewModel)是一种软件架构设计模式,是一种简化用户界面的事件驱动编程方式。
- View:视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建。
- Model :数据模型,泛指后端进行的各种业务逻辑处理和数据操控,主要围绕数据库系统展开。
- ViewModel :视图数据层。在这一层,前端开发者对从后端获取的Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与Model 层交互
MVVM 的核心是 ViewModel 层,负责转换 Model 中的数据对象来让数据变得更容易管理和使用,其作用如下:
- 该层向上与视图层进行双向数据绑定
- 向下与 Model 层通过接口请求进行数据交互
ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的。
– 比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示)
– 页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互)
1.4 MVVM 模式的实现者
- Model:模型层,在这里表示 JavaScript 对象
- View:视图层,在这里表示 DOM(HTML 操作的元素)
- ViewModel:连接视图和数据的中间件,Vue.js 就是 MVVM 中的 ViewModel 层的实现者
在 MVVM 架构中,是不允许 数据 和 视图 直接通信的,只能通过 ViewModel 来通信,而 ViewModel就是定义了一个 Observer 观察者。
- ViewModel 能够观察到数据的变化,并对视图对应的内容进行更新
- ViewModel 能够监听到视图的变化,并能够通知数据发生改变
2. Vue 初步认识
2.1 声明式编程
- 创建一个div元素,设置id属性
- 定义一个vue对象,将div挂载在vue对象上
- 在vue对象内定义变量message,并绑定数据
- 将message变量放在div元素上显示
- 修改vue对象中的变量message,div元素数据自动改变
2.2 Vue 实例
通过 new 一个 Vue 来创建一个实例,Vue 是一个构造函数。
当创建一个 Vue 实例时,可以传入一个选项对象(options),定义挂载元素、数据、方法等。
options选项:
-
el挂载元素:
- 类型:String / HTMLElement
- 作用:决定之后Vue实例会管理哪一个DOM
- 注:该元素不能是 html 或 body
-
data数据:
- 类型:Object / Function
- 作用:Vue实例对应的数据对象
- 当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
-
methods方法:
-
类型:{ [key:string]: Function }
-
作用:定义属于Vue的一些方法
-
注:methods 中的方法名不能与 data 中的属性名重复
-
2.3 Vue实现(count计数器)
- 创建一个 HTML 文件 count.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>计数器</title>
</head>
<body>
</body>
</html>
- 引入 Vue.js
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
- 创建一个 Vue 实例
<script>
const vue = new new Vue({
// 绑定元素的Class
el: ".div",
// 数据对象中有一个名为 count 的属性,并设置了初始值 0
data: {
count: 0
},
// 有两个实现方法
methods: {
increament: function(){
this.count++;
},
decreament: function(){
this.count--;
}
}
})
</script>
- 将数据绑定到页面元素 (视图层)
只需要在绑定的元素中使用 双花括号{{ variable }}
将 Vue 创建的名为 count 属性包裹起来,即可实现数据绑定功能
<div class="div">
<h2>当前计数:{{count}}</h2>
<button @click="increament">+</button>
<button @click="decreament">-</button>
</div>
- 由于 vue 是声明式编程,所以在开发者模式中console控制台,改变vue对象的count值(
vm.varible
修改值),页面显示也随之改变。
3. 语法
3.1 插值语法
v-once
在某些情况下,我们可能不希望界面中 Mustach 中的值随意的跟随改变,就可以使用一个 Vue 的指令:v-once
- 该指令后面不需要跟任何表达式
- 该指令表示元素和组件只渲染一次,不会随着数据的改变而改变
<div class="app">
<h2>{{message}}</h2>
<h2 v-once>{{message}}</h2><!-- 只会渲染一次 -->
</div>
<script>
const vue = new Vue({
el: '.app',
data: {
message: '邹小胖'
}
})
</script>
v-html
从服务器请求到的数据本身就是一个 HTML 代码,如果直接通过 {{}}
来输出,会将 HTML 代码也一起输出,但如果希望按照 HTML 格式进行解析,并且显示对应的内容,可以使用 v-html
指令。
v-html
指令后面往往会跟上一个 string 类型,会将 string 的 html 解析出来并且进行。
v-text
v-text 作用和 Mustache 比较相似,都是用于将数据显示在界面中,会覆盖原来的内容。
v-text 通常情况下,接受一个 string 类型。
v-pre
v-pre
用于跳过这个元素和它子元素的编译过程,显示原本的 Mustache 语法,将代码原封不动的解析出来
v-cloak
隐藏未编译的 Mustache 标签直到实例准备完毕。
主要用于单纯引入 vue.js 来使用时,会闪现出花括号
[v-cloak] {
display: none;
}
可以直接放在最外层的 #app
上,也可以单个放在其内部的某个标签上
<div v-cloak id="app">
</div>
3.2 基础语法
v-bind 动态绑定sytle
v-bind
指令动态绑定属性,用于绑定一个或多个属性值。
<div class="app">
<h5 v-bind:title="message"> 鼠标悬停几秒钟查看此处动态绑定状态 </h5>
<a v-bind:href="aHref">vue.js官网</a>
</div>
<script>
const vue = new Vue({
el: '.app',
data: {
message: '邹小胖',
aHref: 'https://cn.vuejs.org/'
}
})
</script>
语法糖(简写方式): 省略v-bind,直接写:(冒号)
<h5 :title="message"> 鼠标悬停几秒钟查看此处动态绑定状态 </h5>
<a :href="aHref">vue.js官网</a>
v-if 条件判断
条件判断语句:
- v-if
- v-else-if
- v-else
<div id="app">
<h4 v-if="type === 'A'">A</h4>
<h4 v-else-if="type === 'B'">B</h4>
<h4 v-else-if="type === 'C'">C</h4>
<h4 v-else>who?</h4>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
type: 'A'
}
})
</script>
初始页面显示A,后在控制台分别输入vue.type=‘B’、‘C’、‘D’,会分别显示B、C、who?
v-on 事件监听
事件监听:在前端开发中,我们需要经常和用户进行交互,这个时候,就必须监听用户发生的事件,比如点击、拖拽、键盘事件等,在 Vue 中监听事件使用 v-on 指令。
语法糖: 省略v-on
,直接写@
。以 2.3 count计数器为例:
<!-- 常规写法 -->
<button v-on:click="increament">+</button>
<!-- 语法糖形式 -->
<button @click="decreament">-</button>
当通过 methods 中定义方法,以 @click
调用时,需要注意参数问题:
- 如果该方法不需要额外参数,那么方法后的 () 可以不添加。
- 如果方法本身中有一个参数,那么会默认将原生事件 event 参数传递进去。
例:分别点击按钮1.2.3,显示内容如下图
<div class="app">
<!-- undefined -->
<button @click="btnClick()">按钮1</button>
<!-- 123 -->
<button @click="btnClick(123)">按钮2</button>
<!-- MouseEvent对象:vue会默认将浏览器产生的event对象作为参数传到方法中 -->
<button @click="btnClick">按钮3</button>
</div>
<script>
const vue = new Vue({
el: '.app',
data: {
count: 0
},
methods: {
btnClick(num) {
console.log(num);
}
}
})
</script>
- 如果需要同时传入某个参数,同时需要 event 时,可以通过
$event
传入事件。
<div class="app">
<button @click="btnClick(2,$event)">按钮</button>
</div>
<script>
const vue = new Vue({
el: '.app',
data: {
count: 0
},
methods: {
btnClick(num,event) {
console.log(num,event);
}
}
})
</script>
v-for 循环
当我们有一组数据需要进行渲染时,我们就可以使用 v-for 来完成。
v-for 的语法类似于 JavaScript 中的 for 循环:item in items
。
- 遍历数组
<div class="app">
<ul>
<li v-for="item in idols">{{item}}</li>
<br>
<li v-for="(item,index) in idols">{{index+1}}.{{item}}</li>
</ul>
</div>
<script>
const vue = new Vue({
el: '.app',
data: {
idols: ['连淮伟','王琳凯']
}
})
</script>
- 遍历对象
<div class="app">
<ul>
<li v-for="item in infos">{{item}}</li><br>
<li v-for="(item,key) in infos">{{key}}:{{item}}</li> <br>
<li v-for="(item,key,index) in infos">{{index+1}}.{{key}}:{{item}}</li>
</ul>
</div>
<script>
const vue = new Vue({
el: '.app',
data: {
infos: {
name: '邹小胖',
age: 20,
gender: '女',
}
}
})
</script>
v-model 表单绑定
v-model 指令来实现表单元素和数据的双向绑定
解析:当我们在输入框输入内容时,因为 input 中的 v-model 绑定了 message,所以会实时将输入的内容传递给 message,message 发生改变,当 message 发生改变时,因为使用了 Mustache 语法,所以将 message 的值插入到 DOM 中,所以 DOM 会发生响应的改变,所以通过 v-model 实现了双向绑定。
- 原理:
v-model 其实是一个语法糖,它的背后本质上是包含两个操作:
① v-bind 绑定一个 value 属性
②v-on 指令给当前元素绑定 input 事件
<input type="text" v-model="message">
<!-- 等同于 -->
<input type="text"
:value="message"
<!-- event.target.value( ) 获取当前文本框的值(由事件触发时)-->
@input="message = $event.target.value">
- 结合radio使用
<div class="app">
<label for="male">
<input type="radio" v-model="gender" id="male" value="男">男
</label>
<label for="female">
<input type="radio" v-model="gender" id="female" value="女">女
</label>
<h3>您选择的性别是:{{gender}}</h3>
</div>
<script>
const app = new Vue({
el: '.app',
data: {
gender: '男'
}
});
</script>
- 结合checkbox使用(单选框)
<div class="app">
<!-- checkbox单选框 -->
<label for="licence">
<input type="checkbox" v-model="isAgree" id="licence">同意协议
</label>
<h3>您的选择是:{{isAgree}}</h3>
<button :disabled="!isAgree">下一步</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '.app',
data: {
isAgree: false
}
});
</script>
- 结合checkbox使用(多选框)
<div class="app">
<label for="sing">
<input type="checkbox" v-model="hobbies" value="唱">唱
</label>
<label for="jump">
<input type="checkbox" v-model="hobbies" value="跳">跳
</label>
<label for="rap">
<input type="checkbox" v-model="hobbies" value="rap">rap
</label>
<label for="basketball">
<input type="checkbox" v-model="hobbies" value="篮球">篮球
</label>
<h3>您的爱好是:{{hobbies}}</h3>
</div>
<script>
const app = new Vue({
el: '.app',
data: {
hobbies: []
}
});
</script>
- 结合select使用(下拉框)
<div class="app">
<!-- 选择一个值 -->
<h3>请选择最喜欢的水果:</h3>
<select name="" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="橘子">橘子</option>
<option value="西瓜">西瓜</option>
<option value="榴莲">榴莲</option>
</select>
<h3>您最喜欢的水果是:{{fruit}}</h3><br>
<!-- 选择多个值 -->
<h3>请选择喜欢的水果(可多选):</h3>
<select name="" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="橘子">橘子</option>
<option value="西瓜">西瓜</option>
<option value="榴莲">榴莲</option>
</select>
<h3>您最喜欢的水果是:{{fruits}}</h3>
</div>
<script>
const app = new Vue({
el: '.app',
data: {
fruit: '苹果', //默认选中苹果
fruits: []
}
});
</script>
- input值绑定
<div id="app">
<label v-for="item in fruitList" :for="item">
<input type="checkbox" :value="item" :id="item" v-model="fruit">{{item}}
</label>
<h3>你喜欢的水果:{{fruit}}</h3>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
fruitList: ['西瓜','菠萝','柚子','香蕉','草莓'],
fruit: []
}
});
</script>
4 计算属性 computed
在模板中可以直接通过插值语法显示一些 data 中的数据,但是在某些情况下,可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示,这时可以使用计算属性 computed。
4.1 基本使用
比如:现在有 firstName 和 lastName 两个变量,需要显示完整的名称,可能直接使用空格将其隔开:
<h2>{{firstName}} {{lastName}}</h2>
或者使用加号拼接:
<h2>{{firstName + '' +lastName}}</h2>
但是如果多个地方都需要显示完整的名称,我们就需要写多个 {{firstName}} {{lastName}}
或 {{firstName + '' +lastName}}
,使代码看上去很不优雅。可以将{{firstName + '' +lastName}}
封装为一个函数,通过函数的方式调用,但其实最佳方案是使用计算属性:
<div id="app">
<!-- 方式1 -->
<h2>{{firstName}} {{lastName}}</h2>
<!-- 方式2 -->
<h2>{{firstName + ' ' + lastName}}</h2>
<!-- 方式3 -->
<h2>{{fullName}}</h2>
<!-- 方式4 -->
<h2>{{getFullName()}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<script>
const vue = new Vue({
el: '#app',
data: {
firstName: 'xiaopang',
lastName: 'Zou'
},
computed: {
fullName(){
return this.firstName + ' ' + this.lastName;
}
},
methods: {
getFullName: function(){
return this.firstName + ' ' + this.lastName;
}
}
})
</script>
4.2 计算属性的 setter 和 getter
每个计算属性都包含一个 getter 和一个 setter,getter 用来读取值,setter 用来设置值(但 setter 不常用)。
<div id="app">
<h2>{{fullName}}</h2>
</div>
<script>
const vue = new Vue({
el: '#app',
data: {
firstName: '邹',
lastName: '小胖'
},
computed: {
fullName:{
set: function(newValue) {
let newName = newValue.split(' ');
this.firstName = newName[0];
this.lastName = newName[1];
},
get: function() {
return this.firstName + ' ' + this.lastName;
}
}
}
})
</script>
4.3 computed 与 methods 的区别
- computed 是属性调用,而 methods 是函数调用
computed 定义的方法,我们是以属性访问的形式调用的,{{computedTest}}
,但是 methods 定义的方法,我们必须要加上()
来调用,如{{methodTest()}}
。
- computed 带有缓存功能,而 methods 没有
computed 依赖于 data 中的数据,只有在它的相关依赖数据发生改变时才会重新求值。
我们可以将同一函数定义为一个方法而不是一个计算属性,两种方式的最终结果确实是完全相同的然而,不同的是计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值,这就意味着只要 text 还没有发生改变,多次访问 getText 计算属性会立即返回之前的计算结果,而不必再次执行函数,而方法只要页面中的属性发生改变就会重新执行。
5. 组件化开发
组件化思想:如果将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展,但如果将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
任何的应用都会被抽象成一颗组件树。
5.1 注册组件
-
创建组件构造器
Vue.extend()
调用Vue.extend()创建的是一个组件构造器。
通常在创建组件构造器时,传入template代表我们自定义组件的模板。该模板就是在使用到组件的地方,要显示的HTML代码。
-
注册组件
Vue.component()
:调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
需要传递两个参数:①注册组件的标签名;②组件构造器
-
使用组件
组件必须挂载在某个Vue实例下,否则它不会生效。
注意:以上代码方式创建的组件是全局组件,即可以在多个 vue 的实例下使用,要改为局部组件,需要将注册方法写到具体的某个实例中。
5.2 组件分类
全局组件和局部组件
当我们通过调用 Vue.component()
注册组件时,组件的注册是全局的,这意味着该组件可以在任意 Vue 实例下使用;如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件。
父组件和子组件
组件和组件之间存在层级关系,而其中一种非常重要的关系就是父子组件的关系。
5.3 注册组件语法糖
省去了调用Vue.extend()
的步骤,直接使用一个对象来代替。
语法糖注册全局组件和局部组件:
<div class="app">
<my-cpn1></my-cpn1>
<my-cpn2></my-cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 1. 注册全局组件的语法糖
Vue.component('my-cpn1', {
template: `
<div>
<h2>组件标题1</h2>
<p>组件1中的一个段落内容</p>
</div>
`
})
const app = new Vue({
el: '.app',
// 2. 注册局部组件的语法糖
components: {
'my-cpn2': {
template: `
<div>
<h2>组件标题2</h2>
<p>组件2中的一个段落内容</p>
</div>
`
}
}
})
</script>
5.4 注册组件模块分离
使用<script>
标签
使用<template>
标签
6. 插槽slot
slot 翻译为插槽,插槽的目的是让我们原来的设备具备更多的扩展性,让使用者可以决定组件内部的一些内容到底展示什么。
在子组件中,使用特殊的元素 <slot>
就可以为子组件开启一个插槽,该插槽插入什么内容取决于父组件如何使用。
6.1 具名插槽
当子组件的功能复杂时,子组件的插槽可能并非是一个,比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。这个时候,我们就需要给插槽起一个名字,这就是具名插槽。
6.2 slot实操
比如准备制作一个待办事项组件(todo),该组件由待办标题(todo-title)和待办内容(todo-items)组成,但这三个组件又是相互独立的,该如何操作呢?
第一步: 定义一个待办事件的组件
<template id="myCpn">
<div>
<div>待办事项</div>
<ul>
<li>学习Vue</li>
</ul>
</div>
</template>
第二步: 我们需要让待办事项的标题和值实现动态绑定,需要使用slot
- 将上面的代码流出两个插槽,即slot
<template id="myCpn">
<div>
<slot name="todo-title"></slot>
<ul>
<slot name="todo-items"></slot>
</ul>
</div>
</template>
- 定义一个名为 todo-title 的待办标题组件 和 todo-items 的待办内容组件。
components: {
'cpn': {
template: '#myCpn'
},
'todo-title': {
props: ['title'],
template: '<h4>{{title}}</h4>'
},
'todo-items': {
props: ['item','index'],
template: '<li>{{index+1}}. {{item}}</li>'
}
}
- 实例化 Vue 并初始化数据
const app = new Vue({
el: '#app',
data: {
todoItems: ['Java','Python','Html']
}
}
- 将这些值通过插槽插入
<div id="app">
<cpn>
<todo-title slot="todo-title" title="邹小胖需学习内容"></todo-title>
<todo-items slot="todo-items" v-for="(item,index) in todoItems"
:item="item" :index="index"></todo-items>
</cpn>
</div>