본문 바로가기
국비학원/수업기록

국비 지원 개발자 과정_Day51

by 루팽 2023. 2. 9.

리액트 프로젝트 템플릿 작성하기

1. npm install -g yarn

2. yarn -version

3. yarn create react-app template2023

4. yarn init --force 엔터 한 후 질문지에 대해 모두 엔터 치면 자동 완성됨

 

현재 페이지가 몇 번째 페이지인지 알고 있어야 한다 → 변하는 값이니 let으로 설정

현재 페이지가 몇번째 페이지인지 기억하는 변수가 필요하다

const store = {
	currentPage: 1
}

 

이전페이지일 경우, 내가 현재 바라보는 페이지를 기준으로 -1 한다

주의할 점은 -1은 되지 않아야 한다(페이지는 1부터 시작하기에) → 삼항연산자 이용

// store.currentPage가 1보다 크면 -1하고 아니라면 1 유지
store.currentPage > 1 ? store.currentPage - 1 : 1

 

다음 페이지일 경우, 내가 현재 바라보는 페이지를 기준으로 +1 한다

// 아래 코드는 3페이지까지 있는 경우를 가정
store.currentPage < 3 ? store.currentPage + 1 : 3

 

currentPage의 초기값은 1(전역변수로 선언함)

왜냐하면 상세 보기 화면으로 갔다가 다시 목록으로 돌아갈 때 이전에 바라보던 페이지로 돌아가려면 이 값이 공유되어야 함 → 그렇기에 전변으로 처리

 

내가 바라보는 페이지가 1번이면 배열의 0~9 인덱스만 호출

i = (1-1)*10 → 0
i < 1*10 → 10

 

내가 바라보는 페이지가 2번이면 배열의 10~19 인덱스만 호출

i = (2-1)*10 → 10
i < 2*10 → 20

 

내가 바라보는 페이지가 3번이면 배열의 20~29 인덱스만 호출

i = (3-1)*10 → 20
i < 3*10 → 30

 

for(let i = (store.currentPage - 1) * 10; i < store.currentPage * 10; i++) {
}

 

<step 5>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>페이징 처리 - step5</title>
    <script defer src="./app.js"></script>
  </head>
  <body>
    <h2>실습제목: 페이징 처리</h2>
    <div id="title"></div>
  </body>
</html>

 

const xhr = new XMLHttpRequest();
const NEWS = "https://api.hnpwa.com/v0/news/1.json";
const CONTENT = "https://api.hnpwa.com/v0/item/@id.json";
const container = document.querySelector("#title");
const divContent = document.createElement("div"); // 제목에 대한 상세내용
// 페이징 처리 <step 1>
const store = {
  currentPage: 1, // 현재 내가 바라보는 페이지 번호
};

// 아래 함수의 사용처는 두 군데
// 요청 URL만 바뀜 나머지는 같음 -> 그래서 파라미터를 url로 줌
function getData(url) {
  xhr.open("GET", url, false);
  xhr.send();
  return JSON.parse(xhr.responseText);
}

// 뉴스 목록 가져오기
function newsList() {
  const news = getData(NEWS);
  // li태그를 담을 배열 선언하기
  const getList = [];
  // li 태그를 담기 전에 ul태그 먼저 담기
  getList.push("<ul>"); // push()는 배열의 끝에 요소를 추가하고 배열의 새로운 길이를 반환
  for (let i = (store.currentPage - 1) * 10; i < store.currentPage * 10; i++) {
    getList.push(`
  <li>
    <a href = '#/show/${news[i].id}'>${news[i].title}(${news[i].comments_count})</a>
  </li>
  `);
  } // end of for
  getList.push("</ul>");
  // 배열의 맨 뒤에 추가하는 함수가 push이다
  getList.push("<div>"); // 스타일 추가 고려한 div태그 추가
  getList.push(
    `<a href='#/page/${
      store.currentPage > 1 ? store.currentPage - 1 : 1
    }'>이전페이지</a>`
  );
  getList.push("&nbsp;&nbsp"); // 링크 사이 띄어쓰기
  getList.push(
    `<a href='#/page/${
      store.currentPage < 3 ? store.currentPage + 1 : 3
    }'>다음페이지</a>`
  );
  getList.push("</div>");
  // 빈문자열 주면 구분자 없는 하나의 합쳐진 HTML 문자열을 얻음
  container.innerHTML = getList.join(""); // join()은 배열의 모든 요소를 연결해 하나의 문자열로 만듦
} // end of newsList

