在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。结合响应式系统,Vue能智能计算出最少需要重新渲染多少组件,并把DOM操作次数减到最少。
{
beforeCreate () {}, // 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
created () {}, // 组件初始化完毕,各种数据可以使用,常用于异步数据获取
beforeMount () {}, // 未执行渲染、更新,dom未创建
mounted () {}, // 初始化结束,dom已创建,可用于获取访问数据和dom元素
beforeUpdate () {}, // 更新前,可用于获取更新前各种状态
updated () {}, // 更新后,所有状态已是最新
beforeDestroy () {}, // 销毁前,用于一些定时器或订阅取消
destroyed () {}, // 组件已销毁,作用同上
}
组件化是Vue的精髓,Vue应用就是由一个个组件构成的
render()
-> Virtual DOM -> DOMVue.set
(vm.$set
) Vue.set(target, property/index, value)
Vue.delete
(vm.$delete
) Vue.delete(target, property/index)
vm.$emit
触发,回调函数会接收所有传入事件触发函数的额外参数vm.$on('test', function(msg) {
console.log(msg);
})
vm.$emit('test', 'hello');
vm.$once('test', function(msg) {
console.log(msg);
})
vm.$off(); // 没有提供参数时,移除所有的事件监听器
vm.$off('test'); // 如果只提供了事件,则移除该事件所有的监听器
vm.$off('test', callback); // 如果同时提供了事件与回调,则只移除这个回调的监听器
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果,包括:
CSS 过渡动画
v-enter
:定义进入过渡的开始,在元素被插入之前生效,在元素被插入之后的下一帧失效.fade-enter { opacity: 0; }
v-enter-active
: 定义进入过渡生效时的状态。在元素被插入之前生效,在过渡/动画完成之后移除.fade-enter-active { transition: opacity 0.5s; }
v-enter-to
:定义进入过渡的结束状态。在元素被插入之后的下一帧生效(与此同时v-enter被移除),在过渡/动画完成之后移除.fade-enter-to { opacity: 1; }
v-leave
:定义离开过渡的开始状态,在离开过渡被触发时立刻生效,下一帧被移除.fade-leave { opacity: 1; }
v-leave-active
:定义离开过渡生效时的状态,在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。该类可以被用来定义离开过渡的过程时间,延迟和曲线函数。.fade-leave-active { transition: opacity 0.5s; }
v-leave-to
:定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效(与此同时v-else
被删除),在过渡/动画完成之后移除.fade-leave-to { opacity: 0; }
使用CSS动画库
通过自定义过渡类名可以有效结合 Animate.css 这类动画库制作动画效果
<transition
enter-active-class="animated bounceIn"
leave-active-class="animated bounceOut"></transition>
JS动画
可以在 <transition>
属性中声明 JS 钩子,使用 JS 实现动画
<transition
v-on:before-enter="beforeEnter" // 动画开始前,设置初始状态
v-on:enter="enter" // 执行动画
v-on:after-enter="afterEnter" // 动画结束,清理工作
v-on:enter-cancelled="enterCancelled" // 取消动画
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"></transition>
列表过渡
利用 transition-group
可以对 v-for
渲染的每个元素应用过度
<transition-group name="fade">
<div v-for="c in courses" :key="c.name">{{ c.name }} - ¥{{c.price}}
<button @click="addToCart(c)">加购</button>
</div>
</transition-group>
v-bind
表达式<!-- 双花括号 -->
{{ message | capitalize }}
<!-- 在 v-bind 中 -->
<div v-bind:id="rawId | formatId"></div>
{{ c.price | currency('RMB) }}
filter: {
currency(value, symbol = '¥') {
return symbol + value;
}
}
Vue.directive('focus', {
inserted(el) {
el.focus();
}
})
<input v-focus>
指令定义对象钩子函数
bind
:只调用一次,指令第一次绑定到元素时调用,可在此进行一次性的初始化操作inserted
:被绑定元素插入到父节点时调用(仅保证父节点存在,但不一定已经被插入文档)update
:所在组件的 VNode 更新时调用,但可能发生在其子 VNode 更新之前componentUpdate
:指令所在组件的 VNode 及其子 VNode 全部更新后调用unbind
:只调用一次,指令与元素解绑时调用在按钮权限控制中的应用
const role = 'user';
Vue.directive('permission', {
inserted(el, binding) {
if (role !== binding.value) {
el.parentElement.removeChild(el);
}
}
})
<div class="toolbar" v-permission="'admin'"></div>
render: function(createElement) {
// createElement返回的结果是VNode
return createElement(
tag, // 标签名
data, // 数据
children, // 子节点数组
)
}
示例
Vue.component('heading', {
props: {
level: {
type: String,
required: true,
},
title: {
type: String,
default: '',
}
},
render(h) {
return h(
'h' + this.level, // tagname
{ attrs: { title: this.title } } // 参数
this.$slots.default, // 子节点数组
)
}
})
// <heading :level="1" :title="title">{{ title }}</heading>
虚拟 DOM
createElement参数
// @return {VNode}
createElement(
// {String | Object | Function} tagname
'div', // 必填,标签名或组件名,也可以是返回前两个的函数
// {Object}
// 一个与模板中属性对应的数据对象,可选
{
//
},
// {String | Array}
// 子级虚拟节点(VNodes),由createElement()构建而成,
// 也可以使用字符串来生成“文本虚拟节点”,可选
[
'some text',
createElement('h1', 'another text'),
createElement(MyComponent, {
props: {
someProp: 'foobar',
}
}),
]
);
functional
,即意味着它无状态(没有响应式数据),也没有实例(没有 this
上下文) Vue.component('heading', {
functional: true,
props: ['level','title','icon'],
render(h, context) {
let children = [];
// 属性获取
const { icon, title, level } = context.props;
if (icon) {
children.push(h(
'svg',
{ class: 'icon' },
[h('use', { attrs: { 'xlink:href': '#icon-' + icon } })],
));
// 子元素获取
children = children.concat(context.children);
}
vnode = h(
'h' + level,
{ attrs: { title }},
children,
)
return vnode;
}
})
// 定义一个混入对象
var myMixin = {
created: function() {
this.hello();
},
methods: {
hello: function() {
console.log("hello mixin");
}
}
};
// 定义一个使用混入对象的组件
Vue.component('comp', {
mixins: [myMixin]
});
Vue.prototype
上实现MyPlugin.install = function(Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function() {}
// 2. 添加全局资源
Vue.directive('my-directive', {})
// 3. 注入组件选项
Vue.mixin({
created: function() {
}
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function(methodOptions) {}
}
const MyPlugin = {
install(Vue, options) {
Vue.component('heading', {/*...*/});
}
}
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.use(MyPlugin)
}
快速原型开发 安装全局扩展
npm i -g @vue/cli-service-global
启动一个服务并运行原型
vue serve Hello.vue
创建项目
vue create my-vue-app
插件
vue add router
开发
处理资源路径 在 JS、CSS 或 vue 文件中使用相对路径(必须以.开头)引用一个静态资源时,该资源将被 webpack 处理
转换规则
如果 URL 是一个绝对路径,会被保留
<img alt="vue logo" src="/assets/logo.png">
<img alt="vue logo" src="http://img.xx.com/logo.png">
如果 URL 以.
开头会作为一个相对模块请求被解析并给予文件系统相对路径
<img alt="vue logo" src="./assets/logo.png">
如果 URL 以~
开头会作为一个模块被请求被解析,即可以引用 Node 模块中的资源
<img alt="vue logo" src="~some-npm-pkg/foo.png">
如果 URL 以@
开头会作为一个模块请求被解析,VueCLI 默认会设置一个指向 src
的别名 @
import Hello from '@/components/Hello.vue';
什么时候使用 public 文件夹 通过 webpack 的处理
什么时候直接用 public 文件夹
<script>
标签引入没有别的选择使用 public 文件夹的注意事项
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/cart/' : '/'
}
public/index.htmnl
等通过 html-webpack-plugin 用作模板的 HTML 文件中,需要设置链接前缀<link rel="icon" href="<%= BASE_URL %>favicon.ico">
BASE_URL
data() {
return {
publicPath: process.env.BASE_URL,
}
}
// 使用
// <img :src="`${publicPath}my-img.jpg"`">
CSS 相关
使用预处理器
# sass
npm i -D sass-loader node-sass
# Less
npm i -D less-loader less
# Stylus
npm i -D stylus-loader stylus
自动化导入样式 自动化导入样式文件(用于颜色、变量、mixin
等),可以使用 style-resources-loader
npm i -D style-resources-loader
配置
// vue.config.js
const path = required('path')
function addStyleResource(rule) {
rule.use('style-resource')
.loader('style-resources-loader')
.options({
patterns: [
path.resolve(__dirname, './src/styles/import.scss'),
]
})
}
module.exports = {
chainWebpack: config => {
const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
types.forEach(type => {
addStyleResource(config.module.rule('scss').oneOf(type))
})
}
}
Scoped CSS 当 <style>
标签有 scoped
属性时,它的 CSS 只作用于当前组件中的元素
<style scoped>
.red {
color: red;
}
</style>
原理,使用 PostCSS 实现
<template>
<div class="red" data-v-f3f3eg9>hello</div>
</template>
<style>
.red[data-v-f3f3eg9] {
color: red;
}
</style>
混用本地和全局
<style>
/* 全局样式 */
</style>
<style scoped>/* 本地样式 */</style>
深度作用选择器:使用 >>>
操作符可以使 scoped
样式中的一个选择器能够作用的更深
<style scoped>
#app >>> a {
color: red;
}
</style>
Sass 等预处理器无法正确解析 >>>
,这种情况下可以使用 /deep/
或 ::deep
操作符
<style scoped lang="scss">
#app {
/deep/ a {
color: red;
}
::v-deep a {
color: red;
}
}
</style>
CSS Module CSSModules 是用于模块化和组合CSS的系统。vue-loader 提供了与 CSSModules 的集成,可以作为模拟 scoped CSS 的替代方案
<style module lang="scss">
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
模板中通过 $style.xxx
访问
<a :class="$style.red">awesome-vue</a>
<a :class="{[$style.red]: isRed}">awesome-vue</a>
<a :class="[$style.red, $style.bold]">awesome-vue</a>
在 JS 中访问
<script>
export default {
created() {
console.log(this.$style.red); // red_1vyoJ-uz 一个基于文件名和类名生成的标识符
}
}
</script>
数据模拟 使用开发服务器配置 before
选项,可以编写接口,提供模拟数据
// vue.config.js
devServer: {
before(app) {
// app是一个express
app.get('/api/courses', (req, res) => {
res.json([
{ name: 'web', price: 10 },
{ name: 'api', price: 19 }
])
})
}
}
使用
// api/courses.js
import axios from 'axios';
export function getCourses() {
return axios.get('/api/courses').then((res) => res.data);
}
请求代理 设置开发服务器代理选项代理可以有效避免调用接口时出现的跨域问题
// vue.config.js
devServer: {
proxy: 'http://localhost:3000'
}
服务端渲染:将 vue 实例渲染为 HTML 字符串直接返回,在前端激活为交互程序