【问题标题】:Invalid hook call. Hooks can only be called inside of the body of a function component in Carousel component无效的挂钩调用。 Hooks 只能在 Carousel 组件中的函数组件内部调用
【发布时间】:2023-03-18 20:06:02
【问题描述】:

我正在使用 useLayoutEffect 构建轮播组件。这个 useLayoutEffect 钩子已经单独设置在resizeWindow.ts 中。并且 resizeWindow 函数在名为 carousel 的功能组件中调用。我找不到违反规则的地方。

//resizeWindow.ts 

  import { useLayoutEffect, useState, RefObject } from 'react'

/***
 * @function resizeWindow
 * this function is custom hook for grab resizing innerWidth of element.
 * 
 * 
 */
export const resizeWindow: (ref: RefObject<HTMLElement>) => number[]  = (ref) => {
  const [ elementWidth, elementHeight ] = ref.current ?  
    [ref.current.offsetWidth, ref.current.offsetHeight ] :
    [0,0];
  const [size, setSize] = useState([elementWidth, elementHeight]);
  useLayoutEffect(() => {
    const updateSize = () => {
      setSize([elementWidth, elementHeight]);
      console.log(`elementWidth: ${elementWidth}px`);
    };
    updateSize(); 
    window.addEventListener('resize', updateSize);

    return () => window.removeEventListener('resize', updateSize);
  },[]);
  return size;    
};

//carousel.ts
//

import { resizeWindow } from './resizeWindow.ts'; 

export const Carousel: FC = ({
  children
}) => {
  const parentRef = useRef<HTMLDivElement>(null); 
  const slideRef = createRef<HTMLDivElement>(); 

  const [count, setCount ] = useState<number>(0); 
  const [parentWidth, setParentWidth] = resizeWindow(parentRef); 

  const total = React.Children.count(children); 
  const nextSlide = () => {
    if( count < total -1 ){
      setCount( count + 1 );
    } else if( count === total-1 ){
      setCount(1); 
    }
  }
  const prevSlide = () => {
    if( count > 0 ){
      setCount( count -1 );
    } else if( count === 0 ){
      setCount(total -1 );
    }
  }
  useEffect(()=> {
    console.log('parentRef: ', parentRef); 
    if(slideRef.current){
      slideRef.current.style.transition = "all 0.5s ease-in-out";
      slideRef.current.style.transform = `translateX(-${count}00%)`;
    }
    if(parentRef.current){
      resizeWindow(parentRef);
    }
  },[count || parentWidth])
  return(
    <SliderContainer ref={parentRef}>
      <Slider ref={slideRef} width={parentWidth * total}>
        {children}        
      </Slider>   
      <Indicator now={1} total={total}/>
      <Button onClick={prevSlide}>left</Button>
      <Button onClick={nextSlide}>right</Button>
    </SliderContainer>
  )
}

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    resizeWindow 是一个自定义钩子,你不应该在 useEffect 钩子中使用它。这种用法会给您带来错误。

    您还必须通过在名称前加上 use 来命名自定义挂钩

    你还必须在 resizeWindow 钩子中解构 updateSize 函数中的 ref 属性,这样你就不会遇到 updateSize 函数中的闭包问题

    更新后的解决方案如下所示

    export const useResizeWindow: (ref: RefObject<HTMLElement>) => number[]  = (ref) => {
      const [size, setSize] = useState([elementWidth, elementHeight]);
      useLayoutEffect(() => {
        const updateSize = () => {
          const [ elementWidth, elementHeight ] = ref.current ?  
           [ref.current.offsetWidth, ref.current.offsetHeight ] :
           [0,0];
          setSize([elementWidth, elementHeight]);
          console.log(`elementWidth: ${elementWidth}px`);
        };
        updateSize(); 
        window.addEventListener('resize', updateSize);
    
        return () => window.removeEventListener('resize', updateSize);
      },[]);
      return size;    
    };
    

    其用法如下

    //carousel.ts
    //
    
    import { useResizeWindow } from './resizeWindow.ts'; 
    
    export const Carousel: FC = ({
      children
    }) => {
      const parentRef = useRef<HTMLDivElement>(null); 
      const slideRef = createRef<HTMLDivElement>(); 
    
      const [count, setCount ] = useState<number>(0); 
      const [parentWidth, setParentWidth] = useResizeWindow(parentRef); 
    
      const total = React.Children.count(children); 
      const nextSlide = () => {
        if( count < total -1 ){
          setCount( count + 1 );
        } else if( count === total-1 ){
          setCount(1); 
        }
      }
      const prevSlide = () => {
        if( count > 0 ){
          setCount( count -1 );
        } else if( count === 0 ){
          setCount(total -1 );
        }
      }
      useEffect(()=> {
        console.log('parentRef: ', parentRef); 
        if(slideRef.current){
          slideRef.current.style.transition = "all 0.5s ease-in-out";
          slideRef.current.style.transform = `translateX(-${count}00%)`;
        }
      },[count || parentWidth])
      return(
        <SliderContainer ref={parentRef}>
          <Slider ref={slideRef} width={parentWidth * total}>
            {children}        
          </Slider>   
          <Indicator now={1} total={total}/>
          <Button onClick={prevSlide}>left</Button>
          <Button onClick={nextSlide}>right</Button>
        </SliderContainer>
      )
    }
    

    【讨论】:

    • 请让我问一件事。自定义钩子在 useEffect 等其他钩子中是唯一的错误用法吗? setState 可以在 useEffect 中使用吗?
    • 你可以在 useEffect 中使用 setState 因为那不是一个钩子。根据钩子的规则,可以有条件地调用钩子,也可以在其他函数内调用钩子。如果您考虑一下,您的自定义挂钩包含 useLayoutEffect ,因此这意味着您在 useEffect 内调用 useLayoutEffect 是一种违规
    猜你喜欢
    • 2019-11-01
    • 1970-01-01
    • 2019-12-12
    • 2022-01-25
    • 1970-01-01
    • 2021-12-18
    • 2020-07-22
    • 2020-02-24
    相关资源
    最近更新 更多