ReactJS - รับความสูงขององค์ประกอบ


121

ฉันจะได้รับความสูงขององค์ประกอบหลังจากที่ React แสดงผลองค์ประกอบนั้นได้อย่างไร

HTML

<div id="container">
<!-- This element's contents will be replaced with your component. -->
<p>
jnknwqkjnkj<br>
jhiwhiw (this is 36px height)
</p>
</div>

ตอบสนอง

var DivSize = React.createClass({

  render: function() {
    let elHeight = document.getElementById('container').clientHeight
    return <div className="test">Size: <b>{elHeight}px</b> but it should be 18px after the render</div>;
  }
});

ReactDOM.render(
  <DivSize />,
  document.getElementById('container')
);

ผลลัพธ์

Size: 36px but it should be 18px after the render

กำลังคำนวณความสูงของคอนเทนเนอร์ก่อนการแสดงผล (36px) ฉันต้องการได้รับความสูงหลังจากการแสดงผล ผลลัพธ์ที่ถูกต้องควรเป็น 18px ในกรณีนี้ jsfiddle


1
นี่ไม่ใช่คำถามตอบกลับ แต่เป็นคำถาม Javascript และ DOM คุณควรพยายามหาว่าเหตุการณ์ DOM ใดที่คุณควรใช้เพื่อหาความสูงสุดท้ายขององค์ประกอบของคุณ ในการจัดการเหตุการณ์ที่คุณสามารถใช้setStateในการกำหนดค่าความสูงให้กับตัวแปรรัฐ
mostruash

คำตอบ:


28

ดูซอนี้ (ปรับปรุงจริงของคุณ)

คุณต้องเชื่อมต่อcomponentDidMountซึ่งรันหลังจากวิธีการแสดงผล คุณจะได้รับความสูงจริงขององค์ประกอบ

var DivSize = React.createClass({
    getInitialState() {
    return { state: 0 };
  },

  componentDidMount() {
    const height = document.getElementById('container').clientHeight;
    this.setState({ height });
  },

  render: function() {
    return (
        <div className="test">
        Size: <b>{this.state.height}px</b> but it should be 18px after the render
      </div>
    );
  }
});

ReactDOM.render(
  <DivSize />,
  document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>

<div id="container">
<p>
jnknwqkjnkj<br>
jhiwhiw (this is 36px height)
</p>
    <!-- This element's contents will be replaced with your component. -->
</div>

16
ไม่ถูกต้อง. ดูคำตอบของ Paul Vincent Beigang ด้านล่าง
ekatz

1
IMHO ส่วนประกอบไม่ควรทราบชื่อคอนเทนเนอร์
Andrés

157

ต่อไปนี้เป็นตัวอย่างถึงวัน ES6 ใช้โทษ

โปรดจำไว้ว่าเราต้องใช้องค์ประกอบคลาส Reactเนื่องจากเราจำเป็นต้องเข้าถึงเมธอด Lifecycle componentDidMount()เนื่องจากเราสามารถกำหนดความสูงขององค์ประกอบได้หลังจากแสดงผลใน DOM แล้วเท่านั้น

import React, {Component} from 'react'
import {render} from 'react-dom'

class DivSize extends Component {

  constructor(props) {
    super(props)

    this.state = {
      height: 0
    }
  }

  componentDidMount() {
    const height = this.divElement.clientHeight;
    this.setState({ height });
  }

  render() {
    return (
      <div 
        className="test"
        ref={ (divElement) => { this.divElement = divElement } }
      >
        Size: <b>{this.state.height}px</b> but it should be 18px after the render
      </div>
    )
  }
}

render(<DivSize />, document.querySelector('#container'))

คุณสามารถดูตัวอย่างการทำงานได้ที่นี่: https://codepen.io/bassgang/pen/povzjKw


6
วิธีนี้ใช้ได้ผล แต่ฉันได้รับข้อผิดพลาด eslint หลายประการสำหรับ Airbnb styleguide: การ<div ref={ divElement => this.divElement = divElement}>{children}</div>พ่น "[eslint] ฟังก์ชัน Arrow ไม่ควรส่งคืนการมอบหมาย (no-return-assign)" และthis.setState({ height });พ่น "[eslint] อย่าใช้ setState ใน componentDidMount (react / no- ไม่ติดตั้งรัฐ) " ใครมีวิธีแก้ไขตาม styleguide?
Timo

7
ห่องานที่กำหนดไว้ในวงเล็บปีกกาเพื่อให้ทำงานเป็นฟังก์ชัน (แทนที่จะเป็นนิพจน์) เช่นนี้ref={ divElement => {this.divElement = divElement}}
teletypist

3
นี่น่าจะเป็นคำตอบที่ได้รับการยอมรับ :) นอกจากนี้หากคุณต้องการรับขนาดขององค์ประกอบหลังจากอัปเดตองค์ประกอบแล้วคุณสามารถใช้วิธีนี้ได้componentDidUpdateเช่นกัน
jjimenez

