(Vanilla JS๋ก useState ๋ง๋ค๊ธฐ by ํฉ์ค์ผ๋) 4ํธ
๐พ ๊ธฐ์ ์ฑ ์คํฐ๋
23๋
1์๋ถํฐ ํ๋ ์ค์ธ ๊ต์ก์์, ๋ป์ด ๋ง๋ ๋๋ฃ๋ค๊ณผ ํจ๊ป ์งํํ๊ฒ ๋ ์คํฐ๋
์์ผ๋ก๋ ๊พธ์คํ ๊ธฐ์ ์์ ์ ์ฝ๊ณ ํจ๊ป ๋ฐ์ ํ๋ ์๊ฐ์ด ๋์์ผ๋ฉด ์ข๊ฒ ๋ค!
๋ค์ด๊ฐ๊ธฐ์ ์์
์ด ๋ด์ฉ์ ๊ฐ๋ฐ์ ํฉ์ค์ผ - Vanilla Javascript๋ก useState ๋ง๋ค๊ธฐ์ ๊ณต๋ถํ๋ฉฐ ์์ฑํ ๊ธ์ ๋๋ค. ๋๋ถ๋ถ์ ๋ด์ฉ์ ํฉ์ค์ผ๋์ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ์๊ณ ๋ช๊ฐ์ ๊ฐ๋ ๋ด์ฉ ์ ๋๋ง ์ถ๊ฐ ํน์ ๋ด์ฉ ์์ฝ์ด ๋์ด์์ต๋๋ค. ์์ธํ ์ฌํญ์ ํฉ์ค์ผ๋์ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์!(์ ๋ง ๋๋ฌด ์ข์ ๊ธ์ด์์~!)
Vanilla Javascript๋ก React useState Hook ๋ง๋ค๊ธฐ
1. React์ useState
1) ์๋ฌธ๊ฐ๊ธฐ
import React, { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(1);
// ๋์์ ์ง์ ํธ์ถํ๊ธฐ ์ํด window(์ ์ญ๊ฐ์ฒด)์ ํ ๋น
window.increment = () => setCount(count + 1);
return (
<div>
<strong>count: ${count} </strong>
<button
onClick={() => {
setInterval(() => {
setCount((prev) => prev + 1);
}, 500);
}}
>
์ฆ๊ฐ
</button>
</div>
);
}
// ์๋ ๋ธ๋ก๊ทธ ๊ธ์ ์ฝ๋ ์๋ ์ค๋ช
์ ๋ง๊ฒ ์ฝ๋๋ฅผ ์์
- useState๋ฅผ ํธ์ถํ์ฌ state์ setState๋ฅผ ๊ฐ์ ธ์จ๋ค.
- 500ms(0.5์ด)๋ง๋ค setCount๋ฅผ ์คํํ๋ค.
- ๊ฐ์ด 1์ฉ ์ฆ๊ฐํ๋ค.
- setCount๊ฐ ์คํ๋๋ฉด ๋ค์ ๋ ๋๋ง์ด ์คํ๋๋ค.
- ๋ ๋๋ง์ด ์คํ๋๋ฉด Counter๊ฐ ๋ค์ ์คํ๋ ๊ฒ์ด๋ค.
Counter ์ปดํฌ๋ํธ๊ฐ ๋ค์ ์คํ๋์ด๋ count์ ๊ฐ์ ์ด๊ธฐํ๋์ง ์๊ณ ์ ์ง๋๋ค.
์ด๋ป๊ฒ Counter ํจ์๊ฐ ๋ค์ ์คํ๋๋๋ฐ, count์ ๊ฐ์ ์ด๊ธฐํ๋์ง ์๊ณ ์ ์ง๋ ์ ์์๊น?
2) Bottom-up์ผ๋ก ๋ถ์ํด๋ณด๊ธฐ
๊ฐ์ฅ ์ค์ํ ๋ชฉ์ ์ Counter ํจ์๊ฐ ์คํ๋์ด๋ ์ด๋ป๊ฒ count์ ๊ฐ์ด ์ด๊ธฐํ๋์ง ์๊ณ ์ ์ง๋๋๊ฐ
์ด๋ค.
<div id="app"></div>
function useState (initState) { }
function Counter () {
const [count, setCount] = useState(1);
window.increment = () => setCount(count + 1);
return `
<div>
<strong>count: ${count} </strong>
<button onclick="increment()">์ฆ๊ฐ</button>
</div>
`;
}
function render () {
const $app = document.querySelector('#app');
$app.innerHTML = Counter();
}
render();
๋ฆฌ์กํธ๋ฅผ ์ด๋ฏธ ์๊ณ ์๋ค๋ฉด ์์ ๋๋ต์ ์ธ ๊ฐ์ด ์ฌ ์ ์๋ค. useState๋ state์ setState๋ฅผ ๋ฐํํ๊ณ setState๋ฅผ ์คํํ๊ฒ ๋๋ฉด render๊ฐ ์คํ๋๋ ๊ทธ๋ฐ ํํ์ผ ๊ฒ์ด๋ค.
์ด๋ฅผ ๋ฐ์ํ๋ฉด
function useState(initState) {
let state = initState; // state๋ฅผ ์ ์ํ๋ค.
const setState = (newState) => {
state = newState; // ์๋ก์ด state๋ฅผ ํ ๋นํ๋ค
render(); // render๋ฅผ ์คํํ๋ค.
}
return [ state, setState ];
}
// ์ดํด๋ฅผ ์ํด ๋ด์ฉ ์ถ๊ฐ
function Counter () {
const [count, setCount] = useState(1);
window.increment = () => setCount(count + 1);
return `
<div>
<strong>count: ${count} </strong>
<button onclick="increment()">์ฆ๊ฐ</button>
</div>
`;
}
function render () {
const $app = document.querySelector('#app');
$app.innerHTML = Counter();
}
render();
์์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑ ํ, ์ฆ๊ฐ
๋ฒํผ์ ํด๋ฆญํด๋ render๋๋ count ๊ฐ์ ์ด๊ธฐ๊ฐ์ธ 1 ๊ทธ๋๋ก์ธ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ state๋ฅผ ์ ์งํ๊ธฐ ์ํด์๋ state๋ฅผ ์ ์ญ์ผ๋ก ๊ด๋ฆฌ
ํด์ผ ํ๋ค.
let state = undefined; // state๋ฅผ ์ ์ญ์ผ๋ก ๊ด๋ฆฌํ๋ค.
function useState(initState) {
// state์ ๊ฐ์ด ์์ ๋๋ง ์ด๊ธฐํ๋ฅผ ์งํํ๋ค.
if (state === undefined) {
state = initState;
}
const setState = (newState) => {
state = newState; // ์๋ก์ด state๋ฅผ ํ ๋นํ๋ค
render(); // render๋ฅผ ์คํํ๋ค.
};
return [state, setState];
}
function Counter() {
const [count, setCount] = useState(1);
window.increment = () => setCount(count + 1);
return `
<div>
<strong>count: ${count} </strong>
<button onclick="increment()">์ฆ๊ฐ</button>
</div>
`;
}
function render() {
const $app = document.querySelector("#app");
$app.innerHTML = Counter();
}
render();
์์ ์ฝ๋์์ state๋ฅผ ์ ์ญ์ผ๋ก ๋นผ์ค์ผ๋ก์จ setState๋ฅผ ํตํด ๋ณํ๋ state๊ฐ ํ๋ฉด์ ๋ฐ์๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๊ทธ๋ ๋ค๋ฉด ์ด๋ฒ์ useState์ Component๊ฐ ์ฌ๋ฌ๊ฐ๋ผ๋ฉด ์ด๋ป๊ฒ ๋ ๊น??
let state = undefined; // state๋ฅผ ์ ์ญ์ผ๋ก ๊ด๋ฆฌํ๋ค.
function useState(initState) {
// state์ ๊ฐ์ด ์์ ๋๋ง ์ด๊ธฐํ๋ฅผ ์งํํ๋ค.
if (state === undefined) {
state = initState;
}
const setState = (newState) => {
state = newState; // ์๋ก์ด state๋ฅผ ํ ๋นํ๋ค
render(); // render๋ฅผ ์คํํ๋ค.
};
return [state, setState];
}
function Counter() {
const [count, setCount] = useState(1);
window.increment = () => setCount(count + 1);
return `
<div>
<strong>count: ${count} </strong>
<button onclick="increment()">์ฆ๊ฐ</button>
</div>
`;
}
function Cat() {
const [cat, setCat] = useState("๊ณ ์์ด");
window.meow = () => setCat(cat + " ์ผ์น!");
return `
<div>
<strong>${cat}</strong>
<button onclick="meow()">๊ณ ์์ด์ ์ธ์์๋ฆฌ</button>
</div>
`;
}
function render() {
document.querySelector("#app").innerHTML = `
<div>
${Counter()}
${Cat()}
</div>
`;
}
render();
ํ๋์ state ๋ณ์๋ก 2๊ฐ์ ์ปดํฌ๋ํธ์ state๋ฅผ ๊ด๋ฆฌํ๋ฏ๋ก ๋๋ค ๊ฐ์ ๊ฐ์ผ๋ก ๋ ๋๋ง๋๋ค.(์ฌ์ง์ด ํ๋๋ ์ซ์, ํ๋๋ ๋ฌธ์์ด์ ๋ํ๊ธฐ ๋๋ฌธ์ ๋ฌธ์์ด์ ํ๋ฒ์ด๋ผ๋ ๋ํ๊ฒ ๋๋ฉด ๊ธฐ์กด์ ์ซ์๋ฅผ ๋ํ๋ ๋ก์ง ๋ํ ๋ฌธ์์ด๋ก ๋ค์ด๊ฐ์ ์์ ์ซ์๊ฐ ๊ทธ๋ฅ ๋ถ๊ฒ ๋๋ค. ex 1 + 1 => 11)
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด useState๋ฅผ ํธ์ถํ ๋๋ง๋ค state์ ๊ฐฏ์๋ฅผ ๋๋ ค์ฃผ๋ฉด ๋๋ค.
const states = []; // state๋ฅผ ์ ์ญ์ผ๋ก ๊ด๋ฆฌํ๋ค.
let currentStateKey = 0; // currentStateKey๋ฅผ ์ ์ญ์ผ๋ก ๊ด๋ฆฌํ๋ค.
function useState(initState) {
// initState๋ก ์ด๊ธฐ๊ฐ ์ค์
const key = currentStateKey;
if (states.length === key) {
states.push(initState);
}
// state ํ ๋น
const state = states[key];
const setState = (newState) => {
// state๋ฅผ ์ง์ ์์ ํ๋ ๊ฒ์ด ์๋, states ๋ด๋ถ์ ๊ฐ์ ์์
states[key] = newState;
render();
}
currentStateKey += 1;
return [ state, setState ];
}
function Counter () {
const [count, setCount] = useState(1);
window.increment = () => setCount(count + 1);
return `
<div>
<strong>count: ${count} </strong>
<button onclick="increment()">์ฆ๊ฐ</button>
</div>
`;
}
function Cat () {
const [cat, setCat] = useState('๊ณ ์์ด');
window.meow = () => setCat(cat + ' ์ผ์น!');
return `
<div>
<strong>${cat}</strong>
<button onclick="meow()">๊ณ ์์ด์ ์ธ์์๋ฆฌ</button>
</div>
`;
}
const render = () => {
const $app = document.querySelector('#app');
$app.innerHTML = `
<div>
${Counter()}
${Cat()}
</div>
`;
currentStateKey = 0;
}
render();
์ด์ state๋ฅผ ๋ฐ๋ก ๊ด๋ฆฌํ ์ ์๋ค.
2. useState ์ต์ ํํด๋ณด๊ธฐ
1) ๋์ผํ ๊ฐ์ ๋ํด์ render ํ์ง ์๊ธฐ
setState์ ๊ธฐ์กด์ ๊ฐ๊ณผ ๊ฐ์ ๋๋ render๋๋ ๊ฒ ์ฌ์ค ๋ฌธ์ ๋ ์๋๋ค. ๊ทธ๋ ์ง๋ง ๋์ผํ ๊ฐ์ ๋ ํ ๋ฒ ๊ทธ๋ฆฌ๋ ๊ฒ ํจ์จ์ ์ด์ง ์์ผ๋ ์ด์ ๋ํด์ ์ต์ ํํ๋ ๊ฒ ์ข๋ค!
function Counter () {
const [count, setCount] = useState(1);
window.nochange = () => setCount(1); // count์ ๋๊ฐ์ ๊ฐ์ ์ฝ์
ํ๋ค.
return `
<div>
<strong>count: ${count} </strong>
<button onclick="nochange()">๋ณํ์์</button>
</div>
`;
}
let renderCount = 0;
const render = () => {
const $app = document.querySelector('#app');
$app.innerHTML = `
<div>
renderCount: ${renderCount}
${Counter()}
</div>
`;
currentStateKey = 0;
renderCount += 1;
}
์์ ์ฝ๋์์ Counter๋ ๊ทธ๋๋ก 1์ด์ง๋ง renderCount๋ ๊ณ์ ์ฆ๊ฐํ๋ฉด์ render๊ฐ ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๊ทธ๋ผ ์ด์ useState๋ฅผ ์ต์ ํํด๋ณด์
function useState(initState) {
// initState๋ก ์ด๊ธฐ๊ฐ ์ค์
const key = currentStateKey;
if (states.length === key) {
states.push(initState);
}
// state ํ ๋น
const state = states[key];
const setState = (newState) => {
// ๊ฐ์ด ๋๊ฐ์ ๊ฒฝ์ฐ
if (newState === state) return;
// ๋ฐฐ์ด/๊ฐ์ฒด์ผ ๋๋ JSON.stringify๋ฅผ ํตํด ๊ฐ๋จํ๊ฒ ๋น๊ตํ ์ ์๋ค.
// ๊ทธ๋ฐ๋ฐ Set, Map, WeekMap, Symbol ๊ฐ์ ์์ํ์
์ ๊ฒฝ์ฐ
// JSON์ผ๋ก ํ์ฑ๋์ง ์๊ธฐ ๋๋ฌธ์ ์ฃผ์ํด์ผํ๋ค.
// ๊ฐ์ฒด๋ ๋ชจ์์ ๊ฐ์๋ ๋ค๋ฅธ ๊ฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ ์๋์ฒ๋ผ stringfy๋ก ๋ฑ ๊ทธ ๊ฒ๋ชจ์ต๋ง์ ๋น๊ตํ๋ค.
if (JSON.stringify(newState) === JSON.stringify(state)) return;
// ๊ธฐ์กด ๊ฐ๊ณผ ๋ค๋ฅธ ๊ฒฝ์ฐ์๋ง ๊ฐ์ ๋ณ๊ฒฝํ๊ณ render()๋ฅผ ์คํํ๋ค.
states[key] = newState;
render();
}
currentStateKey += 1;
return [ state, setState ];
}
2) ํ๋์ ํจ์์์ ์ฌ๋ฌ๋ฒ์ setState๋ฅผ ํธ์ถํ๋ ๊ฒฝ์ฐ render๋ฅผ ํ๋ฒ๋ง ์คํํ๊ธฐ
function CounterAndMeow () {
const [count, setCount] = useState(1);
const [cat, setCat] = useState('์ผ์น! ');
function countMeow (newCount) {
setCount(newCount);
setCat('์ผ์น! '.repeat(newCount));
}
window.increment = () => countMeow(count + 1);
window.decrement = () => countMeow(count - 1);
return `
<div>
<p>๊ณ ์์ด๊ฐ ${count}๋ฒ ์ธ์ด์ ${cat} </p>
<button onclick="increment()">์ฆ๊ฐ</button>
<button onclick="decrement()">๊ฐ์</button>
</div>
`;
}
let renderCount = 0;
const render = () => {
const $app = document.querySelector('#app');
$app.innerHTML = `
<div>
${renderCount}
${CounterAndMeow()}
</div>
`;
renderCount += 1;
currentStateKey = 0;
}
์ ์ฝ๋๋ countMeow์์ setState๋ฅผ ๋๋ฒ ํธ์ถํ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋น์ฐํ render๋ 2๋ฒ ํธ์ถ๋๋ค. ์ด๋ฅผ ์ต์ ํํด๋ณด์.
let count = 0;
const debounce = (callback, timer = 0) => {
let currentCallbackTimer = -1;
// ํด๋ก์ ๋ฅผ ์ด์ฉํ๊ธฐ ์ํด debounce๋ฅผ ์คํํ๋ฉด ํจ์๋ฅผ ๋ฐํํ๋ค.
return () => {
count += 1;
// ์คํ์ด ์์ฝ๋ ํจ์(callback)๊ฐ ์์ ๊ฒฝ์ฐ ์บ์ฌํ๋ค.
clearTimeout(currentCallbackTimer);
// ํน์ ์๊ฐ(timer) ํ์ callback์ด ์คํ๋๋๋ก ํ๋ค.
currentCallbackTimer = setTimeout(callback, timer)
}
};
const ์ผ์น = debounce(() => console.log('์ผ์น' + count), 100);
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ
setTimeout(์ผ์น, 100); // ์คํ
์ ์ฝ๋๋ ๋๋ฐ์ด์ค๋ฅผ ์ด์ฉํ ์ฝ๋์ด๋ค. debounce ํจ์๋ ํน์ ์๊ฐ(timer)์ด ์ง๋๊ธฐ ์ ์ ํจ์๊ฐ ์คํ๋๋ฉด ์คํ์ ์ทจ์ํ๊ณ , ํน์ ์๊ฐ(timer)์ด ์ง๋๋ฉด ํจ์๋ฅผ ์คํํ๋ค.
์ผ๋จ ๊ธฐ๋ณธ์ ์ผ๋ก ์ผ์น()
์ด๋ผ๋ ์ฝ๋๋ค์ด ์คํ๋๋ ์๋๋ timer๋ณด๋ค ๋น ๋ฅด๋ค. ์ด timer๋ณด๋ค ๋น ๋ฅธ ์คํ๋ค์ ์คํ์ด ์ทจ์๋๋ค. ๊ทธ๋ฆฌ๊ณ timer๋ณด๋ค ๋ฆ๊ฒ ์คํ๋๋ ์ฝ๋๋ค์ ์คํ์ด ์ทจ์๋์ง ์๊ณ ์คํ๋๋ค.
์ด ๋, ์ฐ๋ฆฌ๋ ๋ช
์์ ์ผ๋ก timer
๋ผ๋ ์ด๋ค ์๊ฐ์ ์ค ํ์๊ฐ ์์๊น? ์๊ฐํด๋ณด์.
์ผ๋ฐ์ ์ผ๋ก ๋ชจ๋ํฐ์ ์ฃผ์ฌ์จ์ 60Hz์ด๋ค. ์ฆ, ์ฐ๋ฆฌ๊ฐ ๋์ผ๋ก ๋ณด๋ ํ๋ฉด์ ๋์ฐ๋ ํ์๊ฐ 1์ด์ 60๋ฒ => 1/60์ด์ 1๋ฒ ์ผ์ด๋๋ค๋ ๊ฒ์ด๋ค. ๊ทธ๋ ๋ค๋ฉด 1/60์ด์ 1๋ฒ ์ด์์ render๊ฐ ์ผ์ด๋๋ ๊ฒ์ ์๋ฏธ๊ฐ ์๋ค.
๊ทธ๋ฌ๋ฉด ์์ ํจ์์ ์ด ์๊ฐ์ ์ ์ฉํ๋ฉด ์๋์ ๊ฐ๋ค.
let count = 0;
const debounce = (callback, timer) => {
let currentCallbackTimer = -1;
// ํด๋ก์ ๋ฅผ ์ด์ฉํ๊ธฐ ์ํด debounce๋ฅผ ์คํํ๋ฉด ํจ์๋ฅผ ๋ฐํํ๋ค.
return () => {
count += 1;
// ์คํ์ด ์์ฝ๋ ํจ์(callback)๊ฐ ์์ ๊ฒฝ์ฐ ์บ์ฌํ๋ค.
clearTimeout(currentCallbackTimer);
// ํน์ ์๊ฐ(timer) ํ์ callback์ด ์คํ๋๋๋ก ํ๋ค.
currentCallbackTimer = setTimeout(callback, timer)
}
};
const ์ผ์น = debounce(() => console.log('์ผ์น' + count), 1000 / 60);
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ
setTimeout(์ผ์น, 100); // ์คํ
์ฆ, ์ด์ฐจํผ ํ๋ฉด์ ๊ทธ๋ฆฌ๋ ๊ฑด 1/60์ด์ 1๋ฒ ์ผ์ด๋๊ธฐ ๋๋ฌธ์, 1/60์ด๋ณด๋ค ๋น ๋ฅด๊ฒ ์คํ๋๋ ์ฝ๋๋ค์ ์คํ์ ์ทจ์ํ๊ฒ ๋ค๋ ๊ฒ์ด๋ค.
๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ ๋ ์กฐ์ฌํด์ผํ๋ ๋ถ๋ถ์ด ์๋ค. ๊ณผ์ฐ setTimeout์ ์ ๋ฌํ 1000 / 60 === 16 ms ๋ผ๋ ๊ฐ์ด ์ ๋ง๋ก 1/60์ด๋ฅผ ์๋ฏธํ๋๊ฐ? ๋ผ๋ ๊ฒ์ด๋ค. ์ด๋ ๋ธ๋ผ์ฐ์ ๋ง๋ค ๋ค๋ฅด๋ค. ๊ทธ๋์ ์ด๋ฅผ ์ ํํ๊ฒ ๊ตฌํ๋ ค๋ฉด requestAnimationFrame
์ ์ฌ์ฉํด์ผํ๋ค.
let count = 0;
const debounceFrame = callback => {
let nextFrameCallback = -1;
// ํด๋ก์ ๋ฅผ ์ด์ฉํ๊ธฐ ์ํด debounce๋ฅผ ์คํํ๋ฉด ํจ์๋ฅผ ๋ฐํํ๋ค.
return () => {
count += 1;
// ์คํ์ด ์์ฝ๋ ํจ์(callback)๊ฐ ์์ ๊ฒฝ์ฐ ์บ์ฌํ๋ค.
cancelAnimationFrame(nextFrameCallback);
// ํน์ ์๊ฐ(timer) ํ์ callback์ด ์คํ๋๋๋ก ํ๋ค.
nextFrameCallback = requestAnimationFrame(callback)
}
};
const ์ผ์น = debounceFrame(() => console.log('์ผ์น' + count));
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ ์ทจ์
์ผ์น(); // ์คํ
setTimeout(์ผ์น, 100); // ์คํ
- requestAnimationFrame(callback)์ ๋ธ๋ผ์ฐ์ ์๊ฒ ์ํํ๊ธฐ๋ฅผ ์ํ๋ ์ ๋๋ฉ์ด์ ์ ์๋ฆฌ๊ณ , ๋ค์ ๋ฆฌํ์ธํธ๊ฐ ์งํ๋๊ธฐ ์ ์ ์ธ์๋ก ๋๊ฒจ์ง callbackํจ์๋ฅผ ํธ์ถํ๋ค.
- ์ผ๋ฐ์ ์ผ๋ก requestAnimationFrame์ด 1์ด๋์ ์คํ๋๋ ํ์๋ ๋๋ถ๋ถ์ ๋ธ๋ผ์ฐ์ ์์๋ W3C ๊ถ์ฅ์ฌํญ์ ๋ฐ๋ผ ๋์คํ๋ ์ด ์ฃผ์ฌ์จ๊ณผ ์ผ์นํ๊ฒ ๋๋ค.
function debounceFrame (callback) {
let nextFrameCallback = -1;
return () => {
cancelAnimationFrame(nextFrameCallback);
nextFrameCallback = requestAnimationFrame(callback)
}
};
let renderCount = 0;
const render = debounceFrame(() => {
const $app = document.querySelector('#app');
$app.innerHTML = `
<div>
renderCount: ${renderCount}
${CounterAndMeow()}
</div>
`;
renderCount += 1;
currentStateKey = 0;
});
requestAnimationFrame์ ์ ์ฉํ debounce์ render์ ์ฌ์ฉํด๋ณด์. ์ด์ ๋ 1/60์ด์ 1๋ฒ์ฉ๋ง render๊ฐ ์คํ๋๋ค.
3. render ํจ์ ์ถ์ํํ๊ธฐ
์ด ๋ถ๋ถ์ ์ ์ฒด ์ฝ๋๋ง ์ฌ๋ฆฌ๊ณ ์์ธํ ์ค๋ช ์ ์๋ตํ๊ฒ ์ต๋๋ค.
function MyReact () {
const options = {
currentStateKey: 0,
renderCount: 0,
states: [],
root: null,
rootComponent: null,
}
function useState (initState) {
const { currentStateKey: key, states } = options;
if (states.length === key) states.push(initState);
const state = states[key];
const setState = (newState) => {
states[key] = newState;
_render();
}
options.currentStateKey += 1;
return [ state, setState ];
}
const _render = debounceFrame(() => {
const { root, rootComponent } = options;
if (!root || !rootComponent) return;
root.innerHTML = rootComponent();
options.currentStateKey = 0;
options.renderCount += 1;
});
function render (rootComponent, root) {
options.root = root;
options.rootComponent = rootComponent;
_render();
}
return { useState, render };
}
const { useState, render } = MyReact();
function CounterAndMeow () {
const [count, setCount] = useState(1);
const [cat, setCat] = useState('์ผ์น! ');
function countMeow (newCount) {
setCount(newCount);
setCat('์ผ์น! '.repeat(newCount));
}
window.increment = () => countMeow(count + 1);
window.decrement = () => countMeow(count - 1);
return `
<div>
<p>๊ณ ์์ด๊ฐ ${count}๋ฒ ์ธ์ด์ ${cat} </p>
<button onclick="increment()">์ฆ๊ฐ</button>
<button onclick="decrement()">๊ฐ์</button>
</div>
`;
}
function debounceFrame (callback) {
let nextFrameCallback = -1;
return () => {
cancelAnimationFrame(nextFrameCallback);
nextFrameCallback = requestAnimationFrame(callback)
}
};
const App = () => `
<div>
${CounterAndMeow()}
</div>
`;
render(App, document.querySelector('#app'));
4. ๋ชจ๋ํํ๊ธฐ
๋ชจ๋ํ ๋ด์ฉ๋ ์๋ตํ๊ฒ ์ต๋๋ค.
.
โโ src
โ โโ components
โ โ โโ CounterAndMeow.js
โ โโ core
โ โ โโ MyReact.js
โ โโ utils
โ โ โโ debounceFrame.js
โ โโ App.js
โ โโ main.js
โโ index.html