블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2021. 9. 3. 15:22 React/Start React

react-query를 사용해 본다. 기능

  • 캐싱 기능: fresh, fetching, stale, inactive, delete 상태
    • stale 상태가 받은 데이터를 캐싱한 상태이다
  • QueryClientProvider 설정을 통해 useQuery 훅에 QueryClient를 전달한다.
  • 조회
    • const { data, isLoading, status, error, isFetching } = useQuery(queryKey, queryFunction, options) 호출
    • queryKey는 문자열, 배열이 가능하고 캐싱하는 키로 사용한다.
    • queryFuction은 promise를 반환한다.
    • isLoading: 캐시데이터 없음. 데이터 요청중 true
    • isFetching: 캐시데이터 유무와 상관 없음. 데이터 요청중 true
  • 생성/수정/삭제
    • const { mutate } = useMutation(queryFunction, options);
    • mutate(data, callbacks);
  • 캐싱 무효화
    • const queryClient = useQueryClient();
    • queryClient.invalidateQuries(key);

 

 

설치 및 설정

react-query를 설치한다. 

$> npm install react-query

apps/tube-csr/src/main.tsx에 react-query의 provider를 설정한다. 그리고 ReactQuery의 DevTool도 설치한다. 

// main.tsx
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';

import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';

import App from './app/app';

const queryClient = new QueryClient();

ReactDOM.render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools />
    </QueryClientProvider>
  </StrictMode>,
  document.getElementById('root')
);

 ReactQuery DevTool

 

Custom Hook 만들기 및 api 패키지 구성

useQuery를 통해 custom Hook을 만들 수 있다. useQuery 사용시 Typescript에 대한 타입정의를 하자. api 패키지를 별도로 생성하고, dog.api.ts 파일을 생성한다. api custom Hook과 type 파일을 별도로 모아 둔다

$> nx g @nrwl/react:lib api

libs/api/src/lib 폴더 밑에 dog.api.ts 파일을 생성하고, react-query 의 custom Hook을 만든다. 

// dog.api.ts

import { useQuery } from 'react-query';

import { httpService } from '@rnn-stack/core';

export function useDogList() {
  return useQuery<{ message: string[]; status: string }, Error>('dog-list', () =>
    httpService
      .get('https://dog.ceo/api/breeds/list')
      .toPromise()
      .then((response: any) => response)
  );
}

dog.api.ts 파일을 libs/api/src/index.ts 에 export 추가한다. 

export * from './lib/api';

export * from './lib/api/dog.api';

 

이제 apps/tube-csr/src/app/dog.tsx 에서 사용해 본다. 기존 코드는 주석 처리하고 useDogList 라는 custom Hook을 사용한다. 

useState, useEffect 사용하지 않고 custom Hook을 별도로 작성하였다.

  • 코드가 간결해 졌다.
  • 역할 분리가 잘 되었다.
  • api는 여러곳에서 공통으로 사용하므로 위치투명하게 api 패키지로 이동하였다.
    • import { useDogList } from "@rnn-stack/api";
// dog.tsx
import { List } from 'antd';
import { useDogList } from '@rnn-stack/api';

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);
    // });
  // }, []);

  const { isLoading, data, error } = useDogList();

  if (isLoading || !data) {
    return <span>loading...</span>;
  }

  if (error) {
    return <span>Error: {error.message}</span>;
  }

  return (
    <List
      header={<div>Header</div>}
      footer={<div>Footer</div>}
      bordered
      dataSource={data.message}
      renderItem={(item: string) => <List.Item>{item}</List.Item>}
    />
  );
}

export default DogList;

 

현재는 각 라이브러리의 쓰임세와 구조에 대해서 보고 있다. Production에 사용하려면 좀 더 다음어야 할 것으로 보인다. 다음은 상태관리를 위하여 recoil을 적용해 보자.

 

소스: https://github.com/ysyun/rnn-stack/releases/tag/hh-4

 

<참조>

>ReactQuery에 대한 장점을 잘 설명준다. 

https://swoo1226.tistory.com/216

 

[React-Query] 리액트로 비동기 다루기

react에서 비동기를 다루는 방법은 다양하다. javascript 언어니까 당연히 Promise, async & await으로 처리가 가능하다. redux를 사용하고 있다면, redux saga, thunk 등 다양한 미들웨어가 제공된다. 하지만 이..

swoo1226.tistory.com

 

> react-query & typescript: https://tkdodo.eu/blog/react-query-and-type-script

 

>  react hook & rxjs: https://nils-mehlhorn.de/posts/react-hooks-rxjs

 

React Hooks vs. RxJS

Here's why React Hooks are not reactive programming and how you can use RxJS knowledge from Angular in React

