Coder Social home page Coder Social logo

react-masterclass's People

Contributors

beecomci avatar dbwls94 avatar

Watchers

 avatar

react-masterclass's Issues

[React JS 마스터클래스] Chapter #2 Styled Components

📌 CSS를 적용하는 다양한 방법

1. global css

import "./styles.css";
  • styles.css 내부의 모든 내용이 적용한 모든 페이지에 적용됨

2. inline style

<div style={{backgroundColor: "red"}}></div>
  • 가독성 떨어짐
  • hover 등의 psuedo selector 불가능

3. module css

import styles from "./Movie.module.css";

<div className={styles.title}></div>
  • Movie.module.css의 title class를 styles 객체의 속성 중 하나로 취급해 호출함
  • 컴포넌트별로 같은 class 명을 가져도 module화 되어있기 때문에 서로 겹칠일이 없음
  • className을 css에서 복붙해와야함
  • className만으로 다크모드/라이트모드 구현 번거로움
  • 한번에 2개 이상의 className 지정 불가능

📌 Styled Components

import styled from "styled-components";

const Father = styled.div`
  width: 100px;
  height: 100px;
  background-color: red;
`;

<Father />

image

  • css 코드를 css 방식 그대로 사용 가능
  • div 요소 이름 대신 하나의 컴포넌트로서 생성해서 취급
  • class명을 styled 컴포넌트가 알아서 랜덤하게 생성해줌

props

  • 컴포넌트를 설정 변경 가능하고 확장 가능하도록 하기 위해서 컴포넌트에 데이터를 보내는 방식인 props 사용
  • css 속성값에 함수로 작성

1. 설정 변경 가능한 컴포넌트

// AS-IS
// BoxOne과 BoxTwo는 background-color 속성값만 다름
const BoxOne = styled.div`
  background-color: teal;
  width: 100px;
  height: 100px;
`;
const BoxTwo = styled.div`
  background-color: tomato;
  width: 100px;
  height: 100px;
`;
// To-BE
const Box = styled.div`
  background-color: ${props => props.bgColor};
  width: 100px;
  height: 100px;
`;

<Box bgColor="teal"/>
<Box bgColor="tomato"/>

image

  • Box에 전달하는 props명과 Box 컴포넌트에서 사용하는 props의 속성명은 동일해야 함
  • Styled 컴포넌트에 props를 보내면 알아서 알맞게 class명을 각각 생성함

2. 확장 가능한 컴포넌트

// AS-IS
// Box와 Circle은 border-radius 이외의 css가 모두 동일해서 중복되는 코드 
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: 50px;
`;
  • Box가 가진 모든 속성들에 Circle만 가진 border-radius만 추가해서 확장해서 사용하고 싶다
// To-BE
const Box = styled.div`
  background-color: ${props => props.bgColor};
  width: 100px;
  height: 100px;
`;
const Circle = styled(Box)`
  border-radius: 50px;
`;
  • 기존 컴포넌트의 모든 속성을 가져오고 새로운 속성까지 더해주는 확장 가능한 컴포넌트
  • Box를 확장해서 Circle 컴포넌트를 생성

as

  • 컴포넌트의 요소는 바꾸고 싶은데 스타일을 바꾸고 싶지 않을 때는 props 대신 as 사용
// button 요소가 아니라 모종의 이유로 a 요소지만 모든 스타일은 같도록 사용하고 싶다면 ? 
const Btn = styled.button`
  color: white;
  background-color: tomato;
  border-radius: 15px;
  border: 0;
`;

// Link 컴포넌트로 확장해서 만들어도 button 요소는 그대로며 같은 스타일을 사용하고 싶은거지 확장하고 싶은게 아님
const Link = styled(Btn)``;
<Btn>Log in</Btn>
<Btn as="a" href="#">Log in</Btn>

image

  • Btn 컴포넌트의 button 요소만 변경하기 위해서 as 사용
  • as에 변경하고자 하는 요소 전달

attrs

// AS-IS
<Input required />
  • Styled 컴포넌트에서 html 속성 정의 가능
  • 같은 컴포넌트가 반복되어서 사용되는데, 각각에 html 속성을 동일하게 써줘야 할 때 아예 컴포넌트 정의시에 attrs로 정의 가능
// TO-BE
const Input = styled.input.attrs({ required: true })`
  background-color: yellow;
