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

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를 수행할 수 있다.



[Meteor + React] 개발환경 만들기 - 6

Meteor

Ant Design 컴포넌트 또는 Elatstic UI 같은 컴포넌트 적용방법을 알아보자. 


  - Ant Design 컴포넌트 적용

  - Elastic UI  컴포넌트 적용

  - 적용 소스




Final Form 에 Ant Design 컴포넌트 적용하기


적용하기-1 블로그에서 Antd을 설치했다. Ant Design의 Form을 사용하여 Login, SignOut, AddLink의 Input을 수정한다. 

  - imports/ui/sdk/antd/antd-final-input.tsx 파일 생성

  -antd 의 input react 컴포넌트를  react-final-form의 input의 custom component로 설정한다. (third component support )

import * as React from 'react';

import { Input } from 'antd';

import { Field } from 'react-final-form';


// antd 의 react input component를 react-final-form에서 사용할 수 있는 custom component로 만듦

const AntdInput = ({input, ...rest}) => {

  return (

    <Input {...input} {...rest} />

  )

}


// 최종 애플리케이션에서 사용할 react-final-form 컴포넌트

const AInput = (propsany=> {

    return (

      <Field {...props} component={AntdInput} />

    );

}


export default AInput;


antd의 layout 컴포넌트은 Col, Row를 사용해서 레이아웃을 잡는다. 

import { Row, Col, Button } from 'antd';

import AInput from '../sdk/antd/antd-final-input';


export default class Login extends React.Component<LoginProps, LoginState> {

...

  makeForm = ({ handleSubmitsubmittingpristine }) => {

    return (

        <form onSubmit={handleSubmit}>

          <Col span={4}><AInput name="email" type="email" placeholder="Email" /></Col>

          <Col span={4}><AInput name="password" type="password" placeholder="Passowrd" /></Col>

          <Col span={2}><Button type="primary" htmlType="submit" disabled={submitting || pristine}>Login</Button></Col>

        </form>

    );

  };


  public render() {

    return (

      <div>

        <h1>Login to short Link</h1>

        {this.state.error ? <p>{this.state.error} </p> : undefined}

        <Row gutter={5}>

          <Form onSubmit={this.onLogin} render={this.makeForm} />

        </Row>

        <Row gutter={5}>

          <Link to="/signup">Have a account?</Link>

        </Row>

      </div>

    );

  }

}



AddLink.tsx, Signup.tsx도 동일하게 바꾸어 본다. 




Final Form에 Elastic UI 적용하기 


eui(Elastic UI)는 Elastic Search의 React기반 오픈소스이다. Elastic Search의 Kibana에서 사용중이다. AntD대신 Eui를 적용해 본다. 

eui는 yarn install 만 지원하는 관계로 Meteor에서 yarn을 사용하기 위해서 다음 과정을 최초 한번 설정한다. 

$ meteor npm i -g yarn

$ meteor yarn info 

yarn info v1.12.3


$ rm -rf node_modules (MS는 윈도우 명령으로)

$ rm package-lock.json


// package.json 내용 설치

$ meteor yarn 


  - @elastic/eui 패키지 설치

  - css 설정

// eui는 npm install을 지원하지 않는다. 

meteor yarn add  @elastic/eui


// client/main.tsx에서 import한다. 

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


// client/theme.less 안은 import는 주석처리한다.

// @import '{}/node_modules/antd/dist/antd.less';


Lisk화면을 다음과 같이 전환한다. 


react-final-form에 EuiFieldText (input tag) 컴포넌트를 적용한다. 

  - imports/ui/sdk/eui/eui-final-input.tsx 파일 생성

import * as React from 'react';

import { EuiFieldText } from '@elastic/eui';

import { Field } from 'react-final-form';


const EuiInput = ({ input, ...rest }) => {

  return (

    <EuiFieldText {...input} {...rest} />

  )

}


const EInput = (propsany=> {

  return (

    <Field {...props} component={EuiInput} />

  );

}


export default EInput;


imports/ui/Info.tsx 에서 EuiPage 관련 컴포넌트로 레이아웃을 꾸민다. 

  - EuiPageHeader, EuiPageContent로 나눔

  - 태그안의 정렬은 FlexBox가 적용된 EuiFlexGroup과 EuiFlexItem을 사용한다.

  - width, padding은 style일 직접 설정한다.

import {

  EuiFlexGroup, EuiFlexItem, EuiButton, EuiPage, EuiSpacer,

  EuiPageBody, EuiPageHeader,  EuiPageContent, EuiPageContentBody, EuiTitle

} from '@elastic/eui';


class Info extends React.Component<InfoProps, any> {

...

  render() {

    return (

      <EuiPage>

        <EuiPageBody>

          <EuiPageHeader>

              <EuiFlexGroup justifyContent="spaceBetween">

                <EuiFlexItem grow={1} style={{ paddingLeft: 20 }}>

                  <EuiTitle size="m"><h1>Add Link & List</h1></EuiTitle>

                </EuiFlexItem>

                <EuiFlexItem style={{ maxWidth: 130, paddingRight: 30 }}>

                  <EuiButton style={{ maxWidth: 100 }} onClick={this.onLogout}>Log out</EuiButton>

                </EuiFlexItem>

              </EuiFlexGroup>

          </EuiPageHeader>

          <EuiPageContent>

            <EuiPageContentBody>

              <EuiFlexGroup direction="column" justifyContent="spaceBetween">

                <EuiFlexItem style={{ maxWidth: 800 }}>

                  <AddLink /> 

                </EuiFlexItem>

                <EuiSpacer />

                <EuiFlexItem style={{ maxWidth: 400 }}>

                  {this.linkList()}

                </EuiFlexItem>

              </EuiFlexGroup>

            </EuiPageContentBody>

          </EuiPageContent>

        </EuiPageBody>

      </EuiPage>

    );

  }

}


imports/ui/pages/link/AddLink.tsx도 수정한다.

  - EuiFlexGroup으로 레이아웃 적용: flexbox direction은 row이다.

  - EInput 컴포넌트 적용

  - EuiButton 적용하기: type="submit" 설정

  - AntD적용 내용은 모두 주석처리한다. 

import EInput from '../../sdk/eui/eui-final-input';

import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';



class AddLink extends React.Component<AddLinkProps, any> {

...

  makeForm = ({handleSubmitsubmittingpristine}) => {

    return (

      <form onSubmit={handleSubmit}>

        {/* <Col span={4}><AInput name="title" component="input" type="text" placeholder="Title" /></Col>

        <Col span={4}><AInput name="url" component="input" type="text" placeholder="Url" /></Col>

        <Col span={2}><Button type="primary" htmlType="submit" disabled={submitting || pristine}>Add Link</Button></Col> */}

        <EuiFlexGroup direction="row" gutterSize="s">

          <EuiFlexItem><EInput name="title" component="input" type="text" placeholder="Title" /></EuiFlexItem>

          <EuiFlexItem><EInput name="url" component="input" type="text" placeholder="Url" /></EuiFlexItem>

          <EuiFlexItem style={{ maxWidth: 100 }}>

            <EuiButton type="submit" fill disabled={submitting || pristine}>Add Link</EuiButton>

          </EuiFlexItem>

        </EuiFlexGroup>

      </form>

    );

  }


  public render() {

    return (

      // <Row gutter={5}>

        <Form onSubmit={this.handleSubmit} render={this.makeForm} />

      // </Row>

      );

  }

}


Link.tsx도 수정한다. 

  - EuiButton 사이즈 small 설정

  - EuiButton minWidth 적용: default가 120px 이므로 overrriding을 minWidth: 40 을 설정한다

class Link extends React.Component<LinkProps, any> {

...

  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

        <a href={link.url} target="_blank">{link.title}</a>

        <EuiButton size="s" style={{minWidth: 40, marginLeft: 10}} onClick={this.deleteLink}>x</EuiButton>

      </li>

    );

  }

}


Login.tsx, Signup.tsx도 eui 컴포넌트로 변경해 본다. 개인적으로 FlexBox를 많이 사용하는데 이에대한 컴포넌트 레벨 지원을 Eui가 제공하므로, 앞으로 Eui 컴포넌트를 사용한다. 다음은 style을 직접 적용하는 방식이 아니라 class 적용방법을 알아본다. 




<참조> 

Ant Design React Component

Elastic UI React Component 

Meteor에서 yarn 사용하기 - 마지막 답변 참조