网站建设与实践,吧网站做软件的软件下载,网站开发实例,凡科做的网站手机版action 与 mutation 的区别 mutation 是同步更新#xff0c; $watch 严格模式下会报错 action 是异步操作#xff0c;可以获取数据后调用 mutation 提交最终数据
MVVM的优缺点?
优点:
分离视图#xff08;View#xff09;和模型#xff08;Model#xff09;#xff…action 与 mutation 的区别 mutation 是同步更新 $watch 严格模式下会报错 action 是异步操作可以获取数据后调用 mutation 提交最终数据
MVVM的优缺点?
优点:
分离视图View和模型Model降低代码耦合提⾼视图或者逻辑的重⽤性: ⽐如视图View可以独⽴于Model变化和修改⼀个ViewModel可以绑定不同的View上当View变化的时候Model不可以不变当Model变化的时候View也可以不变。你可以把⼀些视图逻辑放在⼀个ViewModel⾥⾯让很多view重⽤这段视图逻辑提⾼可测试性: ViewModel的存在可以帮助开发者更好地编写测试代码⾃动更新dom: 利⽤双向绑定,数据更新后视图⾃动更新,让开发者从繁琐的⼿动dom中解放
缺点:
Bug很难被调试: 因为使⽤双向绑定的模式当你看到界⾯异常了有可能是你View的代码有Bug也可能是Model的代码有问题。数据绑定使得⼀个位置的Bug被快速传递到别的位置要定位原始出问题的地⽅就变得不那么容易了。另外数据绑定的声明是指令式地写在View的模版当中的这些内容是没办法去打断点debug的⼀个⼤的模块中model也会很⼤虽然使⽤⽅便了也很容易保证了数据的⼀致性当时⻓期持有不释放内存就造成了花费更多的内存对于⼤型的图形应⽤程序视图状态较多ViewModel的构建和维护的成本都会⽐较⾼。
描述下Vue自定义指令
在 Vue2.0 中代码复用和抽象的主要形式是组件。然而有的情况下你仍然需要对普通 DOM 元素进行底层操作这时候就会用到自定义指令。 一般需要对DOM元素进行底层操作时使用尽量只用来操作 DOM展示不修改内部的值。当使用自定义指令直接修改 value 值时绑定v-model的值也不会同步更新如必须修改可以在自定义指令中使用keydown事件在vue组件中使用 change事件回调中修改vue数据;
1自定义指令基本内容 全局定义Vue.directive(focus,{}) 局部定义directives:{focus:{}} 钩子函数指令定义对象提供钩子函数 o bind只调用一次指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 o inSerted被绑定元素插入父节点时调用仅保证父节点存在但不一定已被插入文档中。 o update所在组件的VNode更新时调用但是可能发生在其子VNode更新之前调用。指令的值可能发生了改变也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。 o ComponentUpdate指令所在组件的 VNode及其子VNode全部更新后调用。 o unbind只调用一次指令与元素解绑时调用。 钩子函数参数 o el绑定元素 o bing 指令核心对象描述指令全部信息属性 o name o value o oldValue o expression o arg o modifers o vnode 虚拟节点 o oldVnode上一个虚拟节点更新钩子函数中才有用
2使用场景 普通DOM元素进行底层操作的时候可以使用自定义指令 自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展不仅可用于定义任何的DOM操作并且是可复用的。
3使用案例
初级应用
鼠标聚焦下拉菜单相对时间转换滚动动画
高级应用
自定义指令实现图片懒加载自定义指令集成第三方插件
Vue-Router 的懒加载如何实现
非懒加载
import List from /components/list.vue
const router new VueRouter({routes: [{ path: /list, component: List }]
})
1方案一(常用)使用箭头函数import动态加载
const List () import(/components/list.vue)
const router new VueRouter({routes: [{ path: /list, component: List }]
})
2方案二使用箭头函数require动态加载
const router new Router({routes: [{path: /list,component: resolve require([/components/list], resolve)}]
})
3方案三使用webpack的require.ensure技术也可以实现按需加载。 这种情况下多个路由指定相同的chunkName会合并打包成一个js文件。
// r就是resolve
const List r require.ensure([], () r(require(/components/list)), list);
// 路由也是正常的写法 这种是官方推荐的写的 按模块划分懒加载
const router new Router({routes: [{path: /list,component: List,name: list}]
}))
Vue 3.0 中的 Vue Composition API
在 Vue2 中代码是 Options API 风格的也就是通过填充 (option) data、methods、computed 等属性来完成一个 Vue 组件。这种风格使得 Vue 相对于 React极为容易上手同时也造成了几个问题
由于 Options API 不够灵活的开发方式使得Vue开发缺乏优雅的方法来在组件间共用代码。Vue 组件过于依赖this上下文Vue 背后的一些小技巧使得 Vue 组件的开发看起来与 JavaScript 的开发原则相悖比如在methods 中的this竟然指向组件实例来不指向methods所在的对象。这也使得 TypeScript 在Vue2 中很不好用。
于是在 Vue3 中舍弃了 Options API转而投向 Composition API。Composition API本质上是将 Options API 背后的机制暴露给用户直接使用这样用户就拥有了更多的灵活性也使得 Vue3 更适合于 TypeScript 结合。
如下是一个使用了 Vue Composition API 的 Vue3 组件
templatebutton clickincrementCount: {{ count }} /button
/templatescript
// Composition API 将组件属性暴露为函数因此第一步是导入所需的函数
import { ref, computed, onMounted } from vueexport default { setup() {
// 使用 ref 函数声明了称为 count 的响应属性对应于Vue2中的data函数const count ref(0)
// Vue2中需要在methods option中声明的函数现在直接声明function increment() { count.value } // 对应于Vue2中的mounted声明周期onMounted(() console.log(component mounted!)) return { count, increment } }}
/script
显而易见Vue Composition API 使得 Vue3 的开发风格更接近于原生 JavaScript带给开发者更多地灵活性
SPA、SSR的区别是什么
我们现在编写的Vue、React和Angular应用大多数情况下都会在一个页面中点击链接跳转页面通常是内容切换而非页面跳转由于良好的用户体验逐渐成为主流的开发模式。但同时也会有首屏加载时间长SEO不友好的问题因此有了SSR这也是为什么面试中会问到两者的区别
SPASingle Page Application即单页面应用。一般也称为 客户端渲染Client Side Render 简称 CSR。SSRServer Side Render即 服务端渲染。一般也称为 多页面应用Mulpile Page Application简称 MPASPA应用只会首次请求html文件后续只需要请求JSON数据即可因此用户体验更好节约流量服务端压力也较小。但是首屏加载的时间会变长而且SEO不友好。为了解决以上缺点就有了SSR方案由于HTML内容在服务器一次性生成出来首屏加载快搜索引擎也可以很方便的抓取页面信息。但同时SSR方案也会有性能开发受限等问题在选择上如果我们的应用存在首屏加载优化需求SEO需求时就可以考虑SSR但并不是只有这一种替代方案比如对一些不常变化的静态网站SSR反而浪费资源我们可以考虑预渲染prerender方案。另外nuxt.js/next.js中给我们提供了SSGStatic Site Generate静态网站生成方案也是很好的静态站点解决方案结合一些CI手段可以起到很好的优化效果且能节约服务器资源
内容生成上的区别
SSR SPA 部署上的区别 参考 前端进阶面试题详细解答
delete和Vue.delete删除数组的区别
delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。Vue.delete直接删除了数组 改变了数组的键值。
var a[1,2,3,4]
var b[1,2,3,4]
delete a[0]
console.log(a) //[empty,2,3,4]
this.$delete(b,0)
console.log(b) //[2,3,4]说说你对slot的理解slot使用场景有哪些
一、slot是什么
在HTML中 slot 元素 作为 Web Components 技术套件的一部分是Web组件内的一个占位符
该占位符可以在后期使用自己的标记语言填充
举个栗子
template idelement-details-templateslot nameelement-nameSlot template/slot
/template
element-detailsspan slotelement-name1/span
/element-details
element-detailsspan slotelement-name2/span
/element-detailstemplate不会展示到页面中需要用先获取它的引用然后添加到DOM中
customElements.define(element-details,class extends HTMLElement {constructor() {super();const template document.getElementById(element-details-template).content;const shadowRoot this.attachShadow({mode: open}).appendChild(template.cloneNode(true));}
})在Vue中的概念也是如此
Slot 艺名插槽花名“占坑”我们可以理解为solt在组件模板中占好了位置当使用该组件标签时候组件标签里面的内容就会自动填坑替换组件模板中slot位置作为承载分发内容的出口
二、使用场景
通过插槽可以让用户可以拓展组件去更好地复用组件和对其做定制化处理
如果父组件在使用到一个复用组件的时候获取这个组件在不同的地方有少量的更改如果去重写组件是一件不明智的事情
通过slot插槽向组件内部指定位置传递内容完成这个复用组件在不同场景的应用
比如布局组件、表格列、下拉选、弹框显示内容等
如果让你从零开始写一个vuex说说你的思路
思路分析
这个题目很有难度首先思考vuex解决的问题存储用户全局状态并提供管理状态API。
vuex需求分析如何实现这些需求
回答范例
官方说vuex是一个状态管理模式和库并确保这些状态以可预期的方式变更。可见要实现一个vuex
要实现一个Store存储全局状态要提供修改状态所需APIcommit(type, payload), dispatch(type, payload)
实现Store时可以定义Store类构造函数接收选项options设置属性state对外暴露状态提供commit和dispatch修改属性state。这里需要设置state为响应式对象同时将Store定义为一个Vue插件commit(type, payload)方法中可以获取用户传入mutations并执行它这样可以按用户提供的方法修改状态。 dispatch(type, payload)类似但需要注意它可能是异步的需要返回一个Promise给用户以处理异步结果
实践
Store的实现
class Store {constructor(options) {this.state reactive(options.state)this.options options}commit(type, payload) {this.options.mutations[type].call(this, this.state, payload)}
}vuex简易版
/*** 1 实现插件挂载$store* 2 实现store*/let Vue;class Store {constructor(options) {// state响应式处理// 外部访问 this.$store.state.***// 第一种写法// this.state new Vue({// data: options.state// })// 第二种写法防止外界直接接触内部vue实例防止外部强行变更this._vm new Vue({data: {$$state: options.state}})this._mutations options.mutationsthis._actions options.actionsthis.getters {}options.getters this.handleGetters(options.getters)this.commit this.commit.bind(this)this.dispatch this.dispatch.bind(this)}get state () {return this._vm._data.$$state}set state (val) {return new Error(Please use replaceState to reset state)}handleGetters (getters) {Object.keys(getters).map(key {Object.defineProperty(this.getters, key, {get: () getters[key](this.state)})})}commit (type, payload) {let entry this._mutations[type]if (!entry) {return new Error(${type} is not defined)}entry(this.state, payload)}dispatch (type, payload) {let entry this._actions[type]if (!entry) {return new Error(${type} is not defined)}entry(this, payload)}
}const install (_Vue) {Vue _VueVue.mixin({beforeCreate () {if (this.$options.store) {Vue.prototype.$store this.$options.store}},})
}export default { Store, install }验证方式
import Vue from vue
import Vuex from ./vuex
// this.$store
Vue.use(Vuex)export default new Vuex.Store({state: {counter: 0},mutations: {// state从哪里来的add (state) {state.counter}},getters: {doubleCounter (state) {return state.counter * 2}},actions: {add ({ commit }) {setTimeout(() {commit(add)}, 1000)}},modules: {}
})Vue中v-html会导致哪些问题
可能会导致 xss 攻击v-html 会替换掉标签内部的子元素
let template require(vue-template-compiler);
let r template.compile(div v-htmlspanhello/span/div) // with(this){return _c(div,{domProps: {innerHTML:_s(spanhello/span)}})}
console.log(r.render);// _c 定义在core/instance/render.js
// _s 定义在core/instance/render-helpers/index,js
if (key textContent || key innerHTML) { if (vnode.children) vnode.children.length 0 if (cur oldProps[key]) continue // #6601 work around Chrome version 55 bug where single textNode // replaced by innerHTML/textContent retains its parentNode property if (elm.childNodes.length 1) { elm.removeChild(elm.childNodes[0]) }
}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-modelsth /
!-- 等同于 --
input :valuesth inputsth $event.target.value /当在input元素中使用v-model实现双数据绑定其实就是在输入的时候触发元素的input事件通过这个语法糖实现了数据的双向绑定 这个语法糖必须是固定的也就是说属性必须为value方法名必须为input。知道了v-model的原理我们可以在自定义组件上实现v-model
//Parent
template{{num}}Child v-modelnum
/template
export default {data(){return {num: 0}}
}//Child
templatediv clickaddAdd/div
/template
export default {props: [value], // 属性必须为valuemethods:{add(){// 方法名为inputthis.$emit(input, this.value 1)}}
}原理
会将组件的 v-model 默认转化成valueinput
const VueTemplateCompiler require(vue-template-compiler);
const ele VueTemplateCompiler.compile(el-checkbox v-modelcheck/el- checkbox); // 观察输出的渲染函数
// with(this) {
// return _c(el-checkbox, {
// model: {
// value: (check),
// callback: function ($$v) { check $$v },
// expression: check
// }
// })
// }// 源码位置 core/vdom/create-component.js line:155function 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-modelvalue/);// 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 doesnt 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 (tag input type radio) { genRadioModel(el, value, modifiers)
} else if (tag input || tag textarea) { genDefaultModel(el, value, modifiers)
} else if (!config.isReservedTag(tag)) { genComponentModel(el, value, modifiers) // component v-model doesnt need extra runtime return false
}运行时会对元素处理一些关于输入法的问题 platforms/web/runtime/directives/model.js inserted (el, binding, vnode, oldVnode) { if (vnode.tag select) { // #6903 if (oldVnode.elm !oldVnode.elm._vOptions) { mergeVNodeHook(vnode, postpatch, () { directive.componentUpdated(el, binding, vnode) }) } else { setSelected(el, binding, vnode.context) }el._vOptions [].map.call(el.options, getValue) } else if (vnode.tag textarea || isTextInputType(el.type)) { el._vModifiers binding.modifiers if (!binding.modifiers.lazy) { el.addEventListener(compositionstart, onCompositionStart) el.addEventListener(compositionend, onCompositionEnd) // Safari 10.2 UIWebView doesnt fire compositionend when // switching focus before confirming composition choice // this also fixes the issue where some browsers e.g. iOS Chrome// fires change instead of input on autocomplete. el.addEventListener(change, onCompositionEnd) /* istanbul ignore if */ if (isIE9) { el.vmodel true }}}
}子组件可以直接改变父组件的数据么说明原因
这是一个实践知识点组件化开发过程中有个单项数据流原则不在子组件中修改父组件是个常识问题
思路
讲讲单项数据流原则表明为何不能这么做举几个常见场景的例子说说解决方案结合实践讲讲如果需要修改父组件状态应该如何做
回答范例
所有的 prop 都使得其父子之间形成了一个单向下行绑定父级 prop 的更新会向下流动到子组件中但是反过来则不行。这样会防止从子组件意外变更父级组件的状态从而导致你的应用的数据流向难以理解。另外每次父级组件发生变更时子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了Vue 会在浏览器控制台中发出警告
const props defineProps([foo])
// ❌ 下面行为会被警告, props是只读的!
props.foo bar实际开发过程中有两个场景会想要修改一个属性
这个 prop 用来传递一个初始值这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下最好定义一个本地的 data并将这个 prop 用作其初始值
const props defineProps([initialCounter])
const counter ref(props.initialCounter)这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下最好使用这个 prop 的值来定义一个计算属性
const props defineProps([size])
// prop变化计算属性自动更新
const normalizedSize computed(() props.size.trim().toLowerCase())实践中如果确实想要改变父组件属性应该emit一个事件让父组件去做这个变更。注意虽然我们不能直接修改一个传入的对象或者数组类型的prop但是我们还是能够直接改内嵌的对象或属性
怎么缓存当前的组件缓存后怎么更新
缓存组件使用keep-alive组件这是一个非常常见且有用的优化手段vue3中keep-alive有比较大的更新能说的点比较多
思路
缓存用keep-alive它的作用与用法使用细节例如缓存指定/排除、结合router和transition组件缓存后更新可以利用activated或者beforeRouteEnter原理阐述
回答范例
开发中缓存组件使用keep-alive组件keep-alive是vue内置组件keep-alive包裹动态组件component时会缓存不活动的组件实例而不是销毁它们这样在组件切换过程中将状态保留在内存中防止重复渲染DOM
keep-alivecomponent :isview/component
/keep-alive结合属性include和exclude可以明确指定缓存哪些组件或排除缓存指定组件。vue3中结合vue-router时变化较大之前是keep-alive包裹router-view现在需要反过来用router-view包裹keep-alive
router-view v-slot{ Component }keep-alivecomponent :isComponent/component/keep-alive
/router-view缓存后如果要获取数据解决方案可以有以下两种
beforeRouteEnter在有vue-router的项目每次进入路由的时候都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){next(vm{console.log(vm)// 每次进入路由执行vm.getData() // 获取数据})
},actived在keep-alive缓存的组件被激活的时候都会执行actived钩子
activated(){this.getData() // 获取数据
},keep-alive是一个通用组件它内部定义了一个map缓存创建过的组件实例它返回的渲染函数内部会查找内嵌的component组件对应组件的vnode如果该组件在map中存在就直接返回它。由于component的is属性是个响应式数据因此只要它变化keep-alive的render函数就会重新执行
Vue中修饰符.sync与v-model的区别
sync的作用
.sync修饰符可以实现父子组件之间的双向绑定并且可以实现子组件同步修改父组件的值相比较与v-model来说,sync修饰符就简单很多了一个组件上可以有多个.sync修饰符
!-- 正常父传子 --
Son :anum :bnum2 /!-- 加上sync之后的父传子 --
Son :a.syncnum :b.syncnum2 /!-- 它等价于 --
Son :anum :bnum2 update:avalnumval update:bvalnum2val
/!-- 相当于多了一个事件监听事件名是update:a, --
!-- 回调函数中会把接收到的值赋值给属性绑定的数据项中。 --v-model的工作原理
com1 v-modelnum/com1
!-- 等价于 --
com1 :valuenum input(val)numval/com1相同点 都是语法糖都可以实现父子组件中的数据的双向通信 区别点 格式不同v-modelnum, :num.syncnumv-model: input value:num.sync: update:numv-model只能用一次.sync可以有多个
Vue组件之间通信方式有哪些 Vue 组件间通信是面试常考的知识点之一这题有点类似于开放题你回答出越多方法当然越加分表明你对 Vue 掌握的越熟练。 Vue 组件间通信只要指以下 3 类通信 父子组件通信、隔代组件通信、兄弟组件通信下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信 组件传参的各种方式 组件通信常用方式有以下几种
props / $emit 适用 父子组件通信 父组件向子组件传递数据是通过 prop 传递的子组件传递数据给父组件是通过$emit 触发事件来做到的 ref 与 $parent / $children(vue3废弃) 适用 父子组件通信 ref如果在普通的 DOM 元素上使用引用指向的就是 DOM 元素如果用在子组件上引用就指向组件实例$parent / $children访问访问父组件的属性或方法 / 访问子组件的属性或方法 EventBus $emit / $on 适用于 父子、隔代、兄弟组件通信 这种方法通过一个空的 Vue 实例作为中央事件总线事件中心用它来触发事件和监听事件从而实现任何组件间的通信包括父子、隔代、兄弟组件 $attrs / $listeners(vue3废弃) 适用于 隔代组件通信 $attrs包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop时这里会包含所有父作用域的绑定 ( class 和 style 除外 )并且可以通过 v-bind$attrs 传入内部组件。通常配合 inheritAttrs 选项一起使用$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on$listeners 传入内部组件 provide / inject 适用于 隔代组件通信 祖先组件中通过 provider 来提供变量然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题 不过它的使用场景主要是子组件获取上级组件的状态 跨级组件间建立了一种主动提供与依赖注入的关系 $root 适用于 隔代组件通信 访问根组件中的属性或方法是根组件不是父组件。$root只对根组件有用Vuex 适用于 父子、隔代、兄弟组件通信 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store仓库。“store” 基本上就是一个容器它包含着你的应用中大部分的状态 ( state )Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候若 store 中的状态发生变化那么相应的组件也会相应地得到高效更新。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
根据组件之间关系讨论组件通信最为清晰有效
父子组件props/$emit/$parent/ref兄弟组件$parent/eventbus/vuex跨层级关系eventbus/vuex/provideinject/$attrs $listeners/$root 下面演示组件之间通讯三种情况: 父传子、子传父、兄弟组件之间的通讯 1. 父子组件通信 使用props父组件可以使用props向子组件传递数据。 父组件vue模板father.vue:
templatechild :msgmessage/child
/templatescript
import child from ./child.vue;
export default {components: {child},data () {return {message: father message;}}
}
/script子组件vue模板child.vue:
templatediv{{msg}}/div
/templatescript
export default {props: {msg: {type: String,required: true}}
}
/script回调函数callBack
父传子将父组件里定义的method作为props传入子组件
// 父组件Parent.vue
Child :changeMsgFnchangeMessage
methods: {changeMessage(){this.message test}
}// 子组件Child.vue
button clickchangeMsgFn
props:[changeMsgFn]子组件向父组件通信 父组件向子组件传递事件方法子组件通过$emit触发事件回调给父组件 父组件vue模板father.vue:
templatechild msgFuncfunc/child
/templatescript
import child from ./child.vue;
export default {components: {child},methods: {func (msg) {console.log(msg);}}
}
/script子组件vue模板child.vue:
templatebutton clickhandleClick点我/button
/templatescript
export default {props: {msg: {type: String,required: true}},methods () {handleClick () {//........this.$emit(msgFunc);}}
}
/script2. provide / inject 跨级访问祖先组件的数据
父组件通过使用provide(){return{}}提供需要传递的数据
export default {data() {return {title: 我是父组件,name: poetry}},methods: {say() {alert(1)}},// provide属性 能够为后面的后代组件/嵌套的组件提供所需要的变量和方法provide() {return {message: 我是祖先组件提供的数据,name: this.name, // 传递属性say: this.say}}
}子组件通过使用inject:[“参数1”,”参数2”,…]接收父组件传递的参数
templatep曾孙组件/pp{{message}}/p
/template
script
export default {// inject 注入/接收祖先组件传递的所需要的数据即可 //接收到的数据 变量 跟data里面的变量一样 可以直接绑定到页面 {{}}inject: [ message,say],mounted() {this.say();},
};
/script3. $parent $children 获取父组件实例和子组件实例的集合
this.$parent 可以直接访问该组件的父实例或组件父组件也可以通过 this.$children 访问它所有的子组件需要注意 $children 并不保证顺序也不是响应式的
!-- parent.vue --
template
divchild1/child1 child2/child2 button clickclickChild$children方式获取子组件值/button
/div
/template
script
import child1 from ./child1
import child2 from ./child2
export default {data(){return {total: 108}},components: {child1,child2 },methods: {funa(e){console.log(index,e)},clickChild(){console.log(this.$children[0].msg);console.log(this.$children[1].msg);}}
}
/script!-- child1.vue --
templatedivbutton clickparentClick点击访问父组件/button/div
/template
script
export default {data(){return {msg:child1}},methods: {// 访问父组件数据parentClick(){this.$parent.funa(xx)console.log(this.$parent.total);}}
}
/script!-- child2.vue --
templatedivchild2/div
/template
script
export default {data(){return {msg: child2}}
}
/script4. $attrs $listeners多级组件通信 $attrs 包含了从父组件传过来的所有props属性 // 父组件Parent.vue
Child :namename :ageage/// 子组件Child.vue
GrandChild v-bind$attrs /// 孙子组件GrandChild
p姓名{{$attrs.name}}/p
p年龄{{$attrs.age}}/p$listeners包含了父组件监听的所有事件 // 父组件Parent.vue
Child :namename :ageage changeNameFnchangeName/// 子组件Child.vue
button click$listeners.changeNameFn/button5. ref 父子组件通信
// 父组件Parent.vue
Child refchildComp/
button clickchangeName/button
changeName(){console.log(this.$refs.childComp.age);this.$refs.childComp.changeAge()
}// 子组件Child.vue
data(){return{age:20}
},
methods(){changeAge(){this.age15}
}6. 非父子, 兄弟组件之间通信 vue2中废弃了broadcast广播和分发事件的方法。父子组件中可以用props和$emit()。如何实现非父子组件间的通信可以通过实例一个vue实例Bus作为媒介要相互通信的兄弟组件之中都引入Bus然后通过分别调用Bus事件触发和监听来实现通信和参数传递。Bus.js可以是这样: // Bus.js// 创建一个中央时间总线类
class Bus { constructor() { this.callbacks {}; // 存放事件的名字 } $on(name, fn) { this.callbacks[name] this.callbacks[name] || []; this.callbacks[name].push(fn); } $emit(name, args) { if (this.callbacks[name]) { this.callbacks[name].forEach((cb) cb(args)); } }
} // main.js
Vue.prototype.$bus new Bus() // 将$bus挂载到vue实例的原型上
// 另一种方式
Vue.prototype.$bus new Vue() // Vue已经实现了Bus的功能 templatebutton clicktoBus子组件传给兄弟组件/button
/templatescript
export default{methods: {toBus () {this.$bus.$emit(foo, 来自兄弟组件)}}
}
/script另一个组件也在钩子函数中监听on事件
export default {data() {return {message: }},mounted() {this.$bus.$on(foo, (msg) {this.message msg})}
}7. $root 访问根组件中的属性或方法
作用访问根组件中的属性或方法注意是根组件不是父组件。$root只对根组件有用
var vm new Vue({el: #app,data() {return {rootInfo:我是根元素的属性}},methods: {alerts() {alert(111)}},components: {com1: {data() {return {info: 组件1}},template: p{{ info }} com2/com2/p,components: {com2: {template: p我是组件1的子组件/p,created() {this.$root.alerts()// 根组件方法console.log(this.$root.rootInfo)// 我是根元素的属性}}}}}
});8. vuex
适用场景: 复杂关系的组件数据传递Vuex作用相当于一个用来存储共享变量的容器 state用来存放共享变量的地方getter可以增加一个getter派生状态(相当于store中的计算属性用来获得共享变量的值mutations用来存放修改state的方法。actions也是用来存放修改state的方法不过action是在mutations的基础上进行。常用来做一些异步操作
小结
父子关系的组件数据传递选择 props 与 $emit进行传递也可选择ref兄弟关系的组件数据传递可选择$bus其次可以选择$parent进行传递祖先与后代组件数据传递可选择attrs与listeners或者 Provide与 Inject复杂关系的组件数据传递可以通过vuex存放共享的变量
vue-loader是什么它有什么作用
回答范例
vue-loader是用于处理单文件组件SFCSingle-File Component的webpack loader因为有了vue-loader我们就可以在项目中编写SFC格式的Vue组件我们可以把代码分割为template、script和style代码会异常清晰。结合其他loader我们还可以用Pug编写template用SASS编写style用TS编写script。我们的style还可以单独作用当前组件webpack打包时会以loader的方式调用vue-loadervue-loader被执行时它会对SFC中的每个语言块用单独的loader链处理。最后将这些单独的块装配成最终的组件模块
原理
vue-loader会调用vue/compiler-sfc模块解析SFC源码为一个描述符Descriptor然后为每个语言块生成import代码返回的代码类似下面
// source.vue被vue-loader处理之后返回的代码
// import the template block
import render from source.vue?vuetypetemplate
// import the script block
import script from source.vue?vuetypescript
export * from source.vue?vuetypescript
// import style blocks
import source.vue?vuetypestyleindex1
script.render render
export default script我们想要script块中的内容被作为js处理当然如果是script langts被作为ts理这样我们想要webpack把配置中跟.js匹配的规则都应用到形如source.vue?vuetypescript的这个请求上。例如我们对所有*.js配置了babel-loader这个规则将被克隆并应用到所在Vue SFC
import script from source.vue?vuetypescript将被展开为
import script from babel-loader!vue-loader!source.vue?vuetypescript类似的如果我们对.sass文件配置了style-loader css-loader sass-loader对下面的代码
style scoped langscssvue-loader将会返回给我们下面结果
import source.vue?vuetypestyleindex1scopedlangscss然后webpack会展开如下
import style-loader!css-loader!sass-loader!vue-loader!source.vue?vuetypestyleindex1scopedlangscss当处理展开请求时vue-loader将被再次调用。这次loader将会关注那些有查询串的请求且仅针对特定块它会选中特定块内部的内容并传递给后面匹配的loader对于script块处理到这就可以了但是template 和 style还有一些额外任务要做比如 需要用 Vue 模板编译器编译template从而得到render函数需要对 style scoped中的CSS做后处理post-process该操作在css-loader之后但在style-loader之前
实现上这些附加的loader需要被注入到已经展开的loader链上最终的请求会像下面这样
// template langpug
import vue-loader/template-loader!pug-loader!source.vue?vuetypetemplate
// style scoped langscss
import style-loader!vue-loader/style-post-loader!css-loader!sass-loader!vue-loader!source.vue?vuetypestyleindex1scopedlangscssdiff算法
时间复杂度 个树的完全 diff 算法是一个时间复杂度为 O(n*3 vue进行优化转化成 O(n) 。
理解
最小量更新 key 很重要。这个可以是这个节点的唯一标识告诉 diff 算法在更改前后它们是同一个DOM节点 扩展 v-for 为什么要有 key 没有 key 会暴力复用举例子的话随便说一个比如移动节点或者增加节点修改DOM加 key 只会移动减少操作DOM。 只有是同一个虚拟节点才会进行精细化比较否则就是暴力删除旧的插入新的。只进行同层比较不会进行跨层比较。
diff算法的优化策略四种命中查找四个指针
旧前与新前先比开头后插入和删除节点的这种情况旧后与新后比结尾前插入或删除的情况旧前与新后头与尾比此种发生了涉及移动节点那么新前指向的节点移动到旧后之后旧后与新前尾与头比此种发生了涉及移动节点那么新前指向的节点移动到旧前之前
vue3中 watch、watchEffect区别
watch是惰性执行也就是只有监听的值发生变化的时候才会执行但是watchEffect不同每次代码加载watchEffect都会执行忽略watch第三个参数的配置如果修改配置项也可以实现立即执行watch需要传递监听的对象watchEffect不需要watch只能监听响应式数据ref定义的属性和reactive定义的对象如果直接监听reactive定义对象中的属性是不允许的会报警告除非使用函数转换一下。其实就是官网上说的监听一个getterwatchEffect如果监听reactive定义的对象是不起作用的只能监听对象中的属性
看一下watchEffect的代码
template
div请输入firstNameinput typetext v-modelfirstName
/div
div请输入lastNameinput typetext v-modellastName
/div
div请输入obj.textinput typetext v-modelobj.text
/divdiv【obj.text】 {{obj.text}}/div
/templatescript
import {ref, reactive, watch, watchEffect} from vue
export default {name: HelloWorld,props: {msg: String,},setup(props,content){let firstName ref()let lastName ref()let obj reactive({text:hello})watchEffect((){console.log(触发了watchEffect);console.log(组合后的名称为${firstName.value}${lastName.value})})return{obj,firstName,lastName}}
};
/script改造一下代码
watchEffect((){console.log(触发了watchEffect);// 这里我们不使用firstName.value/lastName.value 相当于是监控整个ref,对应第四点上面的结论console.log(组合后的名称为${firstName}${lastName})
})watchEffect((){console.log(触发了watchEffect);console.log(obj);
})稍微改造一下
let obj reactive({text:hello
})
watchEffect((){console.log(触发了watchEffect);console.log(obj.text);
})再看一下watch的代码验证一下
let obj reactive({text:hello
})
// watch是惰性执行 默认初始化之后不会执行只有值有变化才会触发可通过配置参数实现默认执行
watch(obj, (newValue, oldValue) {// 回调函数console.log(触发监控更新了new, newValue);console.log(触发监控更新了old, oldValue);
},{// 配置immediate参数立即执行以及深层次监听immediate: true,deep: true
})监控整个reactive对象从上面的图可以看到 deep 实际默认是开启的就算我们设置为false也还是无效。而且旧值获取不到。要获取旧值则需要监控对象的属性也就是监听一个getter看下图 总结
如果定义了reactive的数据想去使用watch监听数据改变则无法正确获取旧值并且deep属性配置无效自动强制开启了深层次监听。如果使用 ref 初始化一个对象或者数组类型的数据会被自动转成reactive的实现方式生成proxy代理对象。也会变得无法正确取旧值。用任何方式生成的数据如果接收的变量是一个proxy代理对象就都会导致watch这个对象时,watch回调里无法正确获取旧值。所以当大家使用watch监听对象时如果在不需要使用旧值的情况可以正常监听对象没关系但是如果当监听改变函数里面需要用到旧值时只能监听 对象.xxx属性 的方式才行
watch和watchEffect异同总结
体验
watchEffect立即运行一个函数然后被动地追踪它的依赖当这些依赖改变时重新执行该函数
const count ref(0)
watchEffect(() console.log(count.value))
// - logs 0
count.value
// - logs 1watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数
const state reactive({ count: 0 })
watch(() state.count,(count, prevCount) {/* ... */}
)回答范例
watchEffect立即运行一个函数然后被动地追踪它的依赖当这些依赖改变时重新执行该函数。watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数watchEffect(effect)是一种特殊watch传入的函数既是依赖收集的数据源也是回调函数。如果我们不关心响应式数据变化前后的值只是想拿这些数据做些事情那么watchEffect就是我们需要的。watch更底层可以接收多种数据源包括用于依赖收集的getter函数因此它完全可以实现watchEffect的功能同时由于可以指定getter函数依赖可以控制的更精确还能获取数据变化前后的值因此如果需要这些时我们会使用watchwatchEffect在使用时传入的函数会立刻执行一次。watch默认情况下并不会执行回调函数除非我们手动设置immediate选项从实现上来说watchEffect(fn)相当于watch(fn,fn,{immediate:true})
watchEffect定义如下
export function watchEffect(effect: WatchEffect,options?: WatchOptionsBase
): WatchStopHandle {return doWatch(effect, null, options)
}watch定义如下
export function watchT any, Immediate extends Readonlyboolean false(source: T | WatchSourceT,cb: any,options?: WatchOptionsImmediate
): WatchStopHandle {return doWatch(source as any, cb, options)
}很明显watchEffect就是一种特殊的watch实现。
vue要做权限管理该怎么做如果控制到按钮级别的权限怎么做
一、是什么
权限是对特定资源的访问许可所谓权限控制也就是确保用户只能访问到被分配的资源
而前端权限归根结底是请求的发起权请求的发起可能有下面两种形式触发
页面加载触发页面上的按钮点击触发
总的来说所有的请求发起都触发自前端路由或视图
所以我们可以从这两方面入手对触发权限的源头进行控制最终要实现的目标是
路由方面用户登录后只能看到自己有权访问的导航菜单也只能访问自己有权访问的路由地址否则将跳转 4xx 提示页视图方面用户只能看到自己有权浏览的内容和有权操作的控件最后再加上请求控制作为最后一道防线路由可能配置失误按钮可能忘了加权限这种时候请求控制可以用来兜底越权请求将在前端被拦截
二、如何做
前端权限控制可以分为四个方面
接口权限按钮权限菜单权限路由权限
接口权限
接口权限目前一般采用jwt的形式来验证没有通过的话一般返回401跳转到登录页面重新进行登录
登录完拿到token将token存起来通过axios请求拦截器进行拦截每次请求的时候头部携带token
axios.interceptors.request.use(config {config.headers[token] cookie.get(token)return config
})
axios.interceptors.response.use(res{},{response}{if (response.data.code 40099 || response.data.code 40098) { //token过期或者错误router.push(/login)}
})路由权限控制
方案一
初始化即挂载全部路由并且在路由上标记相应的权限信息每次路由跳转前做校验
const routerMap [{path: /permission,component: Layout,redirect: /permission/index,alwaysShow: true, // will always show the root menumeta: {title: permission,icon: lock,roles: [admin, editor] // you can set roles in root nav},children: [{path: page,component: () import(/views/permission/page),name: pagePermission,meta: {title: pagePermission,roles: [admin] // or you can only set roles in sub nav}}, {path: directive,component: () import(/views/permission/directive),name: directivePermission,meta: {title: directivePermission// if do not set roles, means: this page does not require permission}}]}]这种方式存在以下四种缺点
加载所有的路由如果路由很多而用户并不是所有的路由都有权限访问对性能会有影响。全局路由守卫里每次路由跳转都要做权限判断。菜单信息写死在前端要改个显示文字或权限信息需要重新编译菜单跟路由耦合在一起定义路由的时候还有添加菜单显示标题图标之类的信息而且路由不一定作为菜单显示还要多加字段进行标识
方案二
初始化的时候先挂载不需要权限控制的路由比如登录页404等错误页。如果用户通过URL进行强制访问则会直接进入404相当于从源头上做了控制
登录后获取用户的权限信息然后筛选有权限访问的路由在全局路由守卫里进行调用addRoutes添加路由
import router from ./router
import store from ./store
import { Message } from element-ui
import NProgress from nprogress // progress bar
import nprogress/nprogress.css// progress bar style
import { getToken } from /utils/auth // getToken from cookieNProgress.configure({ showSpinner: false })// NProgress Configuration// permission judge function
function hasPermission(roles, permissionRoles) {if (roles.indexOf(admin) 0) return true // admin permission passed directlyif (!permissionRoles) return truereturn roles.some(role permissionRoles.indexOf(role) 0)
}const whiteList [/login, /authredirect]// no redirect whitelistrouter.beforeEach((to, from, next) {NProgress.start() // start progress barif (getToken()) { // determine if there has token/* has token*/if (to.path /login) {next({ path: / })NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it} else {if (store.getters.roles.length 0) { // 判断当前用户是否已拉取完user_info信息store.dispatch(GetUserInfo).then(res { // 拉取user_infoconst roles res.data.roles // note: roles must be a array! such as: [editor,develop]store.dispatch(GenerateRoutes, { roles }).then(() { // 根据roles权限生成可访问的路由表router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record})}).catch((err) {store.dispatch(FedLogOut).then(() {Message.error(err || Verification failed, please login again)next({ path: / })})})} else {// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓if (hasPermission(store.getters.roles, to.meta.roles)) {next()//} else {next({ path: /401, replace: true, query: { noGoBack: true }})}// 可删 ↑}}} else {/* has no token*/if (whiteList.indexOf(to.path) ! -1) { // 在免登录白名单直接进入next()} else {next(/login) // 否则全部重定向到登录页NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it}}
})router.afterEach(() {NProgress.done() // finish progress bar
})按需挂载路由就需要知道用户的路由权限也就是在用户登录进来的时候就要知道当前用户拥有哪些路由权限
这种方式也存在了以下的缺点
全局路由守卫里每次路由跳转都要做判断菜单信息写死在前端要改个显示文字或权限信息需要重新编译菜单跟路由耦合在一起定义路由的时候还有添加菜单显示标题图标之类的信息而且路由不一定作为菜单显示还要多加字段进行标识
菜单权限
菜单权限可以理解成将页面与理由进行解耦
方案一
菜单与路由分离菜单由后端返回
前端定义路由信息
{name: login,path: /login,component: () import(/pages/Login.vue)
}name字段都不为空需要根据此字段与后端返回菜单做关联后端返回的菜单信息中必须要有name对应的字段并且做唯一性校验
全局路由守卫里做判断
function hasPermission(router, accessMenu) {if (whiteList.indexOf(router.path) ! -1) {return true;}let menu Util.getMenuByName(router.name, accessMenu);if (menu.name) {return true;}return false;}Router.beforeEach(async (to, from, next) {if (getToken()) {let userInfo store.state.user.userInfo;if (!userInfo.name) {try {await store.dispatch(GetUserInfo)await store.dispatch(updateAccessMenu)if (to.path /login) {next({ name: home_index })} else {//Util.toDefaultPage([...routers], to.name, router, next);next({ ...to, replace: true })//菜单权限更新完成,重新进一次当前路由}} catch (e) {if (whiteList.indexOf(to.path) ! -1) { // 在免登录白名单直接进入next()} else {next(/login)}}} else {if (to.path /login) {next({ name: home_index })} else {if (hasPermission(to, store.getters.accessMenu)) {Util.toDefaultPage(store.getters.accessMenu,to, routes, next);} else {next({ path: /403,replace:true })}}}} else {if (whiteList.indexOf(to.path) ! -1) { // 在免登录白名单直接进入next()} else {next(/login)}}let menu Util.getMenuByName(to.name, store.getters.accessMenu);Util.title(menu.title);
});Router.afterEach((to) {window.scrollTo(0, 0);
});每次路由跳转的时候都要判断权限这里的判断也很简单因为菜单的name与路由的name是一一对应的而后端返回的菜单就已经是经过权限过滤的
如果根据路由name找不到对应的菜单就表示用户有没权限访问
如果路由很多可以在应用初始化的时候只挂载不需要权限控制的路由。取得后端返回的菜单后根据菜单与路由的对应关系筛选出可访问的路由通过addRoutes动态挂载
这种方式的缺点
菜单需要与路由做一一对应前端添加了新功能需要通过菜单管理功能添加新的菜单如果菜单配置的不对会导致应用不能正常使用全局路由守卫里每次路由跳转都要做判断
方案二
菜单和路由都由后端返回
前端统一定义路由组件
const Home () import(../pages/Home.vue);
const UserInfo () import(../pages/UserInfo.vue);
export default {home: Home,userInfo: UserInfo
};后端路由组件返回以下格式
[{name: home,path: /,component: home},{name: home,path: /userinfo,component: userInfo}
]在将后端返回路由通过addRoutes动态挂载之间需要将数据处理一下将component字段换为真正的组件
如果有嵌套路由后端功能设计的时候要注意添加相应的字段前端拿到数据也要做相应的处理
这种方法也会存在缺点
全局路由守卫里每次路由跳转都要做判断前后端的配合要求更高
按钮权限
方案一
按钮权限也可以用v-if判断
但是如果页面过多每个页面页面都要获取用户权限role和路由表里的meta.btnPermissions然后再做判断
这种方式就不展开举例了
方案二
通过自定义指令进行按钮权限的判断
首先配置路由
{path: /permission,component: Layout,name: 权限测试,meta: {btnPermissions: [admin, supper, normal]},//页面需要的权限children: [{path: supper,component: _import(system/supper),name: 权限测试页,meta: {btnPermissions: [admin, supper]} //页面需要的权限},{path: normal,component: _import(system/normal),name: 权限测试页,meta: {btnPermissions: [admin]} //页面需要的权限}]
}自定义权限鉴定指令
import Vue from vue
/**权限指令**/
const has Vue.directive(has, {bind: function (el, binding, vnode) {// 获取页面按钮权限let btnPermissionsArr [];if(binding.value){// 如果指令传值获取指令参数根据指令参数和当前登录人按钮权限做比较。btnPermissionsArr Array.of(binding.value);}else{// 否则获取路由中的参数根据路由的btnPermissionsArr和当前登录人按钮权限做比较。btnPermissionsArr vnode.context.$route.meta.btnPermissions;}if (!Vue.prototype.$_has(btnPermissionsArr)) {el.parentNode.removeChild(el);}}
});
// 权限检查方法
Vue.prototype.$_has function (value) {let isExist false;// 获取用户按钮权限let btnPermissionsStr sessionStorage.getItem(btnPermissions);if (btnPermissionsStr undefined || btnPermissionsStr null) {return false;}if (value.indexOf(btnPermissionsStr) -1) {isExist true;}return isExist;
};
export {has}在使用的按钮中只需要引用v-has指令
el-button clickeditClick typeprimary v-has编辑/el-button小结
关于权限如何选择哪种合适的方案可以根据自己项目的方案项目如考虑路由与菜单是否分离
权限需要前后端结合前端尽可能的去控制更多的需要后台判断
SPA首屏加载速度慢的怎么解决
一、什么是首屏加载
首屏时间First Contentful Paint指的是浏览器从响应用户输入网址地址到首屏内容渲染完成的时间此时整个网页不一定要全部渲染完成但需要展示当前视窗需要的内容
首屏加载可以说是用户体验中最重要的环节
关于计算首屏时间
利用performance.timing提供的数据 通过DOMContentLoad或者performance来计算出首屏时间
// 方案一
document.addEventListener(DOMContentLoaded, (event) {console.log(first contentful painting);
});
// 方案二
performance.getEntriesByName(first-contentful-paint)[0].startTime// performance.getEntriesByName(first-contentful-paint)[0]
// 会返回一个 PerformancePaintTiming的实例结构如下
{name: first-contentful-paint,entryType: paint,startTime: 507.80000002123415,duration: 0,
};二、加载慢的原因
在页面渲染的过程导致加载速度慢的因素可能如下
网络延时问题资源文件体积是否过大资源是否重复发送请求去加载了加载脚本的时候渲染内容堵塞了
三、解决方案
常见的几种SPA首屏优化方式
减小入口文件积静态资源本地缓存UI框架按需加载图片资源的压缩组件重复打包开启GZip压缩使用SSR
1. 减小入口文件体积
常用的手段是路由懒加载把不同路由对应的组件分割成不同的代码块待路由被请求的时候会单独打包路由使得入口文件变小加载速度大大增加 在vue-router配置路由的时候采用动态加载路由的形式
routes:[ path: Blogs,name: ShowBlogs,component: () import(./components/ShowBlogs.vue)
]以函数的形式加载路由这样就可以把各自的路由文件分别打包只有在解析给定的路由时才会加载路由组件
2. 静态资源本地缓存
后端返回资源问题
采用HTTP缓存设置Cache-ControlLast-ModifiedEtag等响应头采用Service Worker离线缓存
前端合理利用localStorage
3. UI框架按需加载
在日常使用UI框架例如element-UI、或者antd我们经常性直接引用整个UI库
import ElementUI from element-ui
Vue.use(ElementUI)但实际上我用到的组件只有按钮分页表格输入与警告 所以我们要按需引用
import { Button, Input, Pagination, Table, TableColumn, MessageBox } from element-ui;
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)4. 组件重复打包
假设A.js文件是一个常用的库现在有多个路由使用了A.js文件这就造成了重复下载
解决方案在webpack的config文件中修改CommonsChunkPlugin的配置
minChunks: 3minChunks为3表示会把使用3次及以上的包抽离出来放进公共依赖文件避免了重复加载组件
5. 图片资源的压缩
图片资源虽然不在编码过程中但它却是对页面性能影响最大的因素
对于所有的图片资源我们可以进行适当的压缩
对页面上使用到的icon可以使用在线字体图标或者雪碧图将众多小图标合并到同一张图上用以减轻http请求压力。
6. 开启GZip压缩
拆完包之后我们再用gzip做一下压缩 安装compression-webpack-plugin
cnmp i compression-webpack-plugin -D在vue.congig.js中引入并修改webpack配置
const CompressionPlugin require(compression-webpack-plugin)configureWebpack: (config) {if (process.env.NODE_ENV production) {// 为生产环境修改配置...config.mode productionreturn {plugins: [new CompressionPlugin({test: /\.js$|\.html$|\.css/, //匹配文件名threshold: 10240, //对超过10k的数据进行压缩deleteOriginalAssets: false //是否删除原文件})]}}在服务器我们也要做相应的配置 如果发送请求的浏览器支持gzip就发送给它gzip格式的文件 我的服务器是用express框架搭建的 只要安装一下compression就能使用
const compression require(compression)
app.use(compression()) // 在其他中间件使用之前调用7. 使用SSR
SSRServer side 也就是服务端渲染组件或页面通过服务器生成html字符串再发送到浏览器
从头搭建一个服务端渲染是很复杂的vue应用建议使用Nuxt.js实现服务端渲染
四、小结
减少首屏渲染时间的方法有很多总的来讲可以分成两大部分 资源加载优化 和 页面渲染优化
下图是更为全面的首屏优化的方案 大家可以根据自己项目的情况选择各种方式进行首屏渲染的优化