มุมมองการแสดงผลบนเบราว์เซอร์ปรับขนาดด้วย React


313

ฉันจะทำให้ React แสดงมุมมองใหม่ได้อย่างไรเมื่อมีการปรับขนาดหน้าต่างเบราว์เซอร์

พื้นหลัง

ฉันมีบล็อกบางส่วนที่ฉันต้องการจัดวางแบบแยกทีละหน้า ผลลัพธ์ที่ได้จะเป็นรูปแบบ Pinterest ของ Ben Hollandแต่เขียนโดยใช้ React ไม่ใช่แค่ jQuery ฉันยังคงออกเดินทาง

รหัส

นี่คือแอพของฉัน:

var MyApp = React.createClass({
  //does the http get from the server
  loadBlocksFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      mimeType: 'textPlain',
      success: function(data) {
        this.setState({data: data.events});
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentWillMount: function() {
    this.loadBlocksFromServer();

  },    
  render: function() {
    return (
        <div>
      <Blocks data={this.state.data}/>
      </div>
    );
  }
});

React.renderComponent(
  <MyApp url="url_here"/>,
  document.getElementById('view')
)

จากนั้นฉันก็มีBlockองค์ประกอบ (เทียบเท่ากับPinในตัวอย่าง Pinterest ข้างต้น):

var Block = React.createClass({
  render: function() {
    return (
        <div class="dp-block" style={{left: this.props.top, top: this.props.left}}>
        <h2>{this.props.title}</h2>
        <p>{this.props.children}</p>
        </div>
    );
  }
});

และรายการ / ชุดBlocks:

var Blocks = React.createClass({

  render: function() {

    //I've temporarily got code that assigns a random position
    //See inside the function below...

    var blockNodes = this.props.data.map(function (block) {   
      //temporary random position
      var topOffset = Math.random() * $(window).width() + 'px'; 
      var leftOffset = Math.random() * $(window).height() + 'px'; 
      return <Block order={block.id} title={block.summary} left={leftOffset} top={topOffset}>{block.description}</Block>;
    });

    return (
        <div>{blockNodes}</div>
    );
  }
});

คำถาม

ฉันควรจะเพิ่มขนาดหน้าต่างของ jQuery หรือไม่? ถ้าเป็นเช่นนั้นอยู่ที่ไหน

$( window ).resize(function() {
  // re-render the component
});

มีวิธี "ตอบโต้" มากกว่านี้หรือไม่?

คำตอบ:


531

การใช้ React Hooks:

คุณสามารถกำหนด Hook แบบกำหนดเองที่ฟังresizeเหตุการณ์ในหน้าต่างได้ดังนี้:

import React, { useLayoutEffect, useState } from 'react';

function useWindowSize() {
  const [size, setSize] = useState([0, 0]);
  useLayoutEffect(() => {
    function updateSize() {
      setSize([window.innerWidth, window.innerHeight]);
    }
    window.addEventListener('resize', updateSize);
    updateSize();
    return () => window.removeEventListener('resize', updateSize);
  }, []);
  return size;
}

function ShowWindowDimensions(props) {
  const [width, height] = useWindowSize();
  return <span>Window size: {width} x {height}</span>;
}

ข้อดีที่นี่คือตรรกะถูกห่อหุ้มและคุณสามารถใช้ Hook นี้ได้ทุกที่ที่คุณต้องการใช้ขนาดหน้าต่าง

ใช้เรียน React:

คุณสามารถฟังใน componentDidMount สิ่งที่ต้องการส่วนประกอบนี้ซึ่งเพิ่งแสดงขนาดหน้าต่าง (เช่น<span>Window size: 1024 x 768</span>):

import React from 'react';

class ShowWindowDimensions extends React.Component {
  state = { width: 0, height: 0 };
  render() {
    return <span>Window size: {this.state.width} x {this.state.height}</span>;
  }
  updateDimensions = () => {
    this.setState({ width: window.innerWidth, height: window.innerHeight });
  };
  componentDidMount() {
    window.addEventListener('resize', this.updateDimensions);
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.updateDimensions);
  }
}

5
มันทำงานอย่างไร this.updateDimensionsส่งผ่านไปยังaddEventListenerเป็นเพียงการอ้างอิงฟังก์ชั่นเปลือยซึ่งจะมีค่าไม่thisเมื่อเรียก ควรใช้ฟังก์ชั่นนิรนามหรือการโทร. ผูก () เพื่อเพิ่มthisหรือฉันเข้าใจผิด?
fadedbee

