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

Publication

09-22 00:37

Category

Recent Comment

2021. 9. 20. 19:36 React/Architecture

애플리케이션이 계속 증가하고, 작은 변경에 기민하게 대응할 수 있고, 팀별 기술셋의 다양성을 수용하는 환경에서 Micro Service 개발/운영 환경을 구축해 본다. 

 

Micro Service

모노리틱환경의 단점을 극복한다. (참조)

  • 작은 사이즈의 번들링 및 배포
  • 지속적인 애플리케이션 추가
  • 점점 늘어나는 접속자의 대응

Gateway Pattern 적용

  • gateway 역할
    • api gateway: micro service의 api server와 TCP로 연결한다. 또는 중간에 Queue(Redis, RabbitMQ, Kafka)등을 두어서 구성해도 된다.
    • http proxy: micro service에 대한 reverse proxy 역할을 한다. 
    • http server: login UI부분을 gateway에서 담당하고 JWT를 사용한다. 
    • auth server: login시 auth 역할을 한다. 
  • micro service 역할
    • api server: api는 TCP로 응답한다. 
    • http server: reverse proxy의 static 파일 대응을 한다. 

gateway 1개만 구성한 환경

 

가용성을 위하여 gateway를 두개를 두어 용도별로 운영할 수 있다. 

docker 기반으로 운영

 

 

BFF 패턴

Backend For Frontend 패턴 방식으로 개발한다. 

  • Server는 NodeJS 기반으로 구성한다. 
  • Client/Server 개발은 TypeScript로 통일한다.
  • CSR(Client Side Rendering), SSR(Server Side Rendering)을 사용한다. 

참조: https://giljae.medium.com/back-end-for-front-end-pattern-bff-4b73f29858d6 

 

 

 

개발 폴더 구성

멀티 애플리케이션을 mono repo에서 개발할 수 있는 폴더 구조를 정의한다. 

  • apps 폴더
    • 4개의 애플리케이션이 존재한다.
  • libs 폴더
    • page: 애플리케이션에 단위 화면 page를 개발하는 것이 아니라. page단위 별도 구성하고, application에서 조합한다.
    • ui: 모든 애플리케이션에서 사용하는 공통 ui
    • domain: core 업무에 대한 모든 내역들, 변경에 대해 유연하게 대처하기 위해 hexagonal architect방식으로 구성한다.
    • shared: common 라이브러리를 적용한다.

 

application의 libs 라이브러리 참조 방식

  • api: api server역할이므로 주로 none visible한 라이브러리 참조
  • web: ui 애플리케이션이므로 page, ui 라이브러도 참조

 

애플리케이션과 라이브러리 참조 관계

 

 

운영 폴더 구성

gateway와 micro services를 배포하는 폴더 구조를 정의한다. 

  • bin: start/stop shell
  • config: 환경파일
  • lib: server 구동 번들링 파일
  • public: static, web 번들링 파일들
  • logs: 서버의 로그 파일

 

 

<참조>

- 마이크로 서비스 아키텍쳐 패턴 그리고 장/단점 (강추)

https://medium.com/design-microservices-architecture-with-patterns/monolithic-to-microservices-architecture-with-patterns-best-practices-a768272797b2

 

Monolithic to Microservices Architecture with Patterns & Best Practices

In this article, we’re going to learn how to Design Microservices Architecture with using Design Patterns, Principles and the Best…

medium.com

 

- 헥사고날 아키텍쳐

https://engineering-skcc.github.io/microservice%20inner%20achitecture/inner-architecture-2/

 

마이크로서비스 내부아키텍처 - 2회 : 클린 아키텍처와 헥사고널 아키텍처

마이크서비스 내부 아키텍처에 대해 살펴보자. 3회에 걸쳐서 기존 아키텍처의 문제점 및 이를 개선하고자 했던 아키텍처 변화양상과 이를 반영한 어플리케이션 구조에 대해 알아 보겠다.

engineering-skcc.github.io

 

- BFF 패턴

https://giljae.medium.com/back-end-for-front-end-pattern-bff-4b73f29858d6

 

Back-end for Front-end Pattern (BFF)

소프트웨어 구성의 진화

giljae.medium.com

 

posted by peter yun 윤영식
2021. 9. 8. 19:10 React/Start React

서버 사이드 렌더링을 위하여 Next.JS를 적용해 본다. Micro Frontend 전략을 활용하여 구성해 본다. 

  • apps 밑에는 multi application이 존재한다.
  • libs 밑에는 base 패키지들이 있다. base 패키지는 계속 추가될 수 있다.
  • feature는 view와 domain이 존재한다.
    • view: 업무 UI 표현
    • domain: 업무 api 호출(react-query or graphql query) 및 결과 data model, custom Hook 기반
  • page에서 view와 domain을 조합한다.
    • SSR에서 Next.JS 기반으로 진행시 feature의 domain을 통해 데이터를 다룬다. 

 

멀티 애플리케이션에서의 단위 업무화면 구성방식

 

 

NextJS 개념

최신것이 항상 좋은 것이 아니다. SPA 프레임워크의 CSR(Client Side Rendering)만으로 개발하다가 예전의 JSP, ASP같은 SSR(Server Side Rendering)의 이점이 있었다. NextJS는 CSR, SSR 뿐만아니라 SSG(Static Site Generation)도 지원을 한다. 

 

  • CSR
  • SSR
  • SSG

 

SSG에 대한 개념

 

SSG를 위한 getStaticProps, getStaticPaths

  • getStaticProps와 getStaticPaths는 async이다.
  • getStaticProps, getStaticPaths에서 데이터를 가져오는 domain에 위치한 api를 호출한다. 
  • getStaticPaths는 URL path에 따라 데이터가 변경될 때를 대비한다. 

 

SSR를 위한 getServerSideProps

  • 서버에 요청때 마다 데이터를 맵핑하여 응답한다. 

 

Use Case

  • Data가 즉각적으로 변화하는 Dashboard에는 CSR을 적용
  • eCommerce 같은 경우 제품의 변경이 수시로 일어나지 않을 때 SSG 적용
  • NodeJS위에 Isomorphic Application으로 간다. 

 

Frontend 개발 영역의 변경

  • NodeJS 기반위에 페이지 개발의 주도권을 Frontend에서 갖는다.
  • Backend API를 조합하는 역할을 수행한다. 

Frontend 개발영역의 확장. (출처: D2 참조글)

 

Rendering on the Web 위치

Rendering on the Web 출처

 

NextJS기반 애플리케이션은 이전 블로그에서 tube-ssr 애플리케이션을 생성하였다. 이를 기반으로 다음 글에서 로그인 화면을 만들어 보자 

 

 

<참조>

> Vercel의 NextJS 소개 영상

https://www.youtube.com/watch?v=eMwTEo6AjDc 

> CSR, SSR, SSG 의 흐름

https://wonit.tistory.com/361

 

[정직하게 배워보는 Next.js] (번외) 웹의 발전 과정으로 보는 CSR 그리고 SSG와 SSR

해당 블로그 시리즈는 정직하게 배워보는 Next js 시리즈로써 총 8부작으로 이루어져 있습니다. Next.js공식 홈페이지에서 이야기하는 내용을 최대한 이해하기 쉽고 직관적이게 구성하였습니다. 0.

wonit.tistory.com

 

> SPA의 SSR 지원

https://d2.naver.com/helloworld/7804182

 

> NextJS 기반 실습 시리즈

https://velog.io/@mskwon/next-js-page-static-generation

 

Next JS Page Static Generation

Next JS에서는 각 페이지에 대해서 pre-rendering을 위해 SSR(서버 사이드 렌더링) 또는 SG(정적 생성) 옵션을 제공한다. 각각 페이지 컴포넌트에서 getServerSideProps, getStaticProps/getStaticPaths 함수 expo

velog.io

 

posted by peter yun 윤영식
2021. 9. 7. 17:49 React/Start React

 props를 통해 하위 컴포넌트에 전파하는 것이 아니라 글로벌하게 접근할 수 있는 상태 저장소 사용을 위해 recoil을 사용한다. recoil 사용이유는 간결하며 Hook스타일 개발에 적합해 보인다.

 

Concept & API

1) Flexible shared state: 유연하게 상태를 공유해 보자

2) Derived data and queries: 파생으로 데이터를 만들고 조회할 수 있다.

3) App-wide state observation: 애플리케이션 전체에 걸쳐 변경을 관찰할 수 있다.

 

atom: 상태의 단위이다. 

useRecoilState: useState와 같이 read/write 가능

useSetRecoilState/useResetRecoilState: write-only

useRecoilValue: read-only, useRecoilValue(atom | selector)

selector: 파생상태를 갖거나, Async호출을 하고 싶을 경우

사용예) recoil-paint 소스

 

 

설치 및 설정

recoil을 설치한다. 

$> npm install recoil

ESLint에 recoil hook 을 설정한다. 루트의 .eslintrc.json 파일에 useRecoilCallback 을 설정한다. 

{
  "root": true,
  "ignorePatterns": ["**/*"],
  "plugins": ["@nrwl/nx"],
  "overrides": [
    {
      "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
      "rules": {
        "@nrwl/nx/enforce-module-boundaries": [
          "error",
          {
            "enforceBuildableLibDependency": true,
            "allow": [],
            "depConstraints": [
              {
                "sourceTag": "*",
                "onlyDependOnLibsWithTags": ["*"]
              }
            ]
          }
        ],
        // 해당 하위 설정
        "react-hooks/exhaustive-deps": [
          "warn",
          {
            "additionalHooks": "useRecoilCallback"
          }
        ]
      }
    },
    {
      "files": ["*.ts", "*.tsx"],
      "extends": ["plugin:@nrwl/nx/typescript"],
      "rules": {}
    },
    {
      "files": ["*.js", "*.jsx"],
      "extends": ["plugin:@nrwl/nx/javascript"],
      "rules": {}
    }
  ]
}

apps/tube-csr/src/main.tsx 에 RecoilRoot를 설정한다. 

import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';

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

import App from './app/app';

const queryClient = new QueryClient();

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

 

state 패키지 구성 및 async 적용하기

libs/state 패키지를 생성한다. 

$> nx g @nrwl/react:lib state

UPDATE workspace.json
CREATE libs/state/project.json
CREATE libs/state/.eslintrc.json
CREATE libs/state/.babelrc
CREATE libs/state/README.md
CREATE libs/state/src/index.ts
CREATE libs/state/tsconfig.json
CREATE libs/state/tsconfig.lib.json
UPDATE tsconfig.base.json
CREATE libs/state/jest.config.js
CREATE libs/state/tsconfig.spec.json
CREATE libs/state/src/lib/state.module.scss
CREATE libs/state/src/lib/state.spec.tsx
CREATE libs/state/src/lib/state.tsx

 

core, state, api의 사용관계도. 원칙으로 상호 참조는 할 수 없고, 단방향 사용만 가능하다.

core와 api 패키지는 최하단에 위치하고, app(application)은 core,state, api모두를 이용한다. state는 core, api를 이용한다.

조건)

  - state는 selector를 통해 state 변경이 요구될 때만 aync ajax 호출이 있는 경우 api 패키지를 접근한다. 

  - api는 기본적으로 모든 ajax call을 처리한다.

사용 관계도

 

State Custom Hook 을 만들어 기존의 useDogApi (기존 useDogList를 Api postfix로 명칭 변경)를 호출토록 한다. libs/state/src/lib 폴더에 dog.state.tsx를 만들고 다음과 같이 리팩토링한다. 

  • api 패키지의 useDogApi()를 호출한다. 
  • atom을 생성한다: dog 목록을 저장
  • useEffect안에서 render이후 설정한다. useEffect를 사용하지 않으면 에러가 발생한다.

without useEffect

import { useEffect } from 'react';
import { atom, useRecoilState } from 'recoil';

import { useDogApi } from '@rnn-stack/api';

