26.vue源码分析
1.用到的js知识
1)伪数组如何转成真数组
const list2 = [...list1]
const list3 = Array.from(list1)
const list4 = Array.prototype.slice.call(list1) //改变数组方法的调用者,并将伪数组的元素全部截取并返回
!! const list5 = [].slice.call(list1)
2)节点类型
console.log(eleNode.nodeType) // 1 标签
console.log(attrNode.nodeType) // 2 属性
console.log(txtNode.nodeType) // 3 文本
3) Object.definePrototype()方法---为对象添加属性并配置属性特性
const person = {
firstName: '东方',
lastName: '华哥'
}
Object.defineProperty(person, 'fullName', {
configurable: true, // 默认是false,不能被重设设置,是否可以被删除
enumerable: false, // 默认是false,是否可以枚举
// value:'哈_哈', // 该属性的默认值
// writable:false , // 默认是false, 是否可以被重写
!! get() {// 要使用get()和set()就不能设置默认值和writable
// console.log(person.fullName) 就会进入到get
return this.firstName + '_' + this.lastName
},
set(val) {
// 什么时候会进入到set方法: person.fullName='西门_华哥'
const name = val.split('_')
this.firstName = name[0]
this.lastName = name[1]
}
})
4) Object.keys()方法:将对象的key取出来放在数组中并返回
const keys = Object.keys(person)
5) 对象.hasOwnProperty()方法,判断当前对象中是否包含这个私有属性
console.log(person.hasOwnProperty('fullName')) // true
console.log(person.hasOwnProperty('toString')) // false
6) 文档碎片对象模型----DoucmentFragment---(高效的批量处理多个节点)
// 要求.通过文档碎片对象,把html标签容器中的内容更新
// 1. 创建文档碎片对象
var fragment = document.createDocumentFragment()
// 2. 获取容器对象
var divObj = document.getElementById('demo')
// 3. 把容器对象中的所有的节点全部的存放在文档碎片对象中
var child
while (child = divObj.firstChild) { // 相当于剪切操作
fragment.appendChild(child)
}
// 4. 遍历文档碎片对象中的节点,进行内容更新
fragment.childNodes.forEach(node=>{
node.innerHTML='我才是最帅的'
})
// 5. 把文档碎片对象放在容器中即可
divObj.appendChild(fragment)
2.vue源码文件
1.mvvm.js文件
1)MVVM:创建vue实例的构造函数
1.调用方法实现数据代理:调用Object.keys()/原型上的_proxyData()方法将data对象的属性交给vue实例来代理
2.调用方法实现数据劫持:模板解析前调用observer.js文件中的observe()方法劫持data对象的属性
3.调用方法实现模板解析:调用原型上的$compile()方法解析模板
2)MVVM.prototype原型
1._proxyData():通过Object.defineProperty()方法把data对象中的每个属性添加到Vue的实例对象上,,并重写了Object.defineProperty()方法的set和get方法
2.observer.js文件
1)observe函数:判断传递过来的配置对象的data属性有没有值,或者是不是对象,创建Observer实例对象
2)Observer构造函数
1.把data对象的属性添加到Observer实例对象上
2.调用原型的wall()方法,开始劫持数据,创建dep对象,并且当Dep.target中如果有值,就创建dep对象和Watcher实例对象的依赖关系
3)Observer.prototype原型
1.walk()方法:遍历data对象的属性的key
2.convert()方法:内部调用劫持数据的方法
3.defineReactive()方法
1)调用Dep构造函数创建dep实例对象,每个dep实例对象都有唯一的id标识和一个专门用来保存watcher实例对象的空数组subs
2)实现劫持数据,并重写了Object.defineProperty()方法的set和get方法
4)Dep构造函数:
1.创建dep实例对象,每个dep实例对象都有唯一的id标识,和一个专门用来保存watcher实例对象的空数组subs
2.有一个target属性专门用来储存Watcher的实例对象,来建立dep对象和Watcher实例对象的依赖关系
5)Dep.prototype原型
1.depend():建立dep对象和Watcher实例对象的依赖关系
2.addSub():建立dep对象和Watcher实例对象的依赖关系
3.notify(): data对象属性发生变化通知watcher对象更新数据
3.watcher.js文件
1)Watcher构造函数:将表达式的值添加到watcher实例对象上
2)Watcher.prototype原型
1.parseGetter():返回获取表达式的值的函数
2.get():调用获取表达式的值的函数来获取表达式的值
3.addDep():建立dep对象和Watcher实例对象的依赖关系(把dep的id和dep对象以键值对的方式添加到watcher对象的depIds对象中)
4.update(): data对象属性发生变化,watcher对象更新数据操作
4.compile.js文件
1)Compile编译对象构造函数:
1.创建文档碎片对象,并把容器对象中所有的节点全都存放在文档碎片对象
2.模版解析
3.把模版解析后的文档碎片对象放在容器对象中
2)Compile.prototype原型
1.node2Fragment():创建文档碎片对象,并把容器对象中所有的节点全都存放在文档碎片对象,返回文档碎片对象
2.init():内部调用真正解析模板的方法
3.compileElement():真正解析模板的方法
4.isElementNode():判断当前的节点是不是标签
5.compile():判断标签属性是否是指令,指令是事件指令还是普通指令
6.isDirective():判断是否是指令
7.isEventDirective():判断当前的指令是不是事件指令
8.isTextNode():判断当前的节点是不是文本
9.compileText():调用compileUtil方法来解析文本
3)compileUtil工具对象
1.text属性:执行v-text指令的准备工作,内部会调用v-bind指令
2.html属性:执行v-html指令的准备工作,内部会调用v-bind指令
3.class属性:执行v-class指令的准备工作,内部会调用v-bind指令
4.model属性:执行v-model指令的准备工作,内部会调用v-bind指令
5.bind属性:获取表达式的值,创建Watcher实例对象
6.eventHandler属性:给标签节点添加事件
7._getVMVal:获取表达式的值,展示在页面上
4)updater更新对象
1.textUpdater属性:执行v-text指令
2.htmlUpdater属性:执行v-html指令
3.classUpdater属性:执行v-class指令
4.modelUpdater属性:执行v-model指令
3.剖析vue功能
1)数据代理: 某个对象的属性,可以通过其他对象来访问,在Vue中是有数据代理,Vue的实例对象代理了data对象的属性
创建Vue的实例对象的时候,把data对象中所有数据通过Object.keys()进行遍历,内部调用Object.defineProperty()把data对象中所有的数据一个一个添加到vm实例对象上,
vm对象可以直接访问data对象中的数据了,vm代理了data,data是被代理者
2)数据劫持
当Vue中数据代理结束后,就开始数据劫持,通过observe()方法开始进行数据的劫持,判断data是一个对象后,创建劫持的实例对象,内部遍历vm的data对象,然后把vm的data对象中所有的数据一个一个添加到劫持对象的data对象上,当前在正式添加之前,创建dep对象(id,subs数组),
只要vm中的data对象有多少个属性就会创建多少个dep对象(将来和watcher建立关系)
3)模板解析:把页面中html模版里面使用到的表达式(插值语法/事件指令/一般指令),解析为真正的数据的操作,并渲染界面
1)在创建Vue实例对象时候,数据代理和数据劫持后,开始模版解析,会在MVVM的对象中实例化Compile对象
2)Compile内部会把当前的Vue实例对象控制的容器对象中所有的节点全都存放在文档碎片对象中(文档碎片对象可以高效的批量操作DOM节点,在内存中进行节点的操作,这是所谓的虚拟DOM)
3)取出文档碎片对象所有的子节点进行遍历,如果是文本节点,并且符合插值语法的正则,就要调用CompileUtil对象中的相关方法进而调用bind方法,
然后在调用updater对象中相关的方法把当前节点用到的表达式进行数据的替换,最后渲染页面即可
4)取出文档碎片对象所有的子节点进行遍历,如果当前的节点是标签节点,取出当前标签节点的所有属性,遍历所有属性
然后判断每个属性是不是Vue中的指令(以v-开头),然后判断当前的指令是事件指令(v-后面是:on)还是一般指令
5)如果是事件指令,就把当前这个指令进行字符串切割,获取事件类型,还要拿着事件回调函数名去vue实例对象的methods属性中找对应的回调函数,
然后通过addEventListener方法为当前的标签节点绑定对应的事件,并将事件回调函数的this指向vue实例对象,最后在通过removeAttribute()移除当前标签节点的所有属性,最后渲染页面
6)如果是普通指令,调用CompileUtil中的相关方法,进而调用bind方法,然后在调用updater对象中相关的方法把当前节点用到的表达式进行数据的替换,最后渲染页面即可
1.updater对象中如何执行v-text和v-html指令?
通过文本节点的textContent和innerHTML属性替换表达式值
2.updater对象中如何执行v-class指令?
获取当前标签节点的类样式的名字,如果有类名就添加一个空格在拼接上表达式的值,在把最终值添加给当前标签节点的类样式
7)模板解析中,当在内存中成功替换表达式的值之后,bind方法内部会创建watcher对象,会根据表达式的个数来创建对应个数的watcher对象
进入到watcher内部后,会调用get方法,进而完成开始建立dep对象和watcher对象的关系,进来会监视data对象属性的变化
dep和watcher的关系类型:
1对1的关系:1个dep对应一个watcher,data中只有一个属性,模版中只用了一个表达式
1对多的关系:1个dep对应多个watcher,data中只有一个属性,模版中用了多个表达式
多对1的关系:多个dep对应1个watcher,data中有多个属性,模版中用了一个表达式(data属性是对象,表达式:对象.属性)
多对多的关系:多个dep对应多个watcher,data中有多个属性,模版中用了多个表达式(data属性是对象,表达式:对象.属性)
4)双向数据绑定
创建Vue的实例的时候,除了数据代理和数据劫持以外,会进入到compile模版解析中,在内存中创建文档碎片对象,把html容器中所有的子级节点全部的存放在文档碎片对象中,
遍历所有的节点判断当前的节点是不是标签,然后获取当前节点标签中所有的属性,判断当前的属性是不是指令,然后再判断当前的指令是不是普通指令v-model,是的话就把把表达式的值赋值给节点value属性,再然后为当前的节点标签绑定input事件
如果标签中的数据发生变化,此时触发input事件,判断表达式之前的数据和现在输入的数据是否不同,之后会进入到MVVM的set方法内部再进入到observer.js的set方法内部,
根据当前的这个属性的dep对象通知当前dep对象中subs数组中的watcher进行数据的更新操作
(vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的)
27.vue-property-decorator(简化ts在vue中的写法)
vue-property-decorator是在Vue中使用TypeScript时,非常好用的一个库,使用装饰器来简化书写。(装饰器的作用就是接收vue组件,返回处理过后的vue组件)
该库完全依赖于vue-class-component,因此在使用该库之前,请先阅读vue-class-component库。
1)@component装饰器(来源于vue-class-component)
@Component({
name:'',
components:{},
filters:{},
directives:{}
})
@Component装饰器可以接收一个对象作为参数,可以在对象中声明name , components ,mixins,filters,directives等装饰器选项,
也可以声明computed,watch等,但并不推荐这么做,因为在访问this时,编译器会给出错误提示
// 为了使用TypeScript,需要在script标签上添加 lang = ts
// 等价于
2)@Prop, Data, methods, computed, watch
1.使用props
//前面类型大写,后面可以小写
@Prop(options: (PropOptions | Constructor[] | Constructor) = {})
@Prop(String)
@Prop([String,Number])
@Prop({type: String, default: 'Developer',required: false})
@Prop装饰器接收一个参数,这个参数可以有三种写法(字符串、数组、对象),可以添加 type, default, required为props指定验证要求,同样也可以使用 readonly 禁止操作
// 等价于
export default {
props: {
propA,
propB:String,
propC:[String,Number],
propD: {
type: string,
default: 'Developer',
required: false
}
}
}
2.使用data(data数据可以声明为类属性)
export default class HelloWorld extends Vue {
private msg: string = "welcome to my app"
private list: Array
}
}
4.使用watch属性
@Watch(path: string, options: WatchOptions = {})
@Watch 装饰器接收两个参数
path:被侦听的属性名
options:可以包含两个属性
immediate:侦听开始之后是否立即调用该回调函数
deep:是否开启深度监视
侦听开始,发生在beforeCreate勾子之后,created勾子之前
@Watch('child')
onChildChanged (val: string, oldVal: string) {
if (val !== oldVal) {
window.console.log(val)
}
}
//等价于
watch: {
'child': {
handler: 'onChildChanged',
immediate: false,
deep: false
}
},
method: {
onChildChanged(val, oldVal) {
if (val !== oldVal) {
window.console.log(val)
}
}
}
5.使用method属性(methods可以直接声明为类成员方法)
export default class HelloWorld extends Vue {
public clickMe(): void {
console.log('clicked')
console.log(this.addNum(4, 2))
}
public addNum(num1: number, num2: number): number {
return num1 + num2
}
}
//等价于
export default {
methods: {
clickMe() {
console.log('clicked')
console.log(this.addNum(4, 2))
}
addNum(num1, num2) {
return num1 + num2
}
}
}
6.Lifecycle hooks(生命周期函数)
所有Vue生命周期挂钩也可以直接声明为类成员方法
export default class HelloWorld extends Vue {
mounted() {}
beforeUpdate() {}
}
//等价于
export default {
mounted() {}
beforeUpdate() {}
}
3)@Emit 装饰器
子组件触发父组件的自定义事件并传递数据,在TypeScript中使用@Emit 装饰器
@Emit(event?: string)
@Emit 装饰器接收一个可选参数,该参数是$Emit的第一个参数,充当事件名。如果没有提供这个参数,$Emit会将回调函数名的camelCase转为kebab-case,并将其作为事件名
@Emit会将回调函数的返回值作为第二个参数,如果返回值是一个Promise对象,$emit会在Promise对象被标记为resolved之后触发,promise的结果值作为第二个参数
@Emit的回调函数的参数,会放在其返回值之后,一起被$emit当做参数使用
1.@Emit()没有指定参数,相当于触发'add-to-count',addToCount函数参数为传递给'add-to-count'的数据
import { Vue, Component, Emit } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Emit()
addToCount(n: number) {
this.count += n
}
}
//等价于
export default {
methods: {
addToCount(n) {
this.count += n
this.$emit('add-to-count', n)
}
}
}
2.@Emit()没有指定参数,相当于触发'return-value',returnValue函数返回值为传递给'return-value'的数据
@Emit()
returnValue() {
return 10
}
returnValue() {
this.$emit('return-value', 10)
}
3.@Emit()没有指定参数,相当于触发'on-input-change',onInputChange函数返回值和参数为传递给'return-value'的数据
@Emit()
onInputChange(e) {
return e.target.value
}
onInputChange(e) {
this.$emit('on-input-change', e.target.value, e)
}
4.@Emit()没有参数并且返回一个Promise对象,Promise对象的结果值为传递给'promise'的数据
@Emit()
promise() {
return new Promise(resolve => {
resolve(20)
})
}
//等价于
promise() {
const promise = new Promise(resolve => {
resolve(20)
})
promise.then(value => {
this.$emit('promise', value)
})
}
5.@Emit()有参数,那么触发'reset'
@Emit('reset')
resetCount() {
this.count = 0
}
//等价于
resetCount() {
this.count = 0
this.$emit('reset')
}
4)vuex
1.首先安装
npm install vuex-module-decorators -D
npm install vuex-class -D
2.如果想通过名字空间的形式来使用module, 需在@Module装饰器中添加额外的参数.
例如, 以下示例代码中添加一个namespaced为user的module
import { VuexModule, Module, Mutation, Action } from 'vuex-module-decorators'
@Module({ namespaced: true, name: 'user' })
class User extends VuexModule {
public name: string = ''
@Mutation
public setName(newName: string): void {
this.name = newName
}
@Action
public updateName(newName: string): void {
this.context.commit('setName', newName)
}
}
export default User
注意:@Module装饰器的属性字段name值, 必须与new store({modules: {}})中注册module name名称保持一致
//等价于
export default {
namespaced: true,
state: {
name: ''
},
mutations: {
setName(state, newName) {
state.name = newName
}
},
actions: {
updateName(context, newName) {
context.commit('setName', newName)
}
}
}
3.在组件里面使用vuex
要使用Vuex,可以使用vuex-class库。该库提供了装饰器,可以在Vue组件中绑定State,Getter,Mutation和Action。
由于已经使用了命名空间的Vuex模块,因此我们首先从vuex-class导入命名空间,然后传递模块名称以访问该模块
import { Component, Vue } from 'vue-property-decorator'
import { namespace } from 'vuex-class'
const user = namespace('user')
@Component
export default class User extends Vue {
@user.State
public name!: string
@user.Getter
public nameUpperCase!: string
@user.Action
public updateName!: (newName: string) => void
}
//等价于
import { mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState('user', ['name']),
...mapGetters('user', ['nameUpperCase'])
}
methods: {
...mapActions('user', ['updateName'])
}
}
5.其它
1)@Provide 提供 / @Inject 注入
与 React的context组件间通信相似,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代传递数据,
数据通过Provide传递下去,然后子组件通过Inject来获取
import { Component, Inject, Provide, Vue } from 'vue-property-decorator'
const symbol = Symbol('baz')
@Component
export class MyComponent extends Vue {
@Inject() foo!: string
@Inject('bar') bar!: string
@Inject({ from: 'optional', default: 'default' }) optional!: string
@Inject(symbol) readonly baz!: string
@Provide() foo = 'foo'
@Provide('bar') baz = 'bar'
}
//等价于
const symbol = Symbol('baz')
export const MyComponent = Vue.extend({
inject: {
foo: 'foo',
bar: 'bar',
optional: { from: 'optional', default: 'default' },
baz: symbol,
},
data() {
return {
foo: 'foo',
baz: 'bar',
}
},
provide() {
return {
foo: this.foo,
bar: this.baz,
}
},
})
2)@Ref(refKey?: string)
@Ref 装饰器接收一个可选参数,用来指向元素或子组件的引用信息。如果没有提供这个参数,会使用装饰器后面的属性名充当参数
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Form } from 'element-ui'
@Componentexport default class MyComponent extends Vue {
@Ref() readonly loginForm!: Form
@Ref('changePasswordForm') readonly passwordForm!: Form
public handleLogin() {
this.loginForm.validate(valide => {
if (valide) {
// login...
} else {
// error tips
}
})
}
}
//等价于
export default {
computed: {
loginForm: {
cache: false,
get() {
return this.$refs.loginForm
}
},
passwordForm: {
cache: false,
get() {
return this.$refs.changePasswordForm
}
}
}
}
3)@Model装饰器允许我们在一个组件上自定义v-model
@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})
event:事件名。
options:与@Prop的第一个参数一致,可以是字符串、数组、对象类型
下面例子中指定的是change事件,所以我们还需要在template中加上相应的事件:
type="text"
:value="value"
@change="$emit('change', $event.target.value)"
/>
import { Vue, Component, Model } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Model('change', { type: Boolean }) readonly checked!: boolean
}
//等价于
export default {
model: {
prop: 'checked',
event: 'change',
},
props: {
checked: {
type: Boolean,
},
},
}
4)Mixins
假设当前已经有一个mixins/ProjectMixin文件 如何在其他组件里面使用方式如下
import { Component, Vue, Mixins } from 'vue-property-decorator'
import ProjectMixin from '@/mixins/ProjectMixin'
@Component
export default class Project extends Mixins(ProjectMixin) {
get projectDetail(): string {
return this.projName + ' ' + 'HS'
}
}
//等价于
import ProjectMixin from '@/mixins/ProjectMixin'
export default {
mixins: [ ProjectMixin ],
computed: {
projectDetail() {
return this.projName + ' ' + 'HS'
}
}
}
5)@PropSync装饰器与@prop用法类似,二者的区别在于:
@PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {})装饰器接收两个参数:
propName:表示父组件传递过来的属性名
options:与@Prop的第一个参数一致,可以是字符串、数组、对象类型
@PropSync 会生成一个新的计算属性。
import { Vue, Component, PropSync } from 'vue-property-decorator'
@Component
export default class MyComponent extends Vue {
@PropSync('propA', { type: String, default: 'abc' }) public syncedPropA!: string
}
//等价于
export default {
props: {
propA: {
type: String,
default: 'abc'
}
},
computed: {
syncedPropA: {
get() {
return this.propA
},
set(value) {
this.$emit('update:propA', value)
}
}
}
}
注意:@PropSync需要配合父组件的.sync修饰符使用
6.总结
1)methods可以直接声明为类成员方法(使用public关键字)
2)computed属性可以声明为类属性访问器(get/set)
3)data数据可以声明为类属性(使用private关键字)
4)render函数和所有Vue生命周期挂钩也可以直接声明为类成员方法,但不能在实例本身上调用它们。
28.服务器端渲染 (SSR)
将一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
1)为什么使用服务器端渲染
1.更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
2.更快的内容到达时间,无需等待所有的 JavaScript都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。
3.服务端渲染, 解决首屏加载速度, 和 seo问题(总结)
2)服务端渲染实例
// 第 1 步:创建一个 Vue 实例
const Vue = require('vue')
const app = new Vue({
template: `
29性能优化
1)开发过程
1.优先使用vIf
v-if是将元素删除来达到隐藏的效果,v-show是将元素display:none来达到隐藏的效果,如果需要频繁切换才使用v-show
2.vFor key避免使用index作为标识
当index作为标识的时候,插入一条数据的时候,列表中它后面的key都发生了变化,那么当前的 vFor 都会对key变化的 Element 重新渲染,
但是其实它们除了插入的 Element 数据都没有发生改变,这就导致了没有必要的开销。
3.释放组件资源
每创建出一个事物都需要消耗资源,资源不是凭空产生的,是分配出来的。所以说,当组件销毁后,尽量把我们开辟出来的资源块给销毁掉,
比如 setInterval , addEventListener等
4.长列表
长列表渲染的时候,建议将DOM移除掉,类似于图片懒加载的模式,只有出现在视图上的DOM才是重要的DOM。网络上有一些很好的解决方案,如 vue-virtual-scroller 库等等
5.图片合理的优化方式
图片应该都不陌生吧,在网页中,往往存在大量的图片资源,这些资源或大或小。当我们页面中DOM中存在大量的图片时,
难免不会碰到一些加载缓慢的问题,导致图片出现加载失败的问题。网络上大部分都在使用 懒加载 的使用方式,
只有当 存在图片的DOM 出现在页面上才会进行图片的加载,无形中起到了分流的作用,下面就说一套实践的方案吧
1)小图标使用 SVG 或者字体图标
2)通过 base64 和 webp 的方式加载小型图片
3)能通过cdn加速的大图尽量用cdn
4)大部分框架都带有懒加载的图片
6.使用路由懒加载
component: () => import('@/components/HelloWorld')
7.第三方模块(UI/工具插件)按需导入
8.SPA 页面采用keep-alive缓存组件
9.防抖、节流
10.首屏优化
众所周知,第一次打开Vue的时候,如果你的项目够大,那么首次加载资源时,会非常的久。由于资源没有加载完毕,
界面的DOM也不会渲染,会造成白屏的问题。用户此时并不知道是加载的问题,所以会带来一个不好的体验。
因此通常会在public下写一个加载动画,告诉用户,网页在加载中这个提示。当页面加载成功后,页面渲染出来的这一个体验比白屏等开机要好太多了。
11.静态模板尽量使用函数组件替代普通组件
12.render函数只要数据一改变就会触发,所以数据的处理尽量放在render函数外面,render函数里面的方法提前定义在外面
因为方法是执行render函数临时创建的。消耗性能
13.结构显示隐藏加动画
2)webpack
1.优化打包构建速度
1) HMR 热模块替换
2) cache 缓存(针对js)
3) oneOf
4) 多进程打包
2.优化打包代码体积和性能
1) 兼容性处理
2) tree shaking 树摇
3) code split 代码分割 / lazy loading 懒加载
4) preload 和 prefetch 预加载
5) cache 缓存(浏览器缓存)
6) PWA 渐进式网络应用程序(离线加载技术)
3)其它
1.服务端渲染SSR
1)vuex中
actions: {
//app相当于实例对象
nuxtServerInit({commit}, {req, app}) {
}
}
2)组件内
//ctx相当于实例对象
asyncData(ctx){}
2.还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
//熊健静态服务器/utils/compress.js文件
const { createGzip, createDeflate } = require("zlib");
// 设置响应头,告诉客户端,内容经过了压缩
res.setHeader("Content-Encoding", "gzip");
// rs可读流会将数据读取传给gzip压缩,返回值还是一个rs(可读流中的数据已经压缩了~)
rs = rs.pipe(createGzip());
3.依赖库CDN加速
看到有小伙伴使用CDN的方式引入一些依赖包,觉得非常的 Nice ,然后我也开始使用了。我将 Vue Axios Echarts 等等都分离了出来,
在正式环境下,通过CDN,确实有了一些明显的提升,所以说大家可以进行尝试。
// 在html引入script标签后。在vue的配置中,进行声明
configureWebpack: {
externals: {
'echarts': 'echarts' // 配置使用CDN
}
}
30.项目结构分析
gshop(脚手架2)
|-- build : webpack 相关的配置文件夹(基本不需要修改)
|-- config: webpack 相关的配置文件夹(基本不需要修改)
|-- index.js: 指定后台服务的端口号和静态资源文件夹
|-- node_modules
|-- src
|-- components------------非路由组件文件夹
|-- FooterGuide---------------底部组件文件夹
|-- FooterGuide.vue--------底部组件 vue |--
pages-----------------路由组件文件夹
|-- Msite---------------首页组件文件夹
|-- Msite.vue--------首页组件 vue
|-- Search----------------搜索组件文件夹
|-- Search.vue---------搜索组件 vue
|-- Order--------------订单组件文件夹
|-- Order.vue-------订单组件 vue
|-- Profile--------------个人组件文件夹
|-- Profile.vue-------个人组件 vue |--
App.vue---------------应用根组件 vue
|-- main.js---------------应用入口 js
|-- static: 静态资源文件夹
|-- .babelrc: babel 的配置文件
|-- .editorconfig: 通过编辑器的编码/格式进行一定的配置
|-- .eslintignore: eslint 检查忽略的配置
|-- .eslintrc.js: eslint 检查的配置
|-- .gitignore: git 版本管制忽略的配置
|-- index.html: 主页面文件
|-- package.json: 应用包配置文件
|-- README.md: 应用描述说明的 readme 文件
shop-client(脚手架3)
|-- node_modules
|-- public: 任何放置在 public 文件夹的静态资源都会被简单的复制,而不经过 webpack。你需要通过绝对路径来引用它们。
|-- index.html: 主页面文件
|-- src
|-- main.js: 应用入口js
|-- babel.config.js: babel的配置文件
|-- vue.config.js: vue的配置文件
|-- .gitignore: git版本管制忽略的配置
|-- package.json: 应用包配置文件
|-- README.md: 应用描述说明的readme文件
31.vue.config.js文件配置
const isProduction = process.env.NODE_ENV === 'production';
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const cdn = {
css: ["https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.8.2/theme-chalk/index.css"],
js: [
'https://cdn.bootcss.com/vue/2.5.17/vue.runtime.min.js',
'https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js',
'https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js',
'https://cdn.bootcss.com/axios/0.18.0/axios.min.js',
'https://cdn.bootcss.com/element-ui/2.8.2/index.js',
'https://cdn.bootcss.com/echarts/3.8.5/echarts.min.js'
]
}
module.exports = {
// 基本路径
// '/'绝对路径,'./'或''相对路径
publicPath: process.env.NODE_ENV === 'production' ? '/' : './',
// 运行 vue-cli-service build 时生成的生产环境构建文件的目录
// 默认构建前清除文件夹(构建时传入 --no-clean 可关闭该行为
outputDir: 'dist',
// 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录
assetsDir: 'static',
// 指定生成的 index.html 的输出路径 (相对于 outputDir),也可以是一个绝对路径
indexPath: 'index.html',
// 生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存
filenameHashing: true,
// 当在 multi-page 模式下构建时,webpack 配置会包含不一样的插件
// (这时会存在多个 html-webpack-plugin 和 preload-webpack-plugin 的实例)。
// 如果你试图修改这些插件的选项,请确认运行 vue inspect
pages: {
index: {
// page 的入口
entry: 'src/pages/index/index.js',
// 模板来源
template: 'src/pages/index/index.html',
// 在 dist 的输出为 index.html
filename: 'index.html',
// 当使用 title 选项时,
// template 中的 title 标签需要是