双向绑定
通过构造函数 Vue 就可以创建一个 Vue 的根示例,并启动 Vue 应用,Vue 实例需要挂载一个 DOM 元素,它可以是 HTMLElement,也可以是 CSS 选择器
1 |
|
上述 Vue 实例的构造器中的成员变量可以通过app.$el
,app.$data
的方式去访问,对于 data 数据,可以直接使用app.message
去访问。在 dom 上以 {{}} 包含的内容与 app 进行双向绑定。当我们在 console 控制台修改app.messege
的值时,页面也随着刷新 message 的内容
Vue 在观察到数据变化时并不是直接更新 DOM,而是开启一个队列,并缓冲同一事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和 DOM 操作。然后在下一个事件循环 tick 中,Vue 刷新队列并执行实际(已去重的)工作
1 | <div id="app"> |
生命周期
每个 Vue 示例创建时,都会经历一系列的初始化过程,同时也会调用相应的生命周期钩子。比较常用的生命周期有
- created 示例创建完成后调用,次阶段完成了数据的观测等,但尚未挂载,$el 还不可用,需要初始化一些处理数据时会比较有用
- mounted el 挂载到实例上后调用
- beforeDestroy 实例销毁前调用。主要是解绑一些使用 addEventListener 监听的事件等。
这些钩子与 el 和 data 类似,作为选项写入 Vue 实例
1 | var app = new Vue({ |
手动挂载实例
Vue 提供了 Vue.extend 和$mount两个方法将 vue 实例挂载到一个 dom 上,即如果 Vue 实例在实例化时它没有收到 el 选项,它就处于“未挂载”状态,没有关联的 DOM 元素。可以使用$mount()手动挂载一个未挂载的实例。这个方法返回实例自身,因而可以链式调用其他实例方法。
1 | <div id="app"> |
插值与表达式
{{}} 使用双大括号(Mustache 语法),双向绑定的数据,{{}} 内部可以使用 js 运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14<div id="app">
{{number/10}}
{{isOK?'确定':'取消'}}
{{text.split(',').reverse().join('|')}}
</div>
<script>
new Vue({
el: "#app",
data: {
number:100,
isOK:false,
text:'123,456'
},
});{{}} 支持使用管道符
|
来对数据进行过滤,过滤的规则是自定义了,通过选项filters
来设置,过滤器可以接受参数,过滤器可以串联1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div id="app">
{{text|split}}
<!-- 串联-->
{{text|split|split}}
<!-- arg1 arg2 分别为过滤器的第二个 第三个参数-->
{{text|split('arg1','arg2')}}
</div>
<script>
new Vue({
el: "#app",
data: {
text:'123,456'
},
filters:{
split:function(value){
return value.split(',').join('#');
}
}
});computed 通过 Vue 选项 computed 的计算属性获取数据,每一个计算属性都包含一个 getter 和 setter,默认只使用 getter,当 getter 中所依赖的任何数据变化时,当前 getter 会被自动触发。相对于直接使用 methods,computed 只有在源数据更新后才会被重新调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<div id="app">
{{text}}
</div>
<script>
new Vue({
el: "#app",
data: {
number:100
},
computed:{
text:function(){
return '$'+this.number
}
}
});当我们对
app.number
进行赋值时,页面上 text 的值也会跟着变化,但是对 text 的值进行改变,number
的值不会变化, 当我们指定其 setter 就可以了1
2
3
4
5
6
7
8computed:{
set: function (value) {
this.number = parseInt(value.substring(1))
},
get: function () {
return '$' + this.number
}
}
指令
v-bind
动态更新 html 元素上的属性,可以使用语法糖:
1
2
3
4
5
6
7
8
9
10<a v-bind:href="url"> 链接</a>
<!--语法糖-->
<a :href="url"> 链接</a>
<script>
new Vue({
data: {
url: "http://example.com",
},
});
</script>v-for
迭代的数据也是双向绑定的,对数组或对象进行操作时会触发渲染遍历数组,
需要注意的是直接通过索引去设置值是无法被 Vue 检测到的,也不会触发视图更新 1
2
3
4
5
6
7
8
9
10
11<ul>
<li v-for="book in books">{{ book.name}}</li>
<li v-for="(book,index) in books">{{ index}} - {{book.name}}</li>
</ul>
<script>
new Vue({
data: {
books: [{ name: "v1" }, { name: "v2" }],
},
});
</script>遍历对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14<ul>
<li v-for="(value,key,index) in user">{{ index}} - {{key}}:{{value}}</li>
</ul>
<script>
new Vue({
data: {
user: {
name: "hello",
gender: "man",
age: 23,
},
},
});
</script>迭代整数
1
<li v-for="n in 10">{{ n}}</li>
若我们需要操作数组,可使用数组自带的方法区操作, 例如
- pop 删除末项
- push 添加一项
- shift 删除第一项
- unshift 添加第一项
- splice 截取/修改/删除数组元素
- sort 对数组排序
- reverse 取反
也可以通过直接修改数组的引用 或使用
Vue.set
、this.$set
v-html
输出 HTML1
2
3
4
5
6
7
8
9<span v-html="link">}</span>
<script>
new Vue({
el: "#app",
data: {
},
});
</script>v-if
、v-else-if
(必须紧跟 v-if)、v-else
(b 必须紧跟 v-if 或 v-else-if) 表达式为正,当前元素、组件所有子节点将被渲染,否则全部移除1
2
3<p v-if="status ===1">当status为1时显示这行</p>
<p v-else-if="status ===2">当status为2时显示这行</p>
<p v-else>否则显示这行</p>v-model
绑定表单数据,也是双向绑定的,对于中文输入法,只有在回车后才会触发更新1
2
3
4
5
6
7
8
9
10
11
12
13<input v-model="input" />
<p style="color: red;">{{input}}</p>
<!-- 使用@input可以在输入中文时实时刷新-->
<input @input="inputHandle" />
<script>
//...
methods:{
inputHandle:function(e){
this.input = e.target.value
}
}
</script>v-model 可使用修饰符控制数据同步的机制
- .lazy v-model 默认在 input 事件中同步输入框的数据,使用
.lazy
会转变在 change 事件中同步 - .number 将输入转换为 Number 类型
- .trim 过滤首位空格
示例
1
2
3<input v-model.lazy="value" />
<input v-model.number="value" />
<input v-model.trim="value" />- .lazy v-model 默认在 input 事件中同步输入框的数据,使用
v-on
绑定事件监听器,可以使用语法糖:
,可以用.native
修饰符表示监听的是一个原生事件。1
2
3
4
5
6
7
8
9
10
11
12<a v-on:click="log"> 链接</a>
<!--语法糖-->
<a @click="log"> 链接</a>
<script>
new Vue({
methods: {
log: function (event) {
console.log(event); //event为触发的事件
},
},
});
</script>v-once
值渲染一次,包括元素和组件的所有子节点(包括 v 指令等)。首次渲染后,不再随数据的变化重新渲染v-pre
跳过编译1
<span v-pre> {{这里面不会被编译}}</span>
v-show
进行 CSS 属性切换,是否可见
自定义指令
例如注册一个 v-focus 的指令,用于在<input>
元素初始化时自动获取焦点
1 | //全局注册 |
自定义指令由几个钩子函数组成的,每个都是可选的
- bind 只调用一次,指令在第一次绑定元素时调用,用这个钩子函数可以定义一个绑定时指定一次的初始化动作
- inserted 被绑定元素插入父节点时调用
- update 被绑定元素所在的模板更新时调用,而不论绑定值是否变化,通过比较更新前后的绑定值,可以忽略不必要的模板更新
- componentUpdated 被绑定元素所在模板完成一次更新周期时调用
- unbind 只调用一次,指令与元素解绑时调用
每个钩子函数都一次有几个参数可用
- el 指定绑定的元素,可以用来直接操作 DOM
binding 一个对象,包含以下属性
- name 指令名,不包含 v-前缀
- value 指令绑定的值,例如
v-my-directive="1+1"
,value 的值就是 2 - oldValue 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论值是否改变都可用
- expression 绑定值的字符串形式,例如
v-my-directive="1+1"
,expression 的值就是1+1
- arg 传给指令的参数,例如
v-my-directive:foo
,arg 的值是 foo - modifiers 一个包含修饰符的对象,例如
v-my-directive.foo.bar
,修饰符对象 modifiers 的值是{foo:true,bar:ture}
- vnode Vue 编译生成的虚拟节点
oldVnode 上一个虚拟节点仅在 update 和 componentUpdated 钩子中可用 根据需求在不同的钩子函数内完成逻辑代码,例如 v-focus,我们希望在元素插入父节点时调用,那用到的最好是 inserted。示例如下
1 | <div id="app"> |
自定义指令也可以传入一个 JavaScript 对象字面量
1 | <input v-focus="{msg:'hello',count:100}" /> |
自定义插件
注册插件
1 | // 在Vue内部实际上会调用`MyPlugin.install(Vue)` |
自定义插件
1 | MyPlugin.install = function (Vue, options) { |
绑定 class 的几种方式
v-bind 中的表达式最终会被解析为字符串,Vue 对 v-bind 增强了一些功能,提供了多重语法
对象语法
1
2
3
4
5
6
7
8
9<div :class="{'class1':isActive1,'class2':isActive2}"></div>
<script>
new Vue({
data: {
isActive1: true,
isActive2: false,
},
});
</script>上述渲染后的页面元素为
<div class="class1"></div>
也可使用 computed
1
2
3
4
5
6
7
8
9
10
11
12
13<div :class="classes"></div>
<script>
new Vue({
computed: {
classes: function () {
return {
class1: this.isActive1,
class2: this.isActive2,
};
},
},
});
</script>也可以指定绑定到一个对象上
数组语法
1
2
3
4
5
6
7
8
9<div :class="[c1,c2]"></div>
<script>
new Vue({
data: {
c1: 'class1'
c2: 'class2'
}
});
</script>
事件
修饰符
修饰符
. stop . prevent . capture . self . once
1 | <!-- 阻止单击事件冒泡--> |
按键
keyCode
- .enter
- .tab
- .delete
- .esc
- .space
- .up
- .down
- dblclick 双击
按键可以组合,或和鼠标一起配合使用
1 | <!-- 按下shift时点击--> |
$emit
使用$emit
来触发自定义事件,使用$on
来监听子组件的事件。
例如
1 | <div id="app"> |
v-model
语法糖,v-model
绑定的是一个数据,接收一个 value 属性,,在其有新的 value 时会触发 input 事件。反之亦然。 等价于<input :value='someVariable' @input='someHandle'>
所以上述事例也可以写成如下
1 | <div id="app"> |
不同组件互相通信
建议通过一个空的 vue 实例作为中央事件总线来实现。
1 | <div id="app"> |
slot
当在子组件内使用特殊的<slot>
元素就可以为这个子组件开启一个slot
(插槽),在父组件的模板里,插入在子组件模板标签内的所有内容将替代子组件内的<slot>
标签以及它的内容,slot
可以指定一个 name,其会加载父组件的子组件模板标签内中属性slot
值相同的内容。没有指定slot
值的标签都作为默认匿名 slot
1 | <div id="app"> |
scope_name.msg
的语法访问子组件插槽的数据 msg
1 | <div id="app"> |
作用域插槽更具有代表性的用例是列表组件
1 | <div id="app"> |
我们可以使用$slots
访问默认作用域的 slot 渲染后的标签,$scopedSlots
访问所有作用域的标签
1 | <div id="app"> |
watch
监听 prop 或 data 的改变,当他们发生变化时触发 watch 配置的函数,此时对于节点以及渲染完成,
1 | <div id="app"> |
watch 对第一次赋值是不响应的,可通过设置immediate
来让其执行。watch 一般仅监听属性的重新赋值,对于对象属性的内部值的操作时不响应的,可通过deep
来使其生效
1 | <script> |
watch 可以监听 vuex 中存储的内容
1 | <script> |
表单
单选按钮
当那个单选按钮被选中时,picked 即为对应绑定的数据的值,同一个v-model
单选具有排斥性
1 | <div id="app"> |
组件
定义组件
组件最外层仅能有一个标签
1 | <div id="app"> |
异步组件
Vue 允许将组件定义为一个工厂函数,动态解析组件。工厂函数接收一个 resolve(component)回调,也可以使用 reject(reason)指示加载失败
1 | <div id="app"> |
嵌套组件
嵌套的组件需要定义在全局组件上,必须设定一个条件来限制递归数量
1 | <div id="app"> |
动态组件
Vue 提供了一个特殊的元素<component>
用来挂载不同的组件,使用is
特性来选择要挂载的组件。通常is
特性绑定的是组件的名称,组件也可以直接绑定对象。
1 | <div id="app"> |
X-templates
可以使用这种模板更好的属性模板代码
1 | <script src="vue.min.js"></script> |
使用 props 传递数据
在组件内可以通过声明 props 访问在父类标签中的属性
1 | <div id="app"> |
HTML 特性不区分大小写,当使用 DOM 模板时,驼峰命名的 props 名称要转换为短横分隔命名
1 | <li-div msg-text="来自父组件的数据"></li-div> |
父组件数据变化时传递给子组件默认是单向数据流,即父组件的数据更新会传递给子组件,但反过来不行。
示例:当我们输入内容时,子组件内容跟着变动,而父组件不变
1 | <div id="app"> |
在 JavaScript 中对象和数组式引用类型的,指向同一个内存空间,所以 props 中是对象和数组式,在子组件内改变是会影响符组件的。
示例:当我们输入内容时,子组件内容跟着变动,而父组件也会跟着变动
1 | <div id="app"> |
数据验证
对于组件的 props,我们可以对其属性进行数据验证,
1 | new Vue({ |
验证的 type 可以是
- String
- Number
- Boolean
- Object
- Array
- Function
默认情况下含有静态值(非双向绑定的数据)属性的类型为 String,当我们需要自动推断类型时,可以使用v-bind
1 | <div id="app"> |
type 也可以是一个自定义构造器,使用 instanceof 检测 当 prop 验证失败时,在
mixins
mixins: 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用 mixins 时,所有 mixins 的选项将被组合到该组件本身的选项,类似预编译过程。
- 组件的数据不共享,仅拷贝一份,数组和对象采用的是深拷贝,而非拷贝引用。
- 对于值为对象的选项如 methods,components 等,选项会被合并,键冲突的组件会覆盖混入对象的。
- 值为函数的选项,如 created,mounted 等,就会被合并调用,混合对象里的钩子函数在组件里的钩子函数之前调用
webpack 过程
使用.vue
单文件组件的构建模式,需要 webpack 并使用 vue-loader 对.vue 格式的文件进行处理。一个.vue 文件一般包括 3 部分,即<template>
,<script>
,<style>
例如:
1 | <template> |
示例中的 style 标签使用了 scoped 属性,表示当前的 CSS 只在这个组件有效,如果不加,那么 div 的样式会应用到整个项目。<style>
还可以结合 CSS 预编一起使用,比如使用 Less 处理可以写成<style lang='less'>
使用.vue 文件需要安装vue-loader
、vue-style-loader
等加载器并做配置。因为要使用 ES6 语法,还需要安装babel
和bable-loader
等加载器。使用 npm 逐个安装以下依赖
使用vue-cli
1 | vue2版本对应的cli |
安装后,package.json 文件内容如下
1 | { |
使用npm run dev
即可启动一个 dev 服务
路由
定义
单页面应用指的是只有一个主页面,通过动态替换 DOM 内容并同步修改 url 地址,来模拟多页面应用的效果。切换页面的功能直接由前台脚本来完成,而不是后端渲染完毕后前端只负责显示。
前端路由就是一个前端不同页面的状态管理器,可以不向后台发送请求而直接通过前端技术实现多个页面效果。vue 中的 vue-router,react 的 react-router 均是对这种功能的实现。
实现原理可参考这里
vue-route
我们通过 vue-cli 初始化项目后,main.js 的内容如下
1 | import Vue from "vue"; |
首先页面会渲染成类似如下
1 | <html> |
这里的<app>
即是new Vue()
中可选参数 template 所生产的标签, 然后根据 components 中定义的 App 组件,将 app 渲染成 app.vue 中的内容
1 | <html> |
其中<router-view>
路由视图以用来挂载路由,默认挂载/
,那么我们根据main.js
中引入的 router,找到其对应的 js 文件。
src/router/index.js
1 | import Vue from "vue"; |
根据/
路径配置的组件,我们将<router-view>
渲染成 HelloWorld 组件的内容
1 | <html> |
路由跳转
router-link
是用来动态切换router-view
显示内容组件。
示例
我们新建一个组件src/components/link.vue
1 | <template> |
在 router 配置中配置 link.vue 的请求路径
1 | import Vue from "vue"; |
修改src/App.vue
1 | <template> |
当我们点击由router-link
渲染出来的<a>
链接,其下面的router-view
对应的元素将会被切换至对应的在路由中配置的组件
我们也可以使用 js 方法来实现跳转
1 | <template> |
$router
还有其他一些方法($router
为 main.js 中声明的 router 变量)
- replace 它不会向 history 添加新记录,而是替换掉当前的 history 记录。
- go 类似于 window.history.go(),在 history 记录中向前或后退多少步
路由配置相关
配置默认页面,可以在路由列表的最后新加一项,当访问路径不存在时,重定向到首页。
1 | export default new Router({ |
动态路由
路由列表的 path 也可以带参数,
1 | { |
当我们访问/link/1234
时,我们可以通过this.$route.params.id
的方式取出该参数值
1 | <template> |
路由钩子
router 提供了导航钩子 beforeEach 和 afterEach,它们会在路由即将改变前和改变后触发。
钩子函数有三个参数
- to 即将进入的目标的路由对象,在对应的目标页面我们可以用
this.$route
取出当前路由对象,也就是 to - from 当前导航即将离开的路由对象
- next 调用该方法后,才能进入下一个钩子。next 中还可以设置参数,。设置为 false,可以取消导航,设置为具体的路径可以导航到指定的页面。典型的用法是校验客户是否登录。
例如我们将页面的 title 实时更改为组件的名称
src/main/js
1 | import Vue from 'vue' |
命名视图
<router-view>
可指定 name,来使用指定 name 的组件,路由的路径,可以对应多个组件,默认使用的是 name 为 default 的组件
src/App.vue
1 | <template> |
router/index.js
1 | import Vue from "vue"; |
src/components/input1.vue
和 input2.vue,基本一致
1 | <template> |
当访问/
时,三个router-view
分别会被渲染为对应的组件。
二级路由
App.vue
1 | <html> |
Home.vue
1 | <div> |
Child
1 | <div>the child</div> |
1 | import Vue from "vue"; |
二级路由和命令视图结合
可参考这个demo
app.vue
1 | <template> |
router/index.js
,省略其他组件的代码
1 | import Vue from "vue"; |
其他
在 vue 代码中,我们可以使用
1 | export default { |
使用router-link
或使用$router.push
时,可以传递 obj,指定跳转到命令的路由中
1 | import Vue from "vue"; |
Vuex
1 | npm install --save-dev vuex |
新增文件src/store/index.js
1 | import Vue from "vue"; |
mutations 也可以接受一个包含 type 的对象
1 | mutations: { |
vuex 还有三个选项可以使用:getters
,actions
,modules
getters 一般用来对数据进行处理
1 | export default new Vuex.Store({ |
actions 类似 mutations,但是是异步执行的
modules,它用来将 store 分割到不同的模块中。
1 | const moduleA = { |