2023. 12. 22. 21:15ㆍ개인노트-인강
사이드 프로젝트:10개 기술스택으로 구현하는 풀스택 서버리스 프로젝트 with React
Part 3. 프로젝트 설계하기
Ch02. 기술셋 선정하기
# 기술셋을 선정하는 기준
새로운 기술들이 하루가 멀다하고 나오고 있습니다.
기술의 호수 속에서 우리는 어떤 기술을 사용해야 할까요?
## 올바른 기술셋은 무엇일까요?
- 내가 사용하고 싶은 최신 기술 ❌
- 기술 후보군의 비교/분석 없이 결정한다면 추후에 해당 기술이 지원하지 않는 피쳐가 있을 경우 난감합니다.. 최악의 경우 해당 기술을 드러내고 다른 기술을 다시 리서칭 해야 할 수도 있습니다. 무조건 최신 기술이라고, 무조건 많은 라이브러리를 쓴다고 올바른 것이 아닙니다!
- 시스템의 요구사항을 잘 충족 시킬 수 있도록 서포트 하는 기술 ✅
## 왜 중요한가요?
- 어떤 기술을 선택하느냐에 따라 소프트웨어 운영/유지 비용에 영향 🔺
- AWS S3가 좋다고 들어서 사용했다가는 요금 폭탄을 맞을 수도
- 소프트웨어의 한계점을 만들어 낼 수도
- 지금 선택한 기술이 이 소프트웨어의 근간 기술이 되고, 추후 다른 개발자들도 이 기술을 배우고 사용해야 하기 때문
- 기술을 위해 소프트웨어가 존재하는 것이 아니라, 더 나은 경험을 제공하는 소프트웨어를 만들기 위해 기술이 존재하는 것 ✨
## 어떻게 선정하나요?
1. 시스템의 요구사항을 잘 충족시키기 위해 필요한 것들 리스팅
e.g. 멀티 스레딩 지원?, 성능, 해당 기술의 learning curve, 팀원들의 기술에 대한 친숙도, 해당 기술의 업데이트가 활발하고 커뮤니티가 활성화 되어 있는가? 등..
2. 후보군 조사
기술셋 후보군 중에 사용해본 적이 없는 기술이 보여요. 해당 기술을 배제하는게 맞을까요? → 아닙니다! 리서치를 하는 시간을 충분히 가진 후 비교해야 합니다. 개발자는 항상 모르는 것이 있을 수밖에 없습니다. 모른다고 피하기 보다는 이해하는 시간을 가지세요. 공식 문서를 읽고, 간단하게 기술을 가지고 놀아보는 시간을 가져보세요 :)
3. 비교 테이블 생성 - 각 후보군 별로 시스템의 요구사항을 충족 시키는지 비교하기 위한 테이블을 만들면 됩니다.
4. 팀 내 토론 및 결정!
a. 개인의 취향에 맞게 선택하기 보다는, 이 프로덕트에 가장 적합한 기술을 선택하는 것이 중요.
b. 무슨 기술을 선택하든 선택에 따른 장/단점 존재. 상황에 따라 어떤 장/단점을 수용할 것인지 판단하는 것이 중요.
# 기술셋 선정하기 (1) - 프론트엔드
## 1. 시스템의 요구사항을 잘 충족시키기 위해 필요한 것들 리스팅
- 페이지 라우팅
- UI 코드 재활용 가능
- 상태관리가 가능한가?
- 테스트 코드 작성 용이
- 개발 커뮤니티 활성화
- 기술의 안정성 - v.0.0.1같이 너무 새로운 기술이 아닌, 안정된 버전이 있고, 유지보수 하는 단체가 안정적인가?
- 팀 내 기술 친숙도 (기술 친숙도 만으로 기술을 결정하는 요소가 될 수는 없습니다. 하지만 후보군들이 다 비슷비슷할 경우 기술 친숙도로 결정할 수 있습니다.)
## 2. 후보군 조사
- React.js
- Angular
- Vanilla JS
## 3. 비교 테이블 생성

## 4. 비교/분석 하기