nils-mehlhorn.de

 

> hooks: https://twitter.com/tylermcginnis/status/1169667360795459584

  • useState: Persist value between renders, trigger re-render
  • useRef: Persist value between renders, no re-render
  • useEffect: Side effects that run after render
  • useReducer: useState in reducer pattern
  • useMemo: Memoize value between renders
  • useCallBack: Persist ref equality between renders

 

> hook lifecycle: https://medium.com/doctolib/how-to-replace-rxjs-observables-with-react-hooks-436dc1fbd324

 

How to replace RxJS observables with React hooks

As we saw in one of our previous articles, Doctolib is trying to help our new joiners to ramp up faster on our stack. One of the hardest…

medium.com

 

posted by 윤영식
2021. 8. 28. 12:24 React/Start React

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 

 

Release hh-3 · ysyun/rnn-stack

hh-3 add libraries and http.service.ts

github.com

 

 

posted by 윤영식
2015. 9. 6. 17:49 HTML5, CSS3

Selector는 패턴에 매치되는 노드를 추출하는 것이다. Selector를 배워보자 




Selector


패턴사용은 div[class] 이다. 스펙 참조


"querySelector vs querySelectorAll" : finding element

엘리먼트를 추출한다. querySelector는 하나만 찾고, querySelectorAll은 모두 찾아준다. 

DOM Query Selector-1 on jsbin.com


"Selector Attribute, Selector-Pseudo"

li:nth-child 같은 것은 Selector-Pseudo 이다. 





CSS


Cascading Style Sheet. 콘텐츠의 표현. HTML 엘리먼트에 CSS 적용. 모바일에서 SVG의 용도는 Zoom In/Out해도 깨지지 않는 해상도를 지원한다. 


"Rule Set"


CSS를 설정하는 규칙 

className {

  key: value;

}


"HTML 엘러먼트에 스타일 적용 순서"

디폴트 스타일 < 사용자 스타일 < 개발자 스타일

개발자 스타일이 가장 높음.


"Default Style"

<h1>이 <h2>가 큰 것은 디폴트 스타일에서 적용되어 있기 때문이다. (스펙 참조)


"사용자 Style"

브라우저에 스타일 지정이 가능하다. IE에서만 가능 (옵션에서 사용자 스타일 파일을 지정해 주면 된다.) 예로 만일 파란색을 강제로 브라우저레벨에서 노란색으로 보고 싶을 경우 사용한다. 


"개발자 Style"

개발자 Style 에서 !important 사용은 많이 고려한다. 가장 우선하기 때문이다. 


"DOM View는 최종적으로 스타일이 적용된 상태이다."

최종으로 적용된 스타일 추출로 DocumentView, AbstractView 인터페이스가 있다. 


"getComputedStyle() 은 우선순위에 의해 최종적으로 스타일이 적용된 결과값을 준다."

IE는 currentStyle이다. 

DOM CSS compoutedStyle on jsbin.com





Ajax


"Asynchronous JavaScript + XML"

Jesse James Garrett이 2005년 2월 18일 블로그에 썼다. 제목은 A New Application to Web Application으로 웹 애플리케이션이라 말을 처음 거론함. 이전은 마크업으로 말함. 


"JJG의 원문 블로그를 읽자" 

AJAX는 기술이 아니다. = "XHTML/CSS" + "XML/XLST" + "XMLHttpRequest(XHR)" + "DOM" + "JavaScript" 현재는 "XHTML/CSS"는 "HTML5"가 되고, "XML/XLST"는 사용 안한다. 각 요소기술을 AJAX라고 부른다. 

"XMLHttpRequest는 HTTP 통신 오브젝트이다."

비동기 통신을 하여 데이터 송수신 한다. XMLHttpRequest가 Ajax가 아니라 Ajax라 통칭하는 개념의 일부 요소기술이다. 


"Ajax Web Application Model"

비동기로 받은 데이터를 DOM 제어를 통해 반영한다. Web Application이 복잡해 지면서 MVC, MV* 패턴들이 나온다. 



"동기, 비동기 통신의 차이"

동기는 요청을 보낸 후 응답이 오기전까지 사용자의 액티비티를 할 수 없다. 비동기는 요청과 상관없이 사용자 액티비티가 가능하다. 



'HTML5, CSS3' 카테고리의 다른 글

[DOM] 김영보 Document Object Model 이해 - 2  (0) 2015.09.06
[DOM] 김영보 Document Object Model 이해 - 1  (0) 2015.09.05
[HTML5] 이해하기  (0) 2012.09.13
[CSS3] 시작하기  (0) 2012.09.11
posted by 윤영식
prev 1 next