`;

<Input />
<Input />
<Input />

image

  • 모든 input 태그들이 별도로 html 속성을 각각 입력 할 필요 없이 required 속성을 가짐

애니메이션

const rotationAnimation = keyframes`
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
`;

const Box = styled.div`
  width: 200px;
  height: 200px;
  background-color: tomato;
  animation: ${rotationAnimation} 1s linear infinite;
`;
  • keyframes를 import해주고 사용

selector

1. Styled 컴포넌트 내의 요소 select

// span 태그는 styled 컴포넌트가 아님, Box 컴포넌트 안에 위치해있을 뿐
<Box>
  <span>happy</span>
</Box>

// 그럼 Box 안에서 span을 select해서 사용하면 됨
// hover과 같은 psuedo selectors는 마치 sass처럼 &로 사용 가능 
const Box = styled.div`
  display: flex;
  width: 200px;
  height: 200px;
  background-color: tomato;
  justify-content: center;
  align-items: center;
  animation: ${rotationAnimation} 1s linear infinite;

  span {
    font-size: 30px;

    &:hover {
      color: yellow;
    }
  }
`;
  • 꼭 모든 컴포넌트를 styled 컴포넌트 처리해줄 필요는 없음
  • 위처럼 styled 컴포넌트 안에서 target을 정해서 css 처리해줘도 됨

2. Styled 컴포넌트 내의 Styled 컴포넌트 select

const Text = styled.span`
  font-size: 30px;
`;

const Box = styled.div`
  display: flex;
  width: 200px;
  height: 200px;
  background-color: tomato;
  justify-content: center;
  align-items: center;
  animation: ${rotationAnimation} 1s linear infinite;

  ${Text} {
    &:hover {
      color: yellow;
    }
  }
`;

<Text as="p">happy</Text>
  • 1번 방법처럼 하면, span 태그가 다른 태그로 변경되었을 때 css는 태그명에 의존하고 있으므로 selector도 변경된 태그로 같이 변경해줘야 함
  • styled 컴포넌트 내부 요소 자체를 하나의 styled 컴포넌트로 생성하는 방법으로 적용

📌 Themes

  • theme : 모든 색을 가진 object
  • 나중에 색을 바꿀 때 컴포넌트를 하나하나 변경하는게 아니라 그냥 이 object의 색을 변경하면 됨
// index.js
import { ThemeProvider } from "styled-components";

// darkmode & lightmode 스위칭을 위해 속성명 동일하게 맞춤
// 그래야 theme을 사용하는 하위 styled 컴포넌트에서 모드 변경시 속성명까지 변경할 일이 없음 
const darkTheme = {
  textColor: "whitesmoke",
  backgroundColor: "#111"
};

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

ReactDOM.render(
  <React.StrictMode>
    <ThemeProvider theme={darkTheme}>
      <App />
    </ThemeProvider>
  </React.StrictMode>,
  document.getElementById("root")
);
// App.js
const Wrapper = styled.div`
  display: flex;
  height: 100vh;
  width: 100vw;
  justify-content: center;
  align-items: center;
  background-color: ${props => props.theme.backgroundColor};
`;

const Title = styled.h1`
  color: ${props => props.theme.textColor};
