React 使用Polymorphic Component实现细粒度权限校验

背景

都2202年了,前端工程师们都纷纷开始卷起来了,最近在学了vue2和vue3后还想再卷一点,就继续学习react,边学边尝试搭建一个通用的后台管理的模板框架。其中涉及到细粒度的按钮权限设计,于是就有了下文。

传统的权限校验

在vue中,对于按钮级别的权限校验,可以简单的封装一个v-permission的指令实现按钮的权限控制,但是在react中好像没有指令只一个概念,所以指令是行不通的。传统react的权限校验大多都是在jsx中判断:

{hasAuth ? (<button>权限按钮</button>) : null}

这样的写法简介明了,但是这样的话对于每一个按钮,都要写一遍这一长串的东西,工作量增加不说,看起来也比较冗余。所以衍生出了高阶组件。

基于高阶组件的权限校验

使用高阶组件将原来的组件包裹起来达到权限校验的目的:

// AuthBtn
import React from 'react';
import { Button } from 'antd';

export interface AuthBtnProps {
    auth?: string
}

const AuthBtn: React.FC<AuthBtnProps> = (props) => {
  let { auth, children } = props;
  // btnIds 应该有后台接口返回,告诉前端用户有哪些按钮权限
  let btnIds = ['read', 'edit'];
  let hasAuth = btnIds.includes(auth);
  // 这里可以根据实际需求封装
  return <>{hasAuth ? (<Button>{children}</Button>) : null}</>
};
export default AuthBtn;

这样就可以实现按钮的权限控制,但是新的问题又出现了,这里只是对于Button组件的封装,如果是其他展示组件呢。每一个组件都有一层包裹,那就要分别对多个组件封装。远远达不到我们预期的要求,那么能不能实现一次逻辑,对所有组件都适用呢。

基于多态的高阶组件权限校验

基于上一个问题的基础上,我们烤炉,能不能将我们要渲染的组件直接通过props传递过去,所以就有了下面的实现:

import React from 'react'

export interface AuthProps<E> {
  as?: E
  fallback?: React.ReactNode
  auth?: string | boolean | (() => boolean),
  children?: React.ReactNode
}

const Auth = <E extends React.ElementType>(props: AuthProps<E>) => {
  const { as, fallback, auth, ...rest } = props
  const Component = as ?? 'span'
  let hasAuth = false

  if (auth === undefined) {
    return <Component {...rest} />
  } else if (typeof auth === 'function') {
    hasAuth = auth()
  } else if (typeof auth === 'boolean') {
    hasAuth = auth
  } else {
    // todo 自定义权限逻辑校验
    hasAuth = [''].includes(auth)
  }
  if (hasAuth) {
    return <Component {...rest} />
  }
  return <>{fallback ? fallback : null}</>
}

export default Auth

将要渲染的组件作为as参数传递过去,这样能实现我们要的功能

3

但是既然使用了typescript,那我们希望在边写组件的时候,能够根据我们as传入的值,有具体的属性提示

改造前:

3

改造后:

3

下面直接贴实现代码:

import React from 'react'

type AuthComponentProps<E extends React.ElementType> = {
  as?: E
  fallback?: React.ReactNode
  auth?: string | (() => boolean) | boolean
}

type AuthProps<E extends React.ElementType> = AuthComponentProps<E> &
  Omit<React.ComponentProps<E>, keyof AuthComponentProps<E>>

const Auth = <E extends React.ElementType>(props: AuthProps<E>) => {
  const { as, fallback, auth, ...rest } = props
  const Component = as ?? 'span'
  let hasAuth = false

  if (auth === undefined) {
    return <Component {...rest} />
  } else if (typeof auth === 'function') {
    hasAuth = auth()
  } else if (typeof auth === 'boolean') {
    hasAuth = auth
  } else {
    // todo 自定义权限逻辑校验
    hasAuth = [''].includes(auth)
  }
  if (hasAuth) {
    return <Component {...rest} />
  }
  return <>{fallback ? fallback : null}</>
}

export default Auth

实现提示功能主要是ts类型的定义,这个部分细节讲起来比较晦涩。

推荐两个学习资料:

一、www.bilibili.com/video/BV1cS…

二、github.com/kripod/reac…

以上是我自己学习的时候看的视频和项目,觉得收益很大,一下是我边学习边搭建的框架(小生BB.jpg)

三、github.com/HULANG-BTB/…

3.gif