24
@chrisdew ฉันมาสายนิดหน่อย แต่ React auto-ผูกthisสำหรับวิธีการใด ๆ ที่กำหนดไว้โดยตรงในองค์ประกอบ
Michelle Tilley

3
@MattDell ใช่คลาส ES6 เป็นเพียงคลาสปกติดังนั้นจึงไม่มีผลผูกพันกับคลาสเหล่านั้นโดยอัตโนมัติ
Michelle Tilley

30
ไม่มี jQuery จำเป็น - การใช้งานinnerHeightและจากinnerWidth windowและคุณสามารถข้ามcomponentWillMountถ้าคุณใช้getInitialStateการตั้งค่าและheight width
sighrobot

2
@MattDell ดูเหมือนว่า::ไวยากรณ์การผูกจะออกตอนนี้sitepoint.com/bind-javascripts-this-keyword-react "ผู้ประกอบการผูก (: :) จะไม่เป็นส่วนหนึ่งของ ES7 เป็นชุดคุณสมบัติ ES7 ถูกแช่แข็งในเดือนกุมภาพันธ์ ผู้ประกอบการผูกมัดเป็นข้อเสนอสำหรับ ES8 "
Jarrod Smith

130

@SophieAlpert ถูกต้อง +1 ฉันแค่ต้องการมอบโซลูชันที่มีการแก้ไขโดยไม่มี jQueryตามคำตอบนี้

var WindowDimensions = React.createClass({
    render: function() {
        return <span>{this.state.width} x {this.state.height}</span>;
    },
    updateDimensions: function() {

    var w = window,
        d = document,
        documentElement = d.documentElement,
        body = d.getElementsByTagName('body')[0],
        width = w.innerWidth || documentElement.clientWidth || body.clientWidth,
        height = w.innerHeight|| documentElement.clientHeight|| body.clientHeight;

        this.setState({width: width, height: height});
        // if you are using ES2015 I'm pretty sure you can do this: this.setState({width, height});
    },
    componentWillMount: function() {
        this.updateDimensions();
    },
    componentDidMount: function() {
        window.addEventListener("resize", this.updateDimensions);
    },
    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    }
});

ใช้ได้เฉพาะเมื่อคุณมีโพลีฟิลส์ที่เกี่ยวข้องกับ IE ของคุณตั้งค่าไว้สำหรับผู้ฟังเหตุการณ์วานิลลา JS
nnnn

@nnnn คุณสามารถทำอย่างละเอียด? ฉันยอมรับว่าฉันทดสอบใน Chrome เท่านั้น คุณกำลังพูดว่า window.addEventListener จะไม่ทำงานบน IE โดยไม่ใช้ polyfills หรือไม่
André Pena

2
@andrerpena caniuse.com/#search=addeventlistenerระบุว่า ie8 จะมีปัญหา
nnnn

2
@nnnn ฉันเห็น. ใช่ .. ดังนั้นโซลูชันของฉันไม่ทำงานบน IE 8 แต่ได้ผลจาก 9 ใน :) ขอบคุณ
André Pena

35
ใครบ้างที่ยังสนใจเกี่ยวกับ IE8 จริง ๆ หรือมันเป็นเพียงนิสัย?
frostymarvelous

48

ทางออกที่ง่ายมาก:

resize = () => this.forceUpdate()

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

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

12
อย่าลืมที่จะเร่งการอัพเดทไม่เช่นนั้นมันก็จะดูแย่มาก
k2snowman69

4
อย่าลืมลบผู้ฟังออกด้วยcomponentWillUnmount()!
Jemar Jones

นี่คือทางออกที่ดีที่สุดหากมีการควบคุมปริมาณ ฉันหมายความว่าถ้าforceUpdateใช้อย่างมีเงื่อนไข
asmmahmud

1
ไม่ใช่ forceUpdate (สิ่งที่ถูกเรียก) ที่ต้องมีการควบคุมปริมาณ แต่เป็นเหตุการณ์การปรับขนาดที่จำเป็นต้องควบคุมปริมาณ (สิ่งที่ถูกยิง) เทคนิคการปรับขนาดเหตุการณ์สามารถเรียกได้ในทุกพิกเซลในขณะที่คุณปรับขนาดหน้าต่างจากใหญ่ไปเล็ก เมื่อผู้ใช้ทำสิ่งนี้อย่างรวดเร็วนั่นเป็นเหตุการณ์ที่มากกว่าที่คุณใส่ใจ ยิ่งไปกว่านั้นคุณกำลังเชื่อมโยงเธรด UI กับเธรด Javascript ซึ่งหมายความว่าแอปของคุณจะเริ่มรู้สึกช้าอย่างจริงจังเนื่องจากพยายามจัดการเหตุการณ์แต่ละรายการ
k2snowman69

1
แทนที่จะเรียกใช้ฟังก์ชั่นปรับขนาดในทุกพิกเซลคุณจะเรียกใช้ในระยะเวลาสั้น ๆ เพื่อให้ภาพลวงของความลื่นไหลซึ่งคุณจัดการกับเหตุการณ์แรกและครั้งสุดท้ายเสมอดังนั้นจึงให้ความรู้สึกว่ามันปรับขนาดได้อย่างคล่องแคล่ว
k2snowman69

42

มันเป็นตัวอย่างที่ง่ายและสั้นของการใช้ es6 ที่ไม่มี jQuery

import React, { Component } from 'react';

export default class CreateContact extends Component {
  state = {
    windowHeight: undefined,
    windowWidth: undefined
  }

  handleResize = () => this.setState({
    windowHeight: window.innerHeight,
    windowWidth: window.innerWidth
  });

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

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

  render() {
    return (
      <span>
        {this.state.windowWidth} x {this.state.windowHeight}
      </span>
    );
  }
}

ตะขอ

import React, { useEffect, useState } from "react";

let App = () => {
  const [windowWidth, setWindowWidth] = useState(0);
  const [windowHeight, setWindowHeight] = useState(0);
  let resizeWindow = () => {
    setWindowWidth(window.innerWidth);
    setWindowHeight(window.innerHeight);
  };

  useEffect(() => {
    resizeWindow();
    window.addEventListener("resize", resizeWindow);
    return () => window.removeEventListener("resize", resizeWindow);
  }, []);

  return (
    <div>
      <span>
        {windowWidth} x {windowHeight}
      </span>
    </div>
  );
};

4
นี่เป็นคำตอบที่ดีและกระชับ แต่ AFAICT มีข้อบกพร่องหนึ่งข้อ: เว้นแต่ฉันจะเข้าใจผิดตัว::ดำเนินการเชื่อมโยงจะส่งคืนค่าใหม่ทุกครั้งที่นำไปใช้ ดังนั้นฟังเหตุการณ์ของคุณจะไม่ได้รับจริงไม่ลงทะเบียนตั้งแต่คุณปลายขึ้นผ่านฟังก์ชั่นที่แตกต่างจากสิ่งที่ผ่านมาเพื่อremoveEventListener addEventListener
natevw

21

ตั้งแต่ React 16.8 คุณสามารถใช้Hooks ได้ !

/* globals window */
import React, { useState, useEffect } from 'react'
import _debounce from 'lodash.debounce'

const Example = () => {
  const [width, setWidth] = useState(window.innerWidth)

  useEffect(() => {
    const handleResize = _debounce(() => setWidth(window.innerWidth), 100)

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    }
  }, [])

  return <>Width: {width}</>
}

13

แก้ไข 2018 : ตอนนี้ตอบสนองได้รับการสนับสนุนชั้นหนึ่งสำหรับบริบท


ฉันจะพยายามให้คำตอบทั่วไปว่ามีเป้าหมายเฉพาะปัญหานี้ แต่ปัญหาทั่วไปอีกด้วย

หากคุณไม่สนใจเกี่ยวกับ libs ผลข้างเคียงคุณสามารถใช้Packery

หากคุณใช้ Flux คุณสามารถสร้างร้านค้าที่มีคุณสมบัติของหน้าต่างเพื่อให้คุณสามารถใช้ฟังก์ชั่นการเรนเดอร์แบบบริสุทธิ์โดยไม่ต้องค้นหาวัตถุหน้าต่างทุกครั้ง

ในกรณีอื่น ๆ ที่คุณต้องการสร้างเว็บไซต์ที่ตอบสนองได้ แต่คุณต้องการ React สไตล์ในการสืบค้นสื่อหรือต้องการให้พฤติกรรม HTML / JS เปลี่ยนตามความกว้างของหน้าต่างอ่านต่อ:

