TypeScript

 

JavaScript With Syntax For Types.

TypeScript extends JavaScript by adding types to the language. TypeScript speeds up your development experience by catching errors and providing fixes before you even run your code.

www.typescriptlang.org

  • JS를 기반으로 한 프로그래밍 언어
  • Strongly-typed 언어: 프로그래밍 언어가 작동하기 전에 데이터의 type을 먼저 확인
JS는 type을 신경쓰지 않는다
const plus = (a, b) => a + b;
plus(2, 2);
// 4
plus(2, "hi");
// '2hi'
const user = {
  firstName: "Angela",
  lastName: "Davis",
  role: "Professor",
};
console.log(user.name);
// undefined

 

데이터의 타입을 제어할 수 있을까?

 

 

const plus = (a: number, b: number) => a + b;

설치

처음부터 TypeScript로 프로젝트 시작하기

create-react-app 관련문서

 

Adding TypeScript | Create React App

Note: this feature is available with react-scripts@2.1.0 and higher.

create-react-app.dev

npx create-react-app my-app --template typescript

기존 프로젝트에 TypeScript 적용하기

1. 아래 설치

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

2. 확장자 변경

  • .js -> .tsx (리액트에서 사용할 경우)로 변경
  •  .js -> .ts 리액트가 아닐 경우

3. tsconfig.json 파일 생성 및 수정

npx tsc --init
tsconfig.json 파일 수정
// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
...

4. index.tsx 파일 수정

아래 오류 발생 시 
TS2345: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element | DocumentFragment'.
Type 'null' is not assignable to type 'Element | DocumentFragment'.
// index.tsx
const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);

위 과정까지 한 경우 실행이 되는 것을 확인함

5. @types/styled-components 설치

npm i --save-dev @types/styled-components
호환이 되지 않아 설치가 안될 경우, --legacy-peer-deps를 입력하면 무시하고 설치할 수 있다
npm i --save-dev @types/styled-components --save --legacy-peer-deps

@types?

DefinitelyTyped Repository

 

GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions.

The repository for high quality TypeScript type definitions. - GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions.

github.com

유명한 npm package들을 TypeScript가 이해할 수 있도록 TypeScript Definition이 있는 repo

🏋️‍♂️용어: how to TYPE

component를 type한다 = component에 type을 추가한다 = TS한테 뭐가 뭔지 설명한다


PropTypes vs TypeScript

  • PropTypes는 prop이 거기 있는지 없는지 확인해주지만, 코드를 실행한 "후"에 확인 가능
  • TypeScript는 코드 실행 "전"에 오류를 확인해줌

interface

interface는 object shape을 TS에게 설명해준다

playerObj의 object shape을 설명한 간단한 예시
interface PlayerShape {
  name: string;
  age: number;
}

const sayHello = (playerObj: PlayerShape) =>
  `Hello ${playerObj.name}! You are ${playerObj.age}`;

sayHello({ name: "홍엽", age: 10 });
Circle안에 들어가는 object shape을 CircleProps로 type하고,
Circle이 리턴하는 Container의 object shape을 ContainerProps로 type 함
// Circle.tsx
import styled from "styled-components";

interface ContainerProps {
  bgColor: string;
}

const Container = styled.div<ContainerProps>`
  width: 200px;
  height: 200px;
  border-radius: 100px;
  background-color: ${(props) => props.bgColor};
`;

interface CircleProps {
  bgColor: string;
}

function Circle({ bgColor }: CircleProps) {
  return <Container bgColor={bgColor} />;
}

export default Circle;
// App.tsx
import styled from "styled-components";
import Circle from "./Circle";

function App() {
  return (
    <div>
      <Circle bgColor="teal" />
      <Circle bgColor="tomato" />
    </div>
  );
}

export default App;

optional한 값을 주고 싶은 경우 ?를 사용

interface CircleProps {
  bgColor: string;
  borderColor?: string;
}

default한 값을 주고 싶을 경우 ??를 사용

