본문 바로가기

22 - 1학기/팀 스터디

[웹 FE 스터디] ReactJs 7

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

 TIL 포스팅입니다.

작성자 40기_박지민

 


노마드 코더 - ReactJS로 영화 웹 서비스 만들기


▶Todo 프로그램 예제

import {useState,useEffect} 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((currentArray) => [toDo, ...currentArray] );
        setToDo("");
    };
    
    return (
    <div>
        <h1>MY TODOSSS ({toDOs.length})</h1>
        <form onSubmit={onSubmit}>
        <input 
            onChange={onChange} 
            value ={toDo}
            type = "text" 
            placeholder="Write your to do..."/>
        </form>
        <hr />
        <ul>
        {toDos.map((item,index)=>(
            <li key = {index}>{item}</li>
        ))} 
        </ul>
        
    </div>
    );
}

export default App;

 

▷ OnSubmit

- toDo가 비어있게 될 경우를 대비해 onSubmit 내에 if(toDo === ""){return;}을 추가해 toDo가 비었을 때 함수가 작동하지 않도록 한다.

- OnSubmit 실행 시마다 input의 값을 비워주고 싶기 때문에 setTodo("");를 이용해 setTodo함수를 불러온다. 

※setTodo는 toDo값을 수정하는 함수고 그 값은 input과 연결되어 있기 때문에 toDo의 값을 변경하면 input 값도 변경!

- toDos는 배열이지만 State이기 때문에 일반적인 방법인 toDos.push()나 toDos =  ' ' 로는 수정할 수 없다.

  toDos를 수정하는 함수를 따로 만들어줘야 함.  

  이 때 setToDos(currentArray => ~) 는 setToDos(funciton(currentArray{ return ~ })와 같은 내용를 나타낸다.

 

▷새 요소에 기존 배열의 요소 추가

- ... 배열명 으로 추가가 가능

Ex) num = [1,2,3,4] , 새 요소 =  5

      newNum = [5, num ]  → [5,num(4)]

      newNum = [5, ...num ]  → [5,1,2,3,4]

 

▷ map()

- 자바스크립트 함수. 배열 각각의 요소들을 바꾸고 싶을 때 사용. 배열의 모든 항목에 대해 실행되며 리턴한 값이 새 배열에 들어간다.

- 배열.map((item(이름변경가능)=>변경내용)  형태로 사용 

 Ex)

['I','am','so',sleepy'].map(()=>":<");   [ ':<' , ':<' , ':<' , ':<' ]
['I','am','so',sleepy'].map((item)=>item.toUpperCase()); ['I','AM','SO','SLEEPY']

※ {toDos.map((item)=>( <li>{item}</li>))} 의 문제. 

    console에서 문제를 확인가능. 같은 컴포넌트의 list를 렌더링할 때 key라는 prop가 필요하다는 내용이 뜬다.

    리액트가 기본적으로 리스트에 있는 모든 아이템을 인식하기 떄문에 발생하는 문제.

    map함수의 인자를 첫번째는 value(toDo), 두번째를 인덱스로 두고 key값을 {index}로 하면 해결된다.

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

     + 이후 console.log로 만들어진 배열을 확인할 수 있다.

    문자열로 구성된 배열 : console.log(toDos) 

    리액트 요소로서의 배열 : console.log(toDos.map((item,index)=><li key= {index}>{item}</li>))


▶코인트래커 예제

 - 암호화폐들과 그 가격을 나열하는 프로그램

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;

 

- useEffect 

: 해당 함수 내의 내용을 한 번만 사용하기 위해 실행 내용 뒤는 빈 배열로 남겨둔다.

  fetch 안 링크에는 코인들의 정보가 담겨있음

  response에서 json을 받아오고 setCoins를 통해 coins의 state로 만든다.
 이 때 로딩이 끝났으니 loading의 값은 false로

 

- map()

: map()를 통해 coins 배열의 각 요소들을 name(symbol):USDprice 형태로 한다.

 

※ coins의 초기값이 []인 이유

- 본래 coins의 초기값은 undefined이기 때문에 길이를 표현하는 coins.length에서 오류 발생. 

   useState([])를 통해 기본값을 빈 배열로 지정해줘야 한다.

 

- $ : string(문자열) 의미


▶ 영화 정보 프로그램 예제

▷App.js

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/:id">
          <Detail />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
    </Router>
  );
}

export default App;

▷index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

