你还在用指令实现权限控制?

144 阅读3分钟

框架: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)

image.png
这运行结果显然不是我们想象的那样,有可能是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 将无法再次正确更改它)

image.png

怎么办?

经过踩坑后不敢用自定义指令去做权限控制了,那应该用什么方式替代自定义指令呢?毕竟指令又清晰又方便使用,但是路子就几个,我决定使用组件的方式去做。

image.png

用组件实现权限控制

一般来说组件都是有内容的,但是我这个组件没有内容,只做逻辑,代码也很简单,组件接受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>

还有话说

可能有人觉得组件的方式没有指令的方式来的优雅,把指令改成组件之后代码多了一点。这确实是实话,但是这个组件也有他的优点:

  1. 避免直接操作 dom 节点,运行更稳定🐶。
  2. 如果两个相邻的节点使用同一个权限,v-auth要写两次或者用一个 div 包裹使用,但是permission组件可以包裹多个使用同一个权限的节点而不增加多余的节点👍。
  3. 指令一般用于操作 dom 的场景,但是组件适用于很多复杂的场景。假如有一天老板让你没有权限的节点需要显示“申请xx权限”的按钮,点击发送请求申请xx权限,这时候组件就很好用啦。(这个需求我瞎说的🙈)

最后

解决问题的方法不止一种,如果大家有更好的想法欢迎提出~