Вопрос по reactjs, javascript, resize – Rerender вид в браузере изменить размер с React

219

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

Фон

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

React.renderComponent(
  ,
  document.getElementById('view')
)

Тогда у меня естьBlock компонент (эквивалентныйPin в приведенном выше примере Pinterest):

var Block = React.createClass({
  render: function() {
    return (
        
        {this.props.title}
        <p>{this.props.children}</p>
        
    );
  }
});

и список / коллекция: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.description};
    });

    return (
        {blockNodes}
    );
  }
});
Вопрос

Должен ли я добавить JQueryизменить размер окна? Если да, то где?

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

Есть ли ещеReact» способ сделать это?

Ваш Ответ

16   ответов
14

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

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

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

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

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

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

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

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

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

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

Что поставить в контекст

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

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

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

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

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

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

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

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

Например:

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

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

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

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

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

9

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

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

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

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

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

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

Вы'получаю предупреждение по причине: что выЭто плохая практика. Ваша функция render () должна быть чистой функцией реквизита и состояния. В вашем случае вы должны сохранить новый размер в состоянии. zoran404
29

Это простой и короткий пример использования 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>
    );
  }
}
Это хороший, краткий ответ, но у AFAICT есть одна ошибка: если я не ошибаюсь,:: Оператор связывания возвращает новое значение каждый раз, когда оно применяется. Таким образом, ваш слушатель события не будет фактически незарегистрированным, так как вашremoveEventListener заканчивается передачей функции, отличной от той, которая была первоначально передана.addEventListener natevw
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());
    }
}));

К вашему сведению, эти файлы были частично обрезаны.

Это излишне. Jason Rice
@DavidSinclair Или даже лучше,onresize может отправить.WindowResizedAction John Weisz
@frostymarvelous действительно, возможно, лучше переместить в компонент, как в других ответах. David Sinclair
Состояние хранилища должно изменяться только в ответ на отправленное действие. Это определенно не хороший способ сделать это. frostymarvelous
1
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();
};

Затем обновите размер рендера (холст), назначьте новое соотношение сторон для камеры.

Размонтирование и перенастройка - сумасшедшее решение, на мой взгляд ....

ниже крепление при необходимости.

             {
                    this.mount = mount;
                }}
            />
37

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

resize = () => this.forceUpdate()

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

componentWillUnmount() {
  window.removeEventListener('resize', this.resize)
}
Также не надоне забудьте удалить слушателя!componentWillUnmount() Jemar Jones
Это лучшее решение, если оно задушено. Я имею в виду, еслиforceUpdate применяется условно asmmahmud
Дон»не забудьте ограничить обновление силы илибудет выглядеть очень глючно. k2snowman69
6

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
Этот проект больше не поддерживается. Parabolord
Размер окна тривиально получить. Для этого не нужна библиотека.window.innerWidthwindow.innerHeightreact-dimensions решает более важную часть проблемы, а также запускает код макета при изменении размера окна (а также при изменении размера контейнера). MindJuice
В одном случае использования моих случаев, когда это неэтого не достаточно - у нас есть алгоритм компоновки, который размещает масштабируемые блоки контента на странице в строках. В пределах определенных границ мы хотим, чтобы строки помещались примерно на 2,5 строки по высоте экрана. Ширина подходит для размера контейнера, но высота должна знать размер окна. mjtamlyn
Это не дает размер окна, просто контейнер. mjtamlyn
0

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

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

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

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

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

Разница лишь в том, что яm отменяет (работает только каждые 200 мс) updateWindowDimensions для события resize, чтобы немного повысить производительность, НО не отменяет его, когда оно 'вызывается на ComponentDidMount.

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

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

Это'определено в конструкторе :) этоs просто updateWindowDimensions, привязанный к компоненту, но без отладки. Matt Wills
Где определениеinitUpdateWindowDimensions метод? Matt
аааа, понятно спасибо Я не'не заметил ... Matt
351

что-то вроде этого компонента, который просто отображает размеры окна (например,<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);
    }
});
@MattDell да, классы ES6 - это просто обычные классы, поэтому с ними нет автоматической привязки. Michelle Tilley
увидетьfacebook.github.io/react/tips/dom-event-listeners.html :) Tieme
Можем ли мы угробить$ для jQuery Athman
@MattDell выглядит как:: Синтаксис привязки пока отсутствуетsitepoint.com/bind-javascripts-this-keyword-react "оператор связывания (: :) не будет частью ES7, поскольку набор функций ES7 был заморожен в феврале, оператор связывания является предложением для ES8 " Jarrod Smith
0

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

106

@ Софи Алперт права, +1, я просто хочу предоставить модифицированную версию ее решения,без jQueryна основеэтот ответ

var WindowDimensions = React.    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);
    }
});
@andrerpenacaniuse.com/#search=addeventlistener указывает на то, что ie8 будет иметь проблемы nnnn
@LessNoviceProgrammer В прошлом году, когда я сделал этот комментарий, мы поддержали IE8 в публичной части нашего продукта (и использовали React в другом разделе). рад сообщить, что мыскоро уходим от этого. но что делать, когда ваш код затрагивает 14-15 миллионов доменов на международном уровне? nnnn
@nnnn можешь уточнить? Я признаю, что только проверял это в Chrome. Вы говорите, что window.addEventListener isn 'работать на IE без полифилов? André Pena
Кто-нибудь действительно все еще заботится о IE8? Или это просто привычка? frostymarvelous
6

Этот код использует новыйРеагировать на контекст 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>
1

https://github.com/renatorib/react-sizes это HOC, чтобы сделать это, сохраняя при этом хорошую производительность.

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

Похожие вопросы