บริบทของปฏิกิริยาคืออะไรและทำไมฉันถึงพูดถึงมัน

บริบทการโต้ตอบคือไม่ได้อยู่ใน public API และอนุญาตให้ส่งผ่านคุณสมบัติไปยังลำดับชั้นของส่วนประกอบทั้งหมด

บริบทการตอบสนองมีประโยชน์อย่างยิ่งในการส่งผ่านสิ่งที่แอพทั้งหมดของคุณที่ไม่เคยเปลี่ยนแปลง (ใช้โดยเฟรมเวิร์ก Flux จำนวนมากผ่านมิกซ์อิน) คุณสามารถใช้มันเพื่อจัดเก็บค่าคงที่ทางธุรกิจแอป (เช่น userId ที่เชื่อมต่อเพื่อให้สามารถใช้ได้ทุกที่)

แต่มันยังสามารถใช้เพื่อเก็บสิ่งต่าง ๆ ที่สามารถเปลี่ยนแปลงได้ ปัญหาคือเมื่อบริบทเปลี่ยนแปลงส่วนประกอบทั้งหมดที่ใช้ควรจะถูกเรนเดอร์ใหม่และไม่ง่ายที่จะทำเช่นนั้นทางออกที่ดีที่สุดมักจะให้ unmount / remount แอปทั้งหมดด้วยบริบทใหม่ โปรดจำไว้ว่าforceUpdate ไม่สามารถเรียกซ้ำได้

ดังนั้นตามที่คุณเข้าใจบริบทนั้นเป็นจริง แต่มีผลกระทบต่อประสิทธิภาพเมื่อมันเปลี่ยนแปลงดังนั้นจึงไม่ควรเปลี่ยนบ่อยเกินไป

สิ่งที่ควรใส่ในบริบท

  • Invariants: เช่น userId ที่เชื่อมต่อ, sessionToken อะไรก็ตาม ...
  • สิ่งที่ไม่เปลี่ยนแปลงบ่อย

นี่คือสิ่งที่ไม่เปลี่ยนแปลงบ่อย:

ภาษาผู้ใช้ปัจจุบัน :

มันไม่ได้เปลี่ยนบ่อยนักและเมื่อเป็นเช่นนั้นแอพทั้งหมดได้รับการแปลเราจะต้องทำการเรนเดอร์ใหม่อีกครั้ง: การใช้ usecase ที่ดีของการเปลี่ยนค่าความร้อน

คุณสมบัติของหน้าต่าง

ความกว้างและความสูงไม่เปลี่ยนแปลงบ่อย แต่เมื่อเราทำเค้าโครงและพฤติกรรมของเราอาจต้องปรับตัว สำหรับเลย์เอาต์บางครั้งมันง่ายในการปรับแต่งด้วย CSS mediaqueries แต่บางครั้งมันก็ไม่ได้และต้องการโครงสร้าง HTML ที่แตกต่างกัน สำหรับพฤติกรรมที่คุณต้องจัดการกับ Javascript

คุณไม่ต้องการที่จะแสดงทุกอย่างอีกครั้งในทุกกิจกรรมการปรับขนาดดังนั้นคุณต้อง debounce เหตุการณ์การปรับขนาด

สิ่งที่ฉันเข้าใจเกี่ยวกับปัญหาของคุณคือคุณต้องการทราบจำนวนรายการที่จะแสดงตามความกว้างของหน้าจอ ดังนั้นคุณต้องกำหนดจุดพักการตอบสนองก่อนและระบุจำนวนประเภทโครงร่างที่แตกต่างกันที่คุณมี

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

  • โครงร่าง "1col" สำหรับความกว้าง <= 600
  • เค้าโครง "2col" สำหรับ 600 <width <1,000
  • เลย์เอาต์ "3col" สำหรับ 1000 <= width

เกี่ยวกับการปรับขนาดกิจกรรม (debounce) คุณสามารถรับประเภทเค้าโครงปัจจุบันได้อย่างง่ายดายโดยการสอบถามวัตถุหน้าต่าง

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

เมื่อคุณมีแล้วคุณสามารถใช้ประเภทรูปแบบภายในแอปของคุณ (เข้าถึงได้ผ่านบริบท) เพื่อให้คุณสามารถปรับแต่ง HTML, พฤติกรรม, คลาส CSS ... คุณรู้ประเภทรูปแบบของคุณภายในฟังก์ชั่นการแสดงผล React ดังนั้นนี่หมายความว่าคุณ สามารถเขียนเว็บไซต์ที่ตอบสนองได้อย่างปลอดภัยโดยใช้รูปแบบอินไลน์และไม่ต้องการ Mediaqueries เลย

