我有Tabs组件,它具有子Tab组件。安装后,它会计算选项卡和选定选项卡的元数据。然后设置选项卡指示器的样式。由于某种原因,每次活动选项卡更改时,函数updateIndicatorState都会在useEffect挂钩中触发多次,并且应该仅触发一次。有人可以解释一下我在做什么错吗?如果我从第二个dep中移除,则useEffect挂钩函数本身并添加一个值prop作为dep。它只能正确触发一次。但就我所读过的react文档而言,我不应该欺骗useEffect依赖数组,还有更好的解决方案来避免这种情况。
import React,{ useRef,useEffect,useState,useCallback } from 'react';
import PropTypes from 'prop-types';
import { defProperty } from 'helpers';
const Tabs = ({ children,value,orientation,onChange }) => {
console.log(value);
const indicatorRef = useRef(null);
const tabsRef = useRef(null);
const childrenWrapperRef = useRef(null);
const valueToIndex = new Map();
const vertical = orientation === 'vertical';
const start = vertical ? 'top' : 'left';
const size = vertical ? 'height' : 'width';
const [mounted,setMounted] = useState(false);
const [indicatorStyle,setIndicatorStyle] = useState({});
const [transition,setTransition] = useState('none');
const getTabsMeta = useCallback(() => {
console.log('getTabsMeta');
const tabsnode = tabsRef.current;
let tabsMeta;
if (tabsnode) {
const rect = tabsnode.getBoundingClientRect();
tabsMeta = {
clientWidth: tabsnode.clientWidth,scrollLeft: tabsnode.scrollLeft,scrollTop: tabsnode.scrollTop,scrollWidth: tabsnode.scrollWidth,top: rect.top,bottom: rect.bottom,left: rect.left,right: rect.right,};
}
let tabMeta;
if (tabsnode && value !== false) {
const wrapperChildren = childrenWrapperRef.current.children;
if (wrapperChildren.length > 0) {
const tab = wrapperChildren[valueToIndex.get(value)];
tabMeta = tab ? tab.getBoundingClientRect() : null;
}
}
return {
tabsMeta,tabMeta,};
},[value,valueToIndex]);
const updateIndicatorState = useCallback(() => {
console.log('updateIndicatorState');
let _newIndicatorStyle;
const { tabsMeta,tabMeta } = getTabsMeta();
let startvalue;
if (tabMeta && tabsMeta) {
if (vertical) {
startvalue = tabMeta.top - tabsMeta.top + tabsMeta.scrollTop;
} else {
startvalue = tabMeta.left - tabsMeta.left;
}
}
const newIndicatorStyle =
((_newIndicatorStyle = {}),defProperty(_newIndicatorStyle,start,startvalue),size,tabMeta ? tabMeta[size] : 0),_newIndicatorStyle);
if (isnaN(indicatorStyle[start]) || isnaN(indicatorStyle[size])) {
setIndicatorStyle(newIndicatorStyle);
} else {
const dStart = Math.abs(indicatorStyle[start] - newIndicatorStyle[start]);
const dSize = Math.abs(indicatorStyle[size] - newIndicatorStyle[size]);
if (dStart >= 1 || dSize >= 1) {
setIndicatorStyle(newIndicatorStyle);
if (transition === 'none') {
setTransition(`${[start]} 0.3s ease-in-out`);
}
}
}
},[getTabsMeta,indicatorStyle,transition,vertical]);
useEffect(() => {
const timeout = setTimeout(() => {
setMounted(true);
},350);
return () => {
clearTimeout(timeout);
};
},[]);
useEffect(() => {
if (mounted) {
console.log('1st call mounted');
updateIndicatorState();
}
},[mounted,updateIndicatorState]);
let childIndex = 0;
const childrenItems = React.Children.map(children,child => {
const childValue = child.props.value === undefined ? childIndex : child.props.value;
valueToIndex.set(childValue,childIndex);
const selected = childValue === value;
childIndex += 1;
return React.cloneElement(child,{
selected,indicator: selected && !mounted,value: childValue,onChange,});
});
const styles = {
[size]: `${indicatorStyle[size]}px`,[start]: `${indicatorStyle[start]}px`,};
console.log(styles);
return (
<>
{value !== 2 ? (
<div classname={`tabs tabs--${orientation}`} ref={tabsRef}>
<span classname="tab__indicator-wrapper">
<span classname="tab__indicator" ref={indicatorRef} style={styles} />
</span>
<div classname="tabs__wrapper" ref={childrenWrapperRef}>
{childrenItems}
</div>
</div>
) : null}
</>
);
};
Tabs.defaultProps = {
orientation: 'horizontal',};
Tabs.propTypes = {
children: PropTypes.node.isrequired,value: PropTypes.number.isrequired,orientation: PropTypes.oneOf(['horizontal','vertical']),onChange: PropTypes.func.isrequired,};
export default Tabs;