아래의 경우에 borderColor가 없을 경우 borderColor 값은 bgColor 값 입력
function Circle({ bgColor, borderColor }: CircleProps) {
  return <Container bgColor={bgColor} borderColor={borderColor ?? bgColor} />;

최종 예제 코드

// Circle.tsx
import styled from "styled-components";

interface ContainerProps {
  bgColor: string;
  borderColor: string;
}

const Container = styled.div<ContainerProps>`
  width: 200px;
  height: 200px;
  border-radius: 100px;
  background-color: ${(props) => props.bgColor};
  border: 5px solid ${(props) => props.borderColor};
`;

interface CircleProps {
  bgColor: string;
  borderColor?: string;
  text?: string;
}

function Circle({ bgColor, borderColor, text = "default txt" }: CircleProps) {
  return (
    <Container bgColor={bgColor} borderColor={borderColor ?? bgColor}>
      {text}
    </Container>
  );
}

export default Circle;
// App.js
import styled from "styled-components";
import Circle from "./Circle";

function App() {
  return (
    <div>
      <Circle bgColor="teal" borderColor="coral" text="new text" />
      <Circle bgColor="tomato" />
    </div>
  );
}

export default App;

useState

  • TypeScript는 초기값으로 type을 예측
  • 필요한 경우 옆에 타입을 명시함으로 여러 type을 사용할 수 있음
const [counter, setCounter] = useState<number | string>(1);
setCounter("hi");

Form Event

  • 이벤트 타입의 명시 방법
  • 어떤 타입을 사용할지는 구글링해서 익숙해져야 함
  • event의 target을 currentTarget으로 사용하는 것을 확인할 수 있었음
import { useState } from "react";
import styled from "styled-components";

function App() {
  const [value, setValue] = useState("");
  const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value },
    } = event;
    setValue(value);
  };
  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(`hello ${value}`);
  };
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          value={value}
          onChange={onChange}
          type="text"
          placeholder="username"
        />
        <button>Log in</button>
      </form>
    </div>
  );
}

export default App;

theme 적용 방법

npm i --save-dev @types/styled-components 설치 이후

1. styled component 확장

// styled.d.ts
import "styled-components";

// and extend them!
declare module "styled-components" {
  export interface DefaultTheme {
    textColor: string;
    bgColor: string;
  }
}

2. theme.ts 생성

// theme.ts
import { DefaultTheme } from "styled-components/dist/types";

export const lightTheme: DefaultTheme = {
  bgColor: "white",
  textColor: "black",
};

export const darkTheme: DefaultTheme = {
  bgColor: "black",
  textColor: "white",
};

3. index.tsx에서 theme 적용

// index.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { ThemeProvider } from "styled-components";
import { lightTheme } from "./theme";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <ThemeProvider theme={lightTheme}>
    <App />
  </ThemeProvider>
);

4. App 내에서 theme 값 사용

// App.tsx
import { useState } from "react";
import styled from "styled-components";

const Container = styled.div`
  background-color: ${(props) => props.theme.bgColor};
`;

const H1 = styled.h1`
  color: ${(props) => props.theme.textColor};
`;

function App() {
  return (
    <div>
      <Container>
        <H1>test</H1>
      </Container>
    </div>
  );
}

export default App;

NAT

  • NAT(Network Address Translation)은 IP 패킷의 TCP/UDP 포트 숫자와 소스 및 목적지의 IP 주소 등을 재기록하면서 라우터를 통해 네트워크 트래픽을 주고 받는 기술
  • 패킷에 변화가 생기기 때문에  IP나  TCP/UDP의 체크섬(checksum)도 다시 계산되어 재기록되어야 함
  • NAT를 이용하는 이유는 대개 사설 네트워크에 속한 여러 개의 호스트가 하나의 공인 IP 주소를 사용하여 인터넷에 접속하기 위함
  • 하지만 꼭 사설 IP를 공인 IP로 변환하는데에만 사용하는 기술은 아님


포트 포워딩

  • 포트 포워딩 또는 포트 매핑(port mapping)은 패킷이 라우터나 방화벽과 같은 네트워크 장비를 가로지르는 동안 특정 IP 주소와 포트 번호의 통신 요청을 특정 다른  IP와 포트 번호로 넘겨주는 네트워크 주소 변환(NAT)의 응용
  • 게이트웨이(외부망)의 반대쪽에 위치한 사설 네트워크에 상주하는 호스트에 대한 서비스를 생성하기 위해 흔히 사용

