ฉันจะตอบสนองต่อความกว้างขององค์ประกอบ DOM ที่ปรับขนาดอัตโนมัติใน React ได้อย่างไร


86

ฉันมีหน้าเว็บที่ซับซ้อนโดยใช้ส่วนประกอบ React และกำลังพยายามแปลงหน้าจากเค้าโครงแบบคงที่เป็นเค้าโครงที่ตอบสนองและปรับขนาดได้มากขึ้น อย่างไรก็ตามฉันพบข้อ จำกัด ของ React อยู่เรื่อย ๆ และฉันสงสัยว่ามีรูปแบบมาตรฐานสำหรับจัดการปัญหาเหล่านี้หรือไม่ ในกรณีเฉพาะของฉันฉันมีส่วนประกอบที่แสดงผลเป็น div พร้อม display: table-cell และ width: auto

น่าเสียดายที่ฉันไม่สามารถสืบค้นความกว้างขององค์ประกอบของฉันได้เนื่องจากคุณไม่สามารถคำนวณขนาดขององค์ประกอบได้เว้นแต่จะวางไว้ใน DOM (ซึ่งมีบริบททั้งหมดที่จะอนุมานความกว้างที่แสดงผลจริง) นอกเหนือจากการใช้สิ่งนี้สำหรับสิ่งต่างๆเช่นการวางตำแหน่งเมาส์แบบสัมพัทธ์แล้วฉันยังต้องการสิ่งนี้เพื่อตั้งค่าแอตทริบิวต์ความกว้างอย่างเหมาะสมในองค์ประกอบ SVG ภายในส่วนประกอบ

นอกจากนี้เมื่อปรับขนาดหน้าต่างฉันจะสื่อสารการเปลี่ยนแปลงขนาดจากส่วนประกอบหนึ่งไปยังอีกส่วนหนึ่งในระหว่างการตั้งค่าได้อย่างไร เรากำลังทำการเรนเดอร์ SVG ของบุคคลที่สามทั้งหมดใน shouldComponentUpdate แต่คุณไม่สามารถตั้งค่าสถานะหรือคุณสมบัติของตัวคุณเองหรือส่วนประกอบย่อยอื่น ๆ ภายในวิธีการนั้นได้

มีวิธีมาตรฐานในการจัดการกับปัญหานี้โดยใช้ React หรือไม่?


1
อย่างไรก็ตามคุณแน่ใจหรือไม่ว่าshouldComponentUpdateเป็นสถานที่ที่ดีที่สุดในการแสดง SVG มันเสียงเหมือนสิ่งที่คุณต้องการเป็นcomponentWillReceivePropsหรือถ้าไม่ได้componentWillUpdate render
Andy

1
นี่อาจจะใช่หรือไม่ใช่สิ่งที่คุณกำลังมองหา แต่มีไลบรารีที่ยอดเยี่ยมสำหรับสิ่งนี้: github.com/bvaughn/react-virtualized ลองดูที่ส่วนประกอบ AutoSizer มันจะจัดการความกว้างและ / หรือความสูงโดยอัตโนมัติดังนั้นคุณจึงไม่จำเป็นต้องทำ
Maggie

@Maggie ตรวจสอบgithub.com/souporserious/react-measureด้วยมันเป็นไลบรารีแบบสแตนด์อโลนสำหรับจุดประสงค์นี้และจะไม่ใส่สิ่งอื่น ๆ ที่ไม่ได้ใช้ลงในกลุ่มไคลเอ็นต์ของคุณ
Andy

เฮ้ฉันตอบคำถามที่คล้ายกันที่นี่มันเป็นวิธีการที่แตกต่างออกไปและให้คุณตัดสินใจว่าจะแสดงผลอะไรขึ้นอยู่กับประเภท scren ของคุณ (มือถือแท็บเล็ตเดสก์ท็อป)
Calin ortan

@ Maggie ฉันอาจจะผิดเกี่ยวกับเรื่องนี้ แต่ฉันคิดว่า Auto Sizer พยายามเติมเต็มแม่ของมันเสมอแทนที่จะตรวจสอบขนาดที่เด็กได้รับเพื่อให้พอดีกับเนื้อหา ทั้งสองอย่างมีประโยชน์ในสถานการณ์ที่แตกต่างกันเล็กน้อย
Andy

