Làm cách nào để đáp ứng độ rộng của phần tử DOM có kích thước tự động trong React?


86

Tôi có một trang web phức tạp sử dụng các thành phần React và đang cố gắng chuyển đổi trang từ một bố cục tĩnh sang một bố cục có thể thay đổi kích thước, phản hồi nhanh hơn. Tuy nhiên, tôi vẫn gặp phải những hạn chế với React và đang tự hỏi liệu có một khuôn mẫu chuẩn nào để xử lý những vấn đề này hay không. Trong trường hợp cụ thể của tôi, tôi có một thành phần hiển thị dưới dạng div với display: table-cell và width: auto.

Rất tiếc, tôi không thể truy vấn chiều rộng của thành phần của mình, bởi vì bạn không thể tính kích thước của một phần tử trừ khi nó thực sự được đặt trong DOM (có ngữ cảnh đầy đủ để suy ra chiều rộng được hiển thị thực tế). Bên cạnh việc sử dụng điều này cho những thứ như định vị chuột tương đối, tôi cũng cần điều này để thiết lập đúng các thuộc tính chiều rộng trên các phần tử SVG trong thành phần.

Ngoài ra, khi cửa sổ thay đổi kích thước, làm cách nào để giao tiếp các thay đổi kích thước từ thành phần này sang thành phần khác trong quá trình thiết lập? Chúng tôi đang thực hiện tất cả việc hiển thị SVG bên thứ 3 của mình trong shouldComponentUpdate, nhưng bạn không thể đặt trạng thái hoặc thuộc tính cho chính mình hoặc các thành phần con khác trong phương thức đó.

Có cách nào tiêu chuẩn để giải quyết vấn đề này bằng React không?


1
nhân tiện, bạn có chắc shouldComponentUpdatelà nơi tốt nhất để hiển thị SVG không? Có vẻ như những gì bạn muốn là componentWillReceivePropshoặc componentWillUpdatenếu không render.
Andy

1
Đây có thể không phải là thứ bạn đang tìm kiếm, nhưng có một thư viện tuyệt vời cho việc này: github.com/bvaughn/react-virtualized Hãy xem thành phần AutoSizer. Nó tự động quản lý chiều rộng và / hoặc chiều cao, vì vậy bạn không cần phải làm như vậy.
Maggie

@Maggie cũng kiểm tra github.com/souporserious/react-measure , đó là một thư viện độc lập cho mục đích này và sẽ không đưa những thứ không sử dụng khác vào gói ứng dụng khách của bạn.
Andy

hey tôi đã trả lời một câu hỏi tương tự ở đây Đó là bằng cách nào đó một cách tiếp cận khác nhau và nó cho phép bạn quyết định những gì để render tùy thuộc vào loại scren (điện thoại di động, tablet, máy tính để bàn)
Calin ortan

@Maggie Tôi có thể sai về điều này, nhưng tôi nghĩ Bộ chỉnh sửa tự động luôn cố gắng điền vào phụ huynh của nó, thay vì phát hiện kích thước trẻ em đã sử dụng để phù hợp với nội dung của nó. Cả hai đều là hữu ích trong những tình huống hơi khác nhau
Andy

Câu trả lời:


65

Giải pháp thiết thực nhất là sử dụng một thư viện cho điều này như biện pháp phản ứng .

Cập nhật : hiện có một móc tùy chỉnh để phát hiện thay đổi kích thước (mà tôi chưa thử cá nhân): react-resize-Recognition . Là một móc tùy chỉnh, nó trông thuận tiện hơn để sử dụng hơn 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>
)

Để giao tiếp các thay đổi kích thước giữa các thành phần, bạn có thể chuyển một lệnh onResizegọi lại và lưu trữ các giá trị mà nó nhận được ở đâu đó (cách chia sẻ trạng thái tiêu chuẩn ngày nay là sử dụng 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>
  )
}

Cách cuộn của riêng bạn nếu bạn thực sự thích:

Tạo một thành phần trình bao bọc xử lý việc nhận các giá trị từ DOM và lắng nghe các sự kiện thay đổi kích thước cửa sổ (hoặc phát hiện thay đổi kích thước thành phần như được sử dụng bởi react-measure). Bạn cho nó biết đạo cụ nào cần lấy từ DOM và cung cấp chức năng kết xuất lấy các đạo cụ đó khi còn nhỏ.

Những gì bạn kết xuất phải được gắn kết trước khi các đạo cụ DOM có thể được đọc; khi những đạo cụ đó không khả dụng trong lần hiển thị ban đầu, bạn có thể muốn sử dụng style={{visibility: 'hidden'}}để người dùng không thể nhìn thấy nó trước khi nó có bố cục được tính toán 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)}/>;
  }
}

Với điều này, phản hồi với các thay đổi chiều rộng rất đơn giản:

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

Một câu hỏi về cách tiếp cận này mà tôi chưa điều tra là liệu nó có can thiệp vào SSR hay không. Tôi chưa chắc cách tốt nhất để xử lý trường hợp đó là gì.
Andy

