데이터 시각화 서비스 만들기

1인 지식 기업을 꿈꾸는 30, 40대 직장인을 위한 실무 프로젝트
React, Meteor, D3.js, Chart, Data Visualization

[Chart 추천 서비스] React + Meteor + Redux + Typescript + EUI 환경 구성

Chart 추천 서비스

기획한 Chart Recommendation 서비스를 위한 React, Meteor, Typescript, SCSS, Elastic UI(eui), Redux 기반 환경을 구성하는 과정을 정리한다. 


  - Meteor create을 통한 react환경

  - Typescript, SCSS 지원

  - redux-observable을 이용한 Redux 환경 설정

  - Elastic UI 및 react-final-form 설치

  - react+meteor+eui+scss boilerplate 소스




React기반의 Meteor, Typescript, SCSS 프로젝트 생성


Node.js 8.12.0 사용, 현재 meteor 버전은 1.8.0.1 이다. 

$ meteor create --react react-meteor-redux-boilerplate

$ cd react-meteor-redux-boilerplate

// 최신 버전 설치

$ meteor update 

// 정상 작동하는지 확인 

$ meteor run


scss, typescript 컴파일러 설치

$ meteor add fourseven:scss

$ meteor add barbatus:typescript


typescript를 위한 @types 및 패키지 설치

$ meteor npm install --save-dev @types/meteor 

$ meteor npm install --save-dev @types/node

$ meteor npm install --save-dev @types/react

$ meteor npm install --save-dev @types/react-dom


$ meteor npm install --save-dev react-router-dom 

$ meteor npm install --save-dev typescript

$ meteor npm install --save-dev tslint

$ meteor npm install --save-dev tslint-react


root에 tsconfig.json 및 tslint.json 추가

// tsconfig.json

{

    "compilerOptions": {

        "target": "es6",

        "allowJs": true,

        "jsx": "preserve",

        "moduleResolution": "node",

        "types": [

            "node"

        ]

    }

}


// tslint.json

{

    // "extends": [

    //     "tslint:latest",

    //     "tslint-react"

    // ],

    "rules": {

        "quotemark": [

            true,

            "single",

            "jsx-double"

        ],

        "ordered-imports": false,

        "no-var-requires": false

    }

}


.css, .jsx, .js 를 .scss, .tsx, .ts 확장자로 코드는 다음과 같이 변경한다. 

// import

import React from 'react'; 

=> 

import * as React from 'react';


// import path: 절대경로를 상대경로 변경

import App from '/imports/ui/App';

=> 

import App from '../imports/ui/App';


// class Props, State 타입지정

