框架:vue 2.+
相信大家很多人在实现权限控制的时候都有参考网上写的那种v-permission自定义指令的写法。我也是,大概是下面这么写的。这段代码很短很清晰,大概意思就是如果发现这个节点不满足权限的话直接将该节点移除。
export default {
inserted(el, binding, vNode) {
filterNodePermission(el, binding, vNode)
},
componentUpdated(el, binding, vNode) {
filterNodePermission(el, binding, vNode)
}
}
function filterNodePermission(el, binding, vNode) {
const { value } = binding
const hasAuth = checkAuth(value)
if (!hasAuth) {
el.parentNode && el.parentNode.removeChild(el)
}
}
自定义指令踩坑
这段代码乍一看没什么问题,但是在实际使用中却出现了一些问题。
复现问题代码片段
<template>
<div class="container">
<div>
<el-button v-auth="'auth1'">1</el-button>
<el-button v-auth="'auth2'">2</el-button>
<template v-if="!status">
<el-button v-auth="'auth3'">3</el-button>
<el-button v-auth="'auth4'">4</el-button>
</template>
<template v-if="status">
<el-button v-auth="'auth5'">5</el-button>
<el-button v-auth="'auth6'">6</el-button>
</template>
</div>
</div>
</template>
<script>
export default {
data() {
return {
status: true
}
},
mounted() {
this.status = false
}
}
</script>
假设只有拥有 auth1 和 auth3 两个权限,运行界面应该出现 按钮1 和 按钮3。实际的运行结果如下:(只出现了按钮1)
这运行结果显然不是我们想象的那样,有可能是v-auth直接移除节点干扰了 vue dom diff 比对的结果。在 v-if 和 v-auth 一起使用的时候比较明显,github 上也有相关的 issues #11521,当然他的代码跟我的稍微有些不一样,但是同样表达出一个问题就是自定义指令直接移除节点并不稳定。看看对应 issues 下的回答:You shouldn't manually change the DOM that is controlled by Vue. Otherwise Vue won't be able to correctly change it again(你不应该手动更改由 Vue 控制的 DOM,否则 Vue 将无法再次正确更改它)
怎么办?
经过踩坑后不敢用自定义指令去做权限控制了,那应该用什么方式替代自定义指令呢?毕竟指令又清晰又方便使用,但是路子就几个,我决定使用组件的方式去做。
用组件实现权限控制
一般来说组件都是有内容的,但是我这个组件没有内容,只做逻辑,代码也很简单,组件接受auth参数,render方法里面判断权限,如果权限校验通过则渲染组件默认slot的内容,否则渲染一个空串。看下图。
export default {
name: 'Permission',
functional: true,
inheritAttrs: true,
props: {
auth: {
type: String,
default: ''
}
},
render(createElement, context) {
const { auth } = context.props
const hasAuth = auth ? checkAuth(auth) : true
return (
hasAuth ? context.slots().default : ''
)
}
}
改一下问题代码片段
运行结果是正确的 按钮1 和 按钮3。
<template>
<div class="container">
<div>
<permission auth="auth1">
<el-button>1</el-button>
</permission>
<permission auth="auth2">
<el-button>2</el-button>
</permission>
<template v-if="!status">
<permission auth="auth3">
<el-button>3</el-button>
</permission>
<permission auth="auth4">
<el-button>4</el-button>
</permission>
</template>
<template v-if="status">
<permission auth="auth3">
<el-button>5</el-button>
</permission>
<permission auth="auth4">
<el-button>6</el-button>
</permission>
</template>
</div>
</div>
</template>
<script>
export default {
data() {
return {
status: true
}
},
mounted() {
this.status = false
}
}
</script>
还有话说
可能有人觉得组件的方式没有指令的方式来的优雅,把指令改成组件之后代码多了一点。这确实是实话,但是这个组件也有他的优点:
- 避免直接操作 dom 节点,运行更稳定🐶。
- 如果两个相邻的节点使用同一个权限,
v-auth要写两次或者用一个 div 包裹使用,但是permission组件可以包裹多个使用同一个权限的节点而不增加多余的节点👍。 - 指令一般用于操作 dom 的场景,但是组件适用于很多复杂的场景。假如有一天老板让你没有权限的节点需要显示“申请xx权限”的按钮,点击发送请求申请xx权限,这时候组件就很好用啦。(这个需求我瞎说的🙈)
最后
解决问题的方法不止一种,如果大家有更好的想法欢迎提出~