หากคุณใช้ Flux คุณสามารถใช้ร้านค้าแทนบริบทการตอบสนอง แต่หากแอปของคุณมีองค์ประกอบตอบสนองจำนวนมากบางทีการใช้บริบทนั้นง่ายกว่าไหม


10

ฉันใช้วิธีการแก้ปัญหาของ @senornestor แต่เพื่อให้ถูกต้องทั้งหมดคุณต้องลบผู้ฟังเหตุการณ์ด้วย:

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

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

handleResize = () => {
    this.forceUpdate();
};

มิฉะนั้นคุณจะได้รับคำเตือน:

คำเตือน: forceUpdate (... ): สามารถอัปเดตส่วนประกอบที่ประกอบหรือติดตั้งได้เท่านั้น ซึ่งมักจะหมายความว่าคุณเรียก forceUpdate () ในองค์ประกอบที่ไม่ได้ประกอบเข้าไป นี่คือไม่มี -op โปรดตรวจสอบรหัสสำหรับองค์ประกอบ XXX


1
คุณได้รับคำเตือนด้วยเหตุผล: สิ่งที่คุณกำลังทำคือการปฏิบัติที่ไม่ดี ฟังก์ชั่น render () ของคุณควรเป็นฟังก์ชั่นบริสุทธิ์ของอุปกรณ์ประกอบฉากและสถานะ ในกรณีของคุณคุณควรบันทึกขนาดใหม่ในสถานะ
zoran404

7

ฉันจะข้ามคำตอบข้างต้นทั้งหมดและเริ่มใช้react-dimensionsส่วนประกอบคำสั่งซื้อที่สูงขึ้น

https://github.com/digidem/react-dimensions

เพียงเพิ่มการโทรที่ง่ายimportและฟังก์ชั่นและคุณสามารถเข้าถึงthis.props.containerWidthและthis.props.containerHeightในองค์ประกอบของคุณ

// Example using ES6 syntax
import React from 'react'
import Dimensions from 'react-dimensions'

class MyComponent extends React.Component {
  render() (
    <div
      containerWidth={this.props.containerWidth}
      containerHeight={this.props.containerHeight}
    >
    </div>
  )
}

export default Dimensions()(MyComponent) // Enhanced component

1
นี่ไม่ได้ให้ขนาดของหน้าต่างเพียงแค่คอนเทนเนอร์
mjtamlyn

3
ใช่นั่นคือประเด็นทั้งหมด ขนาดของหน้าต่างเป็นสิ่งสำคัญในการค้นหา ขนาดของคอนเทนเนอร์นั้นหายากกว่าและมีประโยชน์มากกว่าสำหรับส่วนประกอบ React react-dimensionsแม้แต่เวอร์ชันล่าสุดยังใช้งานได้กับมิติที่มีการเปลี่ยนแปลงทางโปรแกรม (เช่นขนาดของ div ที่เปลี่ยนไปซึ่งมีผลต่อขนาดของคอนเทนเนอร์ของคุณดังนั้นคุณต้องอัปเดตขนาดของคุณ) คำตอบของเบ็นอัลเพิร์ตช่วยในการปรับขนาดหน้าต่างเบราว์เซอร์เท่านั้น ดูที่นี่: github.com/digidem/react-dimensions/issues/4
MindJuice

1
react-dimensionsจัดการกับการเปลี่ยนแปลงขนาดของหน้าต่างการเปลี่ยนแปลงเค้าโครงเฟล็กบ็อกซ์และการเปลี่ยนแปลงเนื่องจากการปรับขนาด JavaScript ฉันคิดว่ามันครอบคลุม คุณมีตัวอย่างที่ไม่สามารถจัดการได้อย่างเหมาะสมหรือไม่?
MindJuice

2
ขนาดของหน้าต่างเป็นสิ่งสำคัญที่จะได้รับ ไม่จำเป็นต้องมีห้องสมุดสำหรับสิ่งนั้น: window.innerWidth, window.innerHeight. react-dimensionsแก้ไขส่วนที่สำคัญของปัญหาและทริกเกอร์โค้ดเลย์เอาต์ของคุณเมื่อมีการปรับขนาดหน้าต่าง (เช่นเดียวกับเมื่อขนาดของคอนเทนเนอร์เปลี่ยนไป)
MindJuice

