日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

React?組件權限控制的實現_React

作者:Pwcong ? 更新時間: 2022-04-19 編程語言

前話

具備用戶體系的業務系統往往需要具備權限控制的能力。前后不分離的開發模式權限控制主要由后端結合模版引擎實現,而前后分離的開發模式由前后兩端協商權限配置分別進行數據權限控制和組件權限控制。

正文

權限配置格式不限,為進行后續演示本篇設定權限配置如下:

export type IAuthConfig = {
? /** 權限標識列表 */
? keys: string[];
};

且提供一個Hook直接獲取當前用戶信息:

export type IUser = {
? /** 權限配置 */
? authConfig: IAuthConfig;
};

/** 用戶信息Hook */
export function useUser() {
? // ...略
? return user;
}

首先我們先定義一個權限Hook,內容如下:

/**
?* 轉換為權限標識數組
?* @param auth 權限標識
?* @returns
?*/
function getAuthKeys(auth: string | string[]) {
? return Array.isArray(auth) ? auth : [auth];
}

/** 權限鑒權類型 */
export enum EAuthType {
? /** 或權限(至少匹配一個) */
? "some" = "some",
? /** 與權限(全部匹配) */
? "every" = "every",
}

/** 權限Hook */
export function useAuth() {
? const { authConfig } = useUser();

? // 權限標識列表
? const authKeys = useMemo(() => authConfig?.keys ?? [], [authConfig]);
? // 校驗是否具備權限
? const hasAuth = useCallback(
? ? (auth: string | string[], type?: EAuthType) =>
? ? ? getAuthKeys(auth)[type ?? EAuthType.every]((key) =>
? ? ? ? authKeys.includes(key)
? ? ? ),
? ? [authKeys]
? );

? const ret: [typeof authKeys, typeof hasAuth] = [authKeys, hasAuth];
? return ret;
}

1. 控制方式

對于前端開發而言一般只需做組件控制,控制方式有很多種寫法。

1.1 直接計算

const Cmpt: React.FC = () => {
? const [, hasAuth] = useAuth();
? // 計算是否有權限
? const authorized = useMemo(() => hasAuth("test"), [hasAuth]);

? // ...略
};

1.2 通用權限Hoc

export function withAuth<P extends Record<string, unknown> = {}>(
? auth: string | string[],
? type?: EAuthType
) {
? return function (Component: any) {
? ? const WrappedComponent: React.FC<P> = (props) => {
? ? ? const [, hasAuth] = useAuth();

? ? ? const instance = React.createElement(Component, {
? ? ? ? ...props,
? ? ? });

? ? ? // 計算是否有權限
? ? ? const authorized = hasAuth(auth, type);

? ? ? // ...略
? ? };

? ? WrappedComponent.displayName = `withAuth(${getDisplayName(Component)})`;

? ? return WrappedComponent;
? };
}

1.3 權限包裹組件

const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) => {
? const [, hasAuth] = useAuth();
? // 計算是否有權限
? const authorized = useMemo(() => hasAuth(auth, type), [auth, type, hasAuth]);

? // ...略
};

2. 控制結果

常見控制結果為控制組件的顯示或隱藏,或根據是否具備權限做組件的自定義渲染。

為方便演示后面統一使用權限包裹組件進行說明。

2.1 顯隱控制

具備權限的組件直接渲染,否則返回null,即可實現顯隱控制。權限包裹組件實現如下:

const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) => {
? const [, hasAuth] = useAuth();
? // 計算是否有權限
? const authorized = useMemo(() => hasAuth(auth, type), [auth, type, hasAuth]);
? // 具備權限的組件直接渲染,否則返回null
? return authorized ? children : null;
};

在需要權限控制的組件外包裹權限組件即可。

const Cmpt: React.FC = () => {
? <>
? ? <AuthWrapper auth="test">
? ? ? <Button>Hello World</Button>
? ? </AuthWrapper>
? </>;
};

2.2 自定義渲染

實際業務場景中,存在需要提醒用戶沒有權限的情況,這時需要權限包裹組件支持自定義渲染,實現方式有多種:

  • 靜態props注入
  • 動態props映射
  • render props

