22 - 1학기/팀 스터디

[웹 FE 스터디] 7

luii0022 2022. 11. 10. 00:34

TAB 2022 2학기 학회활동 [팀 스터디]

 TIL 포스팅입니다.

작성자 : 40기_강한림

 


노마드 코더 - ReactJS로 영화 웹 서비스 만들기 수강 과정입니다.

 

7.0

지금까지 공부한 state, effect, props 등을 연습합니다.

 

- 학습 목표 : to do list 를 만듭니다

더보기

1. 기본 상태 입니다.

function App() {
  return (
    <div>
      <input text="text" placeholder="Write your to do..." />
    </div>
  );
}

export default App;

2. input 값을 조정하기 위해서 useState()를 사용합니다. 또한, onChange function을 사용합니다.

import { useState } from "react";

function App() {
  const [toDo, setToDo] = useState("");
  const onChange = (event) => setToDo(event.target.value);
  return (
    <div>
      <input
        onChange={onChange}
        value={toDo}
        text="text"
        placeholder="Write your to do..."
      />
    </div>
  );
}

export default App;

3. input들을 form에 넣습니다. 그리고, form에는 button을 추가합니다. 이 버튼은 submit 이벤트를 발생시킵니다.

그렇기에, 기본적인 submit 기능을 막기위해서, const 함수를 추가합니다.

import { useState } from "react";

function App() {
  const [toDo, setToDo] = useState("");
  const onChange = (event) => setToDo(event.target.value);
  const onSubmit = (event) => {
    event.preventDefault();
  };

  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          onChange={onChange}
          value={toDo}
          text="text"
          placeholder="Write your to do..."
        />
        <button>Add To Do</button>
      </form>
    </div>
  );
}

export default App;

4. toDo가 비어있는지 확인하는 기능을 추가합니다.

비어있다면, return 시킵니다. // 이 함수가 작동하지 않도록 합니다. 

아니면, toDo를 추가하고, input을 비웁니다.

import { useState } from "react";

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
          onChange={onChange}
          value={toDo}
          text="text"
          placeholder="Write your to do..."
        />
        <button>Add To Do</button>
      </form>
    </div>
  );
}

export default App;

5. 여러개의 toDo를 받을 수 있는 array를 만듭니다. 새로운 state를 만듭니다. array는 state이기 때문에 수정(추가, 삭제)을 원할 때에는 함수를 사용해야함에 주의합니다.

이 강의에서는 함수를 사용해서 수정하고, 이때 값을 받아오는 것 또한, 함수를 사용해서 진행합니다.

기존에 배열에 새로운 원소를 추가하고싶을때,  현재의 배열을 그대로 가져오고 싶다면(원소만을) '...배열이름'으로 작성합니다.

import { useState } from "react";

function App() {
  const [toDo, setToDo] = useState("");
  const [toDos, setToDos] = useState([]);
  const onChange = (event) => setToDo(event.target.value);
  const onSubmit = (event) => {
    event.preventDefault();
    if (toDo === "") {
      return;
    }
    setToDos((cuttnetArray) => [toDo, ...cuttnetArray]);
    setToDo("");
  };

  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          onChange={onChange}
          value={toDo}
          text="text"
          placeholder="Write your to do..."
        />
        <button>Add To Do</button>
      </form>
    </div>
  );
}

export default App;

6. 제목을 추가합니다 (<h1></h1>사용). 이 제목에 toDos의 개수를 나타내도록 합니다.

// div와 form 사이에 작성합니다 (div에 포함, form에는 미포함)

<h1>MY TO DOs({toDos.length})</h1>

 

 

7.1

toDo list의 2번째 시간입니다.

더보기

7. 배열의 요소들 각각을 component로 만듭니다.

<hr />을 사용합니다.(hr : horizontal rule) 이후 map()을 사용합니다

- map() : JS 함수중 하나로, ()에는 함수가 들어갈 수 있으며, 그 함수는 배열의 모든 요소에 적용될 수 있습니다.