// 뉴스 상세 내용 보기
function newsDetail() {
  const id = this.location.hash.substring(7); // 상세보기시 앞의 #/show/를 제거하고 가져옴
  const newsContent = getData(CONTENT.replace("@id", id));
  const h1 = this.document.createElement("h1");
  container.innerHTML = `
    <h1>${newsContent.title}</h1>
    <div>
      <a href = '#/page/${store.currentPage}'>목록으로</a>
    </div>
    `;
} // end of newDetail

// 라우터에서 화면 전환하기
function router() {
  // 글 목록의 링크는 # -> location.href에 #에왔는데 왜 참인가?
  // location.href에 #만 들어오면 빈값을 반환
  // 그렇기에 이 코드가 작동하는 것
  const routePath = location.hash;
  console.log(routePath);
  // 첫 진입(첫 요청시)일 때는 해시값이 없으니까 newsList를 호출한다
  if (routePath === "") {
    newsList();
  } else if (routePath.indexOf("#/page/") >= 0) {
    // 해시값 중에서 숫자값만 추출하기
    // #/page/를 제거하고 문자열을 숫자로 바꿔야함
    store.currentPage = Number(routePath.substring(7));
    newsList();
  } else {
    newsDetail();
  }
} // end of router

window.addEventListener("hashchange", router);
router();

 

<step 1>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>클론코딩 - step1[mostPopular]</title>
    <link rel="stylesheet" href="./youtube.css" />
    <script defer src="./youtube.js"></script>
  </head>
  <body>
    <h3>유튜브 클론코딩 실습 1</h3>
    <div id="root"></div>
  </body>
</html>

 

const ajax = new XMLHttpRequest();
let content = "";
const key = "";
const MOST_URL = `https://youtube.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&maxResults=25&key=${key}`;

ajax.open("GET", MOST_URL, false);
ajax.send();

const most = JSON.parse(ajax.response);
const items = most.items;
console.log(items);
console.log(items.length); // 25

content += `<ul class = "videos">`;
// 25번 반복 - 25개의 li
for (let i = 0; i < items.length; i++) {
  content += `<li class="container">`;
  content += `<div class="video">`;
  content += `<img class = "thumbnail" src='${items[i].snippet.thumbnails.medium.url}' />`;
  content += `<div">`;
  content += `<p class = "title">${items[i].snippet.title}</p>`;
  content += `<p class = "channelTitle">${items[i].snippet.channelTitle}</p>`;
  content += `</div">`;
  content += `</div">`;
  content += `</li">`;
}
content += `</ul>`;

document.querySelector("#root").innerHTML = content;

 

* {
  box-sizing: border-box;
  /* border: 1px solid red; */
}

.videos {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  list-style: none;
  padding-left: 0px;
  margin: 0;
  gap: 14px;
}

.container {
  width: 100%;
  padding: 0.2em;
}

.video {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  border: 1px solid lightgray;
  box-shadow: 3px 3px 5px 0px rgb(185, 185, 185);
  transition: transform 250ms ease-in;
  cursor: pointer;
}

.video:hover {
  transform: scale(1.02);
}

.thumbnail {
  width: 40%;
  height: 100%;
}

.title,
.channelTitle {
  margin: 10px;
  font-size: 0.8rem;
}

.channelTitle {
  font-size: 0.6rem;
}

 

자바는 구조지향적

main을 어디에 넣든 먼저 실행됨

 

html은 절차지향적

그렇기에 javascript에 defer 넣어줌(html 태그를 모두 읽고 나서 js 적용하기 위해)

 

<step 2>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>클론코딩 - step2[mostPopular]</title>
    <link rel="stylesheet" href="./youtube.css" />
    <script defer src="./youtube.js"></script>
  </head>
  <body>
    <h3>유튜브 클론코딩 실습 2</h3>
    <div id="root"></div>
  </body>
</html>

<!--
  실습제목: 문자열을 배열로 바꾸어 처리하기
  let content = "";
  const videoList = [];
-->

 

const ajax = new XMLHttpRequest();
const videoList = [];
const key = "";
const MOST_URL = `https://youtube.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&maxResults=25&key=${key}`;

ajax.open("GET", MOST_URL, false);
ajax.send();

const most = JSON.parse(ajax.response);
const items = most.items;
console.log(items);
console.log(items.length); // 25

// push()는 배열의 끝에 요소를 추가하고 배열의 새로운 길이를 반환함
videoList.push(`<ul class = "videos">`);
// 25번 반복 - 25개의 li
for (let i = 0; i < items.length; i++) {
  videoList.push(`<li class="container">`);
  videoList.push(`<div class="video">`);
  videoList.push(
    `<img class = "thumbnail" src='${items[i].snippet.thumbnails.medium.url}' />`
  );
  videoList.push(`<div">`);
  videoList.push(`<p class = "title">${items[i].snippet.title}</p>`);
  videoList.push(
    `<p class = "channelTitle">${items[i].snippet.channelTitle}</p>`
  );
  videoList.push(`</div">`);
  videoList.push(`</div">`);
  videoList.push(`</li">`);
}
videoList.push(`</ul>`);

