Реагировать «после рендеринга» кода?

180

У меня есть приложение, где мне нужно установить высоту элемента (скажем, "приложение-контент" ) динамически. Он принимает высоту "хром" приложения и вычитает его, а затем устанавливает высоту "содержимого приложения", чтобы соответствовать 100% в пределах этих ограничений. Это очень просто с ванильными JS, jQuery или Backbone-представлениями, но я изо всех сил пытаюсь понять, какой будет правильный процесс для этого в React?

Ниже приведен пример компонента. Я хочу, чтобы установить высоту app-content равным 100% от окна за вычетом размера ActionBar и BalanceBar, но как узнать, когда все будет рендерировано, и где бы я поместил материал вычисления в этот Класс реагирования?

/** @jsx React.DOM */
var List = require('../list');
var ActionBar = require('../action-bar');
var BalanceBar = require('../balance-bar');
var Sidebar = require('../sidebar');
var AppBase = React.createClass({
  render: function () {
    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar title="Title Here" />
          <BalanceBar balance={balance} />
          <div className="app-content">
            <List items={items} />
          </div>
        </div>
      </div>
    );
  }
});

module.exports = AppBase;
  • 2
    В современных браузерах вы можете использовать flexbox, чтобы сделать это лучше. В противном случае Harborhoffer ответ.
  • 2
    Сначала я подумал: "Как бы это исправить во Flexbox?" потом я вспомнил функцию колонки во Flexbox, и она работала как шарм!
Теги:

10 ответов

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

https://facebook.github.io/react/docs/react-component.html#componentdidmount

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

var AppBase = React.createClass({
  componentDidMount: function() {
    var $this = $(ReactDOM.findDOMNode(this));
    // set el height and width etc.
  },

  render: function () {
    return (
      <div className="wrapper">
        <Sidebar />
          <div className="inner-wrapper">
            <ActionBar title="Title Here" />
            <BalanceBar balance={balance} />
            <div className="app-content">
              <List items={items} />
          </div>
        </div>
      </div>
    );
  }
});
  • 181
    или componentDidUpdate если значения могут измениться после первого рендеринга.
  • 4
    Я пытаюсь изменить свойство CSS, которое установлено на переход, так что анимация начинается после рендеринга. К сожалению, изменение css в componentDidMount() не вызывает переход.
Показать ещё 4 комментария
143

Один из недостатков использования componentDidUpdate или componentDidMount заключается в том, что они фактически выполняются до того, как элементы dom будут сделаны, но после того, как они были переданы из React в DOM браузера.

Скажем, например, если вам понадобилось установить node.scrollHeight на обработанный node.scrollTop, тогда React DOM-элементов может быть недостаточно. Вам нужно подождать, пока элементы будут окрашены, чтобы получить их высоту.

Решение:

Используйте requestAnimationFrame, чтобы убедиться, что ваш код запущен после рисования вашего вновь созданного объекта

scrollElement: function() {
  //store a this ref, and
  var _this = this;
  //wait for a paint to do scrolly stuff
  window.requestAnimationFrame(function() {
    var node = _this.getDOMNode();
    if (node !== undefined) {
      //and scroll them!
      node.scrollTop = node.scrollHeight;
    }
  });
},
componentDidMount: function() {
  this.scrollElement();
},
// and or
componentDidUpdate: function() {
  this.scrollElement();
},
// and or
render: function() {
  this.scrollElement()
  return [...]
  • 0
    React.findDOMNode (this) возвращает null в обратном вызове requestAnimationFrame при вызове внутри componentDidMount, для меня.
  • 0
    @DanielCoffman ты можешь использовать bind? stackoverflow.com/questions/6065169/...
Показать ещё 8 комментариев
42

По моему опыту window.requestAnimationFrame было недостаточно, чтобы гарантировать, что DOM был полностью выполнен/завершен с помощью componentDidMount. Я запускаю код, который обращается к DOM сразу после вызова componentDidMount, и использование только window.requestAnimationFrame приведет к тому, что элемент будет присутствовать в DOM; однако обновления размеров элементов пока не отражены, так как переплавка еще не произошла.

Единственный надежный способ для этого - обернуть мой метод в setTimeout и window.requestAnimationFrame, чтобы убедиться, что текущий стек вызовов будет очищен до регистрации для следующего рендеринга кадра.

function onNextFrame(callback) {
    setTimeout(function () {
        window.requestAnimationFrame(callback)
    }, 0)
}

Если мне приходилось размышлять о том, почему это происходит/необходимо, я мог видеть, что React пакетно обновляет DOM и фактически не применяет изменения к DOM до завершения текущего стека.

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

  • 1
    Вам нужен только setTimeout ИЛИ requestAnimationFrame, а не оба.
  • 0
    Также нет необходимости передавать 0 в setTimeout, если вы не даете ему время.
Показать ещё 8 комментариев
4

Я чувствую, что это решение грязно, но здесь мы идем:

componentDidMount() {
    this.componentDidUpdate()
}

componentDidUpdate() {
    // A whole lotta functions here, fired after every render.
}

Теперь я просто буду сидеть здесь и ждать низких голосов.

  • 4
    Вы должны соблюдать жизненный цикл компонента React.
  • 1
    @ TúbalMartín я знаю. Если у вас есть лучший способ достичь того же результата, не стесняйтесь поделиться.
Показать ещё 3 комментария
4

Из документа ReactDOM.render():

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

  • 9
    Можете ли вы добавить пример того, как использовать это? Я в основном возвращаю элементы из метода рендеринга, я не вызываю рендер и не предоставляю значения.
  • 23
    К сожалению, обратный вызов, о котором вы упоминаете, доступен только для верхнего уровня ReactDOM.render , но не для ReactElement.render уровня ReactElement.render (о чем здесь идет ReactElement.render ).
Показать ещё 2 комментария
2

У меня на самом деле проблема с подобным поведением, я визуализую элемент видео в компоненте с его атрибутом id, поэтому, когда RenderDOM.render() заканчивает, он загружает плагин, который нуждается в идентификаторе, чтобы найти местозаполнитель, и он не может найти его.

SetTimeout с 0ms внутри компонентаDidMount() исправил его:)

