Rerender вид в браузере изменить размер с React

134

Как я могу получить React для повторного отображения представления при изменении размера окна браузера?

Фон

У меня есть некоторые блоки, которые я хочу разделить на странице отдельно, однако я также хочу, чтобы они обновлялись при изменении окна браузера. Самый конечный результат будет чем-то вроде , который будет написан на английском языке, но написанный с использованием 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>
    );
  }
});

и список/набор блоков:

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

Есть ли способ "Реакт" сделать это?

Теги:

11 ответов

234
Лучший ответ

Вы можете прослушивать компонент 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);
    }
});
  • 4
    Как это работает? this.updateDimensions передаваемый addEventListener является просто пустой ссылкой на функцию, которая не будет иметь значения для this при вызове. Следует ли использовать анонимную функцию или вызов .bind (), чтобы добавить this , или я неправильно понял?
  • 22
    @chrisdew Я немного опоздал, но React автоматически связывает this для любых методов, определенных непосредственно в компоненте.
Показать ещё 7 комментариев
82

@BenAlpert прав, +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);
    }
});
  • 0
    работает только тогда, когда у вас настроены связанные с IE полизаполнения для прослушивателей vanilla JS
  • 0
    @nnnn можешь уточнить? Я признаю, что только проверял это в Chrome. Вы говорите, что window.addEventListener не будет работать в IE без полизаполнений?
Показать ещё 4 комментария
20

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

resize = () => this.forceUpdate()

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

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
  • 12
    Не забывайте ограничивать обновление силы, иначе оно будет выглядеть очень странно.
  • 4
    Также не забудьте удалить слушатель на componentWillUnmount() !
Показать ещё 1 комментарий
15

Это простой и короткий пример использования es6 без jQuery.

class CreateContact extends Component{
    state = {
    }
    constructor(props) {
        super(props);

        this.state = {
            windowHeight: window.innerHeight,
            windowWidth: window.innerWidth
        };
    }

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

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

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

    render() {

        return (
            <span>
                {this.state.windowWidth} x {this.state.windowHeight}
            </span>
        );
    }
}
  • 3
    Это хороший, лаконичный ответ, но у AFAICT есть одна ошибка: если я не ошибаюсь, оператор :: bind возвращает новое значение каждый раз, когда оно применяется. Таким образом, ваш прослушиватель событий не будет фактически незарегистрированным, так как ваш removeEventListener заканчивает тем, что передает функцию, отличную от той, которая была первоначально передана addEventListener .
9

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

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

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

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

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

Реагировать на контекст, не входит в открытый API и позволяет передавать свойства целой иерархии компонентов.

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

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

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

Что добавить в контекст

  • Инварианты: подобно подключенному userId, sessionToken, независимо...
  • Вещи, которые часто не меняются

Вот что часто не меняется:

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

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

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

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

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

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

Например:

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

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

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

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

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

5

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

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

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

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

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

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

  • 0
    Вы получаете предупреждение по причине: то, что вы делаете, это плохая практика. Ваша функция render () должна быть чистой функцией реквизита и состояния. В вашем случае вы должны сохранить новый размер в состоянии.
4

Я бы пропустил все приведенные выше ответы и начал использовать компонент 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
    Это не дает размер окна, просто контейнер.
  • 2
    Да, вот и весь смысл. Размер окна тривиально найти. Размер контейнера гораздо сложнее найти и гораздо полезнее для компонента React. Последняя версия react-dimensions работает даже с программно измененными размерами (например, изменился размер элемента div, который влияет на размер вашего контейнера, поэтому вам необходимо обновить свой размер). Ответ Бена Альперта помогает только при изменении размера окна браузера. Смотрите здесь: github.com/digidem/react-dimensions/issues/4
Показать ещё 5 комментариев
3

Не уверен, что это лучший подход, но то, что сработало для меня, сначала создало 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
    Состояние хранилища должно изменяться только в ответ на отправленное действие. Это определенно не хороший способ сделать это.
  • 2
    @frostymarvelous действительно, возможно, лучше переместить в компонент, как в других ответах.
Показать ещё 2 комментария
0

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

Это может не помочь 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;
`
0

Привязать его к 'this' в конструкторе, чтобы заставить его работать с синтаксисом класса

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

Спасибо всем за ответы. Здесь мой React + Recompose. Это функция высокого порядка, которая включает свойства windowHeight и windowWidth для компонента.

const withDimentions = 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');
 }})
)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню