/favicon.ico

itcode.dev

[Observer API ํŒŒํ—ค์น˜๊ธฐ] 4. MutationObserver

2024-06-24 (์›”) 02:18:32
https://github.com/RWB0104/blog.itcode.dev/assets/50317129/c472262e-0b99-4a6f-836f-fc797bcf26d9
Observer API ํŒŒํ—ค์น˜๊ธฐ

์‹œ๋ฆฌ์ฆˆ ๋ชจ์•„๋ณด๊ธฐ

Observer API ํŒŒํ—ค์น˜๊ธฐ

4 / 6
Table of Contents

MutationObserver๋Š” DOM์˜ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ์˜ต์ €๋ฒ„๋‹ค. DOM๊ณผ ๊ด€๋ จ๋œ ์–ด๋–ค ์š”์†Œ๊ฐ€ ๋ณ€ํ•˜๋“ ์ง€ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผํ…Œ๋ฉด, width ํ˜น์€ padding ๊ฐ™์€ style ํƒœ๊ทธ๋Š” ๋ฌผ๋ก , class๋‚˜ id, ์ž์‹ ๋…ธ๋“œ์˜ ์ถ”๊ฐ€ ๋“ฑ์ด ์žˆ๋‹ค.

DOM์˜ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•œ๋‹ค๋Š” ํŠน์ง•์œผ๋กœ ์ธํ•ด, DOM์˜ ์‚ฌ์ด์ฆˆ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ResizeObserver๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋‹ค.

TYPESCRIPT
1
2
3
const mo = new MutationObserver(callback);

mo.observe(tag, options);

์š”์†Œ ๋“ฑ๋ก ์‹œ options๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์„ธ๋ถ€ ์˜ต์…˜์„ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. MutationObserverInit ํƒ€์ž…์„ ๊ฐ€์ง„๋‹ค.

attributeFilter์— ์›ํ•˜๋Š” ์†์„ฑ๋ช…์„ ๋ฐฐ์—ด๋กœ ์ง€์ •ํ•˜๋ฉด, ํ•ด๋‹น ์ด๋ฆ„์„ ๊ฐ€์ง„ ์†์„ฑ์˜ ๋ณ€ํ™”๋งŒ์„ ํ•„ํ„ฐ๋งํ•˜์—ฌ ๊ฐ์ง€ํ•œ๋‹ค.

TYPESCRIPT
1
2
3
4
{
    // ...
    attributeFilter: [ 'id', 'class' ]
}

์œ„์™€ ๊ฐ™์ด ์ง€์ •ํ•  ๊ฒฝ์šฐ, id ๋ฐ class๊ฐ€ ๋ณ€ํ• ๋•Œ๋งŒ ์˜ต์ €๋ฒ„๊ฐ€ ๊ฐ์ง€ํ•œ๋‹ค. ๋งŒ์•ฝ ์•„๋ฌด ๊ฐ’๋„ ํ• ๋‹นํ•˜์ง€ ์•Š์€ undefined์ผ ๊ฒฝ์šฐ, ๋ชจ๋“  ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•œ๋‹ค.

MutationObserver๋Š” DOM์ด ๋ณ€ํ™”๋์„ ๋•Œ ๋™์ž‘ํ•œ๋‹ค. ์ด ๋•Œ, ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋ณ€ํ™” ์ด์ „์˜ ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋กœ์ง์„ ๊ตฌ์„ฑํ•˜๊ธฐ๋„ ํ•˜๋Š”๋ฐ, ์ด ๋•Œ attributeOldValue ์„ค์ •์ด ์œ ์šฉํ•  ๊ฒƒ์ด๋‹ค.

TYPESCRIPT
1
2
3
4
{
    // ...
    attributeOldValue: true
}

