简介
Vue 是一个用于构建 Web 用户界面的渐进式 JavaScript 框架。其核心库只关注视图层(view layer),同时具备良好的第三方支持库生态用以应付构建复杂大型单页应用(SPA:Single-Page Application)。
MVVM 模型
Vue 虽然没有完全遵循 MVVM(Model-View-ViewModel)模型,当其设计也受到了 MVVM 的启发,以数据驱动界面,如下图所示:
在 Vue 中,充当 ViewModel 的是一个 Vue 实例(new Vue({})
),该
Vue实例 作用于某一个 HTML 元素上,全权代理该元素节点的所有操作。
Vue实例 内部通过 DOM Listeners 可以观测到页面上 DOM 元素的变化,从而将该种变化同步更改到 Model 中的对应数据。
同时通过 Data Bindings,当 Model 中的数据改变时,则会对相应视图上的显示进行更改,从而实现了 View 和 Model 的数据双向绑定。
注:传统的 Web 编程模型是 结构驱动,即要对一个 DOM 节点进行操作,第一步就是要获取该 DOM 节点对象,然后再修改数据更新到节点上。
而 Vue 的中心思想是 数据驱动,要更改界面,其实就是要更改数据。
简而言之,在 Vue 中,不应当考虑操作 DOM,而是专注于 操作数据。
安装
Vue 的安装有多种方法,这里主要介绍两种方法:
- 通过
标签直接引入:
注:通过标签引入,
Vue
会被注册为一个全局变量
- 直接使用官方提供的快速搭建复杂单页面应用 (SPA) 的脚手架 vue-cli:
- 首先全局安装该脚手架 vue-cli:
npm install -g @vue/cli
- 创建一个项目:
vue create
注:使用 vue-cli 前需确保系统已安装 nodejs。
以上两步操作完成,我们便创建完成一个 Vue 项目。
在项目的 package.json
中,可以看到 vue-cli 提供了两个脚本命令让我们运行与打包项目:
-
npm run serve
:运行项目 -
npm run build
:打包项目到 dist 文件夹
组件化
Vue 的两大特性为 数据驱动 和 组件化。
通常一个大的页面可以划分为许多个小区块,这些小区块有些结构是相似的,我们可以将这些相似的区块抽象出一个统一的结构,方便复用,这种抽象结构的方法即称为组件化。
在实际项目开发中,一个大的页面通常都是由许多个小的组件构造而成的,如下图所示:
Vue 提供了两种组件定义的方式:
- 全局组件:全局组件只需定义一次,便可被其他任意组件使用。
定义方式:Vue.component(id, [definition])
- 局部组件:对于全局组件来说,即使页面没有使用该组件,组件也会被注入到最终的构建结果中,导致了 JavaScript 文件的无谓增加。而局部组件可以做到按需加载,需要哪些组件,按需引入即可,更加灵活高效。
定义方式:通过一个普通的 JavaScript 对象来定义组件,然后 Vue实例 按需引入需要的组件即可。
最佳实践:在 Vue 中,组件通常都定义到一个单独的.vue
文件中,其他组件需要时,导入相应组件的.vue
文件即可。
// MyComponent.vue
{{message}}
可以直接使用以下命令直接运行.vue
文件,查看组件展示效果:
vue serve MyComponent.vue --open
也可以在其他组件内导入该组件,进行使用:
Parent Component
注:在 Vue 中,组件实质是带有一个名字的 Vue实例,其性质与 Vue实例 基本一致(遵循 Vue实例 的生命周期等内容),特点是多了个组件复用功能。
Vue实例
Vue 实例充当 ViewModel 角色,负责 View 和 Model 之间的数据绑定:
new Vue(Options)
当创建一个 Vue 实例时,你可以传入一个选项对象Options
,该Options
的选项列表有如下可选:
- 选项 / 数据
- 选项 / DOM
- 选项 / 生命周期钩子
- 选项 / 资源
- 选项 / 组合
- 选项 / 其他
下面列举一些Options
常用选项
- 选项 / 数据:
data
描述:Vue 实例的数据对象,用于数据的存储与显示。
类型:Object | Function
{{message}}
注:Vue 将会递归将data
的属性转换为getter/setter
,从而让data
的属性能够响应数据变化。比如在控制台输入vm.message = 'Hi Vue!!!
,可以观察到页面数据发生了更改。
注:组件 中的data
属性必须是Fcuntion
类型,其返回一个Object
,原因是组件复用时,保证每个新组件都有独一的一份数据拷贝。
// MyComponent.vue
{{message}}
- 选项 / 数据:
props
描述:该属性用于接收来自父组件的数据。
类型:Array
。| Object
当传递的是Object
类型时,则可以基于对象的语法使用以下选项:
▫type
:指定数据类型,该值可以为原生类型(String
,Number
,Boolean
,Array
,Object
,Date
,Function
,Symbol
),自定义构造函数,或上述内容组成的数组。
▫default:any
:为该prop
指定一个默认值。如果该prop
没有被传入,则使用该默认值。对象或数组的默认值必须从一个工厂函数返回。
▫required: Boolean
:定义该prop
是否为必填项。
▫validator: Function
:自定义验证函数,对该prop
进行校验。
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
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
}
}
}
})
- 选项 / 数据:
propsData
描述:创建实例时传递props
。主要作用是方便测试。
类型:{ [key: string]: any }
var Comp = Vue.extend({
props: ['msg'],
template: '{{ msg }}'
})
var vm = new Comp({
propsData: {
msg: 'hello'
}
})
注:propsData
属性只能用于new
创建的实例中。
- 选项 / 数据:
methods
:
描述:方法定义,Vue实例 可以直接访问这些方法,或在指令表达式中直接调用这些方法。
类型:{ [key: string]: Function }
var vm = new Vue({
data: { a: 1 },
methods: {
plus: function () {
this.a++
}
}
})
vm.plus()
vm.a // 2
注:methods
中的this
自动绑定到当前 Vue实例。
- 选项 / 数据:
computed
描述:计算属性,主要用于对data
数据进行计算转换。
类型:{ [key: string]: Function | { get: Function, set: Function } }
获取数据: {{computedData}}
设置数据:{{setData = 3}}
获取数据: {{setData}}
注:computed
类型为Object
,其具有如下特点:
-
computed
内部定义的属性为访问器属性,即具备getter
和setter
,且其内部this
自动绑定到当前 Vue实例。 -
computed
会自动 缓存 计算结果,只有当依赖的响应式属性变化时,coputed
才会重新进行计算。缓存 是computed
与methods
的最大区别之处,methods
每次调用一定会运行函数,而computed
则不一定。
- 选项 / 数据:
watch
:
描述:侦听属性,用于监控data
或computed
的数据,当数据变更时进行回调通知。
类型:{ [key: string]: string | Function | Object | Array }
注:watch
类型为Object
,其内部属性的类型有多种:string | Function | Object | Array
,这里简单介绍 3 种:
-
string
:字符串表示回调函数名,当数据改变时,回调该函数:
const vm = new Vue({
el: '#app',
data: {
a: 1
},
methods: {
aChanged(value, oldValue) {
console.log(`a changed: new:${value} --> old:${oldValue}`);
}
},
watch: {
a: 'aChanged'
}
});
-
Function
:当数据改变时,直接回调该函数:
const vm = new Vue({
el: '#app',
data: {
a: 1
},
watch: {
a(value, oldValue) {
console.log(`a changed: new:${value} --> old:${oldValue}`);
}
}
});
-
Object
:对监控的属性为对象时,Vue 默认只能监控到对象重新被赋值的变化,而如果需要监听对象内部属性的变化,则可使用该选项,其中:
handler
代表回调函数。
deep
用来控制监听对象属性的层级,deep=true
时只要对象内部 property 改变(不管嵌套有多深),都会监听到。
immediate
用来设置是否立即产生回调。当immediate=true
时,回调函数会立即被调用,传递的是属性当前的值。
const vm = new Vue({
el: '#app',
data: {
a: {
aa: {
aaa: 3
}
}
},
watch: {
a: {
handler(value, oldValue) {
console.log(`a changed: new:${value} --> old:${oldValue}`);
},
deep: true // 被监听对象的 property 改变时被调用,无论嵌套的有多深
}
}
});
注:大多数情况下,观察和响应数据变更使用计算属性(computed
)便足够了,但是当在数据变化时需要执行异步或开销较大的操作时,则此时使用侦听属性(watch
)会更加适合。
- 选项 / DOM:
el
描述:设置 Vue实例 的挂载目标节点
类型:string | Element
new Vue({
el: '#app'
});
注:如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用vm.$mount()
手动开启编译。
- 选项 / DOM:
template
描述:字符串模板,模板会 替换 挂载的元素。
类型:string
const vm = new Vue({
el: '#app',
template: 'template
' // 会被 完全覆盖
})
- 选项 / DOM:
render
描述:字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个createElement
方法作为第一个参数用来创建VNode
。
类型:(createElement: () => VNode) => VNode
new Vue({
render(createElement) {
return createElement('div', {
class: 'rendered'
},
[
createElement('h1', {
domProps: {
innerHTML: 'div>h1 rendered by vue'
}
})
]
);
}}).$mount('#app');
注:Vue 推荐在绝大多数情况下使用模板来创建你的 HTML,只有在一些特殊场景下,比如模板冗长且具备重复元素,则此时使用渲染函数render
通过编写 JavaScript 代码来渲染出页面会更加方便简洁。
- 选项 / 生命周期钩子:见下文
生命周期
每个 Vue实例 在挂载到页面时,都会经历一系列的初始化过程,例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。在创建 Vue实例 的这整个过程中,Vue 为我们预留出了一些 Hook 点,方便我们在 Vue实例 创建过程的某个生命周期中进行一些操作。如下图所示:
注:图片来源于网上,侵删。
这些预留的生命周期钩子函数总共有如下几个:
beforeCreate
:Vue实例 初始化之后,此时data
和methods
中的数据还未进行初始化,因此无法获取。
created
:表示 Vue实例 创建完成,但还未挂载到页面上,此时data
和methods
都已经初始化成功,可以对其进行调用获取,而挂载阶段未开始,所以$el
属性目前不可见。
beforeMount
:在挂载开始之前被调用,此时模板已在内存中被编译完成,只是尚未挂载到页面上,因此,此时页面上显示的还是未渲染的结构。
mounted
:挂载完成,此时页面会显示我们渲染的视图。如果想要操作页面上的 DOM 节点,最早的时间就是该处。
注:mounted
不会 承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用vm.$nextTick
替换掉mounted
:
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
beforeUpdate
:数据更新时调用,此时内存中data
数据已更新,但页面中显示的数据还未更新,数据与页面不同步。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated
:新数据成功渲染到页面,此时数据与页面处于同步状态。
注:updated
不会 承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用vm.$nextTick
替换掉mounted
:
activated
:激活状态,表示当前组件处于前台页面,用户可与该组件进行交互。
注:只有组件被内置组件keep-alive
包裹时,该钩子才有可能被调用。
deactivated
:停用状态,表示当前组件处于后台页面,用户不能与之交互。
注:只有组件被内置组件keep-alive
包裹时,该钩子才有可能被调用。
beforeDestroy
:实例销毁之前调用。在这一步,实例仍然完全可用,即此时实例的data
,methods
等所有数据完全可用。
destroyed
:Vue 实例销毁后调用。此时 Vue实例 指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
errorCaptured
:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回false
以阻止该错误继续向上传播。
指令
指令 (Directives) 是带有v-
前缀的特殊属性。
Vue 提供了以下内置的指令:
-
v-text
:更新元素的textContent
,该指令与使用{{ Mustache }}
插值效果一样。
类型:string
{{msg}}
-
v-html
:更新元素的innerHTML
。
类型:string
注:v-html
的内容只会按普通 HTML 插入,不会作为 Vue 模板进行编译。
注:在单文件组件里,scoped
的样式不会应用在 v-html
内部,因为那部分 HTML 没有被 Vue 的模板编译器处理。
注:在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用v-html
,永不用在用户提交的内容上。
-
v-show
:条件渲染,根据表达式之真假值,切换元素的display
CSS 属性。
类型:any
Hello!
注:v-show
不支持
元素,也不支持v-else
。
-
v-if
:条件渲染,根据表达式的值的真假条件渲染元素。在切换时元素及它的数据绑定 / 组件被销毁并重建。如果元素是
,将提出它的内容作为条件块。
类型:any
Vue is awesome!
注:当和v-if
一起使用时,v-for
的优先级比v-if
更高。
注:v-show = false
时只是把元素设置为:display:none
,元素还留着 DOM 树上。
而v-if = false
时,元素会被整个移除,其上绑定的数据/组件都会被销毁。
-
v-else
:v-if
或v-if-else
的分支。
类型:无
Now you see me
Now you don't
-
v-else-if
:v-if
的分支。
类型:any
A
B
C
Not A/B/C
-
v-for
:遍历源数据,渲染元素列表。
类型:Array | Object | number | string | Iterable
{{ item.text }}
注:v-for
渲染元素时,默认使用“就地更新”策略,即当列表数据改变时,Vue 不会移动当前 DOM 元素来重新匹配数据项,而是根据索引位置重新渲染数据。比如:
现在我们有数据项:
data() {
return {
datas: [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
{ id: 3, name: "three" }
]
}
将这些数据项渲染到页面上:
-
{{item.name}}
我们使用v-for
将每条数据项渲染到一个
上,此时显示效果如下:
如果此时我们勾选第一个
的checkbox
,即one
勾选上,然后再往数据列表前面添加一个数据:this.datas.unshift({ id: 4, name: "four" })
,则可以看到显示效果如下:
可以看到,我们想要的是one
被勾选了,但是效果是数据列表首位被勾选。出现这种现象的原因就是v-for
默认采用的“就地更新”策略:它会复用已渲染完成的 DOM 元素,然后只对变化的数据进行修改,比如这里复用了第一条one
,添加数据项,对第一条
来说,他的数据改变了,但是
不包含在数据项里,因此只会修改数据,将one
修改为four
,而checkbox
仍保持勾选状态。
因此,“就地更新”策略是高效的,但是 只适用于不依赖子组件状态或临时 DOM 状态(例如:表单输入值) 的列表渲染输出。
而要解决上述问题,只需为v-for
提供一个key
属性(key
必须是唯一的),这样 Vue 就可以识别出数据项对应的渲染条目,从而重用和重新排序现有元素:
-
{{item.name}}
由于新添加的数据id=4
,当前已存在的
没有与之对应的标识key
,因此 Vue 会重新渲染一个新的
,并将其与id=4
对应起来,结果如下图所示:
注:“就地更新”策略其实就是使用索引作为节点标识,即:key=index
。
-
v-on
:绑定事件监听器。
缩写:@
类型:Function | Inline Statement | Object
参数:event
修饰符:
▫ .stop
:调用event.stopPropagation()
,停止事件分发。
▫ .prevent
:调用event.preventDefault()
,取消事件的默认动作。
▫ .capture
:添加事件侦听器时使用capture
(捕获)模式。
▫ .self
:只当事件是从侦听器绑定的元素本身触发时才触发回调。
▫ .{keyCode | keyAlias}
:只当事件是从特定键触发时才触发回调。
▫ .native
:监听组件根元素的原生事件。
▫ .once
:只触发一次回调。
▫ .left
: 只当点击鼠标左键时触发。
▫ .right
: 只当点击鼠标右键时触发。
▫ .middle
: 只当点击鼠标中键时触发。
▫ .passive
:以{ passive: true }
模式添加侦听器
注:v-on
用在普通元素上时,只能监听 原生 DOM 事件。用在自定义组件上时,也可以监听子组件触发的自定义事件。
-
v-bind
:动态绑定
缩写::
类型:any (with argument) | Object (without argument)
参数:attrOrProp (optional)
修饰符:
▫ .prop
:被用于绑定 DOM 属性 (property)
▫ .camel
:将 kebab-case 特性名转换为 camelCase(驼峰式)
▫ .sync
:会扩展成一个更新父组件绑定值的 v-on 侦听器
-
v-model
:表单控件与数据属性的双向绑定。
修饰符:
▫ .lazy
:使用
的change
事件进行同步。
▫ .number
:自动将字符串转为数字。
▫ .trim
:输入首尾空格过滤。
-
v-slot
:插槽。
缩写:#
当我们定义组件的时候,有些内容可能需要由父组件传入,因此,此时可以使用插槽,预留出位置给到父组件进行自定义内容传入:
// 子组件:预留插槽
Son Component
// 父组件:传入插槽内容
Parent Component
slot: content from Parent Component
▫ 后备内容:可以通过为
内部提供默认内容,只有当父组件显示传入内容时,才会覆盖默认内容:
Default Content
▫ 具名插槽:我们可以给插槽进行命名(使用name
属性),这样父组件就可指定名字(使用v-for
指令)对特定的插槽进行覆盖:
// 子组件模板
Son Component
// 父组件
Parent Component
替换 header 插槽
替换默认插槽
替换 footer 插槽
注:v-for
只能添加在一个
或 组件 上。
注:默认插槽其实也是一个具名插槽,其名称为:default
。
▫ 插槽 prop:使用 插槽 prop 可以传递子组件的数据给到父组件,使父组件可以在覆盖插槽的内容上使用子组件的数据:
// 子组件
// 父组件:slotProps 接收子组件的 插槽props
{{slotProps.msg}}
-
v-pre
:跳过该元素及其子元素的编译过程。可以用来显示原始 Mustache 标签。
类型:无
{{ this will not be compiled }}
-
v-cloak
:这个指令保持在元素上直到关联实例结束编译。通常结合 CSS 规则来达到隐藏未编译的 Mustache 标签直到实例准备完毕。
类型:无
[v-cloak] {
display: none;
}
{{ message }}
-
v-once
:只渲染元素和组件 一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
This will never change: {{msg}}
- 自定义指令:在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
Vue 提供了两种自定义指令的方式:
- 全局指令:使用
Vue.directive
:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {...})
- 局部指令:组件中定义一个
directives
属性:
// 注册一个局部自定义指令 `v-focus`
directives: {
focus: {...}
}
- 钩子函数:一个指令定义对象可以提供如下几个钩子函数 (均为可选):
▫ bind
:指令第一次绑定到元素时调用。改钩子只会被调用一次,可在此做一些初始化设置。
▫ inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
▫ update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
▫ componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
▫ unbind
:指令与元素解绑时调用。改钩子只会被调用一次,可在此做一些资源释放操作。
示例:使用自定义指令v-customtext
模拟v-text
:
其他
- Class 与 Style 绑定:可以使用
v-bind
对Class
和Style
做动态绑定,并且 Vue 对Class
和Style
的动态绑定做了专门的增强。表达式的类型除了字符串外,还可以使对象或数组:
// 法一:绑定对象:当 isactive === true时,添加类 active,反之去除类 active
// 法二:绑定对象
data: {
classObject: { // 根据属性值添加/去除类:actvie,text-danger
active: true,
'text-danger': false
}
}
// 绑定数组:添加类 active,text-danger
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
// 添加样式:color,fontSize
data: {
activeColor: 'red',
fontSize: 30
}
// 添加样式:直接将样式绑定到一个对象上,更清晰
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
- 组件间通信:
▫ 父传子:子组件通过props
属性可接收父组件传递过来的变量:
// ParentComponent.vue
// SonComponent.vue
{{msg}}
▫ 子传父:子组件可以通过$emit
发送自定义事件向父组件传值,父组件直接注册接收该事件即可:
// SonComponent.vue
// ParentComponent.vue
{{data}}
▫ 父传子孙:父组件可以通过provide
/inject
向其所有子孙后代注入一个依赖,不管组件嵌套多深,依赖都会生效。父组件通过provide
提供变量,子孙组件通过inject
来注入该变量。
// ParentComponent.vue
// SonComponent.vue
{{message}}
更多组件间通信方式,请参考:Vue组件间通信6种方式
参考
Vue 官网
30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)
Vue组件间通信6种方式