"렌더링 후"코드에 반응합니까?


367

요소의 높이 ( "app-content"라고 함)를 동적으로 설정해야하는 앱이 있습니다. 앱의 "크롬"높이를 빼고 빼서 "응용 프로그램 콘텐츠"높이를 해당 제한 내에서 100 %에 맞게 설정합니다. 바닐라 JS, jQuery 또는 Backbone 뷰에서는 매우 간단하지만 React에서 올바른 프로세스가 무엇인지 알아 내려고 고심하고 있습니다.

아래는 예제 구성 요소입니다. app-content높이를 창의 100 %에서 ActionBarand 의 크기를 뺀 값 으로 설정할 수 있기를 원 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가 어떻게 이것을 고치겠습니까?" Flexbox의 열 기능을 기억하고 매력처럼 작동했습니다!
Oscar Godson

답변:


301

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

214
또는 componentDidUpdate첫 번째 렌더링 후에 값이 변경 될 수있는 경우.
zackify

5
렌더링 후 애니메이션이 시작되도록 전환으로 설정된 CSS 속성을 변경하려고합니다. 불행히도 CSS를 변경해도 componentDidMount()전환이 발생하지 않습니다.
eye_mew

8
감사. 이름이 너무 직관적이기 때문에 왜 "init"또는 "initialize"와 같은 말도 안되는 이름을 시도했는지 궁금합니다.
Pawel

13
componentDidMount에서 변경하면 브라우저가 너무 빠릅니다. setTimeout에 싸서 실제 시간을주지 마십시오. 즉 componentDidMount: () => { setTimeout(addClassFunction())}, rAF를 사용하면 아래 답변이이 답변을 제공합니다.

3
이것은 확실히 작동하지 않습니다. 노드 목록을 얻은 다음 노드 목록을 반복하려고하면 길이가 0과 같습니다. setTimeout을 수행하고 1 초를 기다리는 것이 저에게 효과적이었습니다. 불행히도 반응에는 DOM이 렌더링 될 때까지 실제로 기다리는 방법이 없습니다.
NickJ

241

한 사용의 단점 componentDidUpdate, 또는 componentDidMountDOM을 요소가 그려지 완료하기 전에이 실제로 실행하는 것입니다,하지만 그들은에서 통과 한 후에 브라우저의 DOM에 반응한다.

예를 들어 node.scrollHeight를 렌더링 된 node.scrollTop으로 설정해야한다면 React의 DOM 요소로는 충분하지 않을 수 있습니다. 높이를 얻기 위해 요소가 칠해질 때까지 기다려야합니다.

해결책:

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 !!!!!
Alex

2
이상한. 최신 버전의 React에서 변경되었을 수 있으므로 requestAnimationFrame에 대한 호출이 필요하지 않다고 생각합니다. "구성 요소의 업데이트가 DOM으로 플러시 된 직후에 호출됩니다.이 메소드는 초기 렌더링을 위해 호출되지 않습니다. 구성 요소가 업데이트 될 때 DOM에서 작동 할 수있는 기회로 사용하십시오."... 플러시되면 DOM 노드가 있어야합니다. - facebook.github.io/react/docs/...
짐 소호

1
@ JimSoho, 이것이 수정 된 것이 옳기를 바랍니다. 그러나 실제로 그 문서에는 새로운 것이 없습니다. 이것은 업데이트되는 돔이 충분하지 않은 경우에 해당되며 페인트주기를 기다리는 것이 중요합니다. 심지어 몇 버전을 다시 가고, 나는 새 버전과 이전과 바이올린을 만들려고하지만, 나는이 문제를 보여주기 위해 복잡한만큼 구성 요소를 만들 것 couldnt는 ...
그레이엄 P 히스