사용한뒤, 새로운 배열로 return 해줍니다. map의 첫번째 인수는 map이 실행될때의 순서에 해당하는 item 입니다.

- 작성방식 : [ ].map(function), 배열이름.map(function)

이 단계(7)에서는 component를 return 하도록 합니다.

이 내용들은 form태그 아래에 작성됩니다.

<hr />
      <ul>
        {toDos.map((item) => (
          <li>{item}</li>
        ))}
      </ul>

8. 같은 component의 list을 render 할 때에는 key라는 prop을 넣어줘야 한다는 경고가 뜹니다.

key를 추가합니다. key는 고유한 값이어야 합니다.

{toDos.map((item, index) => (
          <li key={index}>{item}</li>
        ))}

 9. 완성되었습니다.

완성 화면(글자는 아무거나)

- 최종적인 코드 입니다.

import { useState } from "react";

function App() {
  const [toDo, setToDo] = useState("");
  const [toDos, setToDos] = useState([]);
  const onChange = (event) => setToDo(event.target.value);
  const onSubmit = (event) => {
    event.preventDefault();
    if (toDo === "") {
      return;
    }
    setToDos((cuttnetArray) => [toDo, ...cuttnetArray]);
    setToDo("");
  };

  return (
    <div>
      <h1>MY TO DOs({toDos.length})</h1>
      <form onSubmit={onSubmit}>
        <input
          onChange={onChange}
          value={toDo}
          text="text"
          placeholder="Write your to do..."
        />
        <button>Add To Do</button>
      </form>
      <hr />
      <ul>
        {toDos.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

 

7.2

- 학습목표 : 암호화폐들과 그 가격을 나열하는 웹 사이트를 만듭니다.

- useEffect를 연습합니다.

더보기

1. 두가지 state를 만듭니다. 하나는 로딩을 위한, 다른 하나는 코인리스트들을 '잠시'가지고 있기 위한 것 입니다.

우선은 로딩을 위한 것 만을 만듭니다.

import { useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  return (
    <div>
      <h1>The Coins</h1>
      {loading ? <strong>Loding...</strong> : null}
    </div>
  );
}

export default App;

2. useEffect를 사용합니다. (api를 불러오기 위해서) 하지만, Effect를 1번만 사용하기 위해서, 비워둡니다.(코드중 [ ]을 의미합니다.) 빈 배열이면 1번만 실행되는것을 이용합니다.

import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    fetch("https://api.coinpaprika.com/v1/tickers");
  }, []);
  return (
    <div>
      <h1>The Coins</h1>
      {loading ? <strong>Loding...</strong> : null}
    </div>
  );
}

export default App;

3. fetch한 것에서, response로 부터, json을 추출합니다.

import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    fetch("https://api.coinpaprika.com/v1/tickers")
      .then((response) => response.json())
      .then((json) => console.log(json));
  }, []);
  return (
    <div>
      <h1>The Coins</h1>
      {loading ? <strong>Loding...</strong> : null}
    </div>
  );
}

export default App;

4. data들을 component에서 보여주도록 합니다. 이를 위해, data를 state에 넣습니다. 기본값으로는 빈 배열이 되도록 합니다. json, 즉, coins(state)를 얻었을때, json의 값을 setCoins 해줍니다.

동시에, coins 얻기를 끝냈다면, loding의 값을 false로 바꾸어야 합니다(기본값은 true)

import { useEffect, useState } from "react";

function App() {
  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);
      });
  }, []);
  return (
    <div>
      <h1>The Coins</h1>
      {loading ? <strong>Loding...</strong> : null}
    </div>
  );
}

export default App;

5. map()을 사용합니다. 차례대로 값을 불러줄 이름을 정해주고, index를 사용합니다. 하지만 이미 값들이 개별적인 id를 가지고 있으므로, 그것을 index 대신 사용합니다.

여기서 사용한 'coin'이라는 이름은, coins라는 배열에 있는 각각의 요소들을 의미합니다.

처음으로는 coin의 name을 보여주고, 그 다음에는 symbol을 보여주도록 합니다. 마지막으로는 USD 가격을 보여줍니다.