4
มีทางเลือกอื่นในการเรียก this.setState () ใน componentDidMount ด้วยตัวอย่างนี้หรือไม่
danjones_mcr

3
ทางออกที่ดี. แต่จะใช้ไม่ได้กับการออกแบบที่ตอบสนอง หากความสูงของส่วนหัวคือ 50px สำหรับเดสก์ท็อปและผู้ใช้ย่อขนาดหน้าต่างและตอนนี้ความสูงกลายเป็น 100px โซลูชันนี้จะไม่ใช้ความสูงที่อัปเดต พวกเขาต้องรีเฟรชหน้าเพื่อรับเอฟเฟกต์ที่เปลี่ยนแปลง
TheCoder

100

สำหรับผู้ที่สนใจใช้react hooksสิ่งนี้อาจช่วยคุณในการเริ่มต้น

import React, { useState, useEffect, useRef } from 'react'

export default () => {
  const [height, setHeight] = useState(0)
  const ref = useRef(null)

  useEffect(() => {
    setHeight(ref.current.clientHeight)
  })

  return (
    <div ref={ref}>
      {height}
    </div>
  )
}

11
ขอบคุณสำหรับคำตอบซึ่งช่วยให้ฉันเริ่มต้นได้ คำถาม 2 ข้อ: (1) สำหรับงานวัดเค้าโครงเหล่านี้เราควรใช้useLayoutEffectแทนuseEffect? เอกสารดูเหมือนจะบ่งชี้ว่าเราควร ดูส่วน "เคล็ดลับ" ที่นี่reactjs.org/docs/hooks-effect.html#detailed-explanation (2) สำหรับกรณีเหล่านี้ที่เราต้องการเพียงแค่การอ้างอิงถึงองค์ประกอบย่อยเราควรใช้useRefแทนcreateRef? ดูเหมือนว่าเอกสารจะระบุว่าuseRefเหมาะสมกว่าสำหรับกรณีการใช้งานนี้ ดู reactjs.org/docs/hooks-reference.html#useref
Jason Frank

ฉันเพิ่งใช้โซลูชันที่ใช้สองรายการที่ฉันแนะนำ ดูเหมือนว่าจะทำงานได้ดี แต่คุณอาจทราบถึงข้อเสียของตัวเลือกเหล่านี้บ้าง? ขอบคุณสำหรับความช่วยเหลือของคุณ!
Jason Frank

4
@JasonFrank คำถามดีๆ 1) ทั้ง useEffect และ useLayoutEffect จะเริ่มทำงานหลังจากเค้าโครงและระบายสี อย่างไรก็ตาม useLayoutEffect จะเริ่มทำงานพร้อมกันก่อนทาสีถัดไป ฉันจะบอกว่าถ้าคุณต้องการความสอดคล้องของภาพจริงๆให้ใช้ useLayoutEffect มิฉะนั้น useEffect ก็น่าจะเพียงพอ 2) คุณพูดถูก! ในคอมโพเนนต์ที่ใช้งานได้ createRef จะสร้างการอ้างอิงใหม่ทุกครั้งที่กำลังแสดงผลคอมโพเนนต์ การใช้ useRef เป็นตัวเลือกที่ดีกว่า ฉันจะอัปเดตคำตอบของฉัน ขอบคุณ!
นาย 14

สิ่งนี้ช่วยให้ฉันเข้าใจวิธีใช้การอ้างอิงกับตะขอ ฉันลงเอยด้วยuseLayoutEffectหลังจากอ่านความคิดเห็นอื่น ๆ ที่นี่ ดูเหมือนจะทำงานได้ดี :)
GuiDoody

1
ผมได้รับการตั้งค่าขนาดขององค์ประกอบของฉันuseEffect(() => setDimensions({width: popup.current.clientWidth, height: popup.current.clientHeight}))และ IDE ของฉัน (WebStorm) แนะนำฉันนี้: ESLint: React ตะขอ useEffect มีการเรียกร้องให้ 'setDimensions' หากไม่มีรายการการอ้างอิงสิ่งนี้อาจนำไปสู่การอัปเดตที่ไม่สิ้นสุด ในการแก้ไขปัญหานี้ให้ส่ง [] เป็นอาร์กิวเมนต์ที่สองไปยัง useEffect Hook (react-hooks / exhaustive-deps)ดังนั้นส่งต่อ[]เป็นอาร์กิวเมนต์ที่สองให้กับคุณuseEffect
Akshdeep Singh