// join()은 배열의 모든 요소를 연결해 하나의 문자열로 만듦
// ()기본은 , / ("")은 공백 / ("-")은 -
document.querySelector("#root").innerHTML = videoList.join("");

 

* {
  box-sizing: border-box;
  /* border: 1px solid red; */
}

.videos {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  list-style: none;
  padding-left: 0px;
  margin: 0;
  gap: 14px;
}

.container {
  width: 100%;
  padding: 0.2em;
}

.video {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  border: 1px solid lightgray;
  box-shadow: 3px 3px 5px 0px rgb(185, 185, 185);
  transition: transform 250ms ease-in;
  cursor: pointer;
}

.video:hover {
  transform: scale(1.02);
}

.thumbnail {
  width: 40%;
  height: 100%;
}

.title,
.channelTitle {
  margin: 10px;
  font-size: 0.8rem;
}

.channelTitle {
  font-size: 0.6rem;
}

 

<step 3>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>클론코딩 - step3[search-검색기]</title>
    <link rel="stylesheet" href="./youtube.css" />
    <link
      rel="shortcut icon"
      href="../../../images/youtube/logo.ico"
      type="image/x-icon"
    />
    <script defer src="./youtube.js"></script>
  </head>
  <body>
    <h3>유튜브 클론코딩 실습 3</h3>
    <header class="header">
      <div class="logo">
        <img src="../../../images/youtube/logo.png" alt="logo" />
        <h1 class="headerTitle">Youtube</h1>
      </div>
      <!-- name은 자바에서 id는 자바스크립트에서 쓸 예정 -->
      <input
        class="input"
        type="search"
        name="keyword"
        id="keyword"
        placeholder="Search"
      />
      <button class="btnSearch">
        <img
          class="imgSearch"
          src="../../../images/youtube/search.png"
          alt="search"
        />
      </button>
    </header>
    <div id="root"></div>
  </body>
</html>

<!-- 
  검색기 붙여보기
  패비콘(favicon) 이미지 붙이기
  배열 - 내장함수(prototype)
-->

 

const keyword = document.querySelector("#keyword");

// input textfield에 입력한 후 엔터쳤을 때를 잡아내는 함수
keyword.addEventListener("keypress", (event) => {
  console.log("keypress => " + event.keyCode);
  // 사용자가 입력한 검색어를 찾는 함수 호출
  handleSearch();
});

// 사용자가 입력한 검색어 읽어오기
const handleSearch = () => {
  const user = document.querySelector("#keyword").value;
  console.log("사용자가 입력한 검색어 => " + user);
  search(user);
};

// 사용자가 입력한 검색어로 조건 검색 구현하기
const search = (query) => {
  console.log("사용자가 입력한 btns 받음 => " + query);
  const ajax = new XMLHttpRequest();
  const videoList = [];
  const key = "";
  const SEARCH_URL = `https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=30&q=${query}&key=${key}`;

  ajax.open("GET", SEARCH_URL, false);
  ajax.send();

  const most = JSON.parse(ajax.response);
  const items = most.items;

  videoList.push(`<ul class = "videos">`);
  for (let i = 0; i < items.length; i++) {
    videoList.push(`<li class="container">`);
    videoList.push(`<div class="video">`);
    videoList.push(
      `<img class = "thumbnail" src='${items[i].snippet.thumbnails.medium.url}' />`
    );
    videoList.push(`<div">`);
    videoList.push(`<p class = "title">${items[i].snippet.title}</p>`);
    videoList.push(
      `<p class = "channelTitle">${items[i].snippet.channelTitle}</p>`
    );
    videoList.push(`</div">`);
    videoList.push(`</div">`);
    videoList.push(`</li">`);
  }
  videoList.push(`</ul>`);

  document.querySelector("#root").innerHTML = videoList.join("");
}; // end of search

 

* {
  box-sizing: border-box;
  /* border: 1px solid red; */
}

.header {
  display: flex;
  height: 4rem;
  padding: 0.5rem 1rem;
  margin: 0 0.2rem;
  background-color: black;
}

.logo {
  display: flex;
  align-items: center;
  margin-right: 1em;
}

.headerTitle {
  color: white;
  margin: 0.2rem 0.5rem;
}

.input {
  flex-basis: 100%;
  font-size: 1.1rem;
  outline: 0;
  margin: 0rem 0.5rem;
}

