【问题标题】:How to get the width of a react element如何获取反应元素的宽度
【发布时间】:2017-05-06 05:36:35
【问题描述】:

我正在尝试创建一个范围输入,在滑块拇指正上方显示一个工具提示。

我在网上浏览了一些 vanilla JS 示例,看来我需要元素的宽度才能完成。

所以我只是想知道如何获取元素宽度?

几乎相当于JQuery方法$(element).width()

【问题讨论】:

  • 2020 年,我强烈推荐使用 react-use 的 useMeasure() 代替。它返回宽度、高度、x 位置、y 位置等。

标签: reactjs


【解决方案1】:
    class MyComponent extends Component {
      constructor(props){
        super(props)
        this.myInput = React.createRef()
      }

      componentDidMount () {
        console.log(this.myInput.current.offsetWidth)
      }

      render () {
        return (
        // new way - as of React@16.3
        <div ref={this.myInput}>some elem</div>
        // legacy way
        // <div ref={(ref) => this.myInput = ref}>some elem</div>
        )
      }
    }

【讨论】:

  • 我只想说,显然 this 创建 refs 的方式现在也被弃用了。 (现在你用React.createRef()在构造函数中创建一个引用)
  • @aaaidan 刚刚检查了文档。回调引用不被弃用。有时,当您不想覆盖克隆元素上的 ref 时,您需要它们。
  • 你想要什么 id 来获取 Div 元素的宽度,它是 React 元素而不是原生 html 元素?
  • 感谢您维护“传统”方式。
【解决方案2】:

hooks:

const MyComponent = () => {
  const ref = useRef(null);
  useEffect(() => {
    console.log('width', ref.current ? ref.current.offsetWidth : 0);
  }, [ref.current]);
  return <div ref={ref}>Hello</div>;
};

【讨论】:

  • 除了使用useRefuseEffect 之外,您可以在上面的示例中简单地使用回调引用。
  • 但是当你的 ref.current 改变时,它不会在任何调整大小发生时更新
  • 效果很好。此外,使用 Typescript 只需添加 const ref = useRef&lt;HTMLHeadingElement&gt;(null);
  • Eslint 说:React Hook useEffect 有一个不必要的依赖:'ref.current'。排除它或删除依赖数组。像“ref.current”这样的可变值不是有效的依赖项,因为改变它们不会重新渲染组件。
  • 我会切换到useLayoutEffect,或者完全跳过, [ref.current](让它在每次渲染时更新),或者添加ResizeObserver 或在调整大小时更新的东西。
【解决方案3】:

这基本上是 Marco Antônio 对 React 自定义钩子的回答,但经过修改以最初设置尺寸,而不仅仅是在调整大小之后。

export const useContainerDimensions = myRef => {
  const getDimensions = () => ({
    width: myRef.current.offsetWidth,
    height: myRef.current.offsetHeight
  })

  const [dimensions, setDimensions] = useState({ width: 0, height: 0 })

  useEffect(() => {
    const handleResize = () => {
      setDimensions(getDimensions())
    }

    if (myRef.current) {
      setDimensions(getDimensions())
    }

    window.addEventListener("resize", handleResize)

    return () => {
      window.removeEventListener("resize", handleResize)
    }
  }, [myRef])

  return dimensions;
};

同理:

const MyComponent = () => {
  const componentRef = useRef()
  const { width, height } = useContainerDimensions(componentRef)

  return (
    <div ref={componentRef}>
      <p>width: {width}px</p>
      <p>height: {height}px</p>
    <div/>
  )
}

