如何获得反应元件的宽度


76

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

我在线上浏览了一些普通的JS示例,看来我需要具有元素的宽度才能完成这一工作。

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

几乎等同于JQuery方法 $(element).width()


2
在2020年,我强烈建议改用react-use的useMeasure()。它返回宽度,高度,x位置,y位置等。
Christopher Regner

Answers:


119
    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>
        )
      }
    }

7
我只是想说,显然现在也不建议使用这种创建引用的方式。(现在,您可以使用来在构造函数中创建一个引用React.createRef()
aaaidan

3
@aaaidan刚刚检查了文档。回调引用不被弃用。有时,当您不想覆盖克隆元素上的引用时,就需要它们。
user1164937'9

您想要什么ID获得Div元素的宽度,该元素是React元素而不是原生html元素?
rezKesh

2
感谢您维护“旧版”方式。
skwidbreth

43

挂钩

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。
伊扎基

7
但是,当发生任何调整大小时,它不会得到更新,仅当您的参考当前更改时
MarcoAntônio20年

2
完美的作品。此外,只需添加Typescriptconst ref = useRef<HTMLHeadingElement>(null);
Albert Tjornejoj

23

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

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/>
  )
}

1
您必须将调整大小事件侦听器附加到我认为的窗口:developer.mozilla.org/en-US/docs/Web/API/Window/resize_event
Gerbus

handleResize()on EventListeners上,否则useResize将返回{ 0, 0}
kcNeko

2
不返回初始大小。即仅在调整大小后有效。要修复,useResize需要包括`if(myRef.current){<setWidth SetHeight等,>)
meesern

17

这基本上是MarcoAntô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/>
  )
}

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不是函数
user3808307 '20

1
这意味着由于某种原因,ref最初并未绑定到该元素。一个简单的解决方法是if (!ref.current) return;在使用ref之前添加,并将ref添加到依赖项数组,这样看起来就好像[ref]不是一个空的数组
charri

2
不是getBoundingRect,而是getBoundingClientRect()
Douglas Schmidt

3

这是@meseern答案的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;
}

嗨!如果是受控组件怎么办?
Antenaina

2

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

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>
  )
}

1
您必须将调整大小事件侦听器附加到我认为的窗口:developer.mozilla.org/en-US/docs/Web/API/Window/resize_event
Gerbus

-1

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

React允许您将函数传递给ref,该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>;
 }

每当更改基础节点时,就会触发此函数。更新之间将为null,因此我们需要检查一下。例:

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

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

用法如下(基于MarcoAntô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/>
  )
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.