※ 오류 발생 시  root.render(<App />); 로 변경.

 

▷Home.js  ( in routes(folder) )

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}
                id = {movie.id}
                coverIMG={movie.medium_cover_image}
                title={movie.title}
                summary={movie.summary}
                genres={movie.genres}
              />
            ))}
          </div>
        )}
      </div>
    );
  }
  
  export default Home;

▷Detail.js  ( in routes(folder) )

import { useEffect,useState } from "react";
import { useParams } from "react-router-dom";
import MovieDetail from "../components/MovieDetail";


function Detail() {
    const [details, setDetails] = useState([]);
    const { id } = useParams(); 
    // App.js에 "/movie/:id"로 표기했기 때문에 {id}를 변수명으로 사용.
    const getMovie = async () => {
        const json = await (
        await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
        ).json();
        setDetails(json.data.details);
    };
    useEffect(() => {
    getMovie();
    }, []);

    return( 
        <div>
            { 
            details.map((movie) => (
                <MovieDetail
                    key={movie.id}
                    coverIMG={movie.medium_cover_image}
                    year = {movie.year}
                    runtime = {movie.runtime}
                    title = {movie.title}
                    rating = {movie.rating}
                    genres = {movie.genres}
            />))}
        </div>
    );
}

※ return 내의 내용은 잘못된 예시. 추후 다른 포스트에서 수정 예정. 

▷Movie.js  ( in components(folder) )
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;
▷MovieDetail.js  ( in components(folder) )
import PropTypes from "prop-types";


function MovieDetail({ coverIMG, title, year, rating,runtime,genres }) {
  return (
    <div className = 'container'>
      <img src={coverIMG} alt={title} />
      <div className = "index">
        <h2>{title}</h2>
        <span>{year}|{runtime}</span>
        <>{rating}</>
        <ul>
            {genres.map(ge =><li key = {ge}>{ge}</li>)}
        </ul>
      </div>
    </div>
  );
}

MovieDetail.prototype = {
  id: PropTypes.number.isRequired,
  coverIMG: PropTypes.string.isRequired,
  year: PropTypes.number.isRequired,
  runtime: PropTypes.number.isRequired,
  rating: PropTypes.number.isRequired,
  title:PropTypes.string.isRequired,
  genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default MovieDetail;

※ return 내의 내용은 불완전한 예시. 추후 다른 포스트에서 수정 예정. 

 

▷asyne-await

- then 대신 사용.

  const 함수명 = asyne() => {

            const json = await(

                     await fetch(~)).json();

            then 후에 들어갔던 내용 ~

  };  형태. 한 번만 실행하기 위해 useEffect(() => { 함수명; }, []); 를 꼭 포함할 것.   

 

▷ map이 이중으로 사용된 이유 

:  genres가 배열이기 때문.

   ※ 특정 고유값 없을 때 element가 고유한 값이라면 그 자체를 key로 둘 수 있다. 예제의 {g} 가 그 예.

▷import PropTypes from "prop-types"

- prop-types를 import해서 component 폴더에서 PropTypes를 지정해주기 위함

 

▷react router

- 터미널에 npm install react-router-dom 입력을 통해 설치.

- react router의 사용을 위해 App.js에 import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 입력.

  Switch : router 탐색 후 URL(route)찾으면 컴포넌트 렌더링. 

  <Switch>

          <Route path="/위치"><실행파일명 /></Route>

          <Route path="/위치"><실행파일명 /></Route>

  <Switch> 형식으로 표현.

  이 때 위치 설명 없이 ' / ' 로 표기할 경우 홈 화면으로 간다는 뜻.

  ※ 링크는 (앞의 주소~ / 위치명 ) 형태로 되어있다.

 

- BrowserRouter : 위치명 구분을 / 로 하게 한다. HashRouter은 /#/로 구분.

 

- useParams : react router에서 제공하는 함수 url의 상세정보를 반환한다.

  사용할 경우 해당 파일에 import {useParams} form "react-router-dom" 입력


▶깃허브 빌드

1. gh-pages 패키지 설치

2. 터미널에 npm i gh-pages 입력 (package.json으로 확인)

3. 호스팅할 깃허브 페이지 링크를 " 홈페이지 " : " 링크명 " 형식으로 맨 아래 작성

4. scripts () 에 deploy 속성 부여 :  "deploy" : "gh-pages -d build" , "predeploy" : "npm run build"

5. 터미널에 npm run build 입력