npx create-react-app 프로젝트명
<Logo size={300} />
import logo from "./logo.svg";
import PropTypes from "prop-types";
function Logo(props) {
return (
<img
src={logo}
className="App-logo"
alt="logo"
style={{ width: props.size, height: props.size }}
/>
);
}
Logo.defaultProps = {
size: 200,
}; // props의 기본값 지정
Logo.propTypes = {
size: PropTypes.number,
}; // props의 타입 지정
export default Logo;
객체 비구조화 할당을 사용하면 좀 더 간단하게 작성할 수 있다.
defaultProps를 정의 할 필요가 없다.
import logo from "./logo.svg";
import PropTypes from "prop-types";
function Logo({ size = 200 }) {
return (
<img
src={logo}
className="App-logo"
alt="logo"
style={{ width: size, height: size }}
/>;
);
}
Logo.propTypes = {
size: PropTypes.number,
}; // props의 타입 지정
export default Logo;
컴포넌트 내부에 작성한 요소를 전달하려면 children 속성을 이용할 수 있다.
children의 타입은 node(JSX 및 각종 Element 요소를 받을 수 있는 타입)이다.
isRequired: 필수 요소
<Paragraph>
Edit <code>src/App.js</code> and save to reload.
</Paragraph>
<Paragraph size={14} color="blue">
I'm blue
</Paragraph>
import PropTypes from "prop-types";
function Paragraph({ children, size = 16, color = "white" }) {
return <p style={{ fontSize: size, color }}>{children}</p>;
}
Paragraph.propTypes = {
children: PropTypes.node.isRequired, // children은 node 타입
size: PropTypes.number,
color: PropTypes.string,
};
export default Paragraph;
논리곱 연산자(&&)를 사용해서 조건부 렌더링을 할 수 있다.
import { useState } from "react";
function App() {
const [visible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(!visible)}>Toggle</button>
{visible && (
<h1>논리곱 연산자를 통해 쉽게 JSX 렌더링 여부를 결정할 수 있습니다.</h1>
)}
</div>
);
}
export default App;
삼항 연산자도 사용 가능하다.
import { useState } from "react";
function App() {
const [visible, setVisible] = useState(false);
return (
<div>
<button onClick={() => setVisible(!visible)}>Toggle</button>
{visible ? (
<h1>논리곱 연산자를 통해 쉽게 JSX 렌더링 여부를 결정할 수 있습니다.</h1>
) : null}
</div>
);
}
export default App;
map 함수를 사용하여 특정 요소를 반복 출력할 수 있다.
이 때 최상위 요소에 key 속성을 반드시 명시해야 한다. -> 내부 최적화
import { useState } from "react";
import Board from "./components/Board";
function App() {
const articles = [
{
id: 1,
title: "리스트1 :",
author: "김영준",
},
{
id: 2,
title: "리스트2 :",
author: "이영준",
},
{
id: 3,
title: "리스트3 :",
author: "박영준",
},
];
return (
<div>
<Board articles={articles} />
</div>
);
}
export default App;
import PropTypes from "prop-types";
const Board = ({ articles }) => {
return (
<div>
<h1>Board</h1>
<ul>
{articles.map((article) => (
<li key={article.id}>
{article.id} | {article.title} | {article.author}
</li>
))}
</ul>
</div>
);
};
Board.propTypes = {
articles: PropTypes.array,
};
export default Board;
useState 훅을 사용해서 지역 변수를 생성할 수 있다.
const [state명, state를 변경할 함수] = useState(초깃값)
const handleIncrease = () => {
setCount(count + 1);
if (onIncrease) {
onIncrease(count + 1);
}
};
const handleDecrease = () => {
setCount(count - 1);
if (onDecrease) {
onDecrease(count - 1);
}
};
return (
<div>
<span style={{ fontSize: 40 }}>{count}</span>
<br />
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
</div>
);
// App.js
import { useState } from "react";
import Counter from "./components/Counter";
function App() {
const [totalCount, setTotalCount] = useState(0);
return (
<div>
TotalCount: {totalCount}
<Counter
onChange={(count) => {
setTotalCount(count);
}}
/>
</div>
);
}
export default App;
// Counter.js
import { useState } from "react";
import PropTypes from "prop-types";
function Counter({ onChange }) {
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount(count + 1);
if (onChange) {
onChange(count + 1);
}
};
const handleDecrease = () => {
setCount(count - 1);
if (onChange) {
onChange(count - 1);
}
};
return (
<div>
<span style={{ fontSize: 40 }}>{count}</span>
<br />
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
</div>
);
}
Counter.propTypes = {
onChange: PropTypes.func,
};
export default Counter;
state의 변화를 감지한다.
첫 번째 인수는 변화가 감지될 때 동작할 함수, 두 번째 인수는 변화를 감지 할 state를 지정
import { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Clicked ${count} times.`);
}, [count]); // count의 변화를 감지한다.
useEffect(() => {
console.log("Component Loaded");
const handleScroll = () => {
console.log(window.scrollY);
};
document.addEventListener("scroll", handleScroll); // 전역적인 이벤트를 사용할 때 쓸 수 있다.
return () => document.removeEventListener("scroll", handleScroll); // return으로 변환한 함수는 컴포넌트가 제거될 때 실행된다.
}, []); // []은 컴포넌트가 처음 로드될 때 실행된다.
return (
<div>
<div>You clicked {count} times.</div>
<button onClick={() => setCount(count + 1)}>+</button>
<div style={{ height: 10000 }}></div>
</div>
);
}
export default App;
선언한 ref의 current 속성으로 해당 Element를 참조할 수 있다.
ref를 컴포넌트에 전달하려면 React.forwardRef() 로 함수를 감싸주어야 한다.
// App.js
import { useRef } from "react";
import Input from "./components/Input";
function App() {
const inputRef = useRef();
return (
<div>
<Input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
);
}
export default App;
// Input.js
import React from "react";
const Input = React.forwardRef((_, ref) => {
return (
<>
Input: <input ref={ref} />
</>
);
});
export default Input;
useState는 값이 변경될 때 다시 렌더링을 한다.
useRef는 값이 변경되더라도 다시 렌더링을 하지 않는다.
따라서 값이 변경되어도 다시 렌더링 하지 않을 지역 변수를 사용할 때 ref를 사용한다.
ref를 지역변수로 사용하는 예제
import { useRef, useState } from "react";
const AutoCounter = () => {
const [count, setCount] = useState(0);
const intervalId = useRef();
const handleStart = () => {
intervalId.current = setInterval(() => {
console.log(intervalId);
setCount((count) => count + 1);
}, 1000);
};
const handleStop = () => {
clearInterval(intervalId.current);
};
return (
<>
<div>{count}</div>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
);
};
export default AutoCounter;
interval id가 변경되어도 다시 렌더링 하지 않는다.
// App.js
import { useState } from "react";
import Board from "./components/Board";
import Pagination from "./components/Pagination";
function App() {
const [page, setPage] = useState(0);
const articles = new Array(100).fill().map((_, i) => ({
id: i,
title: `${i}번 게시물`,
}));
const limit = 10;
const offset = page * limit;
return (
<div>
<Pagination defaultPage={0} limit={limit} total={articles.length} onChange={setPage} />
<Board articles={articles.slice(offset, offset + limit)} />
</div>
);
}
export default App;
// Board.js
import PropTypes from "prop-types";
const Board = ({ articles }) => {
return (
<ul>
{articles.map((article) => (
<li key={article.id}>
{article.id} | {article.title}
</li>
))}
</ul>
);
};
Board.propTypes = {
articles: PropTypes.array,
};
export default Board;
// Pagination.js
import { useState } from "react";
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 < 2) {
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 ? "red" : undefined }}
>
{i + 1}
</button>
))}
<button onClick={() => page + 1 !== totalPage && handleChangePage(page + 1)}>다음</button>
</div>
);
};
export default Pagination;