์œ„์™€ ๊ฐ™์ด ์ง€์ •ํ•  ๊ฒฝ์šฐ, ์†์„ฑ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•  ์‹œ, ๋ณ€ํ™” ์ด์ „์˜ ๊ฐ’๋„ ๊ฐ™์ด ์ œ๊ณตํ•ด์ค€๋‹ค. ์ด์ „ ์†์„ฑ์ด ์กด์žฌํ•  ์ˆ˜ ์—†๋Š” ์ฒซ ๋ณ€ํ™”๊ฑฐ๋‚˜, ์†์„ฑ์ด ์•„๋‹Œ ๋ณ€ํ™”(์ž์‹ ๋…ธ๋“œ์˜ ์ถ”๊ฐ€ ๋“ฑ)์ผ ๊ฒฝ์šฐ null์ด ๋ฐ˜ํ™˜๋œ๋‹ค.

DOM ์†์„ฑ ๋ณ€ํ™”์˜ ๊ฐ์ง€ ์—ฌ๋ถ€๋ฅผ ์ง€์ •ํ•œ๋‹ค.

TYPESCRIPT
1
2
3
4
{
    // ...
    attributes: true
}

id, class, style ๊ฐ™์€ DOM์˜ ์†์„ฑ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ, ํ•ด๋‹น ์„ค์ •์„ true๋กœ ์ง€์ •ํ•ด์ค€๋‹ค.

attributeFilter ํ˜น์€ attributeOldValue์— ๊ฐ’์ด ์ง€์ •๋œ ๊ฒฝ์šฐ, ํ•ด๋‹น ์˜ต์…˜์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋‹ค.

DOM์˜ ํ…์ŠคํŠธ ๋…ธ๋“œ ๋ณ€ํ™”(node.value)์˜ ๊ฐ์ง€ ์—ฌ๋ถ€๋ฅผ ์ง€์ •ํ•œ๋‹ค.

TYPESCRIPT
1
2
3
4
{
    // ...
    characterData: true
}

DOM์˜ textContent ๊ฐ’์ด ๋ฐ”๋€” ๊ฒฝ์šฐ, ์ด๋ฅผ ๊ฐ์ง€ํ•œ๋‹ค.

TYPESCRIPT
1
2
3
4
5
6
7
function handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
    const tag = document.getElementById('target');

    if (tag?.firstChild?.textContent) {
        tag.firstChild.textContent = e.currentTarget.value;
    }
}

์œ„์™€ ๊ฐ™์ด tag.firstChild.textContent ๊ฐ’์ด ๋ณ€ํ•  ๊ฒฝ์šฐ, characterData ์ด๋ฒคํŠธ๊ฐ€ ๊ฐ์ง€๋œ๋‹ค.

true๋กœ ์ง€์ •ํ•  ๊ฒฝ์šฐ, attributeOldValue์™€ ๋น„์Šทํ•˜๊ฒŒ ํ…์ŠคํŠธ ๋…ธ๋“œ ๊ฐ’ ๋ณ€ํ™” ์‹œ, ๋ณ€ํ™” ์ด์ „์˜ ๊ฐ’์„ ๊ฐ™์ด ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

TYPESCRIPT
1
2
3
4
{
    // ...
    characterDataOldValue: true
}

์ด์ „ ์†์„ฑ์ด ์กด์žฌํ•  ์ˆ˜ ์—†๋Š” ์ฒซ ๋ณ€ํ™”๊ฑฐ๋‚˜, ์†์„ฑ์ด ์•„๋‹Œ ๋ณ€ํ™”(์ž์‹ ๋…ธ๋“œ์˜ ์ถ”๊ฐ€ ๋“ฑ)์ผ ๊ฒฝ์šฐ null์ด ๋ฐ˜ํ™˜๋œ๋‹ค.

๋Œ€์ƒ DOM์— ์ž์‹ ๋…ธ๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋  ๊ฒฝ์šฐ๋ฅผ ๊ฐ์ง€ํ•œ๋‹ค.

TYPESCRIPT
1
2
3
4
{
    // ...
    childList: true
}