相比較之下render props更為自由,權限包裹組件完善后實現如下:

type IProps = {
? /** 權限標識 */
? auth: string | string[];
? /** 鑒權類型 */
? type?: EAuthType;
? /** 子內容 */
};

const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) => {
? const [, hasAuth] = useAuth();
? // 計算是否有權限
? const authorized = useMemo(() => hasAuth(auth, type), [auth, type, hasAuth]);

? // 自定義渲染
? if (typeof children === "function") {
? ? return children(authorized);
? }

? // 顯隱控制
? return authorized ? children : null;
};

這時就可以渲染提示無權限組件:

const Cmpt: React.FC = () => {
? <>
? ? <AuthWrapper auth="test">
? ? ? {(authorized) => <Button disabled={!authorized}>Hello World</Button>}
? ? </AuthWrapper>
? </>;
};

3. 權限數據

前端開發做組件控制的顆粒度取決于權限數據的類型,權限數據類型分為靜態權限和動態權限。
其中靜態權限一般在完成登錄認證后即可一次性獲取,而動態權限則在操作數據時進行權限校驗。
因此不難發現靜態權限往往用于控制路由、頁面或者粗糙的操作權限。而動態權限則能夠對動態數據進行更細粒度的操作權限控制(需后端數據權限控制能力配合)。

3.1 靜態權限

如前面描述,登錄認證后即可從用戶信息中得到權限標識,同樣前面的栗子均也為靜態權限示例。

3.2 動態權限

需要動態權限校驗的場景在業務系統中也較為常見,例如用戶能夠看到列表數據,但禁止查閱無權限的數據詳情。
由此可知對于用戶而言,獲取的動態的列表數據需要逐一進行權限校驗,這時我們對權限Hook和包裹組件進行改造,改造后代碼如下:

type IAuthDynamicConfig = {
? // ...略
};

/** 權限Hook */
export function useAuth() {
? // ...略

? // 動態校驗是否具有權限
? const dynamicAuth = useCallback(
? ? (auth: string | string[], type?: EAuthType, config?: IAuthDynamicConfig) =>
? ? ? // 批量異步校驗權限,實現略
? ? ? append2DynamicAuthTask(auth, type, config),
? ? []
? );

? const ret: [typeof authKeys, typeof hasAuth, typeof dynamicAuth] = [
? ? authKeys,
? ? hasAuth,
? ? dynamicAuth,
? ];
? return ret;
}

/** 權限包裹組件 */
const AuthWrapper: React.FC<IProps> = ({ auth, type, config, children }) => {
? const [, hasAuth, dynamicAuth] = useAuth();

? const [authorized, setAuthorized] = useState(false);

? // 計算是否有權限
? useEffect(() => {
? ? if (config === undefined) {
? ? ? setAuthorized(hasAuth(auth, type));
? ? ? return;
? ? }
? ? dynamicAuth(auth, type, config).then(setAuthorized);
? }, [auth, type, config, hasAuth, dynamicAuth]);

? // ...略
};

使用方式如下:

const Cmpt: React.FC = () => {
? // ...略

? <>
? ? {data.map((item) => (
? ? ? <div key={item.id}>
? ? ? ? <div>{item.title}</div>
? ? ? ? <div>
? ? ? ? ? <AuthWrapper
? ? ? ? ? ? auth="detail"
? ? ? ? ? ? config={{
? ? ? ? ? ? ? module: "demo",
? ? ? ? ? ? ? identify: item.id,
? ? ? ? ? ? }}
? ? ? ? ? >
? ? ? ? ? ? {(authorized) => (
? ? ? ? ? ? ? <Button disabled={!authorized} onClick={handleDetail}>
? ? ? ? ? ? ? ? 詳情
? ? ? ? ? ? ? </Button>
? ? ? ? ? ? )}
? ? ? ? ? </AuthWrapper>
? ? ? ? </div>
? ? ? </div>
? ? ))}
? </>;
};

原文鏈接:https://juejin.cn/post/7064124989699522590

欄目分類
最近更新