[React] ๋ฆฌ์กํธ์ ์ฌ๋ฐ๋ฅธ useEffect ์ฌ์ฉํ
๋ฆฌ์กํธ์ useEffect
๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ, ๊ตฌ๋
๊ด๋ฆฌ, DOM ์
๋ฐ์ดํธ, ์ฌ์ด๋ ์ดํํธ ์ฒ๋ฆฌ ๋ฑ ๋ค์ํ ์์
์ ์ฌ์ฉ๋๋ค. ๊ทธ๋ฌ๋ useEffect
๋ฅผ ๊ณผ๋ํ๊ฒ ์ฌ์ฉํ๋ฉด ์ฑ๋ฅ ์ ํ, ๋ถํ์ํ ๋ ๋๋ง, ๋๋ฒ๊น
์ ๋ณต์ก์ฑ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค. "Leave useEffect Alone!" ๋ผ๋ ๊ฐ์ด๋ ๊ธ์ ์ฐธ๊ณ ํ์ฌ ์ฌ๋ฐ๋ฅธ useEffect
์ฌ์ฉ๋ฒ์ ๋ํ ์ถ๊ฐ ์ค๋ช
์ ๋ง๋ถ์ฌ์ ์ ๋ฆฌํด ๋ดค๋ค.
๊ฒฝ์ ์ํ(Race Condition) โญ
๊ฒฝ์ ์ํ๋ ์ฌ๋ฌ ๋น๋๊ธฐ ์์ ์ด ๋์์ ์คํ๋ ๋, ์คํ ์์๋ ๊ฒฐ๊ณผ๊ฐ ์์ธกํ์ง ์์ ๋ฐฉ์์ผ๋ก ์๋ํ๋ ํ์์ ๊ฐ๋ฆฌํจ๋ค.
์๋ ์ฝ๋์์ ๋ฒํผ์ ์ฌ๋ฌ ๋ฒ ํด๋ฆญํ๋ฉด counter
๊ฐ์ด ์ฆ๊ฐํ๊ณ , ๊ฐ ์์ฒญ์ ๋๋คํ ์๊ฐ๋งํผ ๋๊ธฐํ ํ response
์ํ๋ฅผ ์
๋ฐ์ดํธํ๋ค. ํ์ง๋ง ์ด ๊ณผ์ ์์ ๊ฒฝ์ ์ํ๊ฐ ๋ฐ์ํ ์ ์๋ค(์ฝ๋ํ ๋งํฌ).
function RaceConditionExample() {
const [counter, setCounter] = useState(0);
const [response, setResponse] = useState(0);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const request = async (requestId) => {
setIsLoading(true);
await sleep(Math.random() * 3000);
setResponse(requestId);
setIsLoading(false);
};
request(counter);
}, [counter]);
const handleClick = () => {
setCounter((prev) => ++prev);
};
return (
<>
<h3>Current Value: {counter}</h3>
<h3>Settled Response: {response}</h3>
<button onClick={handleClick}>Increment</button>
{/* ... */}
</>
);
}
- ์ฒซ ๋ฒ์งธ ํด๋ฆญ:
counter + 1
,requestId 1
์์ฒญ ์์ - ๋ ๋ฒ์งธ ํด๋ฆญ:
counter + 1
,requestId 2
์์ฒญ ์์ - ๋ง์ฝ ๋ ๋ฒ์งธ ์์ฒญ์ด ๋ ๋นจ๋ฆฌ ์๋ฃ๋๋ฉด
response
์ํ๋2
๋ก ์ ๋ฐ์ดํธ - ์ดํ ์ฒซ ๋ฒ์งธ ์์ฒญ์ด ์๋ฃ๋๋ฉด์
response
์ํ๋ฅผ1
๋ก ๋ฎ์ด์
์ด์ฒ๋ผ response
์ํ๋ ๊ฐ์ฅ ์ต์ ์์ฒญ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ํด์ผ ํ์ง๋ง, ๋น๋๊ธฐ ์์ฒญ์ ์๋ฃ ์์๊ฐ ๋๋ค ํ๊ธฐ ๋๋ฌธ์ ์๋์น ์์ ๊ฒฐ๊ณผ๊ฐ ๋ฐ์ํ๊ณ ์๋ค.
์ด๋ฌํ ๊ฒฝ์ ์ํ๋ ํด๋ฆฐ์ ํจ์๋ฅผ ์ฌ์ฉํด์ ์ฒ๋ฆฌํ ์ ์๋ค. ํด๋ฆฐ์ ํจ์๋ ๋ค์ ์ดํํธ ํจ์๊ฐ ์คํ๋๊ธฐ ์ ์ ํธ์ถ๋๋ฉฐ, ์ด์ ์ดํํธ ํจ์์ ์ค์ฝํ์์ ๋์ํ๋ค.
useEffect(() => {
let ignore = false; // ํ์ฌ useEffect ํจ์์ ์ค์ฝํ
const request = async (requestId) => {
setIsLoading(true);
await sleep(Math.random() * 3000);
if (!ignore) {
setResponse(requestId);
setIsLoading(false);
}
};
request(counter);
return () => {
ignore = true; // ์ด์ useEffect ํจ์์ ์ค์ฝํ
};
}, [counter]);
- ์ฒซ ๋ฒ์งธ ํด๋ฆญ:
counter + 1
,requestId 1
์์ฒญ ์์,ignore = false
๋ก ์ด๊ธฐํ - ๋ ๋ฒ์งธ ํด๋ฆญ:
counter + 1
,requestId 2
์์ฒญ ์์- ์ฒซ ๋ฒ์งธ ํด๋ฆญ์์ ์คํ๋
useEffect
ํด๋ฆฐ์ ํจ์ ํธ์ถ →ignore = true
๋ก ๋ณ๊ฒฝ (์ด์ ์์ฒญ ๋ฌดํจํ) - ๋ ๋ฒ์งธ ํด๋ฆญ์์ ์คํ๋
useEffect
ํจ์ ํธ์ถ →ignore = false
๋ก ์ด๊ธฐํ
- ์ฒซ ๋ฒ์งธ ํด๋ฆญ์์ ์คํ๋
- ๋ ๋ฒ์งธ ์์ฒญ ์๋ฃ: ๋ ๋ฒ์งธ
useEffect
์ignore
๋ณ์๊ฐfalse
์ด๋ฏ๋กresponse
์ํ2
๋ก ์ ๋ฐ์ดํธ - ์ฒซ ๋ฒ์งธ ์์ฒญ ์๋ฃ: ์ฒซ ๋ฒ์งธ
useEffect
์ignore
๋ณ์๊ฐtrue
์ด๋ฏ๋กresponse
์ํ ๋ณ๊ฒฝ ์ ํจ
๋ถํ์ํ ๋ ๋๋ง
์๋ ์ฝ๋๋ useEffect
์ฌ์ฉ์ผ๋ก ๋ถํ์ํ ๋ ๋๋ง์ด ํ ๋ฒ ๋ ๋ฐ์ํ๋ค.
function Parent() {
const [someState, setSomeState] = useState();
return <Child onChange={(v) => setSomeState(v)} />;
}
function Child({ onChange }) {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
onChange(isOn); // ์ถ๊ฐ ๋ ๋๋ง ์ ๋ฐ
}, [isOn, onChange]);
function handleClick() {
setIsOn(!isOn);
}
return <button onClick={handleClick}>Toggle</button>;
}
- ํด๋ฆญ ์ด๋ฒคํธ ๋ฐ์ →
Child
์ปดํฌ๋ํธ์ ๋ก์ปฌ ์ํ(isOn
) ์ ๋ฐ์ดํธ useEffect
์คํ →Parent
์ปดํฌ๋ํธ์์ ์ ๋ฌ๋ฐ์ ํธ๋ค๋ฌ(onChange
) ์คํParent
์ปดํฌ๋ํธ ์ํ ์ ๋ฐ์ดํธ → ๋ฆฌ๋ ๋๋งChild
์ปดํฌ๋ํธ ๋ฆฌ๋ ๋๋ง
ํด๋ฆญ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ์ฝ๋ฐฑ(onChange
)์ ์คํํ๋ ๋ก์ง์ด๋ฏ๋ก useEffect
๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์๋ค. ํด๋ฆญ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ด๋ถ์์ ์ง์ onChange
๋ฅผ ํธ์ถํ๋ฉด ๋ถํ์ํ ๋ ๋๋ง์ ์ค์ผ ์ ์๊ณ , ์ฝ๋๋ ๋ ๊น๋ํด์ง๋ค.
function Parent() {
const [someState, setSomeState] = useState();
return <Child onChange={(v) => setSomeState(v)} />;
}
function Child({ onChange }) {
const [isOn, setIsOn] = useState(false);
function handleClick() {
const newValue = !isOn;
setIsOn(newValue); // ๋ก์ปฌ ์ํ ์
๋ฐ์ดํธ
onChange(newValue); // ๋ถ๋ชจ ํธ๋ค๋ฌ ํธ์ถ
}
return <button onClick={handleClick}>Toggle</button>;
}
๋ฐ์ดํฐ ํ๋ฆ
๋ฆฌ์กํธ์์ ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ(๋ถ๋ชจ → ์์)์ ๋ฐ๋ฅด๋ ๊ฒ์ด ์ ์ง๋ณด์๋ ๊ฐ๋ ์ฑ ์ธก๋ฉด์์ ์ ๋ฆฌํ๋ค. ์ด๋ฅผ ํตํด ๋ฐ์ดํฐ ์ถ์ ์ด ์ฉ์ดํด์ง๊ณ , ์ปดํฌ๋ํธ ๊ฐ ์์กด์ฑ์ ์ค์ฌ์ ์ฝ๋ ์์ ์ฑ๊ณผ ์ฌ์ฌ์ฉ์ฑ์ ๋์ผ ์ ์๋ค.
์๋ ์ฝ๋์ฒ๋ผ ์์ ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์จ ํ ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํ๋ฉด ๋ฐ์ดํฐ ๋ก๋ฉ ์ํ์ ์ค๋ฅ ์ฒ๋ฆฌ ๋ก์ง์ด ๋ถ์ฐ๋๋ฉด์ ๊ด๋ฆฌ์ ๋๋ฒ๊น ์ด ๋ณต์กํด์ง๋ค. ๋ํ, ๋ฐ์ดํฐ ์์ ๊ถ์ด ๋ถ๋ช ํํด์ง๊ณ ์ปดํฌ๋ํธ์ ์ฑ ์ ๊ฒฝ๊ณ๊ฐ ํ๋ ค์ง๋ ๋จ์ ๋ ์๋ค.
function Parent() {
const [data, setData] = useState(null);
return <Child onFetched={setData} />;
}
function Child({ onFetched }) {
const data = useFetchData();
useEffect(() => {
if (data) onFetched(data);
}, [onFetched, data]);
return <>{JSON.stringify(data)}</>;
}
๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๊ณ , ํ์ํ ๋ฐ์ดํฐ๋ง ์์ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํ๋ฉด, ๋ฐ์ดํฐ ํ๋ฆ์ด ๋จ์ํด์ง๊ณ ์ปดํฌ๋ํธ ๊ฐ ์ฑ ์ ๋ถ๋ฆฌ๊ฐ ์ฌ์์ง๋ค.
function Parent() {
const data = useFetchData();
return <Child data={data} />;
}
function Child({ data }) {
return <>{JSON.stringify(data)}</>;
}
๋ง์ฝ ์์ ์ปดํฌ๋ํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํด์ผ ํ๋ ์ํฉ์ด๋ผ๋ฉด, ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์ ์ํ ํธ๋ค๋ฌ๋ฅผ ์์ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํ์ฌ ๋ฐ์ดํฐ ๋ก์ง์ ์ฑ ์์ ๋ถ๋ชจ ์ปดํฌ๋ํธ๊ฐ ๊ด๋ฆฌํ๋๋ก ํ๋ ๊ฒ์ด ์ข๋ค.
function Parent() {
const handleSuccess = (data) => { /* ... */ };
const handleError = (error) => { /* ... */ };
return <Child onSuccess={handleSuccess} onError={handleError} />;
}
function Child({ onSuccess, onError }) {
const { data } = useMutateData({ onSuccess, onError });
return <>{JSON.stringify(data)}</>;
}
์ด๊ธฐํ
์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ๋ ๋ ํ ๋ฒ๋ง ์คํ๋๋ ์ด๊ธฐํ ๋ก์ง์ useEffect
์ ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก ๋น ๋ฐฐ์ด []
์ ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ ์ ์๋ค.
function App() {
useEffect(() => {
someOneTimeLogic();
}, []);
// ...
}
ํ์ง๋ง ๋ฆฌ์กํธ ๊ฐ๋ฐ ํ๊ฒฝ์์ ์๊ฒฉ(strict) ๋ชจ๋๊ฐ ํ์ฑํ๋์ด ์๋ค๋ฉด, ์ฌ์ดํธ ์ดํํธ๊ฐ ์์ ํ๊ฒ ์ฒ๋ฆฌ๋๋์ง ํ์ธํ๊ธฐ ์ํด useEffect
๊ฐ ํ ๋ฒ ๋ ์คํ๋๋ค.
๋ํ React 18 ๋ฒ์ ๋ถํฐ ๋์
๋ Concurrent Mode๊ฐ ํ์ฑํ๋๋ฉด, ์์
์ ์ฐ์ ์์๊ฐ ๋ณ๊ฒฝ๋๊ฑฐ๋ ์ต์ ํ๋ฅผ ์ํด ์์
์ด ์ทจ์๋ ํ ๋ค์ ์๋๋ ์ ์๋ค. ์ด๋ก ์ธํด ๋ ๋๋ง์ด ์ค๋จ๋๊ฑฐ๋ ์ฌ์คํ๋๋ ๊ณผ์ ์์ useEffect
๊ฐ ์ฌ๋ฌ ๋ฒ ์คํ๋ ๊ฐ๋ฅ์ฑ์ด ์๋ค.
์ด๊ธฐํ ๋ก์ง์ด ์ ํํ ํ ๋ฒ๋ง ์คํ๋๋๋ก ๋ณด์ฅํ๋ ค๋ฉด ์๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์๋ค.
// ๋ฐฉ๋ฒ1: ์ต์์ ํ๋๊ทธ ์ฌ์ฉ
let didInit = false;
function App() {
useEffect(() => {
if (!didInit) {
didInit = true;
someOneTimeLogic();
}
}, []);
// ...
}
// ๋ฐฉ๋ฒ2: ์ปดํฌ๋ํธ ๋ ๋๋ง ์ด์ ์ ์ด๊ธฐํ
if (typeof window !== "undefined") {
someOneTimeLogic();
}
function App() {
// ...
}
์ปดํฌ๋ํธ ์ธ๋ง์ดํธ
useEffect
๋ด๋ถ์์ ๋น๋๊ธฐ ์์
ํ ์ํ ์
๋ฐ์ดํธ๋ฅผ ์๋ํ๋ฉด ์๋์ ๊ฐ์ ๊ฒฝ๊ณ ๊ฐ ํ์๋ ์ ์๋ค.
Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ์ํฉ์์ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํ ๋ชฉ์ ์ ๊ฒฝ๊ณ ๋ฉ์์ง๋ค. ๋ง์ฝ ๋น๋๊ธฐ ์์ ์ด ์๋ฃ๋๊ธฐ ์ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋๋ฉด, ๋ถํ์ํ ์ํ ์ ๋ฐ์ดํธ๋ก ์ธํด ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ด๋ํ ์ ์๋ค.
์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ผ๋ฐ์ ์ผ๋ก useEffect
์ ํด๋ฆฐ์
ํจ์๋ฅผ ํ์ฉํ์ฌ ์ปดํฌ๋ํธ์ ๋ง์ดํธ ์ํ๋ฅผ ์ถ์ ํ๊ณ ์์ ํ ์ํ ์
๋ฐ์ดํธ๋ฅผ ๊ตฌํํ๋ค.
// ์ธ๋ง์ดํธ ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํ ํ
const useMountedRef = () => {
const mounted = useRef(true);
useEffect(() => {
return () => {
mounted.current = false;
};
}, []);
return mounted;
};
const MyCompoenet = () => {
const [loading, setLoading] = useState(false);
const mountedRef = useMountedRef();
const handleDeleteBill = async (id) => {
setLoading(true);
await axios.delete(`/bill/${id}`);
// ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋์ง ์์์ ๋๋ง ์ํ ์
๋ฐ์ดํธ
if (mountedRef.current) setLoading(false);
};
return (
<button onClick={handleDeleteBill} disabled={loading}>
Delete Bill
</button>
);
};
ํ์ง๋ง ๋ฆฌ์กํธ ํ์ ์ด๋ฌํ ๊ฒฝ๊ณ ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๋๋ฐ ํฐ ๋์์ด ๋์ง ์์ผ๋ฉฐ, ์คํ๋ ค ๊ฐ๋ฐ ๊ฒฝํ์ ์ ํดํ๋ค๊ณ ํ๋จํ์ฌ ๊ฒฝ๊ณ ๋ฅผ ์ ๊ฑฐํ๋ค(์ฐธ๊ณ ๋งํฌ). React 18 ๋ฒ์ ๋ถํด ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ๊ฒฝ์ฐ์๋ ์ํ ์ ๋ฐ์ดํธ์ ๋ํ ๊ฒฝ๊ณ ์์ด ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.
์ปดํฌ๋ํธ ๋น๋๊ธฐ ์์
์ด ์ธ๋ง์ดํธ ์ํ์ ๊ด๊ณ์์ด ์์ ํ๊ฒ ์คํ๋ ์ ์๋ค๋ฉด, ์ฆ ๋น๋๊ธฐ ์์
์ ๊ฒฐ๊ณผ๊ฐ ์ปดํฌ๋ํธ์ ์ํ์ ์ง์ ์ ์ผ๋ก ์ฐ๊ฒฐ๋์ง ์๋ค๋ฉด ํด๋ฆฐ์
ํจ์๋ฅผ ์ฌ์ฉํ๋ ๋ณต์กํ ์ฒ๋ฆฌ๊ฐ ํ์ํ์ง ์๋ค. ์ ์์ ์์ ๋จ์ํ ์ญ์ ์์ฒญ์ ๋ณด๋ด๋ ์์
์ด๊ธฐ ๋๋ฌธ์ useEffect
๋ฅผ ์ ๊ฑฐํ์ฌ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์์ฑํ ์ ์๋ค.
const MyCompoenet = () => {
const [loading, setLoading] = useState(false);
const handleDeleteBill = async (id) => {
setLoading(true);
await axios.delete(`/bill/${id}`);
setLoading(false);
};
return (
<button onClick={handleDeleteBill} disabled={loading}>
Delete Bill
</button>
);
};
๐ก useEffect
๋ด๋ถ์์ ์ด๋ฒคํธ ๋ฆฌ์ค๋, ํ์ด๋จธ, ์น์์ผ ๊ตฌ๋
๋ฑ์ ์ค์ ํ๋ ๊ฒฝ์ฐ ๋ฉ๋ชจ๋ฆฌ ๋์๋ ์๊ธฐ์น ์์ ๋์์ด ๋ฐ์ํ ์ ์๊ธฐ ๋๋ฌธ์ ํด๋ฆฐ์
ํจ์๋ฅผ ์ฌ์ฉํด ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ๋ ๊ฒ์ด ์ข๋ค.
๋ ํผ๋ฐ์ค
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋ ์ต์ ํ ๊ธฐ๋ฒ ๋ชจ์ (23) | 2024.12.07 |
---|---|
[Algorithm] ์ฌ๋ผ์ด๋ฉ ์๋์ฐ Sliding Window ์๊ณ ๋ฆฌ์ฆ ํบ์๋ณด๊ธฐ (2) | 2024.11.11 |
[React] ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ๊ฐ์ ํ ์ ์๋ 4๊ฐ์ง ํ (0) | 2024.10.28 |
[Flutter] ํ๋ฌํฐ ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ - Part 2 (1) | 2024.10.13 |
[Flutter] ํ๋ฌํฐ ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ - Part 1 (0) | 2024.10.05 |
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋ ์ต์ ํ ๊ธฐ๋ฒ ๋ชจ์
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋ ์ต์ ํ ๊ธฐ๋ฒ ๋ชจ์
2024.12.07 -
[Algorithm] ์ฌ๋ผ์ด๋ฉ ์๋์ฐ Sliding Window ์๊ณ ๋ฆฌ์ฆ ํบ์๋ณด๊ธฐ
[Algorithm] ์ฌ๋ผ์ด๋ฉ ์๋์ฐ Sliding Window ์๊ณ ๋ฆฌ์ฆ ํบ์๋ณด๊ธฐ
2024.11.11 -
[React] ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ๊ฐ์ ํ ์ ์๋ 4๊ฐ์ง ํ
[React] ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ๊ฐ์ ํ ์ ์๋ 4๊ฐ์ง ํ
2024.10.28 -
[Flutter] ํ๋ฌํฐ ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ - Part 2
[Flutter] ํ๋ฌํฐ ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ - Part 2
2024.10.13