Реагує код "після візуалізації"?


367

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

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

/** @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;

4
Спочатку я був на кшталт "як би це виправити флексбокс?" тоді я згадав про функцію стовпця у Flexbox, і вона спрацювала як шарм!
Оскар Годсон

Відповіді:


301

компонентDidMount ()

Цей метод викликається один раз після надання вашого компонента. Так ваш код виглядав би так.

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

214
або componentDidUpdateякщо значення можуть змінитися після першого візуалізації.
zackify

5
Я намагаюся змінити властивість css, встановлену на перехід, щоб анімація починалася після візуалізації. На жаль, зміна css у componentDidMount()не викликає переходу.
eye_mew

8
Дякую. Назва настільки інтуїтивно зрозуміла, що мені цікаво, чому я намагався смішні назви типу "init" чи навіть "ініціалізувати".
Павло

13
Зміна його в компонентDidMount занадто швидка для браузера. Загорніть його в setTimeout і не дайте йому фактичного часу. тобто componentDidMount: () => { setTimeout(addClassFunction())}використовуйте rAF, відповідь нижче цього дає цю відповідь.

3
Це, звичайно, НЕ працює. Якщо ви отримаєте список вузлів, а потім спробуйте перебрати список вузлів, ви побачите, що довжина дорівнює 0. Виконання setTimeout та очікування 1 секунди працювало на мене. На жаль, не реагує, що в реакції є метод, який справді чекає, поки DOM не буде наданий.
NickJ

241

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

Скажімо, наприклад, якщо вам потрібно встановити node.scrollHeight на виведений node.scrollTop, то DOM-елементів React може бути недостатньо. Потрібно почекати, поки елементи будуть виконані фарбуванням, щоб отримати висоту.

Рішення:

Використовуйте requestAnimationFrameдля того, щоб ваш код запускався після фарбування новоспеченого об’єкта

scrollElement: function() {
  // Store a 'this' ref, and
  var _this = this;
  // wait for a paint before running scrollHeight dependent code.
  window.requestAnimationFrame(function() {
    var node = _this.getDOMNode();
    if (node !== undefined) {
      node.scrollTop = node.scrollHeight;
    }
  });
},
componentDidMount: function() {
  this.scrollElement();
},
// and or
componentDidUpdate: function() {
  this.scrollElement();
},
// and or
render: function() {
  this.scrollElement()
  return [...]

29
window.requestAnimationFrame мені не вистачало. Мені довелося зламати його з window.setTimeout. Argggghhhhhh !!!!!
Олексій

2
Незвичайно. Можливо, це змінилося в останній версії React, я не думаю, що заклик на запитAnimationFrame необхідний. Документація говорить: "Викликається відразу після того, як оновлення компонента буде передано до DOM. Цей метод не викликається для початкового візуалізації. Використовуйте це як можливість працювати з DOM, коли компонент оновлений." ... тобто, вона промивається, вузол DOM повинен бути присутнім. - facebook.github.io/react/docs/…
Джим Сохо

1
@JimSoho, сподіваюся, ти маєш рацію, що це було виправлено, але насправді нічого нового в цій документації немає. Це стосується крайових випадків, коли оновлюваний будинок недостатній, і важливо, щоб ми зачекали циклу фарби. Я спробував створити загадку з новими версіями та старими, але, здається, не міг створити достатньо складний компонент, щоб продемонструвати проблему, навіть повернувшись до декількох версій ...
Graham P Heath,

3
@neptunian Суворо кажучи, "[RAF] викликається [...] до наступного перефарбування ..." - [ developer.mozilla.org/en-US/Apps/Fundamentals/Performance/… ]. У цій ситуації для вузла все ще потрібно мати свій макет, обчислений DOM (він же "поповнений"). Це використовує RAF як спосіб переходу з макета до макета після нього. Документація на браузер від Elm - це корисне місце для більше: elmprogramming.com/virtual-dom.html#how-browsers-render-html
Graham P Heath

1
_this.getDOMNode is not a functionщо за чорт цей код?
OZZIE

104

З мого досвіду, цього window.requestAnimationFrameбуло недостатньо для того, щоб переконатися, що DOM був повністю виправлений / заповнений componentDidMount. У мене працює код, який звертається до DOM відразу після componentDidMountдзвінка і використання виключно window.requestAnimationFrameпризведе до того, що елемент буде присутній у DOM; однак оновлення розмірів елемента ще не відображаються, оскільки оновлення ще не відбулося.

Єдиний справді надійний спосіб для цього полягав у тому, щоб обернути мій метод у a setTimeoutта, window.requestAnimationFrameщоб забезпечити очищення поточного стека React перед тим, як зареєструватися для візуалізації наступного кадру.

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

Якби мені довелося міркувати, чому це відбувається / необхідне, я міг би побачити Реагувати пакетні оновлення DOM, а фактично не застосовувати зміни до DOM, поки не завершиться поточний стек.

Зрештою, якщо ви використовуєте вимірювання DOM у коді, який ви запускаєте після зворотного виклику React, ви, ймовірно, захочете скористатися цим методом.


1
Вам потрібен лише setTimeout АБО requestAnimationFrame, а не обидва.

7
Як правило - ви праві. Однак, у контексті методу компонента React компонентDidMount, якщо ви додаєте requestAnimationFrame до завершення цього стека, DOM фактично не може бути повністю оновлений. У мене є код, який послідовно відтворює цю поведінку в контексті зворотних викликів React. Єдиний спосіб бути впевненим, що ваш код виконує (ще раз, у цьому конкретному випадку реагування на використання React) після оновлення DOM - це дозволить спершу очистити стек викликів за допомогою setTimeout.
Елліот Чонг

6
Ви помітите інші коментарі, в яких згадується необхідність такого ж вирішення, наприклад: stackoverflow.com/questions/26556436/react-after-render-code/… Це єдиний 100% надійний метод для цього випадку реагування на реагування. Якщо мені доведеться ризикувати, це може бути пов'язано із самими оновленнями React batching, які потенційно не застосовуються в поточному стеку (отже, відклавши requestAnimationFrame до наступного кадру, щоб переконатися, що пакет застосовується).
Елліот Чонг

4
Я думаю, що вам може знадобитися підключити внутрішні програми JS ... altitudelabs.com/blog/what-is-the-javascript-event-loop stackoverflow.com/questions/8058612/…
Elliot Chong

2
Очікування очищення поточного стеку дзвінків очевидно не є "безглуздим" способом розповідати про цикл події, але я думаю про кожен свій власний ...
Елліот Чонг

17

Просто щоб трохи оновити це питання новими методами 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.
         ...
     }, [])
}

Відповідно до документації React, використання UseLayoutEffect відбувається після всіх мутацій DOM reactjs.org/docs/hooks-reference.html#uselayouteffect
Філіп Хеберт

Щоправда, але це працює, перш ніж у макета з’явиться шанс намалювати Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.я редагую.
P Fuster

Чи знаєте ви, чи використовує useEffect після перезавантаження браузера (не те, що React називає "фарбою")? Чи безпечно запитувати прокручування елемента Висота за допомогою useEffect?
eMontielG

Це безпечно для використанняEffect
P Fuster

13

Ви можете змінити стан, а потім виконати свої обчислення в зворотному звороті setState . Згідно з документацією React, це "гарантовано запуститься після застосування оновлення".

Це слід робити у componentDidMountкоді чи десь іншому в коді (як, наприклад, в обробці події зміни розміру), а не в конструкторі.

Це хороша альтернатива, 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>
        );
    }
}

1
Це моя улюблена відповідь. Чистий і хороший ідіоматичний код реагування.
phatmann

1
Це чудова відповідь! Дякую!
Брайан Джих Хенг Чонг

1
Це прекрасно, підтверджуйте цей коментар!
bingeScripter

11

Я відчуваю, що це рішення забруднене, але ось ми:

componentDidMount() {
    this.componentDidUpdate()
}

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

Зараз я просто збираюся сидіти тут і чекати прихильників.


5
Ви повинні дотримуватися життєвого циклу компонентів React.
Тубал Мартін

2
@ TúbalMartín Я знаю. Якщо у вас є кращий спосіб досягти того ж результату, сміливо діліться.
Jaakko Karhu

8
Гм, образний +1 для "сидіти тут і чекати прихильників". Хоробрий чоловік. ; ^)
ruffin

14
Замість того, щоб викликати метод з обох життєвих циклів, тоді вам не доведеться запускати цикли з інших циклів.
Tjorriemorrie

1
компонентWillReceiveProps повинен зробити це
Пабло

8

У React є кілька методів життєвого циклу, які допомагають у цих ситуаціях, списки включають, але не обмежуються, getInitialState, getDefaultProps, компонентWillMount, компонентDidMount тощо.

У вашому випадку та випадках, які потребують взаємодії з елементами DOM, потрібно дочекатися, коли домівка буде готова, тому використовуйте компонентDidMount, як показано нижче:

/** @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, компонентWillMount, компонентDidMount


мій компонент монтування працює перед тим, як сторінка відображається, спричиняючи велику затримку, оскільки завантаження даних api-дзвінка
Джейсон Г

6

Я зіткнувся з тією ж проблемою.

У більшості сценаріїв, що використовують hack-ish 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));
}

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 висоти головного діва компонента з фіксованою шириною, а потім висоту головного компонента третього компонента за допомогою автоматичного. Отже, виходячи із вмісту третього дива буде присвоєно висоту.


2

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

SetTimeout з 0ms всередині компонентаDidMount () виправив його :)

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

1

З документації ReactDOM.render () :

Якщо надається необов'язковий зворотний дзвінок, він буде виконуватися після надання або оновлення компонента.


9
чи можете ви додати приклад, як це використовувати? Я здебільшого повертаю елементи з методу візуалізації, я не називаю візуалізацію та надаю значення.
dcsan

23
На жаль, зворотний виклик, який ви згадуєте, доступний лише у верхньому рівніReactDOM.render , а не на рівні компонентаReactElement.render (що тут є предметом).
Брамус

1
Приклад тут буде корисним
DanV

1
Я натиснув посилання у вашій відповіді, і я не зміг знайти рядок, який ви цитували, і ваша відповідь не містить достатньої кількості інформації, щоб працювати без цього. Будь ласка, дивіться stackoverflow.com/help/how-to-answer для поради щодо того, як написати гарне запитання
Benubird

1

Для мене жодна комбінація 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);
        }
    }());
}

0

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

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

В основному я зробив ланцюжок requestAnimationFrame, не впевнений, це хороша ідея чи ні, але це працює в 100% випадків для мене до цих пір (я використовую 30 як значення для n змінної).


0

Насправді є набагато простіша і чистіша версія, ніж використання анімаційного анімаційного кадру або таймаутів. Я здивувався, що ніхто не підніс це: обробник ванільної завантаження. Якщо ви можете, використовуйте компонент зробив монтаж, якщо ні, просто прив’яжіть функцію до пристрою завантаження компонента jsx. Якщо ви хочете, щоб функція запускала кожен візуалізацію, також виконайте її перед поверненням результатів функції візуалізації. код виглядатиме так:

runAfterRender = () => 
{
  const myElem = document.getElementById("myElem")
  if(myElem)
  {
    //do important stuff
  }
}

render()
{
  this.runAfterRender()
  return (
    <div
      onLoad = {this.runAfterRender}
    >
      //more stuff
    </div>
  )
}

}


-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: життєвий цикл компонентів

Кожен компонент має безліч методів, подібних componentDidMountнаприклад.

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

1
Ніякої неповаги, але як це відповідає на питання? Показано оновлення на ES6 насправді не пов'язане з питанням / нічого не змінює. Усі набагато старші відповіді вже говорять про те, як компонентDidMount не працює самостійно.
dave4jr
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.