import { useEffect, useState } from "react";

function App() {
  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);
      });
  }, []);
  return (
    <div>
      <h1>The Coins</h1>
      {loading ? <strong>Loding...</strong> : null}
      <ul>
        {coins.map((coin) => (
          <li>
            {coin.name} ({coin.symbol}): {coin.quotes.USD.price} USD
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

- 처음에는, 비어있는 array를 넘겨주기에 coin이 0입니다. 기본값을 지정해 주지 않는다면, length 입력 때문에 이후 오류가 납니다.

6. 제목에 coin의 개수를 보여주도록 합니다. 

<h1>The Coins ({coins.length}) </h1>

7. loading 이면, loading...을 보여주고, 아니면 코인 목록들을 보여주도록 합니다.

제목또한 loading이면 아무것도 보여주지 않고, 아니면 코인들의 개수를 보여주도록 합니다.

<h1>The Coins {loading ? "" : `(${coins.length})`} </h1>
.....
{loading ? (
        <strong>Loding...</strong>
      ) : (
        <select>
          {coins.map((coin) => (
            <option>
              {coin.name} ({coin.symbol}): {coin.quotes.USD.price} USD
            </option>
          ))}
        </select>
      )}

8. 완성된 모습

- 완성 코드

import { useEffect, useState } from "react";

function App() {
  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);
      });
  }, []);
  return (
    <div>
      <h1>The Coins {loading ? "" : `(${coins.length})`} </h1>
      {loading ? (
        <strong>Loding...</strong>
      ) : (
        <select>
          {coins.map((coin) => (
            <option>
              {coin.name} ({coin.symbol}): {coin.quotes.USD.price} USD
            </option>
          ))}
        </select>
      )}
    </div>
  );
}

export default App;

 

7.3 

학습 목표 : 영화를 보여주고, 영화들의 정보를 보여주고, 링크를 넣어서 다른곳(영화의 정보를 더 볼 수 있는 곳)으로 연결 시켜줍니다.

더보기

1. 전과 같이 로딩중임을 보여주고, 로딩이 끝났을 때, 영화를 보여주도록 합니다. 그리고, useEffect를 사용해서, 특정한 코드는 1번만 실행되도록 합니다.

데이터를 옮기는 작업까지 완료합니다.

import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  useEffect(() => {
    fetch(
      `https://yts.mx/api/v2/list_movies.json?minimum_rating=8.8&sort_by=year`
    )
      .then((response) => response.json())
      .then((json) => {
        setMovies(json.data.movies);
        setLoading(false);
      });
  }, []);
  return <div>{loading ? <h1>Loading... </h1> : null}</div>;
}

export default App;

2. then 대신 async-await를 사용합니다. 사용하기 위해서 getMovies 함수를 만듭니다.

import { useEffect, useState } from "react";