`;
  • ThemeProvider로 감싸진 모든 컴포넌트에서 props로 theme 접근 가능
  • App에서 사용할 때 직접적으로 theme 값을 갖고 있지 않고 theme에 대한 reference만 갖고 있음� -> 컬러값 변경시 theme을 관리하는 index에서만 변경해주면 됨

[React JS 마스터클래스] Chapter #3 Typescript

📌 Why use Typescript ?

  • Typescript : Javascript 베이스 언어이며 strong-typed 언어

stronly-typed 언어 : 언어가 작동하기 전에 type을 확인

// AS-IS
const plus = (a, b) => a + b;

plus(2, "hi"); // '2hi' 

// TO-BE
const plus = (a:number, b:number) => a + b;

plus(2, "hi"); // 오류 발생 
  • Javascript는 데이터 타입을 1도 신경쓰지 않음
  • 결과를 내뱉기전에 이런 결과가 나오기전에 a와 b는 number type이라는걸 Javascript에게 알려줘야해 -> Typescript
const user = {
  firstName: "Youjin",
  lastName: "park"
};

console.log(user.name); // undefined 
  • user가 갖고 있지 않은 name을 호출해서 Javascript는 그저 undefined를 반환할 뿐임, 지 할일만 함
  • Typescript를 사용하면 프로그램을 돌리기도 전에 name 속성은 해당 type에 존재하지 않는다는 오류로 미리 알려줄 수 있음
  • 이렇게 Typescript는 하나의 안전장치가 되어줄 수 있음
// Typescript (Before Compile)
const plus = (a:number, b:number) => a + b;

// Javascript (After Compile)
const plus = (a, b) => a + b;
  • 브라우저는 Javascript 언어만을 이해 가능
  • 브라우저가 Typescript를 읽기 위해서 컴파일해서 우리가 아는 normal Javascript로 변환
  • 고로 Typescript의 안전장치는 코드가 동작하기 에 발생한다는 것 !!!

📌 Install (on CRA project)

1. typescript용 cra 생성

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

# or

yarn create react-app my-app --template typescript

2. 이미 javascript로 생성된 cra에 typescript 설치

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

# or

yarn add typescript @types/node @types/react @types/react-dom @types/jest
  • 위처럼 typescript 설치 후,
  • App.jsindex.js 등 모든 js 파일 확장자를 .tsx로 변환 (React + Typescript는 tsx, Typescript는 ts)
  • src 하위에 react-app-env.d.ts 파일 생성 후 아래와 같이 작성
/// <reference types="react-scripts" />
  • tsconfig.json 파일 생성
  • Javascript 기반으로 만들어진 패키지는 Typescript용 패키지로 변경해서 설치
    • Typescript 환경에서 Javascript 기반 패키지를 가져와서 사용하면 Typescript는 이 코드가 뭔지 몰라서 오류 발생
    • @types : 패키지의 type definition을 알려줌 (원래 패키지를 보고 Typescript에게 해줄 설명 모음집..?)
    • @types 네임스페이스는 DefinitelyTyped 커뮤니티의 types로부터 파생 (여기에 없다면 직접 만들어야함)
    • @types/styled-components는 styled-components의 type 정보를 알려주는 패키지
    • Typescript에게 내가 사용할 패키지의 코드가 뭐가 뭔지 알려주기 위함

📌 Typing the Props

  • 컴포넌트가 필요로 하는 prop을 Typescript에게 설명하는 법
  • 기존에 사용하던 propTypes는 브라우저에 콘솔로 경고 표시를 해줌, prop이 거기에 있는지 확인해주지만 코드가 실행된 에야 확인 가능
  • Typescript로는 의도한 prop이 제대로 전달되었는지 코드가 실행되기 에 확인 가능
// App.tsx
import Circle from "./Circle";

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

// Circle.tsx
// 예제에서는 Container와 Circle이 다른 종류의 props를 사용하게될거라는 가정 하에 같은 종류의 interface지만 2개를 각각 생성함
interface ContainerProps {
  bgColor: string;
}

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

interface CircleProps {
  bgColor: string;
}

function Circle({ bgColor }: CircleProps) {
  return <Container bgColor={bgColor} />;
}
  • interface : object가 어떻게 보일지 알려주는 설명서
  • Circle 컴포넌트에 bgColor props를 보냄
    • Typescript : Circle이 받는 bgColor가 뭐야?!!?!?!? 나 이런거 모름~~
    • 👩🏻‍💻 : interface로 CircleProps라는 네이밍으로(자유) bgColor의 타입을 정의해줄게
  • Circle은 bgColor를 받아서 styled 컴포넌트인 Container에 보냄
    • Typescript : Container는 걍 div인데 이 bgColor는 또 뭐야?!?!? 나 얘도 모름~~
    • 👩🏻‍💻 : Container의 bgColor prop의 타입도 정의해줄게
interface PlayerShape {
  name: string;
  age: string;
}

const hello = (playerObj: PlayerShape) => `Hello ${playerObj.name} you are ${playerObj.age} years old.`;
hello({ name: "youjin", age: "20" });
hello({ name: "youjin", age: 20 }); // age는 string이어야함 오류
hello({ name: "youjin", age: "20", hello: 1 }); // hello는 없는 속성 오류
  • interface를 정의함으로써 전달받는 playerObj 객체의 shape의 설명서를 Typescript에게 전달 -> Typescript는 코드가 실행되기전에 이 설명서를 보고 제대로 전달되는건지 확인
  • 위 Circle에서는 전달받는 props 객체의 shape를 설명

📌 Optional Props

  • 위에서처럼만 작성하면 모든 props가 required이기 때문에 넘겨주지 않는 경우에는 오류 발생함
// App.tsx
function App() {
  return (
    <div>
      <Circle bgColor="teal" borderColor="red" />
      <Circle bgColor="tomato" text="hi" />
    </div>
  );
}

// Circle.tsx
interface ContainerProps {
  bgColor: string;
  borderColor?: string;
}

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

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

function Circle({ bgColor, borderColor, text = "default text" }: CircleProps) {
  return <Container bgColor={bgColor} borderColor={borderColor ?? bgColor}>{text}</Container>;
}
  • borderColor를 optional한 props로 설정하고자 함 -> borderColor?: string
  • Circle이 받는 borderColor props는 optional함
  • 그럼 Container가 이어받는 bgColor props도 optional하게 설정해줘야 함
  • default 값도 fallback 설정 가능 -> borderColor={borderColor ?? bgColor} : borderColor가 없으면 bgColor로 default 설정
    • 여기서 bgColor는 항상 string이기 때문에 default 값으로 설정 가능
    • 아니면 Circle에서 props를 받을 때 아예 default 값 설정 가능 (ES6 특성 사용)

📌 State

const [counter, setCounter] = useState(1);
  • Typescript는 useState의 default값을 보고 number 타입이 들어올거라는걸 예측함
  • 그래서 Typescript를 따로 사용하지않아도 이외의 타입 값이 들어오면 오류 발생
  • default값이 없으면 Typescript가 추론 할 수 없어서 undefined 타입
const [counter, setCounter] = useState<number|string>(1);
setCounter(2);
setCounter("hello");
setCounter(true); // Boolean 타입 오류 발생
  • state는 보통 1개의 타입으로 계속 사용되지만, 종종 여러 타입이 사용되는 경우는 위와 같이 사용 (but state 타입이 바뀌는 경우는 드뭄)

📌 Forms

function App() {
  const [value, setValue] = useState("");

  // 어떤 종류의 element가 이 onChange 이벤트를 발생시킬지 특정 가능
  // Typescript는 이 onChange 함수가 InputElement에 의해 실행될 것을 알 수 있음
  const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value }
    } = event;

    // Typescript는 onChange 이벤트가 type이 text인 input에 의해서 생성되었으며
    // currentTarget의 value가 string임을 알고 있음
    setValue(value);
  };

  // event: React.{어떤 이벤트냐}<{어떤 element가 이 이벤트를 발생시키냐}>
  // 타입 시스템마다 달라서 React 이외의 다른 라이브러리를 사용한다면 방식이 달라짐 참고 
  const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault(); // Typescript에게 event가 뭔지 설명해줌으로써 preventDefault가 함수란걸 앎 (만약 event 설명이 없다면 문법 오류가 나도 어디서 나는지 모름)
    console.log(value);
  };

  return (
    <div>
      <form onSubmit={onSubmit}>
        <input
          type="text"
          value={value}
          onChange={onChange}
          placeholder="username"
        />
        <button>Log in</button>
      </form>
    </div>
  );
}
  • any : Typesscript의 타입으로 무엇이든 될 수 있는 타입
  • any는 기본적으로 Javascript가 하던 어떤 타입이던 상관없어하는 행동과 동일하기 때문에 Typescript를 사용한다면 any타입을 지양하도록 노력해야 함
  • 어디에 어떤 타입이 적당한지는 잘 모르니 항상 구글링해서 적당한 타입을 찾을 것 -> React SyntheticEvent 참고

📌 Themes

1. declarations file 생성

  • 앞서서 @type/styled-components를 설치했으니 이제 declarations file 생성 (ex. ***.d.ts)
  • styled.d.ts 파일 생성함으로써 styled-components 내에 기존에 있던 index.d.ts 파일을 override -> 우리 테마에 사용할 타입들을 재정의하기 위해서
// src/styled.d.ts
// import original module declarations
import "styled-components";

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

2. theme.ts

  • 사용할 lightTheme와 darkTheme를 위에서 interface로 타입을 정의해둔 DefaultTheme를 가져와서 정의 및 export
import { DefaultTheme } from "styled-components";

// 앞서 styled.d.ts에서 생성한 DefaultTheme interface를 정의해둬서
// light와 dark 모드에서 속성을 하나라도 빠뜨리지 않고 작성 가능
export const lightTheme: DefaultTheme = {
  bgColor: "white",
  textColor: "black"
};

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

3. App을 ThemeProvider로 감싸기

// index.ts
import { ThemeProvider } from "styled-components";
import { lightTheme } from "./theme";

ReactDOM.render(
  <React.StrictMode>
    <ThemeProvider theme={lightTheme}>
      <App />
    </ThemeProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

4. App에서 이제 theme.ts에 정의해둔 lightTheme or darkTheme 사용

// App.ts
import styled from "styled-components";

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

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

  return (
    <div>
      <Container>
        <H1>Hello</H1>
      </Container>
    </div>
  );
}

github-pages publish시 빈 화면으로 노출됨

이슈

  • github-pages로 작업물 publish시 빈 화면으로 노출됨

원인

  • deploy뒤에 BrowserRouter가 가리키는 / 경로는 https://beecomci.github.io 뒤에 오는 / 의 경로
  • 하지만 내 프로젝트 경로는 https://beecomci.github.io/react-masterclass이기 때문에 빈 화면으로 노출됨

해결 방안

<BrowserRouter basename={process.env.PUBLIC_URL}>
  • BrowserRouter에 basename props 설정
  • 여기서 PUBLIC_URL은 package.json의 homepage URL으로 설정됨

[React JS 마스터클래스] Chapter #4 CRYPTO TRACKER

📌 react-query

  • react-query : 편한 방식으로 데이터를 fetch 할 수 있는 패키지

Setup

  • react-router-dom & react-query install
  • react-router-dom : 어플리케이션이 URL을 가져서 각기 다른 화면을 갖게 해줌
  • 추가로 @types/react-router-dom 설치

react-router-dom

  • 앞선 react 강의에서 다뤘던 부분
  • 강의에서는 5.x.x대 버전을 사용하지만 난 6.x.x 버전 사용 중
  • 6.x.x 버전 부터는 Switch 대신 Routes로 사용
    • 한번에 하나의 Route를 렌더링 할 수 있도록 함

📌 Styles

Reset Style

1. styled-reset

2. createGlobalStyle

import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  body {
    color: red;
  }
`;

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