componentDidMount() {
    if (this.props.onDidMount instanceof Function) {
        setTimeout(() => {
            this.props.onDidMount();
        }, 0);
    }
}
2

После рендеринга вы можете указать высоту, как показано ниже, и указать высоту для соответствующих компонентов реакции.

render: function () {
    var style1 = {height: '100px'};
    var style2 = { height: '100px'};

   //window. height actually will get the height of the window.
   var hght = $(window).height();
   var style3 = {hght - (style1 + style2)} ;

    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar style={style1} title="Title Here" />
          <BalanceBar style={style2} balance={balance} />
          <div className="app-content" style={style3}>
            <List items={items} />
          </div>
        </div>
      </div>
    );`
  }

или вы можете указать высоту каждого реагирующего компонента с помощью sass. Укажите первый 2-х активный основной элемент компонента с фиксированной шириной, а затем третий основной размер главного элемента с авто. Таким образом, на основе третьего содержимого div будет назначена высота.

1

React имеет несколько методов жизненного цикла, которые помогают в этих ситуациях, включая, помимо прочего, getInitialState, getDefaultProps, componentWillMount, componentDidMount и т.д.

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

/** @jsx React.DOM */
var List = require('../list');
var ActionBar = require('../action-bar');
var BalanceBar = require('../balance-bar');
var Sidebar = require('../sidebar');
var AppBase = React.createClass({
  componentDidMount: function() {
    ReactDOM.findDOMNode(this).height = /* whatever HEIGHT */;
  },
  render: function () {
    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar title="Title Here" />
          <BalanceBar balance={balance} />
          <div className="app-content">
            <List items={items} />
          </div>
        </div>
      </div>
    );
  }
});

module.exports = AppBase;

Также для получения дополнительной информации о жизненном цикле в реакции вы можете посмотреть ссылку ниже: https://facebook.github.io/react/docs/state-and-lifecycle.html

Изображение 2696

1

Немного обновить с помощью ES6 классов вместо React.createClass

import React, { Component } from 'react';

class SomeComponent extends Component {
  constructor(props) {
    super(props);
    // this code might be called when there is no element avaliable in `document` yet (eg. initial render)
  }

  componentDidMount() {
    // this code will be always called when component is mounted in browser DOM ('after render')
  }

  render() {
    return (
      <div className="component">
        Some Content
      </div>
    );
  }
}

Также - проверьте методы жизненного цикла компонентов React: https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle

Каждый компонент имеет множество методов, подобных componentDidMount, например.

  • componentWillUnmount() - компонент будет удален из браузера DOM
0

Я столкнулся с той же проблемой.

В большинстве сценариев, использующих hack-ish setTimeout(() => { }, 0) в componentDidMount(), работал.

Но не в частном случае; и я не хотел использовать ReachDOM findDOMNode, так как в документации говорится:

Примечание: findDOMNode - это escape-люк, используемый для доступа к базовому DOM node. В большинстве случаев использование этого выходного люка не рекомендуется он пробивает абстракцию компонента.

(Источник: https://facebook.github.io/react/docs/react-dom.html#finddomnode)

Итак, в этом конкретном компоненте мне пришлось использовать событие componentDidUpdate(), поэтому мой код оказался следующим:

componentDidMount() {
    // feel this a little hacky? check this: http://stackoverflow.com/questions/26556436/react-after-render-code
    setTimeout(() => {
       window.addEventListener("resize", this.updateDimensions.bind(this));
       this.updateDimensions();
    }, 0);
}

И затем:

componentDidUpdate() {
    this.updateDimensions();
}

Наконец, в моем случае мне пришлось удалить слушателя, созданного в componentDidMount:

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

Ещё вопросы

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