App.js
import React, { useState } from "react";
import "./App.scss";
import MovieList from "./components/MovieList";
function App() {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
/*
// ๋น๋๊ธฐโญ
function fetchMovieHandler() {
fetch("https://swapi.dev/api/films/")
.then((response) => {
return response.json(); // json ๋์ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ์ ์๋ ์๋ฐ์คํฌ๋ฆฝํธ ํํ๋ก ๋ณํ
})
.then((data) => {
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
// ํ์ํ 4๊ฐ๋ง ์๋ก์ด ๊ฐ์ฒด๋ก ๋ฐํ
};
});
setMovies(transformedMovies);
});
}
*/
// ๋น๋๊ธฐโ (async, await)
async function fetchMovieHandler() {
setIsLoading(true);
const response = await fetch("https://swapi.dev/api/films/");
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
// ํ์ํ 4๊ฐ๋ง ์๋ก์ด ๊ฐ์ฒด๋ก ๋ฐํ
};
});
setMovies(transformedMovies);
setIsLoading(false);
}
return (
<main>
<section>
<button onClick={fetchMovieHandler}>Fetch Movie</button>
</section>
<section>
{/* isLoading์ด false์ผ ๋ */}
{!isLoading && <MovieList movie={movies} />}
{!isLoading && <p>NO MOVIE</p>}
{isLoading && <p>LOADING...</p>}
</section>
</main>
);
}
export default App;
App.scss
$bg-color: #4c536e;
$text-color: #ffe600;
@mixin box($width: 100%, $height: auto) {
width: $width; height: $height; border-radius: 12px; background: $bg-color; text-align: center; color: #fff; }
* { margin: 0; padding: 0; box-sizing: border-box; }
ul, li { list-style: none; }
body { background: $bg-color; }
section {
@include box(); background: #fff; max-width: 40rem; margin: 1rem auto; padding: 2rem;
& > p { color: $bg-color; }
}
button {
@include box(150px, 40px); border: none; cursor: pointer;
&:hover, &:active { background: lighten($bg-color, 10%); // sass ํจ์
}
}
.movie {
@include box(); padding: 2rem;
& + & { margin-top: 1rem; }
// ์ฒซ๋ฒ์งธ movie + ๋ฐ๋ก ๋ค์ ์๋ movie
h2 { font-size: 2rem; color: $text-color; margin-bottom: 1rem; }
h3 { font-size: 1rem; color: $text-color; margin-bottom: .5rem; font-weight: normal; }
}
MovieList.jsx
import React from "react";
import Movie from "./Movie";
const MovieList = (props) => {
return (
<ul>
{props.movie.map((item) => (
<Movie
key={item.id}
id={item.id}
title={item.title}
releaseDate={item.releaseDate}
openingText={item.openingText}
/>
))}
</ul>
);
};
export default MovieList;
Movie.jsx
import React from "react";
const Movie = (props) => {
return (
<li className="movie">
<h2>{props.title}</h2>
<h3>{props.releaseDate}</h3>
<p>{props.openingText}</p>
</li>
);
};
export default Movie;
jsํ์ผ์ ๋ง๋ค์ด์ index์ ์ ์ฉ์ํจ๋ค.
npm์ผ๋ก ์ค์น ๋ pwa๋ npm run dev๋ก ์คํ
index.html
<!-- ์๋น์ค ์์ปค ์ง์ ์ฌ๋ถ -->
<script>
if("serviceWorker" in navigator) {
// ๋ธ๋ผ์ฐ์ ์ ์๋น์ค์์ปค๊ฐ ์์ ๋ (์ง์์ฌ๋ถ)
navigator.serviceWorker.register("sw.js") // ๋น๋๊ธฐ
.then((success)=>{
console.log("servieceWorker ์ค์น์ ์ฑ๊ณตํ์ต๋๋ค", success);
})
}
</script>
sw.js
const CHACHE_NAME = "pwa-offline-v1"; // ์บ์ฑ์คํ ๋ฆฌ์ง์ ์ ์ฅ๋ ํ์ผ ์ด๋ฆ
const fileToCache = ["/", "/css/main.css"]; // "/" - html๋ฌธ์
// ์๋น์ค์์ปค ์ค์น(์น์์ ์บ์ฑ)
// ์๋น์ค์์ปค์์ self๋ window์ ๊ฐ์ ์๋ฏธ (ํ์ด์ง์์ ์๋์ฐ๋ฅผ ๊ฐ์ง)
self.addEventListener("install", function (e) {
// waitUntil() - ๊ดํธ ์์ ๋ก์ง์ด ๋๋๊ธฐ ์ ๊น์ง ์ด๋ฒคํธ๊ฐ ๋๋์ง ์์
e.waitUntil(
// caches - ์์ฝ์ด
caches.open(CHACHE_NAME)
.then((cache)=>{
return cache.addAll(fileToCache); // ์บ์ ์ ์ฅ
})
);
});