간단한 to-do-list를 만들어 보자!
function App() {
const [toDo, setToDo] = useState("");
const onChange = (event) => setToDo(event.target.value);
const onSubmit = (event) => {
event.preventDefault();
if (toDo === "") {
return;
}
setToDo("");
}
return (
<div>
<form onSubmit={onSubmit}>
<input
value={toDo}
type="text"
placeholder="Write your to do..."
onChange={onChange}
/>
<button>Add To Do</button>
</form>
</div>
);
}
우리가 해야할 건 여러 개의 toDo를 받을 수 있는 array이기 때문에 useState 기본값이 빈 배열인 것을 선언해준다.
일반 자바스크립트라면 배열에 push를 하겠지만 state를 직접적으로 수정하면 안되기에 배열을 직접적으로 수정하지 않으면서 setToDos 함수로 배열에 요소를 추가하는 방법을 사용할 것이다.
1. setToDos 함수의 첫번째 인자로 현재의 state를 받아온다.
2. return값으로는 새로운 배열에는 state에 있는 toDo와 모든 이전의 toDos을 적는다.
기존 배열과 새로운 요소를 추가하려면
var food = [1, 2, 3, 4];
[6, food]
이런 식으로 가져오면 [6, Array(4)], 즉 요소 1개와 배열 1개를 가진 배열을 반환받게 된다.
따라서 배열의 요소를 가져오려면
[6, ...food]
-와 같이 사용해야한다. 현재 하고 있는 예제라면
setToDos(currentArray => [toDo, ...currentArray]);
-와 같이 사용해야 배열의 값을 가져올 수 있다.
그럼 toDos배열에 입력한 input값이 들어있는 걸 확인할 수 있다.
h1태그로 페이지 타이틀을 적어주고, 그 옆에 현재 toDo가 몇 개인지 숫자로 나타내보자.
<h1>My To Dos {toDos.length}</h1>
useState의 modified함수를 사용할 때 두가지 옵션이 있다는 걸 기억하자.
1. setToDo("");
우리가 저장한 data를 보낼 수 있다. 따라서 위와 같은 코드를 작성하면 toDo가 빈 String을 반환할 것이다.
2. setToDo((currentArray) => [toDo, ...currentArray]);
함수를 보낼 때는 함수의 첫번째 매개변수를 현재 state로 보낸다. 그러면 현재 state를 계산하거나 새로운 state를 만드는 데 사용할 수 있게 된다. 위와 같은 코드를 작성하면 현재 toDos를 받아와서 새로운 toDo의 배열로 반환하게 된다.
그렇다면 우리가 저장한 toDos배열을 각각의 컴포넌트로 만들기 위해선 어떻게 해야할까?
map함수를 사용하자!
배열의 각각의 요소를 바꾸고 싶고, 새로운 배열을 반환받을 때 사용한다.
map함수 괄호안에 함수를 사용할 수 있는데 만약에 배열 길이가 6개라면 총 6번 실행된다.
['there', 'are', 'you', 'are', 'how', 'hello'].map(() => ":)")
> [':)', ':)', ':)', ':)', ':)']
다만, 이런 식으로 사용하게 되면 기존의 배열값이 사라지게 된다. 하지만 함수의 매개변수를 사용하여 현재의 값을 가져올 수 있다.
['there', 'are', 'you', 'are', 'how', 'hello'].map((item) => item.toUpperCase())
> ['THERE', 'ARE', 'YOU', 'ARE', 'HOW', 'HELLO']
map함수를 이용해서 toDos배열을 li태그에 뿌려주자!
<ul>
{toDos.map((item) => <li>{item}</li>)}
</ul>
이때, 콘솔에 같은 컴포넌트의 li를 읽어올때는 key라고 하는 prop을 넣어야 한다는 경고문이 떠있을 것이다.
따라서 해당 li에 key라는 속성값을 넣어주면 되는데 이 key는 고유의 값이어야 한다. map의 두번째 매개변수에 index를 넣어주고, key값으로 index를 넣어주자.
<ul>
{toDos.map((item, index) => <li key={index}>{item}</li>)}
</ul>
암호화폐 가격을 나열하는 트래커 만들기!
const [loading, setLoading] = useState(true);
const [coins, setCoins] = useState([]);
useEffect(() => {
fetch("https://api.coinpaprika.com/v1/tickers")
.then((response) => response.json())
.then((json) => {
setCoins(json);
setLoading(false);
});
}, [])
<ul>
{coins.map((coin) =>
<li>
{coin.name} ({coin.symbol}): ${coin.quotes.USD.price} USD
</li>
)}
</ul>
<h1>The Coins! ({coins.length})</h1>
{loading ? <strong>Loading...</strong> : null}
영화정보를 보여주는 앱 만들기.
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
useEffect(() => {
fetch(`https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year`)
.then((response) => response.json())
.then((json) => {
setMovies(json.data.movies);
setLoading(false);
});
}, []);
요즘은 then보다 async-await를 좀 더 보편적으로 사용하기 때문에 아래와 같이 코드를 고쳐보자.
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const getMovies = async () => {
const response = await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year`
);
const json = await response.json();
setMovies(json.data.movies);
setLoading(false);
}
useEffect(() => {
getMovies();
}, []);
그리고 아래 코드가 더 짧은 버젼이다.
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
}
useEffect(() => {
getMovies();
}, []);
return (
<div>
{loading ? <h1>Loading...</h1> : <div>{movies.map(movie =>
<div key={movie.id}>
<img src={movie.medium_cover_image}></img>
<h2>{movie.title}</h2>
<p>{movie.summary}</p>
<ul>
{movie.genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
)}</div>}
</div>
);
로딩이 되었을 때 setLoading을 false로 바꿔준다. -> setMovies로 moives를 받아오면 API로부터 얻은 데이터를 state로 변경한다. -> return문 안에서 movies.map을 하고 각각의 movie에 접근해서 그 값을 변환할 수 있다.
react로 페이지 전환 추가하기!
// Movie.js
function Movie({coverImg, title, summary, genres}) {
return <div>
<img src={coverImg} alt={title} />
<h2>{title}</h2>
<p>{summary}</p>
<ul>
{genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
}
//App.js
return (
<div>
{loading ? (
<h1>Loading...</h1>
) : (
<div>
{movies.map((movie) => (
<Movie
key={movie.id}
coverImg={movie.medium_cover_image}
title={movie.title}
summary={movie.summary}
genres={movie.genres}
/>
))}
</div>
)}
</div>
);
key는 react에서만, map안에서 컴포넌트를 랜더할 때 사용한다.
Movie.propTypes = {
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
function Detail(){
return <h1>Detail</h1>;
}
react-router-dom 사용법 알아보기!
import {
BrowserRouter as Router, // router는 Hash Router와 Browser Router가 있다.
Routes, // Routes이 하는 일은 route를 찾는 것! 여기서 route는 url(localhost:3000/movies/123)을 말한다. 그리고 route를 찾으면 컴포넌트를 렌더링한다.
Route,
} from "react-router-dom";
function App() {
return <Router>
<Routes>
<Route path="/movie" element={<Detail />}>
</Route>
<Route path="/" element={<Home/>}>
</Route>
</Routers>
</Router>
;
}
Hash Router를 쓰면 url뒤에 #이 붙는다.
대부분은 Browser Router을 사용한다. url뒤에 #가 붙지 않으므로.
ex) localhost:3000/#/movie