Zmień widok widoku w przeglądarce za pomocą React

313

Jak mogę przekonać Reacta do ponownego renderowania widoku po zmianie rozmiaru okna przeglądarki?

tło

Mam kilka bloków, które chcę układać indywidualnie na stronie, ale chcę też, aby były aktualizowane po zmianie okna przeglądarki. Końcowym rezultatem będzie coś w stylu Bena Holland'a na Pinterest, ale napisane przy użyciu React nie tylko jQuery. Nadal jestem daleko.

Kod

Oto moja aplikacja:

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

Następnie mam Blockkomponent (odpowiednik Pinw powyższym przykładzie 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>
    );
  }
});

oraz lista / kolekcja 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>
    );
  }
});

Pytanie

Czy powinienem dodać rozmiar okna jQuery? Jeśli tak to gdzie?

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

Czy istnieje bardziej „reakcyjny” sposób na zrobienie tego?

digibake
źródło

Odpowiedzi:

531

Korzystanie z haczyków React:

Możesz zdefiniować niestandardowy zaczep, który nasłuchuje resizezdarzenia okna , mniej więcej tak:

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

Zaletą jest to, że logika jest hermetyzowana i możesz użyć tego haka w dowolnym miejscu, w którym chcesz użyć rozmiaru okna.

Korzystanie z klas React:

Możesz nasłuchiwać w componentDidMount, coś takiego jak ten komponent, który wyświetla wymiary okna (jak <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);
  }
}
Sophie Alpert
źródło
5
Jak to działa? this.updateDimensionsPrzekazane addEventListenerjest tylko gołe odniesienia funkcja, która nie będzie miała wartość dla thiskiedy dzwonił. Czy do dodania należy użyć funkcji anonimowej lub wywołania .bind () this, czy też źle zrozumiałem?
fadedbee
24
@chrisdew Trochę się tu spóźniam, ale React automatycznie wiąże się thisz dowolnymi metodami zdefiniowanymi bezpośrednio na komponencie.
Michelle Tilley
3
@MattDell tak, klasy ES6 są zwykłymi klasami, więc nie można z nimi automatycznie łączyć.
Michelle Tilley
30
Nie wymaga jQuery - użyj innerHeighti innerWidthod window. Możesz pominąć, componentWillMountjeśli używasz, getInitialStateaby ustawić heighti width.
sighrobot,
2
@MattDell wygląda na ::to, że obecnie dostępna jest składnia powiązań sitepoint.com/bind-javascripts-this-keyword-react "operator powiązania (: :) nie będzie częścią ES7, ponieważ zestaw funkcji ES7 został zamrożony w lutym , operator powiązania jest propozycją dla ES8 ”
Jarrod Smith
130

@SophieAlpert ma rację, +1, chcę tylko dostarczyć zmodyfikowaną wersję jej rozwiązania, bez jQuery , w oparciu o tę odpowiedź .

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);
    }
});
André Pena
źródło
działa tylko wtedy, gdy masz skonfigurowane polifill związane z IE dla nasłuchiwania zdarzeń waniliowych JS
nnnn
@nnnn czy możesz opracować? Przyznaję, że testowałem to tylko w Chrome. Mówisz, że window.addEventListener nie będzie działał na IE bez wielopełniaczy?
André Pena
2
@andrerpena caniuse.com/#search=addeventlistener wskazuje, że ie8 miałoby problem
nnnn
2
@nnnn. Widzę. Tak .. Więc moje rozwiązanie nie działa na IE 8, ale działa od 9 na :). Dzięki.
André Pena
35
Czy ktoś naprawdę dba o IE8? Czy to tylko zwyczaj?
frostymarvelous
48

Bardzo proste rozwiązanie:

resize = () => this.forceUpdate()

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

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
senornestor
źródło
12
Nie zapomnij dusić aktualizacji siły, bo inaczej będzie wyglądać bardzo nieładnie.
k2snowman69,
4
Nie zapomnij też usunąć nasłuchiwacza componentWillUnmount()!
Jemar Jones
To najlepsze rozwiązanie, jeśli jest dławione. Chodzi mi o to, że forceUpdatestosuje się warunkowo
asmmahmud
1
To nie jest forceUpdate (rzecz nazywana), która musi być dławiona, to wyzwalanie zdarzenia zmiany rozmiaru, które musi być dławione (rzecz uruchamiająca). Zdarzenia zmiany rozmiaru można technicznie wywoływać przy każdym pikselu podczas zmiany rozmiaru okna z dużego na małe. Gdy użytkownik robi to szybko, oznacza to więcej zdarzeń, niż ci zależy. Co gorsza, wiążesz wątek interfejsu użytkownika z wątkiem JavaScript, co oznacza, że ​​Twoja aplikacja zacznie odczuwać bardzo powolne wrażenie, gdy będzie próbowała obsłużyć każde zdarzenie indywidualnie.
k2snowman69
1
Zamiast uruchamiać funkcję zmiany rozmiaru przy każdym pikselu, uruchamiasz ją w krótkim okresie czasu, aby dać złudzenie płynności, z którą zawsze sobie radzisz przy pierwszym i ostatnim zdarzeniu, dając w ten sposób wrażenie, że płynnie obsługuje zmianę rozmiaru.
k2snowman69
42