class Info extends Component {

=> 

class Info extends React.Component<{links: any}, undefined> {


// export 

export default InfoContainer = withTracker(() => {

=>

const InfoContainer = withTracker(() => {

...

export default InfoContainer;



package.json 의 start 파일 변경

"mainModule": {

  "client": "client/main.tsx",

  "server": "server/main.ts"

}




Meteor Pub/Sub 환경 설정


필요없는 패키지 제거

$ meteor remove autopublish


imports/api/links.ts 에 publish 설정

// link.ts

if (Meteor.isServer) {

  Meteor.publish('links', () => Links.find());

}


// Info.tsx 에 react-meteor-data의 withTracker안에 subscribe 설정

export default withTracker(() => {

       const handle = Meteor.subscribe('links');

return {

links: Links.find().fetch(),

loading: !handle.ready()

}

})(Info);


두번째로 필요없는 패키지 제거

$ meteor remove insecure


deny/allow를 설정

  - allow: true로 설정된 것만 가능하고 그외는 모두 불가능하다. (가능한 것이 적으면 allow 설정)

  - deny: true로 서정된 것만 불가능하고 그외는 모두 가능하다.  (불가능한 것이 적으면 deny 설정)

// api/links.ts의 allow 설정 예

if (Meteor.isServer) {

  Meteor.publish('links', () => Links.find());

  Links.allow({

    insert (userIdstringdocany) {

      console.log('insert doc:', doc);

      return (userId && doc.owner === userId);

    },

    remove(userIdstringdocany) {

     console.log('delete doc:', doc);

      return (userId && doc.owner === userId);

    }

  });

}


사용자 로그인 및 Collection을 위한 simple schem 패키지 설치

$ meteor add accounts-password

$ meteor npm install --save bcrypt

$ meteor npm install --save simpl-schema



폴더 구조를 바꾼다. 

client

imports

  - api: collection

  - client

    - layouts: layout container unit

    - pages: panel unit

    - sdk: shared components, libs

  - startup

    - client

    - server

server




Redux 환경 설정


redux 기본 패키지 설치, react-router-redux는 react-router v4로 오면서 connected-react-router 패키지로 변경되었다. 

$ meteor npm install --save redux react-redux connected-react-router

$ meteor npm install --save-dev @types/react-redux


// typesafe-action 설치

$ meteor npm install --save typesafe-actions


// rxjs 기반 side effect 패키지인 redux-observable 설치

$ meteor npm install --save redux-observable rxjs


// state 성능 향상 reselect 설치 

$ meteor npm install --save reselect


redux-observble 환경에서의 store 생성 및 acction, reducer, epic 개발은 다음의 블로그를 참조한다. 




Elastic UI 환경 설정


먼저 final-form, react-final-form 설치

$ meteor npm install --save final-form react-final-form


styled-components, moment  패키지 설치

$ meteor npm install --save styled-components moment


elastic UI(이하 eui) 설치하기. eui는 yarn으로만 설치됨을 주의한다.

  - react-final-form의 third part component로 Eui wrapper component 개발하기는 다음 블로그를 참조한다.

$ meteor npm i -g yarn

$ meteor yarn info

yarn info v1.12.3


// 기존것 제거 하고 yarn으로 다시 설치 

$ rm -rf node_modules

$ rm package-lock.json


// package.json 내용 설치

$ meteor yarn 

meteor yarn add  @elastic/eui


client/main.tsx에 사용할 테마 css를 import 한다. 

import '@elastic/eui/dist/eui_theme_light.css';


주의할 부분은 eui를 yarn으로 설치한 후 추가 패키지를 위해 "meteor npm install <package>" 할 경우 다시 "meteor yarn" 명령을 수행해 주어야 한다. 이 문제는 eui가 yarn만을 지원하고 meteor가 yarn에 대한 지원이 미흡한데 있지 않을까 싶다. 추정일 뿐이고 누가 해결하면 알려주시면 좋겠다.


최종 meteor run 을 수행하면 로그인 화면이 나온다. 계정을 생성하고 로그인 하면 이제 Link에 대한 CRUD를 수행할 수 있다.



[React] Typescript + SCSS 환경 만들기

React

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



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 소개

[Chart 추천 서비스] Voyager 기술 스택과 신규 서비스 SRS

Chart 추천 서비스

Voyager 기술 스택을 살펴보고, 새로 디자인 하려는 기술스택을 정리해 보자. 


  - Voyager 기술 스택 알기

  - 새로운 기술 스택 준비하기 




Voyager 기술 스택


기술 스택 이해를 위해 voyager의 의존성 패키지를 살펴보자. 


  - Typescript

  - React

  - React third part components: datetime, dnd, file-download, modal, spinners, split-pane, tabs, tether

  - Redux: redux-action-log, redux-thunk, redux-undo, reselect

  - common packages: moment, font-awesome

  - vega: vega-datasets, vega-expression, vega-lite, vega-tooltip, vega-typings, vega-util

  - compassql

  - webpack 빌드


React 개발시 사용하는 환경에서 개발되었음으로 알 수 있다. Voyager 설명과 사용방법은 Gitbook을 사용하고 있다. 


  - 로컬 데이터 로딩, URL입력으로 불러오기: JSON, CSV, TSV 파일형식 지원

  - 데이터 Field 자동 분류: quantitative(숫자), temporal(날짜), categorical(문자)

  - quantitative에 대해 min, max, mean등의 함수 적용

  - temporal에 대해 year, month, date, hours등의 함수 적용





Lime 기술 스택


라임은 Voyager의 기능을 흡수하고 내가 사용하고 싶은 UX 를 가미한 서비스이다. 라임은 다음의 기술 스택으로 만들어 보려한다. 


  - React + Redux

  - Meteor

  - MongoDB

  - vega-lite, compassQL


라임 서비스의 SRS(Software Requirements Specification, 소프트웨어 요구사항 명세)는 다음과 같다.


  - 데이터는 Voyager에서 지원하는 형태와 동일하다. 이후 데이터에 대한 수집, 클렌징 작업은 또 하나의 프로젝트가 될 정도의 규모이므로 여기서는 배제한다. 

  - 차트를 디자인한 후, 직접 Vega-lite의 JSON 환경설정을 에디팅 할 수 있도록 한다. 에디터는 Vega-editor를 사용한다. 

  - 원하는 차트 형태가 나왔다면 데이터와 차트 설정을 저장할 수 있다. 

  - 저장한 차트 목록에서 원하는 것을 대시보드화 할 수 있다. 즉, 차트 -> 대시보드 기능을 가진다. 

  - 각 차트와 대시보드는 다른 애플리케이션에 embedding하여 표현할 수 있다. 

  - 대시보드는 생성한 Vega차트를 표현하거나, Text, Image등의 Widget을 제공하여 사용자 정의 정적 표현을 할 수 있다. 


차트 생성


대시보드 생성


어짜피 혼자 개발하는 것이니 한장 짜리 SRS로 시작해 본다.




<참고>

- Voyager Github

- Voyager GitBook