3
@neptunian 엄밀히 말하면 "[RAF]는 다음 다시 그리기 전에 [...]로 불립니다 ..."-[ developer.mozilla.org/en-US/Apps/Fundamentals/Performance/… ]. 이 상황에서 노드는 여전히 DOM에 의해 계산 된 레이아웃을 가져야합니다 (일명 "리플 로우"). 이것은 RAF를 레이아웃 이전에서 레이아웃 이후로 점프하는 방법으로 사용합니다. 느릅 나무에서 브라우저 문서는 이상 좋은 장소입니다 : elmprogramming.com/virtual-dom.html#how-browsers-render-html
그레이엄 P 히스

1
_this.getDOMNode is not a function이 코드는 무엇입니까?
OZZIE

104

내 경험상 window.requestAnimationFrameDOM이에서 완전히 리플 로우 / 리플 로우 완료되었는지 확인하기에 충분하지 않았습니다 componentDidMount. componentDidMount호출 직후 DOM에 액세스하는 코드를 실행 중이며 단독으로 사용 window.requestAnimationFrame하면 DOM에 요소가 존재합니다. 그러나 리플 로우가 아직 발생하지 않았으므로 요소의 치수에 대한 업데이트는 아직 반영되지 않습니다.

이 작업을 수행 할 수있는 진정으로 신뢰할 수있는 유일한 방법 은 다음 프레임의 렌더에 등록하기 전에 React의 현재 호출 스택이 지워지도록 메소드를 a setTimeout와 a 로 감싸는 것 window.requestAnimationFrame입니다.

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

이것이 왜 발생하고 필요한지 추측 해야하는 경우 React batching DOM 업데이트를보고 현재 스택이 완료 될 때까지 DOM에 변경 사항을 실제로 적용하지 않을 수 있습니다.

궁극적으로 React 콜백 후에 발생하는 코드에서 DOM 측정을 사용하는 경우이 방법을 사용하는 것이 좋습니다.


1
setTimeout 또는 requestAnimationFrame 만 필요하며 둘다는 아닙니다.

7
일반적으로 당신은 맞습니다. 그러나 React의 componentDidMount 메소드의 컨텍스트에서 스택이 완료되기 전에 requestAnimationFrame을 연결하면 DOM이 실제로 완전히 업데이트되지 않을 수 있습니다. React의 콜백 컨텍스트 에서이 동작을 일관되게 재현하는 코드가 있습니다. DOM이 업데이트 된 후 코드가 실행되도록하는 유일한 방법은 (이 특정 React 유스 케이스에서 다시 한 번) setTimeout으로 호출 스택을 먼저 지우는 것입니다.
Elliot Chong

6
위에서 언급 한 동일한 해결 방법이 필요한 다른 의견도 있습니다. 예 : stackoverflow.com/questions/26556436/react-after-render-code/… 이것은이 React 유스 케이스에 대해 100 % 신뢰할 수있는 유일한 방법입니다. 추측을해야한다면 React 배치 업데이트가 현재 스택 내에 적용되지 않을 가능성이 있습니다 (따라서 배치가 적용되도록 requestAnimationFrame을 다음 프레임으로 연기 함).
Elliot Chong


2
현재의 호출 스택이 지워지기를 기다리는 것은 분명히 이벤트 루프에 대해 이야기하는 "무의미한"방법은 아니지만, 각자 자신의 생각입니다.
Elliot Chong

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 돌연변이 가 발생한 발생합니다 .js.org
Philippe Hebert

사실이지만 레이아웃에서 Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.편집 할 페인트를 칠하기 전에 실행 됩니다.
P Fuster

브라우저의 리플 로우 후 useEffect가 실행되는지 알고 있습니까 (React가 '페인트'라고 부르는 것이 아님)? useEffect를 사용하여 요소의 scrollHeight를 요청하는 것이 안전합니까?
eMontielG

사용하기에 안전합니다
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
이것은 좋은 답변입니다! 감사!
Bryan Jyh Herng Chong

1
이것은 아름답습니다.이 의견을 찬성하십시오!
bingeScripter

11

나는이 솔루션이 더럽다고 생각하지만 여기에 우리는 간다.

componentDidMount() {
    this.componentDidUpdate()
}

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

이제 난 그냥 여기 앉아서 아래로 투표를 기다립니다.