참고자료

https://www.youtube.com/watch?v=Qle5cfCcuEY&list=PL0d8NnikouEWcF1jJueLdjRIC4HsUlULi&index=26

위 강의를 보고 정리한 글입니다.

 

styled-components 홈페이지

 

styled-components

CSS for the <Component> Age

styled-components.com

설치

npm i styled-components

tip: vscode-styled-components

vscode extension인 vscode-styled-components를 받으면 자동 완성 및 하이라이트 기능 제공

 

without styled-components

 

function App() {
  return (
    <div style={{ display: "flex" }}>
      <div style={{ backgroundColor: "teal", width: 100, height: 100 }}></div>
      <div style={{ backgroundColor: "tomato", width: 100, height: 100 }}></div>
    </div>
  );
}

export default App;

 

with styled-components

  • styled 뒤에 속성 값을 back tick 사이에 입력
  • back tick 안에 들어간 내용은 일반 css로 작성
  • 실제 페이지에서 검사도구로 확인할 경우, 임의의 class 이름이 부여된 것을 확인 가능
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const BoxOne = styled.div`
  background-color: teal;
  width: 100px;
  height: 100px;
`;

const BoxTwo = styled.div`
  background-color: tomato;
  width: 100px;
  height: 100px;
`;

function App() {
  return (
    <Father>
      <BoxOne />
      <BoxTwo />
    </Father>
  );
}

export default App;

Problem

styled-components내에 중복되는 부분을 어떻게 효율적으로 작성할 수 있을까?

Scratch1; component에 props 받기

import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Box = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
`;

function App() {
  return (
    <Father>
      <Box bgColor="teal" />
      <Box bgColor="tomato" />
    </Father>
  );
}

export default App;
```

Scratch 2; 기존의 styled-component 상속

상속 전
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Box = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
`;

const Circle = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
  border-radius: 9999px;
`;

function App() {
  return (
    <Father>
      <Box bgColor="teal" />
      <Circle bgColor="tomato" />
    </Father>
  );
}

export default App;

 

상속 후: Cicle에서 기존 Box를 상속 받음
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Box = styled.div`
  background-color: ${(props) => props.bgColor};
  width: 100px;
  height: 100px;
`;

const Circle = styled(Box)`
  border-radius: 9999px;
`;

function App() {
  return (
    <Father>
      <Box bgColor="teal" />
      <Circle bgColor="tomato" />
    </Father>
  );
}

export default App;

Scratch3; as

as는 기존의 만들어진 styled-components를 html tag만 변경하여 사용할 수있게 해준다
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Btn = styled.button`
  color: white;
  background-color: tomato;
  border: 0;
  border-radius: 15px;
`;

function App() {
  return (
    <Father>
      <Btn>Log-in</Btn>
      <Btn as="a">Log-in</Btn>
    </Father>
  );
}

export default App;

Scratch 4; attrs

attrs를 사용하여 각 styled-components에 속성 부여를 해준다
import styled from "styled-components";

const Father = styled.div`
  display: flex;
`;

const Input = styled.input.attrs({ required: true, minLength: 10 })`
  background-color: tomato;
`;

function App() {
  return (
    <Father>
      <Input />
      <Input />
      <Input />
      <Input />
    </Father>
  );
}

export default App;

keyframe

keyframe을 사용한 애니메이션 적용 예시

import styled, { keyframes } from "styled-components";

const Wrapper = styled.div`
  display: flex;
`;

const rotationAnimaiton = keyframes`
  0% {
    transform: rotate(0deg);
    border-radius: 0px;
  }
  50% {
    transform: rotate(720deg);
    border-radius: 100px;
  }
  100% {
    transform: rotate(0deg);
    border-radius: 0px;
  }
`;

const Box = styled.div`
  height: 200px;
  width: 200px;
  background-color: salmon;
  animation: ${rotationAnimaiton} 1s linear infinite;
`;

function App() {
  return (
    <Wrapper>
      <Box />
    </Wrapper>
  );
}

export default App;

Pseudoelements, pseudoselectors, and nesting

styled-components 안에서 일반 html tag를 선택할 수 있음
styled-components 안에서 pseudo selector를 선택할 수 있음

 

import styled from "styled-components";

