230608 - React, PWA

๋ฐฑ์Šน์—ฐยท2023๋…„ 6์›” 8์ผ
1

๐Ÿšฉ React

SWAPI

๐Ÿ“ ์„ค๋ช…

  • SWAPI๋ฅผ ๊ฐ€์ ธ๋‹ค๊ฐ€ ์—ฐ๊ฒฐํ•ด๋ณด๊ธฐ


โœ’๏ธ ์‚ฌ์šฉ๋ฒ•

์ž…๋ ฅ

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;



์ถœ๋ ฅ

  • ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒด




๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ & ๋„์›€์ด ๋˜๋Š” ๋งํฌ






๐Ÿšฉ Service Worker API

Service Worker API

๐Ÿ“ ์„ค๋ช…

  • ์›น ์„œ๋น„์Šค์—์„œ๋„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™”, ํ‘ธ์‹œ ์•Œ๋ฆผ ๋“ฑ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ์ง€์›ํ•ด์ฃผ๋Š” ๋„๊ตฌ
  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ
  • ์›นํŽ˜์ด์ง€์™€ ๋ณ„๊ฐœ๋กœ ์ž‘๋™
  • ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์˜ ํ˜•ํƒœ๋ฅผ ๊ฐ€์ง
  • ์บ์‹œ ์ €์žฅ


โœ’๏ธ ์‚ฌ์šฉ๋ฒ•

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); // ์บ์‹œ ์ €์žฅ
    })
  );
});



์ถœ๋ ฅ

  • ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒด

๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ & ๋„์›€์ด ๋˜๋Š” ๋งํฌ






profile
๊ณต๋ถ€ํ•˜๋Š” ๋ฒจ๋กœ๊ทธ

0๊ฐœ์˜ ๋Œ“๊ธ€