.btnSearch {
  background-color: rgb(223, 50, 50);
  outline: 0;
}

.imgSearch {
  height: 100%;
  padding: 0.5em 0.2em;
}

.videos {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  list-style: none;
  padding-left: 0px;
  margin: 0;
  gap: 14px;
}

.container {
  width: 100%;
  padding: 0.2em;
}

.video {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  border: 1px solid lightgray;
  box-shadow: 3px 3px 5px 0px rgb(185, 185, 185);
  transition: transform 250ms ease-in;
  cursor: pointer;
}

.video:hover {
  transform: scale(1.02);
}

.thumbnail {
  width: 40%;
  height: 100%;
}

.title,
.channelTitle {
  margin: 10px;
  font-size: 0.8rem;
}

.channelTitle {
  font-size: 0.6rem;
}

 

Babel은 Javascript 컴파일러이다

컴파일러(Compiler) 언어 해석기, 특정 언어를 다른 프로그래밍 언어로 옮기는 프로그램

 

내가 쓴 내용

const element = <h1>Hello, React</h1>

 

Babel이 바꿔준 내용

"use strict"; // 엄격모드(제약조건 생김)
const element = /#PURE/React.createElement("h1", null, "Hello, React");

 

<React, Babel 예제 1>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>바벨소개</title>
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <style>
      .title {
        color: green;
      }
    </style>
    <!-- type을 babel로 해야 바닐라스크립트 -> 리액트 섞어쓰기 가능 -->
    <script type="text/babel">
      const rootElement = document.querySelector("#root");
      const element = React.createElement(
        "h1",
        {
          className: "title",
        },
        ["Java", "React", "Spring"]
      );
      ReactDOM.createRoot(rootElement).render(element);
    </script>
  </body>
</html>

 

<React, Babel 예제 2>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>바벨소개</title>
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <style>
      .title {
        color: red;
      }
    </style>
    <!-- type을 babel로 해야 바닐라스크립트 -> 리액트 섞어쓰기 가능 -->
    <script type="text/babel">
      const rootElement = document.querySelector("#root");
      const element = <h1 className="title">JAVA</h1>;
      ReactDOM.createRoot(rootElement).render(element);
    </script>
  </body>
</html>

 

<React, Babel 예제 3>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>바벨소개</title>
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <style>
      .title {
        color: purple;
        font-size: 58px;
      }
    </style>
    <!-- type을 babel로 해야 바닐라스크립트 -> 리액트 섞어쓰기 가능 -->
    <script type="text/babel">
      const rootElement = document.querySelector("#root");
      const subject = "React";
      const 클래스이름 = "title";
      const element = <h1 className={클래스이름}>{subject}</h1>;
      ReactDOM.createRoot(rootElement).render(element);
    </script>
  </body>
</html>

 

<React, Babel 예제 4>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>바벨소개</title>
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <style>
      .title {
        color: blue;
        font-size: 58px;
      }
    </style>
    <!-- type을 babel로 해야 바닐라스크립트 -> 리액트 섞어쓰기 가능 -->
    <script type="text/babel">
      const rootElement = document.querySelector("#root");
      const subject = "React";
      const 클래스이름 = "title";
      const 사용자정의태그 = <h1 className={클래스이름}>{subject}</h1>;
      ReactDOM.createRoot(rootElement).render(사용자정의태그);
    </script>
  </body>
</html>

 

 

<React, Babel 예제 5>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>바벨소개</title>
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <style>
      .title {
        color: yellow;
        background-color: blue;
        font-size: 58px;
      }
    </style>
    <!-- type을 babel로 해야 바닐라스크립트 -> 리액트 섞어쓰기 가능 -->
    <script type="text/babel">
      const rootElement = document.querySelector("#root");
      const subject = "React";
      const 클래스이름 = "title";
      const props = { className: 클래스이름, children: subject };
      const 사용자정의태그 = <h1 {...props} />;
      ReactDOM.createRoot(rootElement).render(사용자정의태그);
    </script>
  </body>
</html>

 

<React, Babel 예제 6>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>바벨소개</title>
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <style>
      .title {
        color: pink;
        background-color: lightgray;
        font-size: 58px;
      }
    </style>
    <!-- type을 babel로 해야 바닐라스크립트 -> 리액트 섞어쓰기 가능 -->
    <script type="text/babel">
      const rootElement = document.querySelector("#root");
      const subject = "React";
      const 클래스이름 = "title";
      const props = { className: 클래스이름, children: subject };
      const 사용자정의태그 = (
        <h1 className={props.className} children={props.children}></h1>
      );
      ReactDOM.createRoot(rootElement).render(사용자정의태그);
    </script>
  </body>
</html>

댓글