export const dogAtom = atom<string[]>({
  key: 'dogAtom',
  default: [],
});

export function useDogState(): [isLoading: boolean, dog: string[], error: Error | null] {
  const { isLoading, data, error } = useDogApi();
  const [dog, setDog] = useRecoilState(dogAtom);

  useEffect(() => {
    setDog(data?.message || []);
  }, [data?.message, setDog]);

  return [isLoading, dog, error];
}

react-query의 반환값을 같이 넘긴다. useDogState Hook을 apps/tube-csr/src/app/dog.tsx에 적용한다. 즉, 기존 useDogApi 코드를 리팩토링한다. 

import { List } from 'antd';
import { useDogState } from '@rnn-stack/state';

function DogList() {
  // recoil을 사용한 custom hook 적용
  const [isLoading, dog, error] = useDogState();

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

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

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

export default DogList;

위의 경우는 api호출후 서버 state에 결과를 저장하는 과정으로 react-query를 사용해도 되지만 예로 작성해 보았다. UI에 대한 state 저장은 소개 영상을 참조한다. 

https://www.youtube.com/watch?v=_ISAA_Jt9kI&t=1s 

 

 

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

 

 

 

<참조> 

> 한글: https://recoiljs.org/ko/

 

Recoil

A state management library for React.

recoiljs.org

> Awesome Recoil: https://github.com/nikhil-malviya/awesome-recoil

 

GitHub - nikhil-malviya/awesome-recoil: A curated list of Recoil libraries, code snippets, best practices, benchmarks and resour

A curated list of Recoil libraries, code snippets, best practices, benchmarks and resources. - GitHub - nikhil-malviya/awesome-recoil: A curated list of Recoil libraries, code snippets, best practi...

github.com

> 2021 libraries & React state mangement

https://dev.to/sfeircode/my-go-to-react-libraries-for-2021-4k1

 

My go-to React libraries for 2021

I’ve been working on React apps for more than 3 years now and I’ve used tons of libraries to build va...

dev.to

https://dev.to/workshub/state-management-battle-in-react-2021-hooks-redux-and-recoil-2am0

 

State Management Battle in React 2021: Hooks, Redux, and Recoil

Introduction: Over the years, the massive growth of React.JS has given birth to different...

dev.to

> State Management 분류

   Flux: Redux, zustand

   Atomic: Jotai, recoil

   Proxy: mobx, valtio, overmind

https://velog.io/@gtah2yk/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%83%81%ED%83%9C%EC%97%90-%EA%B4%80%ED%95%9C-%EC%A0%95%EB%A6%AC-%EA%B8%80

 

프론트엔드 상태에 관한 정리 글

프론트엔드 개발에서 짜증나는건 상태관리 부분이다.몇 가지 좋은 글이 많아서 요약겸 정리를 하면 좋을 것 같다 생각이 든다.https://leerob.io/blog/react-state-management리액트 상태 관리의 역사를 다루

velog.io

 

> Zustand vs Redux vs Jotai vs Recoil
소스: https://github.com/redhwannacef/youtube-tutorials/blob/main/react-state-management/README.md 

https://www.youtube.com/watch?v=BaEWIbxZKco 

posted by peter yun 윤영식
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 peter yun 윤영식
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 peter yun 윤영식
2021. 8. 25. 15:40 React/Start React

NXAngular/CLI를 확장하여 Typescript기반의 멀티 애플리케이션 및 노드 패키지개발을 위한 환경을 제공한다. 또한 Plugin 기반으로 React, NextJS, NestJS와 같은 프레임워크와 노드환경 확장을 통해 FullStack개발을 지원한다. 

 

목적

  • Case1: SPA/CSR의 React 애플리케이션 생성 + NestJS 기반 노드서버 생성
  • Case2: SSR을 위한 NextJS기반 애플리케이션 생성
  • Case1과 2의 두가지 애플리케이션에 대해 비교 테스트 진행하여 성능 이점과 차이점을 비교한다.

 

로컬에 새로운 환경 구성하기

NodeJS기반 테스트 환경 구축시 NodeJS버전을 변경하며 사용할 수 있도록 Local PC에 nvm (Node Version Manager)를 설치한다. Windows는 관련 링크를 참조하여 설치한다.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

NodeJS LTS버전을 nvm을 통해 설치하고 사용설정한다. 

$> nvm install v14.17.5
$> nvm use 14.17.5

NX 개발환경 구성을 위한 글로벌 패키지를 설치한다. Typescript는 v4.3.5 이상을 사용한다.

$> npm i -g @angular/cli@latest
$> npm i -g @nrwl/cli@latest
$> npm i -g yarn@latest

 

NX 개발환경 생성하기

npx 명령으로 개발환경 생성. RNN Stack에서 RNN은 React NestJS NextJS 을 합친 것이다. React Application의 명칭은 "tube-csr" 이다.

반드시 latest 버전으로 설치한다. 

$> npx create-nx-workspace@latest

선택하기
  ✔ Workspace name (e.g., org name)     · rnn-stack
  ✔ What to create in the new workspace · react
  ✔ Application name                    · tube-csr
  ✔ Default stylesheet format           · scss
  ✔ Use Nx Cloud? (It's free and doesn't require registration.) · No
   
$> cd rnn-stack

 

"tube-csr" 애플리케이션을 위한 Node서버로 NestJS 플로그인를 설치하고, "tube-api" 이름으로 서버를 생성한다.

Nx의 NestJS 플러그인 설치
$> yarn add -D @nrwl/nest@latest

생성
$> nx generate @nrwl/nest:app tube-api

 

다음으로 NX의 NextJS 플러그인을 설치한다. NextJS Application은 "tube-ssr" 이다.

설치
$> yarn add -D @nrwl/next@latest

생성
$> nx generate @nrwl/next:app tube-ssr
또는
$> nx g @nrwl/next:app tube-ssr

✔ Which stylesheet format would you like to use? · scss

 

NextJS 애플리케이션을 설치하면 sass-node버전을 v5.0.0이 설치된다. v4.14.1로 변경 사용한다. 5.0으로 사용시 컴파일 오류 발생하여 향후 패치되면 버전 업그레이드 함.

$> npm uninstall node-sass
$> npm install -D node-sass@4.14.1

tube-ssr 애플리케이션의 개발 서버 포트는 4300 으로 변경한다. workspace.json의 "tube-ssr"의 serve options설정에 포트 정보를 수정한다.

 

 

Nx 환경파일 재구성

애플리케이션과 라이브러리를 많이 만들다 보면 rnn-stack/workspace.json 파일안에 설정이 계속 추가된다. 가독성을 위하여 애플리케이션(라이브러리 포함) Nx의 환경설정을 프로젝트별 별도 파일로 분리한다. 분리후에는 애플리케이션이나 라이브러리 생성시 자동으로 "project.json" 파일로 분리가 된다.

 

각 애플리케이션 root 폴더에 "project.json" 파일을 생성하고 workspace.json의 프로젝트별 설정 정보를 옮긴다. workspace.json에는 애플리케이션의 위치정보로 수정하면 컨벤션에 의해 project.json을 인지한다.

workspace.json은 애플리케이션 위치를 표현한다. 

 

테스트하기

"tube"라는 React 애플리케이션을 Dev Server기반으로 실행하고, "realtime"이라는 NextJS 프렘워크기반 노드 서버를 실행한다. 

  • React Application: http://localhost:4200/ 
  • NextJS Application: http://localhost:4300/
React Single Page Application
$> nx serve tube-csr

NextJS Application with Server
$> nx serve tube-ssr

 

Prettier 코드 포멧터 설정하기

MS Code 편집기를 기준으로 prettier를 설정한다. 

  • rnn-stack 루트 폴더에 .prettierrc파일을 생성한다.
  • MS Code를 위한 Prettier Plugin을 설치한다. 
  • .vscode/settings.json 에 prettier 옵션을 설정한다. settings.json 파일이 존재하지 않다면 생성후 설정한다.
  • .vscode/extensions.json의 recommandation으로 prettier를 설정한다.

.prettierrc 내역

{
    "printWidth": 120,
    "singleQuote": true,
    "useTabs": false,
    "tabWidth": 2,
    "semi": true,
    "bracketSpacing": true
}

. vscode/settings.json 내역

{
    "editor.formatOnPaste": true,
    "editor.formatOnType": true,
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "[typescript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[scss]": {
        "editor.suggest.insertMode": "replace",
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[html]": {
        "editor.suggest.insertMode": "replace",
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    }
}

.vscode/extensions.json 내역

{
  "recommendations": [
    "esbenp.prettier-vscode",
    "nrwl.angular-console",
    "firsttris.vscode-jest-runner",
    "dbaeumer.vscode-eslint"
  ]
}

 

소스

https://github.com/ysyun/rnn-stack/releases/tag/hh-2

 

참조

posted by peter yun 윤영식
2021. 8. 25. 10:48 React/Start React

리액트 프로젝트를 위해 조사해봐야할 항목에 대해서 정리해 본다. 하기 구성에 대해 소스레벨의 준비과정을 작성해 본다.

 

개발환경

  • micro-frontend를 지원하는 환경
  • mono-repo기반 multi application을 개발할 수 있는 환경
  • Nx.dev for React
    • 멀티 애플리케이션 개발 및 애프리케이션별 번들링
    • 라이브러리의 위치 투명성 보장 및 배포

 

구조 설계

  • 1Layer: 애플리케이션
    • Layout: Feature 를 다루는 영역을 분할
    • Feature: UI + Data-Access 를 조합하여 업무 단위 화면을 구성
  • 2Layer: 업무 라이브러리
    • UI: Layout, Feature의 부분을 담당하는 업무 컴포넌트
    • Data-Access: API 호출 및 Data Caching
    • Data-Type: Frontend - Backend간 데이터 타입 및 vadliation
    • State: UI 상태 저장 및 전파
  • 3Layer: 공용 라이브러
    • Component: 사용자 정의 요소 컴포넌트 및 랩퍼
    • Chart: 차트 및 랩퍼
    • Utils: 유틸리티들
  • 4Layer: package.json 
    • AntD, PrimeReact, MaterialUI, Boostrap와 같은 CSS + Component Framework
    • Redux, Mobx, Recoil 과 같은 state
    • moment, date-fns 같은 date
    • immer 같은 immutable state 
    • react-use 같은 Hooks
    • react-query 같은 data api
    • rxjs 같은 reactiv library

 

고려 사항

  • Elagant Code Base 유지
    • Code Convention
    • TSLint
    • Typescript
  • Unit Test Code 
    • commit 전에 Unit Test 수행
  • Loose Coupling을 위한 UI Biz Feature, UI Componnet, Library 관계 정립

 

 

to be continue...

posted by peter yun 윤영식
2021. 8. 5. 09:29 React/Architecture

NX + Angular 기반 개발시 엔터프라이즈 애플리케이션 개발방법에 대한 글을 정리한다. Micro Frontend 개발방법이라기 보다는 애플리케이션을 개발하며 지속적으로 확장할 필요가 있을 때 관심사들을 어떻게 분리하여(Bounded Context) 개발할 수 있을지 보여준다.

 

 

https://indepth.dev/the-shell-library-patterns-with-nx-and-monorepo-architectures/

 

Variations of the Shell Library pattern with Nx & Angular Monorepos

Compare shell library patterns to pick the right one for your Angular monorepo. Nrwl feature shell, Manfred Steyer shell and Composite shell libraries.

indepth.dev

https://connect.nrwl.io/app/books/enterprise-angular-monorepo-patterns

 

Nrwl Connect

Directly leverage Nrwl's Angular expertise. Ship more features in less time.

connect.nrwl.io

 

 

nx.dev를 이용한 라이브러리 관리

Nx는 하나의 레파지토리에서 멀티 애플리케이션과 라이브러리를 개발할 수 있는 환경을 제공한다. @angular/cli를 확장한 형태로 Angular, React, NodeJS를 함께 개발하고 별개로 번들링 배포할 수 있다. Nx 설치는 이전 글을 참조한다.

 

workspace/libs 하위로 폴더를 구분하여 업무를 개발할 수 있고, workspace/apps 의 애플리케이션에서 libs의 내용을 통합하여 사용하는 방법을 제시한다. 

  • application
    • application 안에서 sub-domain을 import하거나, routing 한다.
    • sub-domain을 통합하는 방식이다. 
    • 애플리케이션, web, desktop(using Eletron), mobile(Ionic)을 이용하여 sub-domain을 사용한다.
  • sub-domain
    • 업무 도메인이다.
    • sub-domain은 Bounded Context 이다.
    • Bounded Context는 업무 논리적으로 분리될 수 있는 단위이다.
    •  feature-shell
      • 업무 도메인의 페이지를 통합하고 routing을 설정한다. 
      • sub-domain 안에 1개 존재한다. 
      • shell은 sub-domain을 접근토록 하는 entry point 모듈이다.
      • shell orachestrate the routes of "Bounded Context"
    • feature-<bizName>
      • 일반적으로 웹 화면(Page) 하나라고 정의하자.
      • sub-domain 안에는 feature-<bizName>가 여러개 존재할 수 있다.

 

Nx applications과 libraries 폴더 구조

NX Workspace 의 apps, libs 폴더 구성 예

 

Feature-shell에서 sub-doamin(Bounded Context) 라우팅하기

shell에서 Bounded Context 즉, sub-domain을 routing한다.

 

애플리케이션에서 Feature-shell 라우팅하기. 애플리케이션에서 feature-shell을 조합하여 사용한다.

애플리케이션에서 feature-shell  라우팅

 

 

NX에서 Library 분류 및 개발 방법

nx workspace의 libs/ 밑으로 라이브러리를 개발할 때 다음과 같이 분류하여 개발한다. 

  • Feature-Shell library
    • 업무 sub-domain을 통합하여 애플리케이션에서 사용할 수 있도록 한다.
  • UI library
    • presentation component로써 버튼, 모달같은 업무 로직이 없는 컴포넌트들
  • Shared library
    • Data Access library
      • REST API 서비스
      • WebSocket 통신
      • Ngrx/Store 기반 State 관리
    • Utils library
      • 유틸리티 서비스

 

NX에서 tag 구분 keywords

  • scope
    • sub-domain 
    • grouping folder 
  • type
    • feature-shell
    • ui
    • data-cess
  • platform
    • desktop
    • mobile

예) scope:shared, type:feature,platform:desktop

 