1
โครงการนี้ไม่ได้รับการบำรุงรักษาอีกต่อไป
Parabolord

7

รหัสนี้ใช้React บริบท API ใหม่ :

  import React, { PureComponent, createContext } from 'react';

  const { Provider, Consumer } = createContext({ width: 0, height: 0 });

  class WindowProvider extends PureComponent {
    state = this.getDimensions();

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

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

    getDimensions() {
      const w = window;
      const d = document;
      const documentElement = d.documentElement;
      const body = d.getElementsByTagName('body')[0];
      const width = w.innerWidth || documentElement.clientWidth || body.clientWidth;
      const height = w.innerHeight || documentElement.clientHeight || body.clientHeight;

      return { width, height };
    }

    updateDimensions = () => {
      this.setState(this.getDimensions());
    };

    render() {
      return <Provider value={this.state}>{this.props.children}</Provider>;
    }
  }

จากนั้นคุณสามารถใช้มันทุกที่ที่คุณต้องการในรหัสของคุณเช่นนี้:

<WindowConsumer>
  {({ width, height }) =>  //do what you want}
</WindowConsumer>

6

คุณไม่จำเป็นต้องบังคับให้แสดงผลซ้ำ

สิ่งนี้อาจไม่ช่วย OP แต่ในกรณีของฉันฉันต้องการอัปเดตwidthและheightคุณลักษณะบน Canvas ของฉันเท่านั้น (ซึ่งคุณไม่สามารถทำได้ด้วย CSS)

ดูเหมือนว่านี้:

import React from 'react';
import styled from 'styled-components';
import {throttle} from 'lodash';

class Canvas extends React.Component {

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

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

    resize = throttle(() => {
        this.canvas.width = this.canvas.parentNode.clientWidth;
        this.canvas.height = this.canvas.parentNode.clientHeight;
    },50)

    setRef = node => {
        this.canvas = node;
    }

    render() {
        return <canvas className={this.props.className} ref={this.setRef} />;
    }
}

export default styled(Canvas)`
   cursor: crosshair;
`

5

ไม่แน่ใจว่านี่เป็นวิธีที่ดีที่สุดหรือไม่ แต่สิ่งที่ใช้ได้ผลสำหรับฉันคือการสร้าง Store เป็นครั้งแรกฉันเรียกมันว่า WindowStore:

import {assign, events} from '../../libs';
import Dispatcher from '../dispatcher';
import Constants from '../constants';

let CHANGE_EVENT = 'change';
let defaults = () => {
    return {
        name: 'window',
        width: undefined,
        height: undefined,
        bps: {
            1: 400,
            2: 600,
            3: 800,
            4: 1000,
            5: 1200,
            6: 1400
        }
    };
};
let save = function(object, key, value) {
    // Save within storage
    if(object) {
        object[key] = value;
    }

    // Persist to local storage
    sessionStorage[storage.name] = JSON.stringify(storage);
};
let storage;

let Store = assign({}, events.EventEmitter.prototype, {
    addChangeListener: function(callback) {
        this.on(CHANGE_EVENT, callback);
        window.addEventListener('resize', () => {
            this.updateDimensions();
            this.emitChange();
        });
    },
    emitChange: function() {
        this.emit(CHANGE_EVENT);
    },
    get: function(keys) {
        let value = storage;

        for(let key in keys) {
            value = value[keys[key]];
        }

        return value;
    },
    initialize: function() {
        // Set defaults
        storage = defaults();
        save();
        this.updateDimensions();
    },
    removeChangeListener: function(callback) {
        this.removeListener(CHANGE_EVENT, callback);
        window.removeEventListener('resize', () => {
            this.updateDimensions();
            this.emitChange();
        });
    },
    updateDimensions: function() {
        storage.width =
            window.innerWidth ||
            document.documentElement.clientWidth ||
            document.body.clientWidth;
        storage.height =
            window.innerHeight ||
            document.documentElement.clientHeight ||
            document.body.clientHeight;
        save();
    }
});

export default Store;

จากนั้นฉันใช้ร้านนั้นในส่วนประกอบของฉันอย่างนี้:

import WindowStore from '../stores/window';

let getState = () => {
    return {
        windowWidth: WindowStore.get(['width']),
        windowBps: WindowStore.get(['bps'])
    };
};

export default React.createClass(assign({}, base, {
    getInitialState: function() {
        WindowStore.initialize();

        return getState();
    },
    componentDidMount: function() {
        WindowStore.addChangeListener(this._onChange);
    },
    componentWillUnmount: function() {
        WindowStore.removeChangeListener(this._onChange);
    },
    render: function() {
        if(this.state.windowWidth < this.state.windowBps[2] - 1) {
            // do something
        }

        // return
        return something;
    },
    _onChange: function() {
        this.setState(getState());
    }
}));

FYI ไฟล์เหล่านี้ถูกตัดบางส่วน


1
สถานะร้านค้าควรเปลี่ยนเฉพาะในการตอบสนองต่อการกระทำที่ถูกส่ง นี่ไม่ใช่วิธีที่ดีในการทำเช่นนี้
frostymarvelous

2
@ frostymarvelous แน่นอนอาจจะดีกว่าย้ายเข้าไปอยู่ในองค์ประกอบเช่นเดียวกับในคำตอบอื่น ๆ
David Sinclair

@DavidSinclair หรือดียิ่งขึ้นสามารถจัดส่งonresize WindowResizedAction
John Weisz

1
นี่คือ overkill
เจสันข้าว

5

ฉันรู้ว่าคำตอบนี้ได้รับการตอบรับ แต่แค่คิดว่าฉันจะแบ่งปันคำตอบของฉันในฐานะคำตอบที่ดีที่สุดแม้ว่าจะยอดเยี่ยม แต่ตอนนี้อาจจะล้าสมัยไปแล้ว

    constructor (props) {
      super(props)

      this.state = { width: '0', height: '0' }

      this.initUpdateWindowDimensions = this.updateWindowDimensions.bind(this)
      this.updateWindowDimensions = debounce(this.updateWindowDimensions.bind(this), 200)
    }

    componentDidMount () {
      this.initUpdateWindowDimensions()
      window.addEventListener('resize', this.updateWindowDimensions)
    }

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

    updateWindowDimensions () {
      this.setState({ width: window.innerWidth, height: window.innerHeight })
    }

ข้อแตกต่างที่แท้จริงคือฉันกำลัง debouncing (เรียกใช้ทุก ๆ 200ms) updateWindowDimensions ในเหตุการณ์การปรับขนาดเพื่อเพิ่มประสิทธิภาพเล็กน้อย แต่ไม่ได้ debouncing เมื่อมันถูกเรียกบน ComponentDidMount

ฉันพบว่ามีการหักล้างทำให้มันค่อนข้างล้าในบางครั้งถ้าคุณมีสถานการณ์ที่มันติดตั้งบ่อย

เพียงเพิ่มประสิทธิภาพเล็กน้อย แต่หวังว่าจะช่วยใครบางคน!


คำจำกัดความของinitUpdateWindowDimensionsวิธีอยู่ที่ไหน
Matt

มันถูกกำหนดไว้ในตัวสร้าง :) มันเพิ่ง updateWindowDimensions ที่ถูกผูกไว้กับส่วนประกอบ แต่ไม่มี debounce
Matt Wills