Jest to prosty i krótki przykład użycia es6 bez 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>
    );
  }
}

haki

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>
  );
};
głos
źródło
4
To ładna, zwięzła odpowiedź, ale AFAICT ma jeden błąd: chyba że się mylę, ::operator powiązania zwraca nową wartość za każdym razem, gdy jest stosowany. W związku z tym nasłuchiwanie zdarzeń nie zostanie faktycznie wyrejestrowane, ponieważ removeEventListenerkończy się przekazywanie innej funkcji niż pierwotnie przekazana addEventListener.
natevw
21

Od React 16.8 możesz używać Haków !

/* 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}</>
}
noetix
źródło
13

Edycja 2018 : teraz React ma pierwszorzędne wsparcie kontekstu


Spróbuję udzielić ogólnej odpowiedzi, która dotyczy tego konkretnego problemu, ale także bardziej ogólnego problemu.

Jeśli nie zależy ci na bibliotekach efektów ubocznych, możesz po prostu użyć czegoś takiego jak Packery

Jeśli używasz Fluxa, możesz stworzyć sklep, który zawiera właściwości okna, dzięki czemu zachowasz czystą funkcję renderowania bez konieczności każdorazowego zapytania do obiektu okna.

W innych przypadkach, w których chcesz zbudować responsywną stronę internetową, ale wolisz React style wbudowane zamiast zapytań o media lub chcesz, aby zachowanie HTML / JS zmieniało się w zależności od szerokości okna, czytaj dalej:

Czym jest kontekst React i dlaczego o nim mówię

Kontekst Reakcja an nie znajduje się w publicznym interfejsie API i umożliwia przekazywanie właściwości do całej hierarchii komponentów.

Kontekst React jest szczególnie przydatny do przekazywania do całej aplikacji rzeczy, które nigdy się nie zmieniają (jest używany przez wiele frameworków Flux poprzez mixin). Możesz go używać do przechowywania niezmienników biznesowych aplikacji (takich jak podłączony identyfikator użytkownika, dzięki czemu jest on dostępny wszędzie).

Ale może być również używany do przechowywania rzeczy, które mogą się zmienić. Problem polega na tym, że gdy kontekst się zmienia, wszystkie komponenty, które go używają, powinny zostać zrenderowane i nie jest to łatwe, najlepszym rozwiązaniem jest często odmontowanie / ponowne zamontowanie całej aplikacji w nowym kontekście. Pamiętaj, że forceUpdate nie jest rekurencyjne .

Jak rozumiesz, kontekst jest praktyczny, ale gdy się zmienia, ma wpływ na wydajność, więc raczej nie powinien zmieniać się zbyt często.

Co umieścić w kontekście

  • Niezmienniki: jak podłączony userId, sessionToken, cokolwiek ...
  • Rzeczy, które nie zmieniają się często

Oto rzeczy, które nie zmieniają się często:

Bieżący język użytkownika :

Nie zmienia się bardzo często, a kiedy to robi, ponieważ cała aplikacja jest tłumaczona, musimy ponownie renderować wszystko: bardzo ładny przykład użycia gorącej zmiany języka

Właściwości okna

Szerokość i wysokość nie zmieniają się często, ale kiedy to robimy, nasz układ i zachowanie mogą wymagać dostosowania. Czasem układ można łatwo dostosować za pomocą zapytań multimedialnych CSS, ale czasem nie jest to konieczne i wymaga innej struktury HTML. W przypadku zachowania musisz sobie z tym poradzić za pomocą Javascript.

Nie chcesz ponownie renderować wszystkiego przy każdym zdarzeniu zmiany rozmiaru, więc musisz ogłosić zdarzenia zmiany rozmiaru.

Rozumiem twój problem, że chcesz wiedzieć, ile elementów ma być wyświetlanych zgodnie z szerokością ekranu. Musisz najpierw zdefiniować responsywne punkty przerwania i wyliczyć liczbę różnych typów układów, jakie możesz mieć.

Na przykład:

  • Układ „1col”, dla szerokości <= 600
  • Układ „2col”, dla 600 <szerokość <1000
  • Układ „3col”, dla 1000 <= szerokość

Podczas zmiany rozmiaru zdarzeń (ogłaszane) można łatwo uzyskać bieżący typ układu, wysyłając zapytanie do obiektu okna.

Następnie możesz porównać typ układu z poprzednim typem układu, a jeśli się zmienił, ponownie renderuj aplikację w nowym kontekście: pozwala to w ogóle uniknąć ponownego renderowania aplikacji, gdy użytkownik wyzwoli zdarzenia zmiany rozmiaru, ale w rzeczywistości typ układu nie zmienił się, więc renderujesz tylko wtedy, gdy jest to wymagane.

Gdy to zrobisz, możesz po prostu użyć typu układu w aplikacji (dostępnego w kontekście), aby dostosować HTML, zachowanie, klasy CSS ... Znasz swój typ układu w funkcji renderowania React, co oznacza, że potrafi bezpiecznie pisać responsywne strony internetowe za pomocą wbudowanych stylów i wcale nie potrzebuje zapytań o media.

Jeśli używasz Fluxa, możesz użyć sklepu zamiast kontekstu React, ale jeśli Twoja aplikacja ma wiele responsywnych komponentów, może łatwiej jest używać kontekstu?

Sebastien Lorber
źródło
10

Korzystam z rozwiązania @senornestor, ale aby być całkowicie poprawnym, musisz również usunąć detektor zdarzeń:

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

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

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

W przeciwnym razie otrzymasz ostrzeżenie:

Ostrzeżenie: forceUpdate (...): Można aktualizować tylko zamontowany lub montowany element. Zwykle oznacza to, że wywołałeś forceUpdate () na niezamontowanym komponencie. To nie jest możliwe. Sprawdź kod komponentu XXX.

gkri
źródło
1
Otrzymujesz ostrzeżenie z jakiegoś powodu: to, co robisz, jest złą praktyką. Twoja funkcja render () powinna być czystą funkcją rekwizytów i stanu. W twoim przypadku powinieneś zapisać nowy rozmiar w stanie.
zoran404,
7

Pominąłem wszystkie powyższe odpowiedzi i zacznę używać react-dimensionskomponentu wyższego rzędu.

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

Wystarczy dodać prosty importi wywołanie funkcji, można uzyskać dostęp this.props.containerWidthi this.props.containerHeightw składniku.

// 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
MindJuice
źródło
1
Nie daje to rozmiaru okna, tylko pojemnik.
mjtamlyn
3
Tak, o to chodzi. Rozmiar okna jest łatwy do znalezienia. Rozmiar kontenera jest znacznie trudniejszy do znalezienia i znacznie bardziej użyteczny dla komponentu React. Najnowsza wersja react-dimensionsnawet działa dla programowo zmienionych wymiarów (np. Zmienił się rozmiar div, co wpływa na rozmiar twojego kontenera, więc musisz zaktualizować swój rozmiar). Odpowiedź Ben Alperta pomaga tylko w przypadku zmiany rozmiaru okna przeglądarki. Zobacz tutaj: github.com/digidem/react-dimensions/issues/4
MindJuice
1
react-dimensionsobsługuje zmiany wielkości okna, zmiany układu Flexbox i zmiany wynikające ze zmiany rozmiaru JavaScript. Myślę, że to obejmuje. Czy masz przykład, że nie obsługuje on poprawnie?
MindJuice
2
Rozmiar okna jest prosty. Nie potrzeba do tego biblioteki: window.innerWidth, window.innerHeight. react-dimensionsrozwiązuje ważniejszą część problemu, a także uruchamia kod układu przy zmianie rozmiaru okna (a także przy zmianie rozmiaru kontenera).
MindJuice
1
Ten projekt nie jest już aktywnie utrzymywany.
Parabolord
7

Ten kod używa nowego kontekstowego interfejsu API React :

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

Następnie możesz użyć go w dowolnym miejscu kodu:

<WindowConsumer>
  {({ width, height }) =>  //do what you want}
</WindowConsumer>
Albert Olivé
źródło
6

Nie musisz wymuszać ponownego renderowania.

To może nie pomóc OP, ale w moim przypadku musiałem tylko zaktualizować atrybuty widthi heightna moim płótnie (czego nie można zrobić z CSS).

To wygląda tak:

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;
`
mpen
źródło
5

Nie jestem pewien, czy jest to najlepsze podejście, ale to, co zadziałało dla mnie, to pierwsze utworzenie sklepu, nazwałem go 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;

Potem użyłem tego sklepu w moich komponentach, trochę tak:

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

Do Twojej wiadomości, te pliki zostały częściowo przycięte.

David Sinclair
źródło
1
Stan magazynu powinien ulec zmianie tylko w odpowiedzi na wysłane działanie. To zdecydowanie nie jest dobry sposób na zrobienie tego.
frostymarvelous
2
@ frostymarvell rzeczywiście, być może lepiej przeniósł się do komponentu, jak w innych odpowiedziach.
David Sinclair
@DavidSinclair Lub nawet lepiej, onresizemoże wysłać WindowResizedAction.
John Weisz
1
To jest przesada.
Jason Rice,
5

Wiem, że udzielono odpowiedzi, ale pomyślałem, że podzielę się moim rozwiązaniem, ponieważ najlepsza odpowiedź, choć świetna, może być nieco przestarzała.

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

Jedyną różnicą jest to, że ogłaszam (uruchamiam co 200 ms) updateWindowDimensions na zdarzeniu zmiany rozmiaru, aby nieco zwiększyć wydajność, ALE nie ogłaszam go, gdy jest wywoływany na ComponentDidMount.

Odkryłem, że debaz sprawia, że ​​montowanie jest dość opóźnione, jeśli zdarza się, że często się montuje.

Drobna optymalizacja, ale mam nadzieję, że komuś pomoże!

Matt Wills
źródło
Gdzie jest definicja initUpdateWindowDimensionsmetody?
Matt
Jest zdefiniowany w konstruktorze :) to po prostu updateWindowDimensions powiązane z komponentem, ale bez debounce.
Matt Wills,
3

Aby ulepszyć rozwiązanie @ senornestor w użyciu forceUpdatei rozwiązanie @ gkri w zakresie usuwania resizedetektora zdarzeń z odmontowanego komponentu:

  1. nie zapomnij o zdławieniu (lub odrzuceniu) wezwania do zmiany rozmiaru
  2. upewnij się, że bind(this)w konstruktorze
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>
    )
  }
}

Inną metodą jest użycie stanu „obojętnego” zamiast 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>
    )
  }
}
kimbaudi
źródło
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();
};

Wystarczy zdefiniować funkcję zmiany rozmiaru zdarzenia.

Następnie zaktualizuj rozmiar renderers (płótno), przypisz nowy współczynnik proporcji do kamery.

Odmontowanie i remontowanie to moim zdaniem szalone rozwiązanie ....

poniżej jest mocowanie w razie potrzeby.

            <div
                className={this.state.canvasActive ? 'canvasContainer isActive' : 'canvasContainer'}
                ref={mount => {
                    this.mount = mount;
                }}
            />
Nicolay Hekkens
źródło
2

Chciałem udostępnić tę całkiem fajną rzecz, którą właśnie znalazłem 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 zwróci wartość prawda lub fałsz, jeśli okno jest wyższe lub niższe od określonej wartości maksymalnej szerokości, co oznacza, że ​​nie ma potrzeby ograniczania słuchacza, ponieważ matchMedia uruchamia się tylko raz, gdy zmienia się wartość logiczna.

Mój kod można łatwo dostosować w useStatecelu zapisania zwrotów boolean matchMedia i użyć go do warunkowego renderowania komponentu, akcji ognia itp.

Riley Brown
źródło
1

Musiałem powiązać go z „tym” w konstruktorze, aby działał ze składnią klasy

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)
  }
}
Jim Perris
źródło
1

Dziękuję wszystkim za odpowiedzi. Oto mój React + Recompose . Jest to funkcja wyższego rzędu, która zawiera właściwości windowHeighti windowWidthkomponentu.

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');
 }})
)
dumorango
źródło
1

https://github.com/renatorib/react-sizes jest HOC, aby to zrobić, przy jednoczesnym zachowaniu dobrej wydajności.

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
Russell Cohen
źródło
0

Z tego powodu lepiej jest, jeśli użyjesz tych danych z danych pliku CSS lub JSON, a następnie z tym ustawieniem danych nowy stan za pomocą this.state ({szerokość: „jakaś wartość”, wysokość: „jakaś wartość”}); lub pisanie kodu, który korzysta z danych o szerokości ekranu podczas samodzielnej pracy, jeśli chcesz responsywne obrazy programów

Miodrag Trajanovic
źródło