组件化
React
的组件化最为彻底,甚至可以到函数级别的原子组件,高度的组件化可以是我们的工程易于维护、易于组合拓展天然分层
: jQuery
时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是 MVC
、MVP
还是MVVM
模式都能帮助我们进行分层,代码解耦更易于读写生态
: 现在主流前端框架都自带生态,不管是数据流管理架构还是 UI
库都有成熟的解决方案Vue
(读音 /vjuː/,类似于 view
),不要读错
Vue
是一个渐进式
的框架
渐进式
意味着你可以将Vue
作为你应用的一部分嵌入其中,带来更丰富的交互体验Vue
实现,那么Vue
的核心库以及其生态系统Core+Vue-router+Vuex
,也可以满足你各种各样的需求Vue
有很多特点和Web
开发中常见的高级功能
解耦视图和数据
可复用的组件
前端路由技术
状态管理
虚拟DOM
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script src="https://cdn.jsdelivr.net/npm/vue">script>
Vue对象
的时候,传入了一些options:{}
{}
中包含了el
属性:该属性决定了这个Vue对象
挂载到哪一个元素上,很明显,我们这里是挂载到了id
为app
的元素上{}
中包含了data
属性:该属性中通常会存储一些数据
message
这样网络
,从服务器加载的响应式
的<div id="app">{{message}}div>
<script src="../vue.js" charset="utf-8">script>
<script type="text/javascript">
// 编程范式:声明式编程
const app = new Vue({
el:'#app', // 用于挂载要管理的元素
data:{ // 定义数据
message:'hello Vue.js'
}
})
script>
HTML
中HTML
代码中,使用v-for
指令JavaScript
代码中完成DOM
的拼接相关操作响应式
的
<div id="app">
<ul>
<li v-for="item in movies">{{item}}li>
ul>
div>
<script src="../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['星际穿越', '大话西游', '少年派', '盗梦空间']
}
})
script>
methods
,该属性用于在Vue
对象中定义方法。@click
, 该指令用于监听某个元素的点击事件,并且需要指定当发生点击时,执行的方法(方法通常是methods
中定义的方法)<div id="app">
<h1>计数器:{{count}}h1>
<button type="button" name="button" v-on:click='jia'>+button>
<button type="button" name="button" @click='jian'>-button>
div>
<script src="../vue.js" charset="utf-8">script>
<script type="text/javascript">
// 编程范式: 声明式编程
const app = new Vue({
el:'#app', // 用于挂载要管理的元素
data:{ // 定义数据
count:0
},
methods:{
jia:function(){
this.count++;
},
jian:function(){
this.count--;
}
}
})
script>
MVVM
MVVM
思想
View
依然是我们的DOM
Model
就是我们我们抽离出来的objViewModel
就是我们创建的Vue
对象实例ViewModel
通过Data Binding
让obj
中的数据实时的在DOM
中显示ViewModel
通过DOM Listener
来监听DOM
事件,并且通过methods
中的操作,来改变obj
中的数据Vue
帮助我们完成VueModel
层的任务,在后续的开发,我们就可以专注于数据的处理,以及DOM
的编写工作View
层:
视图层
DOM
层。Model
层:
数据层
obj
,当然,里面的数据可能没有这么简单。VueModel
层:
视图模型层
View
和Model
沟通的桥梁。Data Binding
,也就是数据绑定,将Model
的改变实时的反应到View
中DOM Listener
,也就是DOM
监听,当DOM
发生一些事件(点击、滚动、touch
等)时,可以监听到,并在需要的情况下改变对应的Data
Vue
实例的时候,传入了一个对象options
options
中可以包含哪些选项呢?
el
:
string | HTMLElement
Vue
实例会管理哪一个DOM
data
:
Object | Function
(组件当中data
必须是一个函数)Vue
实例对应的数据对象methods
:
{ [key: string]: Function }
Vue
的一些方法,可以在其他地方调用,也可以在指令中使用Mustache
语法data
中的文本数据,插入到HTML
中呢?
双大括号
)Mustache
: 胡子/胡须.响应式
<div id="app">
<h2>{{message}}h2>
<h2>{{message}}, 李银河!h2>
<h2>{{firstName + lastName}}h2>
<h2>{{firstName + ' ' + lastName}}h2>
<h2>{{firstName}} {{lastName}}h2>
<h2>{{counter * 2}}h2>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
firstName: 'kobe',
lastName: 'bryant',
counter: 100
}
})
script>
v-once
语法Vue
的v-once
指令不需要跟任何表达式
(比如之前的v-for
后面是由跟表达式的)<div id="app">
<h2>{{message}}h2>
<h2 v-once>{{message}}h2>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
script>
v-html
语法HTML
代码
{{}}
来输出,会将HTML
代码也一起输出HTML
格式进行解析,并且显示对应的内容HTML
展示v-html
指令string
类型string
的html
解析出来并且进行渲染<div id="app">
<h2>{{url}}h2>
<h2 v-html="url">h2>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
url: '百度一下'
}
})
script>
v-text
作用和Mustache
比较相似:都是用于将数据显示在界面中v-text
通常情况下,接受一个string
类型v-text
不够灵活,会将标签中的文本内容覆盖
<div id="app">
<h2>{{message}}, Vue.js!h2>
<h2 v-text="message">, Vue.js!h2>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
script>
v-pre
语法v-pre
用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache
语法第一个h2
元素中的内容会被编译解析出来对应的内容第二个h2
元素中会直接显示{{message}}
<div id="app">
<h2>{{message}}h2>
<h2 v-pre>{{message}}h2>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
script>
v-cloak
语法js
卡顿情况下,我们浏览器可能会直接显然出未编译的Mustache
标签Vue
实例加载出来的时候,再显示html
cloak
: 斗篷<style>
[v-cloak] {
display: none;
}
style>
<div id="app" v-cloak>
<h2>{{message}}h2>
div>
<script src="../../vue.js">script>
<script>
// 在vue解析之前, div中有一个属性v-cloak
// 在vue解析之后, div中没有一个属性v-cloak
setTimeout(function () {
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
}, 1000)
script>
属性
我们也希望动态来绑定。
a
元素的href
属性img
元素的src
属性v-bind
指令:
动态绑定属性
:
any (with argument) | Object (without argument)
attrOrProp (optional)
v-bind
用于绑定一个或多个属性值,或者向另一个组件传递props
值链接src
、网站的链接href
、动态绑定一些类
、样式
等等Vue
实例中的data
绑定元素的src
和href
<div id="app">
<img v-bind:src="imgURL" alt="">
<a v-bind:href="aHref">百度一下a>
<img :src="imgURL" alt="">
<a :href="aHref">百度一下a>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
imgURL: 'https://img11.360buyimg.com/mobilecms/s350x250_jfs/t1/20559/1/1424/73138/5c125595E3cbaa3c8/74fc2f84e53a9c23.jpg!q90!cc_350x250.webp',
aHref: 'http://www.baidu.com'
}
})
script>
v-bind
有一个对应的语法糖,也就是简写方式
<div id="app">
<a :href="link">Vue.js官网a>
<img :src="logoURL" alt="">
div>
class
,比如:
class
有两种方式:
对象语法
数组语法
对象语法
:class
后面跟的是一个对象
用法一:直接通过{}绑定一个类
<h2 :class="{'active': isActive}">Hello Worldh2>
用法二:也可以通过判断,传入多个值
<h2 :class="{'active': isActive, 'line': isLine}">Hello Worldh2>
用法三:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title/active/line三个类
<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello Worldh2>
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello Worldh2>
数组语法
:class
后面跟的是一个数组
用法一:直接通过{}绑定一个类
<h2 :class="['active']">Hello Worldh2>
用法二:也可以传入多个值
<h2 :class=“[‘active’, ‘line']">Hello Worldh2>
用法三:和普通的类同时存在,并不冲突
注:会有title/active/line三个类
<h2 class="title" :class=“[‘active’, 'line']">Hello Worldh2>
用法四:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello Worldh2>
v-bind:style
来绑定一些CSS内联样式
CSS属性名
的时候,比如font-size
驼峰式 (camelCase) fontSize
短横线分隔
(kebab-case,记得用单引号括起来) ‘font-size’
对象语法
数组语法
对象语法
style
后面跟的是一个对象类型
key
是CSS
属性名称value
是具体赋的值,值可以来自于data
中的属性<h2 :style="{color: currentColor, fontSize: fontSize + 'px'}">{{messages}}h2>
数组语法
style
后面跟的是一个数组类型
<div v-bind:style="[baseStyles, overridingStyles]">div>
data
中的数据。firstName
和lastName
两个变量,我们需要显示完整的名称{{firstName}}
{{lastName}}
computed
选项中的<div id="app">
<h2>{{firstName}}{{lastName}}h2>
div>
<script>
const vm = new Vue({
el: '#app',
data: {
firstName: 'Kobe',
lastName: 'Bryant'
}
})
<script>
<div id="app">
<h2>{{fullName}}h2>
div>
<script>
const vm = new Vue({
el: '#app',
computed: {
fullName(){
return this.firstName + '' + this.lastName
}
}
})
<script>
<div id="app">
<h2>总价格: {{totalPrice}}h2>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
books: [
{id: 110, name: 'Unix编程艺术', price: 119},
{id: 111, name: '代码大全', price: 105},
{id: 112, name: '深入理解计算机原理', price: 98},
{id: 113, name: '现代操作系统', price: 87},
]
},
computed: {
totalPrice: function () {
let result = 0
for (let i=0; i < this.books.length; i++) {
result += this.books[i].price
}
return result
}
}
})
script>
getter
和一个setter
getter
来读取setter
方法(不常用)setter
的时候,代码如下:computed: {
fullName: {
set: function(newValue) {
// console.log('-----', newValue);
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1];
},
get: function () {
return this.firstName + ' ' + this.lastName
}
},
}
methods
和computed
区别
computed
会进行缓存,如果多次使用时,计算属性只会调用一次methods
会多次调用<div id="app">
<div>{{fullName}}div>
<div>{{fullName}}div>
<div>{{fullName}}div>
<div>{{getFullName()}}div>
<div>{{getFullName()}}div>
<div>{{getFullName()}}div>
div>
computed: {
fullName(){
console.log('执行fullName的计算属性');
return this.firstName + ' ' + this.lastName;
}
},
methods: {
getFullName(){
console.log('执行getFullName的方法');
return this.firstName + ' ' + this.lastName;
}
}
Vue
中使用v-on
指令监听事件v-on
介绍
绑定事件监听器
@
Function | Inline Statement | Object
event
v-on:click="counter++”
methods
中定义的函数<div id="app">
<h2>点击次数: {{counter}}h2>
<button v-on:click="counter++">按钮点击button>
<button v-on:click="btnClick">按钮点击button>
div>
<script>
const app = new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
btnClick(){
this.counter++
}
}
})
script>
v-on
也有对应的语法糖:
v-on:click
可以写成@click
<div id="app">
<h2>点击次数: {{counter}}h2>
<button @click="counter++">按钮点击button>
<button @click="btnClick">按钮点击button>
div>
methods
中定义方法,以供@click
调用时,需要注意参数问题:()
可以不添加
event
参数传递进去event
时,可以通过$event
传入事件<div id="app">
<h2>点击次数: {{counter}}h2>
<button @click="handleAdd">+1button>
<button @click="handleAddTen(10,$event)">+10button>
div>
methods: {
handleAdd(event){
console.log(event);
this.counter++
},
handleAddTen(count,event){
console.log(event);
this.counter += 10;
}
}
event
的目的可能是进行一些事件处理Vue
提供了修饰符来帮助我们方便的处理一些事件:
.stop
- 调用 event.stopPropagation()
.prevent
- 调用 event.preventDefault()
.{keyCode | keyAlias}
- 只当事件是从特定键触发时才触发回调.native
- 监听组件根元素的原生事件.once
- 只触发一次回调1. 停止冒泡
<button @click.stop="doThis">button>
2. 阻止默认行为
<button @click.prevent="doThis">button>
3. 阻止默认行为,没有表达式
<form @submit.prevent>form>
4. 串联修饰符
<button @click.stop.prevent="doThis">button>
5. 键修饰符,键别名
<input @keyup.enter="onEnter">
6. 键修饰符,键代码
<input @keyup.13="onEnter">
7. 点击回调只会触发一次
<button @click.once="doThis">button>
v-if
、v-else-if
、v-else
JavaScript
的条件语句if
、else
、else if
类似。Vue
的条件指令可以根据表达式的值在DOM
中渲染或销毁元素或组件<div id="app">
<h2 v-if="score>=90">优秀h2>
<h2 v-else-if="score>=80">良好h2>
<h2 v-else-if="score>=60">及格h2>
<h2 v-else>不及格h2>
div>
<script>
const app = new Vue({
el: '#app',
data: {
score: 99
}
})
script>
v-if
的原理:
v-if
后面的条件为false
时,对应的元素以及其子元素不会渲染DOM
中v-show
的用法和v-if
非常相似,也用于决定一个元素是否渲染:v-if
和v-show
对比v-if
和v-show
都可以决定一个元素是否渲染,那么开发中我们如何选择呢?
v-if
当条件为false
时,压根不会有对应的元素在DOM
中v-show
当条件为false
时,仅仅是将元素的display
属性设置为none
而已。频繁
时,使用v-show
一次切换
时,通过使用v-if
<div id="app">
<button @click="toggle">切换显示button>
<h2 v-show="isShow">我要不要显示呢h2>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
isShow: true
},
methods: {
toggle() {
this.isShow = !this.isShow
}
}
})
script>
v-for
来完成
v-for
的语法类似于JavaScript
中的for
循环item in items
的形式v-for="movie in movies"
movies
中取出movie
,并且在元素的内容中,我们可以使用Mustache
语法,来使用movie
v-for=(item, index) in items
index
就代表了取出的item
在原数组的索引值<div id="app">
<ul>
<li v-for="item in movies">{{item}}li>
<li v-for="(movie,index) in movies">{{index+1}}.{{movie}}li>
ul>
div>
<script>
const app = new Vue({
el: '#app',
data: {
movies: [
'星际穿越','盗墓笔记','大话西游','少年派'
]
}
})
script>
v-for
可以用户遍历对象
:
<div id="app">
<ul>
<li v-for="(value,key,index) in info">{{value}}-{{key}}-{{index}}li>
ul>
div>
<script>
const app = new Vue({
el: '#app',
data: {
info: [
name: 'xxx',
age: 18,
height: 1.80
]
}
})
script>
key属性
v-for
时,给对应的元素或组件添加上一个:key属性
key属性
呢(了解)?
Vue
的虚拟DOM
的Diff
算法有关系。React’s diff algorithm
中的一张图来简单说明一下:Diff
算法默认执行起来是这样的。key
来给每个节点做一个唯一标识
Diff
算法就可以正确的识别此节点key
的作用主要是为了高效的更新虚拟DOM
Vue
是响应式
的,所以当数据发生变化时,Vue
会自动检测数据变化,视图会发生对应的更新Vue
中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新methods: {
updateData: function(){
// 响应式数组方法
// 1. push方法
this.names.push('xxx','zs')
// 2. pop方法
this.names.pop()
// 3. unshift方法
this.names.unshift('xxx','zs')
// 4. shift方法
this.names.shift()
// 5. splice方法
// 传递一个参数(index):将对应index,以及后面的所有数据删除掉
this.names.splice(2)
// 6. sort排序数据
this.names.sort()
// 7. reverse反转数据
this.names.reverse()
// 注意: 通过索引值修改数组中的元素(不可响应式)
this.letters[0] = 'bbbbbb';
// 通过以下方法
this.letters.splice(0, 1, 'bbbbbb')
// set(要修改的对象, 索引值, 修改后的值)
Vue.set(this.letters, 0, 'bbbbbb')
Vue
中使用v-model
指令来实现表单元素和数据的双向绑定
input
中的v-model
绑定了message
,所以会实时将输入的内容传递给message
,message
发生改变。message
发生改变时,因为上面我们使用Mustache
语法,将message
的值插入到DOM
中,所以DOM
会发生响应的改变。v-model
实现了双向的绑定。v-model
用于textarea
元素<div id="app">
<input type="text" v-model="message">
{{message}}
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
}
})
script>
v-model
其实是一个语法糖,它的背后本质上是包含两个操作:
v-bind
绑定一个value
属性v-on
指令给当前元素绑定input
事件<input type="text" v-model="message">
等于下面代码
<input type="text" :value="message" @input="message = $event.target.value">
v-model: radio
<div id="app">
<label for="male">
<input type="radio" id="male" value="男" v-model="sex">男
label>
<label for="female">
<input type="radio" id="female" value="女" v-model="sex">女
label>
<h2>您选择的性别是: {{sex}}h2>
div>
<script src="../../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
sex: '女'
}
})
script>
v-model: checkbox
单个勾选框
和多个勾选框
单个勾选框
:
v-model
即为布尔值
input
的value
并不影响v-model
的值多个复选框
:
data
中属性是一个数组input
的value
添加到数组中<div id="app">
<label for="check">
<input type="checkbox" id="check" v-model="checked">同意协议
label>
<p>是否选中: {{checked}}p>
<label><input type="checkbox" v-model="hobbies" value="篮球">篮球label>
<label><input type="checkbox" v-model="hobbies" value="足球">足球label>
<label><input type="checkbox" v-model="hobbies" value="台球">台球label>
<p>您选中的爱好: {{hobbies}}p>
div>
<script>
const app = new Vue({
el: '#app',
data: {
checked: false, // 单选框
hobbies: [], // 多选框
})
script>
v-model:select
checkbox
一样,select
也分单选和多选两种情况v-model
绑定的是一个值。option
中的一个时,会将它对应的value
赋值到mySelect
中v-model
绑定的是一个数组。option
对应的value
添加到数组mySelects
中
<select name="abc" v-model="mySelect">
<option value="苹果">苹果option>
<option value="香蕉">香蕉option>
<option value="榴莲">榴莲option>
<option value="葡萄">葡萄option>
select>
<h2>您选择的水果是: {{mySelect}}h2>
<select name="abc" v-model="mySelects" multiple>
<option value="苹果">苹果option>
<option value="香蕉">香蕉option>
<option value="榴莲">榴莲option>
<option value="葡萄">葡萄option>
select>
<h2>您选择的水果是: {{mySelects}}h2>
<script>
const app = new Vue({
el: '#app',
data: {
mySelect: '香蕉',
mySelects: []
}
})
script>
value
赋值而已:
input
的时候直接给定的input
的值可能是从网络获取或定义在data
中的v-bind:value
动态的给value
绑定值 <label v-for="item in originHobbies" :for="item">
<input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}}
label>
<script>
const app = new Vue({
el: '#app',
data: {
hobbies: [], // 多选框,
originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球']
}
})
script>
lazy
修饰符:
v-model
默认是在input
事件中同步输入框的数据的data
中的数据就会自动发生改变lazy
修饰符可以让数据在失去焦点或者回车时才会更新
:number
修饰符:
number
修饰符可以让在输入框中输入的内容自动转成数字类型
:trim
修饰符:
trim
修饰符可以过滤内容左右两边的空格
<input type="text" v-model.lazy="message">
<h2>{{message}}h2>
<input type="number" v-model.number="age">
<h2>{{age}}-{{typeof age}}h2>
<input type="text" v-model.trim="name">
<h2>您输入的名字:{{name}}h2>
拆解
Vue.js
中的重要思想
创建组件构造器
注册组件
使用组件
div
看起来并没有什么区别。
来完成<div id="app">
<my-cpn>my-cpn>
div>
<script>
// 1.创建组件构造器对象(步骤一)
const myComponent = Vue.extend({
template: `
组件标题
我是组件中的一个段落内容
`
})
// 2.注册组件,并且定义组件标签的名称(步骤二)
Vue.component('my-cpn', myComponent)
const app = new Vue({
el: '#app'
})
script>
Vue.extend()
Vue.extend()
创建的是一个组件构造器
template
代表我们自定义组件的模板HTML
代码Vue2.x
的文档中几乎已经看不到了,它会直接使用下语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础Vue.component()
Vue.component()
是将刚才的组件构造器注册
为一个组件,并且给它起一个组件的标签名称参数
:
注册组件的标签名
组件构造器
Vue
实例下,否则它不会生效
Vue.component()
注册组件时,组件的注册是全局
的
任意Vue示例
下使用挂载
在某个实例中, 那么就是一个局部
组件全局组件
<div id="app">
<my-cpn>my-cpn>
div>
<div id="app2">
<my-cpn>my-cpn>
div>
<script>
const cpnC = Vue.extend({
template: `
组件标题
组件内容
`
})
Vue.component('my-cpn',cpnC)
const app = new Vue({
el: '#app'
})
script>
局部组件
<div id="app">
<my-cpn>my-cpn>
div>
<script>
const cpnC = Vue.extend({
template: `
组件标题
组件内容
`
})
const app = new Vue({
el: '#app',
components: {
my-cpn: cpnC
}
})
script>
层级
关系<div id="app">
<cpn2>cpn2>
div>
<script src="../vue.js">script>
<script>
// 1.创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
我是子组件
`
})
// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template: `
我是父组件
`,
components: {
cpn1: cpnC1
}
})
// root组件
const app = new Vue({
el: '#app',
components: {
cpn2: cpnC2
}
})
script>
错误
用法:以子标签
的形式在Vue
实例中使用
components
时,Vue
会编译好父组件
的模块父组件
将要渲染的HTML
(相当于父组件
中已经有了子组件
中的内容了)
是只能在父组件
中被识别的
是会被浏览器忽略的Vue
为了简化这个过程,提供了注册的语法糖Vue.extend()
的步骤,而是可以直接使用一个对象
来代替全局组件
和局部组件
:全局组件
// 2.注册组件
Vue.component('cpn1', {
template: `
我是标题1
我是内容, 哈哈哈哈
`
})
局部组件
// 2.注册局部组件的语法糖
const app = new Vue({
el: '#app',
components: {
'cpn2': {
template: `
我是标题2
我是内容, 呵呵呵
`
}
}
})
Vue
组件的注册过程,另外还有一个地方的写法比较麻烦,就是template
模块写法HTML
分离出来写,然后挂载
到对应的组件上,必然结构会变得非常清晰Vue
提供了两种方案来定义HTML
模块内容:
标签
标签
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
script>
<template id="cpn">
<div>
<h2>我是标题h2>
<p>我是内容,呵呵呵p>
div>
template>
<script>
// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
})
const app = new Vue({
el: '#app'
})
script>
单独
功能模块的封装:
HTML
模板,也应该有属性自己的数据data
不能直接访问Vue实例中的data数据
,而且即使可以访问,如果将所有的数据都放在Vue
实例中,Vue
实例就会变的非常臃肿
。Vue
组件应该有自己保存数据
的地方data
属性(也可以有methods
等属性)data
属性必须是一个函数
对象
,对象内部保存着数据// 1.注册一个全局组件
Vue.component('cpn', {
template: '#cpn',
data() {
return {
title: 'abc'
}
}
})
Vue
直接就会报错
Vue
让每个组件对象都返回一个新的对象
,因为如果是同一个对象的,组件在多次使用后会相互影响
Vue
实例的数据的父组件
)将数据传递给小组件(子组件
)Vue
官方提到
Vue
实例当做父组件,并且其中包含子组件来简化代码Vue
实例和子组件的通信和父组件和子组件的通信过程是一样的props
来声明需要从父级
接收到的数据props
的值有两种方式:
字符串数组
,数组中的字符串就是传递时的名称对象
,对象可以设置传递时的类型,也可以设置默认值等props
传递:<div id="app">
<child-cpn :cmessage="message">child-cpn>
div>
<template id="childCpn">
<div>显示信息:{{message}}div>
template>
<script src="../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
'child-cpn': {
template: '#childCpn',
props: ['cmessage']
}
}
})
script>
props
选项是使用一个数组
数组
之外,我们也可以使用对象
,当需要对props
进行类型等验证
时,就需要对象
写法了验证
都支持哪些数据类型呢?
String
Number
Boolean
Array
Object
Date
Function
Symbol
自定义构造函数
时,验证也支持自定义的类型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
}
}
}
})
function Person (firstName,lastName) {
this.firstName = firstName
this.lastName = lastName
}
vue.component('blog-post',{
props: {
author: Person
}
})
驼峰处
用-
代替<div id="app">
<cpn :c-info="info" :child-my-message="message" v-bind:class>cpn>
div>
<template id="cpn">
<div>
<h2>{{cInfo}}h2>
<h2>{{childMyMessage}}h2>
div>
template>
const cpn = {
template: '#cpn',
props: {
cInfo: {
type: Object,
default() {
return {}
}
},
childMyMessage: {
type: String,
default: ''
}
}
}
props
用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中自定义事件
来完成子组件需要向父组件传递数据
时,就要用到自定义事件了v-on
不仅仅可以用于监听DOM
事件,也可以用于组件间的自定义事件子组件
中,通过$emit()
来触发事件v-on
来监听子组件事件counter
counter
,传给父组件的某个属性,比如total
<div id="app">
<cpn @increment='changeTotal' @decrement='changeTotal'>cpn>
<h2>点击次数:{{total}}h2>
div>
<template id="childCpn">
<div class="">
<button type="button" name="button" @click='increment'>+1button>
<button type="button" name="button" @click='decrement' :disabled='counter <= 0'>-1button>
div>
template>
<script src="../vue.js" charset="utf-8">script>
<script type="text/javascript">
const cpn = {
template: '#childCpn',
data(){
return {
counter: 0
}
},
methods: {
increment(){
this.counter++;
this.$emit('increment',this.counter)
},
decrement(){
this.counter--;
this.$emit('decrement',this.counter)
}
}
}
const vm = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
changeTotal(counter) {
this.total = counter;
}
},
components: {
cpn
}
})
script>
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
div>
<template id="cpn">
<div>
<h2>props:{{number1}}h2>
<h2>data:{{dnumber1}}h2>
<input type="text" v-model="dnumber1">
<h2>props:{{number2}}h2>
<h2>data:{{dnumber2}}h2>
<input type="text" v-model="dnumber2">
div>
template>
<script src="../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
num1change(value) {
this.num1 = parseFloat(value)
},
num2change(value) {
this.num2 = parseFloat(value)
}
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number,
name: ''
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2
}
},
watch: {
dnumber1(newValue) {
this.dnumber2 = newValue * 100;
this.$emit('num1change', newValue);
},
dnumber2(newValue) {
this.number1 = newValue / 100;
this.$emit('num2change', newValue);
}
}
}
}
})
script>
父组件直接访问子组件
,子组件直接访问父组件
,或者是子组件访问根组件。
$children
或$refs
reference(引用)$parent
$children
$children
的访问
this.$children
是一个数组
类型,它包含所有子组件对象。message
状态。<div id="app">
<parent-cpn>parent-cpn>
div>
<template id="parentCpn">
<div>
<child-cpn1>child-cpn1>
<child-cpn2>child-cpn2>
<button @click="showChildCpn">显示所有子组件信息button>
div>
template>
<template id="childCpn1">
<h2>我是子组件1h2>
template>
<template id="childCpn2">
<h2>我是子组件2h2>
template>
Vue.component('parent-cpn',{
template: '#parentCpn',
methods: {
showChildCpn() {
for(let i = 0; i < this.$children.length; i++) {
console.log(this.$children[i].message)
}
}
}
}
$children
的缺陷:
$children
访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。$refs
$refs
$refs
的使用:
$refs
和ref
指令通常是一起使用的。ref
给某一个子组件绑定一个特定的ID
。this.$refs.ID
就可以访问到该组件了。<child-cpn1 ref="child1">child-cpn1>
<child-cpn2 ref="child2">child-cpn2>
<button @click="showRefsCpn">通过refs访问子组件button>
showRefsCon() {
console.log(this.$rsfs.child1.message);
console.log(this.$rsfs.child2.message);
}
$parent
$parent
Vue
开发中,我们允许通过$parent
来访问父组件,但是在真实开发中尽量不要这样做耦合度
太高了$parent
直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护<div id="app">
<parent-cpn>parent-cpn>
div>
<template id="parentCpn">
<child-cpn>child-cpn>
template>
<template id="childCpn">
<button @click="showParent">显示父组件信息button>
template>
Vue.component('parent-cpn',{
template: '#parentCpn',
data() {
return {
message: '我是父组件‘
}
},
components: {
'child-cpn': {
template: '#childCpn',
methods: {
showParent() {
console.log(this.$parent.message);
}
}
}
}
})
Vue1.x
的时候,可以通过$dispatch
和$broadcast
完成
$dispatch
用于向上级派发事件$broadcast
用于向下级广播事件Vue2.x
都被取消了Vue2.x
中,有一种方案是通过中央事件总线
,也就是一个中介来完成
Vuex
的状态管理方案还是逊色很多Vuex
提供了更多好用的功能slot
翻译为插槽
:
目的
是让我们原来的设备具备更多的扩展性
封装
的组件更加具有扩展性
nav-bar
组件。封装
这类的组件呢?
抽取共性,保留不同
将共性抽取到组件中,将不同暴露为插槽
slot
?
就可以为子组件开启一个插槽
中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容<div id="app">
<my-cpn>my-cpn>
<my-cpn>
<h2>我是替换插槽的内容h2>
my-cpn>
div>
<template id="myCpn">
<div>
<slot>我是一个插槽的默认内容slot>
div>
template>
<script src="../vue.js">script>
<script>
const app = new Vue({
el: '#app',
components: {
myCpn: {
template: '#myCpn'
}
}
})
script>
名字
slot
元素一个name
属性即可,
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
script>
编译作用域
中,我们使用了isShow
属性isShow
属性包含在组件中,也包含在Vue
实例中。Vue
实例的属性。
的时候,整个组件的使用过程是相当于在父组件中出现的。isShow
使用的是Vue实例中的属性,而不是子组件的属性。<div id="app">
<cpn v-show="isShow">cpn>
<cpn v-for="item in names">cpn>
div>
<template id="cpn">
<div>
<h2>我是子组件h2>
<p>我是内容, 哈哈哈p>
<button v-show="isShow">按钮button>
div>
template>
<script src="../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isShow: true
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false
}
}
},
}
})
script>
slot
一个比较难理解的点,而且官方文档说的又有点不清晰。父组件替换插槽的标签,但是内容由子组件来提供
pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']
<div id="app">
<cpn>cpn>
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join(' - ')}}span>
template>
cpn>
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join(' * ')}}span>
template>
cpn>
div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}li>
ul>
slot>
div>
template>
<script src="../vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
}
}
}
}
})
script>
js
制作作为一种脚本语言
,做一些简单的表单验证或动画实现等,那个时候代码还是很少的
标签中即可ajax
异步请求的出现,慢慢形成了前后端的分离
js
文件中,进行维护全局变量同名
问题js
文件的依赖顺序几乎是强制性
的
js
文件过多,比如有几十个的时候,弄清楚它们的顺序
是一件比较头疼的事情。// aaa.js文件中,小明定义了一个变量,名称是flag,并且为true
flag = true
// bbb.js文件中,小丽也喜欢用flag这个变量名称是,只是为false
flag = false
// main.js文件中,小明想通过flag进行一些判断,完成后续的事情
if (flag) {
console.log('小明是个天才');
}
匿名函数
来解决方面的重名
问题
aaa.js
文件中,我们使用匿名函数
(function(){
var flag = true
})()
main.js
文件中,用到flag
,应该如何处理呢?flag
是一个局部
变量匿名函数
内部,定义一个对象。MoudleA
接受。man.js
中怎么使用呢?
前端模块化开发
已经有了很多既有的规范,以及对应的实现方案CommonJS
、AMD
、CMD
,也有ES6
的Modules
var ModuleA = (function() {
// 1. 定义一个对象
var obj = {}
// 2. 在对象内部添加变量和方法
obj.flag = true
obj.myFunc = function(info){
console.log(info);
}
// 3. 将对象返回
return obj
})()
if (ModuleA.flag){
console.log('小明是个天才');
}
ModuleA.myFunc('小明长得真帅')
console.log(ModuleA);
导出
和导入
CommonJS
的导出:module.exports = {
flag: true,
test(a,b){
return a + b
},
demo(a,b){
return a * b
}
}
CommonJS
的导入// CommonJS模块
let {test,demo,flag} = require('moduleA');
// 等同于
let _mA = require('moduleA');
let test = _mA.test;
let demo = _mA.demo;
let flag = _mA.flag;
export
指令用于导出变量
,比如下面的代码:// info.js
export let name = 'why'
export let age = 18
export let height = 1.88
// info.js
let name = 'why'
let age = 18
let height = 1.88
export {name,age,height}
输出变量
,也可以输出函数
或者输出类
export function test(content){
console.log(content);
}
export class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
run(){
console.log(this.name + '在奔跑');
}
}
function test(content){
console.log(content);
}
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
run(){
console.log(this.name + '在奔跑');
}
}
export {test,Person}
export default
// info.js
export default function(){
console.log('default function');
}
main.js
中,这样使用就可以了
myFunc
是我自己命名的,你可以根据需要命名它对应的名字import myFunc from './info.js'
myFunc()
export default
在同一个模块中,不允许同时存在多个export
指令导出了模块对外提供的接口,下面我们就可以通过import
命令来加载对应的这个模块了HTML
代码中引入两个js
文件,并且类型需要设置为module
<script src="info.js" type="module">script>
<script src="main.js" type="module">script>
import
指令用于导入模块中的内容,比如main.js
的代码import {name,age,height} from "./info.js"
console.log(name,age,height);
*
可以导入模块中所有的export
变量*
起一个别名,方便后续的使用import * as info from './info.js'
console.log(info.name, info.age, info.height, info.friends);
Vue
的Demo
程序, 那么不需要Vue CLI
.Vue CLI
Vue.js
开发大型应用时,我们需要考虑代码目录结构
、项目结构
和部署
、热加载
、代码单元测试
等事情。CLI
是什么意思?
CLI
是Command-Line Interface
, 翻译为命令行界面, 但是俗称脚手架
.Vue CLI
是一个官方发布 vue.js
项目脚手架vue-cli
可以快速搭建Vue
开发环境以及对应的webpack
配置.NodeJS
Node
和NPM
8.9以上
或者更高版本node -v
npm -v
NPM
呢?
NPM
的全称是Node Package Manager
NodeJS
包管理和分发工具,已经成为了非官方的发布Node
模块(包)的标准。NPM
来安装一些开发过程中依赖包.cnpm
安装
npm
的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像
。cnpm
(gzip 压缩支持
) 命令行工具代替默认的 npm
:
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm
命令来安装模块了:
cnpm install [name]
Vue.js
官方脚手架工具就使用了webpack
模板
优化操作
Webpack
的全局安装
npm install webpack -g
Vue
脚手架
npm install -g @vue/cli
vue --version
Vue CLI3
的版本,如果需要想按照Vue CLI2
的方式初始化项目时是不可以
的Vue CLI2
初始化项目
vue init webpack my-project
Vue CLI3
初始化项目
vue create my-project
template
,就需要选择Runtime-Compiler
.vue
文件夹开发,那么可以选择Runtime-only
Runtime-Compiler
和 Runtime-only
new Vue({
el: '#app',
components: { App },
template: ' '
})
new Vue({
el: '#app',
render: h => h(App)
})
Vue应用程序
是如何运行起来的Vue
中的模板如何最终渲染成真实DOM
new Vue({
el: '#app',
render: (createElement) => {
// 1. 使用方式一:
return createElement('标签','相关数据对象(可以不传)'.['内容数组'])
// 1.1 render函数基本使用
return createElement('div',{class: 'box'}, ['xxx'])
// 1.2 嵌套render函数
return createElement('div',{class: 'box'}, ['xxx', createElement('h2', ['标题'])])
}
})
const cpn = Vue.component('cpn',{
template: '我是cpn组件',
data() {
return {
}
}
})
new Vue({
el: '#app',
render: (createElement) => {
// 2. 使用方式二:传入一个组件对象
return createElement(cpn)
}
})
resolve: {
extensions: ['.js','.vue','.json'],
alias: {
'@': resolve('src'),
'pages': resolve('src/pages'),
'common':resolve('src/common'),
'components': resolve('src/components'),
'network': resolve('src/network')
}
}
vue-cli 3
与 2 版本有很大区别
UI
方面的配置
vue ui
const path = require('path')
function resolve (dir) {
return path.join(__dirname,dir)
}
module.exports = {
// 1. 基础的配置方式
configureWebpack: {
resolve: {
alias: {
'components': '@/components',
'pages': '@/pages'
}
}
},
// 2. 利用webpack4的webpack-chain来配置
chainWebpack: (config) => {
config.resolve.alias
.set('@$',resolve('src'))
.set('components',resolve('src/components')
}
}
routing
)就是通过互联的网络把信息从源地址传输到目的地址的活动. — 维基百科路由
和转送
路由
是决定数据包从来源到目的地的路径.转送
将输入端的数据转移到合适的输出端.HTML
页面是由服务器来渲染的.
HTML
页面, 返回给客户端进行展示.URL
.URL
会发送到服务器, 服务器会通过正则对该URL
进行匹配, 并且最后交给一个Controller
进行处理.Controller
进行各种处理, 最终生成HTML
或者数据
, 返回给前端.IO操作
.后端路由
.
js
和css
, 可以直接交给浏览器展示, 这样也有利于SEO的优化
.后端路由
的缺点
:
PHP
和Java
等语言来编写页面代码.HTML
代码和数据
以及对应的逻辑
会混在一起, 编写和维护都是非常糟糕的事情.前后端分离
阶段:
Ajax
的出现, 有了前后端分离的开发模式.API
来返回数据, 前端通过Ajax
获取数据, 并且可以通过JavaScript
将数据渲染到页面中.前后端责任的清晰
, 后端
专注于数据
上, 前端
专注于交互
和可视化
上.移动端(iOS/Android)
出现后, 后端不需要进行任何处理, 依然使用之前的一套API
即可.SPA
最主要的特点就是在前后端分离的基础上加了一层前端路由.URL
,但是页面不进行整体的刷新。pushState
history
接口是HTML5
新增的, 它有五种模式改变URL
而不刷新页面.history.pushState()
replaceState
go
history.back()
等价于 history.go(-1)
history.forward()
则等价于 history.go(1)
Angular
的ngRouter
React
的ReactRouter
Vue
的vue-router
vue-router
vue-router
是Vue.js
官方的路由插件,它和vue.js
是深度集成的,适合用于构建单页面应用。vue-router
是基于路由
和组件
的
vue-router
的单页面应用中, 页面的路径的改变就是组件的切换.webpack
, 后续开发中我们主要是通过工程化
的方式进行开发的.
npm
来安装路由
即可.vue-router
npm install vue-router --save
Vue.use()
来安装路由功能)
导入
路由对象,并且调用 Vue.use(VueRouter)
路由实例
,并且传入路由映射配置
Vue
实例中挂载
创建的路由实例
import Vue from 'vue'
import VueRouter form 'vue-router'
Vue.use(VueRouter)
vue-router
的步骤:
和
: 该标签会根据当前的路径, 动态渲染出不同的组件.
处于同一个等级.
挂载的组件, 其他内容不会发生改变.
渲染首页的内容.
渲染首页组件呢?
const routes = [
{
path: '/',
redirect: '/home'
}
]
routes
中又配置了一个映射.path
配置的是根路径: /
redirect
是重定向
, 也就是我们将根路径重定向到/home
的路径下, 这样就可以得到我们想要的结果了.URL的hash
HTML5的history
URL的hash.
HTML5
的history
模式, 非常简单, 进行如下配置即可:const router = new VueRouter({
routes,
mode: 'history'
})
中, 我们只是使用了一个属性: to
, 用于指定跳转的路径.
还有一些其他属性:
class
具体的名称也可以通过router
实例的属性进行修改exact-active-class
active-class
, 只是在精准匹配下才会出现的class
.path
路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:
/user/aaaa
或/user/bbbb
/user
之外,后面还跟上了用户的ID
path
和Component
的匹配关系,我们称之为动态路由(也是路由传递数据的一种方式)。{
path: '/user/:id',
component: User
}
<div>
<h2>{{$route.params.id}}h2>
div>
<router-link to="/user/123">用户router-link>
Javascript
包会变得非常大,影响页面加载。js
文件中.js
文件中, 必然会造成这个页面非常的大.短暂空白
的情况.将路由对应的组件打包成一个个的js代码块.
被访问
到的时候, 才加载对应的组件结合Vue的异步组件和Webpack的代码分析
.const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
ADM写法
const About = resolve => require(['../components/About.vue'], resolve);
ES6
中, 我们可以有更加简单的写法来组织Vue
异步组件和Webpack
的代码分割.const Home = () => import('../components/Home.vue')
home
页面中, 我们希望通过/home/news
和/home/message
访问一些内容.
标签.params
和query
params
的类型:
/router/:id
path
后面跟上对应的值/router/123
, /router/abc
query
的类型:
/router
, 也就是普通配置query
的key
作为传递方式/router?id=123
, /router?id=abc
的方式和JavaScript
代码方式
JavaScript代码
$route
对象获取的.
vue-router
的应用中,路由对象会被注入每个组件中,赋值为 this.$route
,并且当路由切换时,路由对象会被更新。$route
获取传递的信息如下:$route
和$router
是有区别的$route
和$router
是有区别的
SPA
应用中, 如何改变网页的标题呢?
来显示的, 但是SPA
只有一个固定的HTML
, 切换不同的页面时, 标题并不会改变.JavaScript
来修改
的内容.window.document.title = '新的标题'.
Vue
项目中, 在哪里修改? 什么时候修改比较合适呢?.vue
文件中.mounted
声明周期函数, 执行对应的代码进行修改即可.vue-router
提供的导航守卫主要用来监听路由的进入和离开
的.vue-router
提供了beforeEach
和afterEach
的钩子函数, 它们会在路由即将改变前
和改变后
触发.beforeEach
来完成标题的修改.
to
: 即将要进入的目标的路由对象.from
: 当前导航即将要离开的路由对象.next
: 调用该方法后, 才能进入下一个钩子.后置钩子
, 也就是afterEach
, 不需要主动调用next()
函数.全局守卫.
keep-alive
是 Vue
内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
include
- 字符串或正则表达,只有匹配的组件会被缓存exclude
- 字符串或正则表达式,任何匹配的组件都不会被缓存router-view
也是一个组件,如果直接被包在 keep-alive
里面,所有路径匹配到的视图组件都会被缓存:<keep-alive>
<router-view>
router-view>
keep-alive>
create
声明周期函数来验证TabBar组件
,你如何封装
TabBar组件
,在APP
中使用TabBar
出于底部,并且设置相关的样式TabBar
中显示的内容由外界决定
插槽
flex
布局平分TabBar
TabBarItem
,可以传入 图片和文字
TabBarItem
,并且定义两个插槽:图片、文字。div
,用于设置样式。TabBar
的效果active-icon
的数据isActive
,通过v-show
来决定是否显示对应的icon
TabBarItem
绑定路由数据
npm install vue-router —save
router/index.js
的内容,以及创建对应的组件main.js
中注册router
APP
中加入
组件item
跳转到对应路由,并且动态决定isActive
item
的点击,通过this.$router.replace()
替换路由路径this.$route.path.indexOf(this.link) !== -1
来判断是否是active
active
样式
this.isActive ? {'color': 'red'} : {}
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式
集中式存储管理
应用的所有组件的状态,并以相应的规则
保证状态以一种可预测
的方式发生变化Vuex
也集成到 Vue
的官方调试工具 devtools extension
,提供了诸如零配置的 time-travel
调试、状态快照
导入导出等高级调试功能状态管理模式
、集中式存储管理
这些名词听起来就非常高大上,让人捉摸不透Vue
实例中,让其他组件可以使用变量属性
了呢插件Vuex
呢?难道我们不能自己封装一个对象来管理吗?
VueJS
带给我们最大的便利是什么呢?就是响应式
Vuex
就是为了提供这样一个在多个组件间共享状态
的插件多个状态,在多个界面间的共享问题
登录状态
、用户名称
、头像
、地理位置信息
等等。商品的收藏
、购物车中的物品
等等。状态信息
,我们都可以放在统一
的地方,对它进行保存和管理
,而且它们还是响应式
的<template>
<div class="test">
<div>当前计数:{{counter}}div>
<button @click="counter+=1">+1button>
<button @click="counter-=1">-1button>
div>
template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
counter: 0
}
}
}
script>
counter
counter
需要某种方式被记录下来,也就是我们的State
counter
目前的值需要被显示在界面中,也就是我们的View
部分操作
时(我们这里是用户的点击
,也可以是用户的input
),需要去更新状态
,也就是我们的Actions
Vue
已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
多个视图都依赖同一个状态
(一个状态改了,多个界面需要进行更新)Actions
都想修改同一个状态(Home.vue
需要修改,Profile.vue
也需要修改这个状态)(状态1/状态2/状态3)
来说只属于我们某一个视图
,但是也有一些状态(状态a/状态b/状态c)
属于多个视图共同想要维护
的
状态1/状态2/状态3
你放在自己的房间中,你自己管理自己用,没问题。状态a/状态b/状态c
我们希望交给一个大管家来统一帮助我们管理Vuex
就是为我们提供这个大管家的工具
全局单例模式
统一进行管理
操作
Vuex
背后的基本思想。Vuex
代码:
store
,并且在其中创建一个index.js
文件index.js
文件中写入如下代码:import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
}
})
Vue组件
都可以使用这个store对象
main.js
文件,导入store对象
,并且放在new Vue
中Vue组件
中,我们就可以通过this.$store
的方式,获取到这个store对象
了import Vue from 'vue'
import App from './App'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
<template>
<div id="app">
<p>{{counter}}p>
<button @click="increment">+1button>
<button @click="decrement">-1button>
div>
template>
<script>
export default {
name: 'App',
components: {
},
computed: {
count: function() {
return this.$store.state.count
}
},
methods: {
increment: function() {
this.$store.commit('increment')
},
decrement: function() {
this.$store.commit('decrement')
}
}
}
script>
Vuex
最简单的方式了store对象
,用于保存
在多个组件中共享的状态
store对象
放置在new Vue对象
中,这样可以保证在所有的组件中都可以使用到store对象
中保存的状态即可
this.$store.state.属性
的方式来访问
状态this.$store.commit('mutation中方法')
来修改
状态提交mutation
的方式,而非直接改变store.state.count
Vuex
可以更明确的追踪状态的变化,所以不要直接改变store.state.count
的值Vuex
提出使用单一状态树
, 什么是单一状态树呢?
Single Source of Truth
,也可以翻译成单一数据源
类比
记录
,比如上学时的个人档案
,工作后的社保记录
,公积金记录
,结婚后的婚姻信息
,以及其他相关的户口、医疗、文凭、房产记录
等等。低效
,而且不方便管理
,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护
,当然国家目前已经在完善我们的这个系统了)。Store对象
中的,那么之后的管理
和维护
等等都会变得特别困难。Vuex
也使用了单一状态树
来管理应用层级的全部状态
单一状态树
能够让我们最直接
的方式找到某个状态的片段,而且在之后的维护
和调试
过程中,也可以非常方便的管理
和维护
store
中获取一些state变异后的状态
,比如下面的Store
中:
const store = new Vuex.Store({
state: {
students: [
{id: 110, name: 'zs', age: 18},
{id: 111, name: 'ls', age: 21},
{id: 112, name: 'we', age: 25},
{id: 113, name: 'zx', age: 30}
]
}
})
Store
中定义getters
computed: {
getGreaterAgesCount() {
return this.$store.students.filter(age => age >= 20).length
}
},
getters: {
greaterAgesCount: state => {
return state.students.filter(s => s.age >= 20).length
}
}
getters
, 那么代码可以这样来写getters: {
greaterAgesStus: state => {
return state.students.filter(s => s.age >= 20)
},
greaterAgesCount: (state,getters) => {
return getters.greaterAgesStus.length
}
}
getters
默认是不能传递参数的, 如果希望传递参数, 那么只能让getters
本身返回另一个函数.
ID
获取用户的信息getters: {
stuByID: state => {
return id => {
return state.students.find(s => s.id === id)
}
}
}
Vuex
的store状态
的更新唯一方式:提交Mutation
Mutation
主要包括两部分:
字符串的事件类型(type)
一个回调函数(handler)
,该回调函数的第一个参数就是state
mutation
的定义方式:mutations: {
increment(state) {
state.count++
}
}
mutation更新
increment: function () {
this.$store.commit('increment')
}
mutation
更新数据的时候, 有可能我们希望携带一些额外的参数
mutation的载荷(Payload)
Mutation
中的代码:decrement(state,n) {
state.count -= n
}
decrement: function () {
this.$store.commit('decrement',2)
}
以对象的形式传递
, 也就是payload
是一个对象
changeCount(state,payload) {
state.count = payload.count
}
changeCount: function () {
this.$store.commit('changeCount',{count: 0})
}
commit
进行提交是一种普通的方式Vue
还提供了另外一种风格, 它是一个包含type
属性的对象this.$store.commit({
type: 'changeCount',
count: 100
})
Mutation
中的处理方式是将整个commit
的对象作为payload
使用, 所以代码没有改变, 依然如下:changeCount(state,payload) {
state.count = payload.count
}
Vuex
的store
中的state
是响应式
的, 当state
中的数据发生改变时, Vue
组件会自动更新
Vuex
对应的规则:
store
中初始化好所需的属性.state
中的对象添加新属性时, 使用下面的方式:
Vue.set(obj, 'newProp', 123)
重新赋值
点击更新信息
时, 界面并没有发生对应改变.App.vue
<template>
<div id="app">
<p>我的个人信息:{{info}}p>
<button @click="updateInfo">更新信息button>
div>
template>
<script>
export default {
name: 'App',
components: {
},
computed: {
info () {
return this.$store.state.info
}
},
methods: {
updateInfo () {
this.$store.commit('updateInfo', {height: 1.88})
}
}
}
script>
Vuex
const store = new Vuex.Store({
state: {
info: {
name: 'xxx', age: 18
}
},
mutations: {
updateInfo(state,payload) {
state.info['height'] = payload.height
}
}
})
state
中的属性是响应式
的mutations: {
updateInfo(state,payload) {
// state.info['height'] = payload.height
// 方式一: Vue.set()
Vue.set(state.info, 'height', payload.height)
// 方式二: 给 info 赋值一个新的对象
state.info = {...state.info, 'height': payload.height}
}
}
mutation
中, 我们定义了很多事件类型(也就是其中的方法名称).Vuex
管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation
中的方法越来越多.Flux
实现中, 一种很常见的方案就是使用常量替代Mutation事件
的类型.app
所有的事件类型一目了然.mutation-types.js
, 并且在其中定义我们的常量.ES2015
中的风格, 使用一个常量来作为函数的名称.mutation-types.js
export const UPDATE_INFO = 'UPDATE_INFO'
index.js
import Vuex from 'vuex'
import Vue from 'vue'
import * as types from './mutation-types'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
info: {
name: 'xxx', age: 18
}
},
mutations: {
[types.UPDATE_INFO](state, payload) {
state.info = {...state.info, 'height': payload.height}
}
}
})
export default store
App.vue
<script>
import {UPDATE_INFO} from "./store/mutation-types";
export default {
name: 'App',
components: {
},
computed: {
info() {
return this.$store.state.info
}
},
methods: {
updateInfo() {
this.$store.commit(UPDATE_INFO, {height: 1.88})
}
}
}
script>
Vuex
要求我们Mutation
中的方法必须是同步方法
devtools
时, 可以devtools
可以帮助我们捕捉mutation
的快照.异步操作
, 那么devtools
将不能很好的追踪这个操作什么时候会被完成.devtools
中会有如下信息:Vuex
中的代码, 我们使用了异步函数
:mutations: {
[types.UPDATE_INFO](state,payload) {
setTimeout(() => {
state.info = {...state.info, 'height': payload.height}
},10000)
}
}
Mutation
中进行异步操作
Vuex
中进行一些异步操作
, 比如网络请求
, 必然是异步的. 这个时候怎么处理呢?Action
类似于Mutation
, 但是是用来代替Mutation
进行异步操作
的Action
的基本使用代码如下:context
是什么?
context
是和store对象
具有相同方法和属性的对象.context
去进行commit
相关的操作, 也可以获取context.state
等.Vuex
中有异步操作
, 那么我们就可以在actions
中完成了const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
increment(context) {
context.commit('increment')
}
}
})
Vue
组件中, 如果我们调用action
中的方法, 那么就需要使用dispatch
methods: {
increment() {
this.$store.dispatch('increment')
}
}
支持传递payload
methods: {
increment() {
this.$store.dispatch('increment',{cCount: 5})
}
}
mutations: {
increment(state,payload) {
state.count += payload.cCount
}
},
actions: {
increment(context,payload) {
setTimeout(() => {
context.commit('increment',payload)
},5000)
}
}
ES6
语法的时候, Promise
经常用于异步操作
Action
中, 我们可以将异步操作
放在一个Promise
中, 并且在成功或者失败后, 调用对应的resolve
或reject
actions: {
increment(context) {
return new Promise((resolve) => {
setTimeout(() => {
context.commit('increment')
resolve()
},1000)
})
}
}
methods: {
increment() {
this.$store.dispatch('increment').then(res => {
console.log('完成了更新操作')
})
}
}
Module
是模块的意思, 为什么在Vuex
中我们要使用模块呢?
Vue
使用单一状态树
,那么也意味着很多状态都会交给Vuex
来管理.store对象
就有可能变得相当臃肿.Vuex
允许我们将store
分割成模块(Module)
, 而每个模块拥有自己的state
、mutations
、actions
、getters
等const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
moduleA
中添加state
、mutations
、getters
mutation
和getters
接收的第一个参数是局部状态
对象const moduleA = {
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
}
const moduleB = {
}
const store = new Vuex.Store({
state: {
gCount: 111
},
modules: {
a: moduleA,
b: moduleB
}
})
export default store;
<script>
export default {
name: 'App',
components: {
},
computed: {
count() {
return this.$store.getters.doubleCount
}
},
methods: {
increment() {
this.$store.commit('increment')
}
}
}
script>
doubleCount
和increment
都是定义在对象内部
的this.$store
来直接调用的actions
的写法呢? 接收一个context参数对象
局部状态
通过 context.state
暴露出来,根节点状态
则为 context.rootState
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state,commit,rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
getters
中也需要使用全局的状态, 可以接受更多的参数const moduleA = {
// ...
getters: {
sumWithRootCount (state,getters,rootState) {
return state.count + rootState.count
}
}
}
Vue
中发送网络请求有非常多的方式, 那么, 在开发中, 如何选择呢?
Ajax
Ajax
是基于XMLHttpRequest(XHR)
配置
和调用方式
等非常混乱.jQuery-Ajax
jQuery-Ajax
jQuery-Ajax
,相对于传统的Ajax
非常好用.Vue
的整个开发中都是不需要使用jQuery
了.jQuery
, 你觉得合理吗?jQuery
的代码1w+行.Vue
的代码才1w+行.网络请求
就引用这个重量级
的框架.Vue-resource
Vue1.x
的时候, 推出了Vue-resource
.
Vue-resource
的体积相对于jQuery
小很多.Vue-resource
是官方推出的.Vue2.0
退出后, Vue
作者就在GitHub
的Issues
中说明了去掉vue-resource
, 并且以后也不会再更新
.vue-reource
不再支持新的版本时, 也不会再继续更新
和维护
.隐患
.axios
vue-resource
的同时, 作者还推荐了一个框架: axios
axios
有非常多的优点, 并且用起来也非常方便.网络请求
方式就是JSONP
JSONP
最主要的原因往往是为了解决跨域访问
的问题.JSONP
的原理是什么呢?
JSONP
的核心在于通过