์œ„์™€ ๊ฐ™์€ ์„ค์ •์„ ํ†ตํ•ด, ํŠน์ • DOM์˜ ๊ตฌ์กฐ ๋ณ€ํ™”๋ฅผ ๋Šฅ๋™์ ์œผ๋กœ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

subtree ์˜ต์…˜์œผ๋กœ ๊ฐ์ง€ ๋ฒ”์œ„๋ฅผ ํ•˜์œ„ ๋…ธ๋“œ๊นŒ์ง€ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

TYPESCRIPT
1
2
3
4
{
    // ...
    subtree: true
}

์œ„ ์˜ต์…˜์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด, ํ•˜์œ„ ๋…ธ๋“œ์˜ attributes, characterData, childList ๋ณ€ํ™”๊นŒ์ง€ ๊ฐ์ง€ํ•ด์ค€๋‹ค. ๊ฐ์ง€ ๊ธฐ์ค€์€ ์˜ต์…˜์— ํ• ๋‹นํ•œ ๋ถ€๋ชจ์˜ ๊ธฐ์ค€๊ณผ ๋™์ผํ•˜๋‹ค.

์˜ˆ๋ฅผ๋“ค์–ด, attributes ๊ฐ์ง€๋งŒ ํ™œ์„ฑํ™”ํ•˜๊ณ  subtree๋ฅผ ์ ์šฉํ•˜๋ฉด, ์ž์‹  ๋ฐ ํ•˜์œ„ ๋…ธ๋“œ ์ „์ฒด์˜ ์†์„ฑ ๋ณ€ํ™” ์—ฌ๋ถ€๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด DOM ๋ณ€ํ™” ์‹œ ๋™์ž‘ํ•  ๋กœ์ง์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. MutationRecord ํƒ€์ž…์„ ๊ฐ€์ง„๋‹ค. ์ด๋ฅผ ์ฝ”๋“œ๋กœ ํ‘œ๊ธฐํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

TYPESCRIPT
1
const mo = new MutationObserver((record) => {});

addedNodes๋Š” ์ถ”๊ฐ€๋œ ๋…ธ๋“œ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ์ด๋ฅผ ๋ฐฐ์—ด ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋…ธ๋“œ ์ถ”๊ฐ€, ์‚ญ์ œ์™€ ๊ด€๋ จ๋œ childList ์˜ต์…˜์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ๋ฐ˜ํ™˜๋œ๋‹ค.

HTML
1
2
3
4
5
6
7
8
9
<!-- ์ดˆ๊ธฐ๊ฐ’ -->
<div id="target">
    
</div>

<!-- addedNodes ๋ฐ˜ํ™˜๋จ -->
<div id="target">
    <p>just added!</p>
</div>

์œ„ ์˜ˆ์‹œ์˜ ๊ฒฝ์šฐ, #target ํ•˜์œ„์— ์ถ”๊ฐ€๋œ ๋…ธ๋“œ p์˜ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด๋‹ค.

removedNodes๋Š” ์‚ญ์ œ๋œ ๋…ธ๋“œ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ์ด๋ฅผ ๋ฐฐ์—ด ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์กฐ๊ฑด์€ addedNodes์™€ ๊ฐ™๋‹ค.

HTML
1
2
3
4
5
6
7
8
9
<!-- ์ดˆ๊ธฐ๊ฐ’ -->
<div id="target">
    <p>just added!</p>
</div>

<!-- removedNodes ๋ฐ˜ํ™˜๋จ -->
<div id="target">
    
</div>

attributeName๋Š” ์†์„ฑ์ด ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ, ๋ณ€๊ฒฝ๋œ ์†์„ฑ์˜ ์ด๋ฆ„์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์†์„ฑ์˜ ๋ณ€ํ™”์™€ ๊ด€๋ จ๋œ attributes ์˜ต์…˜์ด ํ™œ์„ฑํ™” ๋œ ๊ฒฝ์šฐ ๋ฐ˜ํ™˜๋œ๋‹ค.

HTML
1
2
3
4
5
6
7
8
9
<!-- ์ดˆ๊ธฐ๊ฐ’ -->
<div id="target">
    