각 라이브러리의 특성에 맞게 libs/ 밑으로 폴더/폴더 형태의 grouping folder 구조로 개발한다. 

예로 booking 폴더 밑에 feature-shell libary가 존재

 

feature-shell의 모듈 명칭은 "feature-shell" 으로 한다.

nx를 이용하여 library 생성시 옵션들

  • --directory=<grouping folder name>
  • --routing: forChild routing 자동 설정
  • --lazy: application에 lazy routing 자동 설정, 반드시 parent-module을 지정함.
  • --parent-module=<application module path and file name>: lazy 옵션 설정시 lazy routing 설정할 module 위치를 지정함.
  • --tags=<tag>,<tag>: nx.json에 포함됨, nx.json에서 직접 수정 및 추가 가능 예) --tags=scope:shared,type:feature
  • --publishable: 특별히 npm registry에 publish 할 수 있는 library를 만들고 싶을 때 사용한다. ng-packagr 기반이다.

//Command
nx g lib feature-<sub-domain name> --routing --directory=<grouping folder name> --lazy --parent-module=apps/<application>/src/app/app.module.ts

//Sample
$ nx g lib feature-shell --routing --directory=admin --lazy --parent-module=apps/app-container/src/app/app.module.ts
? Which stylesheet format would you like to use? SASS(.scss)  [ http://sass-lang.com   ]
CREATE libs/admin/feature-shell/README.md (162 bytes)
CREATE libs/admin/feature-shell/tsconfig.lib.json (459 bytes)
CREATE libs/admin/feature-shell/tslint.json (276 bytes)
CREATE libs/admin/feature-shell/src/index.ts (50 bytes)
CREATE libs/admin/feature-shell/src/lib/admin-feature-shell.module.ts (367 bytes)
CREATE libs/admin/feature-shell/src/lib/admin-feature-shell.module.spec.ts (432 bytes)
CREATE libs/admin/feature-shell/tsconfig.json (138 bytes)
CREATE libs/admin/feature-shell/jest.config.js (405 bytes)
CREATE libs/admin/feature-shell/tsconfig.spec.json (258 bytes)
CREATE libs/admin/feature-shell/src/test-setup.ts (30 bytes)
UPDATE angular.json (15807 bytes)
UPDATE nx.json (1098 bytes)
UPDATE tsconfig.json (863 bytes)

 

 

품질 및 일관성 유지 방법

  • Single Version을 apps와 libs에 적용한다. 
    • package.json에서 관리한다. 
  • Code Formatting은 prettier를 사용한다.
  • Library Dependencies 관리
    • library 순환 참조는 안된다. 
    • library가 application을 import하거나 의존하지 않는다. 
    • npm run dep-graph 또는 npm run affected:dep-graph 수행하면 라이브러리 의존관계도가 자동생성되어 웹브라우져에서 확인할 수 있다.
    • MS Code Editor의 Plugin으로 "Nx Console"을 설치하면 Editor에서 바로 수행해 볼 수도 있다.

의존성 그래프를 웹에서 확인

 

Nx Console이 Left Menu 하단에 N> 아이콘으로 있고, 명령어 목록을 클릭한다.

 

 

Nx Schematics 관리

Nx에 Built-in 된 schematics

  • ngrx: Ngrx/store 기반으로 reducer, action, selector, facade를 자동 생성함
  • node: Express node application의 경우 
  • jest: unit test
  • cypress: E2E test
  • etc..

Custom schematic 만들기

  • 개발자들이 샘플코드를 copy & paste하지 않고 schematic을 통해 업무 개발을 위한 파일을 생성토록할 때 사용한다.
  • nx g workspace-schematic <custom-schematic-name> 명령을 수행하면 workspace/tools/ 폴더 밑에 schematic파일이 생성됨.
    • 예제 파일 생성: nx g workspace-schematic data-access-lib 수행
    • 예제 파일 수행: npm run workspace-schematic data-access-lib <name>

index.ts에서 개발함

//index.ts에 ngrx를 추가로 적용한 예제

import { chain, externalSchematic, Rule } from '@angular-devkit/schematics';
import * as path from 'path';

export default function (schema: any): Rule {
    if (!schema.name.startsWith('data-access-')) {
        throw new Error(`Data-access lib names should start with 'data-access-'`);
    }
    const stateName = schema.name.replace('data-access-', '');
    return chain([
        externalSchematic('@nrwl/workspace', 'lib', {
            name: schema.name,
            tags: 'type:data-access',
        }),
        externalSchematic('@nrwl/schematics', 'ngrx', {
            name: stateName,
            module: path.join('libs', schema.name, 'src', 'lib', `${schema.name}.module.ts`),
        }),
    ]);
}

 

관련소스: github.com/ysyun/blog-5730-micro-demo.git

 

 

참조

https://indepth.dev/the-shell-library-patterns-with-nx-and-monorepo-architectures/

 

Variations of the Shell Library pattern with Nx & Angular Monorepos

Compare shell library patterns to pick the right one for your Angular monorepo. Nrwl feature shell, Manfred Steyer shell and Composite shell libraries.

indepth.dev

NX에서 Eletron, Ionic, NativeScript 통합 개발하기

https://nstudio.io/xplat/fundamentals/architecture

 

nstudio | xplat xplat/fundamentals/architecture - cross platform tools for Nx workspaces

Passionate about implementing creative solutions for you. Technology, Consulting, Audio/Video Production. Web/Mobile apps, Angular/NativeScript plugins, Vue/NativeScript, product development, team training and help with project features/objectives.

nstudio.io

 

posted by peter yun 윤영식
2020. 5. 18. 20:23 React/Architecture

Angular v6부터 Web Components에 대한 지원으로 @angular/elements 기능이 추가되어 Custom HTML Tag을 만들 수 있도록 지원한다. 본 글은 해당 사이트의 글Nx.dev 환경과 통합하여 개발하는 과정을 설명한다. Nx 환경은 mono repository 기반으로 multi application을 개발 할 수 있는 환경을 제공한다. Angular/CLI기반이지만 Angular, React, Node.js 개발까지 하나의 Git Repository안에서 개발하고 번들링 할 수 있도록 지원한다. 따라서 micro frontend에서 multi application 개발 잇점을 갖는다. 

 

블로그 소스 [GitHub]

 

 

NX 환경 준비

Angular v9.* 

@angular/cli v9.1.6

RxJS v6.5.*

Typescript v3.8.*

NodeJS v12.16.*

Node Version Manager(nvm)를 통해 로컬환경에 여러 Node버전을 관리하자.

// NodeJS
$ nvm install 12.16.2
$ nvm alias default 12.16.2
$ nvm use 12.16.2

// Angular/CLI 최신버전 사용
$ npm i -g @angular/cli@latest
$ npm i -g @nrwl/cli@latest
$ npm i -g yarn@latest

// local 설치
$ yarn add

NX workspace를 생성한다. 

$ npx create-nx-workspace@latest
// 선택 및 입력
? Workspace name (e.g., org name)     micro-demo
? What to create in the new workspace angular [a workspace with a single Angular application]
? Application name                    app-container
? Default stylesheet format           SASS(.scss)  [ http://sass-lang.com   ]

 

 

Web Components 개발 환경 설정

@angular/elements 를 설치한다. 

$ yarn add @angular/elements

UI Component로 ng-antd v9.1.* 를 사용한다. yarn 이 아니라 angular/cli의 'ng' 명령을 사용한다. Yes와 sidemenu 형태 선택한다.

$ ng add ng-zorro-antd

선택하기 
? Enable icon dynamic loading [ Detail: https://ng.ant.design/components/icon/en ] Yes
? Set up custom theme file [ Detail: https://ng.ant.design/docs/customize-theme/en ] Yes
? Choose your locale code: en_US
? Choose template to create project: sidemenu

설치 후에 Nx workspace와 ng-zorro-antd의 불일치를 해결한다.

  • apps/app-container/theme.less의 첫줄의 import 문구 수정
    • @import "../../../node_modules/ng-zorro-antd/ng-zorro-antd.less";
  • apps/app-container/index.html의 root tag 수정
    • <app-root></app-root>

설정 수정후 실행을 하면 sidemenu가 있는 환경이 자동 셋업되어 아래와 같이 보인다. 자세한 설치방법은 사이트를 참조한다.

$ ng serve --open

ng-zorro-antd 자동 적용 화면

 

 

 

Monitor Web Components 개발 및 번들링

 

monitor 애플리케이션을 신규 생성한다. 모니터 애플리케이션을 Web Components로 만들어 app-container 애플리케이션에서 동적으로 로딩해 본다. 

$ ng g app monitor

선택
? Which stylesheet format would you like to use? SASS(.scss)  [ http://sass-lang.com   ]
? Would you like to configure routing for this application? No

apps/monitor/src/app/app.component.html과 app.component.ts 를 변경한다. 

// app.component.html
{{title}} Application

// app.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'micro-demo-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  @Input() title = 'monitor';
}

apps/monitor/src/app/app.module.ts에서 Web Comopennts를 등록한다. 

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [],
  // 동적으로 생성하므로 entryComponents에 등록
  entryComponents: [AppComponent],
  // 정적 bootstrap을 사용하지 않음
  // bootstrap: [AppComponent],
})
export class AppModule {
  constructor(private injector: Injector) {}

  ngDoBootstrap() {
    // createCustomElement를 통해 Web Components 스펙에 맞는 객체로 반환
    const monitorApp = createCustomElement(AppComponent, { injector: this.injector });
    // browser window객체에 잇는 customElements를 통해 Web Components 등록
    customElements.define('monitor-app', monitorApp);
    // 사용방법: @Input() title이 있으므로 attribute 설정가능
    // <monitor-app title="Monitor Application"></monitor-app>
  }
}

monitor 애플리케이션을 번들링하면 여러개의 파일로 나오는데 번들링 파일을 최소화한다. ngDoBootstrap() 은 정적 bootstrap이 아닌 실행타임에 외부 컴포넌트를 동적으로 로딩할 때 애플케이션 root를 결정할 수 있게 한다. ngDoBootstrap에 대한 설명을 참조하자.

$ ng build monitor --prod --output-hashing=none

수행할 경우 main, polyfill, runtime등의 파일이 생성된다. 파일 최소화를 위해 ngx-build-plus 패키지를 이용한다. 

monitor 애플리케이션의 번들링된 파일들

ng add 명령으로 ngx-build-plus를 설치하고 애플리케이션은 monitor를 지정한다. 

$ ng add ngx-build-plus --project=monitor

ng add 로 수행을 하면 angular.json 파일의 설정을 자동으로 적용해 준다. builder의 명령어를 자동 수정함.

    "monitor": {
      "projectType": "application",
      "schematics": {
        "@nrwl/angular:component": {
          "style": "scss"
        }
      },
      "root": "apps/monitor",
      "sourceRoot": "apps/monitor/src",
      "prefix": "micro-demo",
      "architect": {
        "build": {
          "builder": "ngx-build-plus:browser",  <== builder가 자동 변경됨
          "options": {
          ...
          

다시 명령으로 monitor 애플리케이션을 번들링한다. ngx-build-plus로 확장한 옵션인 --single-bundle true를 추가한다.

$ ng build monitor --prod --output-hashing=none --single-bundle true

main, polyfill로 압축된 번들링 파일

main과 polyfill을 합쳐주는 스크립트를 등록한다. Mac/Linux기준 명령이다. micro-demo 폴더 밑에 buildSingle.sh 파일을 생성한다. 번들링파일 합치는 명령을 넣는다.  

 

#!/bin/sh
ng build monitor --prod --output-hashing=none --single-bundle true && cat dist/apps/monitor/main-es5.js dist/apps/monitor/polyfills-es5.js > apps/app-container/src/assets/monitor-es5.js

 

buildSingle.sh를 수행한 결과 파일은 app-container의 apps/app-container/src/assets/monitor-es5.js 쪽으로 copy된다.

 

 

 

컨테이너 애플리케이션에서 동적로딩

Web Components 스펙을 기준으로 monitor 애플리케이션을 번들링 했기때문에 프레임워크의 종류에 상관없이 <monitor-app> 태그를 사용할 수 있다. 정적으로 사용하는 방법을 살펴보자.

 

apps/app-container/src/app/pages/welcome/welcome.component.ts에서 monitor-es5.js파일을 import한다. 

import { Component, OnInit } from '@angular/core';
// 소스 import
import '../../../assets/monitor-es5.js';  

@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.component.html',
  styleUrls: ['./welcome.component.scss']
})
export class WelcomeComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

 

테스트를 위해 welcome.component.html 에 <monitor-app>태그를 설정해 보자. 

<monitor-app title="Hi Monitor"></monitor-app>

 

여기까지하고 수행을 하면 <monitor-app> 태그를 해석할 수 없다고 Angular가 에러를 뱃는다. <monitor-app> 은 Angular가 해석하는 것이 아니라 Browser에서 해석되는 Web Components이므로 무시하도록 welcome.module.ts에 CUSTOM_ELEMENTS_SCHEMA를 설정한다. 

 

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { WelcomeRoutingModule } from './welcome-routing.module';
import { WelcomeComponent } from './welcome.component';

@NgModule({
  imports: [WelcomeRoutingModule],
  declarations: [WelcomeComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  exports: [WelcomeComponent]
})
export class WelcomeModule { }

 

설정한 "Hi Monitor"와 함께 <monitor-app>이 출력
브라우져가 해석한 <monitor-app> 태그

 

<monitor-app>태그를 설정하지 않고 Javascript를 이용하여 로딩해 본다. 

apps/app-container/src/app/monitor.service.ts 파일을 생성한다. 

  • monitor-es5.js 파일 동적 로딩
  • <monitor-app> DOM 동적 추가
import { Injectable } from '@angular/core';

@Injectable({providedIn: 'root'})
export class MonitorLoaderService {
  loaded = false;

  constructor() { }

  // script 동적 로딩
  loadMonitorScript(): void {
    if (this.loaded) {
      return;
    }

    const script = document.createElement('script');
    script.src = 'assets/monitor-es5.js';
    document.body.appendChild(script);
    this.loaded = true;
  }

  // <monitor-app> 태그 추가
  addMonitorApp(): void {
    const tile = document.createElement('monitor-app');
    // @Input() 내용은 setAttribute로 추가 가능
    tile.setAttribute('title', 'Dynamic Load Monitor');

    const content = document.getElementById('content');
    content.appendChild(tile);
  }
}

 

 위의 경우 monitor-es5.js 파일을 별도로 다운로드받아 동적 로딩을 수행한다. 

monitor-es5.js 파일을 <script> 태그 추가후 동적으로 다운로드 받음

 

 

공통 파일 빼고 번들링하기

만일 app-container과 monitor 에서 사용하는 공통 패키지의 버전이 같다면, app-container 애플리케이션과 monitor 애플리케이션이 공통으로 사용하는 파일중, monitor 애플리케이션을 번들링할 때 공통파일을 제거하는 방법에 대해 알아보자. 제거를 통해 monitor 애플리케이션의 번들링 사이즈를 줄일 수 있다. 이는 Network payload time을 줄여주는 결과를 갖는다. 

 

ngx-build-plus를 이용해서 @angular/cli의 webpack externals 을 자동 생성한다. (참조)

$ ng g ngx-build-plus:externals --project monitor

수행을 하면 angular.json 파일에 별도 환경이 추가되고, apps/monitor/webpack.externals.js 파일이 생성된다. angular.json 내용중

"node_modules/@angular/elements/bundles/elements.umd.js", 내용은 제거한다. elements.umd.js를 공통파일로 빼서 사용하는데 오류가 있다. 

elements.umd.js 를 포함시킬 경우 오류가 발생함

angular.json의 monitor 애플리케이션으 "scripts" 설정 내역 => scripts.js 파일에 설정한 *.umd.js 파일을 합친다.

// angular.json 내의 monitor 애플리케이션 설정
// "scripts"에 externals로 참조하는 파일 설정이 자동으로 입력되어 진다.
    "monitor": {
      "projectType": "application",
      "schematics": {
        "@nrwl/angular:component": {
          "style": "scss"
        }
      },
      "root": "apps/monitor",
      "sourceRoot": "apps/monitor/src",
      "prefix": "micro-demo",
      "architect": {
        "build": {
          "builder": "ngx-build-plus:browser",
          "options": {
            "outputPath": "dist/apps/monitor",
            "index": "apps/monitor/src/index.html",
            "main": "apps/monitor/src/main.ts",
            "polyfills": "apps/monitor/src/polyfills.ts",
            "tsConfig": "apps/monitor/tsconfig.app.json",
            "aot": true,
            "assets": [
              "apps/monitor/src/favicon.ico",
              "apps/monitor/src/assets"
            ],
            "styles": [
              "apps/monitor/src/styles.scss"
            ],
            "scripts": [
              "node_modules/rxjs/bundles/rxjs.umd.js",
              "node_modules/@angular/core/bundles/core.umd.js",
              "node_modules/@angular/common/bundles/common.umd.js",
              "node_modules/@angular/common/bundles/common-http.umd.js",
              "node_modules/@angular/compiler/bundles/compiler.umd.js",
              "node_modules/@angular/elements/bundles/elements.umd.js", // <-- 제거한다
              "node_modules/@angular/platform-browser/bundles/platform-browser.umd.js",
              "node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"
            ]
          },

apps/monitor/webpack.externals.js  파일내역에서 ng.elements도 주석처리한다.  => monitor-es5.js 파일에서 제거되는 파일목록이다.

const webpack = require('webpack');

module.exports = {
    "externals": {
        "rxjs": "rxjs",
        "@angular/core": "ng.core",
        "@angular/common": "ng.common",
        "@angular/common/http": "ng.common.http",
        "@angular/platform-browser": "ng.platformBrowser",
        "@angular/platform-browser-dynamic": "ng.platformBrowserDynamic",
        "@angular/compiler": "ng.compiler",
        // "@angular/elements": "ng.elements",  <-- 주석처리한다.

        // Uncomment and add to scripts in angular.json if needed
        // "@angular/router": "ng.router",
        // "@angular/forms": "ng.forms"
    }
}

buildSingle.sh 내용을 수정한다. scripts.js 파일은 window.ng.core 또는 window.ng.common과 같은 global 객체가 담겨있는 파일이다. 따라서 scripts.js는 app-container 애플리케이션에서 최초 한번만 로딩하면 되고, 이후 monitor 애플리케이션과 같은 web components는 번들 파일은 자신의 내용만을 포함한다.

 

monitor app size including common library  - 129KB

common library를 포함한 사이즈 - 129KB

monitor app size excluding common library - 19KB

monitor app과 @angular/elements만 포함한 사이즈 - 19KB

#!/bin/sh
ng build monitor --prod --extra-webpack-config=apps/monitor/webpack.externals.js --output-hashing=none --single-bundle true && cat dist/apps/monitor/main-es5.js dist/apps/monitor/polyfill-es5.js > apps/app-container/src/assets/monitor-es5.js
cat dist/apps/monitor/scripts.js > apps/app-container/src/assets/scripts.js

scripts.js 파일은 angular.json 의 app-container 애플리케이션 "scripts" 옵션에 추가한다. 

    "app-container": {
      "projectType": "application",
      "schematics": {
        "@nrwl/angular:component": {
          "style": "scss"
        }
      },
      "root": "apps/app-container",
      "sourceRoot": "apps/app-container/src",
      "prefix": "micro-demo",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/apps/app-container",
            "index": "apps/app-container/src/index.html",
            "main": "apps/app-container/src/main.ts",
            "polyfills": "apps/app-container/src/polyfills.ts",
            "tsConfig": "apps/app-container/tsconfig.app.json",
            "aot": true,
            "assets": [
              "apps/app-container/src/favicon.ico",
              "apps/app-container/src/assets",
              {
                "glob": "**/*",
                "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
                "output": "/assets/"
              }
            ],
            "styles": [
              "apps/app-container/src/theme.less",
              "apps/app-container/src/styles.scss"
            ],
            "scripts": [
              "apps/app-container/src/assets/scripts.js"  <== 추가
            ]
          },

수행하면 네트워크에서 scripts.js 파일은 한번만 로딩되고, 이후 다양한 Web Components들은 자신의 애플리케이션 내역만 포함한 파일로 번들링하여 파일 사이즈를 줄일 수 있다. (참조)

Custom Element에서 common library를 window객체쪽으로 변경함.

 

scripts.js 로딩 후 monitor-es5.js 파일 로딩

 

 

참조

https://www.angulararchitects.io/aktuelles/angular-elements-part-i/

 

Angular Elements, Part I - ANGULARarchitects

A dynamic dashboard in four steps with Web Components

www.angulararchitects.io

https://ng.ant.design/docs/getting-started/en

 

NG-ZORRO - Ant Design Of Angular

An enterprise-class UI design language and Angular-based implementation with a set of high-quality Angular components, one of best Angular UI library for enterprises

ng.ant.design

https://medium.com/angular-in-depth/how-to-manually-bootstrap-an-angular-application-9a36ccf86429

 

How to manually bootstrap an Angular application

AngularInDepth is moving away from Medium. This article, its updates and more recent articles are hosted on the new platform inDepth.dev

medium.com

https://www.angulararchitects.io/aktuelles/your-options-for-building-angular-elements/

 

Your options for building Angular Elements - ANGULARarchitects

with the CLI

www.angulararchitects.io

https://indepth.dev/tiny-angular-application-projects-in-nx-workspaces/

 

Tiny Angular application projects in Nx workspaces

Use assets, styles, and environments workspace libraries to follow the Single Responsibility Principle. Step-by-step commands and instructions.

indepth.dev

https://nstudio.io/blog/custom-web-elements-with-angular-and-react

 

nstudio | Custom web elements for Angular and React with Nx + xplat

Passionate about implementing creative solutions for you. Technology, Consulting, Audio/Video Production. Web/Mobile apps, Angular/NativeScript plugins, Vue/NativeScript, product development, team training and help with project features/objectives.

nstudio.io

 

posted by peter yun 윤영식
2020. 5. 18. 14:56 React/Architecture

마이크로 프론트앤드는 마이크로 서비스처럼 전체 화면을 작동할 수 있는 단위로 나누어 개발한 후 서로 조립하는 방식이다. 여기서 작동 단위에 사용된 프론트앤드 프레임워크로 Angular 이든, React 또는 Vue 또는 Vanilla 자바스크립트에 상관하지 않고 조합 가능한 방법을 제공한다. 본글에서는 마이크로 프론트앤드 개발 방법중 Angular 프레임워크를 사용하면서 Web Components를 사용한 통합 방법에 대핸 알아보자.

 

마이크로 프론트앤드 기반 독립된 팀별 애플리케이션 개발

 

 

Micro Frontend 개념

마이크로 프론앤드 개념으로 개발을 하는 잇점은 대규모 엔터프라이즈 애플리케이션을 개발한다고 가정할 때, 각 팀별 또는 업무단위에 대해 Backend + Frontend 개발 후 통합하는 이슈를 줄일 수 있다. 

  • 작고, 응집력 있고 유지보수성을 가지는 코드베이스를 가질 수 있다. (Simple, decoupled codebase)
  • 분리배포가 용이하고, 자율적인 팀 조직운영이 수월해진다. (Independent deployment, Autonomous teams)
  • 프론트앤드 개발을 점진적 업그레이드 또는 재작성이 수월해진다. (Incremental upgrades)

하지만 단점도 존재한다. 

  • 배포 번들 사이즈가 커질 수 있다. (Payload size)
  • 서로간의 개발 환경의 차이로 복잡도가 올라간다. (Environment differences
  • 운영 및 거버넌스도 당연히 복잡해진다. (Operational governance complexity)

Thoughtworks의 Technology Radar에 의하면 Micro Frontend가 현재 적용 가능한(Adapt) 상황이다.

마틴 파울러의 글 (또는 번역글) 에서 잘 설명을 하고 있으니 참조하자.

 

 

 

Micro Frontend 통합 방법

독립적인 개발 및 배포

마이크로 프론앤드 방식으로 개발 후 각 단위 애플리케이션을 어떻게 통합할지 고려해야 한다. 통합할 때는 각 화면을 조합하는 컨테이너 애플리케이션이 있고, 그 하부에 들어가는 단위 애플리케이션이 존재한다. (참조)

  • 서버 템플릿 통합: 각 서버로 html 템플릿을 요청하고, 최종 응답서버에서 각 템플릿을 조합해서 응답을 보냄
    • 서버측에서 최종 화면을 조합한다.
  • 빌드타임 통합: 단위 애플리케이션을 패키지로 배포하고, package.json에 명시한 후 컨테이너 애플리케이션에서 import하여 사용하는 방법
    • 각 애플리케이션에 대한 런타임 대응이 안된다. 
    • 애플리케이션을 릴리즈하고 최종 애플리케이션에서 컴파일해야 한다. 
  • iframe 통합: 전통적인 방식이면서 가장 쉬운 방식이다.
    • 애플리케이션 통합의 유연성 높다.
    • 애플리케이션의 기술 종속성이 없다. 
    • routing, history, deep-link같은 것이 복잡해질 수 있다. 
    • 컨테이너 애플리케이션과 iframe에 들어가는 단위 애플리케이션간의 통신규약도 필요하다. 
    • UX가 iframe안에 갇히기 때문에 어색한 UI 표현을 가질 수 있다. 
  • Javascript를 통한 런타임 통합: iframe과 달리 유연한 통합이 가능하다. 현실적으로 가장 많이 사용하는 방식이다.
    • 컨테이너 애플리케이션을 단위 애플리케이션 번들을 <script> 태그를 통합 다운로드 받고
    • 약속된 초기화 메소드를 호출한다.
    • 클라이언트측에서 (브라우져) 통합한다.
  • Web Components를 통한 통합: HTML 커스텀 엘리먼트를 통한 통합방법, static, runtime 통합 둘 다 가능함.
    • Javascript를 통한 런타임 통합과 유사하지만 "The web component way"를 지향한다.
    • 클라이언트측에서 (브라우져) 통합한다.

Mirco Frontend 통합할 때 몇가지 고려사항

  • UI 스타일 일관성은 UI Component Library를 만들어 대응한다.
    • 한번에 만들지 말고, 중복코드가 발생하는 지점에서 만들고
    • 코드 일관성을 유지하는 팀이 수행한다. 
  • 어플리케이션 통신은 Custom events를 사용한다.
    • 커스텀 이벤트를 위해 PubSubJS를 고려해 보자.
    • 호출시 URL 라우팅에 넘기기
  • 백앤드 호출 API 구성
    • BFF(Backend for Frontend Pattern) 패턴으로 프론트앤드 전용 API를 갖는다. 
    • 별도의 데이터베이스를 가질 수도 있다.
    • 로그인은 인증 정보는 통합하는 Container가 소유한다. 

프론트앤드와 백앤드의 구조화

 

 

참조

https://micro-frontends.org/

 

Micro Frontends - extending the microservice idea to frontend development

Techniques, strategies and recipes for building a modern web app with multiple teams using different JavaScript frameworks.

micro-frontends.org

https://martinfowler.com/articles/micro-frontends.html

 

Micro Frontends

How to split up your large, complex, frontend codebases into simple, composable, independently deliverable apps.

martinfowler.com

번역글: https://medium.com/@juyeon.kate/micro-frontends-%EB%B2%88%EC%97%AD%EA%B8%80-1-5-29c80baf5df

 

Micro Frontends 번역글 1/5

이 글은, https://martinfowler.com/articles/micro-frontends.html 페이지를 번역한 글 입니다.

medium.com

https://www.thoughtworks.com/radar/techniques/micro-frontends

 

Micro frontends | Technology Radar | ThoughtWorks

This Technology Radar quadrant explores the techniques being used to develop and deliver software

www.thoughtworks.com

https://www.angulararchitects.io/aktuelles/angular-elements-part-i/

 

Angular Elements, Part I - ANGULARarchitects

A dynamic dashboard in four steps with Web Components

www.angulararchitects.io

 

posted by peter yun 윤영식
2020. 4. 23. 17:53 React/Architecture

React를 해야하지 접근했다 다시 놓고, 접근했다 다시 놓고 여러번의 시도를 하면서 이번에도? 생각할 수 있지만 이제는 정말 필요에 의해서 해야겠다는 생각이 든다. Angular를 할 수록 다른 것을 써봐야겠다는 욕구가 더 강해지고, 앞으로 개발할 소프트웨어에 어느 것이 더 적합할지 판단하기 위해 React를 다시 들여다 보고 있다. 그래서 Rethinkg React이지만 그속의 개념을 암기용으로 간단히 정리해 본다. 

 

 

ReactDom 

ReactDom은 Real DOM에 React Element 이 업데이트 하는 것을 관리한다.

SPA 구성할 때 ReactDOM.render(element, selector) 한번만 호출한다.

 

JSX

JSX는 React Element이다.

JSX를 React,createElement(type, properties, children)으로 쓸 수도 있다.

JSX의 attributes와 children은 "props" 객체를 통해 컴포넌트로 전달된다.

 

Component & props

컴포넌트는 SPA기반 개발의 경우 UI를 독립적으로 쪼개고, 격리시켜서 개발할 수 있고, 재사용 가능한  단위이다.

컴포넌트는 props를 가진다. props는 read only 이다.

컴포넌트는 순수함수로(Pure Function)로 컴포넌트를 만들 수 있다. 순수함수의 argument로 자동 전달된다.

컴포넌트는 ES6의 class 로 정의할 수 있다. 

컴포넌트는 데이터를 맵핑해서 JSX 조각을 리턴할 뿐이다. (순수함수는 JSX조합 리턴, 클래스 컴포넌트는 render메소드에서 JSX조합 리턴)

props 객체는 컴포넌트를 조합할(Composition) 때 하위 컴포넌트로 값을 내려 보낼수도 있는 객체이다. 

props 객체는 컴포넌트 -> 컴포넌트로의 값 전달 단위이다. Data flow Down => Top-Down unidirection flow

state가 있으면 stateful 컴포넌트, props만 사용하면 stateless 컴포넌트이다.

 

State & LifeCycle

Local state는 컴포넌트내부에서 DOM과 대화할 수 있는 유일한 수단이다.

Local state에 대해 컴포넌트안에서 값을 변경하고 JSX에서 반영한다. 

Local state는 setState의 Async 호출은 컴포넌트의 render()를 재수행토록 한다.

this.state 객체 값변경은 반드시 setState만을 통해 수행한다. 즉, this.state 값변경의 화면의 업데이트를 위한 것이다.

this.state와 this.props의 값은 Async하게 바뀐다. 

   - setState할 때 this.state값을 개별적으로 업데이트하면 변경된 값만 반영된다. 

   - setState안에서 this.state와 this.props를 사용할 경우 (state, props) => { ... } 함수를 등록하여 사용한다. 이때 첫번째 인자인 state는 이전 state 객체값이다. 

LifeCycle을 통해 업데이트 하는 시점을 잡는다. 컴포넌트 시작 componentDidMount, 종료되기전 componentWillUnmount

 

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
더보기
  1. When <Clock /> is passed to ReactDOM.render(), React calls the constructor of the Clock component. Since Clock needs to display the current time, it initializes this.state with an object including the current time. We will later update this state.
  2. React then calls the Clock component’s render() method. This is how React learns what should be displayed on the screen. React then updates the DOM to match the Clock’s render output.
  3. When the Clock output is inserted in the DOM, React calls the componentDidMount() lifecycle method. Inside it, the Clock component asks the browser to set up a timer to call the component’s tick() method once a second.
  4. Every second the browser calls the tick() method. Inside it, the Clock component schedules a UI update by calling setState() with an object containing the current time. Thanks to the setState() call, React knows the state has changed, and calls the render() method again to learn what should be on the screen. This time, this.state.date in the render() method will be different, and so the render output will include the updated time. React updates the DOM accordingly.
  5. If the Clock component is ever removed from the DOM, React calls the componentWillUnmount() lifecycle method so the timer is stopped.

 

이벤트 처리

이벤트 핸들러는 JSX안에서 함수 형태로 전달한다. 

함수 호출이 아닌, 함수를 서절하는 것이므로 preventDefault 하기위해 false를 리턴할 수 없는 구조이다. preventDefault()를 명시적으로 호출한다. 

React Element는 HTML의 이벤트가 아닌 SyntheticEvent를 사용해서 cross-browser 호환성을 고민할 필요가 없다.

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

TypeScript 방식의 경우 

  • { (params) => this.method(params) }
  • { this.method.bind(this, params) }
import React, { Component } from 'react';

interface State {
    isToggleOn: boolean;
}

export default class App2 extends Component<{}, State> {
    state: State = { isToggleOn: true };
    render() {
        return <button onClick={() => this.handleClick()}>{this.state.isToggleOn ? 'ON' : 'OFF'}</button>;
    }

    private handleClick() {
        this.setState(state => ({
            isToggleOn: !state.isToggleOn
        }));
    }
}

 

조건이 맞을 때 화면 렌더링 (Conditional Rendering)

{expression} 을 통해 JSX 를 리턴할 수 있다. 이를 위해 Short curcuit, 삼항식, 고차함수 사용가능. expression 이니깐...

expression의 리턴값이 null이면 화면 렌더링을 하지 않는다. 이때는 render가 호출안되고 componentDidUpdate만 호출된다.

 

 

Lists 와 Keys

JSX map의 리턴으로 받은 JSX List를 {[JSX, JSX, JSX]}으로도 화면 렌더링을 할 수 있다. 

map list할 때는 특별한 key attribute를 list element에 설정을 해야한다. 그래야 warning 안남, map에서 반드시 key 사용하기

Key는 아이템의 변경을 체크하는데 사용된다. key값은 unique해야 한다. map에서 index를 사용하지 말고 별도의 값을 사용하자

Key는 한번 순수함수 컴포넌트로 맵핑한 해당 컴포넌트에 할당한다. 

Key는 array안에서만 Unique하면 된다.

props.key는 map list에 예약 attribute이니 일반 컴포넌트에서 props로 key를 사용하지 말자

import React, { Component } from 'react';

// key설정하지 않고 반복하는 React Element
const ListItem = ({ value }) => <li>{value}</li>;
const numbers = [1, 2, 3, 4];
const List = ({ isToggleOn }) => {
    return isToggleOn ? (
        <ul>
            // map을 embedding했다. expression이니깐 당연히 가능
            {numbers.map((number, index) => (
                // 반복되는 React Element에 key를 설정한다. 
                <ListItem key={number.toString()} value={number} />
            ))}
        </ul>
    ) : (
        <div>There is no list.</div>
    );
};

interface State {
    isToggleOn: boolean;
}
export default class App2 extends Component<{}, State> {
    state: State = { isToggleOn: false };
    render() {
        return (
            <>
                <button onClick={() => this.handleClick()}>{this.state.isToggleOn ? 'ON' : 'OFF'}</button>
                <List isToggleOn={this.state.isToggleOn} />
            </>
        );
    }

    private handleClick() {
        this.setState(state => ({
            isToggleOn: !state.isToggleOn
        }));
    }
}

 

Forms 

form elements인 <input>, <textarea>, <select> 같은 것은 자신의 state를 가지고, 사용자 input을 기반으로 state를 업데이트한다.

React에 의해 값이 제어되는 input form element를 controlled component라 한다.

<select value="state value"> <option/> ... </select> 로 root <select>에서 value를 selected attribute로 대치한다. 

import React, { Component } from 'react';

interface State {
    value: string;
}
export default class App2 extends Component {
    state: State = { value: '0' };

    render() {
        return (
            <form onSubmit={this.handleSubmit.bind(this)}>
                <select value={this.state.value} onChange={this.handleChange.bind(this)}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>
            </form>
        );
    }

    private handleSubmit(event) {
        event.preventDefault();
    }

    private handleChange(event) {
        this.setState({ value: event.target.value });
    }
}

<input type="file"> is uncotrolled component 이다. 

여러개의 input을 다룰때는 input 태그이 name을 통해 setState에 값을 할당한다.

setState는 부분적인 state를 현재 state에 합친다. 

Formik같은 패키지를 써보자.

 

 

Composition vs Inheritance

React는 컴폰넌트사용에서 Composition을 추천한다. 

props.children를 사용하거나, props를 통해 함수 컴포넌트를 전달할 수 있다. props통해 함수를 전달할 수 있음. (Lifting state up 참조)

// props.children을 사용하여 composition
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

// props를 통해 컴포넌트를 전달하여 composition
function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

 

참조

- reactjs.org

posted by peter yun 윤영식
2018. 9. 14. 17:31 React

실제 프로젝트에서 사용을 안하다보니 자꾸 처음 내용이 익숙해 지지 않은 상태에서 잊어버리고 만다. React + Typescript기반을 다시 시작해 본다. 



Install React with Typescript

React와 Typescript 환경을 만든다

// 2018.9 현재 LTS NodeJS 버전

$ nvm use 8.12.0

// typescript v3.0.3 으로 업데이트

$ npm i -g typescript 

$ npm i -g create-react-app (or yarn global add create-react-app)


// TYPESCRIPT-CSS 

$ create-react-app my-app --scrips-version=react-scripts-ts


package.json에 eject기능이 있어서 webpack config를 밖으로 추출하여 직접 핸들링할 수 있게 한다. 

$ cd my-app

$ npm run eject



SCSS 환경구축

css 환경을 scss 환경으로 바꿔준다. 

// scss loader 설치

$ yarn add node-sass sass-loader --dev


App.css와 index.css의 확장자를 .scss로 바꾸고, App.tsx, index.tsx의 import 문 확장자를 .scss로 바꾼다. 

import './App.scss';


config/webpack.config.dev.js 와 webpack.config.prod.js 파일안에 scss설정을 추가한다. 빨간 부분을 

      {

            test: /\.(css|scss)$/,

            use: [

              require.resolve('style-loader'),

              {

                loader: require.resolve('css-loader'),

                options: {

                  importLoaders: 1,

                },

              },

              {

                loader: require.resolve('postcss-loader'),

                options: {

                  // Necessary for external CSS imports to work

                  // https://github.com/facebookincubator/create-react-app/issues/2677

                  ident: 'postcss',

                  plugins: () => [

                    require('postcss-flexbugs-fixes'),

                    autoprefixer({

                      browsers: [

                        '>1%',

                        'last 4 versions',

                        'Firefox ESR',

                        'not ie < 9', // React doesn't support IE8 anyway

                      ],

                      flexbox: 'no-2009',

                    }),

                  ],

                },

              },

              {

                loader: require.resolve("sass-loader"),                

                options: { } 

              }

            ],

      },


기존 App.scss내용을 다음과 같이 바꾸어 확인해 본다. 

.App {

  text-align: center;

  &-logo {

    animation: App-logo-spin infinite 20s linear;

    height: 80px;

  }


  &-header {

    background-color: rgb(197, 40, 40);

    height: 150px;

    padding: 20px;

    color: white;

  }


  &-title {

    font-size: 1.5em;

  }


  &-intro {

    font-size: large;

  }

}

yarn start하여 점검!



Component LifeCycle


일반 컴포넌트

  constructor -> componentWillMount -> render -> componentDidMount -> componentWillUnmount


props, state 사용 컴포넌트

  componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

  - shouldComponentUpdate: true, false로 다음으로 이벤트를 넘길수도 안할수도 있음




Component Type

PureComponent

  shouldComponentUpdate에서 Shallow compare하므로 reference를 바꾸어 주어야 render가 호출됨. 


Functional Component (Stateless Function Component)

  const myComp: Reat.SFC<Props> = (props) => {...}

import * as React from“ react”

interface WelcomeProps {

  name: string,

}


const Welcome: React.SFC < WelcomeProps > = (props) => {

  return <h1 > Hello, {

    props.name

  } < /h1>;

}



Router for SPA

react-router v4

   BrowserRouter, Route, Link, NavLink, Redirect 사용

   BrowserRouter는 window.history.pushState() 로 동자하는 라우터

   RouteComponentProps: route되면서 url의 파라미터를 props로 받음. history, match 속성을 가짐.

<BrowserRouter>

   <Route exact={true} path="/" component={} or render={} or children={} >

      <Link to="/a" />

      <NavLink activeStyle={{ color: red }} to="/b" /> 

   </Route>

   <Redirect from="/a" to="/b" />

</BrowserRouter>


Switch로 감쌈

  <Switch>

    <Route ... />

    <Route ... /> 

    <Route ... />

 </Switch>



Redux

react-redux

  여러  action을 reducer를 통해 하나의 store를 만든다.

     - action 타입을 만들고 action 객체를 만드는 펑션을 (action creator) 정의한다.

     - action을 처리하는 reducer 펑션을 정의한다. (Pure function, immutable)

        reducer 펑션을 action별로 나눈 다음 사용시에는 combineReducers 로 합쳐서 사용한다. 

     - reducer를 createStore에 넣어주면 single Store가 만들어진다. 

        store에는 getState(), dispatch(액션), subscribe(리스너),  replaceReducer(다른리듀서) 4개 메소드가 있음

   redux를 react에 연결하기

      - componentDidMount: subscribe

      - componentWillUnMount: unsubscribe

      - Provider는 context에 store를 담아 모든 component에서 store를 사용할 수 있도록 한다. 

         Provider를 제공하는 react-redux를 사용한다.  

   connect를 통해 컴포넌트에 연결한다. 

      - App에 대한 High order component이다.

      - 전체 state의 모양과 action creator를 파라미터로 넣어준다.

$ yarn add redux react-redux @types/redux @types/react-redux


const mapStateToProps = (state: { age: number}) => {

  return {

     age: state.age

  }

}


const mapDispatchToProps = (dispatch: Function) => {

  return {

      onAddAge: () => { dispatch(addAge()); }

  };

}


interface AppProps {

  age: number;

  onAddAge: void;

}


connect(mapStateToProps, mapDispatchToProps)(App);


class App<{props: AppProps}> extends Component {

    return <div>{this.props.age}</div>

}


  dispatch안에서 async처리를 할 수 있다.   

  applyMiddleware는 dispatch 전후에 처리할 수 있다. 

  action이 async일 때 미들웨어인 redux-thunk를 이용한다.



<참조>

- 2017 Typescript Korea 강의 (유튜브)

- scss 적용하기

- scss loader 설정하기, Class 사용 in Velopert

- Typescript + React 환경 만들기 in Velopert

- React LifeCycle 설명

- Component Type - SFC 설명

- React Router in Velopert 소개





'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[Flux] Flux 배우는 방법  (0) 2015.07.04
[React] AngularJS와 ReactJS 비교  (0) 2015.07.01
[React] 배우는 방법  (0) 2015.05.15
posted by peter yun 윤영식
2015. 8. 23. 17:48 React

Semantic UIReact 컴포넌트로 만들고 NPM 저장소와 Meteor 저장소인 Atmosphere에 배포해 보자. 토요일 헤커톤 모임을 갖고 스마트링크 멤버분들과 함께 진행했다. 목표는 React Bootstrap 과 같은 오픈소스 패키지를 만들어 보는 것이다. 처음엔 활짝 웃고 있는 모습!







준비 운동 


함께 개발을 진행하는 관계로 코딩 컨벤션은 에어비엔비의 React 코드 컨벤션으로 한다.  다음으로 깃헙(GitHub) 저장소를 만들었다. 소셜 코딩은 역시 깃헙이다. 깃을 사용하므로 커밋 메세지를 잘 작성하면 별도의 CHANGELOG를 작성할 필요가 없다. 따라서 Git Commit에 대한 컨벤션도 앵귤러에서 사용하는 방식을 쓴다. 그리고 정보를 찾고 공유하는 것은 슬랙(Slack)을 이용하고, 할일에 대한 보드는 트렐로(Trello)를 이용해 서로 공유하고 있다. 트렐로를 슬랙과 연동하면 슬랙에서 모든 것을 확인 할 수 있다. 


일단 npm 초기화을 초기화하고 모듈을 설치한다.  package.json 에 설치하는 모듈은 다음과 같다. 

  - ES6를 사용하기에 Babel을 통해 ES5로 트랜스파일한다. 

  - React로 설치하고, classnames는 리액트 코드안에서 클래스명을 확장해 주는 기능을 한다. 

  - fbjs는 ES7의 dectorator를 사용해 React Start Kit에 있는 withStyle을 컴포넌트별로 적용해 보려 했으나 원하는 동작이 안된다. 제거해도 좋음. 

  - *-loader는 웹팩(Webpack)을 통해 전체 패키지를 하나의 배포 파일로 번들링하기 위해 설치한다. 

package.json에 설정을 넣고 한번에 npm install 해서 설치하자

// package.json 일부 내역 


"dependencies": {

  "babel": "^5.8.21",

  "react": "^0.13.3",

  "classnames": "^2.1.3",

  "fbjs": "0.1.0-alpha.7"

},

"devDependencies": {

  "babel-core": "^5.8.22",

  "babel-loader": "^5.3.2",

  "css-loader": "^0.15.6",

  "fbjs": "0.1.0-alpha.7",

  "semantic-ui-css": "^2.0.8",

  "style-loader": "^0.12.3",

  "url-loader": "^0.5.6",

  "webpack": "^1.11.0"

}

 

다음으로 웹팩 환경파일인 webpack.config.js 를 만든다.  웹팩은 사전에 설치할 것이 있는데 홈페이지의 Getting start를 보자. 그리고 어떻게 잘 사용할지는 페북 엔지니어인 피트헌트의 Webpack Howto를 참조한다. 설정 내용은 다음과 같다. 

  - 배포 파일 명칭은 reactjs-semantic-ui.js 파일이고, /src/index.js 파일에 모든 참조 내역을 작성한다. 

  - 배포시 접근하는 네임스페이스는 RSU (React Semanctic Ui) 이다. 예로 본 패키지를 리액트 코드에서 사용하면 RSU.Button 식의 접근이다. 

  - .js 또는 .jsx 확장자는 babel-loader로 ES6를 ES5로 트랜스파일한다. 

  - 웹팩은 css 파일과 이미지 파일도 함께 번들링 하므로 style-loader 와 css-loader를 설정하고, 이미지와 폰트 관련해서 url-loader를 설정한다. 

  - react.js 관련 파일은 웹팩으로 번들링시에 제외토록 externals를 설정한다. 

// webapck.config.js 내역 


module.exports = {

entry: {

'reactjs-semantic-ui': './src/index.js'

},

output: {

path: './dist',

filename: '[name].js',

library: 'RSU',

libraryTarget: 'umd'

},

module: {

loaders: [

{

test: /\.jsx?$/,

exclude: /(node_modules|bower_components)/,

loader: 'babel-loader'

                       },

{

test: /\.css$/,

                                exclude: /\.useable\.css$/,

loader: 'style-loader!css-loader'

},

{

test: /\.(png|jpg|svg|eot|woff|woff2|ttf)$/,

loader: 'url-loader?limit=8192'

} // inline base64 URLs for <=8k images, direct URLs for the rest

                ]

},


externals: [

          {

              'react': {

                   root: 'React',

                   commonjs2: 'react',

                   commonjs: 'react',

                   amd: 'react'

             }

         }

     ]

}


Babel 사용시 주의 점은 간혹 ES7 의 decorator를 사용하거나 특정 스펙 레벨까지 적용하고 싶다면 루트에 .babelrc를 생성하고 stage를 설정해야 한다. stage 설정 가이드를 참조하자.

{

    "stage" : 0

}




 

개발 시작


환경 준비하며 문제점을 해결하는데 3시간 가까이 소비를 했다. 본격적으로 시멘틱 UI를 리액트화 하기 위해 다음과 같은 순서로 작업을 한다. 

  - src/index.js안에 작성한 컴포넌트들을 설정한다. 

  - 시멘틱 UI가 구분해 놓은 Elements, Collections, Views ... 와 같이 구분해서 폴더를 만들어 그밑으로 컴포넌트를 만든다. 

  - 시멘틱 UI의 core css는 reset.css 로 Reset 클래스를 하나 만들었다. 

  - ES6 구문을 사용한다. ES5 Features를 참조하자 

    + import ... from 

    + { key : value } 가 동일 설정이면 { key1, key2 }로 나열 

    + export default RSU 

// Globals
import Reset from './globals/Reset';

// Elements
import { Button, Buttons } from './elements/Button';
import Container from './elements/Container';
import Dimmer from './elements/Dimmer';
import Divider from './elements/Divider';
import Flag from './elements/Flag';
import Header from './elements/Header';
import Icon from './elements/Icon';
import Image from './elements/Image';
import Input from './elements/Input';
import Label from './elements/Label';
import { List, Item } from './elements/List';
import Loader from './elements/Loader';
import Rail from './elements/Rail';
import Reveal from './elements/Reveal';
import Segment from './elements/Segment';
import { Step, Steps } from './elements/Step';

// Collections
import Breadcrumb from './collections/Breadcrumb/Breadcrumb';
import BreadcrumbDivider from './collections/Breadcrumb/BreadcrumbDivider';
import BreadcrumbSection from './collections/Breadcrumb/BreadcrumbSection';
import Form from './collections/Form/Form';
import Grid from './collections/Grid/Grid';
import Column from './collections/Grid/Column';
import Row from './collections/Grid/Row';
import Menu from './collections/Menu/Menu';
import Message from './collections/Message/Message';
import Table from './collections/Table/Table';

// Views
import Card from './views/card/Card';
import CardImage from './views/card/CardImage';
import CardContent from './views/card/CardContent';
import CardHeader from './views/card/CardHeader';
import CardMeta from './views/card/CardMeta';
import CardDescription from './views/card/CardDescription';

const RSU = {
    // Elements
    Button, Buttons,
    Container,
    Dimmer,
    Divider,
    Flag,
    Header,
    Icon,
    Image,
    Input,
    Label,
    List, Item,
    Loader,
    Rail,
    Reveal,
    Segment,

    Step, Steps,

    // Views
    Card,
    CardImage,
    CardContent,
    CardHeader,
    CardMeta,
    CardDescription,

    // Collections
    Breadcrumb,
    BreadcrumbDivider,
    BreadcrumbSection,
    Form,
    Grid,
    Row,
    Column,
    Menu,
    Message,
    Table
}
 

export default RSU;


리액트 컴포넌트는 다음과 같이 만든다. 1주차엔 시멘트 UI의 css를 적용해서 잘 나오는지만 확인하는 것으로 리액트 컴포넌트의 라이프사이클 메소드를 전부 설정해서 넣는다. 

  - 어빈앤비의 리액트 코딩 컨벤션에 따라 작성한다. 

  - shouldComponentUpdate 구문은 향후 Immutable.js 사용과 PureRenderMixin의 조합을 고려해 성능을 향상시켜야 한다. 

    이에 대한 좋은 글이 있으니 꼭 읽어보자.  Immutable.js에 대한 연재글 [1], [2], [3]

  - 리액트의 라이프 사이클 코드가 대부분의 컴포넌트에 중복해서 들어 있으므로 이것도 ES6 구문을 위한 리액트의 Mixin 패키지를 사용해 리팩토링해야 한다.

  - classNames( x, y, this.props.className )은 classnames 패키지를 사용해서 <Card className="ui card <상속 className>"> 을 설정해 준다.  

  - <div {...this.props} 를 하면 상위에 설정한다. 모든 key="value" 애트리리뷰트를 그대로 설정해준다. 단, 주의 점은 <div {...this.props} className...> 처럼 사용하면 className은 오버라이딩 된다는 점이다. 만일 <div className={componentClass} {...this.props}> 로 하고 <Card className="dowon" /> 를 사용하면 className={componentClass} 는 적용이 되지 않는다. 즉 태그안에서 중복된 키이면 뒤에 놓은 것으로 설정이 덮어써진다.

import React, { Component, PropTypes } from 'react';
import card from 'semantic-ui-css/components/card.css';
import classNames from 'classnames';

const propTypes = {
	className: PropTypes.string,
};

const defaultProps = {
	className: ''
};

class Card extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  };

  componentWillMount() {};
  componentDidMount() {};
  componentWillReceiveProps(nextProps) {};
  shouldComponentUpdate(nextProps, nextState) {
    return true;
  };
  componentWillUpdate(nextProps, nextState) {};
  componentDidUpdate(prevProps, prevState) {};
  componentWillUnmount() {}

  render() {
    var componentClass = classNames(
      'ui',
      'card',
      this.props.className
    );

    return (
      <div {...this.props} className={componentClass}>{this.props.children}</div>
    )
  };
}

Card.propTypes = propTypes;
Card.defaultProps = defaultProps;
 

export default Card;


학학 다들 컴포넌트 만드는데 저녁 7시를 치닫고 있다. 아 나만 힘들어...






테스트 하기 


만들어진 컴포넌트를 테스트하기 위해 src 폴더 외에 docs 폴더를 만들어 예제를 만든다. 

  - 시멘틱 UI 처럼 폴더 구조를 가지고 각각 index.html파일을 만든다. 

  - 패키지를 사용하는 입장이기 때문에 react.min.js를 넣고 테스트용이기 때문에 <script type="text/jsx;harmony=true"> 해석을 위해 JSXTransformer도 넣었다.

  - 테스트 코드는 Card.js 이면 CardExample.js 로 해서 작성한다. 

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title> Reactjs Semantic Ui </title> </head> <body style="margin-left: 20px; margin-top: 20px;"> <div id="app"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script> <script src="../../dist/reactjs-semantic-ui.js"></script> <script type="text/jsx;harmony=true" src="./CardExample.js"></script> <script type="text/jsx;harmony=true"> // Card React.render( <CardExample />, document.getElementById('app') ); // </script> </body> 

</html>


CardExample.js를 다음과 같다. 

  - import 구문이 안먹는다. 이유 아는분 메세지 좀 주세요. 그래서 render안에 RSU 네임스페이스 제거하고 사용한다. 

  - Card 관련 <div class="xxx"> 설정을 리액트 컴포넌트로 만들어 사용해 본것이다. 

// import { Card, CardImage, CardContent, CardHeader, CardMeta, CardDescription } from 'RSU';

class CardExample extends React.Component {

  render() {
    var Card = RSU.Card,
        CardImage = RSU.CardImage,
        CardContent = RSU.CardContent,
        CardHeader = RSU.CardHeader,
        CardMeta = RSU.CardMeta,
        CardDescription = RSU.CardDescription

    return (
      <div>
        <h3><a href="http://semantic-ui.com/views/card.html" target="_blank">Card</a></h3>
        <Card>
          <CardImage onClick={Alert} src="../assets/images/kristy.png" />
          <CardContent>
            <CardHeader desc="Kristy" />
            <CardMeta>
              <span className="date">Joined in 2013</span>
            </CardMeta>
            <CardDescription>
              Kristy is an art director...
            </CardDescription>
          </CardContent>
          <CardContent className="extra">
            <a>
              <i class="user icon"></i>
              22 Friends
            </a>
          </CardContent>
        </Card>
      </div>
    );
  }
 

}


이제 테스트를 위해 "npm install -g webpack-dev-server"를 설치하고 실행한다. 실행은 소스 루트에서 한다. 

  - 호출은 http://localhost:8080/docs/views 로 직접 경로를 지정한다. 

$ webpack-dev-server --progress --colors

  0% compilehttp://localhost:8080/webpack-dev-server/

webpack result is served from /

content is served from /Users/yunyoungsik/mobicon/open-sources/unplugdj/src/reactjs-semantic-ui

Hash: 0d552960154cf0b24cf3

Version: webpack 1.11.0

Time: 2759ms

                 Asset    Size  Chunks             Chunk Names

reactjs-semantic-ui.js  113 kB       0  [emitted]  reactjs-semantic-ui

chunk    {0} reactjs-semantic-ui.js (reactjs-semantic-ui) 110 kB [rendered]

    [0] ./src/index.js 418 bytes {0} [built]

    [1] ./src/elements/Button.js 4.6 kB {0} [built]

    [3] ./~/semantic-ui-css/components/button.css 870 bytes {0} [built]

    [4] ./~/css-loader!./~/semantic-ui-css/components/button.css 87.5 kB {0} [built]

    [5] ./~/css-loader/lib/css-base.js 1.51 kB {0} [built]

    [6] ./~/style-loader/addStyles.js 6.09 kB {0} [built]

    [7] ./src/utils/withStyles.js 3.86 kB {0} [built]

    [8] ./~/fbjs/lib/invariant.js 1.51 kB {0} [built]

    [9] (webpack)/~/node-libs-browser/~/process/browser.js 2.02 kB {0} [built]

   [10] ./~/fbjs/lib/ExecutionEnvironment.js 1.09 kB {0} [built]

     + 1 hidden modules

webpack: bundle is now VALID.


결과화면이다. 예들 상단에 시멘틱 UI쪽에 관련 링크를 걸기로 한다. 


앞으로 시멘틱 UI의 자바스크립트 코드를 리액트에 넣고 나머지 부분을 컴포넌트화 해야 한다. 그리고 성능 향상과 중복 코드 제거를 위한 Mixin을 이용한 리팩토링이 필요하다. 그 후엔 패키지 배포. 다음 시간에 다시 2차 헤커톤을 진행하기로 하고 저녁 10시에 종료! 




To Be Continue...

'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[Flux] Flux 배우는 방법  (0) 2015.07.04
[React] AngularJS와 ReactJS 비교  (0) 2015.07.01
[React] 배우는 방법  (0) 2015.05.15
posted by peter yun 윤영식
2015. 8. 15. 12:26 React

서비스를 개발할 때 만들어 놓은 CSS 프레임워크를 사용한다면 보통 트위터 부트스트랩을 많이 선택한다. 앵귤러 프레임워크를 사용할 때 트위터 부트스트랩을 앵귤러 컴포넌트로 만들어 놓은 모듈을 사용하는데, 리액트에서는 어떤 것을 사용할 수 있는지 정리해 보자. 




트위터 부트스트랩

http://react-bootstrap.github.io/

  + v1.0.0 이 릴리즈 되었다. 

  + 부트스트랩의 기능을 충실히 구현




머터리얼 디자인

http://material-ui.com/

  + 구글의 머터리얼 디자인을 충실히 구현

  + 모바일 first 디자인




그외 프레임워크 

http://elemental-ui.com/

  + 가장 기본적인 부분들을 리액트 컴포넌트로 제공


http://nikgraf.github.io/belle/

  + elemental-ui와 유사


http://semantic-ui.com/

  + 리액트 컴포넌트로 만들어져 있지 않지만 앞으로 만들어 보고 싶은 css 프레임워크

  + v1.* 보다 다양한 실용적인 Theme을 제공하고, 컴포넌트가 다양하다. 

  + 향후 직접 React UI Component로 만들어 사용할 예정!





모바일 전용 프레임워크 

http://touchstonejs.io/

  + ionic 의 css 와 유사하게 native 느낌을 준다. 





<참조>

- 리액트 12가지 css 프레임워크

- 리액트로 만든 관리자 화면 예제 : ClojureScript를 사용해서 React 컴포넌트를 개발했음

- 다양한 React UI Components 목록



'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[Flux] Flux 배우는 방법  (0) 2015.07.04
[React] AngularJS와 ReactJS 비교  (0) 2015.07.01
[React] 배우는 방법  (0) 2015.05.15
posted by peter yun 윤영식
2015. 7. 4. 22:41 React

Flux는 페이스북이 ReactJS를 보다 더 큰 애플리케이션에 적용하기 위해 만든 아키텍처이다. 그러기 때문에 페이스북 웹 서비스와 인스타그램에도 적용되어 사용되고 있다. 하지만 처음 접하는 개발자에게는 생소한 패턴이고 새로운 용어들이 난무한다. Flux를 제대로 이해하기 위해서는 먼저 ReactJS의 데이터 흐름을 이해한 후 Flux를 보면 도움이 된다.  





Flux 넌 뭐니?


스터디에서 발표한 Flux 패턴에 대한 설명 동영상을 보자. 1시간 가량의 발표 및 토론 내용이다. 1주일동안 짬을 내어 Flux 예제 소스를 분석해 보았고, Flux 내의 Action, Dispatcher, Store, View 간의 데이터 흐름과 각각의 역할에 대해 살펴보고 있다. Flux를 이해하고 싶다면 본 예제를 돌려보고 설명하는 흐름에 따라 개념을 잡아보자. 

  - CartApp 예제에 대한 원본 글

  - CartApp 데모


   


발표한 내용의 PPT 자료도 동영상을 보며 살펴보자 

   - Flux 아키텍처에 대한 원본 글 


    




AngularJS에서 Flux 아키텍처 적용하기 


Flux는 아키텍처이고 사상일 뿐이다. 따라서 AngularJS에서도 당연히 적용해 볼 수 있다. 

  - AngularJS에서 Flux 적용하기 예제




참조 

  - Cart 소스 

  - ReactJS 개념 및 에코 시스템 이해하기 (스마트 링크 팀블로깅)

  - AngularJS vs ReatJS 이해하기

  - Flux 패턴 구현체들 (Fluxxor)

  - Flux pattern 설명

  - Flux를 이용한 Note App 예제

  - SlideShare: ReactJS와 Flux에서 데이터 흐름 설명 

'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[Flux] Flux 배우는 방법  (0) 2015.07.04
[React] AngularJS와 ReactJS 비교  (0) 2015.07.01
[React] 배우는 방법  (0) 2015.05.15
posted by peter yun 윤영식
2015. 7. 1. 09:17 React
AngularJS로 여러번의 프로젝트를 했었다. 그럴때 마다는 느끼는 보다낳은 컨트롤러의 구조는 무엇일까? 그리고 $scope를 어떻게 하면 잘 사용할 수 있을까이다. 어쩌면 $scope에 대해 고민하기 보다 다른 접근법을 시도해 보는 것은 어떨까? 그래서 ReactJS를 공부하기 시작했다. 1주일밖에 안된 입장에서 어떻게 접근하는 것이 좋을지 간략히 정리하고 스마트링커들과 토론한 내용을 올린다. 



AngularJS와 ReactJS 비교

  - AngularJS와 ReactJS간의 개발 방법론에 대한 스터디 자료 

    



  - MVC 패턴과 ReactJS의 차이점을 이야기한다. 

   + 해당 발표를 보면 위에서 내가 설명한 내용을 좀 더 이해하기 쉽지 않을까 싶다.

   


'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[Flux] Flux 배우는 방법  (0) 2015.07.04
[React] AngularJS와 ReactJS 비교  (0) 2015.07.01
[React] 배우는 방법  (0) 2015.05.15
posted by peter yun 윤영식
2015. 5. 15. 11:38 React

ReactJS(리액트)는 자바스크립트로 UI 컴포넌트를 만드는 프레임워크이다. 만들어진 컴포넌트를 사용할 수도 있고, 수정할 수 있고, 조합할 수 있으며 자신만의 UI 컴포넌트를 직접 만들 수 있다. 즉 프론트앤드 UI의 화면을 컴포넌트 조합으로 만들고, 화면의 데이터를 앵귤러처럼 자동 업데이트 해주지만 앵귤러와 틀리게 단방향 데이터 바인딩이다. 리액트는 앵귤러(AngularJS)의 지시자(Directive)와 비교되지만 훨씬 간단하고 성능 또한 월등하다. 이제 배우고 익혀 사용할 때가 되었다. 







개념잡기 


  - 쿼라(Quora)에서 이야기하는 장/단점을 살펴보자. 

     장점

       + Virtual DOM 을 JS로 구현해서 UI 컴포넌트의 속도가 엄청 빠르다. 

       + Component의 재사용과 복잡한 UI 컴포넌트 조합이 가능하다.

       + Uni-direction(단방향) 방식하에 데이터가 변경되면 관련된 모든 UI 컴포넌트가 변경된다.

       + commonJS / AMD 패턴과 잘 조합되고, 크롬 익스텐션을 통해 디버깅할 수 있다. 

     단점

       + 초보자들에겐 역시 러닝 커브가 존재한다.

       + 다른 프레임워크가 동작하기 위해선 부가적인 작업들이 필요하다. 

       + 앵귤러처럼 router, model등 Frontend Full Framework을 제공하지 않아서 꼭 다른 프레임워크와의 조합이 필요하다. 


  - 처음에는 무조건 공식 홈페이지의 가이드를 보자. 

      + Quick Start

      + Advanced Guides

      + Reference


  - 프리젠테이션으로 정리해 보자. 재미있게 진행되니 WTF는 날리지 말자.

     

  - egghead.io 에서 리액트에 관련하여 ReactJS, React Native, Flux Architecture를 동강으로 제공한다. 물론 무료가 있다. 
    * 유료강좌를 듣길 추천한다. 앵귤러와 리액트에 대한 좋은 강좌가 많다. 한달 대략 15달러
    + Render MethodProperties, State 사용하기

  - 제이쿼리를 리액트로 바꾸는 과정을 잘 설명해 주고 있다. 글을 읽고 다시 egghead.io 를 보면 이해가 될 것이다. 

    + jQuery to React 가이드

       내용중에 앵귤러를 사용하고 있는 개발자라면 앵귤러의 지사자에서 scope: { createXXX: '&' } 방식으로 자식의 부모의 함수를 호출 할 수 있다

       그리고 자식, 부모간에 scope 객체의 상속을 통해 scope 속성을 접근할 수 있다는 것도 알 수 있다. 

    + Flux 소개 아래 영상을 보면 react가 Virtual DOM을 통해 더 낳은 성능과 단순성을 통한 버그 가능성 제거를 이룰 수 있는지 가늠할 수 있다

       * Flux 사이트

      


  - 이제 다시 설치부터 기초 개념을 문서를 통해 쭉 살펴보자. 

  - ng-conf 말고 react-conf도 있다. 동영상을 통해 향후 방향을 점쳐보자. 
    

  - 리액트 컴포넌트를 찾고 싶을 경우 


  - 리액트 자료 

    + Awesome 자료 목록들

    + 앵귤러와 결합해서 사용하기 : NGREACT 


  - 리액트 컴포넌트 프레임워크 : 트위터 부트스트랩같은 조합

   + reapp.io


  - 새 술은 새 푸대에 넣자. 리액트가 UI 컴포넌트라면 Webpack 또는 Browserify 를 이용해 모듈화 하자
    + 리액트를 위한 웹팩의 starter kit

  - 디버깅은 Chrome Extention 설치하고 시작
  - 필요에 맞는 툴과 환경을 선택한다.

 


Immutable Data에 대한 이해 

불변 데이터에 대해 알아 두어야 한다. 예로 리스트를 생성하고 거기에 새로운 값을 넣으면 리스트를 가르키는 레퍼런스는 같을까 틀릴까? 기존 방식이라면 동일한 레퍼런스이겠지만 Immutable의 세계에 오면 값이 변경 되면 새로운 객체가 된다. 리액트에서는 성능향상을 위해 Immutable.js를 사용토록 권장하고 있다. 이것에 대해 발표한 영상을 보고 영상을 요약한 블로그 글을 보도록 한다. 
  - 영상 설명 블로그 [1], [2], [3]
  - 발표 영상

  - 최근 보고 있는 Clojure는 기본적으로 Immutable Data이다. 클로져 관련 블로그를 읽어보자.



자바스크립트 개념 장착

리액트를 하다 보면 this와 bind(this)를 자주 사용할 때가 있고, ES6로 전환하다보면 좀 더 빈번히 보게 된다. 제대로 이해하고 React 코드를 보자 
  - bind를 해주는 이유 Callback 때문 그렇다면 Callback을 이해하자
  - Callback은 자바스크립트가 Funtional Programming이 가능하게 해주고 Closure이다. 클로저를 이해하자
  - React에서 Object만드는 것이 많이 나오니 자바스크립트의 Object에 대해 알아보자
  - http://javascriptissexy.com/ 에서 다른 글도 참조하시라


'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[Flux] Flux 배우는 방법  (0) 2015.07.04
[React] AngularJS와 ReactJS 비교  (0) 2015.07.01
[React] 배우는 방법  (0) 2015.05.15
posted by peter yun 윤영식
prev 1 next