const Wrapper = styled.div`
  display: flex;
`;

const Box = styled.div`
  height: 200px;
  width: 200px;
  background-color: salmon;
  display: flex;
  justify-content: center;
  align-items: center;
  span {
    font-size: 36px;
    &:hover {
      font-size: 72px;
    }
  }
`;

function App() {
  return (
    <Wrapper>
      <Box>
        <span>🤪</span>
      </Box>
    </Wrapper>
  );
}

export default App;
styled-components 안에서 styled-components를 선택할 수도 있음
import styled, { keyframes } from "styled-components";

const Wrapper = styled.div`
  display: flex;
`;

const rotationAnimaiton = keyframes`
  0% {
    transform: rotate(0deg);
    border-radius: 0px;
  }
  50% {
    transform: rotate(360deg);
    border-radius: 100px;
  }
  100% {
    transform: rotate(0deg);
    border-radius: 0px;
  }
`;

const Emoji = styled.span`
  font-size: 36px;
`;

const Box = styled.div`
  height: 200px;
  width: 200px;
  background-color: salmon;
  display: flex;
  justify-content: center;
  align-items: center;
  animation: ${rotationAnimaiton} 1s linear infinite;
  ${Emoji} {
    &:hover {
      font-size: 72px;
    }
  }
`;

function App() {
  return (
    <Wrapper>
      <Box>
        <Emoji>🤪</Emoji>
      </Box>
    </Wrapper>
  );
}

export default App;

theme

  • 기본적으로 모든 색상을 가지고 있는 object
  • local estate management와 응용하면 다크모드 구현 가능
  • App을 ThemeProvider로 감싸줌
  • theme 간의 property 이름은 같아야함
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { ThemeProvider } from "styled-components";
import App from "./App";

const darkTheme = {
  textColor: "whitesmoke",
  backgroundColor: "#111",
};

const lightTheme = {
  textColor: "#111",
  backgroundColor: "whitesmoke",
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <ThemeProvider theme={darkTheme}>
    <App />
  </ThemeProvider>
);
ThemeProvider의 값에 접근 가능
// App.js
import styled from "styled-components";

const Wrapper = styled.div`
  background-color: ${(props) => props.theme.backgroundColor};
`;

const Title = styled.h1`
  color: ${(props) => props.theme.textColor};
`;

function App() {
  return (
    <Wrapper>
      <Title>Title</Title>
    </Wrapper>
  );
}

export default App;

'React' 카테고리의 다른 글

[React] 가상화폐시세 사이트 1 - CSS reset  (0) 2023.08.29
[React] TypeScript  (0) 2023.08.17
[React 기초] 08. 예제 - Movie info  (0) 2023.08.10
[React 기초] 07. 예제 - Coin tracker  (0) 2023.08.10
[React 기초] 06. 예제 - Todo List  (0) 2023.08.10

 

TCP 프로토콜

TCP가 하는 일

  • 전송 제어 프로토콜(Transmission Control Protocol, TCP)은 인터넷에 연결된 컴퓨터에서 실행되는 프로그램 간에 통신을 안정적으로, 순서대로, 에러 없이 교환할 수 있게 함
  • TCP의 안정성을 필요로 하지 않는 애플리케이션의 경우 일반적으로 TCP 대신 비접속형 사용자 데이터그램 프로토콜(UDP)을 사용
  • TCP는 UDP보다 안전하지만 느림

TCP의 구조

안전한 연결을 지향하는 TCP 프로토콜

  • window: 데이터를 얼마나 더 보내야할 지 알려줌


TCP 플래그

TCP 플래그의 종류

어떤 플래그를 셋팅하고 보내느냐에 따라서 연결 유형이 달라짐

TCP의 주된 기능이 플래그에 따라 나뉘어 짐

  • U(Urgent Flag): 보내는 데이터에 급한 데이터가 있음
    • 밑의 Urgent Pointer 와 셋트
  • A(Acknowledgement Flag): 승인을 해주는 플래그
  • P(Push Flag): TCP 버퍼에 관계 없이 계속해서 데이터를 밀어넣음
  • R(Reset Flag): 연결을 새로고침
  • S(Synchronization Flag): 상대방과 연결을 시작할 때 무조건 사용, 보내진 다음부터 연결 동기화 시작 (가장 중요!)
  • F(Finish Flag): 종료