คำตอบ:


65

การแก้ปัญหาการปฏิบัติมากที่สุดคือการใช้ห้องสมุดสำหรับเช่นนี้ตอบสนองวัด

ปรับปรุง : ตอนนี้จะมีตะขอที่กำหนดเองสำหรับการตรวจสอบการปรับขนาด (ซึ่งผมไม่ได้พยายามส่วนตัว): ตอบสนองปรับขนาดตระหนักถึง react-measureเป็นตะขอที่กำหนดเองมันดูสะดวกสบายในการใช้งานมากกว่า

import * as React from 'react'
import Measure from 'react-measure'

const MeasuredComp = () => (
  <Measure bounds>
    {({ measureRef, contentRect: { bounds: { width }} }) => (
      <div ref={measureRef}>My width is {width}</div>
    )}
  </Measure>
)

ในการสื่อสารการเปลี่ยนแปลงขนาดระหว่างส่วนประกอบคุณสามารถส่งการonResizeโทรกลับและเก็บค่าที่ได้รับไว้ที่ใดที่หนึ่ง (วิธีมาตรฐานในการแบ่งปันสถานะในปัจจุบันคือการใช้Redux ):

import * as React from 'react'
import Measure from 'react-measure'
import { useSelector, useDispatch } from 'react-redux'
import { setMyCompWidth } from './actions' // some action that stores width in somewhere in redux state

export default function MyComp(props) {
  const width = useSelector(state => state.myCompWidth) 
  const dispatch = useDispatch()
  const handleResize = React.useCallback(
    (({ contentRect })) => dispatch(setMyCompWidth(contentRect.bounds.width)),
    [dispatch]
  )

  return (
    <Measure bounds onResize={handleResize}>
      {({ measureRef }) => (
        <div ref={measureRef}>MyComp width is {width}</div>
      )}
    </Measure>
  )
}

วิธีการม้วนของคุณเองหากคุณต้องการ:

สร้างคอมโพเนนต์ Wrapper ที่จัดการการรับค่าจาก DOM และการรับฟังเหตุการณ์การปรับขนาดหน้าต่าง (หรือการตรวจจับการปรับขนาดคอมโพเนนต์ที่ใช้โดย react-measure ) คุณบอกได้ว่าอุปกรณ์ประกอบฉากใดที่จะได้รับจาก DOM และจัดเตรียมฟังก์ชันการเรนเดอร์โดยนำอุปกรณ์ประกอบฉากเหล่านั้นมาเป็นเด็ก

สิ่งที่คุณแสดงจะต้องติดตั้งก่อนที่จะอ่านอุปกรณ์ประกอบฉาก DOM เมื่ออุปกรณ์ประกอบฉากเหล่านั้นไม่สามารถใช้งานได้ในระหว่างการเรนเดอร์ครั้งแรกคุณอาจต้องการใช้style={{visibility: 'hidden'}}เพื่อให้ผู้ใช้มองไม่เห็นก่อนที่จะได้รับเลย์เอาต์ที่คำนวณด้วย JS

// @flow

import React, {Component} from 'react';
import shallowEqual from 'shallowequal';
import throttle from 'lodash.throttle';

type DefaultProps = {
  component: ReactClass<any>,
};

type Props = {
  domProps?: Array<string>,
  computedStyleProps?: Array<string>,
  children: (state: State) => ?React.Element<any>,
  component: ReactClass<any>,
};

type State = {
  remeasure: () => void,
  computedStyle?: Object,
  [domProp: string]: any,
};

export default class Responsive extends Component<DefaultProps,Props,State> {
  static defaultProps = {
    component: 'div',
  };

  remeasure: () => void = throttle(() => {
    const {root} = this;
    if (!root) return;
    const {domProps, computedStyleProps} = this.props;
    const nextState: $Shape<State> = {};
    if (domProps) domProps.forEach(prop => nextState[prop] = root[prop]);
    if (computedStyleProps) {
      nextState.computedStyle = {};
      const computedStyle = getComputedStyle(root);
      computedStyleProps.forEach(prop => 
        nextState.computedStyle[prop] = computedStyle[prop]
      );
    }
    this.setState(nextState);
  }, 500);
  // put remeasure in state just so that it gets passed to child 
  // function along with computedStyle and domProps
  state: State = {remeasure: this.remeasure};
  root: ?Object;