image

  • styled 컴포넌트로 만든 스타일은 지정된/사용되는 요소에만 국한됨
  • createGlobalStyle은 렌더링 될 때 전역 스코프에 스타일을 올려줘서 global한 style을 가지게 됨
  • document의 head에 style 주입

Fragment

  • 일종의 유령 컴포넌트로 기존에 React에서 1개의 element만 return 되어야 해서 사용하지도 않는 쓸데없는 빈 div를 마구잡이로 썼다면
  • Fragment로 대체 가능

Font

Theme

  • 이제 내 앱에서 필요한 theme color 주입

📌 Coins Home

  • API에서 데이러를 가져와도 똑같이 Typescript에게 뭐가 오는지 알려줘야 함
  • 다른 페이지(ex. Coin Detail)에서 다시 돌아왔을 때도 Loading되는 이슈는 추후 react-query에서 수정 예정

API fetch

// 1. normal
const getCoins = async () => {
    const json = await (
      await fetch("https://api.coinpaprika.com/v1/coins")
    ).json();
};

useEffect(() => getCoins(), []);
// 2. IIFE로 굳이 getCoins 함수를 또 만들 필요 없이 즉시 실행
useEffect(() => {
    (async () => {
      const json = await (
        await fetch("https://api.coinpaprika.com/v1/coins")
      ).json();
    })();
}, []);