5
React 컴포넌트 라이프 사이클을 존중해야합니다.
Túbal Martín

2
@ TúbalMartín 알고 있습니다. 같은 결과를 얻는 더 좋은 방법이 있다면 자유롭게 공유하십시오.
Jaakko Karhu

8
음, "여기에 앉아서 하강 투표를 기다린다"는 비유적인 +1입니다. 용감한 사람. ; ^)
ruffin

14
오히려 두 수명주기에서 메소드를 호출하면 다른주기에서주기를 트리거 할 필요가 없습니다.
Tjorriemorrie 2016 년

1
componentWillReceiveProps는 이렇게해야합니다
Pablo

8

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


API 호출이 데이터에로드 될 때 페이지가 렌더링되기 전에 내 구성 요소가 마운트를 실행했습니다.
Jason G

6

나는 같은 문제에 부딪쳤다.

대부분의 경우 해킹 틱를 사용 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 개의 반응 구성 요소 메인 div를 지정한 다음 자동으로 세 번째 구성 요소 메인 div의 높이를 지정하십시오. 따라서 세 번째 div의 내용을 기반으로 높이가 할당됩니다.


2

실제로 비슷한 동작에 문제가 있습니다. id 속성으로 Component에 비디오 요소를 렌더링하므로 RenderDOM.render ()가 끝나면 자리 표시자를 찾기 위해 id가 필요한 플러그인을로드하고 찾지 못합니다.

componentDidMount () 내부에 0ms의 setTimeout이 수정되었습니다 :)

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

1

로부터 ReactDOM.render () 문서 :

선택적 콜백이 제공되면 구성 요소가 렌더링되거나 업데이트 된 후 실행됩니다.


9
이것을 사용하는 방법의 예를 추가 할 수 있습니까? 나는 주로 render 메소드에서 요소를 반환하고 render를 호출하지 않고 값을 제공합니다.
dcsan

23
불행히도 언급 한 콜백 은 최상위 ReactDOM.render수준ReactElement.render 에서만 사용할 수 있으며 구성 요소 수준 (여기서는 주제)이 아닙니다 .
Bramus

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

큰 양의 데이터를 수신하고 캔버스에 페인트하는 반응 구성 요소를 인쇄해야 할 때 이상한 상황이 발생했습니다. 언급 된 모든 접근 방식을 시도했지만 setTimeout 내의 requestAnimationFrame을 사용하여 20 %의 시간 동안 빈 캔버스를 얻으므로 다음 중 하나를 수행했습니다.

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

기본적으로 requestAnimationFrame 체인을 만들었습니다.이 좋은 아이디어인지 확실하지 않지만 지금까지 100 %의 경우에서 작동합니다 (n 변수의 값으로 30을 사용하고 있습니다).


0

실제로 요청 애니메이션 프레임 또는 시간 초과를 사용하는 것보다 훨씬 간단하고 깨끗한 버전이 있습니다. Iam은 아무도 그것을 제기하지 않았다 : vanilla-js onload handler. 가능하면 component 마운트를 사용하십시오. 그렇지 않은 경우 jsx 구성 요소의 onload hanlder에 함수를 바인딩하십시오. 함수가 모든 렌더를 실행하도록하려면 렌더 함수로 결과를 리턴하기 전에 실행하십시오. 코드는 다음과 같습니다.

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 component lifecycle methods : The Component Lifecycle을 확인하십시오.

모든 구성 요소에는 componentDidMount예 를 들어 유사한 방법이 많이 있습니다.

  • componentWillUnmount() -컴포넌트가 브라우저 DOM에서 제거 될 예정입니다.

1
무례하지는 않지만이 질문에 어떻게 대답합니까? ES6에 대한 업데이트를 표시하는 것은 실제로 질문과 관련이 없으며 아무것도 변경하지 않습니다. 이전의 모든 답변은 이미 componentDidMount가 자체적으로 작동하지 않는 방식에 대해 이야기합니다.
dave4jr 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.