React에서 virtual Dom을 통해 생명주기라는 단어를 들어본 적이 있지만 HTML의 생명주기라는 것은 몹시 생소했다
이에 따라 내용을 정리한다
복습과 설명수준의 지식을 위해 다시금 복습하자면, React의 생명주기는 크게 Mount, Update, Unmount의 순서를 가진다
마운트라는 것은 DOM에 요소를 붙이는 것을 의미한다.
이 과정을 위해 필요한 메서드가 4가지이다
1. constructor
2. getDerivedStateFromProps
3. render
4. componnetDidMount
constructor은 클래스의 그것과 동일하다. 즉, JSX 객체를 만들기 위해 호출되는 생성자 함수이다.
이때, 일반적으로 상속을 통해 리엑트에서 제공하는 부모 클래스 React.Component의 내부내용을 전달받아 만들어진다
전달받는 내용은 key, props, type,ref등과 같다
getDerivedStateFromProps는 호출되면서 내부의 state과 props를 인자로 받아 상태변경과 같은 로직을 행한다.
잘 사용되지는 않는다. 공식 설명에 따르면, 변화가 심한 props에 따라 state역시 변경되는 구조로 만들었을 경우, 미리 state를 스냅샷처럼 가져와서 비교하기 위해 필요하다.
render은 virtual DOM을 실제 DOM에 반영하는 역할을 한다.
componentDidMount는 DOM에 해당 내용이 반영된 이후 호출된다.
위의 초기과정이 반영된 코드는 아래와 같다.
class Header extends React.Component {
constructor(props) {
super(props)
this.state = { favoritecolor: 'red' }
}
static getDerivedStateFromProps(props, state) {
return { favoritecolor: props.favcol }
}
render() {
return <h1>My Favorite Color is {this.state.favoritecolor}</h1>
}
}
ReactDOM.render(<Header favcol="yellow" />, document.getElementById('root'))
컴포넌트 객체가 변경되는 것을 의미한다. props나 state, 부모의 랜더링, 구독상태 변경 등과 같은 이유로 리랜더링이 발생한다
이에 필요한 메서드는 아래와 같다
객체의 내부 데이터가 변경되기 전, getDerivedStateFromProps 는 현재 객체에서 props와, state를 찾아낸 후 저장할 수 있다.
shouldComponentUpdate 는 저장되었던 props와 state, 그리고 현재의 변경된 props와 state를 비교하여 boolean을 리턴한다. 일반적으로 직접비교의 로직에 따라 성능이 안좋아질 가능성이 있으므로, 되도록 내부적으로 얕은 비교를 수행해주는 PureComponent를 사용하길 권장한다. 만약 이 함수가 false를 리턴할 경우, 이후작업(render 이후) 은 실행되지 않는다.
컴포넌트가 render 메서드를 통해 DOM에 반영되기 전, getSnapshopBeforeUpdate를 호출하여 필요한 행위를 할 수 있다. 인자로 prevProps, prevState라는 이름으로 기존의 프로퍼티와 상태값이 들어온다. 잘 사용되지 않는다. 사용 용례에는 기존 스크롤 위치를 기억한 후, 업데이트가 되었을 때 스크롤 위치를 기존값에 머물게 한다.
componentDidUpdate는 업데이트가 감지되었을 때 실행되는 함수로, 인자로 기존상태와 기존 props, 그리고 snapshot을 받아 내부적인 로직을 처리한다.
컴포넌트가 DOM에서 떨어져 나오는 것을 의미한다.
신기한점은, DOM 역시 해당 객체에 대한 이벤트 리스너가 존재하고 발동된다는 사실이다. 이때 생명주기 리스너들은 다음과 같다
("click" 처럼 이벤트가 등록되어 있다)
이때, DOMContentLoaded의 실행 시점이 상당히 유동적인데, 그 이유는 아래와 같다
<script>
function ready() {
alert('DOM이 준비되었습니다!');
// 이미지가 로드되지 않은 상태이기 때문에 사이즈는 0x0입니다.
alert(`이미지 사이즈: ${img.offsetWidth}x${img.offsetHeight}`);
}
document.addEventListener("DOMContentLoaded", ready);
</script>
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
위의 내용대로 DOM을 구상하면, 각 태그별로 프로퍼티가 완성된 객체가 만들어질 것이다.
이때, img는 외부 자원을 받아오고 있기 때문에 script태그에 있는 DOMContentLoaded가 실행되는 순간 함수 내부에서는 img의 offsetWidth와 offsetHeight은 "0" 인 상태이다.
<script>
document.addEventListener("DOMContentLoaded", () => {
alert("DOM이 준비되었습니다!");
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
<script>
alert("라이브러리 로딩이 끝나고 인라인 스크립트가 실행되었습니다.");
</script>
만약 위와같이 script 태그가 외부 요소를 로딩하는 경우, DOM의 파싱이 멈추기 떄문에 해당 파일의 패칭이 완료되기 전까지는 DOMContentLoaded 이벤트가 발생하지 않는다.
다만, defer이 붙어있는 script의 경우 DOM의 파싱을 막지 않지만 DOMContentLoaded가 호출되기 이전에 스크립트 태그의 내용들이 엔진에 의해 실행된다. 이때 실행되는 순서는 문서에 추가되어 있는 순서를 sync한다.
async가 붙어있는 script의 경우, DOM의 파싱을 막지 않지만 해당 자원이 패칭이 완료되면 DOM의 형성을 멈추고 스크립트 내용이 엔진에 의해 실행되게 된다.
<link type="text/css" rel="stylesheet" href="style.css">
<script>
// 이 스크립트는 위 스타일시트가 로드될 때까지 실행되지 않습니다.
alert(getComputedStyle(document.body).marginTop);
</script>
일반적으로 외부 스타일시트는 DOM의 형성을 막지 않기 때문에 DOMContentLoaded의 실행은 즉시 이루어진다.
단, 위와같이 link태그 바로 아래에 script 태그가 존재할 경우, 브라우저는 해당 link 태그의 자원을 불러옴으로 인해 스크립트로 무언가 행위가 실행될 수 있다고 판단하여 DOM의 파싱을 멈추고 link 의 스타일시트가 패칭되는 것을 기다리게 된다.