Vue基础
1. VUE简介
MVVM思想
M:Model,模型,包括数据和一些基本操作
V:View,视图,页面渲染结果
VM:即View-Model,模型与视图之间的双向操作(无需开发人员干涉)
个人理解,像是消息中间件的原理,subscriber和publisher通过VM进行自动交互.
在MVVM之前,先要从后端获取数据模型,然后要通过DOM操作Model渲染到View中.
然后当用户操作视图,还要通过DOM获取View中的数据,然后同步到Model中.
而MVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用再关心Model和View之间如何互相影响的,
只需要将他们都与VM关联起来即可.
双向绑定:
·只要Model发生了变化,View上自然就会表现出来
·当用户修改了View,Model中的数据也会跟着改变
中文官网地址:
cn.vuejs.org
2. 安装
首先,Vue的运行需要Node.js的支持,就像Java需要JDK一样.
所以我们需要
- 先安装Node.js
http://nodejs.cn/download/
查看版本号,以确定是否安装成功
npm -v
- 初始化环境
安装 好Node.js后,新建一个目录,来作为我们学习的工作目录
E:\VueDemo
进入此目录,进行环境初始化,
npm init -y
执行成功后会再目录中生成一个package.json
的文件,npm安装时候回根据此文件来下载依赖,根maven原理类似.
- 执行安装
npm install vue
执行成功后会生成一个node_modules
目录
- 引入vue.js
这里可以使用自己喜欢的工具来操作,我这里用的是vscode
<script src="./node_modules/vue/dist/vue.js"></script>
当然你也可以忽略上面的所有安装操作,仅仅引入官方提供的js即可
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
3. Hello Word
<div id="app">
{{message}}
</div>
let vm = new Vue({
el: "#app",
data: {
message: "Hello World"
}
});
这样我们就完成了一个Vue的Hello World,下面来解释一下.
首先上面的代码分为2部分,
- html部分,即View
- JavaScript部分,这其中的Vue实例,是vm,而数据data可以想成是Model.
vm通过el
与id为"app"
的页面元素实现关联,通过data
属性来与数据进行关联,这样我们只需要将数据拿到交给vm即可,整个数据的显示过程都由vm来控制,免去了我们认为的操作.使得我们可以真正的将注意力集中在View上.
{{message}}
是vue的插值表达式,它将vm的data中key
为message
的数据的value显示在页面上.
{{}}可以支持js语法,可以支持函数调用,但是必须是有返回结果的,但不能声明变量(let a = 2
)
4. v-text命令:
功能和插值表达式{{}}
类似,都是用来显示内容的.
<div id="app" v-text="message"></div>
let vm = new Vue({
el: "#app",
data: {
message: "Hello World With v-text"
}
});
建议使用v-text
,原因是插值表达式在页面被完全渲染前会显示{{message}}
这种样子,而v-text
在完全渲染前不会显示内容.
可以在浏览器按F12,找到NetWork,选择Slow 3G,就会出现下列状态.
v-html:
与v-text类似的命令,只不过它是用来将内容按照html标签的形式来解析的,而v-text只是显示文本内容.
<div id="app" v-text="h1"></div>
let vm = new Vue({
el: "#app",
data: {
h1: "<h1>Hello World!!!</h1>"
}
});
使用v-html
的效果:
<div id="app" v-html="h1"></div>
let vm = new Vue({
el: "#app",
data: {
h1: "<h1>Hello World!!!</h1>"
}
});
5. v-bind
vue几乎为html标签的任何原生属性都提供了v-bind
的支持,比如title
属性,可以用来在鼠标悬停时显示信息.(v-bind:xxx
,通常简写为:xxx
,即v-bind
可以省略,只留一个:
+属性即可)
<div id="app" v-bind:title="msg">鼠标来来来</div>
let vm = new Vue({
el: "#app",
data: {
msg: "悬停中~~~~~~~~~"
}
});
v-bind还特别为class
,和style
进行了进一步的支持.
.active {
font-size :xx-large
}
<div id="app" v-text="msg" v-bind:class="{active: isActive}"></div>
let vm = new Vue({
el: "#app",
data: {
msg: "v-bind演示用",
isActive: true
}
});
当isAvtive为true时,class为active,否则class为空.
v-bind:class="{active: isActive}"
让我们在F12
的Console
手动的更改一下isAvtive
的值,可以看到更改之后的值立即被响应到了页面的组件上,这就是前面说过的,我们只需要将view和data都绑定到vm即可,其他的vm都会帮我们来完成.
6. v-model
v-model
指令,是用来实现双向绑定的.前面我们见识过了从data的变化到view的动态响应,而要在次基础上实现从view的改变动态响应到data,则需要使用v-model
<div id="app">
<input type="text" v-model="msg"/>
<br>
<span v-text="msg"></span>
</div>
let vm = new Vue({
el: "#app",
data: {
//message: "Hello World With v-text"
msg: "v-mode演示用"
}
});
当我们在input
中输入内容,下面的展示内容也会跟着发生变化,此时的vm.msg
变成了input
框中的内容.
而当我们改变vm.msg
的值时,发现input
框中的内容也会跟着改变,这就是双向绑定的含义.
7. v-on
v-on
指令监听DOM事件,比如onclick
事件的监听:
<div id="app">
<button v-text="'我被点击了'+num+'次'" v-on:click="num++"></button>
</div>
let vm = new Vue({
el: "#app",
data: {
num: 1
}
});
每次点击都会触发num++
v-on:xxx
通常简写为@xxx
7.1 事件修饰符
在事件处理程序中调用event.preventDefault()或event.stopPropagation()是非常常见的需求
尽管我们可以在方法中轻松实现这单,但更好的方式是:
方法只有纯粹的数据逻辑,而不是去处理DOM事件细节
为了解决这个问题,Vue.js为v-on提供了事件修饰符
修饰符是由点开头的指令后缀来标示的:
.stop: 阻止事件冒泡到父元素
.prevent: 阻止默认事件发生
.capture: 使用事件捕获模式
.self: 只有元素自身触发事件才执行(冒泡或捕获的都不执行)
.once: 只执行一次
7.2 按键修饰符
vue还为我们提供了特殊按键修饰符.常用的有如下内容
.enter
.tab
.delete
.esc
.space
.up
.down
.left
.right
举个简单的例子,上键++,下键–;
<div id="app">
<input type="text" @keyup.up="num++" @keyup.down="num--" :value="num"/>
</div>
let vm = new Vue({
el: "#app",
data: {
num: 1
}
});
7.3 组合键
除了单一键位之外,vue还提供了组合键的支持.
.ctrl
.alt
.shift
比如还是上面的例子,我们按shift+↑的时候对num进行+10,然后shift+↓的时候对num的值-10;
(不仅是2个键,多建的组合只要继续.xxx
就好了)
<input type="text" @keyup.up="num++" @keyup.down="num--"
@keyup.shift.up="num+=10" @keyup.shift.down="num-=10" :value="num"/>
7.4 exact 修饰符
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
8. 循环指令:v-for
v-for="user in users"
表示从users迭代出的每个元素都交由user
变量接管,所以后续可以使用user
来获取对象的属性.
还可以带上index
,即当前元素在数组中的索引.
<li v-for="(user, index) in users" :key="user.id">
</li>
<div id="app">
<ul>
<li v-for="user in users" :key="user.id">
{{user.id}}==={{user.name}}==={{user.age}}
</li>
</ul>
</div>
let vm = new Vue({
el: "#app",
data: {
users: [
{id:1, name: "jack", age: 18},
{id:2, name: "lize", age: 19},
{id:3, name: "mark", age: 20},
]
}
});
官方建议尽量带上:key
属性,为效率带来提升,这里的:key
一定是不能重复的值,如果重复会报错(我们将2号的id改为1)
除了遍历数组外,v-for
还可以遍历对象:
<div id="app">
<ul>
<li v-for="user in users" :key="user.id">
<span v-for="(v, k, i) in user">
{{"key is " + k}} === {{"value is : " + v}} === {{ "index is : " + i}}<br>
</span>
</li>
</ul>
</div>
由此可见,在v-for
语法中,第一个参数一定是value,而不是key
.
9. v-if和v-show
v-if
和v-show
的作用很类似,都是用来控制空间的显示,隐藏的,但有一点区别,v-if
如果为false
时,整个标签都不会再HTML中存在,而v-show
只是隐藏标签,但是它还是存在于HTML文档中的.
<div id="app">
<span v-if="isVisible">This is for v-if</span>
<br>
<span v-show="isVisible">This is for v-show</span>
</div>
let vm = new Vue({
el: "#app",
data: {
isVisible: true
}
});
当我们将isVisible
的值改为false
时:
除了简单的v-if
外,Vue还为我们提供了v-else
.
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
v-else-if(vue2.1.0新增)
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
v-for
的优先级高于v-if
,也就是说,我们可以在遍历的同时进行逻辑判断.
但是官方并不推荐这么做
<div id="app">
<ul>
<li v-for="user in users" :key="user.id" v-if="user.age > 19">
{{user.name}}
</li>
</ul>
</div>
let vm = new Vue({
el: "#app",
data: {
users: [
{id:1, name: "jack", age: 18},
{id:2, name: "lize", age: 19},
{id:3, name: "mark", age: 20}
]
}
});
10. 计算属性和监听器
模板内的插值表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中多次引用此处的翻转字符串时,就会更加难以处理。
所以,对于任何复杂逻辑,你都应当使用计算属性
。
10.1 基础样例
<div id="app">
<p>Original message: "{{message}}"</p>
<p>Computed reversed message: "{{reversedMessage}}"</p>
</div>
let vm = new Vue({
el: "#app",
data: {
message: "Hello"
},
computed: {
// reversedMessage:function()的简写形式
// reversedMessage是函数的getter
reversedMessage() {
// this可以指向vm实例
return this.message.split("").reverse().join("");
}
}
});
这里我们声明了一个计算属性reversedMessage
。
我们提供的函数将用作属性 vm.reversedMessage
的 getter
函数:
当我们在控制台,手动将vm.message
的值进行改动时,会发现,vm.reversedMessage
的值也跟随着在变化,也就是手它依赖于vm.message
,这种声明式的方式更加符合vue的设计理念,而我们确实只需要将计算属性拿过来显示在view层即可,至于它里面是如何复杂的计算规则,与view层无关.
10.2 计算属性缓存 vs 方法
要达到上述的目的我们还可以使用方法.
<div id="app">
{{reversedMessage()}}
</div>
let vm = new Vue({
el: "#app",
data: {
message: "Hello World"
},
methods: {
reversedMessage() {
return this.message.split("").reverse().join("");
}
}
});
我们声明了一个方法,来达到同样的目的,但是与计算属性不同的是,计算属性是依赖message
而响应式计算
的,而当计算属性所依赖的内容(message
)没有变化时,它第一次的计算结果将会被添加到缓存
中,以后每次访问都直接从缓存获取结果,但method
方法的调用则不然,即使message
值没有任何的 改变,每次访问仍会重新调用一次改函数.
举个例子:
<div id="app">
<p>{{"计算属性:" + now}}</p>
<br>
<p>{{"方法:" + getNow()}}</p>
</div>
let vm = new Vue({
el: "#app",
data: {
message: "Hello World"
},
methods: {
getNow() {
return Date.now();
}
},
computed: {
now() {
return Date.now();
}
}
});
上面的计算属性中,并不是响应依赖的,所以它的值不会发生变化,而显而易见,调用方法时,每次的值都会改变.
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
10.3 监听器
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
下面举一个简单的例子:
<div id="app">
购买BooK A 的数量:<input type="text" v-model="bookANum"/>
<br>
购买BooK B 的数量:<input type="text" v-model="bookBNum"/>
<br>
总消费:{{sum}}
</div>
let vm = new Vue({
el: "#app",
data: {
bookANum: "",
bookAPrice: 10,
bookBNum: "",
bookBPrice: 13,
sum:0
},
watch: {
bookANum(newVal, oldVal) {
console.log(`~~~new val is ${newVal}`);
console.log(`~~~old val is ${oldVal}`);
this.sum = this.bookAPrice * newVal + this.bookBPrice * this.bookBNum;
},
bookBNum(newVal, oldVal) {
console.log(`~~~new val is ${newVal}`);
console.log(`~~~old val is ${oldVal}`);
this.sum = this.bookAPrice * this.bookANum + this.bookBPrice * newVal;
}
}
});
当我们输入数量时,总消费额会跟着变化.
wacth
还提供了,发生变化前后的值
,这也是很多情况下我们需要的.
当然,上面这种写法可以很轻松的用计算属性来代替,这里只是做演示作用,多数监听使用在需要异步请求的情况下的.
11. 表单处理
11.1 基本用法
11.1.1 复选框
v-model
,将复选框与vm
中的数据checkedNames
进行双向绑定,当复选框被选中时,value
会被添加到,checkedNames
中,反选时则从数组中移除.
<div id="app">
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="jhon" value="Jhon" v-model="checkedNames">
<label for="jhon">Jhon</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names {{checkedNames}}</span>
</div>
let vm = new Vue({
el: "#app",
data: {
checkedNames: []
}
});
11.1.2 单选按钮
与复选框类似,不同的是,type="radio"值接收一个value当做值
,即使picked
的初始值是[]
(数组).
<div id="app">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{picked}}</span>
</div>
let vm = new Vue({
el: "#app",
data: {
picked: ""
}
});
11.1.3 下拉框
1. 单选
v-model
与vm
中的,selected
绑定,如果option
中设置了value
属性,那么当被选中时,会将value
的值赋值给selected
,如果没有设置value
属性的话,那么标签中的文本内容,会被当做value
传递给selected
.
<div id="app">
<select v-model="selected">
<option disabled value="">---请选择---</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{selected}}</span>
</div>
let vm = new Vue({
el: "#app",
data: {
selected: ""
}
});
2. 多选
与单选的区别:
multiple
属性
2.selected:[]
定义为数组
<div id="app">
<select multiple v-model="selected" style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{selected}}</span>
</div>
let vm = new Vue({
el: "#app",
data: {
selected: []
}
});
11.2 值绑定
对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串
(对于复选框也可以是布尔值):
<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">
<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle">
<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
但是有时我们可能想把值绑定到 Vue 实例的一个动态属性
上,这时可以用 v-bind 实现,并且这个属性的值可以不是字符串
。
11.2.1 复选框
<div id="app">
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no">
<br>
<span>Checked: {{toggle}}</span>
</div>
let vm = new Vue({
el: "#app",
data: {
toggle: ""
}
});
初始值为""
,如果不进行操作,那么true-value和false-value
对原始的value
不产生任何的影响.
选中时,yes
反选时,no
11.2.2 单选框
在数据库中我们往往需要的只是一个0或1
的标识,而不是真正的显示出来的内容.
<div id="app">
<input type="radio" id="male" v-bind:value="1" v-model="gender">
<label for="male">Male</label>
<input type="radio" id="female" v-bind:value="0" v-model="gender">
<label for="female">Female</label>
<br>
<span>Gender: {{gender}}</span>
</div>
let vm = new Vue({
el: "#app",
data: {
gender: 1
}
});
11.2.3 下拉框
<div id="app">
<select v-model="selected">
<option v-bind:value="{id:1, name: 'java'}">Java</option>
<option v-bind:value="{id:2, name: 'python'}">Python</option>
<option v-bind:value="{id:3, name: 'c++'}">C++</option>
</select>
<br>
<span>Selected: {{selected}}</span>
</div>
let vm = new Vue({
el: "#app",
data: {
selected: ""
}
});
11.3 修饰符
11.3.1 .lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。
你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
11.3.2 .number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。
如果这个值无法被 parseFloat() 解析,则会返回原始的值。
<div id="app">
<input type="number" v-model="age">
<br>
<span>Type of age is: {{typeof age}}</span>
</div>
let vm = new Vue({
el: "#app",
data: {
age: 18
}
});
初始时,是number类型.
但是当我们输入内容之后,它就变成了字符串类型,事实上,所有的input(键盘输入)
都是string类型.
而使用了.number
之后,类型被转换为了number.
11.3.3 .trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<input v-model.trim="msg">
12. 组件基础
在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。
例如可能有相同的头部信息,相同的左侧导航栏.
但是如果每个页面都独自开发,这无疑增加了我们开发的成本。
所以我们会把页面的不同部分拆分成独立的组件,然后在不同页面就可以共享这些组件,避免重复开发。
12.1 全局声明注册一个组件:
<div id="app">
<button-counter></button-counter>
</div>
Vue.component("button-counter", {
template: '<button @click="num++">You clicked me {{num}} times.</button>',
data() {
return {
num: 0
}
}
})
let vm = new Vue({
el: "#app"
});
组件的目的就是为了方便复用,减少重复代码,所以上面的组件我们可以多次使用.
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
这也是为什么在组件中,data
必须是个函数,因为每次调用都会return一个新的对象
,所以可以做到数据隔离的作用,否则,大家都共享同一个变量,那就造成了数据混乱.
12.2 局部声明注册一个组件:
除了,全局声明之外,还可以在局部声明组件.
<div id="app">
<my-btn></my-btn>
<my-btn></my-btn>
<my-btn></my-btn>
</div>
let bt = {
template: '<button @click="num++">Clicked by {{num}} times</button>',
data() {
return {
num: 1
}
}
}
let vm = new Vue({
el: "#app",
components:{
"my-btn": bt
}
});
局部注册的组件与全局组件不同之处在于,局部注册的组件只能应用于,组件所属Vue实例的关联范围
(<div id="app">
).
组件也是一个Vue实例,因此在它定义时也会接收:data,methods,生命周期函数等.
不同的是,组件不会与页面的元素绑定,否则就无法服用了,因此没有el属性。
12.3 通过 Prop 向子组件传递数据
组件只是用来减少重复代码,提高复用性的,但是不可避免的,业务和数据的不同,组件也需要作出不同的渲染,那么如何向组件传递数据呢?
props
选项,可以用来向组件传递数据
我们来看一下官方的例子:
使用<blog-post>
组件,并将title
属性传递给它
<div id="app">
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
</div>
组件方,接收属性时,需要使用props
选项,它的值是一个数组,也就意味着我们可以传递多个属性.
Vue.component("blog-post", {
props: ["title"],
template: "<h3>{{title}}</h3>"
});
let vm = new Vue({
el: "#app"
});
当然,在实际项目中,我们一般而言都会动态的显示需要的内容,所以下面我们来看一下另外一个例子:
这里用了v-bind
来动态传递prop
<div id="app">
<blog-post v-for="blog in posts" :title="blog.title" :id="blog.id" :key="blog.id">
</blog-post>
</div>
Vue.component("blog-post", {
props: ["id", "title"],
template: "<h3>{{id}} : {{title}}</h3>"
})
let vm = new Vue({
el: "#app",
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
});
13. 生命周期和钩子函数
每个Vue实例在创建时都要经过一系列的初始化过程:
<div id="app">
<span id="num">{{num}}</span>
<button @click="num++">赞!</button>
<h2>{{name}},有{{num}}个人点赞</h2>
</div>
let vm = new Vue({
el: "#app",
data: {
name: "Jerry",
num: 100
},
methods: {
show() {
return this.name;
},
add() {
this.num++;
}
},
beforeCreate() {
console.log("============beforeCreate===========");
console.log("数据模板未加载:" + this.name, this.num);
console.log("方法未加载:" + this.show());
console.log("html模板未加载:" + document.getElementById("num"));
},
created() {
console.log("============created===========");
console.log("数据模板已加载:" + this.name, this.num);
console.log("方法已加载:" + this.show());
console.log("html模板已加载:" + document.getElementById("num"));
console.log("html模板未渲染:" + document.getElementById("num").innerText);
},
beforeMount() {
console.log("============beforeMount===========");
console.log("html模板未渲染:" + document.getElementById("num").innerText);
},
mounted() {
console.log("============mounted===========");
console.log("html模板已渲染:" + document.getElementById("num").innerText);
},
beforeUpdate() {
console.log("============beforeUpdate===========");
console.log("数据模型已更新:" + this.num);
console.log("html模板未更新:" + document.getElementById("num").innerText);
},
updated() {
console.log("============updated===========");
console.log("数据模型已更新:" + this.num);
console.log("html模板已更新:" + document.getElementById("num").innerText);
}
});
1. beforeCreate:
数据模板还未加载,方法和html模板也都未加载.
2. created
数据模板和html模板已加载,但是html模板还未渲染.
3. beforeMount
挂载之前,html模板还未渲染.
4. mounted
html模板已渲染,插值表达式的值得以显示.
点击赞!
按钮,num++
,数据模型会发生变化.
5. beforeUpdate
点击按钮后,html模板更新之前
6. updated
html模板更新后