</div>

<!-- attributeName ๋ฐ˜ํ™˜๋จ -->
<div id="target" class="hi">
    
</div>

์œ„ ์˜ˆ์‹œ์˜ ๊ฒฝ์šฐ, #target ์†์„ฑ์œผ๋กœ ์ถ”๊ฐ€๋œ ์†์„ฑ๋ช… class๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.

attributeNamespace ์†์„ฑ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ, ๋ณ€๊ฒฝ๋œ ์†์„ฑ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, attributes ์˜ต์…˜์ด ํ™œ์„ฑํ™” ๋œ ๊ฒฝ์šฐ ๋ฐ˜ํ™˜๋œ๋‹ค.

๋ณ€๊ฒฝ๋œ ์†์„ฑ์ด ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ๊ฒฝ์šฐ, ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๊ฐ™์ด ๋ฐ˜ํ™˜ํ•œ๋‹ค๊ณ  ์ดํ•ดํ•˜๋ฉด ๋œ๋‹ค.

HTML
1
2
3
4
5
6
7
8
9
<!-- ์ดˆ๊ธฐ๊ฐ’ -->
<div id="target">
    
</div>

<!-- attributeName ๋ฐ˜ํ™˜๋จ -->
<div id="target" class="hi">
    
</div>

๋งŒ์•ฝ class ์ƒ์„ฑ ์‹œ setAttributeNS๋ฅผ ํ†ตํ•ด ns๋ผ๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋กœ ๋ฌถ์–ด ์ƒ์„ฑํ•œ๋‹ค๋ฉด, ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋œ๋‹ค.

TYPESCRIPT
1
tag.setAttributeNS("ns", "class", "hi");

์œ„์™€ ๊ฐ™์€ ์˜ˆ์‹œ์—์„œ, attributeName์€ class๋ฅผ, attributeNamespace์€ ns๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋œ๋‹ค.

์ถ”๊ฐ€ ํ˜น์€ ์ œ๊ฑฐ๋œ ๋…ธ๋“œ์˜ ๋‹ค์Œ ํ˜•์ œ DOM์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋…ธ๋“œ ์ถ”๊ฐ€, ์‚ญ์ œ์™€ ๊ด€๋ จ๋œ childList ์˜ต์…˜์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ๋ฐ˜ํ™˜๋œ๋‹ค.

์ถ”๊ฐ€ ํ˜น์€ ์ œ๊ฑฐ๋œ ๋…ธ๋“œ์˜ ์ด์ „ ํ˜•์ œ DOM์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๊ฐ€ ๊ฐ์ง€๋œ ๊ฒฝ์šฐ, ๋ณ€๊ฒฝ ์ด์ „์˜ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. attributeOldValue ๋ฐ characterDataOldValue ์˜ต์…˜์ด true์ผ ๊ฒฝ์šฐ์—๋งŒ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ํƒœ๊ทธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ์˜ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์•„๋ž˜์˜ ๊ฐ’ ์ค‘, ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ์˜ ์ด๋ฆ„์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

  • attributes
  • characterData
  • childList

MutationObserver ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ๋„ MutationObserver๋ฅผ ์—ฐ์‡„์ ์œผ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋‹ค.

MutationObserver๋ฅผ ์ปค์Šคํ…€ ํ›…์„ ํ†ตํ•ด ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด๋ณด์ž.

TYPESCRIPT
1
2
3
4
export function useMutationObserver(): void
{
  //
}

์œ„์™€ ๊ฐ™์ด useMutationObserver ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•œ๋‹ค.

useMutationObserver๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ์•„๋ž˜์˜ ์„ธ ์š”์†Œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

  1. ๋Œ€์ƒ DOM
  2. ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ
  3. ์˜ต์…˜