TCP를 이용한 통신과정

연결 수립 과정

TCP를 이용한 데이터 통신을 할 때, 프로세스와 프로세를 연결하기 위해 가장 먼저 수행되는 과정

  1. 클라이언트가 서버에게 요청 패킷을 보냄
  2. 서버가 클라이언트 요청을 받아들이는 패킷을 보냄
  3. 클라이언트는 이를 최종적으로 수락하는 패킷을 보냄

3개 과정을 3 Way Handshake 라고 함

데이터 송수신 과정

TCP를 이용한 데이터 통신을 할 때, 단순히 TCP 패킷만을 캡슐화해서 통신하는 것이 아닌 페이로드를 포함한 패킷을 주고 받을 때의 일정한 규칙

  • 보낸 쪽에서 또 보낼 때에는 SEQ 번호와 ACK 번호가 그대로
  • 받는 쪽에서 SEQ 번호 =  받은 ACK 번호
  • 받는 쪽에서 ACK 번호 = 받은 SEQ번호 + 데이터의 크기  

위 handshake에서 이어짐


TCP 상태전이도

  • 실선: 클라이언트의 상태 변화
  • 점선: 서버의 상태 변화
  • Listen
    • 포트 번호를 열어놓고 있는 상태
    • 서버가 프로그램을 사용하고 있는 상태
    • 서버가 요청을 듣고 있는 상태
  • Established
    • 연결이 수립된 상태

TCP 상태 변화

참고자료

https://www.youtube.com/watch?v=cOK_f9_k_O0&list=PL0d8NnikouEWcF1jJueLdjRIC4HsUlULi&index=21

위 강의를 보고 정리한 글입니다.

 

UDP 프로토콜

UDP가 하는 일

  • 사용자 데이터그램 프로토콜 (User Datagram Protocol, UDP)은 유니버셜 데이터그램 프로토콜 (Universal Datagram Protocol)이라고 일컫기도 함
  • 전송 방식은 너무 단순해서 서비스의 신뢰성 낮음, 데이터 도착 순서가 바뀌거나, 중복되거나, 통보 없이 누락됨
  • 일반적으로 오류의 검사와 수정이 필요 없는 프로그램에서 수행할 것으로 가정

UDP 구조


UDP 프로토콜을 사용하는 프로그램

DNS 서버

도메인을 물으면 IP 을 알려줌

tftp 서버

UDP로 파일 공유

RIP 프로토콜

라이팅 정보를 공유


실습

tftpd를 사용하여 데이터 공유

tftpd64 다운로드

해당 프로그램 실행

 

호스트: 공유할 경로 설정 및 IP 주소를 선택

클라이언트: Tftp Client 탭으로 이동해서 Host 및 Port 설정

Tftp는 well-known이라서 포트가 69 고정

Get 버튼을 누르면 해당 파일을 호스트로부터 받게 됨

 


참고자료

https://www.youtube.com/watch?v=3MkI3FBFzX8&list=PL0d8NnikouEWcF1jJueLdjRIC4HsUlULi&index=19

위 강의를 보고 정리한 글입니다.

 

영화 정보 api

 

API Documentation - YTS YIFY

Official YTS YIFY API documentation. YTS offers free API - an easy way to access the YIFY movies details.

yts.mx

아래 조건을 사용한 api를 사용함

https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year

import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovie = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    );
    const json = await response.json();
    // 아래 같이 한줄로 작성해도 됨
    // const json = await (
    //   await fetch(
    //     "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    //   )
    // ).json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <div key={movie.id}>
              <h2>{movie.title}</h2>
              <img src={movie.medium_cover_image}></img>
              <p>{movie.summary}</p>
              <ul>
                {/* hasOwnProperty를 사용하여 movie 내에 genres 프로퍼티가 있는지 없는지 확인할 수 있음 */}
                {movie.hasOwnProperty("genres")
                  ? movie.genres.map((genre) => <li key={genre}>{genre}</li>)
                  : null}
              </ul>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

컴포넌트 분리

