[DAY-48] React (1)
๐ ํท๊ฐ๋ ธ๋ & ๋ชฐ๋๋ ๋ถ๋ถ๋ค๋ง ์ ๋ฆฌํ๋ ๋๋ง์ TIL
๐ฏ ๋ชจ๋ ๊ฐ์ ๋ด์ฉ์ ์ ์ง ์์์!
์ค๋์ ์๊ฐ์?
์ด์ ์ข ์นํด์ง๋ ์ถ์์ง๋ง ๋ทฐ๋ ๋ ๋๊ฐ์ต๋๋ค... ํํ
๊ธฐ๋ค๋ฆฌ๊ณ ๊ธฐ๋ค๋ฆฌ๋ ๋ฆฌ์ํธ ์์์
๋๋ค!
์ฒ์์ ์ค์ต์ ์์ํ๊ณ ๊ตฌ์กฐ๊ฐ ํ ๋์ ์กํ์ง ์์ ํ๋ค์์ต๋๋ค.
ํ์ง๋ง ํ์ดํ์ ํ๋ค๋ณด๋.. ๋น์ทํ๋ค์?!
๊ผญ ์ ํ์ตํด์ ํ๋ก์ ํธ์ ๋
น์ฌ๋ด๊ฒ ์ต๋๋ค.
[1] React
React๋ facebook
์์ ๋์จ ํ๋ก ํธ์๋(์๋ฐ์คํฌ๋ฆฝํธ) ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค.
[1-1] ํน์ง
- Reactive Programming
React๋ Reactive Programming์ ์ง์ํฉ๋๋ค.
์ฆ ์ํ๋ฅผ ๊ด์ฐฐํ๊ณ ๋ณํ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ, ์ฐ๊ด๋ ๊ณณ์์ ๋ค์ ์ฐ์ฐ์ด ์ํ๋ฉ๋๋ค.
- View
๋ํ React๋ MVC ์ํคํ ์ณ ํจํด ์ค View๋ง์ ๊ด๋ฆฌํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
๋ํ ์ปดํฌ๋ํธ์ ์กฐํฉ์ผ๋ก View๋ฅผ ๊ตฌ์ฑํฉ๋๋ค.
- Virtual DOM
Virtual DOM์ ์ง์ํฉ๋๋ค.
ํ์ํ ๋ถ๋ถ๋ง ํ ๋ฒ์ ๋ ๋๋งํ๋ฏ๋ก, ๋ณ๋ค๋ฅธ ์ต์ ํ ์์ด ๋น ๋ฅธ ์ฑ๋ฅ์ ๋ผ ์ ์์ต๋๋ค.
ํ์ง๋ง ์ผ๋ฐ์ ์ผ๋ก DOM์ ์ง์ ์์ฑํ๋ ๊ฒ ๋ณด๋ค๋ ๋๋ฆฝ๋๋ค.
๋ฐ๋ผ์ ์ฑ๋ฅ๋ณด๋ค๋ ๊ฐ๋ฐ ํธ์์ฑ์ ์ํด Virtual DOM์ ์ฑํํ์์ ์ ์ ์์ต๋๋ค.
[2] ๋ฆฌ์ํธ ์ดํ๋ฆฌ์ผ์ด์
create-react-app
์ ์ฌ์ฉํ์ฌ react-app์ ์์ฑํ ์ ์์ต๋๋ค.
npx create-react-app my-app
npm start
[2-1] ํํ์
ํํ์์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ ์ ์์ต๋๋ค.
function App() {
const name = "์ง์";
return (
// ์ด์ ๊ฐ์ด Javascript ํ์ผ ๋ด์์ html ๊ฐ์ ๋์ ํฌํจํ๋ ํํ๋ฅผ JSX๋ผ๊ณ ๋ถ๋ฆ
๋๋ค.
// javascript ๋ด์์ class๊ฐ ์์ฝ์ด๊ธฐ ๋๋ฌธ์ className์ ์ด์ฉํฉ๋๋ค.
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React with {name} // ์ด๋ ๊ฒ ํํ์์ ์์ฑํฉ๋๋ค.
</a>
</header>
</div>
);
}
[2-2] ์กฐ๊ฑด๋ฌธ
์กฐ๊ฑด๋ถ ๋ ๋๋ง์๋ ํด๋น ํํ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋ํ ์ฃผ๋ก ์ผํฅ ์ฐ์ฐ์๋ ๋ง์ด ์ฌ์ฉ๋ฉ๋๋ค.
// ํํ์ ์ฌ์ฉ
{
showLink && (
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React with {name}
</a>
);
}
// showLink๊ฐ true์ผ๋๋ง ํด๋น a ํ๊ทธ๊ฐ ๋ ๋๋ง๋ฉ๋๋ค.
// ์ผํฅ ์ฐ์ฐ์ ์ฌ์ฉ
{
showLogo === "show" ? (
<img src={logo} className="App-logo" alt="logo" />
) : (
<h1>์ค์</h1>
);
}
[2-3] ๋ฐ๋ณต๋ฌธ
๋ฐ๋ณตํด์ ๋ ๋๋ง์ ํ๊ธฐ ์ํด์๋ mapํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์ฆ VanillaJS์ ๋ฐ๋ณต ๋ ๋๋ง ๊ตฌ๋ฌธ์ ํํ์ ์์์ ์์ฑํ ์ ์์ต๋๋ค.
<ul>
{names.map((item) => {
return <li key={item}>{item}</li>;
// ๋ฐ๋ณต๋ฌธ์์๋ ๋ฐ๋์ key๋ฅผ ์ ์ด์ค๋๋ค.
// react์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํจ์
๋๋ค.
})}
</ul>
[3] ์ปดํฌ๋ํธ๋?
๋ฆฌ์ํธ ์ปดํฌ๋ํธ๋ ์ฌ๋ฌ ๋ก์ง์ ๋ด์ ์ ์์ผ๋ฉฐ, ์ํ๋ฅผ ๊ฐ์ง๋๋ค.
์ปดํฌ๋ํธ๋ ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ์๋ก ๋ฐ์ดํฐ์ ๋ฉ์ธ์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์ต๋๋ค.
์ด๋, ๋ฐ์ดํฐ๋ ์์์์ ํ์๋ก (๋จ๋ฐฉํฅ)์ผ๋ก ํ๋ฆ ๋๋ค.
์ปดํฌ๋ํธ๋ ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํ ํํ๋ก ์ค๊ณํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
์ด๋ฅผ ์ํด์๋ UI๋ฅผ ์ถ์์ ์ผ๋ก ๋ฐ๋ผ๋ณด๋ ๊ด์ ์ด ํ์ํฉ๋๋ค.
๋ํ ๋ฆฌ์ํธ์ ์ปดํฌ๋ํธ๋ ํจ์๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค.
์ปดํฌ๋ํธ๋ Props๋ผ๋ ๊ฐ์ฒด๋ฅผ ๋ฐ์์ ํจ์ ๋ด์ ๋ก์ง์ ๊ฑฐ์ณ JSX๋ฅผ ๋ฐํํฉ๋๋ค.
[3-1] ์ปดํฌ๋ํธ ๊ตฌ์กฐ
์ปดํฌ๋ํธ๋ ์ฃผ๋ก src/components
์์ ์ ์ธํฉ๋๋ค.
์ปดํฌ๋ํธ์ ์ด๋ฆ์ผ๋ก ํด๋๋ฅผ ์์ฑํ๊ณ , index.js
๋ก ํจ์๋ฅผ ์์ฑํฉ๋๋ค.
// Logo Component (index.js)
import logo from "./logo.svg";
import propTypes from "prop-types"; // props์ ํ์
์ ์ง์ ํด์ค๋๋ค.
function Logo({ size = 200 }) {
return (
<img
src={logo}
className="App-logo"
alt="logo"
style={{ width: size, height: size }}
/>
);
}
Logo.propTypes = {
size: propTypes.number,
};
export default Logo;
// App.js
import "./App.css";
import Logo from "./components/Logo";
function App() {
return (
// ํด๋น ์ปดํฌ๋ํธ์ ์ด๋ฆ์ผ๋ก ํ๊ทธ๋ฅผ ์์ฑํ์ฌ ์ฌ์ฉํฉ๋๋ค.
<div className="App">
<header className="App-header">
<Logo size={100} />
<Logo />
// ...
);
}
[4] ๋ถ๊ธฐ์ ๋ฐ๋ณต
์ปดํฌ๋ํธ๋ ์์์ ์ดํด๋ดค๋ฏ์ด JSX
๋ฅผ ๋ฐํํ๋ โํจ์โ์
๋๋ค.
ํจ์ ๋ด์์ JSX
๋ฅผ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์์ ๋ถ๊ธฐ์ ๋ฐ๋ณต์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
JSX
๋ด์์ if์ for์ ๊ฐ์ ๋ฌธ๋ฒ ์ฌ์ฉ์ด ์ด๋ ต๊ธฐ ๋๋ฌธ์
ํธ์๋ฅผ ์ํด ์ผํฅ์ฐ์ฐ์, ๋ ผ๋ฆฌ๊ณฑ ์ฐ์ฐ์์ map, filter์ ๊ฐ์ ๊ณ ์ฐจ ํจ์๋ฅผ ์ด์ฉํฉ๋๋ค.
[5] ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ
[5-2] ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ
์ปดํฌ๋ํธ์ ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ์ ๊ฐ๋จํฉ๋๋ค.
์ฌ์ฉํ ํจ์๋ฅผ ์ ์ธํ๊ณ , ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ์ ํ ์ ์์ต๋๋ค.
// Counter Component ์ผ๋ถ
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount(count + 1);
if (onIncrease) {
onIncrease(count + 1);
}
};
const handleDecrease = () => {
setCount(count - 1);
if (onDecrease) {
onDecrease(count - 1);
}
};
return (
<div>
<span>{count}</span>
<br />
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
</div>
);
[5-3] ๋ถ๋ชจ ์ปดํฌ๋ํธ ๋ฉ์ธ์ง ์ ๋ฌ
props
๋ฅผ ํตํด์ ๋ฉ์ธ์ง๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค.
๊ฐ์ฅ ๊ฐ๋จํ๊ฒ ํจ์๋ฅผ ์ ๋ฌํด์ ๋ฐ์ ์ ์์ต๋๋ค.
์ด๋, ๊ฐ ํ์ ์ปดํฌ๋ํธ๋ค์ ์ํ์ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ์ํ๋ ์๋ก ๊ฐ์ญํ์ง ์์ต๋๋ค.
// App Component ์ผ๋ถ
<Counter
onIncrease={(count) => {
setTotalCount(totalCount + 1);
}}
onDecrease={(count) => {
setTotalCount(totalCount - 1);
}}
/>
์ด ๋ฐฉ๋ฒ ์ธ์๋ ๋ค์ํ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
์ถํ์ ์ ๋ฆฌํ๋๋ก ํ๊ฒ ์ต๋๋ค!
[6] ํ
[6-1] useState ํ
์ปดํฌ๋ํธ์ ์ง์ญ ์ํ ๊ด๋ฆฌ์๋ useState
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
useState
๋ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด ๋ค์ ๋ ๋๋ง์ ํฉ๋๋ค.
React์์ ์ํ๋ ๋์ ์ธ ๋ฐ์ดํฐ๋ฅผ ๋งํฉ๋๋ค.
useState
๋ฅผ ์ฌ์ฉํ๋ฉด ์ํ์ ๊ธฐ๋ณธ๊ฐ์ ํ๋ผ๋ฏธํฐ๋ก ๋ฃ์ด ํด๋น ํจ์๋ฅผ ํธ์ถํด์ค๋๋ค.
ํจ์๊ฐ ํธ์ถ๋๋ฉด ์ฒซ๋ฒ์งธ ์์๊ฐ ํ์ฌ ์ํ, ๋๋ฒ์งธ ์์๊ฐ Setter ํจ์์ธ ๋ฐฐ์ด์ด ๋ฐํ๋ฉ๋๋ค.
import { useState } from "react"; // ๋ฆฌ์ํธ ํจํค์ง์์ ๋ถ๋ฌ์ ์ฌ์ฉํฉ๋๋ค.
import "./App.css";
function App() {
const [visible, setVisible] = useState(false);
// ๋ฐฐ์ด ๋น๊ตฌ์กฐํ ํ ๋น์ ํตํด ํด๋น ์ฝ๋์ฒ๋ผ ์์ฑํ ์ ์์ต๋๋ค.
<button onClick={() => setVisible(!visible)}>Toggle</button>
setVisible(Setter) ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฒซ๋ฒ์งธ ์์์ธ ํ์ฌ ์ํ๋ฅผ ์ ๋ฐ์ดํธํด์ค ์ ์์ต๋๋ค.
[6-2] useEffect ํ
useEffect
ํ
์ ๋ฌด์ธ๊ฐ ๋ณํ๊ฐ ์์ ๋ ๊ฐ์งํ์ฌ ๋ฐ์ํ๋ ํ
์
๋๋ค.
useEffect(() => {
console.log("Change totalCount!");
}, [totalCount]);
// totalCount์ ๊ฐ์ด ๋ณํ ๋ ๋ง๋ค ์์ ๋ก์ง์ด ์คํ๋ฉ๋๋ค!
๋ง์ฝ ๋ค์ ๋ฐฐ์ด์ ์๋ฌด ๊ฐ๋ ๋ฃ์ง ์์์ ๋, ์ปดํฌ๋ํธ๊ฐ ์ฒ์์ผ๋ก ๋ก๋๋ ๋๋ง ์คํ๋ฉ๋๋ค.
์ด ๊ฒฝ์ฐ๋ ์คํฌ๋กค ์ด๋ฒคํธ์ ๊ฐ์ด ์ ์ญ์ ์ผ๋ก ๋ณํ๋ฅผ ๊ฐ์งํด์ผ ํ ๋ ์ฃผ๋ก ์ฌ์ฉํฉ๋๋ค.
useEffect(() => {
const handleScroll = () => {
console.log(window.scrollY);
};
document.addEventListener("scroll", handleScroll);
return () => document.removeEventListener("scroll", handleScroll);
}, []);
์ฃผ์ํด์ผ ํ ์ ์, ํด๋น ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ผ์ ธ๋ ์ด๋ฒคํธ๊ฐ ๋จ์์์ง ์๊ฒ
return ๋ฌธ์ ํจ์๋ฅผ ๋ฃ์ด์ค์ผ๋ก์จ ๊ผญ ์ด๋ฒคํธ๋ฅผ ์ญ์ ํด์ค์ผ ํฉ๋๋ค.
[6-3] useRef ํ
useRef
๋ DOM์ ์ง์ ์ ๊ทผํ ๋์ ์ง์ญ๋ณ์๋ก ์ฌ์ฉํ ๋ ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
useRef
๋ ๊ฐ์ด ๋ณ๊ฒฝ๋๋๋ผ๋ ๋ค์ ๋ ๋๋ง์ ํ์ง ์์ต๋๋ค.
[1] DOM ์ ๊ทผ
const inputRef = useRef();
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
// ํด๋น ๋ฒํผ์ ๋๋ฅด๋ฉด useRef๋ก ์ ๊ทผํ input Dom ์์์ ํฌ์ปค์ค๊ฐ ์กํ๊ฒ ๋ฉ๋๋ค.
ํน์ Component์ DOM์๋ ์ ๊ทผํ ์ ์์ต๋๋ค.
์ด ๊ฒฝ์ฐ forwardRef
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
// Input Component
import React from "react";
const Input = React.forwardRef((_, ref) => {
return (
<>
Input: <input ref={ref} />
</>
);
});
export default Input;
// App Component ์ผ๋ถ
<Input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
[2] ์ง์ญ๋ณ์
๋ค์ ๋ ๋๋งํ์ง ์์ ์ง์ญ ๋ณ์๋ฅผ ์ ์ธํ ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
// AutoCounter Component ์ผ๋ถ
const [count, setCount] = useState(0);
const intervalId = useRef();
const handeleStart = () => {
intervalId.current = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
};
const handleStop = () => {
clearInterval(intervalId.current);
};
[7] ํ์ด์ง๋ค์ด์
๋ณดํต ์๋ฒ ์ธก์์ ํ์ด์ง๋ฅผ ๋ถํ ํด์ ๋ณด๋ด์ฃผ๊ณค ํฉ๋๋ค.
ํ์ง๋ง ํด๋ผ์ด์ธํธ ์ธก์์ ํ์ด์ง๋ค์ด์ ์ ๊ตฌํํ๋ ๊ฒฝ์ฐ๋ ์ ์ง ์์ต๋๋ค.
// Pagenation Component ์ผ๋ถ
// defaultPage (๋งจ ์ฒ์ ์ ํ๋ ํ์ด์ง)
// limit (ํ ํ์ด์ง ๋น ๋ณด์ฌ์ค article์ ๊ฐฏ์)
// total (์ ์ฒด article์ ๊ฐฏ์)
// onChange (ํ์ -> ์์ ์ปดํฌ๋ํธ๋ก ํ์ด์ง๊ฐ ๋ฐ๋์ ์๋ ค์ฃผ๋ ํจ์)
const Pagination = ({ defaultPage, limit, total, onChange }) => {
const [page, setPage] = useState(defaultPage);
const totalPage = Math.ceil(total / limit);
const handleChangePage = (newPage) => {
onChange(newPage);
setPage(newPage);
};
return (
<div>
<button onClick={() => page !== 0 && handleChangePage(page - 1)}>
์ด์
</button>
{Array.from(new Array(totalPage), (_, i) => i)
.filter((i) => {
if (page < 3) {
return i < 5;
} else if (page > totalPage - 3) {
return i >= totalPage - 5;
}
return i >= page - 2 && i <= page + 2;
})
.map((i) => (
<button
key={i}
onClick={() => handleChangePage(i)}
style={{ backgroundColor: page === i ? "aqua" : undefined }}
>
{i + 1}
</button>
))}
<button
onClick={() => page + 1 !== totalPage && handleChangePage(page + 1)}
>
๋ค์
</button>
</div>
);
};
VanillaJS์ ๋ก์ง์ด ์ ์ฌํฉ๋๋ค. ๊ฒ๋จน์ง ๋ง๊ณ ๊ณต๋ถํด๋ด์ผ๊ฒ ์ด์..!
๋๊ธ๋จ๊ธฐ๊ธฐ