min000 / react-for-master

0 stars 0 forks source link

[Practice] CRYPTO TRACKER #4

Open min000 opened 2 years ago

min000 commented 2 years ago

react-router-dom

설치

npm i react-router-dom@5.3.0

1. setup

Router.tsx

function Router(){ return (

// url 뒤에 오는 값을 :로 넘겨줌
);

} export default Router;


### /Routes/Coins.tsx
- Home 페이지
```javascript
function Coins(){
    return (
        <h1>coins</h1>
    );
}
export default Coins;

/Routes/Coin.tsx

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

interface RouteParams{
    coinId: string;
}
function Coin(){
    const {coinId} = useParams<RouteParams>();
    return (
        <h1>Coin : {coinId}</h1>
    );
}
export default Coin;

// 2. function Coin(){ const {coinId} = useParams<{coinId:string}>(); ...


### App.tsx
```javascript
import Router from './Router';

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

2. CSS setup

reset css

1. styled-reset

npm i styled-reset

import { Reset } from 'styled-reset'

const App = () => (
  <>
    <Reset />
    <div>Hi, I'm an app!</div>
  </>
)

2. createGlobalStyle

const GlobalStyle = createGlobalStyle body { line-height: 1; } ... // 초기화 속성값 선언 ;

function App(){ return ( <>

  <Router />
</>

); }

- 웹폰트 선언
```javascript
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400&display=swap');

body {
  font-family: 'Source Sans Pro', sans-serif;
}
... 
`;

theme props style

  • ThemeProvider 내부에 App이 존재함
    • App에서 props.theme 접근가능
      
      // index.tsx
      ...
      <ThemeProvider theme={theme}>
      <App/>
      </ThemeProvider>
      ...

// App.tsx const GlobalStyle = createGlobalStyle body { background-color:${(props) => props.theme.bgColor}; color:${(props) => props.theme.textColor} } ...

- App 내부에 Router 컴포넌트 존재 => Router 내부에 Coins 컴포넌트 존재
    - Coins에서 props.theme 접근가능 
```javascript
// Coins.tsx
import styled from 'styled-components';

function Coins(){
    const Title = styled.h1`
        color: ${props => props.theme.accentColor}
    `;
    return (
        <Title>coins</Title>
    );
}
export default Coins;

3. Home

  • /Routes/Coins.tsx

    styled Components

  • 컴포넌트를 스타일 컴포넌트로 정의
    
    import styled from "styled-components";

const Container = styled.div; const Header = styled.header; const CoinsList = styled.ul; const Coin = styled.li; const Title = styled.h1``;

function Coins() { return 코인; return (

코인

); } export default Coins;

### styled Data
- 데이터 가공
```javascript
<CoinsList>
    {coins.map((coin) => (
        <Coin key={coin.id}>{coin.name}</Coin>
     ))}
</CoinsList>
  • react는 a태그 대신 Link로 사용
    • Link는 Dom에서 a태그로 뱉음
    • Link tovalueRoute path에 들어가는 value
      
      // Router.tsx
      <Route path="/:coinId">

// route/coins.tsx <Link to={/${coin.id}}>{coin.name} →


