Before the words
The business system with user system often needs the ability of permission control. In the development mode, the permission control is mainly realized by the backend combined with the template engine, while in the development mode, the permission configuration is negotiated by the front and back ends for data permission control and component permission control respectively.
The body of the
The permission configuration format is not limited. For the following demonstration, the permission configuration is set as follows:
export type IAuthConfig = {
/** Permission identifier list */
keys: string[];
};
Copy the code
And provide a Hook to directly obtain the current user information:
export type IUser = {
/** Permission configuration */
authConfig: IAuthConfig;
};
/** user info Hook */
export function useUser() {
/ /... slightly
return user;
}
Copy the code
First, we define a permission Hook, the content is as follows:
/** * converts to permission identifier array *@param Auth Permission identifier *@returns* /
function getAuthKeys(auth: string | string[]) {
return Array.isArray(auth) ? auth : [auth];
}
/** Permission authentication type */
export enum EAuthType {
/** or permission (at least one match) */
"some" = "some"./** With permissions (all matches) */
"every" = "every",}/** permission Hook */
export function useAuth() {
const { authConfig } = useUser();
// List of permissions
const authKeys = useMemo(() = >authConfig? .keys ?? [], [authConfig]);// Verify whether you have permission
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;
}
Copy the code
1. Control mode
For front-end development generally only need to do component control, there are many ways to write control.
1.1 Direct Calculation
const Cmpt: React.FC = () = > {
const [, hasAuth] = useAuth();
// Calculate whether permissions are granted
const authorized = useMemo(() = > hasAuth("test"), [hasAuth]);
/ /... slightly
};
Copy the code
1.2 General permission 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();
constinstance = React.createElement(Component, { ... props, });// Calculate whether permissions are granted
const authorized = hasAuth(auth, type);
/ /... slightly
};
WrappedComponent.displayName = `withAuth(${getDisplayName(Component)}) `;
return WrappedComponent;
};
}
Copy the code
1.3 Permission Package Components
const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) = > {
const [, hasAuth] = useAuth();
// Calculate whether permissions are granted
const authorized = useMemo(() = > hasAuth(auth, type), [auth, type, hasAuth]);
/ /... slightly
};
Copy the code
2. Control the outcome
Common control results are to control the display or hiding of components, or to do custom rendering of components depending on whether permissions are available.
For the convenience of the demonstration, the permission package component is used for the explanation.
2.1 Explicit and implicit control
Components with permissions are rendered directly, otherwise null is returned, and explicit and implicit control can be implemented. The permission package component is implemented as follows:
const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) = > {
const [, hasAuth] = useAuth();
// Calculate whether permissions are granted
const authorized = useMemo(() = > hasAuth(auth, type), [auth, type, hasAuth]);
// Components with permissions are rendered directly, otherwise null is returned
return authorized ? children : null;
};
Copy the code
Just wrap the permission component around the component that requires permission control.
const Cmpt: React.FC = () = > {
<>
<AuthWrapper auth="test">
<Button>Hello World</Button>
</AuthWrapper>
</>;
};
Copy the code
2.2 Custom Rendering
In actual business scenarios, users need to be reminded that they do not have permissions. In this case, the permission package component needs to support custom rendering, which can be implemented in various ways:
- Static props injection
- Dynamic props mapping
- render props
In contrast, the render props are more free. After completing the permission package component, the implementation is as follows:
type IProps = {
/** Permission identifier */
auth: string | string[];
/** Authentication type */
type? : EAuthType;/** subcontent */
};
const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) = > {
const [, hasAuth] = useAuth();
// Calculate whether permissions are granted
const authorized = useMemo(() = > hasAuth(auth, type), [auth, type, hasAuth]);
// Custom render
if (typeof children === "function") {
return children(authorized);
}
// explicit and implicit control
return authorized ? children : null;
};
Copy the code
It is now possible to render a prompt for an unauthorized component:
const Cmpt: React.FC = () = > {
<>
<AuthWrapper auth="test">
{(authorized) => <Button disabled={! authorized}>Hello World</Button>}
</AuthWrapper>
</>;
};
Copy the code
3. Permission data
The granularity of front-end development component control depends on the type of permission data, which can be divided into static permission and dynamic permission.
Static rights can be obtained once login authentication is complete, while dynamic rights are verified during data operation.
It is not hard to see static permissions used to control routing, pages, or crude operation permissions. On the other hand, dynamic rights can control the operation rights of dynamic data in finer granularity (with the permission control capability of back-end data).
3.1 Static Rights
As described earlier, after login authentication, you can obtain the permission id from the user information. The previous chestnut is also a static permission example.
3.2 Dynamic Rights
Dynamic permission verification is also common in service systems. For example, users can view list data but cannot view details about unauthorized data.
It can be seen that for users, the acquired dynamic list data needs to be verified one by one. At this time, we transform the permission Hook and package components, and the code after transformation is as follows:
type IAuthDynamicConfig = {
/ /... slightly
};
/** permission Hook */
export function useAuth() {
/ /... slightly
// Dynamic check whether permissions are available
const dynamicAuth = useCallback(
(auth: string | string[].type? : EAuthType, config? : IAuthDynamicConfig) = >
// Batch asynchronous permission verification is omitted
append2DynamicAuthTask(auth, type, config),
[]
);
const ret: [typeof authKeys, typeof hasAuth, typeof dynamicAuth] = [
authKeys,
hasAuth,
dynamicAuth,
];
return ret;
}
/** Permission package component */
const AuthWrapper: React.FC<IProps> = ({ auth, type, config, children }) = > {
const [, hasAuth, dynamicAuth] = useAuth();
const [authorized, setAuthorized] = useState(false);
// Calculate whether permissions are granted
useEffect(() = > {
if (config === undefined) {
setAuthorized(hasAuth(auth, type));
return;
}
dynamicAuth(auth, type, config).then(setAuthorized);
}, [auth, type, config, hasAuth, dynamicAuth]);
/ /... slightly
};
Copy the code
The usage is as follows:
const Cmpt: React.FC = () = > {
/ /... slightly
<>
{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}>details</Button>
)}
</AuthWrapper>
</div>
</div>
))}
</>;
};
Copy the code