์œ„ ์„ธ ์š”์†Œ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ •์˜ํ•˜์ž. 1๋ฒˆ์˜ ๊ฒฝ์šฐ, ์ผ๋ฐ˜์ ์œผ๋กœ HTMLElement๊ฐ€ ํ•„์š”ํ•˜์ง€๋งŒ, ํ•ด๋‹น ํ›…์—์„œ๋Š” HTMLElement ํƒ€์ž… ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, string์„ ๋ฐ›์•„ #id, .class์™€ ๊ฐ™์€ ์„ ํƒ์ž๋กœ ํƒœ๊ทธ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค ๊ฒƒ์ด๋‹ค.

TYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useEffect } from "react";

export type UseMutationObserverCallback = (entry: MutationRecord) => void;

/**
 * MutationObserver ์ ์šฉ ํ›… ๋ฉ”์„œ๋“œ
 *
 * @param {Element | string | null} ref: Element
 * @param {UseMutationObserverCallback} callback: ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ
 * @param {MutationObserverInit} options: ์˜ต์…˜
 */
export function useMutationObserver(ref: Element | string | null, callback: UseMutationObserverCallback, options: MutationObserverInit): void
{
    useEffect(() =>
    {
        const ro = new MutationObserver((entries) =>
        {
            entries.forEach(callback);
        });
    }, [ ref, callback, options ]);
}

MutationObserver๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ mo ๋ณ€์ˆ˜๋กœ ํ• ๋‹นํ•œ๋‹ค.

์ดํ›„ ref๋กœ ์ง€์ •ํ•œ DOM์„ ํ• ๋‹นํ•œ๋‹ค.

ref๋Š” ์„ธ ๊ฐ€์ง€ ์ƒํƒœ์— ๋”ฐ๋ผ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ถ„๊ธฐ๋ฅผ ๊ฑฐ์นœ๋‹ค.

  • null์ผ ๊ฒฝ์šฐ -> ํŒจ์Šคํ•œ๋‹ค.
  • string์ผ ๊ฒฝ์šฐ -> document.querySelector ๋ฉ”์„œ๋“œ๋กœ ํƒœ๊ทธ๋ฅผ ์„ ํƒํ•œ ๋’ค, ํ•ด๋‹น ํƒœ๊ทธ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.
  • HTMLElement์ผ ๊ฒฝ์šฐ -> ์ด๋ฏธ Element์ด๋ฏ€๋กœ, ์ฆ‰์‹œ ๋“ฑ๋กํ•œ๋‹ค.

typeof ref === "string" ๊ตฌ๋ฌธ์„ ํ†ตํ•ด ref๊ฐ€ ๋ฌธ์ž์—ด์ธ์ง€๋ฅผ ํŒ๋ณ„ํ•˜์—ฌ ๊ตฌํ˜„ํ–ˆ๋‹ค.

mo.observe() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํƒœ๊ทธ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค.

TYPESCRIPT
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
39
40
41
42
43
import { useEffect } from "react";

export type UseMutationObserverCallback = (entry: MutationRecord) => void;

/**
 * MutationObserver ์ ์šฉ ํ›… ๋ฉ”์„œ๋“œ
 *
 * @param {Element | string | null} ref: Element
 * @param {UseMutationObserverCallback} callback: ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ
 * @param {MutationObserverInit} options: ์˜ต์…˜
 */
export function useMutationObserver(ref: Element | string | null, callback: UseMutationObserverCallback, options: MutationObserverInit): void
{
    useEffect(() =>
    {
        const mo = new MutationObserver((entries) =>
        {
            entries.forEach(callback);
        });

        // DOM์ด ์œ ํšจํ•  ๊ฒฝ์šฐ
        if (ref)
        {
            // ref๊ฐ€ ๋ฌธ์ž์—ด์ผ ๊ฒฝ์šฐ
            if (typeof ref === 'string')
            {
                const tag = document.querySelector(ref);

                // ํƒœ๊ทธ๊ฐ€ ์œ ํšจํ•  ๊ฒฝ์šฐ
                if (tag)
                {
                    mo.observe(tag, options);
                }
            }

            // DOM์ผ ๊ฒฝ์šฐ
            else
            {
                mo.observe(ref, options);
            }
        }
    }, [ ref, callback, options ]);
}