📌 Coin Detail - Route States

현재까지 개발된 상황

1. Coins Home 페이지에서 Loading 문구가 최초로 노출됨
2. 그 사이에 API request가 종료되면 코인 리스트를 가져올 수 있게 되고 Loading 문구는 사라지고 코인 리스트 UI 노출
3. 특정 코인을 클릭하면 Coin Detail 페이지로 이동 (ex. /btc)
4. 페이지 이동 후, 코인 name 제목이 여전히 특정 코인 데이터를 가져오는 동안 Loading 문구 노출됨
  • Coin Detail 페이지에서 코인 name 제목은 나도 아는 데이터임에도 불구하고 API가 줄 때까지 사용자는 로딩 문구를 봐야 함 -> 좋은 UX가 아님

비하인드 더 씬 데이터

  • 우리는 그동안 한 화면에서 다른 화면으로 데이터를 전송할 때 URL Parameter를 보내는 방식을 사용해 왔음
  • 하지만 위와 같은 이슈가 있어서 state를 사용해보자
// Coins.tsx
// react-router-dom v6점대 이상부터 아래처럼 사용
<Link to={`/${coin.id}`} state={{ name: coin.name }}>

// Coin.tsx
// Link에서 state로 넘겼던 coin.name을 Coin에서 useLoaction을 사용해서 get 
// react-router-dom v6점대 이상부터 <> generic 미지원, 아래처럼 사용 가능 
interface RouteStates {
  state: {
    name: string;
  };
}

