支持滚动加载,增加多个API

This commit is contained in:
yangchch6 2019-08-14 20:50:48 +08:00
parent 473d347f09
commit f1e4303399
16 changed files with 3119 additions and 738 deletions

View File

@ -10,6 +10,14 @@ var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _TreeNode = require('./TreeNode');
var _TreeNode2 = _interopRequireDefault(_TreeNode);
var _infiniteScroll = require('./infiniteScroll');
var _infiniteScroll2 = _interopRequireDefault(_infiniteScroll);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
@ -22,6 +30,10 @@ var _propTypes2 = _interopRequireDefault(_propTypes);
var _tinperBeeCore = require('tinper-bee-core');
var _config = require('./config');
var _config2 = _interopRequireDefault(_config);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }
@ -45,6 +57,86 @@ var Tree = function (_React$Component) {
var _this = _possibleConstructorReturn(this, _React$Component.call(this, props));
_this.handleTreeListChange = function (treeList, startIndex, endIndex) {
// 属性配置设置
var attr = {
id: 'key',
parendId: 'parentKey',
name: 'title',
rootId: null,
isLeaf: 'isLeaf'
};
var treeData = (0, _util.convertListToTree)(treeList, attr, _this.flatTreeKeysMap);
_this.startIndex = typeof startIndex !== "undefined" ? startIndex : _this.startIndex;
_this.endIndex = typeof endIndex !== "undefined" ? endIndex : _this.endIndex;
_this.setState({
treeData: treeData
});
};
_this.deepTraversal = function (treeData) {
var parentKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var isShown = arguments[2];
var expandedKeys = _this.state.expandedKeys,
flatTreeData = [],
flatTreeKeysMap = _this.flatTreeKeysMap,
dataCopy = JSON.parse(JSON.stringify(treeData));
if (Array.isArray(dataCopy)) {
for (var i = 0, l = dataCopy.length; i < l; i++) {
var key = dataCopy[i].hasOwnProperty('key') && dataCopy[i].key,
isLeaf = dataCopy[i].hasOwnProperty('children') ? false : true,
isExpanded = _this.cacheExpandedKeys ? _this.cacheExpandedKeys.indexOf(key) !== -1 : expandedKeys.indexOf(key) !== -1;
dataCopy[i].isExpanded = isExpanded;
dataCopy[i].parentKey = parentKey || null;
dataCopy[i].isShown = isShown;
dataCopy[i].isLeaf = isLeaf;
//该节点的父节点是展开状态 或 该节点是根节点
if (isShown || parentKey === null) {
flatTreeData.push(dataCopy[i]); // 取每项数据放入一个新数组
flatTreeKeysMap[key] = dataCopy[i];
}
if (Array.isArray(dataCopy[i]["children"]) && dataCopy[i]["children"].length > 0) {
// 若存在children则递归调用把数据拼接到新数组中并且删除该children
flatTreeData = flatTreeData.concat(_this.deepTraversal(dataCopy[i]["children"], key, isExpanded));
delete dataCopy[i]["children"];
}
}
} else {
flatTreeData.push(dataCopy); // 取每项数据放入一个新数组
}
return flatTreeData;
};
_this.renderTreefromData = function (data) {
var renderTitle = _this.props.renderTitle;
var loop = function loop(data) {
return data.map(function (item) {
if (item.children) {
return _react2["default"].createElement(
_TreeNode2["default"],
{ key: item.key, title: renderTitle ? renderTitle(item) : item.key, isLeaf: item.isLeaf },
loop(item.children)
);
}
return _react2["default"].createElement(_TreeNode2["default"], { key: item.key, title: renderTitle ? renderTitle(item) : item.key, isLeaf: true });
});
};
return loop(data);
};
_this.getSumHeight = function (start, end) {
var sumHeight = 0;
var span = Math.abs(end - start);
if (span) {
sumHeight = span * _config2["default"].defaultHeight;
}
return sumHeight;
};
['onKeyDown', 'onCheck', "onUlFocus", "_focusDom", "onUlMouseEnter", "onUlMouseLeave"].forEach(function (m) {
_this[m] = _this[m].bind(_this);
});
@ -58,11 +150,47 @@ var Tree = function (_React$Component) {
dragNodesKeys: '',
dragOverNodeKey: '',
dropNodeKey: '',
focusKey: '' //上下箭头选择树节点时用于标识focus状态
focusKey: '', //上下箭头选择树节点时用于标识focus状态
treeData: [], //Tree结构数组(全量)
flatTreeData: [] //一维数组(全量)
};
//默认显示20条rowsInView根据定高算的。在非固定高下这个只是一个大概的值。
_this.rowsInView = _config2["default"].defaultRowsInView;
//一次加载多少数据
_this.loadCount = _config2["default"].loadBuffer ? _this.rowsInView + _config2["default"].loadBuffer * 2 : 16;
_this.flatTreeKeysMap = {}; //存储所有 key-value 的映射,方便获取各节点信息
_this.startIndex = 0;
_this.endIndex = _this.startIndex + _this.loadCount;
_this.cacheTreeNodes = []; //缓存 treenode 节点数组
return _this;
}
Tree.prototype.componentWillMount = function componentWillMount() {
var _this2 = this;
var _props = this.props,
treeData = _props.treeData,
lazyLoad = _props.lazyLoad;
var sliceTreeList = [];
//启用懒加载,把 Tree 结构拍平,为后续动态截取数据做准备
if (lazyLoad) {
var flatTreeData = this.deepTraversal(treeData);
flatTreeData.forEach(function (element) {
if (sliceTreeList.length >= _this2.loadCount) return;
sliceTreeList.push(element);
});
this.handleTreeListChange(sliceTreeList);
this.setState({
flatTreeData: flatTreeData
});
} else {
this.setState({
treeData: treeData
});
}
};
Tree.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
var expandedKeys = this.getDefaultExpandedKeys(nextProps, true);
var checkedKeys = this.getDefaultCheckedKeys(nextProps, true);
@ -84,12 +212,29 @@ var Tree = function (_React$Component) {
if (selectedKeys) {
st.selectedKeys = selectedKeys;
}
if (nextProps.treeData !== this.props.treeData) {
this.dataChange = true;
st.treeData = treeData;
}
if (nextProps.children !== this.props.children) {
this.dataChange = true;
}
this.setState(st);
};
// componentWillUpdate(nextProps, nextState){
// const { expandedKeys,treeData } = this.state;
// if(nextState.expandedKeys !== expandedKeys) {
// this.cacheExpandedKeys = expandedKeys;
// if(this.props.lazyLoad){
// let flatTreeData = this.deepTraversal(treeData);
// this.setState({
// flatTreeData
// })
// }
// }
// }
Tree.prototype.onDragStart = function onDragStart(e, treeNode) {
this.dragNode = treeNode;
this.dragNodesKeys = this.getDragNodes(treeNode);
@ -216,7 +361,11 @@ var Tree = function (_React$Component) {
Tree.prototype.onExpand = function onExpand(treeNode, keyType) {
var _this2 = this;
var _this3 = this;
var _props2 = this.props,
treeData = _props2.treeData,
lazyLoad = _props2.lazyLoad;
var expanded = !treeNode.props.expanded;
var controlled = 'expandedKeys' in this.props;
@ -248,16 +397,26 @@ var Tree = function (_React$Component) {
if (expanded && this.props.loadData) {
return this.props.loadData(treeNode).then(function () {
if (!controlled) {
_this2.setState({
_this3.setState({
expandedKeys: expandedKeys
});
}
});
}
//收起和展开时,缓存 expandedKeys
this.cacheExpandedKeys = expandedKeys;
//启用懒加载,把 Tree 结构拍平,为后续动态截取数据做准备
if (lazyLoad) {
var flatTreeData = this.deepTraversal(treeData);
this.cacheExpandedKeys = null;
this.setState({
flatTreeData: flatTreeData
});
}
};
Tree.prototype.onCheck = function onCheck(treeNode) {
var _this3 = this;
var _this4 = this;
var checked = !treeNode.props.checked;
if (treeNode.props.halfChecked) {
@ -301,7 +460,7 @@ var Tree = function (_React$Component) {
this.treeNodesStates[treeNode.props.pos].checked = true;
var checkedPositions = [];
Object.keys(this.treeNodesStates).forEach(function (i) {
if (_this3.treeNodesStates[i].checked) {
if (_this4.treeNodesStates[i].checked) {
checkedPositions.push(i);
}
});
@ -432,6 +591,7 @@ var Tree = function (_React$Component) {
Tree.prototype.goDown = function goDown(currentPos, currentIndex, e, treeNode) {
var props = this.props;
var treeChildren = props.children ? props.children : this.cacheTreeNodes; //最终渲染在 Tree 标签中的子节点
var nextIndex = parseInt(currentIndex) + 1;
var nextPos = void 0,
@ -458,7 +618,7 @@ var Tree = function (_React$Component) {
tempPosArrLength = tempPosArr.length;
}
//选中下一个相邻的节点
(0, _util.loopAllChildren)(props.children, function (itemNode, index, pos, newKey) {
(0, _util.loopAllChildren)(treeChildren, function (itemNode, index, pos, newKey) {
if (pos == nextPos) {
nextTreeNode = itemNode;
}
@ -593,7 +753,7 @@ var Tree = function (_React$Component) {
Tree.prototype.onUlFocus = function onUlFocus(e) {
var _this4 = this;
var _this5 = this;
var targetDom = e.target;
@ -615,7 +775,7 @@ var Tree = function (_React$Component) {
var onFocusRes = onFocus && onFocus(isExist);
if (onFocusRes instanceof Promise) {
onFocusRes.then(function () {
_this4._focusDom(_this4.selectKeyDomPos, targetDom);
_this5._focusDom(_this5.selectKeyDomPos, targetDom);
});
} else {
this._focusDom(this.selectKeyDomPos, targetDom);
@ -625,12 +785,12 @@ var Tree = function (_React$Component) {
Tree.prototype.onUlMouseEnter = function onUlMouseEnter(e) {
this.isIn = true;
console.log('onUlMouseEnter----isIn-----', this.isIn);
// console.log('onUlMouseEnter----isIn-----',this.isIn);
};
Tree.prototype.onUlMouseLeave = function onUlMouseLeave(e) {
this.isIn = false;
console.log('onUlMouseLeave----isIn-----', this.isIn);
// console.log('onUlMouseLeave----isIn-----',this.isIn);
};
Tree.prototype.getFilterExpandedKeys = function getFilterExpandedKeys(props, expandKeyProp, expandAll) {
@ -752,9 +912,39 @@ var Tree = function (_React$Component) {
return filterTreeNode.call(this, treeNode);
};
/**
* 将截取后的 List 数组转换为 Tree 结构并更新 state
*/
/**
* 深度遍历 treeData把Tree数据拍平变为一维数组
* @param {*} treeData
* @param {*} parentKey 标识父节点
* @param {*} isShown 该节点是否显示在页面中当节点的父节点是展开状态 该节点是根节点时该值为 true
*/
/**
* 根据 treeData 渲染树节点
* @param data 树形结构的数组
* @param preHeight 前置占位高度
* @param sufHeight 后置占位高度
*/
/**
* @description 计算懒加载时的前置占位和后置占位
* @param start {Number} 开始截取数据的位置
* @param end {Number} 结束截取数据的位置
* @return sumHeight {Number} 空白占位的高度
*/
Tree.prototype.renderTreeNode = function renderTreeNode(child, index) {
var level = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
console.log('child', child.props);
var pos = level + '-' + index;
var key = child.key || pos;
@ -777,6 +967,10 @@ var Tree = function (_React$Component) {
if (child.props.hasOwnProperty('draggable')) {
draggable = child.props.draggable;
}
var isLeaf = null;
if (child.props.hasOwnProperty('isLeaf')) {
isLeaf = child.props.isLeaf;
}
var cloneProps = {
root: this,
@ -809,7 +1003,8 @@ var Tree = function (_React$Component) {
tabIndexKey: state.selectedKeys[0],
tabIndexValue: props.tabIndexValue,
ext: child.props.ext,
mustExpandable: props.mustExpandable
mustExpandable: props.mustExpandable,
isLeaf: isLeaf
};
if (props.checkable) {
cloneProps.checkable = props.checkable;
@ -836,19 +1031,50 @@ var Tree = function (_React$Component) {
};
Tree.prototype.render = function render() {
var _this5 = this;
var _this6 = this;
var props = this.props;
var _props3 = this.props,
showLine = _props3.showLine,
prefixCls = _props3.prefixCls,
className = _props3.className,
focusable = _props3.focusable,
checkable = _props3.checkable,
loadData = _props3.loadData,
checkStrictly = _props3.checkStrictly,
tabIndexValue = _props3.tabIndexValue,
lazyLoad = _props3.lazyLoad,
offsetHeight = _props3.offsetHeight;
var _state = this.state,
treeData = _state.treeData,
flatTreeData = _state.flatTreeData;
var startIndex = this.startIndex,
endIndex = this.endIndex,
preHeight = 0,
sufHeight = 0,
treeNode = [],
treeChildren = props.children; //最终渲染在 Tree 标签中的子节点
if (lazyLoad) {
preHeight = this.getSumHeight(0, startIndex);
sufHeight = this.getSumHeight(endIndex, flatTreeData.length);
}
if (!props.children && treeData) {
//传入json数据
treeNode = this.renderTreefromData(treeData);
this.cacheTreeNodes = treeNode;
treeChildren = treeNode;
}
var showLineCls = "";
if (props.showLine) {
showLineCls = props.prefixCls + '-show-line';
if (showLine) {
showLineCls = prefixCls + '-show-line';
}
var domProps = {
className: (0, _classnames2["default"])(props.className, props.prefixCls, showLineCls),
className: (0, _classnames2["default"])(className, prefixCls, showLineCls),
role: 'tree-node'
};
if (props.focusable) {
if (focusable) {
domProps.onFocus = this.onUlFocus;
domProps.onMouseEnter = this.onUlMouseEnter;
domProps.onMouseLeave = this.onUlMouseLeave;
@ -859,18 +1085,18 @@ var Tree = function (_React$Component) {
// // domProps.onKeyDown = this.onKeyDown;//添加到具体的treeNode上了
// }
var getTreeNodesStates = function getTreeNodesStates() {
_this5.treeNodesStates = {};
(0, _util.loopAllChildren)(props.children, function (item, index, pos, keyOrPos, siblingPosition) {
_this5.treeNodesStates[pos] = {
_this6.treeNodesStates = {};
(0, _util.loopAllChildren)(treeChildren, function (item, index, pos, keyOrPos, siblingPosition) {
_this6.treeNodesStates[pos] = {
siblingPosition: siblingPosition
};
});
};
if (props.showLine && !props.checkable) {
if (showLine && !checkable) {
getTreeNodesStates();
}
if (props.checkable && (this.checkedKeysChange || props.loadData || this.dataChange)) {
if (props.checkStrictly) {
if (checkable && (this.checkedKeysChange || loadData || this.dataChange)) {
if (checkStrictly) {
getTreeNodesStates();
} else if (props._treeNodesStates) {
this.treeNodesStates = props._treeNodesStates.treeNodesStates;
@ -879,14 +1105,14 @@ var Tree = function (_React$Component) {
} else {
var checkedKeys = this.state.checkedKeys;
var checkKeys = void 0;
if (!props.loadData && this.checkKeys && this._checkedKeys && (0, _util.arraysEqual)(this._checkedKeys, checkedKeys) && !this.dataChange) {
if (!loadData && this.checkKeys && this._checkedKeys && (0, _util.arraysEqual)(this._checkedKeys, checkedKeys) && !this.dataChange) {
// if checkedKeys the same as _checkedKeys from onCheck, use _checkedKeys.
checkKeys = this.checkKeys;
} else {
var checkedPositions = [];
this.treeNodesStates = {};
(0, _util.loopAllChildren)(props.children, function (item, index, pos, keyOrPos, siblingPosition) {
_this5.treeNodesStates[pos] = {
(0, _util.loopAllChildren)(treeChildren, function (item, index, pos, keyOrPos, siblingPosition) {
_this6.treeNodesStates[pos] = {
node: item,
key: keyOrPos,
checked: false,
@ -894,7 +1120,7 @@ var Tree = function (_React$Component) {
siblingPosition: siblingPosition
};
if (checkedKeys.indexOf(keyOrPos) !== -1) {
_this5.treeNodesStates[pos].checked = true;
_this6.treeNodesStates[pos].checked = true;
checkedPositions.push(pos);
}
});
@ -907,12 +1133,29 @@ var Tree = function (_React$Component) {
}
}
this.selectKeyDomExist = false;
return _react2["default"].createElement(
return lazyLoad ? _react2["default"].createElement(
_infiniteScroll2["default"],
{
className: 'u-tree-infinite-scroll',
treeList: flatTreeData,
handleTreeListChange: this.handleTreeListChange,
offsetHeight: offsetHeight
},
_react2["default"].createElement(
'ul',
_extends({}, domProps, { unselectable: 'true', ref: function ref(el) {
_this6.tree = el;
}, tabIndex: focusable && tabIndexValue }),
_react2["default"].createElement('li', { style: { height: preHeight }, className: 'u-treenode-start', key: 'tree_node_start' }),
_react2["default"].Children.map(treeChildren, this.renderTreeNode, this),
_react2["default"].createElement('li', { style: { height: sufHeight }, className: 'u-treenode-end', key: 'tree_node_end' })
)
) : _react2["default"].createElement(
'ul',
_extends({}, domProps, { unselectable: 'true', ref: function ref(el) {
_this5.tree = el;
}, tabIndex: props.focusable && props.tabIndexValue }),
_react2["default"].Children.map(props.children, this.renderTreeNode, this)
_this6.tree = el;
}, tabIndex: focusable && tabIndexValue }),
_react2["default"].Children.map(treeChildren, this.renderTreeNode, this)
);
};
@ -954,7 +1197,8 @@ Tree.propTypes = {
filterTreeNode: _propTypes2["default"].func,
openTransitionName: _propTypes2["default"].string,
focusable: _propTypes2["default"].bool,
openAnimation: _propTypes2["default"].oneOfType([_propTypes2["default"].string, _propTypes2["default"].object])
openAnimation: _propTypes2["default"].oneOfType([_propTypes2["default"].string, _propTypes2["default"].object]),
lazyLoad: _propTypes2["default"].bool
};
Tree.defaultProps = {
@ -980,7 +1224,8 @@ Tree.defaultProps = {
onDragLeave: noop,
onDrop: noop,
onDragEnd: noop,
tabIndexValue: 0
tabIndexValue: 0,
lazyLoad: false
};
exports["default"] = Tree;

17
build/config.js Normal file
View File

@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
/**
* 在此存储全局配置项
*/
// 树懒加载功能,需要用到的变量
exports["default"] = {
defaultHeight: 24, //树节点行高
loadBuffer: 5, //懒加载时缓冲区数据量
defaultRowsInView: 20, //可视区数据量
rowDiff: 3 //行差值,需要重新截取数据的阈值
};
module.exports = exports["default"];

314
build/infiniteScroll.js Normal file
View File

@ -0,0 +1,314 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _util = require('./util');
var _config = require('./config');
var _config2 = _interopRequireDefault(_config);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : _defaults(subClass, superClass); } /**
* 处理滚动加载逻辑
*/
var InfiniteScroll = function (_Component) {
_inherits(InfiniteScroll, _Component);
function InfiniteScroll(props) {
_classCallCheck(this, InfiniteScroll);
//默认显示20条rowsInView根据定高算的。在非固定高下这个只是一个大概的值。
var _this = _possibleConstructorReturn(this, _Component.call(this, props));
_this.eventListenerOptions = function () {
var options = _this.props.useCapture;
if (_this.isPassiveSupported()) {
options = {
useCapture: _this.props.useCapture,
passive: true
};
}
return options;
};
_this.mousewheelListener = function (e) {
// Prevents Chrome hangups
// See: https://stackoverflow.com/questions/47524205/random-high-content-download-time-in-chrome/47684257#47684257
if (e.deltaY === 1 && !_this.isPassiveSupported()) {
e.preventDefault();
}
};
_this.scrollListener = function () {
var el = _this.scrollComponent;
var parentNode = _this.getParentElement(el);
_this.scrollTop = parentNode.scrollTop;
(0, _util.throttle)(_this.handleScrollY, 500)();
_this.handleScrollY();
};
_this.handleScrollY = function () {
var currentIndex = _this.currentIndex,
startIndex = _this.startIndex,
endIndex = _this.endIndex,
treeList = _this.treeList,
loadCount = _this.loadCount,
rowsInView = _this.rowsInView;
var index = 0;
var tempScrollTop = _this.scrollTop;
//根据 scrollTop 计算 currentIndex
while (tempScrollTop > 0) {
tempScrollTop -= _config2["default"].defaultHeight;
if (tempScrollTop > 0) {
index += 1;
}
}
//true 为向下滚动, false 为向上滚动
var isScrollDown = index - currentIndex > 0 ? true : false;
if (index < 0) index = 0;
//如果之前的索引和下一次的不一样则重置索引和滚动的位置
_this.currentIndex = currentIndex !== index ? index : currentIndex;
// 如果rowsInView 小于 缓存的数据则重新render
// 向下滚动 下临界值超出缓存的endIndex则重新渲染
if (isScrollDown && rowsInView + index > endIndex - _config2["default"].rowDiff) {
startIndex = index - _config2["default"].loadBuffer > 0 ? index - _config2["default"].loadBuffer : 0;
endIndex = startIndex + loadCount;
if (endIndex > treeList.length) {
endIndex = treeList.length;
}
if (endIndex > _this.endIndex) {
_this.startIndex = startIndex;
_this.endIndex = endIndex;
_this.sliceTreeList(_this.startIndex, _this.endIndex);
}
}
// 向上滚动当前的index是否已经加载currentIndex若干上临界值小于startIndex则重新渲染
if (!isScrollDown && index < startIndex + _config2["default"].rowDiff) {
startIndex = index - _config2["default"].loadBuffer;
if (startIndex < 0) {
startIndex = 0;
}
if (startIndex <= _this.startIndex) {
_this.startIndex = startIndex;
_this.endIndex = _this.startIndex + loadCount;
_this.sliceTreeList(_this.startIndex, _this.endIndex);
}
}
};
_this.sliceTreeList = function (startIndex, endIndex) {
var flatTreeData = JSON.parse(JSON.stringify(_this.treeList)),
newTreeList = []; //存储截取后的新数据
// console.log(
// "**startIndex**" + startIndex,
// "**endIndex**" + endIndex
// );
newTreeList = flatTreeData.slice(startIndex, endIndex);
// console.log('截取后', JSON.stringify(newTreeList))
_this.props.handleTreeListChange && _this.props.handleTreeListChange(newTreeList, startIndex, endIndex);
};
_this.rowsInView = _config2["default"].defaultRowsInView;
//一维数组
_this.treeList = props.treeList;
//一次加载多少数据
_this.loadCount = _config2["default"].loadBuffer ? _this.rowsInView + _config2["default"].loadBuffer * 2 : 16;
//可视区第一条数据的 index
_this.currentIndex = 0;
_this.startIndex = _this.currentIndex; //数据开始位置
_this.endIndex = _this.currentIndex + _this.loadCount; //数据结束位置
return _this;
}
InfiniteScroll.prototype.componentDidMount = function componentDidMount() {
this.options = this.eventListenerOptions();
this.attachScrollListener();
};
InfiniteScroll.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
var newTreeList = nextProps.treeList;
var oldTreeList = this.props.treeList;
if (newTreeList !== oldTreeList) {
this.treeList = newTreeList;
this.handleScrollY();
}
};
InfiniteScroll.prototype.componentWillUnmount = function componentWillUnmount() {
this.detachScrollListener();
this.detachMousewheelListener();
};
InfiniteScroll.prototype.isPassiveSupported = function isPassiveSupported() {
var passive = false;
var testOptions = {
get passive() {
passive = true;
}
};
try {
document.addEventListener('test', null, testOptions);
document.removeEventListener('test', null, testOptions);
} catch (e) {
// ignore
}
return passive;
};
/**
* 解除mousewheel事件监听
*/
InfiniteScroll.prototype.detachMousewheelListener = function detachMousewheelListener() {
var scrollEl = window;
if (this.props.useWindow === false) {
scrollEl = this.scrollComponent.parentNode;
}
scrollEl.removeEventListener('mousewheel', this.mousewheelListener, this.options ? this.options : this.props.useCapture);
};
/**
* 解除scroll事件监听
*/
InfiniteScroll.prototype.detachScrollListener = function detachScrollListener() {
var scrollEl = window;
if (this.props.useWindow === false) {
scrollEl = this.getParentElement(this.scrollComponent);
}
scrollEl.removeEventListener('scroll', this.scrollListener, this.options ? this.options : this.props.useCapture);
scrollEl.removeEventListener('resize', this.scrollListener, this.options ? this.options : this.props.useCapture);
};
/**
* 获取父组件(用户自定义父组件或者当前dom的parentNode)
* @param {*} el
*/
InfiniteScroll.prototype.getParentElement = function getParentElement(el) {
var scrollParent = this.props.getScrollParent && this.props.getScrollParent();
if (scrollParent != null) {
return scrollParent;
}
return el && el.parentNode;
};
InfiniteScroll.prototype.filterProps = function filterProps(props) {
return props;
};
/**
* 绑定scroll事件
*/
InfiniteScroll.prototype.attachScrollListener = function attachScrollListener() {
var parentElement = this.getParentElement(this.scrollComponent);
if (!parentElement) {
return;
}
var scrollEl = parentElement;
var scrollY = scrollEl && scrollEl.clientHeight;
//默认显示20条rowsInView根据定高算的。在非固定高下这个只是一个大概的值。
this.rowsInView = scrollY ? Math.floor(scrollY / _config2["default"].defaultHeight) : _config2["default"].defaultRowsInView;
scrollEl.addEventListener('scroll', this.scrollListener, this.options ? this.options : this.props.useCapture);
scrollEl.addEventListener('resize', this.scrollListener, this.options ? this.options : this.props.useCapture);
};
/**
* 滚动事件监听
*/
/**
* @description 根据返回的scrollTop计算当前的索引
*/
/**
* 根据 startIndex endIndex 截取数据
* @param startIndex
* @param endIndex
*/
InfiniteScroll.prototype.render = function render() {
var _this2 = this;
var _props = this.props,
children = _props.children,
element = _props.element,
ref = _props.ref,
getScrollParent = _props.getScrollParent,
treeList = _props.treeList,
props = _objectWithoutProperties(_props, ['children', 'element', 'ref', 'getScrollParent', 'treeList']);
props.ref = function (node) {
_this2.scrollComponent = node;
if (ref) {
ref(node);
}
};
var childrenArray = [children];
return _react2["default"].createElement(element, props, childrenArray);
};
return InfiniteScroll;
}(_react.Component);
InfiniteScroll.propTypes = {
children: _propTypes2["default"].node.isRequired,
element: _propTypes2["default"].node,
ref: _propTypes2["default"].func,
getScrollParent: _propTypes2["default"].func,
treeList: _propTypes2["default"].array,
handleTreeListChange: _propTypes2["default"].func
};
InfiniteScroll.defaultProps = {
element: 'div',
ref: null,
getScrollParent: null,
treeList: [],
handleTreeListChange: function handleTreeListChange() {}
};
exports["default"] = InfiniteScroll;
module.exports = exports['default'];

View File

@ -3,6 +3,9 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /* eslint no-loop-func: 0*/
exports.browser = browser;
exports.getOffset = getOffset;
exports.loopAllChildren = loopAllChildren;
@ -17,6 +20,9 @@ exports.isTreeNode = isTreeNode;
exports.toArray = toArray;
exports.getNodeChildren = getNodeChildren;
exports.warnOnlyTreeNode = warnOnlyTreeNode;
exports.convertListToTree = convertListToTree;
exports.debounce = debounce;
exports.throttle = throttle;
var _react = require('react');
@ -73,8 +79,6 @@ function browser(navigator) {
// }
/* eslint-disable */
/* eslint no-loop-func: 0*/
function getOffset(ele) {
var doc = void 0,
win = void 0,
@ -366,4 +370,137 @@ function warnOnlyTreeNode() {
if (onlyTreeNodeWarned) return;
onlyTreeNodeWarned = true;
console.warn('Tree only accept TreeNode as children.');
}
/**
* 将一维数组转换为树结构
* @param {*} treeData 扁平结构的 List 数组
* @param {*} attr 属性配置设置
* @param {*} flatTreeKeysMap 存储所有 key-value 的映射方便获取各节点信息
*/
function convertListToTree(treeData, attr, flatTreeKeysMap) {
var tree = [];
var resData = treeData,
resKeysMap = {};
resData.map(function (element) {
resKeysMap[element.key] = element;
});
// 查找父节点,为了补充不完整的数据结构
var findParentNode = function findParentNode(node) {
var parentKey = node[attr.parendId];
if (!resKeysMap.hasOwnProperty(parentKey)) {
var obj = {
key: flatTreeKeysMap[parentKey][attr.id],
title: flatTreeKeysMap[parentKey][attr.name],
children: []
};
tree.push(obj);
resKeysMap[obj.key] = obj;
}
return flatTreeKeysMap[parentKey];
};
for (var i = 0; i < resData.length; i++) {
if (resData[i].parentKey === attr.rootId) {
var obj = {
key: resData[i][attr.id],
title: resData[i][attr.name],
isLeaf: resData[i][attr.isLeaf],
children: []
};
tree.push(obj);
resData.splice(i, 1);
i--;
} else {
var parentNode = flatTreeKeysMap[resData[i][attr.id]],
parentKey = parentNode[attr.parendId];
while (parentKey !== attr.rootId) {
parentNode = findParentNode(parentNode);
parentKey = parentNode[attr.parendId];
}
}
}
// console.log('tree',tree);
var run = function run(treeArrs) {
if (resData.length > 0) {
for (var _i2 = 0; _i2 < treeArrs.length; _i2++) {
for (var j = 0; j < resData.length; j++) {
if (treeArrs[_i2].key === resData[j][attr.parendId]) {
var _obj = {
key: resData[j][attr.id],
title: resData[j][attr.name],
isLeaf: resData[j][attr.isLeaf],
children: []
};
treeArrs[_i2].children.push(_obj);
resData.splice(j, 1);
j--;
}
}
run(treeArrs[_i2].children);
}
}
};
run(tree);
return tree;
}
function isObject(value) {
var type = typeof value === 'undefined' ? 'undefined' : _typeof(value);
return value != null && (type == 'object' || type == 'function');
}
/**
* 函数防抖
* @param {*} func
* @param {*} wait
* @param {*} immediate
*/
function debounce(func, wait, immediate) {
var timeout = void 0;
return function debounceFunc() {
var context = this;
var args = arguments;
// https://fb.me/react-event-pooling
if (args[0] && args[0].persist) {
args[0].persist();
}
var later = function later() {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
}
/**
* 函数节流
* @param {*} func 延时调用函数
* @param {*} wait 延迟多长时间
* @param {*} options 至少多长时间触发一次
* @return Function 延迟执行的方法
*/
function throttle(func, wait, options) {
var leading = true;
var trailing = true;
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
return debounce(func, wait, {
leading: leading,
trailing: trailing,
'maxWait': wait
});
}

99
demo/demolist/Demo12.js Normal file
View File

@ -0,0 +1,99 @@
/**
*
* @title 根据 treeData 数组渲染树节点
* @description 设置 treeData 属性则不需要手动构造 TreeNode 节点key 在整个树范围内唯一
*/
import React, { Component } from 'react';
import Tree from '../../src';
const treeData = [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{ title: '0-0-0-0', key: '0-0-0-0' },
{ title: '0-0-0-1', key: '0-0-0-1' },
{ title: '0-0-0-2', key: '0-0-0-2' },
],
},
{
title: '0-0-1',
key: '0-0-1',
children: [
{ title: '0-0-1-0', key: '0-0-1-0' },
{ title: '0-0-1-1', key: '0-0-1-1' },
{ title: '0-0-1-2', key: '0-0-1-2' },
],
},
{
title: '0-0-2',
key: '0-0-2',
},
],
},
{
title: '0-1',
key: '0-1',
children: [
{ title: '0-1-0-0', key: '0-1-0-0' },
{ title: '0-1-0-1', key: '0-1-0-1' },
{ title: '0-1-0-2', key: '0-1-0-2' },
],
},
{
title: '0-2',
key: '0-2',
},
];
class Demo12 extends Component {
state = {
expandedKeys: ['0-0-0', '0-0-1'],
autoExpandParent: true,
checkedKeys: ['0-0-0'],
selectedKeys: [],
};
onExpand = expandedKeys => {
console.log('onExpand', expandedKeys);
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
this.setState({
expandedKeys,
autoExpandParent: false,
});
};
onCheck = checkedKeys => {
console.log('onCheck', checkedKeys);
this.setState({ checkedKeys });
};
onSelect = (selectedKeys, info) => {
console.log('onSelect', info);
this.setState({ selectedKeys });
};
render() {
return (
<Tree
checkable
treeData={treeData}
onExpand={this.onExpand}
expandedKeys={this.state.expandedKeys}
autoExpandParent={this.state.autoExpandParent}
onCheck={this.onCheck}
checkedKeys={this.state.checkedKeys}
onSelect={this.onSelect}
selectedKeys={this.state.selectedKeys}
/>
);
}
}
export default Demo12;

104
demo/demolist/Demo13.js Normal file
View File

@ -0,0 +1,104 @@
/**
*
* @title 滚动加载树节点
* @description 适用于大数据场景注意使用懒加载需要通过 treeData 属性传入完整的数据结构并设置 lazyLoad = {true}可视区域的高度可以自定义 Tree 组件外层包裹一层div即可
*/
import React, { Component } from 'react';
import Tree from '../../src';
const x = 1000;
const y = 1;
const z = 1;
const gData = [];
const generateData = (_level, _preKey, _tns) => {
const preKey = _preKey || '0';
const tns = _tns || gData;
const children = [];
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`;
tns.push({ title: key, key });
if (i < y) {
children.push(key);
}
}
if (_level < 0) {
return tns;
}
const level = _level - 1;
children.forEach((key, index) => {
tns[index].children = [];
return generateData(level, key, tns[index].children);
});
};
generateData(z);
class Demo13 extends Component{
constructor(props) {
super(props);
this.state = {
expandedKeys: ['0-0','0-1','0-2','0-3', '0-4','0-5','0-6','0-0-0','0-0-1'],
autoExpandParent: true,
checkedKeys: ['0-0-0'],
selectedKeys: [],
};
this.onExpand = this.onExpand.bind(this);
this.onCheck = this.onCheck.bind(this);
this.onSelect = this.onSelect.bind(this);
}
onExpand(expandedKeys,nodeInfo) {
// console.log('onExpand---显示ext数据', nodeInfo.node.props.ext.data);
this.setState({
expandedKeys,
autoExpandParent: false,
});
}
onCheck(checkedKeys) {
this.setState({
checkedKeys,
selectedKeys: ['0-3', '0-4'],
});
}
onSelect(selectedKeys, info) {
console.log('onSelect', info);
this.setState({ selectedKeys });
}
// keydown的钩子事件
onKeyDown = (e,treeNode)=>{
console.log('***',e);
return false;
}
//自定义树节点内容
renderTitle = item => {
return item.key
}
render() {
return (
<div style={{height:'300px', border:'1px solid', overflow:'auto'}}>
<Tree
checkable
focusable
treeData={gData}
lazyLoad={true}
renderTitle={this.renderTitle}
onExpand={this.onExpand}
defaultExpandAll={true}
expandedKeys={this.state.expandedKeys}
autoExpandParent={this.state.autoExpandParent}
onCheck={this.onCheck}
onSelect={this.onSelect}
keyFun={this.onKeyDown}
>
</Tree>
</div>
);
}
};
export default Demo13;

View File

@ -1,62 +1,61 @@
/**
*
* @title Tree基本使用示例自定义图标
* @description 添加openIconcloseIcon属性
*
*/
import React, {
Component
} from 'react';
import Tree from '../../src';
import Icon from 'bee-icon';
const TreeNode = Tree.TreeNode;
const defaultProps = {
keys: ['0-0-0', '0-0-1']
}
console.log(Tree);
class Demo1 extends Component {
constructor(props) {
super(props);
const keys = this.props.keys;
this.state = {
defaultExpandedKeys: keys,
defaultSelectedKeys: keys,
defaultCheckedKeys: keys,
};
}
onSelect(info) {
console.log('selected', info);
}
onCheck(info) {
console.log('onCheck', info);
}
render() {
return (
<Tree className="myCls" checkable openIcon={<Icon type="uf-minus" />} closeIcon={<Icon type="uf-plus" />}
defaultExpandedKeys={this.state.defaultExpandedKeys}
defaultSelectedKeys={this.state.defaultSelectedKeys}
defaultCheckedKeys={this.state.defaultCheckedKeys}
onSelect={this.onSelect} onCheck={this.onCheck}
>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="parent 1-0" key="0-0-0" disabled>
<TreeNode title="leaf" key="0-0-0-0" disableCheckbox />
<TreeNode title="leaf" key="0-0-0-1" />
</TreeNode>
<TreeNode title="parent 1-1" key="0-0-1">
<TreeNode title={<span>sss</span>} key="0-0-1-0" />
</TreeNode>
</TreeNode>
</Tree>
);
}
}
Demo1.defaultProps = defaultProps;
/**
*
* @title Tree基本使用示例自定义图标
* @description 添加openIconcloseIcon属性
*
*/
import React, {
Component
} from 'react';
import Tree from '../../src';
import Icon from 'bee-icon';
const TreeNode = Tree.TreeNode;
const defaultProps = {
keys: ['0-0-0', '0-0-1']
}
class Demo1 extends Component {
constructor(props) {
super(props);
const keys = this.props.keys;
this.state = {
defaultExpandedKeys: keys,
defaultSelectedKeys: keys,
defaultCheckedKeys: keys,
};
}
onSelect(info) {
console.log('selected', info);
}
onCheck(info) {
console.log('onCheck', info);
}
render() {
return (
<Tree className="myCls" checkable openIcon={<Icon type="uf-minus" />} closeIcon={<Icon type="uf-plus" />}
defaultExpandedKeys={this.state.defaultExpandedKeys}
defaultSelectedKeys={this.state.defaultSelectedKeys}
defaultCheckedKeys={this.state.defaultCheckedKeys}
onSelect={this.onSelect} onCheck={this.onCheck}
>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="parent 1-0" key="0-0-0" disabled>
<TreeNode title="leaf" key="0-0-0-0" disableCheckbox />
<TreeNode title="leaf" key="0-0-0-1" />
</TreeNode>
<TreeNode title="parent 1-1" key="0-0-1">
<TreeNode title={<span>sss</span>} key="0-0-1-0" />
</TreeNode>
</TreeNode>
</Tree>
);
}
}
Demo1.defaultProps = defaultProps;
export default Demo1;

View File

@ -1,83 +1,83 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Con, Row, Col } from 'bee-layout';
import { Panel } from 'bee-panel';
import Drawer from 'bee-drawer';
import Clipboard from 'bee-clipboard';
import Button from '../src';
{demolist}
class Demo extends Component {
constructor(props){
super(props);
this.state = {
open: false
}
}
handleClick=()=> {
this.setState({ open: !this.state.open })
}
fCloseDrawer=()=>{
this.setState({
open: false
})
}
render () {
const { title, example, code, desc, scss_code } = this.props;
const header = (
<div>
<p className='component-title'>{ title }</p>
<p>{ desc }</p>
<span className='component-code' onClick={this.handleClick}> 查看源码 <i className='uf uf-arrow-right'/> </span>
</div>
);
return (
<Col md={12} id={title.trim()} className='component-demo'>
<Panel header={header}>
{example}
</Panel>
<Drawer className='component-drawerc' title={title} show={this.state.open} placement='right' onClose={this.fCloseDrawer}>
<div className='component-code-copy'> JS代码
<Clipboard action="copy" text={code}/>
</div>
<pre className="pre-js">
<code className="hljs javascript">{ code }</code>
</pre >
{!!scss_code ?<div className='component-code-copy copy-css'> SCSS代码
<Clipboard action="copy" text={scss_code}/>
</div>:null }
{ !!scss_code ? <pre className="pre-css">
<code className="hljs css">{ scss_code }</code>
</pre> : null }
</Drawer>
</Col>
)
}
}
class DemoGroup extends Component {
constructor(props){
super(props)
}
render () {
return (
<Row>
{DemoArray.map((child,index) => {
return (
<Demo example= {child.example} title= {child.title} code= {child.code} scss_code= {child.scss_code} desc= {child.desc} key= {index}/>
)
})}
</Row>
)
}
}
ReactDOM.render(<DemoGroup/>, document.getElementById('tinperBeeDemo'));
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Con, Row, Col } from 'bee-layout';
import { Panel } from 'bee-panel';
import Drawer from 'bee-drawer';
import Clipboard from 'bee-clipboard';
import Button from '../src';
{demolist}
class Demo extends Component {
constructor(props){
super(props);
this.state = {
open: false
}
}
handleClick=()=> {
this.setState({ open: !this.state.open })
}
fCloseDrawer=()=>{
this.setState({
open: false
})
}
render () {
const { title, example, code, desc, scss_code } = this.props;
const header = (
<div>
<p className='component-title'>{ title }</p>
<p>{ desc }</p>
<span className='component-code' onClick={this.handleClick}> 查看源码 <i className='uf uf-arrow-right'/> </span>
</div>
);
return (
<Col md={12} id={title.trim()} className='component-demo'>
<Panel header={header}>
{example}
</Panel>
<Drawer className='component-drawerc' title={title} show={this.state.open} placement='right' onClose={this.fCloseDrawer}>
<div className='component-code-copy'> JS代码
<Clipboard action="copy" text={code}/>
</div>
<pre className="pre-js">
<code className="hljs javascript">{ code }</code>
</pre >
{!!scss_code ?<div className='component-code-copy copy-css'> SCSS代码
<Clipboard action="copy" text={scss_code}/>
</div>:null }
{ !!scss_code ? <pre className="pre-css">
<code className="hljs css">{ scss_code }</code>
</pre> : null }
</Drawer>
</Col>
)
}
}
class DemoGroup extends Component {
constructor(props){
super(props)
}
render () {
return (
<Row>
{DemoArray.map((child,index) => {
return (
<Demo example= {child.example} title= {child.title} code= {child.code} scss_code= {child.scss_code} desc= {child.desc} key= {index}/>
)
})}
</Row>
)
}
}
ReactDOM.render(<DemoGroup/>, document.getElementById('tinperBeeDemo'));

File diff suppressed because one or more lines are too long

1728
dist/demo.js vendored

File diff suppressed because one or more lines are too long

2
dist/demo.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -23,16 +23,16 @@ import 'bee-tree/build/Tree.css';
|参数|说明|类型|默认值|
|:---|:-----|:----|:------|
|multiple|是否允许选择多个树节点|bool|false
|checkable|是否支持添加在树节点前添加Checkbox|bool|false
|defaultExpandAll|默认是否展开所有节点|bool|false
|defaultExpandedKeys|默认展开指定的节点|String[]|[]
|expandedKeys|指定展开的节点(controlled)|String[]|[]
|autoExpandParent|是否自展开父节点|bool|true
|defaultCheckedKeys|指定默认选中的节点key|String[]|[]
|checkedKeys|指定被选中的节点(controlled)PS当指定的是父节点所有的子节点也会被指定当指定的是子节点父节点也会被选中。当checkable和checkStrictly都为true,子节点与父节点的选择情况都不会影响到对方|String[]/{checked:Array,halfChecked:Array}|[]
|checkStrictly|checkable状态下节点选择完全受控父子节点选中状态不再关联|bool|false
|defaultSelectedKeys|指定选中的节点key|String[]|[]
|selectedKeys|指定选中的节点keys(controlled)|String[]|-
|checkable|节点前添加 Checkbox 复选框|bool|false
|defaultExpandAll|默认展开所有节点|bool|false
|defaultExpandedKeys|默认展开指定的节点|String[]|[]
|expandedKeys|(受控)展开指定的节点|String[]|[]
|autoExpandParent|是否自展开父节点|bool|true
|defaultCheckedKeys|默认选中复选框节点|String[]|[]
|checkedKeys|(受控)选中复选框的树节点(注意:父子节点有关联,如果传入父节点 key则子节点自动选中相应当子节点 key 都传入父节点也自动选中。当设置checkable和checkStrictly它是一个有checked和halfChecked属性的对象并且父子节点的选中与否不再关联|String[]/{checked:Array,halfChecked:Array}|[]
|checkStrictly|checkable 状态下节点选择完全受控(父子节点选中状态不再关联)|bool|false
|defaultSelectedKeys|默认选中的树节点|String[]|[]
|selectedKeys|(受控)设置选中的树节点|String[]|-
|cancelUnSelect|选中的节点第二次点击时还是选中,不自动取消选中|bool|false
|showLine|是否显示连接线|bool|false
|openIcon|自定义展开节点图标的名称[参考这里](http://bee.tinper.org/tinper-bee/bee-icon)String[]|-
@ -55,6 +55,9 @@ import 'bee-tree/build/Tree.css';
|tabIndexValue|节点获取焦点时自定义tabIndex的值|Number|0
|Children|必填TreeNode组件|node|-
|mustExpandable|支持disabled的节点可以自定义展开收起默认disabled的节点不可以展开收起|bool|false
|treeData|treeNodes 数据,如果设置则不需要手动构造 TreeNode 节点key 在整个树范围内唯一)|array\<{key, title, children, [disabled, selectable]}>|-
|lazyLoad|是否使用懒加载(适用于大数据场景),[如何使用](http://bee.tinper.org/tinper-bee/bee-tree#%E6%BB%9A%E5%8A%A8%E5%8A%A0%E8%BD%BD%E6%A0%91%E8%8A%82%E7%82%B9)|bool|false
|renderTitle|使用 treeData 渲染树时使用,可通过此函数自定义树节点内容|Function(item)|-
### TreeNode

View File

@ -1,6 +1,7 @@
/* eslint no-console:0 */
import React from 'react';
import TreeNode from './TreeNode';
import InfiniteScroll from './infiniteScroll';
import classNames from 'classnames';
import {
loopAllChildren,
@ -11,10 +12,12 @@ import {
getCheck,
getStrictlyValue,
arraysEqual,
closest
closest,
convertListToTree,
} from './util';
import PropTypes from 'prop-types';
import { KeyCode } from 'tinper-bee-core';
import CONFIG from './config';
function noop() {}
@ -35,7 +38,38 @@ class Tree extends React.Component {
dragOverNodeKey: '',
dropNodeKey: '',
focusKey: '', //上下箭头选择树节点时用于标识focus状态
treeData: [], //Tree结构数组(全量)
flatTreeData: [], //一维数组(全量)
};
//默认显示20条rowsInView根据定高算的。在非固定高下这个只是一个大概的值。
this.rowsInView = CONFIG.defaultRowsInView;
//一次加载多少数据
this.loadCount = CONFIG.loadBuffer ? this.rowsInView + CONFIG.loadBuffer * 2 : 16;
this.flatTreeKeysMap = {}; //存储所有 key-value 的映射,方便获取各节点信息
this.startIndex = 0;
this.endIndex = this.startIndex + this.loadCount;
this.cacheTreeNodes = []; //缓存 treenode 节点数组
}
componentWillMount() {
const { treeData,lazyLoad } = this.props;
let sliceTreeList = [];
//启用懒加载,把 Tree 结构拍平,为后续动态截取数据做准备
if(lazyLoad) {
let flatTreeData = this.deepTraversal(treeData);
flatTreeData.forEach((element) => {
if(sliceTreeList.length >= this.loadCount) return;
sliceTreeList.push(element);
});
this.handleTreeListChange(sliceTreeList);
this.setState({
flatTreeData
})
} else {
this.setState({
treeData
})
}
}
componentWillReceiveProps(nextProps) {
@ -59,12 +93,29 @@ class Tree extends React.Component {
if (selectedKeys) {
st.selectedKeys = selectedKeys;
}
if(nextProps.treeData !== this.props.treeData){
this.dataChange = true;
st.treeData = treeData;
}
if(nextProps.children !== this.props.children){
this.dataChange = true;
}
this.setState(st);
}
// componentWillUpdate(nextProps, nextState){
// const { expandedKeys,treeData } = this.state;
// if(nextState.expandedKeys !== expandedKeys) {
// this.cacheExpandedKeys = expandedKeys;
// if(this.props.lazyLoad){
// let flatTreeData = this.deepTraversal(treeData);
// this.setState({
// flatTreeData
// })
// }
// }
// }
onDragStart(e, treeNode) {
this.dragNode = treeNode;
this.dragNodesKeys = this.getDragNodes(treeNode);
@ -189,6 +240,7 @@ class Tree extends React.Component {
* @memberof Tree
*/
onExpand(treeNode,keyType) {
const { treeData,lazyLoad } = this.props;
let expanded = !treeNode.props.expanded;
const controlled = 'expandedKeys' in this.props;
const expandedKeys = [...this.state.expandedKeys];
@ -225,6 +277,16 @@ onExpand(treeNode,keyType) {
}
});
}
//收起和展开时,缓存 expandedKeys
this.cacheExpandedKeys = expandedKeys;
//启用懒加载,把 Tree 结构拍平,为后续动态截取数据做准备
if(lazyLoad) {
let flatTreeData = this.deepTraversal(treeData);
this.cacheExpandedKeys = null;
this.setState({
flatTreeData
})
}
}
onCheck(treeNode) {
@ -406,6 +468,7 @@ onExpand(treeNode,keyType) {
goDown(currentPos,currentIndex,e,treeNode){
const props = this.props;
let treeChildren = props.children ? props.children : this.cacheTreeNodes; //最终渲染在 Tree 标签中的子节点
const nextIndex = parseInt(currentIndex) + 1;
let nextPos,backNextPos;
@ -430,7 +493,7 @@ onExpand(treeNode,keyType) {
tempPosArrLength = tempPosArr.length;
}
//选中下一个相邻的节点
loopAllChildren(props.children,function(itemNode,index,pos,newKey){
loopAllChildren(treeChildren,function(itemNode,index,pos,newKey){
if(pos == nextPos){
nextTreeNode = itemNode;
}
@ -599,11 +662,11 @@ onExpand(treeNode,keyType) {
onUlMouseEnter(e){
this.isIn = true;
console.log('onUlMouseEnter----isIn-----',this.isIn);
// console.log('onUlMouseEnter----isIn-----',this.isIn);
}
onUlMouseLeave(e){
this.isIn = false;
console.log('onUlMouseLeave----isIn-----',this.isIn);
// console.log('onUlMouseLeave----isIn-----',this.isIn);
}
@ -730,7 +793,103 @@ onExpand(treeNode,keyType) {
return filterTreeNode.call(this, treeNode);
}
/**
* 将截取后的 List 数组转换为 Tree 结构并更新 state
*/
handleTreeListChange = (treeList, startIndex, endIndex) => {
// 属性配置设置
let attr = {
id: 'key',
parendId: 'parentKey',
name: 'title',
rootId: null,
isLeaf: 'isLeaf'
};
let treeData = convertListToTree(treeList, attr, this.flatTreeKeysMap);
this.startIndex = typeof(startIndex) !== "undefined" ? startIndex : this.startIndex;
this.endIndex = typeof(endIndex) !== "undefined" ? endIndex : this.endIndex;
this.setState({
treeData : treeData
})
}
/**
* 深度遍历 treeData把Tree数据拍平变为一维数组
* @param {*} treeData
* @param {*} parentKey 标识父节点
* @param {*} isShown 该节点是否显示在页面中当节点的父节点是展开状态 该节点是根节点时该值为 true
*/
deepTraversal = (treeData, parentKey=null, isShown) => {
let {expandedKeys} = this.state,
flatTreeData = [],
flatTreeKeysMap = this.flatTreeKeysMap, //存储所有 key-value 的映射,方便获取各节点信息
dataCopy = JSON.parse(JSON.stringify(treeData));
if(Array.isArray(dataCopy)){
for (let i=0, l=dataCopy.length; i<l; i++) {
let key = dataCopy[i].hasOwnProperty('key') && dataCopy[i].key,
isLeaf = dataCopy[i].hasOwnProperty('children') ? false : true,
isExpanded = this.cacheExpandedKeys ? this.cacheExpandedKeys.indexOf(key) !== -1 : expandedKeys.indexOf(key) !== -1;
dataCopy[i].isExpanded = isExpanded;
dataCopy[i].parentKey = parentKey || null;
dataCopy[i].isShown = isShown;
dataCopy[i].isLeaf = isLeaf;
//该节点的父节点是展开状态 或 该节点是根节点
if(isShown || parentKey === null){
flatTreeData.push(dataCopy[i]); // 取每项数据放入一个新数组
flatTreeKeysMap[key] = dataCopy[i];
}
if (Array.isArray(dataCopy[i]["children"]) && dataCopy[i]["children"].length > 0){
// 若存在children则递归调用把数据拼接到新数组中并且删除该children
flatTreeData = flatTreeData.concat(this.deepTraversal(dataCopy[i]["children"], key, isExpanded));
delete dataCopy[i]["children"]
}
}
}else {
flatTreeData.push(dataCopy); // 取每项数据放入一个新数组
}
return flatTreeData;
}
/**
* 根据 treeData 渲染树节点
* @param data 树形结构的数组
* @param preHeight 前置占位高度
* @param sufHeight 后置占位高度
*/
renderTreefromData = (data) => {
let {renderTitle} = this.props;
const loop = data => data.map((item) => {
if (item.children) {
return (
<TreeNode key={item.key} title={renderTitle ? renderTitle(item) : item.key} isLeaf={item.isLeaf}>
{loop(item.children)}
</TreeNode>
);
}
return <TreeNode key={item.key} title={renderTitle ? renderTitle(item) : item.key} isLeaf={true}/>;
});
return loop(data);
}
/**
* @description 计算懒加载时的前置占位和后置占位
* @param start {Number} 开始截取数据的位置
* @param end {Number} 结束截取数据的位置
* @return sumHeight {Number} 空白占位的高度
*/
getSumHeight = (start, end) => {
let sumHeight = 0;
let span = Math.abs(end - start);
if(span) {
sumHeight = span * CONFIG.defaultHeight;
}
return sumHeight;
}
renderTreeNode(child, index, level = 0) {
console.log('child',child.props)
const pos = `${level}-${index}`;
const key = child.key || pos;
@ -751,6 +910,10 @@ onExpand(treeNode,keyType) {
if(child.props.hasOwnProperty('draggable')){
draggable = child.props.draggable;
}
let isLeaf = null;
if(child.props.hasOwnProperty('isLeaf')){
isLeaf = child.props.isLeaf;
}
const cloneProps = {
root: this,
@ -783,7 +946,8 @@ onExpand(treeNode,keyType) {
tabIndexKey: state.selectedKeys[0],
tabIndexValue:props.tabIndexValue,
ext:child.props.ext,
mustExpandable:props.mustExpandable
mustExpandable:props.mustExpandable,
isLeaf
};
if (props.checkable) {
cloneProps.checkable = props.checkable;
@ -811,16 +975,32 @@ onExpand(treeNode,keyType) {
render() {
const props = this.props;
const { showLine, prefixCls, className, focusable, checkable, loadData, checkStrictly, tabIndexValue, lazyLoad, offsetHeight } = this.props;
const { treeData,flatTreeData } = this.state;
let { startIndex, endIndex } = this, //数据截取的开始位置和结束位置
preHeight = 0, //前置占位高度
sufHeight = 0, //后置占位高度
treeNode = [], //根据传入的 treeData 生成的 treeNode 节点数组
treeChildren = props.children; //最终渲染在 Tree 标签中的子节点
if(lazyLoad){
preHeight = this.getSumHeight(0, startIndex);
sufHeight = this.getSumHeight(endIndex, flatTreeData.length);
}
if(!props.children && treeData) { //传入json数据
treeNode = this.renderTreefromData(treeData);
this.cacheTreeNodes = treeNode;
treeChildren = treeNode;
}
let showLineCls = "";
if (props.showLine) {
showLineCls = `${props.prefixCls}-show-line`;
if (showLine) {
showLineCls = `${prefixCls}-show-line`;
}
const domProps = {
className: classNames(props.className, props.prefixCls, showLineCls),
className: classNames(className, prefixCls, showLineCls),
role: 'tree-node',
};
if (props.focusable) {
if (focusable) {
domProps.onFocus = this.onUlFocus;
domProps.onMouseEnter = this.onUlMouseEnter;
domProps.onMouseLeave = this.onUlMouseLeave;
@ -832,17 +1012,17 @@ onExpand(treeNode,keyType) {
// }
const getTreeNodesStates = () => {
this.treeNodesStates = {};
loopAllChildren(props.children, (item, index, pos, keyOrPos, siblingPosition) => {
loopAllChildren(treeChildren, (item, index, pos, keyOrPos, siblingPosition) => {
this.treeNodesStates[pos] = {
siblingPosition,
};
});
};
if (props.showLine && !props.checkable ) {
if (showLine && !checkable ) {
getTreeNodesStates();
}
if (props.checkable && (this.checkedKeysChange || props.loadData || this.dataChange)) {
if (props.checkStrictly) {
if (checkable && (this.checkedKeysChange || loadData || this.dataChange)) {
if (checkStrictly) {
getTreeNodesStates();
} else if (props._treeNodesStates) {
this.treeNodesStates = props._treeNodesStates.treeNodesStates;
@ -851,14 +1031,14 @@ onExpand(treeNode,keyType) {
} else {
const checkedKeys = this.state.checkedKeys;
let checkKeys;
if (!props.loadData && this.checkKeys && this._checkedKeys &&
if (!loadData && this.checkKeys && this._checkedKeys &&
arraysEqual(this._checkedKeys, checkedKeys) && !this.dataChange) {
// if checkedKeys the same as _checkedKeys from onCheck, use _checkedKeys.
checkKeys = this.checkKeys;
} else {
const checkedPositions = [];
this.treeNodesStates = {};
loopAllChildren(props.children, (item, index, pos, keyOrPos, siblingPosition) => {
loopAllChildren(treeChildren, (item, index, pos, keyOrPos, siblingPosition) => {
this.treeNodesStates[pos] = {
node: item,
key: keyOrPos,
@ -881,9 +1061,23 @@ onExpand(treeNode,keyType) {
}
this.selectKeyDomExist = false;
return (
<ul {...domProps} unselectable="true" ref={(el)=>{this.tree = el}} tabIndex={props.focusable && props.tabIndexValue}>
{React.Children.map(props.children, this.renderTreeNode, this)}
</ul>
lazyLoad ?
<InfiniteScroll
className="u-tree-infinite-scroll"
treeList={flatTreeData}
handleTreeListChange={this.handleTreeListChange}
offsetHeight={offsetHeight}
>
<ul {...domProps} unselectable="true" ref={(el)=>{this.tree = el}} tabIndex={focusable && tabIndexValue}>
<li style={{height : preHeight}} className='u-treenode-start' key={'tree_node_start'}></li>
{ React.Children.map(treeChildren, this.renderTreeNode, this) }
<li style={{height : sufHeight}} className='u-treenode-end' key={'tree_node_end'}></li>
</ul>
</InfiniteScroll>
:
<ul {...domProps} unselectable="true" ref={(el)=>{this.tree = el}} tabIndex={focusable && tabIndexValue}>
{ React.Children.map(treeChildren, this.renderTreeNode, this) }
</ul>
);
}
}
@ -930,6 +1124,7 @@ Tree.propTypes = {
openTransitionName: PropTypes.string,
focusable: PropTypes.bool,
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
lazyLoad: PropTypes.bool
};
Tree.defaultProps = {
@ -955,7 +1150,8 @@ Tree.defaultProps = {
onDragLeave: noop,
onDrop: noop,
onDragEnd: noop,
tabIndexValue:0
tabIndexValue:0,
lazyLoad: false
};
export default Tree;

11
src/config.js Normal file
View File

@ -0,0 +1,11 @@
/**
* 在此存储全局配置项
*/
// 树懒加载功能,需要用到的变量
export default {
defaultHeight: 24, //树节点行高
loadBuffer: 5, //懒加载时缓冲区数据量
defaultRowsInView: 20, //可视区数据量
rowDiff: 3, //行差值,需要重新截取数据的阈值
};

286
src/infiniteScroll.js Normal file
View File

@ -0,0 +1,286 @@
/**
* 处理滚动加载逻辑
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {debounce, throttle} from './util';
import CONFIG from './config';
export default class InfiniteScroll extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
element: PropTypes.node,
ref: PropTypes.func,
getScrollParent: PropTypes.func,
treeList: PropTypes.array,
handleTreeListChange: PropTypes.func
};
static defaultProps = {
element: 'div',
ref: null,
getScrollParent: null,
treeList: [],
handleTreeListChange: () => {}
};
constructor(props) {
super(props);
//默认显示20条rowsInView根据定高算的。在非固定高下这个只是一个大概的值。
this.rowsInView = CONFIG.defaultRowsInView;
//一维数组
this.treeList = props.treeList;
//一次加载多少数据
this.loadCount = CONFIG.loadBuffer ? this.rowsInView + CONFIG.loadBuffer * 2 : 16;
//可视区第一条数据的 index
this.currentIndex = 0;
this.startIndex = this.currentIndex; //数据开始位置
this.endIndex = this.currentIndex + this.loadCount; //数据结束位置
}
componentDidMount() {
this.options = this.eventListenerOptions();
this.attachScrollListener();
}
componentWillReceiveProps(nextProps){
let {treeList:newTreeList} = nextProps;
let {treeList:oldTreeList} = this.props;
if(newTreeList !== oldTreeList) {
this.treeList = newTreeList;
this.handleScrollY();
}
}
componentWillUnmount() {
this.detachScrollListener();
this.detachMousewheelListener();
}
isPassiveSupported() {
let passive = false;
const testOptions = {
get passive() {
passive = true;
}
};
try {
document.addEventListener('test', null, testOptions);
document.removeEventListener('test', null, testOptions);
} catch (e) {
// ignore
}
return passive;
}
eventListenerOptions = () => {
let options = this.props.useCapture;
if (this.isPassiveSupported()) {
options = {
useCapture: this.props.useCapture,
passive: true
};
}
return options;
}
/**
* 解除mousewheel事件监听
*/
detachMousewheelListener() {
let scrollEl = window;
if (this.props.useWindow === false) {
scrollEl = this.scrollComponent.parentNode;
}
scrollEl.removeEventListener(
'mousewheel',
this.mousewheelListener,
this.options ? this.options : this.props.useCapture
);
}
/**
* 解除scroll事件监听
*/
detachScrollListener() {
let scrollEl = window;
if (this.props.useWindow === false) {
scrollEl = this.getParentElement(this.scrollComponent);
}
scrollEl.removeEventListener(
'scroll',
this.scrollListener,
this.options ? this.options : this.props.useCapture
);
scrollEl.removeEventListener(
'resize',
this.scrollListener,
this.options ? this.options : this.props.useCapture
);
}
/**
* 获取父组件(用户自定义父组件或者当前dom的parentNode)
* @param {*} el
*/
getParentElement(el) {
const scrollParent =
this.props.getScrollParent && this.props.getScrollParent();
if (scrollParent != null) {
return scrollParent;
}
return el && el.parentNode;
}
filterProps(props) {
return props;
}
/**
* 绑定scroll事件
*/
attachScrollListener() {
const parentElement = this.getParentElement(this.scrollComponent);
if (!parentElement) {
return;
}
let scrollEl = parentElement;
let scrollY = scrollEl && scrollEl.clientHeight;
//默认显示20条rowsInView根据定高算的。在非固定高下这个只是一个大概的值。
this.rowsInView = scrollY ? Math.floor(scrollY / CONFIG.defaultHeight) : CONFIG.defaultRowsInView;
scrollEl.addEventListener(
'scroll',
this.scrollListener,
this.options ? this.options : this.props.useCapture
);
scrollEl.addEventListener(
'resize',
this.scrollListener,
this.options ? this.options : this.props.useCapture
);
}
mousewheelListener = (e) => {
// Prevents Chrome hangups
// See: https://stackoverflow.com/questions/47524205/random-high-content-download-time-in-chrome/47684257#47684257
if (e.deltaY === 1 && !this.isPassiveSupported()) {
e.preventDefault();
}
}
/**
* 滚动事件监听
*/
scrollListener = () => {
const el = this.scrollComponent;
const parentNode = this.getParentElement(el);
this.scrollTop = parentNode.scrollTop;
throttle(this.handleScrollY, 500)()
this.handleScrollY();
}
/**
* @description 根据返回的scrollTop计算当前的索引
*/
handleScrollY = () => {
let currentIndex = this.currentIndex,
startIndex = this.startIndex,
endIndex = this.endIndex,
treeList = this.treeList,
loadCount = this.loadCount,
rowsInView = this.rowsInView;
let index = 0;
let tempScrollTop = this.scrollTop;
//根据 scrollTop 计算 currentIndex
while (tempScrollTop > 0) {
tempScrollTop -= CONFIG.defaultHeight;
if (tempScrollTop > 0) {
index += 1;
}
}
//true 为向下滚动, false 为向上滚动
let isScrollDown = index - currentIndex > 0 ? true : false;
if (index < 0) index = 0;
//如果之前的索引和下一次的不一样则重置索引和滚动的位置
this.currentIndex = currentIndex !== index ? index : currentIndex;
// 如果rowsInView 小于 缓存的数据则重新render
// 向下滚动 下临界值超出缓存的endIndex则重新渲染
if (isScrollDown && rowsInView + index > endIndex - CONFIG.rowDiff) {
startIndex = index - CONFIG.loadBuffer > 0 ? index - CONFIG.loadBuffer : 0;
endIndex = startIndex + loadCount;
if (endIndex > treeList.length) {
endIndex = treeList.length;
}
if (endIndex > this.endIndex ) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.sliceTreeList(this.startIndex, this.endIndex);
}
}
// 向上滚动当前的index是否已经加载currentIndex若干上临界值小于startIndex则重新渲染
if (!isScrollDown && index < startIndex + CONFIG.rowDiff) {
startIndex = index - CONFIG.loadBuffer;
if (startIndex < 0) {
startIndex = 0;
}
if (startIndex <= this.startIndex) {
this.startIndex = startIndex;
this.endIndex = this.startIndex + loadCount;
this.sliceTreeList(this.startIndex, this.endIndex);
}
}
}
/**
* 根据 startIndex endIndex 截取数据
* @param startIndex
* @param endIndex
*/
sliceTreeList = (startIndex, endIndex) => {
let flatTreeData = JSON.parse(JSON.stringify(this.treeList)),
newTreeList = []; //存储截取后的新数据
// console.log(
// "**startIndex**" + startIndex,
// "**endIndex**" + endIndex
// );
newTreeList = flatTreeData.slice(startIndex,endIndex);
// console.log('截取后', JSON.stringify(newTreeList))
this.props.handleTreeListChange && this.props.handleTreeListChange(newTreeList, startIndex, endIndex);
}
render() {
const {
children,
element,
ref,
getScrollParent,
treeList,
...props
} = this.props;
props.ref = node => {
this.scrollComponent = node;
if (ref) {
ref(node);
}
};
const childrenArray = [children];
return React.createElement(element, props, childrenArray);
}
}

View File

@ -323,3 +323,135 @@ export function warnOnlyTreeNode() {
console.warn('Tree only accept TreeNode as children.');
}
/**
* 将一维数组转换为树结构
* @param {*} treeData 扁平结构的 List 数组
* @param {*} attr 属性配置设置
* @param {*} flatTreeKeysMap 存储所有 key-value 的映射方便获取各节点信息
*/
export function convertListToTree(treeData, attr, flatTreeKeysMap) {
let tree = [];
let resData = treeData,
resKeysMap = {};
resData.map((element) => {
  resKeysMap[element.key] = element;
  });
// 查找父节点,为了补充不完整的数据结构
let findParentNode = (node) => {
let parentKey = node[attr.parendId];
if (!resKeysMap.hasOwnProperty(parentKey) ) {
let obj = {
key: flatTreeKeysMap[parentKey][attr.id],
title: flatTreeKeysMap[parentKey][attr.name],
children: []
};
tree.push(obj);
resKeysMap[obj.key] = obj;
}
return flatTreeKeysMap[parentKey];
}
for (let i = 0; i < resData.length; i++) {
if (resData[i].parentKey === attr.rootId) {
let obj = {
key: resData[i][attr.id],
title: resData[i][attr.name],
isLeaf: resData[i][attr.isLeaf],
children: []
};
tree.push(obj);
resData.splice(i, 1);
i--;
}else {
let parentNode = flatTreeKeysMap[resData[i][attr.id]],
parentKey = parentNode[attr.parendId];
while(parentKey !== attr.rootId){
parentNode = findParentNode(parentNode);
parentKey = parentNode[attr.parendId];
}
}
}
// console.log('tree',tree);
var run = function(treeArrs) {
if (resData.length > 0) {
for (let i = 0; i < treeArrs.length; i++) {
for (let j = 0; j < resData.length; j++) {
if (treeArrs[i].key === resData[j][attr.parendId]) {
let obj = {
key: resData[j][attr.id],
title: resData[j][attr.name],
isLeaf: resData[j][attr.isLeaf],
children: []
};
treeArrs[i].children.push(obj);
resData.splice(j, 1);
j--;
}
}
run(treeArrs[i].children);
}
}
};
run(tree);
return tree;
}
function isObject(value) {
const type = typeof value
return value != null && (type == 'object' || type == 'function')
}
/**
* 函数防抖
* @param {*} func
* @param {*} wait
* @param {*} immediate
*/
export function debounce(func, wait, immediate) {
let timeout;
return function debounceFunc() {
const context = this;
const args = arguments;
// https://fb.me/react-event-pooling
if (args[0] && args[0].persist) {
args[0].persist();
}
const later = () => {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
}
/**
* 函数节流
* @param {*} func 延时调用函数
* @param {*} wait 延迟多长时间
* @param {*} options 至少多长时间触发一次
* @return Function 延迟执行的方法
*/
export function throttle(func, wait, options) {
let leading = true
let trailing = true
if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading
trailing = 'trailing' in options ? !!options.trailing : trailing
}
return debounce(func, wait, {
leading,
trailing,
'maxWait': wait,
})
}