  componentDidMount() {
    this.remeasure();
    this.remeasure.flush();
    window.addEventListener('resize', this.remeasure);
  }
  componentWillReceiveProps(nextProps: Props) {
    if (!shallowEqual(this.props.domProps, nextProps.domProps) || 
        !shallowEqual(this.props.computedStyleProps, nextProps.computedStyleProps)) {
      this.remeasure();
    }
  }
  componentWillUnmount() {
    this.remeasure.cancel();
    window.removeEventListener('resize', this.remeasure);
  }
  render(): ?React.Element<any> {
    const {props: {children, component: Comp}, state} = this;
    return <Comp ref={c => this.root = c} children={children(state)}/>;
  }
}

ด้วยสิ่งนี้การตอบสนองต่อการเปลี่ยนแปลงความกว้างทำได้ง่ายมาก:

function renderColumns(numColumns: number): React.Element<any> {
  ...
}
const responsiveView = (
  <Responsive domProps={['offsetWidth']}>
    {({offsetWidth}: {offsetWidth: number}): ?React.Element<any> => {
      if (!offsetWidth) return null;
      const numColumns = Math.max(1, Math.floor(offsetWidth / 200));
      return renderColumns(numColumns);
    }}
  </Responsive>
);

คำถามหนึ่งเกี่ยวกับแนวทางนี้ที่ฉันยังไม่ได้ตรวจสอบคือมันรบกวน SSR หรือไม่ ฉันยังไม่แน่ใจว่าวิธีที่ดีที่สุดในการจัดการกับคดีนั้นคืออะไร
Andy

คำอธิบายที่ดีขอบคุณสำหรับการอธิบายอย่างละเอียด :) re: SSR มีการอภิปรายisMounted()ที่นี่: facebook.github.io/react/blog/2015/12/16/…
ptim

1
@memeLab ฉันเพิ่งเพิ่มโค้ดสำหรับส่วนประกอบกระดาษห่อหุ้มที่ดีซึ่งใช้เวลาส่วนใหญ่ของสำเร็จรูปไม่ตอบสนองต่อการเปลี่ยนแปลง DOM ลองดู :)
Andy

1
@Philll_t ใช่มันจะดีถ้า DOM ทำให้สิ่งนี้ง่ายขึ้น แต่เชื่อฉันเถอะว่าการใช้ไลบรารีนี้จะช่วยให้คุณประหยัดได้แม้ว่าจะไม่ใช่วิธีพื้นฐานที่สุดในการวัดผลก็ตาม
Andy

1
@Philll_t อีกสิ่งหนึ่งที่ไลบรารีดูแลคือการใช้ResizeObserver(หรือโพลีฟิลล์) เพื่อรับการอัปเดตขนาดของโค้ดของคุณทันที
Andy

43

componentDidMountผมคิดว่าวิธีการที่วงจรชีวิตที่คุณกำลังมองหาอยู่ องค์ประกอบได้ถูกวางไว้ใน DOM แล้วและคุณสามารถรับข้อมูลเกี่ยวกับองค์ประกอบเหล่านี้ได้จากส่วนประกอบrefsและคุณจะได้รับข้อมูลเกี่ยวกับพวกเขาจากองค์ประกอบของ

ตัวอย่างเช่น:

var Container = React.createComponent({

  componentDidMount: function () {
    // if using React < 0.14, use this.refs.svg.getDOMNode().offsetWidth
    var width = this.refs.svg.offsetWidth;
  },

  render: function () {
    <svg ref="svg" />
  }

});

1
โปรดระวังว่าoffsetWidthไม่มีอยู่ใน Firefox
Christopher Chiche

@ChristopherChiche ฉันไม่เชื่อว่าเป็นเรื่องจริง คุณใช้เวอร์ชันอะไร อย่างน้อยก็ใช้ได้สำหรับฉันและเอกสาร MDN ดูเหมือนจะแนะนำว่าสามารถสันนิษฐานได้: developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/…
couchand