function App() {
  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.8&sort_by=year`
      )
    ).json();

    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovies();
  }, []);
  return <div>{loading ? <h1>Loading... </h1> : null}</div>;
}

export default App;

3. movies를 화면에 보이도록 만듭니다. (map()사용)

현재는 제목만 보입니다.

import { useEffect, useState } from "react";

function App() {
  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.8&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}>{movie.title}</div>
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

 4. 수정합니다(제목을 h2에 넣는다거나, 다른 정보들(제목 외)을 더 보여준다거나...)

- 이때, 추가하는 정보중 genres는 array입니다. 그래서, map을 한번 더 써주어야 합니다. 

요소가 고유한 값이라면, map을 사용할때, 요소 자체를 key 로 주는것도 가능합니다. 

 return (
    <div>
      {loading ? (
        <h1>Loading... </h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <div key={movie.id}>
              <img src={movie.medium_cover_image} />
              <h2>{movie.title}</h2>
              <p>{movie.summary}</p>
              <ul>
                <li>
                  {movie.genres.map((g) => (
                    <li key={g}> {g}</li>
                  ))}
                </li>
              </ul>
            </div>
          ))}
        </div>
      )}
    </div>
  );

 - 다음 단계에서는 링크를 만들것이기 때문에, 각 영화의 id를 얻어야 합니다. // 다음영상에서..

 

7.4

이전 강의에서 이어집니다

더보기

5. movie라는 component를 만듭니다. (Movie.js 생성)

<div> <img ~ 부터, </ul></div>까지를 옮깁니다.

function Movie() {
  return (
    <div>
      <img src={medium_cover_image} />
      <h2>{title}</h2>
      <p>{summary}</p>
      <ul>
        <li>
          {genres.map((g) => (
            <li key={g}> {g}</li>
          ))}
        </li>
      </ul>
    </div>
  );
}

export default Movie;

 - 이러한 상태가 됩니다.

6. prop을 사용합니다. // App.js로부터 정보를 받기 위함.

- midium_cover_image, title, summary, genres는 전부, 부모 component로부터 받아옵니다. 

- 순서대로 App.js, Movie.js의 모습입니다.

- midium_cover_image(prop)의 이름이 coverIMG(prop이름)로 바뀌었습니다.

import { useEffect, useState } from "react";
import Movie from "./Movie";

function App() {
  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.8&sort_by=year`
      )
    ).json();

    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovies();
  }, []);

  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>
  );
}

export default App;
function Movie({ coverIMG, title, summary, genres }) {
  return (
    <div>
      <img src={coverIMG} alt={title} />
      <h2>{title}</h2>
      <p>{summary}</p>
      <ul>
        <li>
          {genres.map((g) => (
            <li key={g}> {g}</li>
          ))}
        </li>
      </ul>
    </div>
  );
}

export default Movie;

7. 어떤 prop이 있는지 확인하기 위해, prop-types를 import합니다. 이것을 통해 Movie 컴포넌트에 있는 prop들의 자료형을 설정해줍니다(PropTypes 설정).

import PropTypes from "prop-types";

function Movie({ coverIMG, title, summary, genres }) {
  return (
    <div>
      <img src={coverIMG} alt={title} />
      <h2>{title}</h2>
      <p>{summary}</p>
      <ul>
        <li>
          {genres.map((g) => (
            <li key={g}> {g}</li>
          ))}
        </li>
      </ul>
    </div>
  );
}

