react-masterclass's People
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 />
- 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"/>
- 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>
- 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 />
- 모든 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 #4 CRYPTO TRACKER
📌 react-query
react-query
: 편한 방식으로 데이터를 fetch 할 수 있는 패키지
Setup
react-router-dom
&react-query
installreact-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
- https://www.npmjs.com/package/styled-reset
- 실질적으로 전체 문서에 적용하기에는 커스텀한 기본값을 추가하기에 어려움이 있음
2. createGlobalStyle
import { createGlobalStyle } from "styled-components";
const GlobalStyle = createGlobalStyle`
body {
color: red;
}
`;
function App() {
return (
<>
<GlobalStyle />
<Router />
</>
);
}
- styled 컴포넌트로 만든 스타일은 지정된/사용되는 요소에만 국한됨
- createGlobalStyle은 렌더링 될 때 전역 스코프에 스타일을 올려줘서 global한 style을 가지게 됨
- document의 head에 style 주입
Fragment
- 일종의 유령 컴포넌트로 기존에 React에서 1개의 element만 return 되어야 해서 사용하지도 않는 쓸데없는 빈 div를 마구잡이로 썼다면
- Fragment로 대체 가능
Font
- https://fonts.google.com/specimen/Source+Sans+Pro
- 강의에서는 위 url에서 필요한 font를 @import해서 사용
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>
);
}
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Optional_chaining
- Javascript의 Optional Chaining 연산자(?.)를 사용해서 name에 접근하기 전에 state의 상태에 따라 다르게 반환됨
- state가 있다면 name 그대로 반환, null or undefined라면 자동으로 단락되어 undefined가 반환됨
📌 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>
- react-router-dom v6점대부터 위처럼 사용
- v6점대부터 달라진 점들
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 반환
📌 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")
);
- https://react-query.tanstack.com/overview
- 이제야 적용하는 react-query
QueryClientProvider
안에 있는 모든걸 하위 컴포넌트에서 접근 가능함 (ThemeProvider와 같은 맥락)
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} />
</>
);
}
- 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
- js 차트 라이브러리
- 자세한 옵션 사용법은 Docs 참고
- https://apexcharts.com/docs/react-charts
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로
HelmetProvider
로Helmet
을 감싸서 사용 - title 뿐만 아니라 favicon, css 등 추가 가능
import { Helmet, HelmetProvider } from "react-helmet-async";
<HelmetProvider>
<Helmet>
<title>{name ? name : loading ? "Loading..." : infoData?.name}</title>
</Helmet>
</HelmetProvider>
[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.js
와index.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
- https://styled-components.com/docs/api#typescript
- 이제 styled 컴포넌트 테마와 Typescript를 연결시켜 보자
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으로 설정됨
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.