Movie.js 파일 생성
// Movie.js
function Movie({ title, coverImg, summary, genres }) {
  return (
    <div>
      <h2>{title}</h2>
      <img src={coverImg} alt={title}></img>
      <p>{summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

export default Movie;
App.js에서 Movie 컴포넌트 사용
// App.js
import { useEffect, useState } from "react";
import Movie from "./Movie";

function App() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovie = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    );
    const json = await response.json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              title={movie.title}
              coverImg={movie.medium_cover_image}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
}

export default App;

입력한 props의 타입 검증을 위한 PropTypes 적용

// Movie.js
import PropTypes from "prop-types";

Movie.propTypes = {
  title: PropTypes.string.isRequired,
  coverImg: PropTypes.string.isRequired,
  summary: PropTypes.string,
  genres: PropTypes.arrayOf(PropTypes.string),
};

React Router

5.3.0 버전을 사용
react router 설치
npm i react-router-dom@5.3.0

이제 스크린 (Router) 단위로 생각

App.js는 route를 받아서 스크린을 불러옴

스크린(Router) 단위로 파일 생성

src 경로 안의 파일 정리

파일 경로 새로 생성

  • components/Movie.js (경로를 옮겼기 때문에 import의 경로 수정)
  • routes/Home.js
  • routes/Detail.js
기존 App.js에 있던 내용을 Home.js로 옮겨줌
// Home.js
import { useEffect, useState } from "react";
import Movie from "./components/Movie";

function Home() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovie = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    );
    const json = await response.json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              title={movie.title}
              coverImg={movie.medium_cover_image}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
}

export default Home;
App에서 router 적용
// App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./routes/Home";
import Movie from "./routes/Detail";

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/movie">
          <Movie />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
    </Router>
  );
}

export default App;
  • 위 코드에서 "/moive" 가 "/" Route보다 아래에 있으면 "/movie" 주소를 입력해도 "/" Route가 먼저 render된다. 위의 코드처럼 router의 위치를 조종하거나 exact={true}를 적용
  • Switch 태그는 Route를 한 번에 하나만 render할 때 사용

페이지 이동

<a> 태그 사용하기

  • 사용 가능
  • 다만 해당 페이지로 이동했을 경우 전체 페이지가 다시 rendering
<h2>
  <a href="/movie">{title}</a>
</h2>

<Link> 사용하기

  • 브라우저의 새로고침 없이 다른 페이지로 이동시켜주는 컴포넌트
// Movie.js
import { Link } from "react-router-dom";

<h2>
  <Link to="/movie">{title}</Link>
</h2>;

Dynamic URL

  • 변화하는 주소를 받을 수 있음
  • 변화하는 부분에 ":" 를 사용
// App.js
<Route path="/movie/:id">
  <Detail />
</Route>
id 값을 사용하기 위해서 props에서 id 전달
// Home.js
{
  movies.map((movie) => (
    <Movie
      key={movie.id}
      id={movie.id}
      title={movie.title}
      coverImg={movie.medium_cover_image}
      summary={movie.summary}
      genres={movie.genres}
    />
  ));
}
전달 받은 props 값 Link에 적용
// Movie.js
function Movie({ title, id, coverImg, summary, genres }) {
  return (
    <div>
      <h2>
        <Link to={`/movie/${id}`}>{title}</Link>
      </h2>
      <img src={coverImg} alt={title}></img>
      <p>{summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

useParams

url에 있는 값을 반환해주는 함수

// Detail.js
import { useParams } from "react-router-dom";

function Detail() {
  const x = useParams();
  console.log(x);
  // {id: '53703'}
  // id: "53703"
  // [[Prototype]]: Object
  return <h1>Detail</h1>;
}

export default Detail;

Detail.js 내에서 api 받아오기

// Detail.js
import { useParams } from "react-router-dom";
import { useEffect } from "react";

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();
    // console.log(json);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return <h1>Detail</h1>;
}

export default Detail;

최종 코드

// App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./routes/Home";
import Detail from "./routes/Detail";

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/movie/:id">
          <Detail />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
    </Router>
  );
}

export default App;

 

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

