快速原型开发
npm i -g @vue/cli
npm i -g @vue/cli-service-global
您可以*.vue使用vue serve和vue build命令只使用一个文件快速进行原型设计,但是它们需要先安装一个额外的全局插件:
npm install -g @vue/cli-service-global
vue-router
产生的每个页面,它本质上也是一个组件(.vue)业务组件更像是介于第一类和第二类之间,在开发上也与独立组件类似,但寄托于项目,你可以使用项目中的技术栈,比如 Vuex、axios、echarts 等,所以它的开发难度相对独立组件要容易点,但也有必要考虑组件的可维护性和复用性。
一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue.js 组件的 API
属性prop
prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。
组件中定义了两个属性:尺寸 size 和 是否禁用 disabled。其中 size 使用 validator
进行了值的自定义验证,也就是说,从父级传入的 size,它的值必须是指定的 small、large、default 中的一个,默认值是 default,如果传入这三个以外的值,都会抛出一条警告。
使用
测试
default测试
测试
属性验证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wtcTR4wW-1581341665189)(Vue特训.assets/1566653497295.png)]
插槽slot
如果要给上面的按钮组件
添加一些文字内容,就要用到组件的第二个 API:插槽 slot,它可以分发组件的内容,比如在上面的按钮组件中定义一个插槽:
当需要多个插槽时,会用到具名 slot,比如上面的组件我们再增加一个 slot,用于设置另一个图标组件:
自定义事件
ref
:给元素或组件注册引用信息;$parent
/ $children
:访问父 / 子实例。
`$parent` 和 `$children` 类似,也是基于当前上下文访问父组件或全部子组件的。
这两种方法的弊端是,无法在**跨级**或**兄弟**间通信
https://cn.vuejs.org/v2/api/#provide-inject](https://cn.vuejs.org/v2/api/#provide-inject)
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
看不懂上面的介绍没有关系,不过上面的这句提示应该明白,就是说 Vue.js 不建议在业务中使用这对 API,而是在插件 / 组件库(比如 iView,事实上 iView 的很多组件都在用)。不过建议归建议,如果你用好了,这个 API 会非常有用。
//父组件
provide(){
return {
parent : this,
};
},
//子组件
inject : [ 'parent' ],//注入,this.parent
注意点
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
$emit会在**当前组件**实例上触发自定义事件,并传递一些参数给监听器的回调,一般来说,都是在父级调用这个组件时,使用
@on` 的方式来监听自定义事件的,比如在子组件中触发事件:
// child.vue,部分代码省略
export default {
methods: {
handleEmitEvent () {
this.$emit('test', 'Hello Vue.js');
}
}
}
在父组件中监听由 child.vue 触发的自定义事件 test:
这里看似是在父组件 parent.vue 中绑定的自定义事件 test 的处理句柄,然而事件 test 并不是在父组件上触发的,而是在子组件 child.vue 里触发的,只是通过 v-on
在父组件中监听。既然是子组件自己触发的,那它自己也可以监听到,这就要使用 $on
来监听实例上的事件,换言之,组件使用 $emit
在自己实例上触发事件,并用 $on
监听它。
$on
监听了自己触发的自定义事件 test,因为有时不确定何时会触发事件,一般会在 mounted
或 created
钩子中来监听。
仅上面的示例,的确是多此一举的,因为大可在 handleEmitEvent 里直接写 window.alert(text),没必要绕一圈。
之所以多此一举,是因为 handleEmitEvent 是当前组件内的 调用的,如果这个方法不是它自己调用,而是其它组件调用的,那这个用法就大有可为了。
虽然 Vue.js 1.x 已经成为过去时,但为了充分理解本节通信方法的使用场景,还是有必要来了解一点它的历史。
在 Vue.js 1.x 中,提供了两个方法:$dispatch
和 $broadcast
,前者用于向上级派发事件,只要是它的父级(一级或多级以上),都可以在组件内通过 $on
(或 events,2.x 已废弃)监听到,后者相反,是由上级向下级广播事件的。
先来看下 emitter.js 的代码:
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
const name = child.$options.name;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
因为是用作 mixins 导入,所以在 methods 里定义的 dispatch 和 broadcast 方法会被混合到组件里,自然就可以用 this.dispatch
和 this.broadcast
来使用。
这两个方法都接收了三个参数,第一个是组件的 name
值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。
可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName
与某个上级组件的 name
选项一致时,结束循环,并在找到的组件实例上,调用 $emit
方法来触发自定义事件 eventName
。broadcast 方法与之类似,只不过是向下遍历寻找。
使用
// 部分代码省略
import Emitter from '../mixins/emitter.js'
export default {
mixins: [ Emitter ],
methods: {
handleDispatch () {
this.dispatch(); // ①
},
handleBroadcast () {
this.broadcast(); // ②
}
}
}
上例中行 ① 和行 ② 的两个方法就是在导入的混合 emitter.js 中定义的,这个稍后我们再讲,先来分析这两个方法应该传入什么参数。一般来说,为了跟 Vue.js 1.x 的方法一致,第一个参数应当是自定义事件名,比如 “test”,第二个参数是传递的数据,比如 “Hello, Vue.js”,但在这里,有什么问题呢?只通过这两个参数,我们没办法知道要在哪个组件上触发事件,因为自行实现的这对方法,与 Vue.js 1.x 的原生方法机理上是有区别的。上文说到,实现这对方法的关键点在于准确地找到组件实例。那在寻找组件实例上,我们的“惯用伎俩”就是通过遍历来匹配组件的 name
选项,在独立组件(库)里,每个组件的 name
值应当是唯一的,name 主要用于递归组件,在后面小节会单独介绍。
来看一下具体的使用方法。有 A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:
// B.vue
export default {
name: 'componentB',
created () {
this.$on('on-message', this.showMessage);
},
methods: {
showMessage (text) {
window.alert(text);
}
}
}
同理,如果是 B 向 A 通信,在 B 中调用 dispatch 方法,在 A 中使用 $on 监听事件即可。
以上就是自行实现的 dispatch 和 broadcast 方法,相比 Vue.js 1.x,有以下不同:
findComponents 系列方法,它并非 Vue.js 内置,而是需要自行实现,以工具函数的形式来使用,它是一系列的函数,可以说是组件通信的终极方案。findComponents 系列方法最终都是返回组件的实例,进而可以读取或调用该组件的数据和方法。
它适用于以下场景:
5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。
// assist.js
// 由一个组件,向上找到最近的指定组件
function findComponentUpward (context, componentName) {
let parent = context.$parent;
let name = parent.$options.name;
while (parent && (!name || [componentName].indexOf(name) < 0)) {
parent = parent.$parent;
if (parent) name = parent.$options.name;
}
return parent;
}
export { findComponentUpward };
findComponentUpward 接收两个参数,第一个是当前上下文,比如你要基于哪个组件来向上寻找,一般都是基于当前的组件,也就是传入 this
;第二个参数是要找的组件的 name
。
findComponentUpward 方法会在 while 语句里不断向上覆盖当前的 parent
对象,通过判断组件(即 parent)的 name 与传入的 componentName 是否一致,直到直到最近的一个组件为止。
使用
使用起来很简单,只要在需要的地方调用 findComponentUpward 方法就行,第一个参数一般都是传入 this,即当前组件的上下文(实例)。
上例的 comA,保险起见,加了一层 if (comA)
来判断是否找到了组件 A,如果没有指定的组件而调用的话,是会报错的。
findComponentUpward 只会找到最近的一个组件实例,如果要找到全部符合要求的组件,就需要用到下面的这个方法。
// assist.js
// 由一个组件,向上找到所有的指定组件
function findComponentsUpward (context, componentName) {
let parents = [];
const parent = context.$parent;
if (parent) {
if (parent.$options.name === componentName) parents.push(parent);
return parents.concat(findComponentsUpward(parent, componentName));
} else {
return [];
}
}
export { findComponentsUpward };
与 findComponentUpward 不同的是,findComponentsUpward 返回的是一个数组,包含了所有找到的组件实例(注意函数名称中多了一个“s”)。
findComponentsUpward 的使用场景较少,一般只用在递归组件里面(后面小节会介绍),因为这个函数是一直向上寻找父级(parent)的,只有递归组件的父级才是自身。事实上,iView 在使用这个方法也都是用在递归组件的场景,比如菜单组件 Menu。由于递归组件在 Vue.js 组件里面并不常用,那自然 findComponentsUpward 也不常用了。
代码如下:
// assist.js
// 由一个组件,向下找到最近的指定组件
function findComponentDownward (context, componentName) {
const childrens = context.$children;
let children = null;
if (childrens.length) {
for (const child of childrens) {
const name = child.$options.name;
if (name === componentName) {
children = child;
break;
} else {
children = findComponentDownward(child, componentName);
if (children) break;
}
}
}
return children;
}
export { findComponentDownward };
context.$children
得到的是当前组件的全部子组件,所以需要遍历一遍,找到有没有匹配到的组件 name
,如果没找到,继续递归找每个 $children 的 $children,直到找到最近的一个为止。
// assist.js
// 由一个组件,向下找到所有指定的组件
function findComponentsDownward (context, componentName) {
return context.$children.reduce((components, child) => {
if (child.$options.name === componentName) components.push(child);
const foundChilds = findComponentsDownward(child, componentName);
return components.concat(foundChilds);
}, []);
}
export { findComponentsDownward };
这个函数实现的方式有很多,这里巧妙使用 reduce
做累加器,并用递归将找到的组件合并为一个数组并返回,代码量较少,但理解起来稍困难。
用法与 findComponentDownward 大同小异
// assist.js
// 由一个组件,找到指定组件的兄弟组件
function findBrothersComponents (context, componentName, exceptMe = true) {
let res = context.$parent.$children.filter(item => {
return item.$options.name === componentName;
});
let index = res.findIndex(item => item._uid === context._uid);
if (exceptMe) res.splice(index, 1);
return res;
}
export { findBrothersComponents };
相比其它 4 个函数,findBrothersComponents 多了一个参数 exceptMe
,是否把本身除外,默认是 true。寻找兄弟组件的方法,是先获取 context.$parent.$children
,也就是父组件的全部子组件,这里面当前包含了本身,所有也会有第三个参数 exceptMe。Vue.js 在渲染组件时,都会给每个组件加一个内置的属性 _uid
,这个 _uid 是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。
举个例子,组件 A 是组件 B 的父级,在 B 中找到所有在 A 中的兄弟组件(也就是所有在 A 中的 B 组件):
组件 A
组件 B
在 ① 的位置,打印出的内容为空数组,原因是当前 A 中只有一个 B,而 findBrothersComponents 的第三个参数默认是 true,也就是将自己除外。如果在 A 中再写一个 B:
组件 A
这时就会打印出 [VueComponent]
,有一个组件了,但要注意在控制台会打印两遍,因为在 A 中写了两个 B,而 console.log
是在 B 中定义的,所以两个都会执行到。如果你看懂了这里,那应该明白打印的两遍 [VueComponent]
,分别是另一个
(如果没有搞懂,要仔细琢磨琢磨哦)。
如果将 B 中 findBrothersComponents 的第三个参数设置为 false:
// component-b.vue
export default {
name: 'componentB',
mounted () {
const comsB = findBrothersComponents(this, 'componentB', false);
console.log(comsB);
}
}
此时就会打印出 [VueComponent, VueComponent]
,也就是包含自身了。
以上就是 5 个函数的详细介绍,get 到这 5 个,以后就再也不用担心组件通信了。
父向子传递数据通过props
1-父组件直接传递数据
========================================================
父组件传递 a = {
{ a }}
=====父组件
parent父组件
父组件:
methods : {
changeA(value){
this.a = value;
},
},
=====子组件
son1子组件
父组件传递过来数据 a = {
{ a }}
子组件son1
子组件触发
this.$emit('update:a', this.b);//.sync父子组件双向数据通信
//子组件触发
this.$emit('input', this.b);
v-model 就是value和input的语法糖
methods:{
changeParent(){
// eventBus
// $dispatch 只会通知自己的父亲
this.$dispatch('input',200)
}
},
// 向上通知
Vue.prototype.$dispatch = function(eventName,value){
let parent = this.$parent;
while(parent){
parent.$emit(eventName,value);
parent = parent.$parent
}
}
// 向下传递
Vue.prototype.$broadcast = function(eventName,value){
// 获取当前组件下的所有的孩子
const broadcast = (children) =>{
children.forEach(child => {
child.$emit(eventName,value);
if(child.$children){
broadcast(child.$children);
}
});
}
broadcast(this.$children);
}
$attrs:属性的集合
$listeners方法的集合
inheritAttrs : false,//属性不挂载在DOM结构上
//parent
//son2
son2
//孙子grandSon2
grandSon2
{
{ $attrs }}
所有的子组件都可以使用
数据的注入
父组件
provide(){
return {
parent : this,
};
},
子组件使用父组件数据
inject : [ 'parent' ],//注入,this.parent
调用父组件的数据
{
{ this.parent.pA }}
数据会变为全局的数据 , 一般不是很推荐使用
父组件
this.$nextTick(() => {
console.log(this.$refs.son2.son2);
this.$refs.son2.sonTest();
});
//EventBus的使用,直接挂载在Vue的原型上
Vue.prototype.$bus = new Vue(); //$on $emit
mounted(){
this.$bus.$on('son1', (params) => {
console.log('son1的$bus挂载的方法', params);
});
},
this.$nextTick(() => {
console.log('======= granSon2');
this.$bus.$emit('son1', 'granSon2');
});
函数式组件 , 没有模板必须有一个render函数
///函数式组件
export default {
props : {
t : {},
},
render(h){//createElement
let tag = 'h' + this.t;
return {this.$slots.default}
},
};
//使用
jsx
标题1
标题2
标题3
标题4
标题5
标题6
列表循环的原始写法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fiye38Ie-1581341665193)(assets/1566059177219.png)]
//父组件
{
{ item }}
JSX写法
//父组件
methods : {
renderAction(h, data){
console.log(data);//data为每一个参数数据
return { data };
},
},
//子组件
{
{ item }}
//函数式组件
export default {
props : {
item : {//数据
type : String,
},
render : {//用户自定义的处理函数
type : Function,
},
},
render(h){
//return this.render(h, this.item);
//也可以直接自定义render
return [ h('div', {
style : {
background : 'rgba(153,153,153,0.42)',
},
}, [
h('p', {
style : {
color : 'red',
},
}, this.item),
h('Tag', {
props : {
color : 'primary',
},
}, this.item),
]),
];
},
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4kCASiz8-1581341665194)(assets/1566060673778.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mw9QQPJW-1581341665195)(assets/1566038161017.png)]
级联组件的编写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-COaLEYt4-1581341665197)(assets/1566038171520.png)]
Vue单元测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aMbcy4Pr-1581341665198)(assets/1566038192007.png)]
Vue权限菜单及按钮权限设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FM9n71iA-1581341665200)(assets/1566038236099.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MmDvffUg-1581341665200)(assets/1566038339711.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Reiq1LJv-1581341665201)(assets/1566038351159.png)]
keep-alive可以实现组件的缓存功能 , 缓存当前组件的实例
1、keep-alive 用法(官方):
Props:
include - 字符串或正则表达式。只有匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何匹配的组件都不会被缓存。
用法:
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
2、当一个组件a.vue被缓存的时候,那么第一次加载这个组件的时候,会执行组件的所有周期函数created()、mounted()等等,但是第二次打开a组件时,就不会触发这些生命周期钩子函数,但是会触发activated 和 deactivated这两个钩子函数(因为被缓存了);
keep-alive源码
/* @flow */
import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'
type VNodeCache = { [key: string]: ?VNode };
function getComponentName (opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag)
}
function matches (pattern: string | RegExp | Array, name: string): boolean {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
/* istanbul ignore next */
return false
}
function pruneCache (keepAliveInstance: any, filter: Function) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode: ?VNode = cache[key]
if (cachedNode) {
const name: ?string = getComponentName(cachedNode.componentOptions)
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
function pruneCacheEntry (
cache: VNodeCache,
key: string,
keys: Array,
current?: VNode
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
const patternTypes: Array = [String, RegExp, Array]
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
const key: ?string = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
提高复用性 / 增加在吗的可维护性
减少不必要的渲染 (尽可能细化拆分组件)
当值为false的时候内部指令不执行 , 具有阻断功能 , 很多情况下使用v-if代替v-show
异步组件: https://github.com/Coffcer/Blog/issues/3
这是我推荐的一种做法,简单有效。还是那个结构,我们给要延迟渲染的组件加上v-if:
new Vue({
data: {
showB: false,
showC: false
},
created () {
// 显示B
setTimeout(() => {
this.showB = true;
}, 0);
// 显示C
setTimeout(() => {
this.showC = true;
}, 0);
}
});
这个示例写起来略显啰嗦,但它已经实现了我们想要的顺序渲染的效果。页面会在A组件初始化完后显示,然后再按顺序渲染其余的组件,整个页面渲染方式看起来是流式的。
有些人可能会担心v-if
存在一个编译/卸载过程,会有性能影响。但这里并不需要担心,因为v-if
是惰性的,只有当第一次值为true时才会开始初始化。
这种写法看起来很麻烦,如果我们能实现一个类似v-if
的组件,然后直接指定多少秒后渲染,那就更好了,例如:
一个简单的指令即可,不需要js端任何配合,并且可以用在普通dom上面,Nice!
在vue里,类似v-if
和v-for
这种是terminal指令,会在指令内部编译组件。如果你想要自己实现一个terminal指令,需要加上terminal: true
,例如:
Vue.directive('lazy', {
terminal: true,
bind () {},
update () {},
unbind () {}
});
这是vue在1.0.19+新增的功能,由于比较冷门,文档也没有特别详细的叙述,最好的方式是参照着v-if
和v-for
的源码来写。
我已经为此封装了一个terminal指令,你可以直接使用:
https://github.com/Coffcer/vue-lazy-component
如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
不要使用对象或数组之类的非基本类型值作为
v-for
的key
。请用字符串或数值类型的值。
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。
let obj = {
foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
描述对象的enumerable
属性,称为“可枚举性”,如果该属性为false
,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略enumerable
为false
的属性。
for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。如果真的想将对象冻结,应该使用Object.freeze
方法。
动态加载组件 ,依赖 webpack-codespliting 功能
export default new Router({
routes : [
...testRouter,
{
path : '/',
component : () => import('../views/main'),
}, {
path : '/sw',
name : 'sw',
component : () => import('../views/sw'),
}
});
https://zhuanlan.zhihu.com/p/26710831
动态导入组件
imprt Dialog from "./Dialog"
export default{
components:{
Dialog:()=>import("./Dialog")
}
}
开发时尽量使用单文件的方式 , vue在webpack打包时会执行模板的转化
vuex-persist 合理使用 (防抖/节流)
第三方模块按需导入 (babel-plugin-component)
图片懒加载 滚动到可视区域动态加载
滚动渲染可视化区域 数据较大时只渲染可视区域(计算scroolTop)
配置webpack插件 vue-skeleton-webpack-plugin
单页骨架屏
app-shell
pwa manifest serviceWorker
http://bit.baidu.com/course/datalist/column/124.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IjYwCGzp-1581341665203)(Vue特训.assets/1566225340517.png)]
vue的预渲染插件
什么是服务端渲染
解决方法很简单,打包 vender 时不打包 vue、vuex、vue-router、axios 等,换用国内的 bootcdn 直接引入到根目录的 index.html 中。
在 webpack 里有个 externals,可以忽略不需要打包的库
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios'
}
多线程打包 happypack
splitChunks抽离公共文件
sourceMap的配置
服务端缓存/ 客户端缓存
服务端gzip压缩
https://juejin.im/post/5c179dcc51882521eb44a3b4
https://juejin.im/post/5cc81076e51d456e361ed97e
技术栈:前台Vue 后台Nodejs
服务器 前台nginx 后台Nodejs
jenkins持续交付流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8XuO80ld-1581341665204)(Vue特训.assets/16519d61b8d7710e-1566228124825)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8MU8HEj3-1581341665205)(assets/1566151013750.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WHP6s4ls-1581341665206)(assets/1566151032038.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tUiII9du-1581341665207)(assets/1566151091744.png)]
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
}
其几个核心的关键属性就差不多了,例如:
tag
属性即这个vnode
的标签属性data
属性包含了最后渲染成真实dom
节点后,节点上的class
,attribute
,style
以及绑定的事件children
属性是vnode
的子节点text
属性是文本属性elm
属性为这个vnode
对应的真实dom
节点key
属性是vnode
的标记,在diff
过程中可以提高diff
的效率[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EfbsoxfH-1581341665207)(assets/1566151124688.png)]
// http://eslint.org/docs/user-guide/configuring
module.exports = {
// 将 ESLint 限制到一个特定的项目,在配置文件里设置 "root": true。ESLint 一旦发现配置文件中有 "root": true,它就会停止在父级目录中寻找。
root: true,
// 检测ES6代码
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
//
env: {
browser: true,
},
// 消除no-undef影响
globals: {
_: true
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'vue',
'html'
],
// add your custom rules here
// 0或’off’:关闭规则。
// 1或’warn’:打开规则,并且作为一个警告(并不会导致检查不通过)。
// 2或’error’:打开规则,并且作为一个错误 (退出码为1,检查不通过)。
// 参数说明:
// 参数1 : 错误等级
// 参数2 : 处理方式
'rules': {
'prefer-promise-reject-errors': 0,
'space-unary-ops': 0,
'no-unused-expressions': 0,
'no-useless-return': 0,
'standard/no-callback-literal': 0,
'import/first': 0,
'import/export': 0,
'no-mixed-operators': 0,
'no-use-before-define': 0,
// 允许使用分号
'semi': [0, 'never'],
// 允许使用==
'eqeqeq': 0,
// 缩进使用不做限制
'indent': 0,
// 允许使用tab
'no-tabs': 0,
// 函数圆括号之前没有空格
'space-before-function-paren': [2, "never"],
// 不要求块内空格填充格式
'padded-blocks': 0,
// 不限制变量一起声明
'one-var': 0,
// debugger使用
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
// 开发模式允许使用console
'no-console': 0,
// 条件语句中复制操作符需要用圆括号括起来
'no-cond-assign': [2, 'except-parens'],
// 允许使用条件表达式使用常量
'no-constant-condition': 0,
// 单行可忽略大括号,多行不可忽略
'curly': [2, 'multi-line'],
// 不允许使用var变量
'no-var': 2,
// 不允许出现多个空格
'no-multi-spaces': ["error", { ignoreEOLComments: true }],
'camelcase': 0,
// 对象字面量的键值空格风格
'key-spacing': 2,
// if语句包含一个return语句, else就多余
'no-else-return': 2,
// 建议将经常出现的数字提取为变量
'no-magic-numbers': [0, {ignoreArrayIndexes: true}],
// 不允许重复声明变量
'no-redeclare': [2, {builtinGlobals: true}],
// 立即执行函数风格
'wrap-iife': [2, 'inside'],
// 不允许圆括号中出现空格
'space-in-parens': [2, 'never'],
// 确保运算符周围有空格
'space-infix-ops': 2,
// 强制点号与属性同一行
'dot-location': [2, 'property'],
// 强制单行代码使用空格
'block-spacing': [2, 'always'],
// 约束for-in使用hasOwnProperty判断
'guard-for-in': 0,
// 采用one true brace style大括号风格
'brace-style': [2, '1tbs', {'allowSingleLine': true}],
// 统一逗号周围空格风格
'comma-spacing': [2, {'before': false, 'after': true}],
// 禁止出现多个空行
'no-multiple-empty-lines': [2, {'max': 1, 'maxEOF': 2}],
// 允许箭头函数不使用圆括号
'arrow-parens': 0,
// 规范generator函数的使用
'generator-star-spacing': [2, {'before': false, 'after': true}],
// 要求在块级
'lines-around-comment': [2, {'beforeBlockComment': true, 'afterBlockComment': false, 'beforeLineComment': true, 'afterLineComment': false}]
}
}
webpack多线程打包
parallel: require('os').cpus().length > 1,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nElWcBTE-1581341665209)(Vue特训.assets/1566659087789.png)]