React 외에 애플리케이션 개발을 위한 라이브러리를 설치한다.
UI Components
PrimeReact, EUI, MaterialUI, AntD 검토후 소스레벨 최신으로 반영하고 있고, 다양한 비즈니스 UX 대응 가능한 AntD를 선택한다.
// UI Component
$> yarn add antd
// Icon
$> yarn add @ant-design/icons
AntD의 스타일을 apps/tube-csr/src/styles.scss에 import 하고, app.tsx에 AntD의 Button 컴포넌트를 테스트 해본다. styles.scss는 글로벌 스타일로 apps/tube-csr/project.json 환경파일에 설정되어 있다.
// styles.scss
@import 'antd/dist/antd.css';
// app.tsx
import { Button } from 'antd';
import styles from './app.module.scss';
export function App() {
return (
<div className={styles.app}>
<Button type="primary">Primary Button</Button>
<Button>Default Button</Button>
<Button type="dashed">Dashed Button</Button>
<br />
<Button type="text">Text Button</Button>
<Button type="link">Link Button</Button>
</div>
);
}
export default App;
테스트 서버를 수행하고 확인해 본다.
$> nx serve tube-csr
라우터 설치
CSR의 Router를 위한 react-router, react-router-dom을 설치한다.
$> yarn add react-router react-router-dom
Ajax를 위한 라이브러리 설치
Client/Server 모두 사용할 수 있는 Axios 를 설치하고 rxjs를 통해 api 를 제어할 것이다.
$> yarn add axios
테스트 프로그램 작성
// dog.tsx
import { List } from 'antd';
import Axios, { AxiosResponse } from 'axios';
import { useEffect, useState } from 'react';
function DogList() {
const [dogList, setDogList] = useState<string[]>([]);
useEffect(() => {
Axios.get('https://dog.ceo/api/breeds/list').then((result: AxiosResponse) => {
setDogList(result.data.message);
});
}, []);
return (
<List
header={<div>Header</div>}
footer={<div>Footer</div>}
bordered
dataSource={dogList}
renderItem={(item: string) => <List.Item>{item}</List.Item>}
/>
);
}
export default DogList;
// app.tsx 에 추가
<DogList />
axios의 처리에 대해 rxjs 라이브러리를 같이 사용해 본다.
$> yarn add rxjs
axios와 rxjs의 Observable을 사용한 라이브러리를 구현한다. 라이브러리는 NX의 libs 폴더 밑으로 생성한다.
$> nx g @nrwl/react:lib core
// 실행을 하면 루트의 libs/core 밑으로 package가 생성된다.
libs/core/src밑으로 ajax 폴더를 생성하고 http.service.ts 파일을 생성한다.
- Axios와 Observable을 조합
- Axios cancel 적용
// libs/core/src/lib/ajax/http.service.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios, {
AxiosError,
AxiosInstance,
AxiosPromise,
AxiosRequestConfig,
AxiosResponse,
CancelTokenSource,
} from 'axios';
import { Observable, Observer } from 'rxjs';
// sample url: https://jsonplaceholder.typicode.com/users
interface RequestArgs {
method: HttpMethod;
url: string;
queryParams?: any;
payload?: any;
}
enum HttpMethod {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
PATCH = 'PATCH',
DELETE = 'DELETE',
}
export class HttpService {
private httpClient!: AxiosInstance;
private cancelTokenSource!: CancelTokenSource;
private options!: AxiosRequestConfig;
private completed!: boolean;
get<T>(url: string, queryParams?: any, options: AxiosRequestConfig = {}): Observable<T> {
this.setOptions(options);
return this.executeRequest<T>({ method: HttpMethod.GET, url, queryParams });
}
post<T>(url: string, payload: any, options: AxiosRequestConfig = {}): Observable<T> {
this.setOptions(options);
return this.executeRequest<T>({
method: HttpMethod.POST,
url,
payload,
});
}
put<T>(url: string, payload: any, options: AxiosRequestConfig = {}): Observable<T> {
this.setOptions(options);
return this.executeRequest<T>({
method: HttpMethod.PUT,
url,
payload,
});
}
patch<T>(url: string, payload: any, options: AxiosRequestConfig = {}): Observable<T> {
this.setOptions(options);
return this.executeRequest<T>({
method: HttpMethod.PATCH,
url,
payload,
});
}
delete<T>(url: string, options: AxiosRequestConfig = {}): Observable<T> {
this.setOptions(options);
return this.executeRequest<T>({
method: HttpMethod.DELETE,
url,
});
}
cancel(forcely = false): void {
if (!this.completed || forcely) {
this.cancelTokenSource.cancel(`${this.options.url} is aborted`);
}
}
private setOptions(options: AxiosRequestConfig = {}): void {
if (this.options) {
this.options = { ...this.options, ...options };
} else {
this.options = options;
}
this.cancelTokenSource = axios.CancelToken.source();
this.httpClient = axios.create({ ...options, cancelToken: this.cancelTokenSource.token });
this.completed = false;
}
private executeRequest<T>(args: RequestArgs): Observable<T> {
const { method, url, queryParams, payload } = args;
let request: AxiosPromise<T>;
switch (method) {
case HttpMethod.GET:
request = this.httpClient.get<T>(url, { params: queryParams });
break;
case HttpMethod.POST:
request = this.httpClient.post<T>(url, payload);
break;
case HttpMethod.PUT:
request = this.httpClient.put<T>(url, payload);
break;
case HttpMethod.PATCH:
request = this.httpClient.patch<T>(url, payload);
break;
case HttpMethod.DELETE:
request = this.httpClient.delete<T>(url);
break;
}
return new Observable<T>((observer: Observer<T>) => {
request
.then((response: AxiosResponse) => {
observer.next(response.data);
})
.catch((error: AxiosError | Error) => {
this.cancel();
observer.error(error);
if (axios.isAxiosError(error)) {
console.log(error.code);
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
}
} else {
console.log(error.message);
}
})
.finally(() => {
this.completed = true;
observer.complete();
});
});
}
}
export const httpService = new HttpService();
다음으로 apps/tube-csr/src/app/dog.tsx에 테스트 코드를 입력한다. httpService를 import할 때 from 구문이 Local file system에 있다하더라도 마치 node_modules에서 import하는 것처럼 위치투명성을 보장한다. 이는 루트에 위치한 tsconfig.base.json 파일에 lib 생성할 때 자동 등록된다.
// dog.tsx
import { useEffect, useState } from 'react';
import { List } from 'antd';
// import Axios, { AxiosResponse } from 'axios';
import { httpService } from '@rnn-stack/core';
function DogList() {
const [dogList, setDogList] = useState<string[]>([]);
useEffect(() => {
httpService.get('https://dog.ceo/api/breeds/list').subscribe((response: any) => {
console.log('axios observable response:', response);
setDogList(response.message);
});
// Axios.get('https://dog.ceo/api/breeds/list').then((result: AxiosResponse) => {
// setDogList(result.data.message);
// });
}, []);
return (
<List
header={<div>Header</div>}
footer={<div>Footer</div>}
bordered
dataSource={dogList}
renderItem={(item: string) => <List.Item>{item}</List.Item>}
/>
);
}
export default DogList;
tsconfig.base.json
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2017", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@rnn-stack/core": ["libs/core/src/index.ts"] <== 요놈
}
},
"exclude": ["node_modules", "tmp"]
}
다음 포스팅에서 계속 진행...
소스: https://github.com/ysyun/rnn-stack/releases/tag/hh-3
'React > Start React' 카테고리의 다른 글
[React HH-6] 서버 사이드 렌더링 SSR - Next.JS (0) | 2021.09.08 |
---|---|
[React HH-5] 라이브러리 설정 - recoil (0) | 2021.09.07 |
[React HH-4] 라이브러리 설정 - ReactQuery (0) | 2021.09.03 |
[React HH-2] NX 기반으로 React 개발환경 구성하기 (0) | 2021.08.25 |
[React HH-1] 시작하는 개발자를 위한 히치하이커 (0) | 2021.08.25 |