【讨论】:

    【解决方案4】:

    实际上,最好将此调整大小逻辑隔离在 自定义挂钩中。您可以像这样创建自定义挂钩:

    const useResize = (myRef) => {
      const [width, setWidth] = useState(0)
      const [height, setHeight] = useState(0)
    
      useEffect(() => {
        const handleResize = () => {
          setWidth(myRef.current.offsetWidth)
          setHeight(myRef.current.offsetHeight)
        }
    
        window.addEventListener('resize', handleResize)
    
        return () => {
          window.removeEventListener('resize', handleResize)
        }
      }, [myRef])
    
      return { width, height }
    }
    

    然后你可以像这样使用它:

    const MyComponent = () => {
      const componentRef = useRef()
      const { width, height } = useResize(componentRef)
    
      return (
        <div ref={myRef}>
          <p>width: {width}px</p>
          <p>height: {height}px</p>
        <div/>
      )
    }
    

    【讨论】:

    • 您必须将调整大小事件侦听器附加到我认为的窗口:developer.mozilla.org/en-US/docs/Web/API/Window/resize_event
    • handleResize() 在 EventListeners 上,否则 useResize 将返回 { 0, 0}
    • 不返回初始大小。 IE。仅在调整大小后有效。修复useResize 需要包含`if (myRef.current) {)
    【解决方案5】:

    一个简单且最新的解决方案是使用 React React useRef 钩子,它存储对组件/元素的引用,结合 useEffect 钩子,它在组件渲染时触发。

    import React, {useState, useEffect, useRef} from 'react';
    
    export default App = () => {
      const [width, setWidth] = useState(0);
      const elementRef = useRef(null);
    
      useEffect(() => {
        setWidth(elementRef.current.getBoundingClientRect().width);
      }, []); //empty dependency array so it only runs once at render
    
      return (
        <div ref={elementRef}>
          {width}
        </div>
      )
    }
    

    【讨论】:

    • 我得到 columnRef.current.getBoundingRect 不是函数
    • 这意味着由于某种原因,在第一次渲染时 ref 没有绑定到元素。一个简单的解决方法是在使用 ref 之前添加 if (!ref.current) return; 并将 ref 添加到依赖数组中,使其看起来像 [ref] 而不是空的
    • 不是getBoundingRect,是getBoundingClientRect()
    【解决方案6】:

    这是@meseern's answer 的 TypeScript 版本,可避免在重新渲染时进行不必要的分配:

    import React, { useState, useEffect } from 'react';
    
    export function useContainerDimensions(myRef: React.RefObject<any>) {
      const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
    
      useEffect(() => {
        const getDimensions = () => ({
          width: (myRef && myRef.current.offsetWidth) || 0,
          height: (myRef && myRef.current.offsetHeight) || 0,
        });
    
        const handleResize = () => {
          setDimensions(getDimensions());
        };
    
        if (myRef.current) {
          setDimensions(getDimensions());
        }
    
        window.addEventListener('resize', handleResize);
    
        return () => {
          window.removeEventListener('resize', handleResize);
        };
      }, [myRef]);
    
      return dimensions;
    }
    

    【讨论】:

    • 嗨!如果它是受控组件怎么办?
    【解决方案7】:

    这可以通过使用 callback refs 以更简单的方式处理。

    React 允许你将一个函数传递给一个 ref,它返回底层的 DOM 元素或组件节点。见:https://reactjs.org/docs/refs-and-the-dom.html#callback-refs

    const MyComponent = () => {
        const myRef = node => console.log(node ? node.innerText : 'NULL!');
        return <div ref={myRef}>Hello World</div>;
     }
    

    只要底层节点发生变化,这个函数就会被触发。在更新之间它将为空,因此我们需要检查这一点。示例:

    const MyComponent = () => {
        const [time, setTime] = React.useState(123);
        const myRef = node => console.log(node ? node.innerText : 'NULL!');
        setTimeout(() => setTime(time+1), 1000);
        return <div ref={myRef}>Hello World {time}</div>;
    }
    /*** Console output: 
     Hello World 123
     NULL!
     Hello World 124
     NULL!
    ...etc
    ***/
    

    虽然这不能像这样处理调整大小(我们仍然需要一个调整大小侦听器来处理用户调整窗口大小),但我不确定这就是 OP 所要求的。此版本将处理因更新而调整的节点大小。

    所以这里有一个基于这个想法的自定义钩子:

    export const useClientRect = () => {
        const [rect, setRect] = useState({width:0, height:0});
        const ref = useCallback(node => {
            if (node !== null) {
                const { width, height } = node.getBoundingClientRect();
                setRect({ width, height });
            }
        }, []);
        return [rect, ref];
    };
    

    以上基于https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

    注意钩子返回一个引用回调,而不是传递一个引用。我们使用 useCallback 来避免每次都重新创建一个新的 ref 函数;不重要,但被认为是良好的做法。

    用法是这样的(基于 Marco Antônio 的示例):

    const MyComponent = ({children}) => {
      const [rect, myRef] = useClientRect();
      const { width, height } = rect;
    
      return (
        <div ref={myRef}>
          <p>width: {width}px</p>
          <p>height: {height}px</p>
          {children}
        <div/>
      )
    }
    

    【讨论】:

      【解决方案8】:

      反应钩子:

      import React, { useState, useEffect,useRef } from 'react';
      ...
      const table1ref = useRef(null);
      const [table1Size, table1SizeSet] = useState({
        width: undefined,
        height: undefined,
      });
      
      useEffect(() => {
          function handleResize() {
            table1SizeSet({
              width: table1ref.current.offsetWidth,
              height: table1ref.current.offsetHeight,
            });
          }
          window.addEventListener("resize", handleResize);
          handleResize();
          return () => window.removeEventListener("resize", handleResize);        
        }, [ ]);
      ...
      <div  ref={table1ref}>
      

      然后打电话:

      {table1Size.width}
      

      当你想使用时。

      【讨论】:

        【解决方案9】:

        只是为了补充克里斯托弗的评论。您可以使用“react-use”库来执行此操作。它还会在浏览器调整大小时进行侦听。供参考:https://github.com/streamich/react-use/blob/master/docs/useMeasure.md

        import React from 'react';
        
        import { useMeasure } from 'react-use';
        
        const sampleView = () => {
         const [ref, { width }] = useMeasure<HTMLDivElement>();
         console.log('Current width of element', width);
         return <div ref={ref}></div>;
        };
        
        export default sampleView;
        

        【讨论】:

        【解决方案10】:

        一个好的做法是监听调整大小事件,以防止在渲染时调整大小,甚至是用户窗口调整大小,这可能会导致您的应用程序出错。

        const MyComponent = ()=> {
          const myRef = useRef(null)
        
          const [myComponenetWidth, setMyComponentWidth] = useState('')
        
          const handleResize = ()=>{ 
            setMyComponentWidth(myRef.current.offsetWidth)
          }
        
          useEffect(() =>{
            if(myRef.current)
              myRef.current.addEventListener('resize', handleResize)
        
            return ()=> {
             myRef.current.removeEventListener('resize', handleResize)
            }
          }, [myRef])
        
          return (
          <div ref={MyRef}>Hello</div>
          )
        }
        

        【讨论】:

        • 您必须将调整大小事件侦听器附加到我认为的窗口:developer.mozilla.org/en-US/docs/Web/API/Window/resize_event
        猜你喜欢
        • 1970-01-01
        • 2012-07-27
        • 2017-08-24
        • 1970-01-01
        • 2019-12-29
        • 2011-06-08
        • 1970-01-01
        • 2017-01-30
        相关资源
        最近更新 更多