- 게다가.. 리액트의 인기가 압도적으로 많음. -> 즉, 새로운 프론트엔드 개발자가 오더라도 리액트를 알고 있을 확률이 높다는 것.
# 기술셋 선정하기 (2) - DevOps
## 시작하기에 앞서, DevOps 란?
Development + Operation의 합성어
소프트웨어 개발에서 배포까지.. 전달(delivery) 과정을 자동화 하고 빠르게 하여 결국에는 비지니스의 가치를 높일 수 있도록 하기 위한 개발 환경이나 문화
## 1. 시스템의 요구사항을 잘 충족시키기 위해 필요한 것들 리스팅
- 배포 단계를 수립하는 것이 쉬운가?
- 배포가 쉬운가?
- 서버를 직접 관리해야 하는가?
- 웹 사이트 호스팅(도메인)를 쉽게 할 수 있는가?
- Github과 연동되어 git push시 배포 프로세스를 시작할 수 있는가?
- 추후 백엔드 API를 개발할 경우 통합하고 배포하는데에 용이한가?
- 배포 모니터링 지원
- 개발 커뮤니티 활성화
- 무료인가?
- 기술의 안정성 - v.0.0.1같이 너무 새로운 기술이 아닌, 안정된 버전이 있고, 유지보수 하는 단체가 안정 적인가?
- 팀 내 기술 친숙도
기술 친숙도 만으로 기술을 결정하는 요소가 될 수는 없습니다. 하지만 후보군들이 다 비슷비슷할 경우 기술 친숙도로 결정할 수 있습니다.
## 2. 후보군 조사
- AWS Amplify
- Jenkins
- Google Firebase
## 3. 비교 테이블 생성

## 4. 결정
AWS가 클라우드 시장에서 선점을 가하고 있고, AWS의 서비스들(Lambda, S3, DynamoDB 등)을 간편하게 연동하여 사용할 수 있다는 것이 큰 장점. 따라서, Firebase와 Amplify 중 선택해야 한다면, 더치페이 프로젝트에서는 AWS Amplify를 선택할 것
## 기술셋을 입힌 아키텍처는 이렇게 바뀝니다!
아키텍처 다이어그램 업데이트!

# AWS Amplify 소개
## 소개
- 모바일/웹 어플리케이션을 빠르게 구성+빌드+배포+운영까지 모든 라이프사이클을 한 곳에서 관리할 수 있도록 통합해둔 AWS의 풀스택 개발 통합 솔루션 서비스
## 둘러보기
### Admin UI 제공

