共计 21761 个字符,预计需要花费 55 分钟才能阅读完成。
watch 原理
watch
实质上是为每个监听属性 setter
创立了一个 watcher
,当被监听的属性更新时,调用传入的回调函数。常见的配置选项有 deep
和 immediate
,对应原理如下
deep
:深度监听对象,为对象的每一个属性创立一个watcher
,从而确保对象的每一个属性更新时都会触发传入的回调函数。次要起因在于对象属于援用类型,单个属性的更新并不会触发对象setter
,因而引入deep
可能很好地解决监听对象的问题。同时也会引入判断机制,确保在多个属性更新时回调函数仅触发一次,防止性能节约。immediate
:在初始化时间接调用回调函数,能够通过在created
阶段手动调用回调函数实现雷同的成果
对虚构 DOM 的了解?
从实质上来说,Virtual Dom 是一个 JavaScript 对象,通过对象的形式来示意 DOM 构造。将页面的状态形象为 JS 对象的模式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将屡次 DOM 批改的后果一次性的更新到页面上,从而无效的缩小页面渲染的次数,缩小批改 DOM 的重绘重排次数,进步渲染性能。
虚构 DOM 是对 DOM 的形象,这个对象是更加轻量级的对 DOM 的形容。它设计的最后目标,就是更好的跨平台,比方 Node.js 就没有 DOM,如果想实现 SSR,那么一个形式就是借助虚构 DOM,因为虚构 DOM 自身是 js 对象。在代码渲染到页面之前,vue 会把代码转换成一个对象(虚构 DOM)。以对象的模式来形容实在 DOM 构造,最终渲染到页面。在每次数据发生变化前,虚构 DOM 都会缓存一份,变动之时,当初的虚构 DOM 会与缓存的虚构 DOM 进行比拟。在 vue 外部封装了 diff 算法,通过这个算法来进行比拟,渲染时批改扭转的变动,原先没有产生扭转的通过原先的数据进行渲染。
另外古代前端框架的一个根本要求就是毋庸手动操作 DOM,一方面是因为手动操作 DOM 无奈保障程序性能,多人合作的我的项目中如果 review 不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动 DOM 操作能够大大提高开发效率。
Vue3 的设计指标是什么?做了哪些优化
1、设计指标
不以解决理论业务痛点的更新都是耍流氓,上面咱们来列举一下 Vue3
之前咱们或者会面临的问题
- 随着性能的增长,简单组件的代码变得越来越难以保护
- 短少一种比拟「洁净」的在多个组件之间提取和复用逻辑的机制
- 类型推断不够敌对
bundle
的工夫太久了
而 Vue3
通过长达两三年工夫的筹备,做了哪些事件?
咱们从后果反推
- 更小
- 更快
- TypeScript 反对
- API 设计一致性
- 进步本身可维护性
- 凋谢更多底层性能
一句话概述,就是更小更快更敌对了
更小
Vue3
移除一些不罕用的API
- 引入
tree-shaking
,能够将无用模块“剪辑”,仅打包须要的,使打包的整体体积变小了
更快
次要体现在编译方面:
diff
算法优化- 动态晋升
- 事件监听缓存
SSR
优化
更敌对
vue3
在兼顾 vue2
的options API
的同时还推出了composition API
,大大增加了代码的逻辑组织和代码复用能力
这里代码简略演示下:
存在一个获取鼠标地位的函数
import {toRefs, reactive} from 'vue';
function useMouse(){const state = reactive({x:0,y:0});
const update = e=>{
state.x = e.pageX;
state.y = e.pageY;
}
onMounted(()=>{window.addEventListener('mousemove',update);
})
onUnmounted(()=>{window.removeEventListener('mousemove',update);
})
return toRefs(state);
}
咱们只须要调用这个函数,即可获取 x
、y
的坐标,齐全不必关注实现过程
试想一下,如果很多相似的第三方库,咱们只须要调用即可,不用关注实现过程,开发效率大大提高
同时,VUE3
是基于 typescipt
编写的,能够享受到主动的类型定义提醒
2、优化计划
vue3
从很多层面都做了优化,能够分成三个方面:
- 源码
- 性能
- 语法 API
源码
源码能够从两个层面开展:
- 源码治理
- TypeScript
源码治理
vue3
整个源码是通过 monorepo
的形式保护的,依据性能将不同的模块拆分到 packages
目录上面不同的子目录中
这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易浏览、了解和更改所有模块源码,进步代码的可维护性
另外一些 package
(比方 reactivity
响应式库)是能够独立于 Vue
应用的,这样用户如果只想应用 Vue3
的响应式能力,能够独自依赖这个响应式库而不必去依赖整个 Vue
TypeScript
Vue3
是基于 typeScript
编写的,提供了更好的类型查看,能反对简单的类型推导
性能
vue3
是从什么哪些方面对性能进行进一步优化呢?
- 体积优化
- 编译优化
- 数据劫持优化
这里讲述数据劫持:
在 vue2
中,数据劫持是通过Object.defineProperty
,这个 API 有一些缺点,并不能检测对象属性的增加和删除
Object.defineProperty(data, 'a',{get(){// track},
set(){// trigger}
})
只管 Vue
为了解决这个问题提供了 set
和 delete
实例办法,然而对于用户来说,还是减少了肯定的心智累赘
同时在面对嵌套层级比拟深的状况下,就存在性能问题
default {
data: {
a: {
b: {
c: {d: 1}
}
}
}
}
相比之下,vue3
是通过 proxy
监听整个对象,那么对于删除还是监听当然也能监听到
同时Proxy
并不能监听到外部深层次的对象变动,而 Vue3
的解决形式是在getter
中去递归响应式,这样的益处是真正拜访到的外部对象才会变成响应式,而不是无脑递归
语法 API
这里当然说的就是composition API
,其两大显著的优化:
- 优化逻辑组织
- 优化逻辑复用
逻辑组织
一张图,咱们能够很直观地感触到 Composition API
在逻辑组织方面的劣势
雷同性能的代码编写在一块,而不像 options API
那样,各个性能的代码混成一块
逻辑复用
在 vue2
中,咱们是通过 mixin
实现性能混合,如果多个 mixin
混合,会存在两个非常明显的问题:命名抵触和数据起源不清晰
而通过 composition
这种模式,能够将一些复用的代码抽离进去作为一个函数,只有的应用的中央间接进行调用即可
同样是上文的获取鼠标地位的例子
import {toRefs, reactive, onUnmounted, onMounted} from 'vue';
function useMouse(){const state = reactive({x:0,y:0});
const update = e=>{
state.x = e.pageX;
state.y = e.pageY;
}
onMounted(()=>{window.addEventListener('mousemove',update);
})
onUnmounted(()=>{window.removeEventListener('mousemove',update);
})
return toRefs(state);
}
组件应用
import useMousePosition from './mouse'
export default {setup() {const { x, y} = useMousePosition()
return {x, y}
}
}
能够看到,整个数据起源清晰了,即便去编写更多的 hook
函数,也不会呈现命名抵触的问题
Vue-router 除了 router-link 怎么实现跳转
申明式导航
<router-link to="/about">Go to About</router-link>
编程式导航
// literal string path
router.push('/users/1')
// object with path
router.push({path: '/users/1'})
// named route with params to let the router build the url
router.push({name: 'user', params: { username: 'test'} })
答复范例
vue-router
导航有两种形式:申明式导航和编程形式导航- 申明式导航形式应用
router-link
组件,增加to
属性导航;编程形式导航更加灵便,可传递调用router.push()
,并传递path
字符串或者RouteLocationRaw
对象,指定path
、name
、params
等信息 - 如果页面中简略示意跳转链接,应用
router-link
最快捷,会渲染一个 a 标签;如果页面是个简单的内容,比方商品信息,能够增加点击事件,应用编程式导航 - 实际上外部两者调用的导航函数是一样的
vue-router 守卫
导航守卫
router.beforeEach
全局前置守卫
to: Route
: 行将要进入的指标(路由对象)from: Route
: 以后导航正要来到的路由next: Function
: 肯定要调用该办法来resolve
这个钩子。(肯定要用这个函数能力去到下一个路由,如果不必就拦挡)- 执行成果依赖 next 办法的调用参数。
next()
: 进行管道中的下一个钩子。如果全副钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false)
: 勾销进入路由,url 地址重置为 from 路由地址(也就是将要来到的路由地址)
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {next();
});
router.beforeResolve((to, from, next) => {next();
});
router.afterEach((to, from) => {console.log('afterEach 全局后置钩子');
});
路由独享的守卫 你能够在路由配置上间接定义
beforeEnter
守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {// ...}
}
]
})
组件内的守卫你能够在路由组件内间接定义以下路由导航守卫
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创立
},
beforeRouteUpdate (to, from, next) {
// 在以后路由扭转,然而该组件被复用时调用
// 举例来说,对于一个带有动静参数的门路 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,// 因为会渲染同样的 Foo 组件,因而组件实例会被复用。而这个钩子就会在这个状况下被调用。// 能够拜访组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航来到该组件的对应路由时调用,咱们用它来禁止用户来到
// 能够拜访组件实例 `this`
// 比方还未保留草稿,或者在用户来到前,将 setInterval 销毁,避免来到之后,定时器还在调用。}
}
如果让你从零开始写一个 vuex,说说你的思路
思路剖析
这个题目很有难度,首先思考 vuex
解决的问题:存储用户全局状态并提供治理状态 API。
vuex
需要剖析- 如何实现这些需要
答复范例
- 官网说
vuex
是一个状态管理模式和库,并确保这些状态以可预期的形式变更。可见要实现一个vuex
- 要实现一个
Store
存储全局状态 - 要提供批改状态所需 API:
commit(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.mutations
this._actions = options.actions
this.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 = _Vue
Vue.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: {}})
参考 前端进阶面试题具体解答
你晓得哪些 Vue3 新个性?
官网列举的最值得注意的新个性:v3-migration.vuejs.org(opens new window)
Composition API
SFC Composition API
语法糖Teleport
传送门Fragments
片段Emits
选项- 自定义渲染器
SFC CSS
变量Suspense
以上这些是 api 相干,另外还有很多框架个性也不能落掉
答复范例
api
层面Vue3
新个性次要包含:Composition API
、SFC Composition API
语法糖、Teleport
传送门、Fragments
片段、Emits
选项、自定义渲染器、SFC CSS
变量、Suspense
- 另外,
Vue3.0
在框架层面也有很多亮眼的改良: -
更快
- 虚构
DOM
重写,diff
算法优化 - 编译器优化:动态晋升、
patchFlags(动态标记)
、事件监听缓存 - 基于
Proxy
的响应式零碎 SSR
优化
- 虚构
- 更小 :更好的摇树优化
tree shaking
、Vue3
移除一些不罕用的API
- 更敌对 :
vue3
在兼顾vue2
的options API
的同时还推出了composition API
,大大增加了代码的逻辑组织和代码复用能力 - 更容易保护:
TypeScript
+ 模块化 -
更容易扩大
- 独立的响应化模块
- 自定义渲染器
你有对 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 查找性能瓶颈
vue 的长处
轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十 kb;
简略易学:国人开发,中文文档,不存在语言障碍,易于了解和学习;
双向数据绑定:保留了 angular 的特点,在数据操作方面更为简略;
组件化:保留了 react 的长处,实现了 html 的封装和重用,在构建单页面利用方面有着独特的劣势;
视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;
虚构 DOM:dom 操作是十分消耗性能的,不再应用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种形式;
运行速度更快: 相比拟与 react 而言,同样是操作虚构 dom,就性能而言,vue 存在很大的劣势。
Vue 组件间通信有哪几种形式?
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。
(1)props / $emit
实用 父子组件通信
这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。
(2)ref
与 $parent / $children
实用 父子组件通信
ref
:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例$parent
/$children
:拜访父 / 子实例
(3)EventBus($emit / $on)
实用于 父子、隔代、兄弟组件通信
这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。
(4)$attrs
/$listeners
实用于 隔代组件通信
$attrs
:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (class 和 style 除外)。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 (class 和 style 除外),并且能够通过v-bind="$attrs"
传入外部组件。通常配合 inheritAttrs 选项一起应用。$listeners
:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过v-on="$listeners"
传入外部组件
(5)provide / inject
实用于 隔代组件通信
先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。
(6)Vuex 实用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state)。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
- 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。
Vue 组件如何通信?
Vue 组件通信的办法如下:
props/$emit+v-on
: 通过 props 将数据自上而下传递,而通过 $emit 和 v -on 来向上传递信息。- EventBus: 通过 EventBus 进行信息的公布与订阅
- vuex: 是全局数据管理库,能够通过 vuex 治理全局的数据流
$attrs/$listeners
: Vue2.4 中退出的$attrs/$listeners
能够进行跨级的组件通信- provide/inject:以容许一个先人组件向其所有子孙后代注入一个依赖,不管组件档次有多深,并在起上下游关系成立的工夫里始终失效,这成为了跨组件通信的根底
还有一些用 solt 插槽或者 ref 实例进行通信的,应用场景过于无限就不赘述了。
在 Vue 中应用插件的步骤
- 采纳
ES6
的import ... from ...
语法或CommonJS
的require()
办法引入插件 - 应用全局办法
Vue.use(plugin)
应用插件, 能够传入一个选项对象Vue.use(MyPlugin, { someOption: true})
Vue 我的项目本地开发实现后部署到服务器后报 404 是什么起因呢
如何部署
前后端拆散开发模式下,前后端是独立布署的,前端只须要将最初的构建物上传至指标服务器的 web
容器指定的动态目录下即可
咱们晓得 vue
我的项目在构建后,是生成一系列的动态文件
惯例布署咱们只须要将这个目录上传至指标服务器即可
让 web
容器跑起来,以 nginx
为例
server {
listen 80;
server_name www.xxx.com;
location / {index /data/dist/index.html;}
}
配置实现记得重启nginx
// 查看配置是否正确
nginx -t
// 平滑重启
nginx -s reload
操作完后就能够在浏览器输出域名进行拜访了
当然下面只是提到最简略也是最间接的一种布署形式
什么自动化,镜像,容器,流水线布署,实质也是将这套逻辑形象,隔离,用程序来代替重复性的劳动,本文不开展
404 问题
这是一个经典的问题,置信很多同学都有遇到过,那么你晓得其真正的起因吗?
咱们先还原一下场景:
vue
我的项目在本地时运行失常,但部署到服务器中,刷新页面,呈现了 404 谬误
先定位一下,HTTP 404 谬误意味着链接指向的资源不存在
问题在于为什么不存在?且为什么只有 history
模式下会呈现这个问题?
为什么 history 模式下有问题
Vue
是属于单页利用(single-page application)
而 SPA
是一种网络应用程序或网站的模型,所有用户交互是通过动静重写以后页面,后面咱们也看到了,不论咱们利用有多少页面,构建物都只会产出一个index.html
当初,咱们回头来看一下咱们的 nginx
配置
server {
listen 80;
server_name www.xxx.com;
location / {index /data/dist/index.html;}
}
能够依据 nginx
配置得出,当咱们在地址栏输出 www.xxx.com
时,这时会关上咱们 dist
目录下的 index.html
文件,而后咱们在跳转路由进入到 www.xxx.com/login
要害在这里,当咱们在 website.com/login
页执行刷新操作,nginx location
是没有相干配置的,所以就会呈现 404
的状况
为什么 hash 模式下没有问题
router hash
模式咱们都晓得是用符号 #示意的,如 website.com/#/login
, hash
的值为 #/login
它的特点在于:hash
尽管呈现在 URL
中,但不会被包含在 HTTP
申请中,对服务端齐全没有影响,因而扭转 hash
不会从新加载页面
hash
模式下,仅 hash
符号之前的内容会被蕴含在申请中,如 website.com/#/login
只有 website.com
会被蕴含在申请中,因而对于服务端来说,即便没有配置 location
,也不会返回404
谬误
解决方案
看到这里我置信大部分同学都能想到怎么解决问题了,
产生问题的实质是因为咱们的路由是通过 JS 来执行视图切换的,
当咱们进入到子路由时刷新页面,web
容器没有绝对应的页面此时会呈现404
所以咱们只须要配置将任意页面都重定向到 index.html
,把路由交由前端解决
对 nginx
配置文件 .conf
批改,增加try_files $uri $uri/ /index.html;
server {
listen 80;
server_name www.xxx.com;
location / {
index /data/dist/index.html;
try_files $uri $uri/ /index.html;
}
}
批改完配置文件后记得配置的更新
nginx -s reload
这么做当前,你的服务器就不再返回 404 谬误页面,因为对于所有门路都会返回 index.html
文件
为了防止这种状况,你应该在 Vue
利用外面笼罩所有的路由状况,而后在给出一个 404 页面
const router = new VueRouter({
mode: 'history',
routes: [{ path: '*', component: NotFoundComponent}
]
})
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 menu
meta: {
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 cookie
NProgress.configure({showSpinner: false})// NProgress Configuration
// permission judge function
function hasPermission(roles, permissionRoles) {if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
if (!permissionRoles) return true
return roles.some(role => permissionRoles.indexOf(role) >= 0)
}
const whiteList = ['/login', '/authredirect']// no redirect whitelist
router.beforeEach((to, from, next) => {NProgress.start() // start progress bar
if (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_info
const 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 @click='editClick' type="primary" v-has> 编辑 </el-button>
小结
对于权限如何抉择哪种适合的计划,能够依据本人我的项目的计划我的项目,如思考路由与菜单是否拆散
权限须要前后端联合,前端尽可能的去管制,更多的须要后盾判断
你感觉 vuex 有什么毛病
剖析
相较于 redux
,vuex
曾经相当简便好用了。但模块的应用比拟繁琐,对 ts
反对也不好。
体验
应用模块:用起来比拟繁琐,应用模式也不对立,基本上得不到类型零碎的任何反对
const store = createStore({
modules: {a: moduleA}
})
store.state.a // -> 要带上 moduleA 的 key,内嵌模块的话会很长,不得不配合 mapState 应用
store.getters.c // -> moduleA 里的 getters,没有 namespaced 时又变成了全局的
store.getters['a/c'] // -> 有 namespaced 时要加 path,应用模式又和 state 不一样
store.commit('d') // -> 没有 namespaced 时变成了全局的,能同时触发多个子模块中同名 mutation
store.commit('a/d') // -> 有 namespaced 时要加 path,配合 mapMutations 应用感觉也没简化
答复范例
vuex
利用响应式,应用起来曾经相当方便快捷了。然而在应用过程中感觉模块化这一块做的过于简单,用的时候容易出错,还要常常查看文档- 比方:拜访
state
时要带上模块key
,内嵌模块的话会很长,不得不配合mapState
应用,加不加namespaced
区别也很大,getters
,mutations
,actions
这些默认是全局,加上之后必须用字符串类型的 path 来匹配,应用模式不对立,容易出错;对 ts 的反对也不敌对,在应用模块时没有代码提醒。 - 之前
Vue2
我的项目中用过vuex-module-decorators
的解决方案,尽管类型反对上有所改善,但又要学一套新货色,减少了学习老本。pinia
呈现之后应用体验好了很多,Vue3 + pinia
会是更好的组合
原理
上面咱们来看看 vuex
中store.state.x.y
这种嵌套的门路是怎么搞进去的
首先是子模块装置过程:父模块状态
parentState
下面设置了子模块名称moduleName
,值为以后模块state
对象。放在下面的例子中相当于:store.state['x'] = moduleX.state
。此过程是递归的,那么store.state.x.y
装置时就是:store.state['x']['y'] = moduleY.state
// 源码地位 https://github1s.com/vuejs/vuex/blob/HEAD/src/store-util.js#L102-L115
if (!isRoot && !hot) {
// 获取父模块 state
const parentState = getNestedState(rootState, path.slice(0, -1))
// 获取子模块名称
const moduleName = path[path.length - 1]
store._withCommit(() => {
// 把子模块 state 设置到父模块上
parentState[moduleName] = module.state
})
}
你是怎么解决 vue 我的项目中的谬误的?
剖析
- 这是一个综合利用题目,在我的项目中咱们经常须要将 App 的异样上报,此时错误处理就很重要了。
- 这里要辨别谬误的类型,针对性做收集。
- 而后是将收集的的错误信息上报服务器。
思路
- 首先辨别谬误类型
- 依据谬误不同类型做相应收集
- 收集的谬误是如何上报服务器的
答复范例
- 利用中的谬误类型分为 ”
接口异样
“ 和“代码逻辑异样
” - 咱们须要依据不同谬误类型做相应解决:接口异样是咱们申请后端接口过程中产生的异样,可能是申请失败,也可能是申请取得了服务器响应,然而返回的是谬误状态。以
Axios
为例,这类异样咱们能够通过封装Axios
,在拦截器中对立解决整个利用中申请的谬误。代码逻辑异样
是咱们编写的前端代码中存在逻辑上的谬误造成的异样,vue
利用中最常见的形式是应用全局谬误处理函数app.config.errorHandler
收集谬误 - 收集到谬误之后,须要对立解决这些异样:剖析谬误,获取须要错误信息和数据。这里应该无效辨别谬误类型,如果是申请谬误,须要上报接口信息,参数,状态码等;对于前端逻辑异样,获取谬误名称和详情即可。另外还能够收集利用名称、环境、版本、用户信息,所在页面等。这些信息能够通过
vuex
存储的全局状态和路由信息获取
实际
axios
拦截器中解决捕捉异样:
// 响应拦截器
instance.interceptors.response.use((response) => {return response.data;},
(error) => {
// 存在 response 阐明服务器有响应
if (error.response) {
let response = error.response;
if (response.status >= 400) {handleError(response);
}
} else {handleError(null);
}
return Promise.reject(error);
},
);
vue
中全局捕捉异样:
import {createApp} from 'vue'
const app = createApp(...)
app.config.errorHandler = (err, instance, info) => {// report error to tracking services}
解决接口申请谬误:
function handleError(error, type) {if(type == 1) {
// 接口谬误,从 config 字段中获取申请信息
let {url, method, params, data} = error.config
let err_data = {
url, method,
params: {query: params, body: data},
error: error.data?.message || JSON.stringify(error.data),
})
}
}
解决前端逻辑谬误:
function handleError(error, type) {if(type == 2) {
let errData = null
// 逻辑谬误
if(error instanceof Error) {let { name, message} = error
errData = {
type: name,
error: message
}
} else {
errData = {
type: 'other',
error: JSON.strigify(error)
}
}
}
}
Vue 模版编译原理晓得吗,能简略说一下吗?
简略说,Vue 的编译过程就是将 template
转化为 render
函数的过程。会经验以下阶段:
- 生成 AST 树
- 优化
- codegen
首先解析模版,生成AST 语法树
(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是 将优化后的 AST 树转换为可执行的代码
。
vue-router 路由钩子函数是什么 执行程序是什么
路由钩子的执行流程, 钩子函数品种有: 全局守卫、路由守卫、组件守卫
残缺的导航解析流程:
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创立好的组件实例会作为回调函数的参数传入。
Vue-router 路由有哪些模式?
个别有两种模式:
(1)**hash 模式 **:前面的 hash 值的变动,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变动会触发 hashchange 事件。(2)**history 模式 **:利用了 HTML5 中新增的 pushState() 和 replaceState() 办法。这两个办法利用于浏览器的历史记录栈,在以后已有的 back、forward、go 的根底之上,它们提供了对历史记录进行批改的性能。只是当它们执行批改时,尽管扭转了以后的 URL,但浏览器不会立刻向后端发送申请。
Vue 中 computed 和 watch 有什么区别?
计算属性 computed:
(1)** 反对缓存 **,只有依赖数据发生变化时,才会从新进行计算函数;(2)计算属性内 ** 不反对异步操作 **;(3)计算属性的函数中 ** 都有一个 get**(默认具备,获取计算属性)** 和 set**(手动增加,设置计算属性)办法;(4)计算属性是主动监听依赖值的变动,从而动静返回内容。
侦听属性 watch:
(1)** 不反对缓存 **,只有数据发生变化,就会执行侦听函数;(2)侦听属性内 ** 反对异步操作 **;(3)侦听属性的值 ** 能够是一个对象,接管 handler 回调,deep,immediate 三个属性 **;(3)监听是一个过程,在监听的值变动时,能够触发一个回调,并 ** 做一些其余事件 **。