React.memo์ ๋ํด ์ค๋ช
ํด์ฃผ์ธ์.์๋: ์ฑ๋ฅ ์ต์ ํ์ ๊ด์ฌ์ด ์๋์ง ํ์ธํ๋ ์ง๋ฌธ
ํ: ์ฌ์ฉ ์์๋ฅผ ๋ค๋ฉด ์ข๋ค.
๋์ ๋ต์
React.memo๋ ๋ฆฌ์กํธ ํจ์ํ ์ปดํฌ๋ํธ์ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ๊ธฐ ์ํ ์ฑ๋ฅ ์ต์ ํ ๋๊ตฌ์ ๋๋ค.๋ฆฌ์กํธ์์๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋๋ฉด, ๊ธฐ๋ณธ์ ์ผ๋ก ์์ ์ปดํฌ๋ํธ๋ ๋ชจ๋ ๋ค์ ๋ ๋๋ง๋ฉ๋๋ค.
์ด๋ ์์์ props๊ฐ ๋ฐ๋์ง ์์๋๋ฐ๋ ๋ฆฌ๋ ๋๋ง์ด ์ผ์ด๋๋ฉด ๋ถํ์ํ ์ฐ์ฐ ๋ญ๋น๊ฐ ๋ฐ์ํฉ๋๋ค.
React.memo๋ ์ด๋ฐ ์ํฉ์ ๋ง๊ธฐ ์ํด ์ปดํฌ๋ํธ์ props๋ฅผ ์์ ๋น๊ต(shallow compare)ํ๊ณ ,
props๊ฐ ์ด์ ๋ ๋์ ๋์ผํ๋ค๋ฉด ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ ๋๋งํ์ง ์๊ณ ์ด์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํฉ๋๋ค.๋ค๋ง, props ๋น๊ต ๊ณผ์ ์์ฒด๋ ๋น์ฉ์ด ์๊ธฐ ๋๋ฌธ์,
๋ ๋๋ง ๋น์ฉ์ด ํฐ ์ปดํฌ๋ํธ๋ ์์ฃผ ๋ฆฌ๋ ๋๋ง๋๋ ์์ ์ปดํฌ๋ํธ์ ์์์ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํ๋ ๊ฒ์ด ํจ๊ณผ์ ์ ๋๋ค.
์ฃผ์ด์ง ๋ต์ (๋ชจ๋ฒ ๋ต์)
๋ฆฌ์กํธ์์ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋๋ ๊ธฐ์ค์ ์ดํด๋ณด์๋ฉด ๋ํ์ ์ผ๋ก ๋ฐ์์จ props์ ๋ณ๊ฒฝ์ด ์์ต๋๋ค.
props๊ฐ ๋ณ๊ฒฝ๋๋ฉด ํ์ ์ปดํฌ๋ํธ ๋ชจ๋๊ฐ ๋ฆฌ๋ ๋๋ง๋ฉ๋๋ค.
๊ทธ๋ฐ๋ฐ ๋ง์ฝ props์ ๋ณ๊ฒฝ๊ณผ ํ์ ์ปดํฌ๋ํธ์ ๋ฆฌ๋ ๋๋ง๊ณผ ๊ด๋ จ์ด ์๋ค๋ฉด ์ด๋จ๊น์?
ํ์ํ ๋ถ๋ถ๋ง ์ ๋ฐ์ดํธํ๊ณ ๋๋จธ์ง๋ ๊ทธ๋๋ก ๋๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ฒ์ด ๋ฐ๋กReact.memo์ ๋๋ค.์๋ฅผ ๋ค์ด์ ๋ฆฌ์คํธ ๋ ๋๋ง์์ ๋ฆฌ์คํธ ์์ดํ ์
React.memo๋ฅผ ๋ถ์ฌ์ฃผ๊ฒ ๋๋ฉด ๋ฆฌ์คํธ์์ ๋ณ๊ฒฝ๋ ๋ถ๋ถ์ ์์ดํ ๋ง ๋ฆฌ๋ ๋๋งํ ์ ์์ต๋๋ค.
๋ฆฌ์คํธ ์ ์ฒด๋ฅผ ๋ฆฌ๋ ๋๋งํ์ง ์์์ผ๋ก์จ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๊ฒ์ด ๊ฐ๋ฅํด์ง ๊ฒ๋๋ค.
React.memo๋?React.memo๋ React์์ ์ ๊ณตํ๋ ๊ณ ์ฐจ ์ปดํฌ๋ํธ(Higher Order Component)๋ก, ์ปดํฌ๋ํธ์ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.React.memo๋ props๊ฐ ๋์ผํ ๊ฒฝ์ฐ ์ปดํฌ๋ํธ์ ๋ ๋๋ง์ ๊ฑด๋๋ฐ๋ ์ญํ ์ ํ๋ค.React.memo์ ์ฌ์ฉ๋ฒ๊ธฐ๋ณธ์ ์ธ ํํ๋ ๋ค์๊ณผ ๊ฐ๋ค.
import React from "react";
const MyComponent = (props) => {
console.log("๋ ๋๋ง!");
return <div>{props.value}</div>;
};
// React.memo๋ก ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ์ ๋ฉ๋ชจ์ด์ ์ด์
export default React.memo(MyComponent);
์ด์ ์ด ์ปดํฌ๋ํธ๋ props๊ฐ ๋ณ๊ฒฝ๋์ง ์๋ ํ ๋ค์ ๋ ๋๋ง๋์ง ์๋๋ค.
React.memo์ ๋์ ์๋ฆฌReact๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ถ๋ชจ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋๋ฉด ์์ ์ปดํฌ๋ํธ๋ ํจ๊ป ๋ฆฌ๋ ๋๋ง๋๋ค.
ํ์ง๋ง ์์ ์ปดํฌ๋ํธ๊ฐ ๋์ผํ props๋ฅผ ๋ฐ๋ ๊ฒฝ์ฐ, React.memo๋ฅผ ์ฌ์ฉํ๋ฉด ์ด์ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฅผ ์บ์ฑํ์ฌ ๋ถํ์ํ ๋ ๋๋ง์ ๋ฐฉ์งํ ์ ์๋ค.
React.memo๋ ๋ด๋ถ์ ์ผ๋ก ์์ ๋น๊ต(shallow comparison)๋ฅผ ์ฌ์ฉํ์ฌ props์ ๋ณ๊ฒฝ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ค.
React.memo ์ฌ์ฉ ์์ ์์ 1: ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ
import React, { useState } from "react";
const Child = React.memo(({ count }) => {
console.log("Child ๋ ๋๋ง");
return <div>Count: {count}</div>;
});
const Parent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
return (
<div>
<button onClick={() => setCount(count + 1)}>์ฆ๊ฐ</button>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="ํ
์คํธ ์
๋ ฅ"
/>
<Child count={count} />
</div>
);
};
export default Parent;
Child ์ปดํฌ๋ํธ๋ React.memo๋ก ๊ฐ์ธ์ ธ ์์ผ๋ฏ๋ก, text๊ฐ ๋ณ๊ฒฝ๋์ด๋ count๊ฐ ๋ณํ์ง ์๋ ํ ๋ฆฌ๋ ๋๋ง๋์ง ์๋๋ค.React.memo๊ฐ ์๋ค๋ฉด, Parent ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋ ๋ Child๋ ํญ์ ๋ฆฌ๋ ๋๋ง๋๋ค.์์ 2: ์ปค์คํ
๋น๊ต ํจ์ ์ฌ์ฉ
๊ธฐ๋ณธ์ ์ผ๋ก React.memo๋ ์์ ๋น๊ต๋ฅผ ์ํํ๋ค.
๋ง์ฝ props๊ฐ ๊ฐ์ฒด๋ ๋ฐฐ์ด์ฒ๋ผ ์ฐธ์กฐ ํ์
์ด๋ผ๋ฉด, React.memo์ ๊ธฐ๋ณธ ๋น๊ต๋ก๋ props๊ฐ ํญ์ ๋ณ๊ฒฝ๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผ๋๋ค.
์ด๋ ์ปค์คํ
๋น๊ต ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
import React from "react";
const Child = React.memo(
({ user }) => {
console.log("Child ๋ ๋๋ง");
return <div>User: {user.name}</div>;
},
(prevProps, nextProps) => {
// ์ฌ์ฉ์ ์ ์ ๋น๊ต ํจ์
return prevProps.user.name === nextProps.user.name;
}
);
const Parent = () => {
const user = { name: "John" };
return <Child user={user} />;
};
export default Parent;
๊ธฐ๋ณธ์ ์ผ๋ก ๊ฐ์ฒด user๋ ํญ์ ์๋ก์ด ์ฐธ์กฐ๊ฐ์ ๊ฐ์ง๋ฏ๋ก, React.memo๋ง ์ฌ์ฉํ๋ฉด Child๊ฐ ๋งค๋ฒ ๋ฆฌ๋ ๋๋ง๋๋ค.
์ปค์คํ
๋น๊ต ํจ์๋ก user.name๋ง ๋น๊ตํ๋ฉด ๋ถํ์ํ ๋ ๋๋ง์ ๋ฐฉ์งํ ์ ์๋ค.
โญ
React.memo์ ์ฌ์ฉ์ ์ ์ ๋น๊ต ํจ์๋?
React.memo๋ ๊ธฐ๋ณธ์ ์ผ๋ก props์ ์์ ๋น๊ต(shallow comparison)๋ฅผ ํตํด ์ด์ props์ ์๋ก์ด props๊ฐ ๋์ผํ์ง ํ์ธํ๋ค.- ๋ง์ฝ ๋์ผํ๋ค๋ฉด, ํด๋น ์ปดํฌ๋ํธ๋ฅผ ๋ฆฌ๋ ๋๋งํ์ง ์๋๋ค.
- ๊ทธ๋ฌ๋ ์์ ๋น๊ต๋ก ์ถฉ๋ถํ์ง ์์ ๊ฒฝ์ฐ, ์ฌ์ฉ์ ์ ์ ๋น๊ต ํจ์(custom comparison function)๋ฅผ ์ฌ์ฉํด ๋ ์ ๊ตํ๊ฒ ๋น๊ต ๋ก์ง์ ์ ์ํ ์ ์๋ค.
โญ ์ฌ์ฉ์ ๋น๊ต ํจ์์ ์ญํ
React.memo๋ ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก ์ฌ์ฉ์ ์ ์ ๋น๊ต ํจ์(areEqual)๋ฅผ ์ ๋ฌํ ์ ์๋ค. ์ด ํจ์๋ ๋ค์๊ณผ ๊ฐ์ ์ญํ ์ ํ๋ค.
- props ๋น๊ต ๋ฐฉ์ ์ปค์คํฐ๋ง์ด์ง
prevProps(์ด์ ๋ ๋๋ง ์์ props)์nextProps(์ด๋ฒ ๋ ๋๋ง ์ ์ ๋ฌ๋ props)๋ฅผ ๋น๊ตํ ํ, ๋น๊ต ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ๋ฆฌ๋ ๋๋ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ค.
โtrue๋ฅผ ๋ฐํํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋์ง ์๋๋ค.
โfalse๋ฅผ ๋ฐํํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง๋๋ค.- ๊ธฐ๋ณธ ์์ ๋น๊ต๋ฅผ ๋์ด, ๊ฐ์ฒด์ ํน์ ์์ฑ๋ง ๋น๊ตํ๊ฑฐ๋ ๋ ๋ณต์กํ ์กฐ๊ฑด์ ์ถ๊ฐํ ์ ์๋ค.
- ์๋ฅผ ๋ค์ด, ๊ฐ์ฒด ์์ฑ์ด ์ฌ๋ฌ ๊ฐ์ผ ๋, ์ค์ํ ์์ฑ๋ง ๋น๊ตํ๋๋ก ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.
- ๋ถํ์ํ ๋ ๋๋ง ๋ฐฉ์ง
- ๋น๊ต ๋ก์ง์ ์ต์ ํํ์ฌ ๋ ๋๋ง ๋น์ฉ์ด ๋์ ์ปดํฌ๋ํธ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ค.
React.memo์ ๊ธฐ๋ณธ ์์ ๋น๊ต๋ง์ผ๋ก๋ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ ์ ์๋ ์ํฉ์์ ํจ๊ณผ์ ์ด๋ค.
โญ ์ฝ๋์์ ์ฌ์ฉ์ ์ ์ ๋น๊ต ํจ์ ๋ถ์
prevProps์nextProps์user.name๊ฐ์ ๋น๊ต
prevProps.user.name๊ณผnextProps.user.name์ด ๊ฐ์ผ๋ฉดtrue๋ฅผ ๋ฐํprevProps.user.name๊ณผnextProps.user.name์ด ๋ค๋ฅด๋ฉดfalse๋ฅผ ๋ฐํ- ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ๋ฆฌ๋ ๋๋ง ์ฌ๋ถ ๊ฒฐ์
true๋ฐํ:React.memo๋ ๋ ๋๋ง์ ๊ฑด๋๋จ๋ค. (์ปดํฌ๋ํธ ์ฌ์ฌ์ฉ)false๋ฐํ:React.memo๋ ์ปดํฌ๋ํธ๋ฅผ ๋ฆฌ๋ ๋๋งํ๋ค.
โญ ์ฌ์ฉ์ ์ ์ ๋น๊ต ํจ์์ ์ฅ์
- ์ฐธ์กฐํ ๋ฐ์ดํฐ ๋น๊ต
๊ฐ์ฒด์ ํน์ ์์ฑ๋ง ๋น๊ตํ๊ฑฐ๋, ๊น์ ๋น๊ต(deep comparison)๋ก ๋ฆฌ๋ ๋๋ง์ ๋ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์๋ค.(prevProps, nextProps) => { return provProps.user.name === nextProps.user.name; }
- ๋ณต์กํ ๋น๊ต ๋ก์ง ์ ์ฉ
์: ๋ฐฐ์ด์ ๊ธธ์ด๋ง ๋น๊ตํ๊ฑฐ๋ ํน์ ์กฐ๊ฑด์ด ๋ง์กฑ๋ ๋๋ง ๋ ๋๋งํ๋ค.(prevProps, nextProps) => { return prevProps.items.length === nextProps.items.length; }
- ํน์ props ๋ฌด์
์ค์ํ์ง ์์ props๋ ๋ฌด์ํ๊ณ ํ์ํ props๋ง ๋น๊ต ๊ฐ๋ฅ(prevProps, nextProps) => { return prevProps.user.name === nextProps.user.name; // age๋ ๋น๊ตํ์ง ์์ }
- ๋น๊ต ๋น์ฉ ์ ๊ฐ
ํน์ ์ํฉ์์๋ ๋น๊ต ๋ก์ง์ ๊ฐ์ํํ์ฌ ๊ธฐ๋ณธ ์์ ๋น๊ต๋ณด๋ค ํจ์จ์ ์ผ๋ก ๋์ํ๋ค.
โญ ์ฌ์ฉ์ ์ ์ ๋น๊ต ํจ์์ ์ฃผ์์
- ํจ์ ์์ฑ ์ ์ฑ๋ฅ ๊ณ ๋ ค
- ๋น๊ต ํจ์๋ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ ๋๋ง๋ค ํธ์ถ๋๋ฏ๋ก, ์ง๋์น๊ฒ ๋ณต์กํ ๋ก์ง์ ์คํ๋ ค ์ฑ๋ฅ์ ์ ํ์ํฌ ์ ์๋ค.
- ๊ฐ๋จํ๊ณ ํจ์จ์ ์ธ ๋น๊ต ๋ก์ง์ ์์ฑํ๋ ๊ฒ์ด ์ค์ํ๋ค.
- ๋ฆฌ๋ ๋๋ง์ด ํ์ํ ์ํฉ์ ์ ํํ ํ๋จ
- ์๋ชป๋ ๋น๊ต ๋ก์ง์ผ๋ก ์ธํด ํ์ํ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ์ง ์๋ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์๋ค.
React.memo๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์๋ ๊ฒฝ์ฐ
React.memo๋ ๋ฆฌ๋ ๋๋ง ๋น์ฉ์ด ๋์ ์ปดํฌ๋ํธ์์ ์ ์ฉํ๋ค.- ๋น์ฉ์ด ๋ฎ์ ์ปดํฌ๋ํธ์์๋
React.memo์ ์ฌ์ฉ์ ์ ์ ๋น๊ต ํจ์๊ฐ ์คํ๋ ค ์ฑ๋ฅ์ ์ ํ์ํฌ ์ ์๋ค.
React.memo๋ฅผ ์ฌ์ฉํ ๋ ์ฃผ์ํ ์ React.memo๋ ์์ ๋น๊ต๋ง ์ํํ๋ฏ๋ก, props๋ก ์ ๋ฌ๋๋ ๊ฐ์ฒด๋ ๋ฐฐ์ด์ด ํญ์ ์๋ก์ด ์ฐธ์กฐ๋ฅผ ๊ฐ์ง๋ ๊ฒฝ์ฐ์๋ ์ ๋๋ก ์๋ํ์ง ์์ ์ ์๋ค.useMemo, useCallback์ผ๋ก ๋ฉ๋ชจ์ด์ ์ด์
)const Parent = () => {
const [count, setCount] = useState(0);
// ๋ฉ๋ชจ์ด์ ์ด์
ํ์ง ์์ ๊ฒฝ์ฐ
const user = { name: "John" };
// useMemo๋ก ๋ฉ๋ชจ์ด์ ์ด์
ํ ๊ฒฝ์ฐ
const memoizedUser = useMemo(() => ({ name: "John" }), []);
return <Child user={memoizedUser} />;
};2) ์ปค์คํ
๋น๊ต ํจ์ ์ฌ์ฉReact.memo๋ props ๋ณ๊ฒฝ์๋ง ์ํฅ์ ๋ฏธ์น๋ค.useContext๋ก ๊ด๋ฆฌ๋๋ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด, ์ฌ์ ํ ๋ฆฌ๋ ๋๋ง๋๋ค.React.memo๋ฅผ ์ฌ์ฉํ๋ฉด ๋น๊ต ์ฐ์ฐ์ ์ฝ๊ฐ์ ์ค๋ฒํค๋๊ฐ ์ถ๊ฐ๋๋ค.React.memo์ ๊ด๋ จ๋ ๋ค๋ฅธ ์ต์ ํ ๊ธฐ๋ฒuseMemo์ ํจ๊ป ์ฌ์ฉ
React.memo๋ฅผ ์ฌ์ฉํ๋๋ผ๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์ ๋ฌํ๋ props๊ฐ ๊ณ์ฐ๋ ๊ฐ์ด๋ผ๋ฉด, useMemo๋ฅผ ์ฌ์ฉํด props๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
ํด์ผ ํจ์จ์ ์ด๋ค.
import React, { useState, useMemo } from "react";
const Child = React.memo(({ value }: { value: number }) => {
console.log("Child ๋ ๋๋ง");
return <div>Computed Value: {value}</div>;
});
const Parent = () => {
const [count, setCount] = useState<number>(0);
const [inputValue, setInputValue] = useState<string>("");
// ๊ณ์ฐ ๋น์ฉ์ด ๋์ ํจ์
const computeExpensiveValue = (num: number) => {
console.log("๊ฐ ๊ณ์ฐ ์ค...");
return num * 2;
};
// useMemo๋ฅผ ์ฌ์ฉํ์ฌ ๊ณ์ฐ๋ ๊ฐ์ ๋ฉ๋ชจ์ด์ ์ด์
const memoizedValue = useMemo(() => computeExpensiveValue(count), [count]);
return (
<div>
<button onClick={() => setCount((prev) => prev + 1)}>์ฆ๊ฐ</button>
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
<Child value={memoizedValue} />
</div>
);
};
export default Parent;
count๊ฐ ๋ณ๊ฒฝ๋๊ณ Parent๊ฐ ๋ฆฌ๋ ๋๋ง๋๋ค.computeExpensiveValue๋ count๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์คํ๋๋ฉฐ, ๋ฉ๋ชจ์ด์ ์ด์
๋ ๊ฐ์ด ์์ ์ปดํฌ๋ํธ๋ก ์ ๋ฌ๋๋ค.React.memo์ useMemo๋ฅผ ํจ๊ป ์ฌ์ฉํด ๋ถํ์ํ ์์ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ๋ค.useCallback๊ณผ ํจ๊ป ์ฌ์ฉ
์์ ์ปดํฌ๋ํธ๋ก ํจ์๋ฅผ ์ ๋ฌํ ๋, useCallback์ ์ฌ์ฉํด ํจ์๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
ํ์ฌ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ๋ค.
import React, { useState, useCallback } from "react";
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
console.log("Child ๋ ๋๋ง");
return <button onClick={onClick}>ํด๋ฆญ</button>;
});
const Parent = () => {
const [count, setCount] = useState(0);
// useCallback์ผ๋ก ํจ์ ๋ฉ๋ชจ์ด์ ์ด์
const handleClick = useCallback(() => {
console.log("Child ๋ฒํผ ํด๋ฆญ");
}, []);
return (
<div>
<button onClick={() => setCount((prev) => prev + 1)}>๋ถ๋ชจ ๋ฒํผ</button>
<p>๋ถ๋ชจ ์นด์ดํธ: {count}</p>
<Child onClick={handleClick} />
</div>
);
};
export default Parent;
count๊ฐ ์
๋ฐ์ดํธ๋์ด Parent๊ฐ ๋ฆฌ๋ ๋๋ง๋๋ค.useCallback์ผ๋ก handleClick์ ๋ฉ๋ชจ์ด์ ์ด์
ํ๊ธฐ ๋๋ฌธ์ Child์ onClick prop์ ๋ณ๊ฒฝ๋์ง ์๋๋ค.React.memo๋ Child๋ฅผ ๋ฆฌ๋ ๋๋งํ์ง ์๋๋ค.React.useTransition
React 18์์ ์ถ๊ฐ๋ useTransition์ ์ํ ์
๋ฐ์ดํธ๋ฅผ ๊ธด๊ธ ์
๋ฐ์ดํธ์ ํธ๋์ง์
์
๋ฐ์ดํธ๋ก ๊ตฌ๋ถํ์ฌ UI ์
๋ฐ์ดํธ๋ฅผ ๋ถ๋๋ฝ๊ฒ ๋ง๋ ๋ค.
ํนํ, ๋น๋๊ธฐ ๋ฐ์ดํฐ ๋ก๋์ ๊ฐ์ ์์
์์ ์ ์ฉํ๋ค.
import React, { useState, useTransition } from "react";
const SlowComponent = React.memo(({ list }: { list: string[] }) => {
console.log("SlowComponent ๋ ๋๋ง");
return (
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
});
const Parent = () => {
const [input, setInput] = useState("");
const [list, setList] = useState<string[]>([]);
// useTransition์ผ๋ก ์ํ ์
๋ฐ์ดํธ๋ฅผ ํธ๋์ง์
์ผ๋ก ์ฒ๋ฆฌ
const [isPending, setIsPending] = useTransition();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInput(value); // 1. ์
๋ ฅ ํ๋ ์ํ๋ ์ฆ์ ์
๋ฐ์ดํธ
// 2. ํธ๋์ง์
์ํ๋ก ๊ธด ์์
์ฒ๋ฆฌ
startTransition(() => {
const newList = Array.from({ length: 2000 }, (_, i) => `${value} - ${i}`);
setList(newList); // ๊ธด ์์
(๋ชฉ๋ก ์์ฑ ๋ฐ ์
๋ฐ์ดํธ)
});
};
return (
<div>
<input type="text" value={input} onChange={handleChange} />
{isPending ? <p>Loading...</p> : <SlowComponent list={list} />}
</div>
);
};
export default Parent;
์ฌ์ฉ์๊ฐ ์
๋ ฅ์ ๋ณ๊ฒฝํ๋ฉด handleChange๊ฐ ํธ์ถ๋๋ค.
startTransition์ list ์
๋ฐ์ดํธ๋ฅผ ํธ๋์ง์
์์
์ผ๋ก ์ฒ๋ฆฌํ์ฌ ๊ธด๊ธ ์
๋ฐ์ดํธ์ธ ์
๋ ฅ ์ฒ๋ฆฌ์ ๋ณ๋๋ก ์คํํ๋ค.
isPending์ผ๋ก ํธ๋์ง์
์ํ๋ฅผ ํ์ํ์ฌ ๋ก๋ฉ ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ค๋ค.
โญ ํธ๋์ง์ ์์ ์ ํต์ฌ ๊ฐ๋
- ํธ๋์ง์ ์์ ์ UI์์ ๊ธด ์์ (์: ๋ชฉ๋ก ์์ฑ, ๋ ๋๋ง)์ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด๋ค.
- ๋ฉ์ธ ์์ (์: ์ ๋ ฅ ํ๋ ์ ๋ฐ์ดํธ)์ ์ฆ์ ์ฒ๋ฆฌํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ถ๋๋ฌ์์ ์ ์งํ๋ค.
- React๋ ํธ๋์ง์ ์์ ์ด ์ฒ๋ฆฌ๋๋ ๋์ UI๊ฐ "๋๊ธฐ ์ค"์์ ๋ํ๋ด๋๋ก ์ค์ ํ ์ ์๋ค.
โญ ํธ๋์ง์ ์์ ๋ถ๋ถ ํด์
setInput(value)(์ฆ๊ฐ์ ์ธ ์ ๋ฐ์ดํธ)
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅ ํ๋์ ์ ๋ ฅํ๋ ํ ์คํธ๋ ์ฆ์ ์ํ๊ฐ ์ ๋ฐ์ดํธ๋๋ค.
- ์ฌ์ฉ์์๊ฒ ๋น ๋ฅด๊ฒ ํผ๋๋ฐฑ์ ์ ๊ณตํ๊ธฐ ์ํด, ์ด ์์ ์ ์ฐ์ ์์๊ฐ ๋๋ค.
startTransition(() => { ... })(ํธ๋์ง์ ์์ )
startTransition์ React์๊ฒ ์ด ์์ ์ ๋ฎ์ ์ฐ์ ์์๋ก ์ฒ๋ฆฌํด๋ ๊ด์ฐฎ๋ค๋ ๊ฒ์ ์๋ ค์ค๋ค.- ์ฆ, React๋ ์ด ์์ ์ด ์๋ฃ๋ ๋๊น์ง ๋ฉ์ธ ์์ (์ ๋ ฅ ํ๋์ ์ฆ๊ฐ์ ์ธ ์ ๋ฐ์ดํธ)์ ๋ฐฉํดํ์ง ์๋๋ก ์ฒ๋ฆฌํ๋ค.
- ์ฌ๊ธฐ์๋ ๊ธด ์์ ์ธ
newList์์ฑ๊ณผsetList๋ฅผ ํธ๋์ง์ ์์ ์ผ๋ก ์ฒ๋ฆฌํ๋ค.isPending์ํ
isPending์ ํธ๋์ง์ ์์ ์ด ์งํ ์ค์ธ์ง ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ์ํ์ด๋ค.- ํธ๋์ง์ ์์ ์ด ์งํ ์ค์ด๋ฉด
true, ์๋ฃ๋๋ฉดfalse๊ฐ ๋๋ค.- ์ด ์ํ๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์๊ฒ ๋ก๋ฉ ์ค์์ ๋ณด์ฌ์ฃผ๋ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋ค.
โญ ํธ๋์ง์ ์์ ์ ๋์ ๋ฐฉ์
- ์ฐ์ ์์ ๋ถ๋ฆฌ
React๋ ํธ๋์ญ์ ์์ ์ ๋ฎ์ ์ฐ์ ์์๋ก ์ฒ๋ฆฌํ๋ค.
์ฆ, ๋ ์ค์ํ ์์ (์ฌ์ฉ์ ์ ๋ ฅ๊ณผ ๊ฐ์)์ ๋จผ์ ์ฒ๋ฆฌํ ํ, ํธ๋์ง์ ์์ ์ ๋์ค์ ์ฒ๋ฆฌํ๋ค.- ๋น๋๊ธฐ ์ฒ๋ฆฌ
ํธ๋์ง์ ์์ ์ UI ์ฐจ๋จ ์์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฒ๋ฆฌ๋๋ค.
์๋ฅผ ๋ค์ด,newList์์ฑ๊ณผsetList๊ฐ ๊ธด ์์ ์ด๋ผ๋ ์ ๋ ฅ ํ๋๋ ์ฆ์ ์ ๋ฐ์ดํธ๋๋ค.- UI ์ํ ํ์
isPending์ ํ์ฉํด ์ฌ์ฉ์์๊ฒ "Loading..." ๊ฐ์ ํผ๋๋ฐฑ์ ์ ๊ณตํ ์ ์๋ค.
ํธ๋์ง์ ์์ ์ด ๋๋ง๋ React๋ UI๋ฅผ ์๋กญ๊ฒ ๋ ๋๋งํ๋ค.
โญ
useTransition์ ์ด์
- ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ
- ๋ฉ์ธ ์์ (์ ๋ ฅ ํ๋ ์ ๋ฐ์ดํธ)์ ์ฆ๊ฐ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ ์ ๋ ฅ์ ์ง์ฐ์ ๋ฐฉ์งํ๋ค.
- ์ฌ์ฉ์์๊ฒ ๋ก๋ฉ ์ํ๋ฅผ ํ์ํจ์ผ๋ก์จ UI๊ฐ ์๋ตํ์ง ์๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๋ ํ์์ ๋ฐฉ์งํ๋ค.
- ๊ธด ์์ ์ ๋ถ๋ฆฌ
- CPU ๋ฆฌ์์ค๊ฐ ๋ง์ด ํ์ํ ๊ธด ์์ ์ ํธ๋์ง์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ UI ์ฐจ๋จ์ ๋ฐฉ์งํ๋ค.
- React์ ์ฐ์ ์์ ๊ด๋ฆฌ ํ์ฉ
- React์ ์ค์ผ์ค๋ง ์์คํ ์ ํ์ฉํ์ฌ ์ฐ์ ์์๋ฅผ ๋๋๊ณ UI ์๋ต์ฑ์ ๊ฐ์ ํ๋ค.
React.useDeferredValue
useDeferredValue๋ ํ์ฌ ๊ฐ์ ์ง์ฐ๋(deferred) ๋ฒ์ ์ ์ ๊ณตํ์ฌ, ๊ธด๊ธ ์ํ ์
๋ฐ์ดํธ์ ๋๋ฆฐ ์
๋ฐ์ดํธ๋ฅผ ๋ถ๋ฆฌํ ์ ์๋ค.
import React, { useState, useDeferredValue } from "react";
const SlowComponent = React.memo(({ list }: { list: string[] }) => {
console.log("SlowComponent ๋ ๋๋ง");
return (
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
});
const Parent = () => {
const [input, setInput] = useState("");
const list = Array.from({ length: 2000 }, (_, i) => `${input} - ${i}`);
// ์
๋ ฅ๋ ๊ฐ์ ์ง์ฐ๋ ๊ฐ์ผ๋ก ์ฒ๋ฆฌ
const deferredList = useDeferredValue(list);
return (
<div>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
<SlowComponent list={deferredList} />
</div>
);
};
export default Parent;
์ฌ์ฉ์๊ฐ ์
๋ ฅ์ ๋ณ๊ฒฝํ๋ฉด list๊ฐ ์ฆ์ ์์ฑ๋๋ค.
useDeferredValue๋ ์ง์ฐ๋ list ๊ฐ์ SlowComponent๋ก ์ ๋ฌํ๋ค.
์
๋ ฅ ์ฒ๋ฆฌ๋ ๋น ๋ฅด๊ฒ, SlowComponent ๋ ๋๋ง์ ๋ค๋ก ๋ฏธ๋ค์ ธ ๋ถ๋๋ฌ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๋ค.
โญ ์ฝ๋์์
useDeferredValue์ ์ญํ
list: ์ ๋ ฅ ๊ฐ(input)์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์์ฑ๋๋ ์ฆ๊ฐ์ ์ธ ๊ฐ์ด๋ค.deferredList:list๊ฐ์ ์ง์ฐ์์ผ, React๊ฐ ๋ฉ์ธ ์์ (์ฌ์ฉ์ ์ ๋ ฅ)์ ๋ํ ๋ ๋๋ง์ ๋จผ์ ์ฒ๋ฆฌํ๊ณ , ์๊ฐ์ด ๋ ๋ ๋น๋ก์SlowComponent๊ฐ ๋ ๋๋ง๋๋๋ก ๋ง๋ ๋ค.
โญ ์ง์ฐ ์์ ์ ๊ตฌ์ฒด์ ์ธ ๋์ ๋ฐฉ์
- ์ฆ๊ฐ์ ์ธ ์ ๋ฐ์ดํธ
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅ ํ๋์ ๊ฐ์ ์ ๋ ฅํ๋ฉด
input์ํ๊ฐ ์ฆ์ ์ ๋ฐ์ดํธ๋๋ค.- ์ด ์ํ๋
list์ ๋ฐ์๋์ง๋ง,list๊ฐ์ด ๋ฐ๋กSlowComponent์ ์ ๋ฌ๋์ง๋ ์๋๋ค.- ์ง์ฐ๋ ๊ฐ ์ ๊ณต
useDeferredValue๋list๊ฐ์ ์ง์ฐ์์ผ ๋ฎ์ ์ฐ์ ์์๋ก ์ฒ๋ฆฌํ๋ค.- ์ฆ, React๋ ๋จผ์ ์ ๋ ฅ ํ๋์ ๋ํ ๋ ๋๋ง์ ์๋ฃํ๊ณ , ๊ทธ ์ดํ์ ์๊ฐ์ด ์์ ๋
deferredList๋ฅผ ์ ๋ฐ์ดํธํ์ฌSlowComponent๋ฅผ ๋ ๋๋งํ๋ค.- ๋ ๋๋ง ์ต์ ํ
- React๋ ์ ๋ ฅ ํ๋ ์ ๋ฐ์ดํธ์ ๊ฐ์ ์ฆ๊ฐ์ ์ธ ์์ ์ ์ฐจ๋จํ์ง ์๊ณ , ์๊ฐ์ด ํ์ฉ๋ ๋
SlowComponent๋ฅผ ์ ๋ฐ์ดํธํ๋ค.- ์ด๋ ๊ฒ ํ๋ฉด ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ํ ๋ฐ์์ฑ์ ์ ์งํ๋ฉด์,
SlowComponent์ ๊ฐ์ด ๋ ๋๋ง ๋น์ฉ์ด ํฐ ์ปดํฌ๋ํธ์ ์ ๋ฐ์ดํธ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ค.
โญ ์
useDeferredValue๊ฐ ํ์ํ๊ฐ?
- ๋ฌธ์ : ๋ง์ฝ
useDeferredValue์์ด ์ฝ๋๊ฐ ์์ฑ๋๋ค๋ฉด
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅ ํ๋์ ๊ฐ์ ์ ๋ ฅํ ๋๋ง๋ค
list๊ฐ ์๋ก ์์ฑ๋๋ค.- ์ฆ๊ฐ์ ์ผ๋ก
SlowComponent๋ก ์ ๋ฌ๋์ด ๋ ๋๋ง์ด ๋ฐ์ํ๋ค.- ๊ฒฐ๊ณผ์ ์ผ๋ก, ์ฌ์ฉ์๊ฐ ์ ๋ ฅ ํ๋์ ๊ฐ์ ์ ๋ ฅํ ๋ ์ ๋ ฅ ์ง์ฐ(lag)์ด ๋ฐ์ํ ์ ์๋ค.
- ํด๊ฒฐ:
useDeferredValue๋ฅผ ์ฌ์ฉํ๋ฉด
- ์ ๋ ฅ ํ๋(
input) ์ ๋ฐ์ดํธ๋ ์ฆ์ ์ฒ๋ฆฌ๋๋ค.list์ ๋ฐ์ดํธ๋ ์ง์ฐ๋์ด React๊ฐ ๋์ค์ ์ฒ๋ฆฌํ๋ค.- ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ํ ๋น ๋ฅธ ์๋ต์ฑ๊ณผ
SlowComponent๋ ๋๋ง์ ํจ์จ์ฑ์ ๋ชจ๋ ์ ์งํ ์ ์๋ค.
โญ
useDeferredValue์ ํน์ง
- ์ฐ์ ์์ ๊ด๋ฆฌ
๋์ ์ฐ์ ์์ ์์ (์: ์ฌ์ฉ์ ์ ๋ ฅ)๊ณผ ๋ฎ์ ์ฐ์ ์์ ์์ (์: ๋ฆฌ์คํธ ๋ ๋๋ง)์ ๋ถ๋ฆฌํ์ฌ ์ฒ๋ฆฌํ๋ค.- ์ง์ฐ๋ ๊ฐ ์ ๋ฐ์ดํธ
deferredList๋ React๊ฐ ์ฌ์ ๊ฐ ์์ ๋๋ง ์ ๋ฐ์ดํธ๋๋ฏ๋ก, ๋น์ผ ๋ ๋๋ง ์์ ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ์ํฅ์ ๋ฏธ์น์ง ์๋๋ค.
React.memo๊ฐ ์ ํฉํ ์ํฉ