### AWS 서비스와의 연동이 간편하다
- 인증(Cognito) - 로그인, 회원가입, 추가 인증 등
- 서비스 모니터링/로깅(CloudWatch)
- 데이터베이스(DynamoDB, Aurora, GraphQL 지원)
- 앱 푸시 알림(PinPoint)
### CLI(Command Line Interface)으로 할 수 있는 것들이 매우 많다!
- 프론트엔드 초기 프로젝트 셋업 (React, Vue, Angular, Next.js 등 지원)
- 백엔드 프로젝트 셋업 (node.js 지원)
- DB 스키마 설정 가능
- RESTful API 기본 코드셋도 제공
- DB와의 연동도 이미 되어 있음
- 배포
- 커맨드 하나면 배포 끝!
- 웹 호스팅 추가
- 별도로 웹 서버 셋팅을 요구하지 않는다. 커맨드 하나면 호스팅 추가 가능. 뒷 단의 웹 서버 관리는 AWS가 알아서 해준다.
### 서비스 배포
- 배포 모니터링, 로깅이 잘 되어있어, 배포가 실패할 경우 왜 실패 했는지 디버깅 하기 용이
- Github과 연동할 수 있어, main 브랜치에 푸시하는 것으로 배포 프로세스 시작 가능
### 프론트엔드와 백엔드 코드를 한 레포지터리 내에서 관리 가능
- 그럼에도 백엔드와 프론트엔드 분리 배포 가능
### 디자인 툴 Figma와 연동하여 Figma 내 컴포넌트를 리액트 컴포넌트화 할 수 있음
### CloudFormation을 이용한 인프라 관리
- Infrastructure as code 서비스의 한 종류
Infrastructure as code란?
한 서비스를 운영하기 위해 필요한 인프라 리소스(서버, 데이터베이스 등)을 코드화 해놓은 것을 의미 합니다. 매 배포시 마다 배포를 중개하는 서비스에서 해당 파일을 읽어들여 해당 코드 대로 인프라를 구성합니다. (e.g. 서버는 auto-scaling을 해줘, DB는 테이블을 이렇게 생성해줘!)
Git으로 버전 관리를 할 수 있으며, 누가 어떤 것을 수정 했는지 트래킹이 가능하다는 장점이 있습니다.
- AWS resource를 관리하기 위한 template file
{
"Description": "DynamoDB resource",
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"partitionKeyName": {
"Type": "String"
},
"partitionKeyType": {
"Type": "String"
},
"env": {
"Type": "String"
},
"tableName": {
"Type": "String"
}
},
"Resources": {
"DynamoDBTable": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
"KeySchema": [
{
"AttributeName": "guid",
"KeyType": "HASH"
}
],
"AttributeDefinitions": [
{
"AttributeName": "guid",
"AttributeType": "S"
}
],
"GlobalSecondaryIndexes": [],
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5
},
"StreamSpecification": {
"StreamViewType": "NEW_IMAGE"
},
"TableName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
{
"Ref": "tableName"
},
{
"Fn::Join": [
"",
[
{
"Ref": "tableName"
},
"-",
{
"Ref": "env"
}
]
]
}
]
}
}
}
},
"Outputs": {
"Name": {
"Value": {
"Ref": "DynamoDBTable"
}
},
"Arn": {
"Value": {
"Fn::GetAtt": [
"DynamoDBTable",
"Arn"
]
}
},
"StreamArn": {
"Value": {
"Fn::GetAtt": [
"DynamoDBTable",
"StreamArn"
]
}
},
"PartitionKeyName": {
"Value": {
"Ref": "partitionKeyName"
}
},
"PartitionKeyType": {
"Value": {
"Ref": "partitionKeyType"
}
},
"Region": {
"Value": {
"Ref": "AWS::Region"
}
}
}
}
# React 소개
사용자 인터페이스를 만들기 위한 JavaScript 라이브러리
## 특징
### 선언형
- 어플리케이션의 각 상태에 대한 뷰(view)만 설계하면 됨
- 상태가 달라짐에 따라 React가 알아서 필요한 컴포넌트만 업데이트하여 렌더링
### 컴포넌트 기반
- 스스로 상태를 관리하는 캡슐화된 컴포넌트
- 컴포넌트를 통해 UI를 재사용 가능한 개별적인 여러 조각으로 나눔.
- 컴포넌트 간 조합 가능 → 복잡한 UI도 구현 가능
- 외부에서 주어지는 인풋인 props, 내부에서 관리하는 상태인 state로 이루어짐
## 핵심 개념 짚고 넘어가기!
### 가상 DOM(virtual DOM)
- 노드 트리를 메모리 상에 로드 해두고, 한 컴포넌트 내 props/state 변화가 있을 때마다, 업데이트 되어야 할 최소한의 컴포넌트를 계산하고, 이를 실제 DOM 트리와 연동하여 렌더링 성능을 향상시키기 위해 탄생
### 고차 컴포넌트(HOC, Higher Order Component)
- 컴포넌트 로직을 재사용하기 위한 React의 고급 기술로, 별도의 API가 아니라 React의 디자인적 특성임
- 고차 컴포넌트는 한마디로, React 컴포넌트를 인풋으로 받아 새 컴포넌트를 반환하는 함수
- 횡단 관심사를 분리하는데 사용됨
- 횡단 관심사(cross-cutting concern)란? 어플리케이션을 계층 구조로 바라 보았을 때, 계층에 관계없이 공통적으로 관심있어할 것들
- e.g. 로깅, 보안 등..
### 제어 컴포넌트(controlled component)
- React와 관계 없이 입력 폼에 관련된 엘리먼트들은 사용자의 입력을 기반으로 독립적으로 state를 관리 (e.g. input, select, textarea 엘리먼트)
- React와 결합할 경우, 관리되는 state의 원천이 여러 곳이 될 수 있음. React state로 모든 컴포넌트를 제어하기 위해, 폼에 발생하는 사용자 입력값 또한 React 컴포넌트 내에서 제어할 수 있도록 하겠다는 개념.
- 즉, 제어 컴포넌트 = React 컴포넌트에 의해 제어가 되는 입력 폼 엘리먼트.
### 컨텍스트(context)
- 컨텍스트는 React 컴포넌트 트리 안에서 전역적으로 사용될 수 있는 데이터를 공유할 수 있도록 고안된 방법
- 컨텍스트를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 이러한 값을 공유
- 사용 하면 좋은 예: 테마 (다크모드, 일반 모드 등), 언어, 현재 로그인 한 유저 등
- Context를 생성한 후, Provider를 이용해 루트 트리에 Context를 제공하면, 어느 뎁스에 있는 자식 컴포넌트라도 이 값을 가져다 쓸 수 있다.
# React Router 소개
- React 어플리케이션에서 페이지/컴포넌트 내비게이션 (i.e. 페이지 라우팅; page routing)을 위해 쓰이는 표준 라이브러리.
- URL에 따른 UI (User Interface)를 동기화하여 일정한 경험을 제공.
페이지 라우팅; Page routing - 주어진 요청(URL)에 따라 해당 URL을 어떤 handler에서 처리하고 어떤 페이지를 렌더링 할 지 결정하는 과정
## React-router가 하는 일
History Stack
- 브라우저의 히스토리 스택. 웹 브라우저는 스택에 사용자가 방문해왔던 링크를 차곡차곡 저장한다.
- History 스택을 관리
- URL과 routes 경로를 매칭
- Route 매칭을 통해 적절한 UI를 렌더링
## 예시로 이해하기
### Route 선언
const rootElement = documents.getElementById("root");
render(
<BrowserRouter>
<Routes>
<!-- option 1 -->
<Route path="groups" element={<Groups />} />
<Route path=":guid" element={<ShowGroup />} />
</Route>
<!-- option 2 -->
<Route path="/groups/:guid" element={<ShowGroup />} />
</Routes>
</BrowserRouter>
);
- `BrowserRouter` - React app과 브라우저의 URL을 연결해서 매칭되는 URL에 따라 어떤 컴포넌트를 렌더링 할 지 정하겠다는 선언
- `Route` - URL이 해당 `path`에 매칭될 경우 해당 `element`를 렌더링하겠다는 선언
- `Route`를 중첩으로 선언할 경우 중첩된 `path`로 URL 매칭. 위 예시 중 option 1의 경우 /groups/:guid가 최종 `path`
- `path`의 `:guid`는 URL param의 name을 의미 (i.e. `:guid -> params.guid`). 예를 들어, URL이 /groups/3일 경우, guid param에는 3이 할당될 것
### Link를 이용한 내비게이션
<Link to="/groups" />Go back to groups</Link>
- `Link`를 사용함으로써 전체 페이지를 새로고침 할 필요 없이 URL을 바꿔줄 수 있다 (반응 속도가 빨라지는 효과!)
- `Link`가 클릭 될 경우 browser stack에 해당 로케이션을 쌓음
### JS 코드를 이용한 내비게이션
const GroupShow = () => {
let navigate = useNavigate();
render(
<div>
<button
onClick={() => {
// do something else
navigate("/groups");
}}
>
Back to groups
</button>
...
<button onClick={() => navigate(-1)}>Back</button>
</div>
);
}
- `useNavigate()` - method call로 페이지 내비게이션을 할 수 있는 함수를 리턴
- `navigate(path: string)` - 주어진 `path`로 리다이렉트. History stack에 해당 location을 push
- `navigate(path: string, { replace: true })` - 주어진 `path`로 URL을 바꿔치기. History stack에 해당 location을 push하지 않고 대체 하는 것
- `navigate(delta: number)` - History stack에서 top에서 `delta` 만큼 밑에 있는 location로 리다이렉트 하고, 해당 location을 history stack에 push
- e.g. `navigate(-1)` = “페이지 뒤로 가기”와 비슷
### URL Param 읽기
const GroupShow = () => {
let params = useParams();
const guid = params.guid;
return ( ... );
}
- URL Parameter는 URL에서 `/`로 분리되는 segment (e.g. /groups/**234** ➡️ 234가 URL param)
- 컴포넌트 안에서 `useParams()`를 호출하면 parameter map이 리턴된다.
- 이 map을 이용하여 param value를 읽을 수 있다.
### Search Param 읽고 쓰기
const FilteredGroup = () => {
let [searchParams, setSearchParams] = useSearchParams();
const sortKey = searchParams.get("sort_key");
}
- URL에서 `?`뒤에 붙여지는 query string 의미 (e.g. /groups?**sort_key=created_at**)
- 컴포넌트 안에서 `useSearchParams()`를 호출하면 parameter map이 리턴된다.
- React Router가 search parameter를 읽고 조작하기 쉽게 만들어줌! 사용법이 React의 `useState()`와 비슷하다.
- URL의 search params 객체는 `useState()`와 비슷하게 state로 관리된다. 따라서, `setSearchParams({ sortKey: 'new_key'})`를 호출할 경우 URL의 search param도 업데이트가 된다 (e.g. /groups?**sort_key=new_key**)
# Recoil 소개
- React를 위한 상태 관리 라이브러리
- Facebook에서 유지보수 하는 오픈소스 프로젝트
## 상태 관리가 별도로 필요한 이유
- 컴포넌트 간 데이터를 공유하고 전달해야 할 때, React의 단방향 데이터 특성 상, 부모→자식으로 밖에 전달할 수가 없다. 자식↔자식 간의 공유를 하려면 부모 컴포넌트에 지저분한 로직들을 넣어야 하고, 결과적으로는 유지보수성, 가독성이 떨어짐.
- 부모에서 증손자 컴포넌트로 데이터를 전달해야 할 경우, 부모 → 자식 → 손자 → 증손자로 props를 전달하며 데이터를 공유해야 하는 경우도 많음 (전문 용어로는 props drilling이라고 부름). 자식, 손자 컴포넌트 입장에서는 불필요한 데이터를 받아야 하고, 상태 관리가 점점 복잡해짐..
- 그래서! 전역 상태 저장소 역할을 할 것이 필요!
## 컨셉
### Atoms
- *“상태의 단위”*
- 왜 Atom 이라고 부를까?
- **Atom = 원자**
- **최소한**의 ****상태 집합만 담자!
(e.g. 사용자별 todo list 컴포넌트 = todo atom + user atom)
- Atom 하나에 앱에서 쓰는 모든 상태를 때려 넣지 말자는 철학!
- 상태 데이터를 담고 있기에, 읽기/쓰기를 할 수 있고, subscribe를 할 수 있다.
- Atom이 업데이트 되면 이를 구독하고 있는 React 컴포넌트는 새로운 값을 반영하여 다시 렌더링 됨
- 컴포넌트들 사이에 공유되는 상태 값 (a.k.a. source of truth)
const todoListState = atom({
key: 'todoListState',
default: [],
});
- `key` : Atom과 Selector에서 관리하는 상태값을 구분하기 위한 값으로, 어플리케이션 전역적으로 고유 해야 한다. 값은 문자열.
### Selectors
- Atom이나 다른 Selector를 입력으로 받아들이는 순수 함수
- 무슨 말이죠?
- 최소한의 상태 집합만 Atom에 담다보니, Atom으로 인해 파생되는 데이터는 Selector에 명시한 함수를 통해 계산을 해야 함
(e.g. todo list *atom* → filtered todo list *selectors*)
- React 컴포넌트는 Selector를 Atom과 같이 구독할 수 있으므로, Selector가 변경되면 컴포넌트들도 다시 렌더링 됨
- Atom과 Selector는 같은 인터페이스를 가지고 있다. 둘 다 상태(state)를 나타낸다
- Selector 함수는 멱등(idempotent)해야 한다. 이유 : Selector 결과값이 캐시가될 수도 있고, 재시작되거나 혹은 수 차례에 걸쳐서 실행될 수 있으므로 중요
멱등이란?
동일한 인풋이 주어지면, 항상 같은 아웃풋을 낸다는 것
예시


const filteredTodoListState = selector({
key: 'filteredTodoListState',
get: ({get}) => {
const todoList = get(todoListState)
return todoList.filter(...)
}
});
- 첫번째 `get` 속성은 계산될 함수.
- 전달되는 `get` 인자를 통해 atoms와 다른 selectors에 접근 가능.
- 다른 atoms나 selectors에 접근하면 자동으로 종속 관계가 생성되므로, 참조했던 다른 atoms나 selectors가 업데이트되면 이 함수도 다시 실행 됨.
### Hooks
- React에서 제공하는 hook 처럼, Recoil도 hook을 제공
useRecoilState
- React 컴포넌트에서 atom을 읽고 쓰기 위한 hook
- React의 `useState`와 사용법은 같지만, 상태(atom)은 컴포넌트 간에 공유된다는 차이점!
const Component() {
const [xxx, setXXX] = useRecoilState(xxxState)
return (...)
}
useRecoilValue
- Atom이나 Selector를 인자로 받아 요청하는 값을 반환
- state를 조작할 필요 없이 값을 읽기만 하면 되는 경우에 사용
const Component() {
const [xxx, setXXX] = useRecoilValue(xxxState)
return (...)
}
기타 다른 hook은 공식 문서 참조
https://recoiljs.org/ko/docs/introduction/installation/
## Recoil로 비동기 쿼리 핸들링 하기
- Selector를 이용해서 가능
- Selector의 get 속성에서, (1) Promise를 리턴하거나, (2) async 함수를 사용하기만 하면 끝!
const useTodoListQuery = selector({
key: 'userTodoList',
get: async ({get}) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
return response.data;
},
});
function UserTodoList() {
const todoList = useRecoilValue(userTodoListQuery);
return <div>{todoList}</div>;
}
# Styled 소개
ES6과 CSS의 장점을 누리면서 당신의 앱을 스트레스 없이 스타일링 하세요!
— Styled 홈페이지 번역
- 파일 단위의 CSS(`public/index.css`)가 아닌, 컴포넌트화 된 스타일링 기법 소개
- Lyft, Coinbase, Atlassian, Doordash, Jane 등 다양한 테크 회사에서 사용 중인 라이브러리
## 왜 Styled-components를 사용해야 할까?
- 리액트 컴포넌트를 CSS로 쉽게 styling 하기 위한 방법 제공
const StyledButton = styled(Button)`
background-color: blue;
color: white;
padding: 20px 10px;
&:hover {
background-color: darkblue;
}
`;
const App = () => {
<h1>Hello world!</h1>
<StyledButton>여기를 클릭!</StyledButton>
}
## ‘쉽게’ 라는게 구체적으로 어떤거죠?
- 코드 스플릿(code split): 하나의 CSS 파일을 전역으로 적용하는 것이 아닌 컴포넌트 단위의 스타일링 가능. 필요한 코드만 로드
- className 이 중복될 염려 ❎: styled-components는 각 컴포넌트에 대해 고유한 class 이름을 생성하므로 이미 존재하는 클래스 이름과 겹칠 염려를 하지 않아도 됨.
- React 컴포넌트 처럼 사용할 수 있음 → 재사용 가능. 컴포넌트 트리에서 볼 수 있음
- 모든 스타일이 컴포넌트화 되어 있으므로, 컴포넌트의 사용처가 없는지 IDE (e.g. VS code, IntelliJ)를 통해 확인하고 쉽게 삭제 가능
- 컴포넌트에 적용되는 CSS가 무엇인지 찾기 위해 여러 파일을 찾지 않아도 됨
참고 : https://styled-components.com/docs/basics#motivation
styled-components: Basics
Get Started with styled-components basics.
styled-components.com
## 핵심 기능
### props에 따른 스타일링 적용
- 여느 React 컴포넌트처럼 styled 컴포넌트도 props를 지원
- props에 따라 스타일링을 유동적으로 적용 가능
const StyledButton = styled.button`
font-size: ${props => props.mobile ? '20px' : '24px'}
`;
render(
<StyledButton mobile={true} />
);
### Style 상속 가능
- React 컴포넌트 혹은 Styled 컴포넌트를 상속 받아 스타일 오버라이딩 가능
- 특정 상황에서 이미 존재하는 컴포넌트의 스타일링이 달라져야 할 때 사용 가능
const StyledButton = styled.button`
font-size: ${props => props.mobile ? '20px' : '24px'};
`;
const StyledFloatingButton = styled(StyledButton)`
display: float;
bottom: 0px;
`;
const StyledPrimaryButton = styled(StyledButton)`
color: blue;
`;
### CSS nesting 가능
const StyledButton = styled(Button)`
background-color: blue;
color: white;
padding: 20px 10px;
&:hover {
background-color: darkblue;
}
`;
### 컴포넌트의 속성(attributes)은 그대로
- 상속하는 컴포넌트에서 제공하는 props/attributes는 Styled 컴포넌트 에서도 사용 가능
- e.g. Button 컴포넌트에서 제공하는 `onClick` 그대로 사용 가능
const StyledButton = styled(Button)`
background-color: blue;
`;
render() {
return(
<StyledButton onClick={handleClicked}>저장</StyledButton>
)
}
### 컴포넌트의 속성(attributes) 설정도 가능
- .attrs 생성자를 이용해서 컴포넌트에 추가 속성(props)를 넘길 수 있음
const StyledButton = styled(Button).attrs(props => ({
disable: props.completed ? false : true
}))`
background-color: blue;
`;
render() {
return(
<StyledButton>저장</StyledButton>
)
}
### Animation도 설정 가능!
- @keyframes를 이용하면 CSS의 animations도 설정할 수 있다.
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const StyledRotatingLoadingIndicator = styled(LoadingIndicator)`
animation: ${rotate} 1s linear infinite;
padding: 10px;
`;
# React Testing Library 소개
- RTL (React Testing Library)
- React 컴포넌트들을 테스팅 하기 위해 필요한 utility성 function들을 제공하는 라이브러리
- https://testing-library.com/
Testing Library | Testing Library
Simple and complete testing utilities that encourage good testing practices
testing-library.com
## 특징
- DOM 노드들을 찾거나, DOM 노드들과 소통하기 위한 솔루션 제공
## React 컴포넌트 렌더링
- render 메서드 이용
test('it renders the component', () => {
render(<ComponentName />)
})
## DOM 노드 검색
DOM 노드 검색 = 형태 + 쿼리 방식
### 형태
- **getBy**XXX - 현재 DOM 트리 내에서 한 노드를 찾는 것. 못찾으면 에러 발생시킴
- **getAllBy**XXX - 현재 DOM 트리 내에서 모든 노드를 찾는 것.
- **findBy**XXX - DOM 트리 내에서 노드 한 개가 나타날 때까지 기다렸다가 해당 DOM 을 선택하는 Promise 를 반환. 특정 시간 내에 나타나지 않으면(타임아웃 시) 에러 발생
- **findAllBy**XXX - 노드 여러개가 나타날 때가지 기다림
- **queryBy**XXX - 현재 DOM 트리 내에서 노드를 찾는 것. 못찾으면 `null`을 리턴 함
- **queryAllBy**XXX - 현재 DOM 트리 내에서 모든 노드를 찾는 것.
### 쿼리 방식
- ByLabelText - `label`이 있는 `input` 엘리먼트의 라벨 내용으로 `input` 엘리먼트를 반환
- ByRole - 특정 `role` 속성을 가지고 있고 값이 매칭하는 엘리먼트를 반환
- ByText - 엘리먼트가 가지고 있는 텍스트 값으로 검색
- ByPlaceholderText - `placeholder` 속성 값을 가지고 값이 매칭하는 엘리먼트를 반환
- ByAltText - `alt` 속성을 가지고 있고(e.g. `img`) 값이 매칭하는 엘리먼트를 반환
- ByDisplayValue - 현재 DOM 트리에서 `input, textarea, select` 등의 엘리먼트가 화면 상에 보이는 현재 값과 주어진 값이 매칭하는 엘리먼트를 반환
- ByTitle - `title` 속성을 가지고 있고 값이 매칭하는 엘리먼트, 혹은 `title` 엘리먼트가 있는 `svg`엘리먼트를 반환
- ByTestId - `data-testid` 속성을 가지고 있고 값이 매칭하는 엘리먼트를 반환. 보통 위의 쿼리 방식들로 노드를 가져올 수 없을 경우, 이 방식을 사용하곤 한다.
### 예시
screen.getByPlaceholderText('그룹 정보 입력')
screen.findAllByRole('button')
screen.queryByLabelText('날짜')
## 이벤트 발생시키기
### 클릭 이벤트
- userEvent / fireEvent 메서드 이용
userEvent.click(screen.getByText('저장'))
### 값 입력
- userEvent / fireEvent 메서드 이용
userEvent.type(screen.getByRole('textbox'), '제주도 여행')
### 값 수정
- fireEvent 메서드 이용
fireEvent.change(input, {target: {value: 'new value'}})
userEvent와 fireEvent의 차이점은 뭔가요? fireEvent는 DOM 노드에 직접 일으키는 이벤트 입니다. userEvent는 실제로 사용자가 이벤트를 발생시켰을 때처럼 모든 인터랙션을 시뮬레이션 합니다. 예를 들면, 실제 특정 버튼 클릭 이후에 트리거 되는 다른 이벤트들도 포함되서 시뮬레이션 되는 것이죠.
## DOM 노드 찾고, 이벤트 발생 시키는 것도 알겠는데.. 정작 중요한 값 확인(assert)은 어떻게 하나요?
- Jest 에서 제공하는 함수 중, `expect` 구문을 이용할 겁니다!
- 다음 채널에서 Jest에 대해 더 자세히 알아보면서 이야기 이어 나가봅시다 🙂
# Jest 소개
Jest is a delightful JavaScript Testing Framework with a focus on simplicity. — 출처: Jest 공식 홈페이지
- 프론트엔드 어플리케이션을 쉽게 테스팅 할 수 있도록 다양한 인터페이스를 제공하는 Javsacript용 테스팅 프레임워크. 메타(전 Facebook)에서 만들고, 오픈소스로 배포.
create-react-app 프로젝트로 React 프로젝트를 생성하면 기본적으로 내장되어 있음 문서화가 아주 잘 되어 있고, 많은 테크 회사에서 사용 중
## Jest의 철학
- Jest를 이용해 테스팅의 기쁨을 느껴 봅시다 😃
## 특징
### 고립됨(isolated)
- 각 테스트 케이스는 고립된 환경에서 돌아감
- 고립되었기 때문에, 테스트 성능을 최대화 시키기 위해 여러 테스트 케이스들을 병렬적으로 돌리는 것도 가능!
### 쉬운 설치 및 시작
- Jest 내에 테스트 러너(runner), 확인(assertion), 코드 커버리지 등 테스팅에 필요한 거의 대부분의 기능들을 이미 지원해서 테스팅을 위해 별도로 다른 툴을 설치하지 않아도 됨
- Zero-config! 패키지를 설치 후, 별도의 설정 없이 빠르게 테스트 가능
- `{fileName}.spec.js` 형식만 따르면 바로 테스트 실행 가능
### 스냅샷(snapshot)
- 객체 내부의 상태/값을 스냅샷으로 파일에 저장해두는 것
- 보통 리액트의 가상 DOM 트리 구조를 비교하기 위해 사용됨. 다음 테스트에서 객체의 현재 상태가 스냅샷과 동일한지 아닌지 테스팅 하기도 함.
- 위 테스트가 처음 돌아가면, 스냅샷 파일이 jest에 의해 아래와 같이 자동으로 생성됨
/* snapshot */
exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[function]}
onMouseLeave={[function]}
>
Facebook
</a>
`;
## 테스트 셋 설정/분해(setup/teardown)
- 테스트가 시작되기 전, 끝난 후에 일어나야 할 일들이 있다 (예를 들면, DB와의 커넥션 맺고 끊기, 데이터 리셋 등)
### 테스트 단위마다 설정/분해가 일어나야 할 경우
- `beforeEach()`
- `afterEach()`
beforeEach(() => {
setupDataSet()
});
afterEach(() => {
clearDataSet()
});
test('그룹을 생성한다.', () => { ... });
test('이름이 없는 경우, 그룹을 생성하지 않고 에러 메시지를 노출한다', () => { ... });
### 테스트 파일 내에서 한 번만 설정/분해가 일어나도 될 경우
- `beforeAll()`
- `afterAll()`
beforeAll(() => {
connectDB()
});
afterAll(() => {
disconnectDB()
});
test('그룹을 생성한다.', () => { ... });
test('이름이 없는 경우, 그룹을 생성하지 않고 에러 메시지를 노출한다.', () => { ... });
### 목(Mock) 함수
- 조작된 값을 리턴하는 가짜 함수를 만드는 것. 이 테스트 코드 내에서 컨트롤 하기 어려운 것들(예를 들면 외부 API 값, 함수)을 이용해서 테스트 코드를 작성해야 할 때 사용함
- `jest.fn()` 으로 목킹(mocking)을 할 수 있고, 목킹 된 함수가 호출될 경우 기본적으로 `undefined`를 리턴함
- 어떤 값을 리턴할 것인지 설정도 가능
const mock = jest.fn();
mock.mockReturnValue(42);
console.log(mock()); // 42
mock.mockReturnValue(43);
console.log(mock()); // 43
'개인노트-인강' 카테고리의 다른 글
풀스택 서버리스 프로젝트 with React - 9. TDD - 테스트 주도 개발 (0) | 2024.01.04 |
---|---|
풀스택 서버리스 프로젝트 with React - 7. 프로젝트 설계 (0) | 2023.12.04 |
풀스택 서버리스 프로젝트 with React - 6. 프로젝트 기획 (0) | 2023.11.22 |
풀스택 서버리스 프로젝트 with React - 5. 프로젝트 기획 (0) | 2023.11.17 |
풀스택 서버리스 프로젝트 with React - 4. 프로젝트 기획 (0) | 2023.11.15 |