๋งˆ์ง€๋ง‰์œผ๋กœ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค MutationObserver์˜ ๋“ฑ๋ก์ด ์ค‘์ฒฉ๋˜์ง€ ์•Š๋„๋ก, ์ดˆ๊ธฐํ™” ์ฝ”๋“œ๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.

mo.disconnect() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด MutationObserver๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.

TYPESCRIPT
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
39
40
41
42
43
44
45
46
47
48
import { useEffect } from "react";

export type UseMutationObserverCallback = (entry: MutationRecord) => void;

/**
 * MutationObserver ์ ์šฉ ํ›… ๋ฉ”์„œ๋“œ
 *
 * @param {Element | string | null} ref: Element
 * @param {UseMutationObserverCallback} callback: ์ฝœ๋ฐฑ ๋ฉ”์„œ๋“œ
 * @param {MutationObserverInit} options: ์˜ต์…˜
 */
export function useMutationObserver(ref: Element | string | null, callback: UseMutationObserverCallback, options: MutationObserverInit): void
{
    useEffect(() =>
    {
        const mo = new MutationObserver((entries) =>
        {
            entries.forEach(callback);
        });

        // DOM์ด ์œ ํšจํ•  ๊ฒฝ์šฐ
        if (ref)
        {
            // ref๊ฐ€ ๋ฌธ์ž์—ด์ผ ๊ฒฝ์šฐ
            if (typeof ref === 'string')
            {
                const tag = document.querySelector(ref);

                // ํƒœ๊ทธ๊ฐ€ ์œ ํšจํ•  ๊ฒฝ์šฐ
                if (tag)
                {
                    mo.observe(tag, options);
                }
            }

            // DOM์ผ ๊ฒฝ์šฐ
            else
            {
                mo.observe(ref, options);
            }
        }

        return () =>
        {
            mo.disconnect();
        };
    }, [ ref, callback, options ]);
}

์ „์ฒด ์ฝ”๋“œ๋Š” ์œ„์™€ ๊ฐ™๋‹ค.

CodeSandbox๋กœ ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.

์ด์™€ ๊ฐ™์ด MutationObserver๋ฅผ ํ™œ์šฉํ•˜๋ฉด DOM์˜ ๋ณ€ํ™”๋ฅผ ๋Šฅ๋™์ ์œผ๋กœ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฐ„ํ˜น, React ์ปดํฌ๋„ŒํŠธ์—์„œ ์š”์†Œ๋ฅผ ๋ณ€๊ฒฝํ•  ๊ฒฝ์šฐ, ์ด๋ฅผ ํƒ€ ์ปดํฌ๋„ŒํŠธ์—์„œ ์—ฐ๊ด€์ง€์–ด ์ถ”๊ฐ€์ ์ธ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ํ•„์š”๊ฐ€ ์ƒ๊ธฐ๊ธฐ๋„ ํ•œ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ปดํฌ๋„ŒํŠธ์— ๋ถˆํ•„์š”ํ•œ ํƒ€ ์ปดํฌ๋„ŒํŠธ ๋กœ์ง์„ ๋„ฃ๊ธฐ๋„ ํ•œ๋‹ค.

MutationObserver๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ด๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‚จ์€ ์˜ต์ €๋ฒ„๋Š” ์ƒ๋Œ€์ ์œผ๋กœ ์‚ฌ์šฉ์„ฑ์ด ๋‚ฎ์€ ๊ฒƒ๋“ค์ด๋‹ค. ๊ทธ ์ค‘, ์„ฑ๋Šฅ๊ณผ ๊ด€๋ จ๋œ PerformanceObserver์— ๋Œ€ํ•ด ๋‹ค๋ฃฐ ์˜ˆ์ •์ด๋‹ค.

Tags

# JavaScript
# TypeScript
# React
# Observer API
# MutationObserver