function Coin() {
  const {
      state: { name }
    } = useLocation() as RouteStates;

  return (
    <Container>
      <Header>
        <Title>{name}</Title> // 더이상 useParmas로 가져온 coinId가 아닌 coin의 name을 직접 뿌려줄 수 있음 
      </Header>
    </Container>
  );
}
  • state는 Coins Home 페이지를 열 때 & Coin Detail 페이지로 넘어갈 때 생성됨
  • state는 Coins Home 페이지에서 특정 코인을 클릭해서 이동할 때, Home에서 Detail 페이지로 보내짐

Uncaught TypeError: Cannot read properties of null (reading 'name')

  • 크롬 시크릿창에서 Coin Detail 페이지로 바로 접속하면 위와 같은 오류 발생
  • state가 생성되려면 Coins Home부터 열어야 그 state를 클릭시 Detail 페이지로 전송 가능

해결 방안

function Coin() {
  const location = useLocation() as RouteStates; // {... state: null}
  const name = location?.state?.name; // undefined 반환 

  return (
    <Container>
      <Header>
        <Title>{name  "Loading..."}</Title>
      </Header>
      {loading ? <Loader>Loading...</Loader> : null}
    </Container>
  );
}

📌 Coin Detail - Data Types

  • 보통 Interface는 네이밍을 맨 앞에 I를 붙임
  • API 데이터의 Interface 정의 시 미세먼지 팁
// 1. 아래 key 복사
temp1 = { API 데이터 };
Object.keys(temp1).join(); // 'id, name, symbol...'