3

เพียงเพื่อปรับปรุงโซลูชันของ @ senornestor ที่จะใช้forceUpdateและโซลูชันของ @ gkri เพื่อลบตัวresizeฟังเหตุการณ์บนคอมโพเนนต์ unmount:

  1. อย่าลืมเค้น (หรือ debounce) การโทรเพื่อปรับขนาด
  2. ตรวจสอบให้แน่ใจbind(this)ในการกำหนด
import React from 'react'
import { throttle } from 'lodash'

class Foo extends React.Component {
  constructor(props) {
    super(props)
    this.resize = throttle(this.resize.bind(this), 100)
  }

  resize = () => this.forceUpdate()

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

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

  render() {
    return (
      <div>{window.innerWidth} x {window.innerHeight}</div>
    )
  }
}

วิธีอื่นคือเพียงใช้สถานะ "จำลอง" แทนforceUpdate:

import React from 'react'
import { throttle } from 'lodash'

class Foo extends React.Component {
  constructor(props) {
    super(props)
    this.state = { foo: 1 }
    this.resize = throttle(this.resize.bind(this), 100)
  }

  resize = () => this.setState({ foo: 1 })

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

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

  render() {
    return (
      <div>{window.innerWidth} x {window.innerHeight}</div>
    )
  }
}