9

คุณยังต้องการใช้ refs กับองค์ประกอบแทนการใช้document.getElementByIdมันเป็นเพียงสิ่งที่แข็งแกร่งกว่าเล็กน้อย


@doublejosh โดยพื้นฐานแล้วคุณพูดถูก แต่จะทำอย่างไรถ้าคุณต้องการมิกซ์อัพเช่นangularJsและreactในขณะที่ย้ายจากที่หนึ่งไปอีกที่หนึ่ง? มีหลายกรณีที่จำเป็นต้องมีการจัดการ DOM โดยตรง
messerbill

3

คำตอบของฉันในปี 2020 (หรือ 2019)

import React, {Component, useRef, useLayoutEffect} from 'react';
import { useDispatch } from 'react-redux';
import { Toast, ToastBody, ToastHeader } from 'reactstrap';

import {WidgetHead} from './WidgetHead';

export const Widget = ({title, toggle, reload, children, width, name}) => {
    let myself = useRef(null);
    const dispatch = useDispatch();
    useLayoutEffect(()=>{
        if (myself.current) {
            const height = myself.current.clientHeight
            dispatch({type:'GRID_WIDGET_HEIGHT', widget:name, height})
        }
    }, [myself.current, myself.current?myself.current.clientHeight:0])

    return (
        <Toast innerRef={myself}>
            <WidgetHead title={title}
                toggle={toggle}
                reload={reload} />
            <ToastBody>
            {children}
            </ToastBody>
        </Toast>
    )
}

ให้ใช้จินตนาการของคุณสำหรับสิ่งที่ขาดหายไปที่นี่ (WidgetHead) reactstrapเป็นสิ่งที่คุณสามารถพบได้ใน npm: แทนที่innerRefด้วยrefองค์ประกอบ Dom แบบเดิม (พูด a <div>)

useEffect หรือ useLayoutEffect

สุดท้ายกล่าวว่าซิงโครนัสสำหรับการเปลี่ยนแปลง

useLayoutEffect(หรือuseEffect) อาร์กิวเมนต์ที่สอง

อาร์กิวเมนต์ที่สองคืออาร์เรย์และมีการตรวจสอบก่อนเรียกใช้ฟังก์ชันในอาร์กิวเมนต์แรก

ฉันใช้

[ตัวเองปัจจุบัน, ตัวเองปัจจุบัน? ตัวเอง.current.clientHeight: 0]

เนื่องจากตัวเองปัจจุบันเป็นโมฆะก่อนที่จะแสดงผลและนั่นเป็นสิ่งที่ดีที่จะไม่ตรวจสอบพารามิเตอร์ที่สองในตอนท้ายmyself.current.clientHeightคือสิ่งที่ฉันต้องการตรวจสอบการเปลี่ยนแปลง

สิ่งที่ฉันกำลังแก้อยู่ที่นี่ (หรือพยายามแก้)

