vanilla js로 componentDidMount 구현하기 1

자려하면 여러 아이디어들이 떠오른다.

오늘은 html의 load이벤트를 통해 React의 componentDidMount()를 구현할 수 있지 않을까 하는 생각이 들었다.

무시하고 자려했으나 잠이 안 와서 간단하게 실험해보기로 했다.

첫 번째 시도

HTML의 요소들에 onload로 콘솔에 hi 태그명을 찍어주었다.

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head onload="console.log('hi head')">
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body onload="console.log('hi body')">
<div class="content" onload="console.log('hi head')"></div>
</body>
</html>

하지만 작동하지 않았다.

두 번째 시도

load이벤트와 onload

mdn을 통해 load와 onload를 찾아보았다.

load

load mdn을 참고하였다.

load이벤트는 전체 문서 혹은 이미지, css stylesheet와 같은 종속 리소스가 로드될 때 발생한다고 되어있다.

onload

마찬가지로 onload mdn을 참고하였다.

load이벤트가 발생하는 대상인 이미지, window, XMLHttpRequest에 핸들러를 등록할 수 있다고 되어있다.

두 번째 시도

다른 방법은 없을까 고민하던 도중 blocking resouce인 <script> 태그를 이용하여 구현해주면 어떨까 하는 아이디어가 떠올랐다.

asyncdefer속성을 지정하지 않은 script태그의 경우 파싱을 멈추고 스크립트를 로드하는 즉시 실행하므로 될 것 같았다.

바로 코드로 옮겼다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="container">
<script>
console.log("container load!");
</script>
<div id="item">1</div>
<script>
console.log("item 1 load!");
</script>
<div id="item">2</div>
<script>
console.log("item 2 load!");
</script>
<div id="item">3</div>
<script>
console.log("item 3 load!");
</script>
<div id="item">4</div>
<script>
console.log("item 4 load!");
</script>
</div>
</body>
</html>

알 맞게 작동하였다.

세 번째 시도

세 번째 시도(코드 함수화 하기)

DOMElementDidMount 함수를 작성하여 함수화 하였다..!

callback fungtion과 js의 나머지 매개변수 문법을 이용하여 callback function을 실행해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div class="container">
<div class="item1">1</div>
<div class="item2">2</div>
<div class="item3">3</div>
<div class="item4">4</div>
</div>
<script>
function DOMElementDidMount($selector, $func, ...$params) {
const $target = [...document.querySelectorAll($selector)];

$target.forEach((ele) => {
const $callfunc = document.createElement("script");
const $textContext = document.createTextNode(
`(${$func})(${$params})`
);

$callfunc.appendChild($textContext);
ele.prepend($callfunc);
});
}
const hi = (...$params) => console.log(`hi ${$params.join(" ")}`);
DOMElementDidMount(".container", hi, '"hello"', '"container"');
DOMElementDidMount(".item1", hi, '"hello"', '"item1"');
DOMElementDidMount(".item2", hi, '"hello"', '"item2"');
DOMElementDidMount(".item3", hi, '"hello"', '"item3"');
DOMElementDidMount(".item4", hi, '"hello"', '"item4"');
</script>
</body>
</html>

알맞은 위치에 script 태그가 추가되었다.

세 번째 결과

콘솔에도 알맞게 출력하고

세 번째 콘솔

크롬의 성능탭에서도 5개의 prepend 이벤트가 순서대로 발생함을 볼 수있다.

세 번째 성능탭

한계

위 방법은 두 가지 한계가 있었다.(내가 모르는 한계가 더 있을 것이다.)

  1. 성능의 심한 저하
    $target이 된 요소마다 script 태그를 집어넣으므로 계속해서 HTML 파싱을 막게된다.
    이는 성능의 큰 저하로 연결된다.

  2. 재사용성의 부재
    처음 랜더링 될 때 한 번을 제외하고는 계속 호출하거나 재사용하기가 어렵다.

개선방안

클래스형을 사용하여 실제 HTML을 수정한 후 최하단에서 정의한 DOMElementDidMount()함수를 호출하면 되지 않을까 싶었다.

느낀점

예전에 vanilla js로 웹컴포넌트를 구현하는 아티클을 읽은며 따라해본 적이 있는데 그 때는 필요성을 잘 느끼지 못했다.

한 번 흉내내려고 해보니 개념도 확실하게 잡히고 확실한 필요성을 느꼈다.

리액트가 왜 클래스형 컴포넌트로 시작했는지를 느꼈다..!(계기는 사소하고 이상하지만!)

댓글