lời giải thích tuyệt vời, cảm ơn vì đã rất thấu đáo :) re: SSR, Có một cuộc thảo luận isMounted()ở đây: facebook.github.io/react/blog/2015/12/16/…
ptim 29/02/16

1
@memeLab Tôi vừa thêm mã cho một thành phần trình bao bọc đẹp mắt giúp sử dụng hầu hết các bản soạn sẵn để phản hồi các thay đổi DOM, hãy xem :)
Andy

1
@Philll_t vâng, sẽ thật tuyệt nếu DOM làm điều này dễ dàng hơn. Nhưng tin tôi đi, sử dụng thư viện này sẽ giúp bạn đỡ rắc rối, mặc dù nó không phải là cách cơ bản nhất để đo lường.
Andy

1
@Philll_t một thứ khác mà các thư viện quan tâm là sử dụng ResizeObserver(hoặc polyfill) để nhận cập nhật kích thước cho mã của bạn ngay lập tức.
Andy

43

Tôi nghĩ rằng phương pháp vòng đời mà bạn đang tìm kiếm là componentDidMount. Các phần tử đã được đặt trong DOM và bạn có thể lấy thông tin về chúng từ thành phần refs.

Ví dụ:

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
Hãy cẩn thận rằng offsetWidthhiện không tồn tại trong Firefox.
Christopher Chiche

@ChristopherChiche Tôi không tin đó là sự thật. Phiên bản nào bạn đang chạy? Nó hoạt động với tôi ít nhất và tài liệu MDN dường như gợi ý rằng nó có thể được giả định: developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/…
couchand

1
Tôi sẽ được, điều đó thật bất tiện. Trong mọi trường hợp, ví dụ của tôi có thể là một ví dụ kém, vì svgdù sao thì bạn cũng phải định kích thước rõ ràng cho một phần tử. AFAIK cho bất kỳ thứ gì bạn đang tìm kiếm kích thước động mà bạn có thể dựa vào offsetWidth.
couchand

3
Đối với bất cứ ai đến đây sử dụng Phản ứng 0,14 trở lên, các .getDOMNode () không còn cần thiết: facebook.github.io/react/blog/2015/10/07/...
Hamund

2
Mặc dù phương pháp này có thể cảm thấy giống như cách dễ nhất (và giống với jQuery nhất) để truy cập phần tử, nhưng Facebook hiện cho biết "Chúng tôi khuyên bạn không nên áp dụng phương pháp này vì các tham chiếu chuỗi có một số vấn đề, được coi là kế thừa và có khả năng bị xóa trong tương lai bản phát hành. Nếu bạn hiện đang sử dụng this.refs.textInput để truy cập các bản giới thiệu, chúng tôi khuyên bạn nên sử dụng mẫu gọi lại để thay thế ". Bạn nên sử dụng một hàm gọi lại thay vì một tham chiếu chuỗi. Thông tin ở đây
ahaurat

22

Ngoài giải pháp couchand, bạn có thể sử dụng findDOMNode

var Container = React.createComponent({

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

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

10
Để làm rõ: trong React <= 0.12.x sử dụng component.getDOMNode (), trong React> = 0.13.x sử dụng React.findDOMNode ()
pxwise

2
@pxwise Aaaaavà bây giờ đối với các tham chiếu phần tử DOM, bạn thậm chí không phải sử dụng cả hai chức năng với React 0.14 :)
Andy

5
@pxwise Tôi tin là ReactDOM.findDOMNode()bây giờ?
ivarni

1
@MichaelScheper Đó là sự thật, có một số ES7 trong mã của tôi. Trong câu trả lời cập nhật của tôi, react-measureminh chứng là (tôi nghĩ) là ES6 thuần túy. Thật khó khăn khi bắt đầu, chắc chắn là ... Tôi đã trải qua cùng một cơn điên trong năm rưỡi qua :)
Andy

2
@MichaelScheper btw, bạn có thể tìm thấy một số hướng dẫn hữu ích ở đây: github.com/gaearon/react-makes-you-sad
Andy

6

Bạn có thể sử dụng thư viện I do tôi viết để giám sát kích thước các thành phần của bạn được hiển thị và chuyển nó cho bạn.

Ví dụ:

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

Demo: https://react-sizeme-example-esbefmsitg.now.sh/

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

Nó sử dụng thuật toán cuộn / đối tượng được tối ưu hóa mà tôi đã mượn từ những người thông minh hơn tôi rất nhiều. :)


Rất vui, cảm ơn vì đã chia sẻ. Tôi cũng sắp tạo một repo cho giải pháp tùy chỉnh của mình cho 'DimensionProvidingHoC'. Nhưng bây giờ tôi sẽ thử cái này.
larrydahooster

Tôi sẽ đánh giá cao bất kỳ phản hồi nào, tích cực hay tiêu cực :-)
ctrlplusb

Cảm ơn vì điều này. <Recharts /> yêu cầu bạn đặt chiều rộng và chiều cao rõ ràng nên điều này rất hữu ích
JP DeVries
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.