@easytool/react-permission
Version:
Easy to handle react component permission.
355 lines (288 loc) • 11.2 kB
JavaScript
import React, { useContext, useState, useEffect, Children } from 'react';
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import _typeof from '@babel/runtime/helpers/typeof';
import PropTypes from 'prop-types';
var PermissionContext = /*#__PURE__*/React.createContext(null);
/**
* @desc 封装了一些项目常用方法.
*/
// 内部函数, 用于判断对象类型
function _getClass(object) {
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
}
function isArray(obj) {
return _getClass(obj).toLowerCase() === 'array';
}
function isString(obj) {
return _getClass(obj).toLowerCase() === 'string';
}
function isObject(obj) {
return _getClass(obj).toLowerCase() === 'object';
}
function isNumber(obj) {
return _getClass(obj).toLowerCase() === 'number';
}
function isPromise(obj) {
return !!obj && (_typeof(obj) === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
/**
* @desc 判断参数是否为空, 包括null, undefined, [], '', {}
* @param {object} obj 需判断的对象
*/
function isEmpty(obj) {
var empty = false;
if (obj === null || obj === undefined) {
// null and undefined
empty = true;
} else if ((isArray(obj) || isString(obj)) && obj.length === 0) {
empty = true;
} else if (isObject(obj)) {
var hasProp = false;
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
hasProp = true;
break;
}
}
if (!hasProp) {
empty = true;
}
} else if (isNumber(obj) && isNaN(obj)) {
empty = true;
}
return empty;
}
function trim(value) {
if (isString(value)) {
return value.trim();
}
return value;
}
/**
* COPY from https://github1s.com/facebook/react/blob/HEAD/fixtures/legacy-jsx-runtimes/react-16/cjs/react-jsx-runtime.development.js
* 参考 https://stackoverflow.com/questions/33199959/how-to-detect-a-react-component-vs-a-react-element
*/
// ATTENTION
// When adding new symbols to this file,
// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
// nor polyfill, then a plain number is used for performance.
// 老版本(兼容性处理)
var REACT_ELEMENT_TYPE = 0xeac7;
if (typeof Symbol === 'function' && Symbol.for) {
var symbolFor = Symbol.for;
REACT_ELEMENT_TYPE = symbolFor('react.element');
symbolFor('react.portal');
symbolFor('react.fragment');
symbolFor('react.strict_mode');
symbolFor('react.profiler');
symbolFor('react.provider');
symbolFor('react.context');
symbolFor('react.forward_ref');
symbolFor('react.suspense');
symbolFor('react.suspense_list');
symbolFor('react.memo');
symbolFor('react.lazy');
symbolFor('react.block');
symbolFor('react.server.block');
symbolFor('react.fundamental');
symbolFor('react.scope');
symbolFor('react.opaque.id');
symbolFor('react.debug_trace_mode');
symbolFor('react.offscreen');
symbolFor('react.legacy_hidden');
}
/*
$$typeof: Symbol(react.element)
*/
function isReactElement(node) {
return /*#__PURE__*/React.isValidElement(node) && (node === null || node === void 0 ? void 0 : node.$$typeof) === REACT_ELEMENT_TYPE;
}
function formatPermission(value) {
var permissions = [];
if (isArray(value)) {
permissions = value;
} else if (isNumber(value)) {
permissions.push(value);
} else if (isString(value)) {
if (value.includes(',')) {
// TODO: 移除开头和结尾的 ","
permissions = value.split(',');
} else {
permissions.push(value);
}
}
return permissions;
}
function generateKey(element) {
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
// var key = '';
// if (isReactDOMElement(element)) {
// key = element.type;
// } else if (isReactComponentElement(element)) {
// key = element.type.name;
// }
// TODO: key有点多余, 先这样吧
return "permission__".concat(index);
}
function comparePermission() {
var elementPermission = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var hasPermission = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
if (elementPermission.length === 0) {
return true;
}
if (elementPermission.length > hasPermission.length) {
return false;
}
var _loop = function _loop(i) {
var requiredPermission = elementPermission[i]; // Compare permission
var allow = hasPermission.some(function (userPermission) {
return trim(requiredPermission) == trim(userPermission);
});
if (!allow) {
return {
v: false
};
}
};
for (var i = 0; i < elementPermission.length; i++) {
var _ret = _loop(i);
if (_typeof(_ret) === "object") return _ret.v;
}
return true;
} // 递归遍历 Virtual Tree, 首次是 Permission.props.children, 然后根据用户自定义来
function filterChildren(element, hasPermission, props) {
var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
if (!element) {
return null;
} // isReactDOMElement: DOMElement.
// isReactComponent: classComponent, functionComponent, portal.
// isReactWrapper: fragment, suspense.
// isReactHOC: memo, lazy, forward_ref.
// if (isReactDOMElement(element) || isReactComponent(element) || isReactWrapper(element) || isReactHOC(element)) {
if (isReactElement(element)) {
if (checkElementPermission(element, hasPermission, props)) {
// TODO: element没有子元素, 说明是通过 props 传递的.
var children = element.props.children;
if (Children.count(children) === 0) {
return element;
}
var validChildren = filterChildren(children, hasPermission, props, index);
var newChildren = handleChildren(validChildren, children); // cloneElement(element, props, children), 第二个, 第三个参数用于覆盖拷贝的 element 属性, 如果不输入默认使用原 element 的.
// key and ref from the original element will be preserved.
var newElement = /*#__PURE__*/React.cloneElement(element, {
key: element.key || generateKey(element, index)
}, newChildren); // 返回权限过滤后的元素.
return newElement;
} // 如果用户权限还未加载完成, 默认隐藏所有元素不显示, 不执行 onDeny 方法
// isEmpty(permission) && isPromise(hasPermission) 表示当前用户权限为 pending 状态
if (isEmpty(hasPermission) && isPromise(props.hasPermission)) {
return null;
}
var onDeny = getPropertyValueByNames(element, ['onDeny', 'deny', 'data-ondeny', 'data-deny']);
return handleDeny(element, onDeny || props.onDeny, index); // 处理 Array
} else if (isArray(element)) {
var _validChildren = []; // 有效的子元素
// 这里筛选出有效的子元素
Children.forEach(element, function (child, _index) {
// checkedChild 已校验过的子元素
var checkedChild = filterChildren(child, hasPermission, props, _index);
checkedChild && _validChildren.push(checkedChild);
});
return _validChildren;
} // TODO: 其他元素类型暴露方法让用户自己处理, 默认不处理
return element;
}
function checkElementPermission(element, hasPermission, props) {
var comparePermission = props.comparePermission;
var elementPermission = getPropertyValueByNames(element, ['permission', 'permissions', 'data-permission', 'data-permissions']); // 元素需要的权限, 空的表示不需要权限
if (isEmpty(elementPermission)) {
return true;
} // 用户的权限, 空的表示没有权限或还没获取到权限
if (isEmpty(hasPermission)) {
return false;
}
return comparePermission(formatPermission(elementPermission), formatPermission(hasPermission));
}
function getPropertyValueByNames() {
var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var names = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
for (var i = 0; i < names.length; i++) {
var value = element.props[names[i]]; // TODO: 方法优化
if (value || value === 0) {
return value;
}
}
return null;
}
function handleChildren(newChildren, oldChildren) {
// children 为数组时 react会检测 key 是否为空, 为空会报警告.
if (!newChildren || newChildren.length === 0) {
newChildren = null; // 确保 newChildren 和 children 数据类型保持一致(object 或 array), 如果类型被修改,
// 会导致 react 以为子组件被替换了, 将卸载已 render 的子组件(componentWillUnmount)并且重新渲染新的子组件(componentDidMount).
} else if (newChildren.length === 1 && oldChildren.length === 1) {
newChildren = newChildren[0];
}
return newChildren;
}
function handleDeny(element, onDeny, index) {
if (!onDeny) {
return null;
}
if ( /*#__PURE__*/React.isValidElement(onDeny)) {
return onDeny;
}
if (typeof onDeny === 'function') {
// TODO: 这里可能会有性能问题, 如果 onDeny 方法直接执行页面跳转, 可能来不及清除内存占用.
var newElement = onDeny(element, index); // 返回值类型必须是有效的React元素
if ( /*#__PURE__*/React.isValidElement(newElement)) {
return newElement;
} // 非 null, undefined, 0
if (newElement) {
throw 'onDeny return a invalid react element.';
}
}
return null;
}
function render(props, permission) {
var children = props.children,
hasPermission = props.hasPermission,
onLoad = props.onLoad; // isEmpty(permission) && isPromise(hasPermission) 表示当前用户权限为 pending 状态
if (isEmpty(permission) && isPromise(hasPermission) && /*#__PURE__*/React.isValidElement(onLoad)) {
return onLoad;
}
var newChildren = filterChildren(children, permission, props);
return newChildren;
}
function Permission(_props) {
var props = Object.assign({
hasPermission: null,
comparePermission: comparePermission,
onLoad: null,
onDeny: null,
onError: null
}, useContext(PermissionContext), _props); // TODO: 增加状态 pending, fulfill 状态.
var _useState = useState(isPromise(props.hasPermission) ? null : props.hasPermission),
_useState2 = _slicedToArray(_useState, 2),
hasPermission = _useState2[0],
setHasPermission = _useState2[1];
useEffect(function () {
if (isPromise(props.hasPermission)) {
props.hasPermission.then(function (permission) {
return setHasPermission(permission);
}, props.onError);
} else {
setHasPermission(props.hasPermission);
}
}, [props.hasPermission]);
return render(props, hasPermission);
}
Permission.propTypes = {
hasPermission: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.func, PropTypes.object]),
comparePermission: PropTypes.func,
onLoad: PropTypes.object,
onDeny: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
onError: PropTypes.func
};
export default Permission;
export { PermissionContext };