useEffect触发器在具有适当依赖项的情况下多次运行

我有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;
qyrwj 回答:useEffect触发器在具有适当依赖项的情况下多次运行

  useEffect(() => {
    if (mounted) {
      console.log('1st call mounted');
      updateIndicatorState();
    }
  },[mounted,updateIndicatorState]);

只要mountedupdateIndicatorState的值更改,就会触发此效果。

  const updateIndicatorState = useCallback(() => {
     ...
  },[getTabsMeta,indicatorStyle,size,start,transition,vertical]);

如果{p}数组中的任何值更改为updateIndicatorState,则getTabsMeta的值也会更改。

const getTabsMeta = useCallback(() => {
   ...
  },[value,valueToIndex]);

只要getTabsMetavalue更改,valueToIndex的值就会更改。根据我从代码中收集的信息,value是所选选项卡的值,而valueToIndex是在此组件的每个渲染上都重新定义的Map。因此,我希望在每个渲染上也重新定义getTabsMeta的值,这将导致包含updateIndicatorState的useEffect在每个渲染上运行。

本文链接:https://www.f2er.com/3084554.html

大家都在问