// 2. VSCode에 붙여넣고 Command + D로 콤마 선택해서 삭제 후 엔터
// 전체 영역 선택 후, Command + Shift + i로 각 모든 커서를 받아서 공통 문자 :와 ; 입력

// 3. 아래 values의 type 복사 
Object.values(temp1).map(item => typeof item).join(); // 'string, string, boolean...'

// 4. 2번처럼 콤마 선택해서 삭제 후 엔터

// 5. 2번까지 마친 key를 전체 선택 후, Comand + Shift + i로 각 모든 커서가 나오면 4번 key 붙여넣기 

// 6. But array같은 경우 object로만 type 변환이 되어서 내부 속성들의 타입도 명시해줘야 함
interface ITag {
  coin_counter: number;
  id: string;
  ...
}

interface IInfoData {
  id: string;
  name: string;
  tag: ITag[];
  ...
}

📌 Coin Detail - Nested Routes

  • Coin.tsx에서 사용하는 API를 컴포넌트 실행 최초 1번만 받아오도록 하기 위한 useEffect의 2번째 param을 coinId로 넣어야 한다는 경고가 뜸
  • hooks의 성능 향상을 위해서 경고 문구
  • 사실 coinId를 넣으면 coinId가 변경될 때마다 useEffect 내의 함수가 실행되기 때문에 우리 의도와는 달라짐
  • But !! 여기서 coinId는 URL로부터 오는 값이기 때문에 절대 변경될 일이 없어서 빈 배열을 넣을 때와 동일하게 컴포넌트 최초 1번만 실행됨

Nested Route

  • route in route
  • 탭 or 화면 내에 많은 섹션 사용시 많이 사용됨
  • 탭들을 state에 넣는 대신 url로 관리해서 url로 다이렉트로 접근 가능하도록 할 때
    • /btc-bitcoin/chart : 차트 탭이 보이도록
  • Link를 사용해서 URL을 바꿈으로써 트리거가 되어 re-render 가능
// 1. Router.tsx
// v6점대부터 exact는 더이상 사용하지 않고 여려 라우팅을 매칭하고 싶은 경우 URL뒤에 /* 붙임 
<Route path="/:coinId/*" element={<Coin />}></Route>

// 2. Coin.tsx
// v6점대부터 상대 경로 지원 
<Routes>
  <Route path="/price" element={<Price />}></Route>
  <Route path="/chart" element={<Chart />}></Route>
</Routes>

useMatch hook

const Tab = styled.span<{ isActice: boolean }>`
  ...
  color: ${props =>
    props.isActice ? props.theme.accentColor : props.theme.textColor};
`;

function Coin() {
  const priceMatch = useMatch("/:coinId/price"); // 내가 이 url 안에 있냐
  const chartMatch = useMatch("/:coinId/chart");

  return (
  {/* chartMatch가 null이 아님 -> /:coinId/chart url에 들어와있다면 isActice는 true */}
    <Tabs>
      <Tab isActice={chartMatch !== null}>
        <Link to={`/${coinId}/chart`}>Chart</Link>
      </Tab>
      <Tab isActice={priceMatch !== null}>
        <Link to={`/${coinId}/price`}>Price</Link>
      </Tab>
     </Tabs>

     <Routes>
       <Route path="/price" element={<Price />}></Route>
       <Route path="/chart" element={<Chart />}></Route>
     </Routes>
  );
}
  • 내가 특정한 URL에 있는지 여부를 알려줌
  • v6점대에서는 useMatch, 이하 버전에서는 useRouteMath란 네이밍
  • 내가 선택한 URL에 들어가 있다면 아래와 같은 object를, 아니라면 null 반환
    image

📌 React Query

// index.tsx
// 초기 세팅
import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={theme}>
        <App />
      </ThemeProvider>
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

Why use react-query

// api.ts
// json data의 Promise 반환하는 함수 export
// 일반적으로 API fetch 관련된 함수는 컴포넌트에서 분리함
export function fetchCoins() {
  // fetch 후 response의 json return
  return fetch("https://api.coinpaprika.com/v1/coins").then(response =>
    response.json()
  );
}