function Detail() {
  const { id } = useParams();
  const [loading, setLoading] = useState(true);
  const [movie, setMovie] = useState("");
  const getMovie = async () => {
    const json = await (
      await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
    ).json();
    // console.log(json);
    setLoading(false);
    setMovie(json.data.movie);
  };
  useEffect(() => {
    getMovie();
  }, []);

  return (
    <div>
      <h1>Detail</h1>
      {loading ? (
        <h1>loading</h1>
      ) : (
        <MovieDetail
          date_uploaded={movie.date_uploaded}
          download_count={movie.download_count}
          genres={movie.genres}
          language={movie.language}
          like_count={movie.like_count}
          large_cover_image={movie.large_cover_image}
          rating={movie.rating}
          runtime={movie.runtime}
          title={movie.title}
          torrents={movie.torrents}
          year={movie.year}
          description={movie.description_full}
        />
      )}
    </div>
  );
}

export default Detail;
// MovieDetail.js
import PropTypes from "prop-types";

function MovieDetail({
  date_uploaded,
  download_count,
  genres,
  language,
  like_count,
  large_cover_image,
  rating,
  runtime,
  title,
  year,
  description,
  torrents,
}) {
  return (
    <div>
      <h2>{title}</h2>
      <img src={large_cover_image} alt={title}></img>
      <p>Release: {year}</p>
      <p>Language: {language}</p>
      <p>Runtime: {runtime}</p>
      <p>Rating: {rating}</p>
      <p>Like Count: {like_count}</p>
      <p>Genre</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
      <p>{description}</p>
      <hr />
      <h3>Download info</h3>
      <p>File Uploaded: {date_uploaded}</p>
      <p>Download Count: {download_count}</p>
      <ul>
        {torrents.map((torrent, i) => (
          <li key={torrent.hash}>
            <a href={torrent.url}>다운로드 링크 {i + 1}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default MovieDetail;
// Home.js

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

function Home() {
  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovie = async () => {
    const response = await fetch(
      "https://yts.mx/api/v2/list_movies.json?minimum_rating=8.5&sort_by=year"
    );
    const json = await response.json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovie();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              id={movie.id}
              title={movie.title}
              coverImg={movie.medium_cover_image}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
}

export default Home;
// Movie.js

import PropTypes from "prop-types";
import { Link } from "react-router-dom";

function Movie({ title, id, coverImg, summary, genres }) {
  return (
    <div>
      <h2>
        <Link to={`/movie/${id}`}>{title}</Link>
      </h2>
      <img src={coverImg} alt={title}></img>
      {/* summary 길이에 따른 처리 */}
      <p>{summary.length > 235 ? `${summary.slice(0, 235)} ...` : summary}</p>
      <ul>
        {genres.map((genre) => (
          <li key={genre}>{genre}</li>
        ))}
      </ul>
    </div>
  );
}

Movie.propTypes = {
  id: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  coverImg: PropTypes.string.isRequired,
  summary: PropTypes.string,
  genres: PropTypes.arrayOf(PropTypes.string),
};

export default Movie;

'React' 카테고리의 다른 글

[React] TypeScript  (0) 2023.08.17
[React] styled-components  (0) 2023.08.16
[React 기초] 07. 예제 - Coin tracker  (0) 2023.08.10
[React 기초] 06. 예제 - Todo List  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10

코인 파프리카 api

해당 주소를 fetch하여 api 요청하고,

then, 응답으로 온 response를 json으로 변환하고,

then, json으로 변환된 데이터를 console.log 해보기

위 과정은 처음 랜더링할 때 한 번만 하면 되므로 useEffect(funciton, []) 적용

useEffect(() => {
  fetch("https://api.coinpaprika.com/v1/tickers")
    .then((response) => response.json())
    .then((json) => console.log(json));
}, []);

위 코드는 async와 await을 사용한 코드로 사용할 수 있음

const getCoin = async () => {
  const response = await fetch(
    "https://api.coinpaprika.com/v1/tickers"
  );
  const json = await response.json();
  console.log(json)
};
useEffect(() => {
  getCoin();
}, []);

Coin tracker ver.1

현재 코인 시세를 리스트로 출력

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);
      });
  }, []);
  const today = new Date();
  return (
    <div>
      <h1>The Coins! - {today.toLocaleString()}</h1>
      {loading ? <strong>Loading...</strong> : null}
      <ul>
        {coins.map((item) => (
          <li key={item.id}>
            {item.name}({item.symbol}) : $ {item.quotes.USD.price}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Coin tracker ver.2

현재 가지고 있는 달러를 입력 받고, 선택한 코인 종류에 따라서 환전량을 보여줄 수 있도록 개선

select를 선택할 때마다 state 값을 변경

option에 있는 text를 가져올 수 있는 방법을 찾지 못해 value에 시세, 심볼을 ","로 이어서 받은 뒤, split(",")을 사용하여 분리하는 방법을 채택

import { useEffect, useState } from "react";

function App() {
  const [loading, setLoading] = useState(true);
  const [coins, setCoins] = useState([]);
  const [cash, setCash] = useState(0);
  const [currentCoin, setCurrentCoin] = useState(false);
  const onChangeCash = (event) => {
    setCash(event.target.value);
  };
  const onChangeCoin = (event) => {
    setCurrentCoin(event.target.value);
  };
  useEffect(() => {
    fetch("https://api.coinpaprika.com/v1/tickers")
      .then((response) => response.json())
      .then((json) => {
        setCoins(json);
        setLoading(false);
      });
  }, []);
  const today = new Date();
  return (
    <div>
      <h1>The Coins! - {today.toLocaleString()} 기준</h1>
      {loading ? (
        <strong>Loading...</strong>
      ) : (
        <div>
          <label htmlFor="cash">I have $</label>
          <input
            id="cash"
            value={cash}
            onChange={onChangeCash}
            type="number"
          ></input>
          <br />
          <label htmlFor="coin">Exchange to </label>
          <select id="coin" onChange={onChangeCoin}>
            {coins.map((item) => (
              <option
                key={item.id}
                value={[item.quotes.USD.price, item.symbol]}
              >
                {item.name}({item.symbol})
              </option>
            ))}
          </select>
          <br />
          {currentCoin ? (
            <strong>
              You can buy{" "}
              <span>{cash / Number(currentCoin.split(",")[0])}</span>{" "}
              <span>{currentCoin.split(",")[1]}</span>
            </strong>
          ) : (
            <strong>Please select coin</strong>
          )}
        </div>
      )}
    </div>
  );
}

export default App;

'React' 카테고리의 다른 글

[React] styled-components  (0) 2023.08.16
[React 기초] 08. 예제 - Movie info  (0) 2023.08.10
[React 기초] 06. 예제 - Todo List  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10
[React 기초] 04. create-react-app  (0) 2023.08.10

JS 문법 

전개구문

 

전개 구문 - JavaScript | MDN

전개 구문을 사용하면 배열이나 문자열과 같이 반복 가능한 문자를 0개 이상의 인수 (함수로 호출할 경우) 또는 요소 (배열 리터럴의 경우)로 확장하여, 0개 이상의 키-값의 쌍으로 객체로 확장시

developer.mozilla.org

전개 구문을 사용하여 기존 배열에서 새로운 배열을 쉽게 추가할 수 있음

const array = [1, 2, 3, 4];
const newElement = 5;
const newArray = [...array, newElement];

map

map 함수를 사용하여, Array에 있는 요소를 react element로 출력할 수 있음

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

Todo List

import { useEffect, 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;
    }
    // 아래와 같이 state를 직접적으로 수정하지 않는다
    // toDos.push()
    // state는 함수를 사용하여 값을 수정
    setToDos((currentArray) => [toDo, ...currentArray]);
    setToDo("");
  };
  return (
    <div>
      <h1>My To Dos ({toDos.length})</h1>
      <form onSubmit={onSubmit}>
        <input
          onChange={onChange}
          value={toDo}
          type="text"
          placeholder="write what you have to..."
        ></input>
        <button>Add to Do</button>
      </form>
      <hr />
      {toDos.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </div>
  );
}

export default App;

'React' 카테고리의 다른 글

[React 기초] 08. 예제 - Movie info  (0) 2023.08.10
[React 기초] 07. 예제 - Coin tracker  (0) 2023.08.10
[React 기초] 05. effects  (0) 2023.08.10
[React 기초] 04. create-react-app  (0) 2023.08.10
[React 기초] 03. Props  (0) 2023.08.02

+ Recent posts