일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- TypeScript
- sucoding
- 리액트프로젝트
- frontend
- STATE
- 공식문서
- 프로젝트캠프
- React Query
- React
- 프론트엔드 개발
- 웅진씽크빅
- Server State
- 상태 관리 라이브러리
- tanstack query
- 스나이퍼팩토리
- 프론트엔드
- 개발
- 유데미
- 수코딩
- Today
- Total
yunicornlab
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 4일차 본문
한 번 배우고 프로젝트도 해봤으니까 어느 정도 알고있다고 생각했는데, 강사님께서 꼼꼼하게 알려주시고 팁도 많이 알려주셔서
굉장히 새로운 기분으로 듣게 되고, 내가 몰랐던 부분들을 채워가고 있어서 너무 좋았다.
오늘은 Children과 조건부/반복/이미지 렌더링하는 법에 대해 배웠다.
Children
React에서는 컴포넌트에 데이터를 전달할 때 props를 사용할 수 있는데, children은 컴포넌트의 props로 전달되는 특수한 prop이다.
props는 어느 정도 전달할 수 있는 타입이 한정되어있는데, children은 전달할 수 있는 데이터가 더 자유로워서 여러 요소나 컴포넌트도 전달할 수 있다.
children을 사용할 땐 React.ReactNode로 타입을 지정해주면 된다.
HTML Form 관련 요소 타입 지정
React.ComponentPropsWithoutRef<"태그종류">
이걸 이용하면 편하게 요소의 모든 속성들의 타입을 정의할 수 있다.
예를 들어 버튼의 경우, 아래처럼 사용할 수 있다.
type TButtonProps = React.ComponentPropsWithoutRef<"button">;
const Button = ({ type, name, disabled, children }: TButtonProps) => {
return (
<>
<button type={type} name={name} disabled={disabled}>{children}</button>
</>
);
}
export default Button;
또는 먼저 props로 받은 후에 비구조화해서 할당하는 방법도 있다. 단, 이때는 props에 마우스를 올려놓아도 자동 추론이 안된다.
type TButtonProps = React.ComponentPropsWithoutRef<"button">;
const Button = (props : TButtonProps) => {
const { children, ...rest } = props;
return (
<>
<button {...rest}>{children}</button>
</>
);
}
export default Button;
여기에 추가로, type의 종류를 text, password, number로 한정짓고 싶을 때는
타입스크립트의 유틸리티 타입 중 Omit을 사용해서 type을 먼저 제거한 다음에, & 연산자를 사용해서 type을 한정지으면 된다.
type TInputProps = Omit<React.ComponentPropsWithoutRef<"input">, "type"> & {
type: "text" | "password" | "number";
};
const Input = (props: TInputProps) => {
const { ...rest } = props;
return (
<>
<div>
<input {...rest} />
</div>
</>
);
};
export default Input;
조건부 렌더링
if 문을 사용하는 방식도 동작은 한다.
const App = () => {
const isChecked = false;
if (isChecked) {
return <h1>Pass</h1>;
}
return (
<>
<h1>Fail</h1>
</>
);
};
export default App;
하지만 보통 삼항 연산자를 이용한다.
const App = () => {
const isChecked = false;
return (
<>
{ isChecked ? <h1>Pass</h1> : <h1>Fail</h1> }
</>
);
};
export default App;
아래처럼 태그 안에 넣어도 된다.
const App = () => {
const isChecked = false;
return (
<>
<h1>{ isChecked ? "Pass" : "Fail" }</h1>
</>
);
};
export default App;
하지만 삼항 연산자를 두 번 이상 사용하게 되는 건 안티 패턴이다. 가독성이 떨어지기 때문이다.
삼항 연산자 대신에 && 연산자를 사용할 수도 있다.
const App = () => {
const isChecked = false;
return (
<>
{isChecked && <h1>Pass</h1>}
{!isChecked && <h1>Fail</h1>}
</>
);
};
export default App;
&& 연산자를 사용할 때는 0을 주의해야 한다.
자바스크립트는 0을 False로 취급하지만, React에서는 0을 문자열로 취급해서 True가 되어 버린다!
그래서 빈 배열인지 아닌지를 판단할 때는 length까지만 하면 안되고 length가 0보다 큰 지와 같은 비교 연산까지 같이 해주어야 한다.
즉, 아래처럼 하면 안 되고
const App = () => {
const isChecked = [];
return (
<>
{isChecked.length && <h1>Pass</h1>}
{!isChecked.length && <h1>Fail</h1>}
</>
);
};
export default App;
아래 코드처럼 해야 한다.
const App = () => {
const isChecked = [];
return (
<>
{isChecked.length > 0 && <h1>Pass</h1>}
{!isChecked.length && > 0 <h1>Fail</h1>}
</>
);
};
export default App;
또는 !!를 사용해 강제로 boolean값으로 판단이 되도록 사용할 수도 있다.
반복 렌더링
같은 태그를 여러번 반복해야 하는 경우, React에서는 보통 map을 사용해서 반복 처리를 해준다.
const App = () => {
const friends = ["John", "Maria", "Kevin", "Sophia"];
return (
<>
<h1>나의 친구들</h1>
<ul>
{friends.length > 0 && friends.map((friend, index) => <li key={index}>{friend}</li>)}
</ul>
</>
);
};
export default App;
이렇게만 작성하면 React는 key에 관련된 에러를 보여준다. 반복할 때 각 태그마다 고유한 값을 부여해줘야 하기 때문이다.
보통은 서버에서 API 응답된 데이터를 사용하는데, 이때는 무조건 고유한 값이 같이 오기 때문에 그 값을 이용해주면 되어서 문제가 없다.
하지만 그런 경우가 아닌 상황에서는 key에 index 번호를 부여하기도 한다. (실무에서는 절대 사용하지 않는 방법이다.)
이미지 렌더링
웹 이미지 주소가 있는 경우는 img 태그의 src 속성에 그 주소를 지정해주면 되기 때문에 간편하다.
하지만 이미지 파일을 렌더링 하려면 이미지 파일들을 src 폴더 안에 두는 방법과 public 폴더 안에 두는 방법, 이렇게 두 가지 방법으로 나뉜다.
src 폴더 하위 방법
src 폴더 하위 방법은, 일반적으로 src 폴더 하위에 assets 폴더 하위의 images 폴더를 생성해서 이미지를 넣고,
파일 하나씩 import해서 src에 넣는다.
import picture from "./assets/images/image_1.jpg";
const App = () => {
return (
<>
<img src={picture} alt="이미지" />
</>
);
};
export default App;
public 폴더 하위 방법
public 폴더 안에 넣으면 따로 import할 필요 없이 public 위치를 기준으로 경로를 작성해주면 된다.
const App = () => {
return (
<>
<img src="/image_1.jpg" alt="이미지" />
</>
);
};
export default App;
src 폴더 하위 vs public 폴더 하위
src 폴더 하위에 두면 모든 파일 하나하나 import를 해와서 작성해야 하지만, public 폴더 하위에 두면 import할 필요 없이 경로만 작성해주면 된다.
하지만, public 폴더 하위에 두면 공개적으로 노출되어서 외부에서 접근 가능한 외부 파일이 된다.
npm run build 후에 npm run preview를 해서 개발자 도구에서 이미지 주소를 확인해보면 주소 방식이 다르게 표현된다.
src 폴더 하위에 두면 리액트가 관리해주게 되면서, build할 때 리액트가 이미지를 외부 브라우저에 노출시키기위해 임시적으로 assets 폴더를 만들어서 이미지를 넣어준다. 첫 번째 이미지의 경로에 나와있는 assets는 리액트가 생성해준 폴더이다.
이론상으로는 src 안에 넣고 사용하는 게 좋지만, 성능상으로 크게 차이가 없고 매번 import하기 불편하기 때문에 실무에서는 public 하위에 두고 사용하는 경우가 많다고 한다.
(사실 실무에서는 파일 서버에 업로드된 이미지를 사용하기 때문에 http 주소로 받아서 사용하는 경우가 대부분이다.)
하나 더 참고하자면, png나 jpg 보다는 압축률이 더 좋기 때문에 webp나 svg 확장자의 파일이 더 좋다.
base64로 변환해주는 사이트를 이용해서 이미지를 압축해보면 확인해볼 수 있다.
Tip
VSCode에서 타입 추론 기능 잘 활용하기
VSCode에서 에러가 발생한 곳이나 변수에 마우스를 올려보면 타입을 추론해서 알려주는 경우가 많다.
이걸 복사해서 사용하면 더 편하게 타입스크립트를 작성할 수 있다.
그리고 재사용하지 않을 것 같은 type alias나 interface는 해당 컴포넌트 안에 작성하지만,
실제 현업에서 재사용할 것 같은 타입들은 src 내의 types라는 폴더를 만들어서 여기에 작성한 후 export 해서 사용한다고 한다.
type alias와 interface 작성 위치
재사용을 하지 않을 것 같은 타입은 사용할 컴포넌트 내에만 작성하고,
재사용할 타입들은 src 폴더 하위에 types라는 폴더를 만들어서 여기에 넣고 export하고 사용할 컴포넌트에서 import 해서 사용한다.
User Snippet 사용
코드 템플릿을 나만의 단축키(단축어)로 지정하는 기능이다.
예를 들어 rafce를 입력하면 아래처럼 작성되도록 하고 싶으면
const 컴포넌트 이름 = () => {
return (
<>
<h1>컴포넌트 이름</h1>
</>
);
}
export default 컴포넌트 이름
VSCode에서 [ 설정(톱니바퀴 아이콘) -> Snippet(코드 조각) -> typescriptreact 검색 ] 순서로 이동한 후
아래처럼 작성하면 된다.
"Arrow Function Component": {
"prefix": "rafce",
"body": [
"const ${1:$TM_FILENAME_BASE} = ($2) => {",
" return (",
" <>",
" <h1>${1:$TM_FILENAME_BASE}</h1>",
" </>",
" );",
"}",
"export default ${1:filename}"
],
"description": "Create an arrow function component"
},
Starter 환경 구축해서 레포지토리로 만들기
React + TypeScript + SWC + Vite + Tailwind CSS + Tailwind Merge + Tailwind Form
프레임워크와 Tailwind CSS의 기본 설정을 깃허브에 업로드해놓고, 새로운 프로젝트를 생성할 때 클론만 받으면 되는 방법을 알려주셔서 나도 깃허브에 올려보았다!
Vite로 React와 TypeScript+SWC를 설치한 후에 Tailwind CSS를 설치 및 설정까지 완료하고,
Tailwind Merge와 Tailwind Form도 같이 설치해놓은 패키지가 있으면
새 프로젝트를 시작할 때마다 매번 설치 과정을 거치지 않고
만들어놓은 패키지만 설치하는 간편함을 위해 만들었다!
https://github.com/Attainy/react-ts-tailwind-starter
GitHub - Attainy/react-ts-tailwind-starter: React + Vite + TypeScript + TailwindCSS + Tailwind-Merge 기본 세팅
React + Vite + TypeScript + TailwindCSS + Tailwind-Merge 기본 세팅 - Attainy/react-ts-tailwind-starter
github.com
Tailwind CSS에서 색상값을 동적으로 지정하고 싶을 때 주의할 점
Tailwind CSS는 className에 색상값만 따로 변수로 바꾸면 적용이 안된다.
예를 들어 const bgColor = "#4F4F4F"로 두고 className={`bg-[${bgColor}]`} 이렇게 하면 될 줄 알았는데, 스타일 적용이 안 된다. 아예 const bgColor = "bg-[#4F4F4F]" 이렇게 두고, className = {`${bgColor}`} 이렇게 한꺼번에 해주어야 스타일 적용이 된다.
Tailwind CSS에서 Custom CSS를 등록하는 방법
css 파일 안에
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom CSS 등록 */
@layer components {
.align-middle {
display: flex;
justify-content: center;
align-items: center;
}
}
보통 tailwind.css 파일을 따로 두고 font용 css도 따로 만들고, 여러 css파일을 만든 후 작성한 다음에
index.css에서는 css 파일들을 import해오는 집합 역할로 많이 사용한다고 한다.
Tailwind CSS 사용 시 도움되는 Extension
인덱스 시그니처 지양
아래와 같은 인덱스 시그니처 방식으로 타입을 정의하면 너무 포괄적이어서 무엇을 넘겨줬는지 파악하기가 어렵다. 그래서 실무에서는 권장되지 않는다고 한다.
type ProfileCardProps = {
[key: string]: string;
} & { onClick: () => void };
순수 HTML 요소는 오히려 ComponentPropsWithoutRef를 사용해서 한 번에 포괄적으로 정의하는 걸 권장하지만, 그 외에는 인덱스 시그니처와 같은 방법은 지양해서 사용하자.
웹에서 Vite 사용하는 방법
나는 기존에 codesandbox만 알고있었는데, stackblitz라는 사이트를 알려주셨다. 사용하기 굉장히 편하다!
StackBlitz | Instant Dev Environments | Click. Code. Done.
StackBlitz is the collaborative browser-based IDE for web developers. StackBlitz eliminates time-consuming local configuration and lets developers spend more time building.
stackblitz.com
'Developer' 카테고리의 다른 글
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 6일차 (0) | 2024.08.26 |
---|---|
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 5일차 (0) | 2024.08.25 |
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 3일차 (0) | 2024.08.21 |
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 2일차 (0) | 2024.08.21 |
[유데미X웅진씽크빅X스나이퍼팩토리] React 2기 - 사전직무교육 1일차 (4) | 2024.08.19 |