你有对 Vue 项目进行哪些优化?
(1)代码层面的优化
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分使用场景
- v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
- 长列表性能优化
- 事件的销毁
- 图片资源懒加载
- 路由懒加载
- 第三方插件的按需引入
- 优化无限列表性能
- 服务端渲染 SSR or 预渲染
(2)Webpack 层面的优化
- Webpack 对图片进行压缩
- 减少 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的 CSS
- 优化 SourceMap
- 构建结果输出分析
- Vue 项目的编译优化
(3)基础的 Web 技术的优化
- 开启 gzip 压缩
- 浏览器缓存
- CDN 的使用
- 使用 Chrome Performance 查找性能瓶颈
Vuex中action和mutation的区别
mutation中的操作是一系列的同步函数,用于修改state中的变量的的状态。当使用vuex时需要通过commit来提交需要操作的内容。mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
state.count++ // 变更状态
}
}
})
当触发一个类型为 increment 的 mutation 时,需要调用此函数:
store.commit('increment')
而Action类似于mutation,不同点在于:
- Action 可以包含任意异步操作。
- Action 提交的是 mutation,而不是直接变更状态。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
所以,两者的不同点如下:
- Mutation专注于修改State,理论上是修改State的唯一途径;Action业务代码、异步请求。
- Mutation:必须同步执行;Action:可以异步,但不能直接操作State。
- 在视图更新时,先触发actions,actions再触发mutation
- mutation的参数是state,它包含store中的数据;store的参数是context,它是 state 的父级,包含 state、getters
函数式组件优势和原理
函数组件的特点
- 函数式组件需要在声明组件是指定
functional:true
- 不需要实例化,所以没有
this
,this
通过render
函数的第二个参数context
来代替 - 没有生命周期钩子函数,不能使用计算属性,
watch
- 不能通过
$emit
对外暴露事件,调用事件只能通过context.listeners.click
的方式调用外部传入的事件 - 因为函数式组件是没有实例化的,所以在外部通过
ref
去引用组件时,实际引用的是HTMLElement
- 函数式组件的
props
可以不用显示声明,所以没有在props
里面声明的属性都会被自动隐式解析为prop
,而普通组件所有未声明的属性都解析到$attrs
里面,并自动挂载到组件根元素上面(可以通过inheritAttrs
属性禁止)
优点
- 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件
- 函数式组件结构比较简单,代码结构更清晰
使用场景:
- 一个简单的展示组件,作为容器组件使用 比如
router-view
就是一个函数式组件 - “高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件
例子
Vue.component('functional',{
// 构造函数产生虚拟节点的
functional:true, // 函数式组件 // data={attrs:{}}
render(h){
return h('div','test')
}
})
const vm = new Vue({
el: '#app'
})
源码相关
// functional component
if (isTrue(Ctor.options.functional)) {
// 带有functional的属性的就是函数式组件
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on // 处理事件
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn // 处理原生事件
// install component management hooks onto the placeholder node
installComponentHooks(data) // 安装组件相关钩子 (函数式组件没有调用此方法,从而性能高于普通组件)
v-model实现原理
我们在
vue
项目中主要使用v-model
指令在表单input
、textarea
、select
等元素上创建双向数据绑定,我们知道v-model
本质上不过是语法糖(可以看成是value + input
方法的语法糖),v-model
在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text
和textarea
元素使用value
属性和input
事件checkbox
和radio
使用checked
属性和change
事件select
字段将value
作为prop
并将change
作为事件
所以我们可以v-model进行如下改写:
<input v-model="sth" />
<!-- 等同于 -->
<input :value="sth" @input="sth = $event.target.value" />
当在
input
元素中使用v-model
实现双数据绑定,其实就是在输入的时候触发元素的input
事件,通过这个语法糖,实现了数据的双向绑定
- 这个语法糖必须是固定的,也就是说属性必须为
value
,方法名必须为:input
。 - 知道了
v-model
的原理,我们可以在自定义组件上实现v-model
//Parent
<template>
{
{
num}}
<Child v-model="num">
</template>
export default {
data(){
return {
num: 0
}
}
}
//Child
<template>
<div @click="add">Add</div>
</template>
export default {
props: ['value'], // 属性必须为value
methods:{
add(){
// 方法名为input
this.$emit('input', this.value + 1)
}
}
}
原理
会将组件的 v-model
默认转化成value+input
const VueTemplateCompiler = require('vue-template-compiler');
const ele = VueTemplateCompiler.compile('<el-checkbox v-model="check"></el- checkbox>');
// 观察输出的渲染函数:
// with(this) {
// return _c('el-checkbox', {
// model: {
// value: (check),
// callback: function ($$v) { check = $$v },
// expression: "check"
// }
// })
// }
// 源码位置 core/vdom/create-component.js line:155
function transformModel (options, data: any) {
const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
;(data.attrs || (data.attrs = {
}))[prop] = data.model.value
const on = data.on || (data.on = {
})
const existing = on[event]
const callback = data.model.callback
if (isDef(existing)) {
if (Array.isArray(existing) ? existing.indexOf(callback) === -1 : existing !== callback ) {
on[event] = [callback].concat(existing)
}
} else {
on[event] = callback
}
}
原生的 v-model
,会根据标签的不同生成不同的事件和属性
const VueTemplateCompiler = require('vue-template-compiler');
const ele = VueTemplateCompiler.compile('<input v-model="value"/>');
// with(this) {
// return _c('input', {
// directives: [{ name: "model", rawName: "v-model", value: (value), expression: "value" }],
// domProps: { "value": (value) },
// on: {"input": function ($event) {
// if ($event.target.composing) return;
// value = $event.target.value
// }
// }
// })
// }
编译时:不同的标签解析出的内容不一样
platforms/web/compiler/directives/model.js
if (el.component) {
genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {
genSelect(el, value, modifiers)
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el, value, modifiers)
} else if