- 자바스크립트문과 문자 함께에 사용
```javascript
{` / ${javaScript} `}
// /javaScript

Data API 연동

  • API Data를 TypeScript에 설명

    interface CoinInerface {
    id: string,
    name: string,
    symbol: string,
    rank: number,
    is_new: boolean,
    is_active: boolean,
    type: string,
    }
  • 컴포넌트 시작시 data fetch

    • useEffect 내에서 async await 사용
  • state로 데이터 전달 및 변경

    • 배열에서 전체 object가 아닌 몇 개의 object만 가져오고 싶을 때 => slice
      
      const [coins, setCoins] = useState<CoinInerface[]>([]);

    useEffect(() => { (async() => { const response = await fetch("https://api.coinpaprika.com/v1/coins"); const json = await response.json(); setCoins(json.slice(0,100)); setLoading(false); })(); },[]);

  • 즉시실행함수

    (( ) => console.log(1))( );

    4.Route State

  • 코인 icon 이미지를 제공

screen 이동시 데이터 전달하는 방법

1. parameter로 URL에 데이터를 넘김

// route/coins.tsx
<Link to={coin.id}>...</Link>

// route/coin.tsx
const {coinId} = useParams<RouteParams>();

// Router.tsx
<Route path="/:coinId">
       <Coin />
</Route>

2. state

  • 비하인드 씬 소통(보이지 않는 방식으로 데이터를 보내는 법)
    • coin.id : URL parameter
    • coin.name : state

2-1. state 전달하는 방법

  • Link to에서 object형태로 전달 가능함
    • url 정보와 state를 전달
// route/coins.tsx
<Link
    to={{
      pathname: `/${coin.id}`,
      state: { name: coin.name },
    }}
  >
...
</Link>

Link Component 옵션

  • string
        <Link to="/about"></Link>
  • query argument
        <Link to="/course?sort=name"></Link>
  • object : data 자체를 전달
        <Link 
            to={{
                pathname: "/course",
                search: "?sort=name",
                hash: "#the-hash",
                state: {fromDashboard : true}
            }}
            >
        </Link>

2-2. state 전달받는 방법

  • coin 페이지에서 loading이 발생하지 않는다.

    • coins 에서 state 생성
    • 클릭하며 coins에 state 전달
    • coins에서 전달받은 state로 state 생성
  • react-router-dom에서 보내주는 location object에 접근 => useLocation

// route/coin.tsx

const {state} = useLocation<RouteState>();
...
<Title>{state.name}</Title>
  • coin(detail) 페이지 접속옵션은 home을 통해 접근하는 방법과 url에서 바로 접근하는 방법이 있음
    • state는 coins페이지와 coin페이지로 넘어갈 때 생성
    • coins에서 state 생성 후 클릭과 동시에 coin에서 state를 전달받는 구조임
    • coins(home)를 거치지 않고, URL를 통해 coin(datail)로 바로 접근하면 에러발생 => state가 정의되지 X
  • ? : state가 있으면 state.name을 출력 없으면 "Loading을 출력"
    <Title>{state?.name || "Loading..."}</Title>

5. Coin Data

  • datail (coin)페이지

    • 코인정보와 가격정보 데이터 불러와서 state에 넣음
  • Data API

    • 코인정보 : https://api.coinpaprika.com/v1/coins/btc-bitcoin
    • 가격정보 : https://api.coinpaprika.com/v1/tickers/btc-bitcoin
      // coin.tsx
      ... 
      const {info,setInfo} = useState({});
      const {price,setPrice} = useState({});
      useEffect(() => {
      (async() => {
      // 코인 정보
      const infoData = await (
        await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`)
        ).json();
      // 가격 정보
      const priceData = await (
        await fetch(`https://api.coinpaprika.com/v1/tickers/${coinId}`)
        ).json();
      setInfo(infoData);
      setPrice(priceData);
      })();
      },[]);
      ...
  • data 확인

  • 캡슐화

    • data를 불러오는 한 줄의 request에서 2개의 변수를 받음.
    • response, json
      
      // 캡슐화 전
      (async() => {
      const response = await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`);
      const json = await response.json();
      })();

// 캡슐화 후 (async() => { const infoData = await (await fetch(https://api.coinpaprika.com/v1/coins/${coinId}) ).json();
})();

## 6. Data Type
- 불러온 Data의 type을 모두 지정해야 함
<img src="https://user-images.githubusercontent.com/75415898/151689501-82e3e983-d7bd-4206-bc41-19b265043b3e.png" width="350">

- 전역변수로 object에 저장.
<img src="https://user-images.githubusercontent.com/75415898/151689383-962d9f42-174f-452c-a02c-7d65e6acaabb.png" width="350">
<img src="https://user-images.githubusercontent.com/75415898/151689407-b32bee0c-f8a5-4bdf-9590-0dad561787cf.png" width="350">

- object의 key값 출력
<img src="https://user-images.githubusercontent.com/75415898/151689742-d79fa6eb-1356-413f-ab10-7b21d18ccd86.png" width="300">
<img src="https://user-images.githubusercontent.com/75415898/151689810-601ccf09-235e-4a7e-9a87-5b67f341c3b4.png" width="350">

- Command + D : 같은 문자 모두 찾아서 변경하기
- Option + Shift + I : 선택한 라인에 각각에 문자 넣기
<img src="https://user-images.githubusercontent.com/75415898/151693784-383293a2-fa2d-4523-8624-5d338997d02b.png" width="350">

- tags의 타입은 object지만 실제로는 아님.
    - object로 이루어진 array. 
    - array로 이루어져 있으면 TypeScript에게 설명해야함. 
    - 네이밍시 앞에 대문자 I를 붙여 코드읽을 때 interface를 구분할 수 있음.
-

- 코인정보 타입 : infoData Type
```javascript
// Coin.tsx

interface ITags{
  coin_counter: number;
  ico_counter: number;
  id: string;
  name: string;
}

interface infoData{
    ...
    tags : ITags[];
    ...
}

function Coin(){
    const {info, setInfo} = useState<InfoData>();
}
  • 가격정보 타입 : infoData Type
    
    // Coin.tsx

interface PriceData { id: string; ... quotes: { USD:{ ath_date: string; ath_price: number; ... } } ... }

function Coin(){ const {price,setPrice} = useState(); }

- Type을 저장해서 자동선언됨
- `const {price,setPrice} = useState<PriceData>()` 하면 TypeScript는 `price`가 `PriceInfo`라고 믿음.
<img src="https://user-images.githubusercontent.com/75415898/151763821-e3c6f169-7ab4-4ab0-9e88-eaa339cfdc65.png" width="400">

## 7. Nested Routes
- route 안에 있는 또 다른 route

- Tab 방법
    - State : home에서 state를 생성한 후 다른 route를 클릭했을 때 전달하며 생성
    - URL : 다이렉트 url로 접속가능함.

### Tab 만들기

```javascript
// Coin.tsx
function Coin(){
    useEffect(() => {
          (async () => {
            const infoData = await (
              await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`)
            ).json();
        })();
      // hooks 내부에서는 [ ] 내부에 값이 들어가야 함. 
      // coinId는 url에 위치하는 값이기 때문에 컴포넌트 생성 주기내에서 변하지 않음 => 최초 1회만 수행
      }, [coinId]);

    return (
        <Title>
              // Tab => State 방법. Coin 클릭시 state가 넘어옴. state의 정보를 사용
              // Tab => URL 방법. state가 넘어오지 않으면 info의 정보를 사용
              {state?.name ? state.name : loading ? "Loading..." : info?.name}
         </Title>
  • Coin Route 내에 Price와 Chart Route를 Render 함.
    • Price
    • Chart
       <Switch>
              <Route path={`/${coinId}/price`}>
                    <Price />
              </Route>
              <Route path={`/${coinId}/chart`}>
                    <Chart />
              </Route>
       </Switch>

Tab과 route 연결하기

  • nested route를 사용해서 Link로 값을 넘김

    • 클릭이벤트를 쓸 필요가 없음 => Link로 url값만 변경해주면 됨
    • coinId : useParams으로 가져온 URL경로
    Price Chart

현재 route 체크

  • useRouteMatch : 현재 route의 URL에 특정값 여부를 판단함.
    • 특정값이 있으면 isExact:true 없으면 null
const Tab = styled.span<{ isActive: boolean }>`
  color: ${(props) =>
    props.isActive ? props.theme.accentColor : props.theme.textColor};
`
...
 const priceMatch = useRouteMatch("/:coinId/price");
 const chartMatch = useRouteMatch("/:coinId/chart");
...
<Tabs>
            <Tab isActive={chartMatch !== null}>
                 <Link to={`/${coinId}/chart`}>Chart</Link>
            </Tab>
            <Tab isActive={priceMatch !== null}>
                 <Link to={`/${coinId}/price`}>Price</Link>
            </Tab>
</Tabs>

8. React Query

// index.tsx import { QueryClient, QueryClientProvider } from 'react-query';

... const queryClient = new QueryClient();

ReactDOM.render(

// 내부 컴포넌트에서 queryClient에 접근가능함 , document.getElementById('root') ); ``` ### fetcher 함수 - API fetch를 Component와 분리 - fetch 함수는 fetch promise를 return해야 함 - As-is ```javascript // Coin.tsx async() => { const response = await fetch("https://api.coinpaprika.com/v1/coins"); const json = await response.json(); })(); ``` - To-be ```javascript // api.tsx export async function fetchCoins(){ return fetch("https://api.coinpaprika.com/v1/coins").then((response)=> response.json() ); } ``` ### useQuery - state와 fetch를 대체 가능한 hook - `useQuery(queryKey,fetcherFunc)` - `queryKey` : query의 고유 식별자 - `fetcherFunc` : 해당하는 fetcher 함수를 불러옴 - return Values - `isLoading` : fetcher 함수가 로딩중이라면 `true`. fetcher 함수가 끝나면 `false`을 return. - `data` : fetcher 함수가 끝나면 그 함수의 data를 return. ```javascript const {isLoading, data} = useQuery("queryKey",fetcherFunc); ``` - As-is ```javascript // Coin.tsx const [loading, setLoading] = useState(true); const [coins, setCoins] = useState([]); useEffect(() => { (async() => { ... setCoins(json.slice(0,100)); setLoading(false); })(); },[]); ``` - To-be ```javascript // Coins.tsx import { useQuery } from 'react-query'; import { fetchCoins } from '../api'; ... const {isLoading, data} = useQuery("allCoins",fetchCoins); ``` - 중복 query Key 해결 - query key는 시스템에 저장되고 작동되기 위해 고유한 값이어야 함 - fetcher함수에 argument 전달 ```javascript // infoLoading에 isLoading을 담고 infoData에 data를 담음 const { isLoading: infoLoading, data: infoData } = useQuery( // query Key를 배열로 전달. info는 카테고리 역할. coinId는 고유의값 ["info", coinId], // 익명함수 만들어서 fetcher 호출 () => fetchCoinInfo(coinId) ); const { isLoading: tickersLoading, data: tickersData } = useQuery( // query Key를 배열로 전달 ["tickers", coinId], // 익명함수 만들어서 fetcher 호출 () => fetchCoinTickers(coinId) ); ``` ### useQuery option - 필수값인 key와 함수 외에 다양한 object를 선택해서 사용함. ```javascript useQuery(queryKey, queryFn?, { cacheTime, enabled, initialData, initialDataUpdatedAt isDataEqual, keepPreviousData, meta, notifyOnChangeProps, notifyOnChangePropsExclusions, onError, onSettled, onSuccess, queryKeyHashFn, refetchInterval, refetchIntervalInBackground, refetchOnMount, refetchOnReconnect, refetchOnWindowFocus, retry, retryOnMount, retryDelay, select, staleTime, structuralSharing, suspense, useErrorBoundary, }) ``` - refetch interval을 10초마다 하는 옵션 - 주기적으로 백그라운드에서 앱을 업데이트 할 수 있음 ```javascript // Chart.tsx const { isLoading, data } = useQuery( ["ohlcv", coinId], () => fetchCoinHistory(coinId), { refetchInterval: 10000, } ); ``` ### DevTools - react query는 fetch해서 얻은 respones를 캐싱함 - fetch한 데이터일 때는 캐싱된 데이터를 가져옴 => 로딩이 발생하지 않음 - DevTools로 캐시에 있는 query를 볼 수 있음 - render 할 수 있는 Component [설명](https://react-query.tanstack.com/devtools#_top) ```javascript // App.tsx import { ReactQueryDevtools } from "react-query/devtools"; ... ``` ## 9. Price Chart - `Coin.tsx`에서 `Chart` 컴포넌트로 parameter를 넘겨주는 방법 - 1) router로부터 parameter 가져옴 ```javascript // Chart.tsx import { useParams } from "react-router-dom"; ... const Params = useParams(); ``` - 2) `Chart` 컴포넌트를 `Coin.tsx`에서 랜더링하고 있음 => `props` ```javascript // Coin.tsx // Chart.tsx interface ChartProps { coinId: string; } function Chart({coinId}:ChartProps) { return

Chart

; } export default Chart; ``` - 시간대별 API fetch - [Coin paprika](https://api.coinpaprika.com/#tag/Coins/paths/~1coins~1{coin_id}~1ohlcv~1historical/get) - 14일 전부터 데이터 가져오기 ```javascript // Api.tsx export function fetchCoinHistory(coinId: string) { // Date.now()는 millisecond으로 전달함. 초로 변경 const endDate = Math.floor(Date.now() / 1000); // 초 * 분 * 시간 * 일수 const startDate = endDate - 60 * 60 * 24 * 7 * 2; return fetch(`${BASE_URL}/coins/${coinId}/ohlcv/historical?start=${startDate}&end=${endDate}`).then((response) => response.json() ); } // Chart.tsx import { useQuery } from "react-query"; import { fetchCoinHistory } from "../api"; ... interface IHistorical { "time_open": string, "time_close": string, "open": number, "high": number, "low": number, "close": number, "volume": number, "market_cap": number } function Chart({coinId}:ChartProps) { // 14개의 object => 배열로 가져옴 const {isLoading,data} = useQuery(["ohlcv",coinId],()=> fetchCoinHistory(coinId)); return

Chart

; } export default Chart; ``` ### APEX CHARTS - javascript chart library - API로 받은 Data 시각화 - [APEX CHARTS](https://apexcharts.com/) , [React APEX CHARTS Doc](https://apexcharts.com/docs/react-charts/) -설치 ``` npm install --save react-apexcharts apexcharts ``` - import - Chart Component가 있으니 이름 변경 ```javascript // import Chart from "react-apexcharts"; import ApexChart from "react-apexcharts"; ``` - 주요 Props - `series` : chart를 그려줄 Data ```javascript price.close), }, { name: "Price2", data: data?.map((price) => price.close), } ]} > ``` ###
min000 commented 2 years ago
min000 commented 2 years ago
min000 commented 2 years ago

npm i react-helmet

min000 commented 2 years ago

이슈