Movie.prototype = {
  coverIMG: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;

 8. react router - 페이지 전환을 사용합니다.

npm i react-router-dom@5.3.0 // router은 5.3.0 버전을 사용했습니다.(강의 버전)

이후 코드 수정이 있습니다. 

- src 폴더 내부에 routes 폴더와 components 폴더를 추가로 생성합니다. 

- Movie.js를 componenets폴더로 이동시킵니다.

- import Movie부분을 'import Movie from "./components/Movie"; '로 수정합니다.

- routes에 Home.js를 생성합니다. Home.js는 App component 전체를 가지게 됩니다.

현재의 Home.js의 모습입니다.(영상으로는 8분40초 정도)

import { useEffect, useState } from "react";
import Movie from "../components/Movie";

function Home() {
  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.8&sort_by=year`
      )
    ).json();

    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovies();
  }, []);

  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>
  );
}

export default Home;

 현재 App.js의 상태입니다. 

function App() {
  return null;
}

export default App;

 

9. 새로운 route를 만듭니다. routes 폴더 안에, Detail.js를 생성합니다.

function Detail() {
  return <h1>Detail</h1>;
}

export default Detail;

 

 

7.5 

이전 강의에서 이어집니다.

더보기

10. react-router-dom을 사용하기 위해서, App.js에 몇가지를 import 합니다.

import { BrowserRouter as Router, Switch, Route } from "react-router-dom";

- Switch는 router을 찾는 역할을 합니다.(route = URL) 그리고 route를 찾으면 컴포넌트를 rendering 합니다.

- App.js의 상태 입니다.

import { BrowserRouter as Router, Switch, Route } from "react-router-dom";

function App() {
  return (
    <Router>
      <Switch>
        <Route>
          
        </Route>
      </Switch>
    </Router>
  );
}

export default App;

11.  2개의 Route를 만듭니다. 하나는 홈 화면으로 갈 때 사용하는 Route입니다. 나머지 하나는 Detail.js를 보여줍니다.

- router 안에 route를 만들고, 유저가 '/'의 위치에 있다면, Home 컴포넌트를 보여줍니다.

- 유저가 '/movie'의 위치에 있다면 Detail을 보여줍니다.

import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Detail from "./routes/Detail";
import Home from "./routes/Home";
function App() {
  return (
    <Router>
      <Switch>
        <Route path="/movie">
          <Detail />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
    </Router>
  );
}

export default App;

- BrowerRouter은 우리가 평소 사용하는 웹사이트의 URL과 같이 생겼습니다.

- HashRouter은 /#/를 붙입니다. (BrowerRouter은 /입니다)
- Switch를 넣은 이유는, 한번에 1개의 route만 렌더링 하기 위함입니다.
 
 
12. 영화 제목을 클릭하면, Detail(route)로 가게 합니다. (단, a태그(재실행됨) 없이 되도록 합니다.)
 
- Link는 브라우저 새로고침 없이 유저를 다른 페이지로 이동시켜주는 컴포넌트 입니다.
- Movie.js에 있던 h2를 다음과 같이 수정합니다.
 
<h2>
    <Link to="/movie">{title}</Link>
</h2>
 
 

 

 

!!!! 

진행중 영화의 제목을 클릭해도, 위의 URL은 변경되지만, 화면이 Detail로 변하지 않는다면,

index.js에서, root.render()내의 부분을 다음과 같이 수정해주시면 됩니다.

(React.StrictMode 삭제)

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

 

7.6

지난 강의에서 이어집니다. 

- Router - 동적 URL 이야기로 시작합니다.

더보기

13. 동적인 URL을 생성합니다. ①

- / 다음에 : 를 쓰는것에 주의합니다.

- App.js의 Route중 /movie를 수정합니다. 

<Route path="/movie/:id">
          <Detail />
        </Route>

 

13. 동적인 URL을 생성합니다. ②

- Movie의 부모 컴포넌트인 Home에서, id값을 보내줄 수 있도록 합니다.

- 순서대로, Home의 수정 모습과, Moive의 수정 모습입니다.

<div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              id={movie.id}
              coverIMG={movie.medium_cover_image}
              title={movie.title}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
function Movie({ id, coverIMG, title, summary, genres }) {
  return (
    <div>
      <img src={coverIMG} alt={title} />
      <h2>
        <Link to={`/movie/${id}`}>{title}</Link>
      </h2>
      <p>{summary}</p>
      <ul>
        <li>
          {genres.map((g) => (
            <li key={g}> {g}</li>
          ))}
        </li>
      </ul>
    </div>
  );
}

Movie.prototype = {
  id: PropTypes.number.isRequired,
  coverIMG: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;

 

14. id 값으로 어떤값이 올지 알아냅니다.

- react router에서 제공하는 함수중, url에 있는 값을 반환해주는 함수{useParams}를 사용합니다.

- Detail.js에 작성합니다.

- 이전에, App.js에서 "/movie/:id"로 작성했기에, 변수의 이름도 {id}로 해줍니다.

import { useParams } from "react-router-dom";

function Detail() {
  const {id} = useParams();
  return <h1>Detail</h1>;
}

export default Detail;

 

15. 받은 id를 가지고 API에 요청을 보내도록 합니다. ①

- await는 async 함수 내부에 있어야 합니다.

import { useEffect } from "react";
import { useParams } from "react-router-dom";

function Detail() {
  const { id } = useParams();
  const getMovie = async () => {
    const json = await (
      await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
    ).json();
  };
  useEffect(() => {
    getMovie();
  }, []);
  return <h1>Detail</h1>;
}

export default Detail;

 

 

7.7 

Publishing에 관련된 내용입니다.(github pages에 올리는 방법)

- 생략합니다.