ฉันกำลังแก้ปัญหาของวิดเจ็ตบนกริดที่เปลี่ยนความสูงตามความต้องการของตัวเองและระบบกริดควรยืดหยุ่นพอที่จะตอบสนอง ( https://github.com/STRML/react-grid-layout )


1
คำตอบที่ดี แต่จะได้รับประโยชน์จากตัวอย่างที่ง่ายกว่า โดยพื้นฐานแล้วคำตอบของ whiteknight แต่มีuseLayoutEffectdiv จริงเพื่อผูกการอ้างอิง
บอ

2

แทนที่จะใช้document.getElementById(...)วิธีแก้ปัญหาที่ดีกว่า (ล่าสุด) คือการใช้ React useRef hook ที่เก็บการอ้างอิงไปยัง component / element รวมกับuseEffect hook ซึ่งจะเริ่มทำงานที่การแสดงผลส่วนประกอบ

import React, {useState, useEffect, useRef} from 'react';

export default App = () => {
  const [height, setHeight] = useState(0);
  const elementRef = useRef(null);

  useEffect(() => {
    setHeight(ref.current.clientHeight);
  }, []); //empty dependency array so it only runs once at render

  return (
    <div ref={elementRef}>
      {height}
    </div>
  )
}

1

ใช้กับตะขอ:

คำตอบนี้จะเป็นประโยชน์หากมิติเนื้อหาของคุณเปลี่ยนไปหลังจากโหลด

onreadystatechange : เกิดขึ้นเมื่อสถานะการโหลดของข้อมูลที่เป็นขององค์ประกอบหรือเอกสาร HTML เปลี่ยนไป เหตุการณ์ onreadystatechange จะเริ่มทำงานบนเอกสาร HTML เมื่อสถานะการโหลดเนื้อหาของเพจเปลี่ยนไป

import {useState, useEffect, useRef} from 'react';
const ref = useRef();
useEffect(() => {
    document.onreadystatechange = () => {
      console.log(ref.current.clientHeight);
    };
  }, []);

ฉันพยายามทำงานกับโปรแกรมเล่นวิดีโอ youtube ที่มีขนาดอาจเปลี่ยนแปลงหลังจากโหลด


0

ฉันพบแพ็คเกจ npm ที่มีประโยชน์https://www.npmjs.com/package/element-resize-detector

ตัวฟังการปรับขนาดข้ามเบราว์เซอร์ที่ได้รับการปรับให้เหมาะสมสำหรับองค์ประกอบ

สามารถใช้กับ React component หรือ functional component (มีประโยชน์เป็นพิเศษสำหรับ react hooks)


0

นี่คืออีกสิ่งหนึ่งหากคุณต้องการเหตุการณ์ปรับขนาดหน้าต่าง:

class DivSize extends React.Component {

  constructor(props) {
    super(props)

    this.state = {
      width: 0,
      height: 0
    }
    this.resizeHandler = this.resizeHandler.bind(this);
  }

  resizeHandler() {
    const width = this.divElement.clientWidth;
    const height = this.divElement.clientHeight;
    this.setState({ width, height });
  }

  componentDidMount() {
    this.resizeHandler();
    window.addEventListener('resize', this.resizeHandler);
  }

  componentWillUnmount(){
    window.removeEventListener('resize', this.resizeHandler);
  }

  render() {
    return (
      <div 
        className="test"
        ref={ (divElement) => { this.divElement = divElement } }
      >
        Size: widht: <b>{this.state.width}px</b>, height: <b>{this.state.height}px</b>
      </div>
    )
  }
}

ReactDOM.render(<DivSize />, document.querySelector('#container'))

ปากการหัส


0

โซลูชันทางเลือกในกรณีที่คุณต้องการเรียกขนาดของ React องค์ประกอบพร้อมกันโดยไม่ต้องเห็นได้ชัดทำให้องค์ประกอบคุณสามารถใช้ReactDOMServerและDOMParser

ฉันจะใช้ฟังก์ชั่นนี้เพื่อให้ได้ความสูงของ renderer รายการของฉันเมื่อใช้ตอบสนองหน้าต่าง ( ตอบสนองเสมือนจริง ) แทนที่จะต้อง hardcode ที่จำเป็นต้องใช้itemSizeไม้ค้ำยันสำหรับFixedSizeList

ยูทิลิตี้ js:

/**
 * @description Common and reusable functions 
 * 
 * @requires react-dom/server
 * 
 * @public
 * @module
 * 
 */
import ReactDOMServer from "react-dom/server";

/**
 * @description Retrieve the width and/or heigh of a React element without rendering and committing the element to the DOM.
 * 
 * @param {object} elementJSX - The target React element written in JSX.
 * @return {object} 
 * @public
 * @function
 * 
 * @example
 * 
 * const { width, height } = getReactElementSize( <div style={{ width: "20px", height: "40px" }} ...props /> );
 * console.log(`W: ${width}, H: ${height});  // W: 20, H: 40
 * 
 */
const getReactElementSize = (elementJSX) => {

    const elementString = ReactDOMServer.renderToStaticMarkup(elementJSX);
    const elementDocument = new DOMParser().parseFromString(elementString, "text/html");
    const elementNode = elementDocument.getRootNode().body.firstChild;

    const container = document.createElement("div");
    const containerStyle = {

        display: "block",
        position: "absolute",
        boxSizing: "border-box",
        margin: "0",
        padding: "0",
        visibility: "hidden"
    };

    Object.assign(container.style, containerStyle);

    container.appendChild(elementNode);
    document.body.appendChild(container);

    const width = container.clientWidth;
    const height = container.clientHeight;

    container.removeChild(elementNode);
    document.body.removeChild(container);

    return {

        width,
        height
    };
};

/**
 * Export module
 * 
 */
export {

    getReactElementSize
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.