2
componentDidMount() {

    // Handle resize
    window.addEventListener('resize', this.handleResize);
}




handleResize = () => {
    this.renderer.setSize(this.mount.clientWidth, this.mount.clientHeight);
    this.camera.aspect = this.mount.clientWidth / this.mount.clientHeight;
    this.camera.updateProjectionMatrix();
};

จำเป็นต้องกำหนดฟังก์ชั่นปรับขนาดเหตุการณ์เท่านั้น

จากนั้นอัปเดตขนาดตัวแสดงผล (แคนวาส) กำหนดอัตราส่วนภาพใหม่สำหรับกล้อง

การเลิกเมานท์และ remouting เป็นทางออกที่บ้าในความคิดของฉัน ....

ด้านล่างเป็นภูเขาหากจำเป็น

            <div
                className={this.state.canvasActive ? 'canvasContainer isActive' : 'canvasContainer'}
                ref={mount => {
                    this.mount = mount;
                }}
            />

2

ต้องการแบ่งปันสิ่งที่ยอดเยี่ยมที่ฉันเพิ่งค้นพบโดยใช้ window.matchMedia

const mq = window.matchMedia('(max-width: 768px)');

  useEffect(() => {
    // initial check to toggle something on or off
    toggle();

    // returns true when window is <= 768px
    mq.addListener(toggle);

    // unmount cleanup handler
    return () => mq.removeListener(toggle);
  }, []);

  // toggle something based on matchMedia event
  const toggle = () => {
    if (mq.matches) {
      // do something here
    } else {
      // do something here
    }
  };

.matches จะส่งคืนจริงหรือเท็จหากหน้าต่างสูงกว่าหรือต่ำกว่าค่าความกว้างสูงสุดที่ระบุซึ่งหมายความว่าไม่จำเป็นต้องเค้นผู้ฟังเนื่องจาก matchMedia จะยิงเพียงครั้งเดียวเมื่อบูลีนเปลี่ยน

รหัสของฉันสามารถปรับได้อย่างง่ายดายเพื่อรวมuseStateไว้ในการบันทึกผลตอบแทนบูลีน matchMedia และใช้มันในการสร้างองค์ประกอบการกระทำไฟ ฯลฯ


1

ต้องผูกมันกับ 'นี่' ในตัวสร้างเพื่อให้มันทำงานกับไวยากรณ์คลาสได้

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.resize = this.resize.bind(this)      
  }
  componentDidMount() {
    window.addEventListener('resize', this.resize)
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.resize)
  }
}

1

ขอบคุณทุกคำตอบ นี่ฉันตอบสนอง + จัดองค์ประกอบภาพ มันเป็นฟังก์ชั่นการสั่งซื้อสูงที่มีคุณสมบัติwindowHeightและwindowWidthส่วนประกอบ

const withDimensions = compose(
 withStateHandlers(
 ({
   windowHeight,
   windowWidth
 }) => ({
   windowHeight: window.innerHeight,
   windowWidth: window.innerWidth
 }), {
  handleResize: () => () => ({
    windowHeight: window.innerHeight,
    windowWidth: window.innerWidth
  })
 }),
 lifecycle({
   componentDidMount() {
   window.addEventListener('resize', this.props.handleResize);
 },
 componentWillUnmount() {
  window.removeEventListener('resize');
 }})
)

1

https://github.com/renatorib/react-sizesเป็น HOC เพื่อทำสิ่งนี้ในขณะที่ยังคงประสิทธิภาพที่ดี

import React from 'react'
import withSizes from 'react-sizes'

@withSizes(({ width }) => ({ isMobile: width < 480 }))
class MyComponent extends Component {
  render() {
    return <div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
  }
}

export default MyComponent

0

ด้วยเหตุผลนี้ดีกว่าคือถ้าคุณใช้ข้อมูลนี้จากข้อมูลไฟล์ CSS หรือ JSON จากนั้นข้อมูลนี้จะตั้งค่าสถานะใหม่ด้วย this.state ({width: "some value", height: "some value"}); หรือการเขียนโค้ดที่ใช้ข้อมูลหน้าจอไวด์สกรีนในการทำงานด้วยตนเองหากคุณต้องการแสดงภาพที่ตอบสนอง

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