Просмотр Rerender при изменении размера браузера с помощью React

как я могу реагировать на повторный рендеринг представления при изменении размера окна браузера?

фон

у меня есть несколько блоков, которые я хочу разместить отдельно на странице, однако я также хочу, чтобы они обновлялись при изменении окна браузера. Самый конечный результат будет чем-то вроде Бен Холланд макет Pinterest, но написанный с использованием React не только jQuery. Я еще далеко.

код

вот мой app:

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

есть ли более "реагировать" способ сделать это?

14 ответов


вы можете слушать в componentDidMount, что-то вроде этого компонента, который просто отображает размеры окна (например,<span>1024 x 768</span>):

var WindowDimensions = React.createClass({
    render: function() {
        return <span>{this.state.width} x {this.state.height}</span>;
    },
    updateDimensions: function() {
        this.setState({width: $(window).width(), height: $(window).height()});
    },
    componentWillMount: function() {
        this.updateDimensions();
    },
    componentDidMount: function() {
        window.addEventListener("resize", this.updateDimensions);
    },
    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    }
});

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

очень простое решение:

resize = () => this.forceUpdate()

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

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

Это простой и короткий пример использования 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>
    );
  }
}

редактировать 2018: теперь React имеет поддержку первого класса для контекст


Я попытаюсь дать общий ответ, который нацелен на эту конкретную проблему, но и более общую проблему.

Если вы не заботитесь о побочных эффектах libs, вы можете просто использовать что-то вроде Packery

Если вы используете Flux, вы можете создать хранилище, содержащее свойства окна, чтобы сохранить чистую функцию рендеринга без необходимости запрашивать объект window каждый раз.

в других случаях, когда вы хотите создать отзывчивый веб-сайт, но предпочитаете реагировать на встроенные стили медиа-запросов или хотите, чтобы поведение HTML/JS менялось в соответствии с шириной окна, продолжайте читать:

что такое контекст React и почему я говорю об этом

реагировать контексте an не находится в общедоступном API и позволяет передавать свойства всей иерархии компонентов.

реагировать контекст особенно полезен для передачи всему приложению вещей, которые никогда не меняются (он используется многими фреймворками Flux через mixin). Вы можете использовать его для хранения бизнес-инвариантов приложения (например, подключенного идентификатора пользователя, чтобы он был доступен везде).

но его также можно использовать для хранения вещей, которые могут измениться. Проблема в том, что при изменении контекста все компоненты, которые его используют, должны быть перерисованы, и это нелегко сделать, лучшим решением часто является размонтировать/перемонтировать все приложение с новым контекстом. Помни!--28-->forceUpdate не является рекурсивным.

Как вы понимаете, контекст практичен, но есть влияние на производительность при его изменении, поэтому он не должен меняться слишком часто.

что поместить в контекст

  • инварианты: как связанный userId, sessionToken, что угодно...
  • вещи, которые не часто меняют

вот вещи, которые не часто менять:

текущий язык пользователя:

Он не меняется очень часто, и когда это происходит, так как все приложение переведено, мы должны повторно отобразить все: очень хороший usecase горячего изменения языка

окне Свойства

ширина и высота не меняются часто, но когда мы делаем наш макет и поведение, возможно, придется адаптироваться. Для макета иногда легко настроить с помощью CSS mediaqueries, но иногда это не так и требует другой структуры HTML. Для поведения вы должны справиться с этим с помощью Javascript.

вы не хотите повторно отображать все на каждом событии изменения размера, поэтому вам нужно отменить события изменения размера.

насколько я понимаю ваша проблема в том, что вы хотите знать, сколько элементов для отображения в соответствии с шириной экрана. Таким образом, вы должны сначала определить отзывчивые точки останова, и перечислите количество различных типов макета вы можете иметь.

например:

  • макет "1col", для ширины
  • макет "2col", для 600
  • макет "3col", для 1000

при изменении размера событий (debounced) вы можете легко получить текущий тип макета, запросив объект window.

затем вы можете сравнить тип макета с прежним типом макета, и если он изменился, повторно отобразить приложение с новым контекстом: это позволяет чтобы избежать повторного рендеринга приложения вообще, когда пользователь имеет события изменения размера триггера, но на самом деле тип макета не изменился, поэтому вы только повторно рендеринг при необходимости.

Как только у вас это будет, вы можете просто использовать тип макета внутри вашего приложения (доступный через контекст), чтобы вы могли настроить классы HTML, behavior, CSS... Вы знаете свой тип макета внутри функции React render, поэтому это означает, что вы можете безопасно писать отзывчивые веб-сайты с помощью встроенных стилей и не нуждаетесь mediaqueries на всех.

Если вы используете Flux, вы можете использовать магазин вместо контекста React, но если ваше приложение имеет много отзывчивых компонентов, может быть, проще использовать контекст?


Я использую решение @senornestor, но, чтобы быть полностью правильным, Вы также должны удалить прослушиватель событий:

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

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

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

в противном случае вы получите предупреждение:

предупреждение: forceUpdate(...): Смогите только уточнить установленное или установку деталь. Это обычно означает, что вы вызвали forceUpdate() на размонтированном деталь. Это не операция. Пожалуйста, проверьте код для XXX деталь.


Я бы пропустил все вышеперечисленные ответы и начал использовать 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

не уверен, что это лучший подход, но то, что сработало для меня, было первым созданием магазина, я назвал его 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, эти файлы были частично обрезаны.


вам не обязательно нужно принудительно перерисовывать.

это может не помочь OP, но в моем случае мне нужно было только обновить width и height атрибуты на моем холсте (что вы не можете сделать с 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;
`

Я знаю, что на это был дан ответ, но просто подумал, что я поделюсь своим решением в качестве верхнего ответа, хотя отлично, теперь может быть немного устаревшим.

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

единственная разница в том, что я дебютирую (только запускаю каждые 200 мс) updateWindowDimensions на событии resize, чтобы немного увеличить производительность, но не дебютирую его, когда он вызывается на ComponentDidMount.

Я обнаружил, что debounce сделал его довольно ЛаГГи, чтобы иногда монтировать, если у вас есть ситуация где он часто монтируется.

просто небольшая оптимизация, но надеюсь, что это помогает кто-то!


спасибо всем за ответы. Вот моя реакция + Recompose. Это функция высокого порядка, которая включает в себя 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');
 }})
)

этот код использует новый реагировать контекст 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() {
      const { children } = this.props;

      return <Provider value={this.state}>{children}</Provider>;
    }
  }

  export default WindowProvider;
  export const WindowConsumer = Consumer;

тогда вы можете использовать его везде, где хотите, в своем коде следующим образом:

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

пришлось привязать его к "этому" в конструкторе, чтобы заставить его работать с синтаксисом класс

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

по этой причине лучше, если вы используете эти данные из данных CSS или JSON-файла, а затем с этим параметром данных новое состояние с этим.состояние ({width: "some value", height:" some value"}); или написание кода, который использует данные width screen в самостоятельной работе, если вы хотите адаптивно показывать изображения