// Coins.tsx
const { isLoading, data } = useQuery<ICoin[]>("allCoins", fetchCoins);
  • useQuery hook은 fetcher 함수를 호출하고 fetcher 함수가 loading 중이라면 react-query는 isLoading으로 로딩 여부를 알려줄것
  • fetcher 함수가 끝나면 fetcher 함수에서 반환된 json을 data에 넣음
  • 앞서 했던 isLoading state + useEffect로 API 호출 방법을 이 한줄이 대체 가능한 편리한 기능 ~~~
  • 앞서 했던 방법으로는 클릭해서 detail 페이지 이동 후, 되돌아오면 다시 로딩된 후 전체 목록을 받아왔다
  • 이젠 로딩이 보이지 않음 왜? -> react query가 data를 캐시해서 유지하고 있기 때문 !

Devtools

// App.tsx
import { ReactQueryDevtools } from "react-query/devtools";


function App() {
  return (
    <>
      <GlobalStyle />
      <Router />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  );
}

image

  • react query에서 Devtools 제공
  • 앱이 가진 모든 query 확인 가능, 캐시를 시각화해서 확인 할 수 있다.
  • 만든 allCoins 쿼리 확인 가능, 원하는 동작 트리거 가능 -> reqct-query가 query를 array로 보고 있음 ["allCoins"]
    • refetch : 데이터를 다시 fetch
    • reset : 쿼리 reset

Coin에 react-query 적용

// Coin.tsx
const { coinId } = useParams();
const location = useLocation() as RouteStates;
const name = location?.state?.name;
const priceMatch = useMatch("/:coinId/price"); // 내가 이 url 안에 있냐
const chartMatch = useMatch("/:coinId/chart");

// param1 : 고유한 값 coinId를 query key로 사용
// param2 : Coins에서는 인자가 필요없었으나 여기선 coinId를 알아야 하니까 (호출이 아닌 함수 자체로 넘겨줘야함 !)
// 그래서 fetchCoinInfo 함수를 호출해서 ciondId를 넣어주는 함수를 생성해서 전달

// react query는 query를 array로 봄
// 넘겨주는 key를 배열로 만들어서 같은 coinId를 사용하지 않도록 생성
const { isLoading: infoLoading, data: infoData } = useQuery<IInfoData>(
  ["info", coinId],
  () => fetchCoinInfo(coinId!)
);
const { isLoading: tickersLoading, data: tickersData } = useQuery<IPriceData>(
  ["tickers", coinId],
  () => fetchCoinTickers(coinId!)
);

// 2개 로딩이 끝나지 않으면 Loading 노출 
const loading = infoLoading || tickersLoading;

return (
  <Container>
    <Header>
      {/* state로부터 온 name이 있으면 -> name or 없으면 -> loading */}
      {/* 근데 loading이 true면 Loading 메세지를 or false면 API로부터 온 name  */}
      <Title>{name ? name : loading ? "Loading..." : infoData?.name}</Title>
    </Header>
    {loading ? (
      <Loader>Loading...</Loader>
    ) : ( ...
);
  • Coins에 적용한 방식과 동일
  • 이제 Bitcoin 클릭 후 다시 되돌아와서 다시 Bitcoin 클릭하면 Loading 보이지 않음 왜???? react-query가 data를 캐싱하고 있기 때문

📌 Price Chart

Apexchart.js

react-helmet

  • 컴포넌트로서 사용하며 컴포넌트에서 무엇을 render하던 문서의 head로 보여줌
  • react-helmet 사용시에 Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. 오류 발생
  • react-helmet-async로 HelmetProviderHelmet을 감싸서 사용
  • title 뿐만 아니라 favicon, css 등 추가 가능
import { Helmet, HelmetProvider } from "react-helmet-async";

<HelmetProvider>
  <Helmet>
    <title>{name ? name : loading ? "Loading..." : infoData?.name}</title>
  </Helmet>
</HelmetProvider>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.