Изменить код "после рендеринга"?

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

Ответ 1

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

Ответ 2

Один из недостатков использования 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 [...]

Ответ 3

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

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

function onNextFrame(callback) {
    setTimeout(function () {
        requestAnimationFrame(callback)
    })
}

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

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

Ответ 4

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

componentDidMount() {
    this.componentDidUpdate()
}

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

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

Ответ 5

У 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.

getInitialState, getDefaultProps, componentWillMount, componentDidMount

Ответ 6

Вы можете изменить состояние и затем выполнить свои вычисления в обратном вызове setState. Согласно документации React, это "гарантированно сработает после применения обновления".

Это должно быть сделано в componentDidMount или где-то еще в коде (как в обработчике события resize), а не в конструкторе.

Это хорошая альтернатива window.requestAnimationFrame и в ней нет проблем, о которых упоминали некоторые пользователи (необходимость комбинировать ее с setTimeout или вызывать ее несколько раз). Например:

class AppBase extends React.Component {
    state = {
        showInProcess: false,
        size: null
    };

    componentDidMount() {
        this.setState({ showInProcess: true }, () => {
            this.setState({
                showInProcess: false,
                size: this.calculateSize()
            });
        });
    }

    render() {
        const appStyle = this.state.showInProcess ? { visibility: 'hidden' } : null;

        return (
            <div className="wrapper">
                ...
                <div className="app-content" style={appStyle}>
                    <List items={items} />
                </div>
                ...
            </div>
        );
    }
}

Ответ 7

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

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

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

Ответ 8

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

import React, { useEffect } from 'react'

export default function App(props) {

     useEffect(() => {
         // your post layout code (or 'effect') here.
         ...
     },
     // array of variables that can trigger an update if they change. Pass an
     // an empty array if you just want to run it once after component mounted. 
     [])
}

Также, если вы хотите запустить перед монтированием макета, используйте хук useLayoutEffect:

import React, { useLayoutEffect } from 'react'

export default function App(props) {

     useLayoutEffect(() => {
         // your pre layout code (or 'effect') here.
         ...
     }, [])
}

Ответ 9

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

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 будет назначена высота.

Ответ 10

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

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

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

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

(Источник: 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));
}

Ответ 11

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

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

Ответ 12

Для меня ни одна комбинация window.requestAnimationFrame или setTimeout давала согласованных результатов. Иногда это срабатывало, но не всегда, а иногда было бы слишком поздно.

Я исправил это, зацикливая window.requestAnimationFrame столько раз, сколько необходимо.
(Обычно 0 или 2-3 раза)

Ключ diff > 0: здесь мы можем точно знать, когда страница обновится.

// Ensure new image was loaded before scrolling
if (oldH > 0 && images.length > prevState.images.length) {
    (function scroll() {
        const newH = ref.scrollHeight;
        const diff = newH - oldH;

        if (diff > 0) {
            const newPos = top + diff;
            window.scrollTo(0, newPos);
        } else {
            window.requestAnimationFrame(scroll);
        }
    }());
}

Ответ 13

У меня была странная ситуация, когда мне нужно напечатать реагирующий компонент, который получает большой объем данных и рисует на холсте. Я перепробовал все упомянутые подходы, ни один из них не работал надежно для меня, с requestAnimationFrame внутри setTimeout я получаю пустой холст в 20% случаев, поэтому я сделал следующее:

nRequest = n => range(0,n).reduce(
(acc,val) => () => requestAnimationFrame(acc), () => requestAnimationFrame(this.save)
);

По сути, я создал цепочку из requestAnimationFrame, не уверен, что это хорошая идея или нет, но пока это работает для меня в 100% случаев (я использую 30 в качестве значения для n-переменной).

Ответ 14

Небольшое обновление с классами 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:Жизненный цикл компонента

У каждого компонента есть много методов, похожих на componentDidMount, например.

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