1
ฉันจะไม่สะดวก ไม่ว่าในกรณีใดตัวอย่างของฉันอาจเป็นตัวอย่างที่ไม่ดีเนื่องจากคุณต้องปรับขนาดsvgองค์ประกอบอย่างชัดเจนอยู่ดี AFAIK สำหรับทุกสิ่งที่คุณกำลังมองหาขนาดไดนามิกที่คุณอาจวางใจoffsetWidthได้
couchand

3
สำหรับใครก็ตามที่มาที่นี่โดยใช้ React 0.14 ขึ้นไปไม่จำเป็นต้องใช้. getDOMNode () อีกต่อไป: facebook.github.io/react/blog/2015/10/07/…
Hamund

2
แม้ว่าวิธีนี้อาจเป็นวิธีที่ง่ายที่สุด (และเหมือน jQuery มากที่สุด) ในการเข้าถึงองค์ประกอบ แต่ตอนนี้ Facebook กล่าวว่า "เราไม่แนะนำให้ใช้เพราะการอ้างอิงสตริงมีปัญหาบางอย่างถือเป็นมรดกและมีแนวโน้มที่จะถูกลบออกในอนาคต เผยแพร่หากคุณกำลังใช้ this.refs.textInput เพื่อเข้าถึงการอ้างอิงเราขอแนะนำให้ใช้รูปแบบการโทรกลับแทน " คุณควรใช้ฟังก์ชันการโทรกลับแทนการอ้างอิงสตริง ข้อมูลที่นี่
ahaurat

22

อีกทางเลือกหนึ่งในการแก้ปัญหา couchand คุณสามารถใช้ findDOMNode

var Container = React.createComponent({

  componentDidMount: function () {
    var width = React.findDOMNode(this).offsetWidth;
  },

  render: function () {
    <svg />
  }
});

10
เพื่อชี้แจง: ใน React <= 0.12.x ใช้ component.getDOMNode () ใน React> = 0.13.x ใช้ React.findDOMNode ()
pxwise

2
@pxwise Aaaaa และตอนนี้สำหรับองค์ประกอบ DOM refs คุณไม่จำเป็นต้องใช้ฟังก์ชันใดก็ได้กับ React 0.14 :)
Andy

5
@pxwise ฉันเชื่อว่าReactDOM.findDOMNode()ตอนนี้?
ivarni

1
@MichaelScheper เป็นเรื่องจริงมี ES7 อยู่ในรหัสของฉัน ในคำตอบที่อัปเดตของฉันการreact-measureสาธิตคือ (ฉันคิดว่า) ES6 บริสุทธิ์ มันยากที่จะเริ่มต้นแน่นอน ... ฉันผ่านความบ้าคลั่งเดียวกันมาตลอดปีครึ่งที่ผ่านมา :)
แอนดี้

2
@MichaelScheper btw คุณอาจพบคำแนะนำที่เป็นประโยชน์ที่นี่: github.com/gaearon/react-makes-you-sad
Andy

6

คุณสามารถใช้ไลบรารี I ที่ฉันเขียนไว้ซึ่งตรวจสอบขนาดการแสดงผลส่วนประกอบของคุณและส่งผ่านไปยังคุณ

ตัวอย่างเช่น:

import SizeMe from 'react-sizeme';

class MySVG extends Component {
  render() {
    // A size prop is passed into your component by my library.
    const { width, height } = this.props.size;

    return (
     <svg width="100" height="100">
        <circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
     </svg>
    );
  }
} 

// Wrap your component export with my library.
export default SizeMe()(MySVG);   

การสาธิต: https://react-sizeme-example-esbefmsitg.now.sh/

Github: https://github.com/ctrlplusb/react-sizeme

มันใช้อัลกอริธึมตามการเลื่อน / วัตถุที่ได้รับการปรับให้เหมาะสมที่ฉันยืมมาจากคนที่ฉลาดกว่าฉันมาก :)


ดีขอบคุณสำหรับการแบ่งปัน ฉันกำลังจะสร้าง repo สำหรับโซลูชันที่กำหนดเองสำหรับ 'DimensionProvidingHoC' เช่นกัน แต่ตอนนี้ฉันจะลองดู
larrydahooster

ฉันจะขอบคุณข้อเสนอแนะใด ๆ ในเชิงบวกหรือเชิงลบ :-)
ctrlplusb

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