Vue开发必备入门知识
渐进式Javascript框架 - Progressive javascript framework
渐进式: vue可以一步步的拓展能力,vue core提供声明式渲染,组件系统等功能。 客户端路由由vue-router提供,大规模状态管理由vuex提供,构建工具由vue-cli负责。
- 易用: vue语法简单,有明显的html,css,js三层结构。
- 灵活: vue支持vue.install注册插件,并提供多种接口。第三方插件快速接入。
- 高效: vue文件小,以及快速的diff算法,让virtual dom渲染更少,更快。
对比 - Compare
| jQuery | Vue |
|---|---|
| 便捷dom操作 | 数据驱动 |
| 浏览器兼容性更强 | 单文件组件 |
| 更多的插件 | 无桥梁,dom无关 |
| virtual dom 异步渲染 | |
| 状态管理 |
操作 - Operation
为一个标签添加点击事件,并修改文本
jQuery实现
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./jquery.1.12.4.js"></script>
</head>
<body>
<p id="text">Hello World</p>
<script>
(function () {
// 获取dom、注册原生事件
$('body').on('click', '#text', function () {
// 获取dom,直接修改文本节点html
$('#text').text('你好世界')
});
})();
</script>
</body>
</html>
vue实现
<template>
<!--模板注册事件,绑定数据对象-->
<p @click="handleClick">{{text}}</p>
</template>
<script>
export default {
data () {
return {
// 设定数据初始值
text: 'Hello World'
}
},
methods: {
handleClick () {
// 修改数据,由Vue监控数据更新后异步渲染更新Dom
this.text = '你好世界';
}
}
}
</script>
使用jQuery操作时,有额外的dom操作。而且没有保存该对象的时候需要多次进行获取dom的操作。 vue则是通过模板的方式展示数据,并以直观的方式在标签上绑定事件和要执行的方法。 dom绑定和视图更新的操作在vue管理。在vue中一般只需关注数据的更新即可。
生命周期 - LifeCycle
js是单线程的语言。正常情况下,js代码会按顺序执行,除开回调函数和事件机制的情况,在js中代码的执行顺序基本就是我们代码的书写顺序。 vue提供了一些生命周期钩子(类似事件机制),这里我截取了vue源码中初始化的代码,并配合实际调用代码进行注释说明不同阶段的区别。
只截取经常用也比较容易用错的部分
initLifecycle(vm) // 重置配置信息,设置父节点
initEvents(vm) // 注册vue事件管理器
initRender(vm) // 处理插槽和父节点vnode
callHook(vm, 'beforeCreate')
initInjections(vm) // 处理inject数据, 并增加监听
initState(vm) // 依次处理props,methods,data,computed,watch
initProvide(vm) //provide暴露数据
callHook(vm, 'created')
// 省略..
vm.$el = el
// 检查template标签或者render函数
callHook(vm, 'beforeMount')
let updateComponent
updateComponent = () => {
// 重新渲染模板
vm._update(vm._render(), hydrating)
}
// 注册update监听器
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
<script>
export default {
beforeCreated () {
// 可以访问原型方法
console.log('实例创建之前');
},
created () {
// 可以访问data, props的数据, methods方法
console.log('实例创建之后,但还未被挂载至虚拟dom');
},
beforeMount () {
console.log('实例挂载dom之前');
},
mounted () {
// 可以访问this.$refs, this.$el
console.log('实例挂载dom之后');
}
}
</script>
注意:不同阶段能访问的内容不同
状态管理 - State
| Vue | Vuex |
|---|---|
| props:外部入参 | mapState:全局唯一数据 |
| data:组件自身数据 | mapGetters:派生state |
| computed:派生数据 | |
| provide/inject:祖孙 |
vue可通过以上几种方式获取保存数据。当然遵循js语法还是访问作用域内能访问到的变量。 但是在template里的数据必须是以上几种或是vue提供的api
provide/inject: vue2.20新增,可以对后代传递数据
组件 - Component
vue明显的三段式代码,让人快速联想到了html,js,css。同时三者结合是一个无依赖组件基本要求。 通过vue提供的统一的交互方式v-model或.sync可以制作一个通用的组件。 以下我简易实现了一个下拉选择器
<template>
<div class="dropdown">
<a class="dropdown-link" @click="isShowDropList = true">{{innerValue}}</a>
<ul v-show="isShowDropList" class="dropdown-menu">
<!--template写的方法不会立刻执行,而是控制传入的参数, 通过$event获取原本的事件参数-->
<li v-for="{value, label} in enums" @click="handleClick(value, $event)">{{label}}</li>
</ul>
</div>
</template>
<script>
const DEFAULT_LABEL = '请选择';
export default {
name: 'selector',
props: {
enums: {
type: Array,
required: true
},
// 使用value获取外界值
value: {
// 传入类型检测
type: [Number, String],
required: true
}
},
// data数据为该组件内部使用状态
data () {
return {
isShowDropList: false
}
},
// 计算属性用于获得需要基础数据二次计算的数据,懒加载
computed: {
innerValue () {
const {label = DEFAULT_LABEL} = this.enums.find(({value: _value}) => _value === this.value) || {};
return label;
}
},
methods: {
// 使用事件触发数据更新
handleClick (value) {
// 使用v-model语法糖时,触发input事件
this.$emit('input', value);
// 使用.sync语法糖时,触发update:xx事件
this.$emit('update:value', value);
},
handleClose () {
this.isShowDropList = false
}
},
mounted () {
// mounted保证dom已挂载真实dom, 此时才可使用dom相关方法
document.body.addEventListener('click', this.handleClose, false)
},
beforeDestroy () {
// destroyed会将dom移除,需在beforeDestroy阶段前进行dom操作
document.body.removeEventListener('click', this.handleClose, false)
}
}
</script>
<style lang="less" scoped>
// 省略css代码
.dropdown {
.dropdown-link {
}
.dropdown-menu {
}
}
</style>
组件化提高了代码的内聚性
插槽 - slot
使用插槽可以让组件插入到已定义的模板之中,以进行布局的控制
<template>
<div class="page-panel page-content" flex="dir:top" flex-box="1">
<div class="page-panel-toolbar" v-if="$slots.toolbar">
<slot name="toolbar"></slot>
</div>
<div class="page-panel-main" flex-box="1">
<slot></slot>
</div>
<div class="page-panel-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
}
</script>
以下代码实现了一个后端水印组件,只需通过这个组件进行包装即可对内层内容添加水印效果
<template>
<div class="watermark-wrapper">
<div class="watermark" :style="{backgroundImage: watermarkStyle, opacity}"></div>
<slot></slot>
</div>
</template>
<script>
export default {
props: {
img: {
type: String,
default: '/watermark'
},
opacity: {
type: Number,
default: 0.1
}
},
computed: {
watermarkStyle: function () {
return `url('${this.img}')`
}
}
}
</script>
<style lang="less" scoped>
.watermark-wrapper {
position: relative;
.watermark {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
z-index: 2;
pointer-events: none;
}
}
</style>
组件内的特殊实现可以抽象出来通过slot插槽在外部实现
组合 - Composition
| mixins | extends |
|---|---|
| 根据数组顺序混合mixin | 单继承 |
| 生命周期钩子均执行 | 优先执行生命周期钩子 |
mixins: 可以提供通用的方法,复用代码
以下代码可以通过方法跟踪长时(ajax)操作
export default {
data () {
return {
isLoading: false
}
},
methods: {
// 加载前钩子
beforeLoading () {
},
// 加载后钩子
afterLoading () {
},
loading () {
this.beforeLoading();
this.isLoading = true
},
loaded () {
this.afterLoading();
this.isLoading = false
}
}
}
extends: 适合显示覆盖原组件的原有实现
以下代码为dialog组件增加了一个将dom插入至body层的功能
<script>
import { Dialog } from 'my-vue-component';
export default {
name: 'unique-dialog',
// 继承已有dialog组件
extends: Dialog,
props: {
// 将组件插入至body层
appendToBody: {
type: Boolean,
default: true
}
},
watch: {
show (show) {
if (show && this.appendToBody) {
document.body.appendChild(this.$el);
}
}
},
mounted () {
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
},
destroyed () {
if (this.appendToBody && this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
}
}
</script>
抽象 - Abstract
页面一般由多个部分组成,仔细观察其实有些部分只有数据信息上的差异。

如果是一个后台系统的增删改查,可以抽象成以上组件。只需对不同的字段部分进行修改和管理即可 。
字段 - Field
一个后台系统的增删改查,不考虑特殊情况的话,只有对应的字段信息不同。把字段从代码中抽离,让组件更加通用化
- 抽离业务相关部分
- 抽象组件渲染方式
- 定义统一格式的字段管理方案
- 提高可维护性
import {typesEnum, statusEnums} from './enums';
import ShowContentComponent from '../component/content/show';
import UserChooserComponent from '../component/user-chooser';
export const id = {
prop: 'id',
label: '公告id'
};
export const title = {
prop: 'title',
label: '公告标题',
required: true
};
export const contents = {
prop: 'contents',
label: '公告内容',
required: true,
listComponent: ShowContentComponent
};
export const type = {
prop: 'type',
label: '公告重要程度',
type: 'select',
enums: typesEnum,
required: true
};
export const beginTime = {
prop: 'begin_time',
label: '公告生效时间'
};
export const endTime = {
prop: 'end_time',
label: '公告失效时间'
};
export const periodTime = {
prop: 'period_time',
props: [beginTime.prop, endTime.prop],
label: '发布生效时间',
type: 'datetimerange',
required: true
};
export const creator = {
prop: 'create_user',
label: '创建人',
editComponent: UserChooserComponent
};
export const status = {
prop: 'status',
label: '状态',
type: 'select',
multiple: false,
enums: statusEnums
};
由于查询条件的组件比较复杂,这里仅提供列表组件渲染模板。
<template>
<el-table :data="datas">
<el-table-column
v-for="{prop, label, listComponent, enums, displayProp} in configs" :key="prop"
:label="label"
:prop="prop">
<template slot-scope="{row}">
<!--优先使用动态组件处理-->
<component v-if="listComponent" :is="listComponent" v-model="row[prop]" :data.sync="row[prop]"/>
<!--专用显示字段-->
<span v-else-if="displayProp">{{row[displayProp] | mapArray | mapDefault }}</span>
<!--使用枚举显示字段信息-->
<span v-else-if="enums">{{getEnumsValue(enums, row[prop]) | mapDefault }}</span>
<!--默认显示-->
<span v-else>{{row[prop] | mapArray | mapDefault }}</span>
</template>
</el-table-column>
<!--可以增加额外的列-->
<slot></slot>
</el-table>
</template>
<script>
import GetEnumsValueMixin from 'src/modules/mixins/get-enums-value';
export default {
mixins: [GetEnumsValueMixin],
// 过滤器,控制文本显示
filters: {
mapArray (val) {
return Array.isArray(val) ? val.join(',') : val;
},
mapDefault (value) {
return value || '--';
}
},
// 动态传入配置和数据
props: {
configs: {
type: Array,
default () {
return [];
}
},
datas: {
type: Array,
default () {
return [];
}
}
}
}
</script>
开发者工具 - Devtools
vue对浏览器提供了vue.js devtools。vue运行在development模式时,即可激活。
- 左侧面板可以查看到编写组件之间的嵌套关系,并快速定位要查看的组件
- 右侧面板可以查看当前组件所挂载的数据,包括props,data,computed,inject,只有data内的数据可以修改,修改会直接显式的在页面上查看到效果

不推荐dom操作 - Deprecated
this.$el:访问当前组件的最顶层原属this.$refs:访问指定ref属性的元素