如何获得反应元素的宽度

Ric*_*iso 51 reactjs

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

我在网上看了一些香草JS的例子,看来我需要有元素的宽度才能完成.

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

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

Ric*_*iso 96

    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>
        )
      }
    }
Run Code Online (Sandbox Code Playgroud)

  • 我只是想说,显然*这种创建引用的方式现在也已被弃用了.(现在用`React.createRef()`在构造函数中创建一个ref (6认同)
  • @aaaidan刚刚检查了文档。回调引用不被弃用。有时,当您不想覆盖克隆元素上的引用时,就需要它们。 (3认同)
  • 感谢您维护“传统”方式。 (2认同)

mee*_*ern 33

这基本上是 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;
};
Run Code Online (Sandbox Code Playgroud)

以同样的方式使用:

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/>
  )
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*nio 30

实际上,最好在自定义 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 }
}
Run Code Online (Sandbox Code Playgroud)

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

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/>
  )
}
Run Code Online (Sandbox Code Playgroud)

  • 不返回初始大小。即仅在调整大小后才起作用。要修复 `useResize` 需要包含 `if (myRef.current) {&lt;setWidth SetHeight etc,&gt;) (3认同)

Nel*_*elu 14

挂钩

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

  • 但是当发生任何大小调整时,当您的 ref.current 发生变化时,它不会更新 (10认同)
  • Eslint 说:React Hook useEffect 有一个不必要的依赖项:“ref.current”。排除它或删除依赖项数组。像“ref.current”这样的可变值不是有效的依赖项,因为改变它们不会重新渲染组件。 (7认同)
  • 那效果很完美。另外,使用 Typescript 只需添加 `const ref = useRef&lt;HTMLHeadingElement&gt;(null);` (6认同)
  • 我会切换到“useLayoutEffect”,并完全跳过“, [ref.current]”(让它更新每个渲染),或者添加“ResizeObserver”或在调整大小时更新的内容。 (3认同)

Tha*_*you 13

2023 年 React 18.x

\n

出于充分的理由,React 18 改变了useEffect工作方式。为组件运行一次初始化代码是有效的,但请阅读You might not need aneffect beforereach for useEffect。要获取元素的尺寸,我们可以使用新的useSyncExternalStore钩子 -

\n
// useDimensions.js\n\nimport { useMemo, useSyncExternalStore } from "react"\n\nfunction subscribe(callback) {\n  window.addEventListener("resize", callback)\n  return () => {\n    window.removeEventListener("resize", callback)\n  }\n}\n\nfunction useDimensions(ref) {\n  const dimensions = useSyncExternalStore(\n    subscribe,\n    () => JSON.stringify({\n      width: ref.current?.offsetWidth ?? 0, // 0 is default width\n      height: ref.current?.offsetHeight ?? 0, // 0 is default height\n    })\n  )\n  return useMemo(() => JSON.parse(dimensions), [dimensions])\n}\n\nexport { useDimensions }\n
Run Code Online (Sandbox Code Playgroud)\n

你可以这样使用它 -

\n
function MyComponent() {\n  const ref = useRef(null)\n  const {width, height} = useDimensions(ref)\n  return <div ref={ref}>\n    The dimensions of this div is {width} x {height}\n  </div>\n}\n
Run Code Online (Sandbox Code Playgroud)\n

为什么 JSON.stringify?

\n

useSyncExternalStore期望getSnapshot函数返回一个缓存的值,否则会导致无限的重新渲染。

\n
{width: 300, height: 200} === {width: 300, height: 200}\n// => false \xe2\x9d\x8c\n
Run Code Online (Sandbox Code Playgroud)\n

JSON.stringify将对象转换为字符串,以便可以建立相等性 -

\n
\'{"width":300,"height":200}\' === \'{"width":300,"height":200}\'\n// => true \xe2\x9c\x85\n
Run Code Online (Sandbox Code Playgroud)\n

最后,该useMemo钩子确保在后续渲染中返回相同尺寸的对象。当dimensions字符串更改时,备忘录会更新,并且使用的组件useDimensions将重新渲染。

\n

尺寸立即可用

\n

这里的其他答案要求用户resize在访问维度之前触发事件。有些人尝试使用内部手动调用来缓解该问题useEffect,但是这些解决方案在 React 18 中失败。使用 的解决方案并非useSyncExternalState如此。享受在第一次渲染时立即访问尺寸的乐趣!

\n

打字稿

\n

这是useDimensions打字稿用户的钩子 -

\n
import { RefObject, useMemo, useSyncExternalStore } from "react"\n\nfunction subscribe(callback: (e: Event) => void) {\n  window.addEventListener("resize", callback)\n  return () => {\n    window.removeEventListener("resize", callback)\n  }\n}\n\nfunction useDimensions(ref: RefObject<HTMLElement>) {\n  const dimensions = useSyncExternalStore(\n    subscribe,\n    () => JSON.stringify({\n      width: ref.current?.offsetWidth ?? 0,\n      height: ref.current?.offsetHeight ?? 0,\n    })\n  )\n  return useMemo(() => JSON.parse(dimensions), [dimensions])\n}\n\nexport { useDimensions }\n
Run Code Online (Sandbox Code Playgroud)\n


cha*_*rri 10

一个简单且最新的解决方案是使用 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>
  )
}
Run Code Online (Sandbox Code Playgroud)

  • 它不是 getBoundingRect,而是 getBoundingClientRect() (3认同)
  • 这意味着由于某种原因,引用在第一次渲染时尚未绑定到元素。一个简单的解决方法是在使用 ref 之前添加 `if (!ref.current) return;` 并将 ref 添加到依赖项数组中,这样它看起来像 `[ref]` 而不是空的 (2认同)

Dom*_*rio 7

如果您需要的只是这个问题的标题:反应元素的宽度,请使用此解决方案。

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

import React from 'react';

import { useMeasure } from 'react-use'; // or just 'react-use-measure'

const sampleView = () => {
 const [ref, { width }] = useMeasure<HTMLDivElement>();
 console.log('Current width of element', width);
 return <div ref={ref}></div>;
};

export default sampleView;
Run Code Online (Sandbox Code Playgroud)


apt*_*lin 5

这是@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;
}
Run Code Online (Sandbox Code Playgroud)