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

Publication

Statistics Graph

Recent Comment

2018.12.13 16:39 Angular/Architecture

Angular 프로젝트를 위한 구조 설계 요건에 대해 알아본다. 


  - 프레임워크 선택 고려 사항

  - 개발 생산성과 유지 보수성을 높일 수 있는 방안




프레임워크 선택 고려 사항


프레임워크는 잘 정돈된 놀이터와 같아 기구를 타고 놀면 된다. 하지만 라이브러리의 경우는 툴박스와 같아서 박스안에는 망치, 드라이버, 못같은 것만 있다. 그럼 놀 것을 어떻게 만들지는 도구를 사용하는 사용자의 몫이다. 현재 UI 프레임워크/라이브러리는 Angular, React, Vue 등이고 풀스택으로는 Meteor를 선택해서 사용하고 있다. Angular 프레임워크와 React UI 라이브러리 또는 Meteor 선택시 고려사항을 개인의견으로 간략히 정리한다. 


Angular Framework

  - 대규모 협업 팀에 유리: 대략 10명이상으로 HTML Publisher와 Javascript 개발자가 분리되어 있을 경우

  - JQuery가 low level의 javascript로 개발해 오던 개발자들이 접근하기에 수월하다고 판단함

  - Typescript, RxJS 반드시 알아야 한다.


React Library

  - HTML Publisher와 Javascript 개발을 같이하는 스타트업 또는 프론앤드 개발자에게 유리

  - 원하는 것들을 선택해 적용할 수 있다. 


Meteor

  - FullStack 으로서 javascript가지고 전부 개발하고 싶을 경우

  - 아이디어를 빠르게 구현하고 검증 받고 싶을 경우

  - UI는 Angular, React, Vue 등으로 선택해 사용할 수 있으나, React가 적합하다 판단



그러나 어떤 것을 선택하든 공통적으로 고려해야 하는 사항으로 애플리케이션 아키텍쳐이다. 이에 대한 좋은 영상이 있어 보기를 권한다. 




애플리케이션 아키텍쳐



위의 영상을 정리해 보면 다음과 같다. 

  - Layered Architecture 구성

  - 역할 분담

    + Module: 업무 최소 단위, 하나를 제거해도 다른 것에 영향을 주지 않는 단위, Module은 Sandbox만 알뿐이다. 

    + Sandbox: Module은 Sandbox를 통해 Application Core나 Base Library를 접근한다.

         - 일관성을 유지시킴

         - 시큐리티 가드 역할

         - 공통 Interface 역할

    + Application Core: Module을 관리(Control) 한다. 

         - Module의 Life Cycle을 담당한다.

         - Module 간 통신을 담당한다 (inter-module communication)

         - Module을 직접 가져다 쓰지 않고, Sandbox의 공통 API를 통해 명령을 내리고, 듣는다. 

         - General error handling

         - Extension을 통해 Application core기능을 확장한다.  

            

   + Extension 확장

       - Error handling

       - Ajax communication: 공통 Format (request/response) 통신, 서버 오류 관리

       - New module capabilities

       - General Utils

       - What you want

    + Base Library

       - 개발자들이 직접 base library를 건드리게 하지 말자. 나중에 바뀔 수 있다. 

       - Browser normalization

       - General purpose utilities: Paser/serializer, Object / DOM manipulation, Ajax Communication

       - low-level extension 제공: application extension처럼 base library를 확장한 것들을 제공한다. 

         


정리

  - Base Library만이 사용중인 브라우저를 알고 있다. 아키텍쳐의 다른 layer에서 알 필요가 없다.

  - Application Core만이 사용중인 Base Library를 알고 있다. 아키텍쳐의 다른 layer에서 알 필요가 없다. 

  - Module은 Sandbox의 존재외에는 아무것도 모른다. 아키텍쳐의 나머지에 대해선 아무것도 모른다.


장점

  - 하나의 프레임워크상에서 서로 다른 멀티 애플리케이션을 만들 수 있다. 즉, 기존의 컴포넌트를 재사용함으로서 시간을 줄일 수 있다. 

  - 느슨한 연결(loose coupling)으로 인해 모듈 단위 테스트가 가능해짐.

  - 확장 가능한 자바스크립트 아키텍쳐는 하나의 블록을 교체한다고 해도 두려워 할 필요가 없는 상태가 되어야 한다. 즉, 이를 달성할 수 있다.




유지 보수성 높이기


유지보수가 좋은 코드는 다음의 특성을 갖는다. 

  - 직관적이다. Intuitive

  - 코드 이해가 쉽다 Understable

  - 적용하기 쉽다 Adaptable

  - 확장하기 쉽다 Extendable

  - 디버깅하기 쉽다 Debuggable


이를 위해 해야할 것들. 
  - Code Style, Code Convention 지키기
  - Naming 잘 하기: 이름에 대한 길이에 대해 걱정하지 말자.
  - Javascript에서 css 핸들링 하지 말자 
  - event object를 바로 다른 function의 argument로 넘기지 말고, 필요한 정보만을 준다. 
  - 사용전 object undefined 체크
  - string 값들은 별도의 Config Data 객체에 담아서 사용한다.

    

  - 빌드 자동화 하자

    


'Angular > Architecture' 카테고리의 다른 글

[Architecture] 애플리케이션 구조 설계 - 1  (0) 2018.12.13
posted by peter yun 윤영식
2018.12.04 15:13 Meteor/React + Meteor

컴포넌트에 style을 직접 적용하는 대신, class를 적용해 본다. 


  - meteor 기반에서 css 적용의 어려운 부분들

  - styled-component를 사용한 적용

  - 적용소스




styled-components 적용하기


meteor는 자체 builder를 통해 패키지를 빌드하는 관계로 webpack과 같은 custom 설정에 제약이 있어 보인다. 물론 잘 알지 못해서 아직 찾지 못했을 수도 있지만 다음과 같은 class 적용이 안된다. 

  - import * as style from './link.scss': from 문구를 사용하지 못 한다. 나머지 다양한 class적용 방법도 잘 안되는 듯 하다. 

  - import './link.scss' 만 사용가능하다. local 적용이 아닌 global 적용을 한다. <head> 태그에 prefix없이 들어간다. 


Meteor의 제약사항을 고려하여 별도의 scss 파일 작성을 최대한 줄이고, 컴포넌트의 style을 확장하기 위해 styled-components를 사용토록 한다. 

$ meteor npm install --save styled-components


imports/ui/sdk/eui/flexgroup.style.tsx 파일 을 생성한다. 

  - 테스트로 확장 컴포넌트로 부터 받은 색을 적용해 본다. 

  - props는 camelCase로 설정한다.

import styled from 'styled-components';

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


export const StyledWidthEuiFlexGroup = styled(EuiFlexGroup)`

  width: ${props => props.width || '100%'};

`;


export const StyledLongEuiFlexItem = styled(EuiFlexItem)`

  max-width: 400px;

  input {

    background: ${props => props.background || 'yellow'};  // 동적으로 bgcolor를 적용한다.

    color: white;

  }

`;


export const StyledShortEuiFlexItem = styled(EuiFlexItem)`

  max-width: 100px;

`;


AddLink.tsx에서 사용한다. 

import { StyledLongEuiFlexItem, StyledShortEuiFlexItem } from '../../sdk/eui/flexgroup.style';


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

...

  makeForm = ({handleSubmit, submitting, pristine}) => {

    return (

      <form onSubmit={handleSubmit}>

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

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

          <StyledLongEuiFlexItem background="red">

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

          </StyledLongEuiFlexItem>

          <StyledShortEuiFlexItem>

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

          </StyledShortEuiFlexItem>

        </EuiFlexGroup>

      </form>

    );

  }

...

}



Login.tsx에도 적용한다. 

  - FlexGroup  의 width=600px으로 설정한다. 

  - JPage, JRow를 설정한다. (아래에서 계속 개발)

import { JPage, JRow } from '../layouts/common.style';


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

...

  makeForm = ({ handleSubmit, submitting, pristine }) => {

    return (

      <form onSubmit={handleSubmit}>

        <StyledWidthEuiFlexGroup width="600px" direction="row" gutterSize="s">

          <EuiFlexItem><EInput name="email" type="email" placeholder="Email" /></EuiFlexItem>

          <EuiFlexItem><EInput name="password" type="password" placeholder="Passowrd" /></EuiFlexItem>

          <StyledShortEuiFlexItem>

                <EuiButton type="submit" disabled={submitting || pristine}>Login</EuiButton>

          </StyledShortEuiFlexItem>

        </StyledWidthEuiFlexGroup>

      </form>

    );

  };


  public render() {

    return (

      <JPage>

        <JRow padding="10px" fontSize="20px">Login to short Link</JRow>

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

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

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

      </JPage>

    );

  }

}


JPage와 JRow 컴포넌트를 imports/ui/layouts/common.style.ts 생성한다. 

  - JPage는  padding

  - JRow는 flexbox의 row direction으로 설정한 컴포넌트이다.

  - 컴포넌트의 properties를 통해 값을 받아 동적으로 설정한다. 

import styled from 'styled-components';


export const JPage = styled.div`

  display: block;

  position: relative;

  padding: 10px;

  height: 100%;

  width: 100%;

`;


export const JRow = styled.div`

  display: flex;

  justify-content: ${ props => props.align || 'flex-start' };

  align-items: ${ props => props.align || 'center' };

  width: ${ props => props.width || '100%' };

  padding: ${ props => props.padding || '5px' };

  font-size: ${ props => props.fontSize || '14px' };

`;



styled-components를 통해 얻을 수 있는 장점


  - 기존 컴포넌트의 style 확장을 컴포넌트 개념을 할 수 있다. 기존은 .css 작성

  - 일반 태그를 style을 동적으로 적용할 수 있는 컴포넌트로 만들어 React컴포넌트에서 Common 컴포넌트처럼 사용할 수 있다. 즉 Style만을 담당하는 컴포넌트임.




<참조>

- class 적용 방법들

- styled-components 홈페이지

posted by peter yun 윤영식
2018.12.03 16:09 Meteor/React + 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 = (props: any) => {

    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 = ({ handleSubmit, submitting, pristine }) => {

    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 = (props: any) => {

  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 = ({handleSubmit, submitting, pristine}) => {

    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 사용하기 - 마지막 답변 참조

posted by peter yun 윤영식
2018.11.30 15:48 Meteor/React + Meteor

React의 route 설정을 리팩토링해보고, createRef() 를 사용하지 않고 form을 변경해 본다. 


  - react-router v4에 맞는 redux 환경 재구성

  - react-final-form  적용하기

  - 사용자별 Link 목록 보여주기

  - 적용소스




React Router v4에 맞는 Redux 설정


react-router-redux는 react router v2와 v3에 맞는 패키지이고 react router v4에 맞는 connected-react-router로 교체해야 한다. recct-router-redux 설정 상태로는 redux action이 작동하지 않는다.

// 설치

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

// 삭제

$ meteor npm uninstall --save react-router-redux

$ meteor npm uninstall --save @types/react-router-redux


imports/ui/store.ts 리팩토링

  - Routes.tsx의 browserHistory 를 store.ts로 옮김

  - connectRouter로 routerReducer를 생성

  - RouterAction, LocationChangeAction을 추가

import { createBrowserHistory } from 'history';

import { connectRouter, RouterAction, LocationChangeAction } from 'connected-react-router';


export const browserHistory = createBrowserHistory();


const rootReducer = combineReducers({

  router: connectRouter(browserHistory),

  links: linkReducer

});


export type RootState = StateType<typeof rootReducer>;


type ReactRouterAction = RouterAction | LocationChangeAction;

export type RootAction = ReactRouterAction | LinkAction;


imports/ui/Routes.tsx 리팩토링

  - react-router v4의 Router를 사용하지 않고, connected-react-router의 ConnectedRouter를 사용

  - 인자로 store.ts 에서 생성한 browserHistory를 사용

import { ConnectedRouter } from 'connected-react-router';

import store, { browserHistory } from './store';


export const Root = (

  <Provider store={store}>

    <ConnectedRouter history={browserHistory}>

      <Switch>

        <Route exact path="/" component={Login} />

        <Route path="/main" component={App} />

        <Route path="/signup" component={Signup} />

        <Route path="/links" component={InfoContainer} />

        <Route path="*" component={NotFound} />

      </Switch>

    </ConnectedRouter>

  </Provider>

);


이제 Redux Chrome Extension에서 LOCATION_CHANGE 액션을 볼 수 있다. 




final-form 통해 입력


form 관련부분을 final-form으로 변경한다. React버전의 react-final-form을 사용한다. 

$ meteor npm install --save final-form 

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


로그인 화면부터 react-final-form으로 변경해 본다. 

  - react-final-form의 Form, Field import

  - Form 의 onSubmit={this.onLogin}을 통해 입력한 values 객체를 받기, 보통 폼 필드의 name을 key로하여 json 객체를 받는다. 

  - <input> 태그를 <Field> 태그로 변경

  - <button> 태그에 disabled 속성추가

  - 필요없는 부분 추석처리: React.createRef()

import * as React from 'react';

import { Link } from 'react-router-dom';

import { Meteor } from 'meteor/meteor';

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


export interface LoginProps {

  history: any;

}


export interface LoginState {

  error: string;

}


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

  // email: any = React.createRef();

  // password: any = React.createRef();


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onLogin = ({email, password}) => {

    // e.preventDefault();

    // let email = this.email.current.value.trim();

    // let password = this.password.current.value.trim();

    if (!email || !password) {

      this.setState({error: 'Please input email and password both'});

      return;

    }

    Meteor.loginWithPassword({ email }, password, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  makeForm = ({ handleSubmit, submitting, pristine, values }) => {

    return (

      <form onSubmit={handleSubmit}>

        <Field name="email" component="input" type="email" placeholder="Email" required/>

        <Field name="password" component="input" type="password" placeholder="Passowrd"/>

        {/* <input type="email" ref={this.email} name="email" placeholder="Email" />

        <input type="password" ref={this.password} name="password" placeholder="Password" /> */}

        <button type="submit" disabled={submitting || pristine}>Login</button>

      </form>

    );

  };


  public render() {

    return (

      <div>

        <h1>Login to short Link</h1>

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

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

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

      </div>

    );

  }

}


Signup.tsx, AddLink.tsx 도 변경한다. 




사용자별 Link 목록 보여주기


사용자를 추가하여 각 사용자가 등록한 목록만 보기 위해서 pub/sub 설정에서 userId  파라미터를 넘겨주어 본인 목록만 조회하여 publish 한다. 

// imports/ui/Info.tsx 맨 하단의 subscribe시에 자신의 아이디를 파라미터로 보낸다. 

export default compose(

  withTracker(() => {

    const connection = Meteor.subscribe('links', {userId: Meteor.userId()});

    return {

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

      loading: !connection.ready()

    };

  }),

  connect(mapProps)

)(Info);


imports/api/links.ts에서 userId를 파라미터로 find한다. 

if (Meteor.isServer) {

  Meteor.publish('links', ({userId}) => {

    console.log('userId:', userId);

    if (!userId) {

      return this.ready();

    }

    return Links.find({owner: userId});

  });

  ...

}




<참조>

- connected-react-router 저장소

- react-final-form 소개 영상

posted by peter yun 윤영식
2018.11.28 16:56 Meteor/React + Meteor

Meteor와 React 환경을 좀 더 Production에 가깝게 만들어 본다. 


  - Insecure 제거 및 allow/deny 설정

  - React의 Refs에 대해 변경

  - Meteor methods/call 사용한 목록 조회

  - SimpleSchema 적용

  - 적용소스




Insecure 패키지 제거 및 Allow/Deny 설정


모든 컬렉션에 대한 자동 subscribe에 대한 autopublish 패키지는 이미 제거를 했고, 다음으로 insecure 패키지를 제거하여 컬렉션의 접근 권한을 제어하는 allow/deny를 설정한다. 

$ meteor remove insecure


insecure를 제거하게 되면 read만 가능하고 update, delete, insert가 불가능하다. 따라서 권한부분은 서버에서 실행되기 때문에 allow/deny설정을 Meteor.isServer블럭안에서 해주어야 한다.

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

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

// api/links.ts

import { Meteor } from 'meteor/meteor';

import { Mongo } from 'meteor/mongo';


const Links = new Mongo.Collection('links');


if (Meteor.isServer) {

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

  Links.allow({

    insert (userId: string, doc: any) {

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

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

    },

    remove(userId: string, doc: any) {

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

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

    }

  })

}

export default Links;


 allow에서 owner 아이디를 비교하므로 epic에서 owner 값을 설정한다. 

// imports/ui/pages/link/link.epic.ts

const addLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.ADD_REQUEST)),

    switchMap(action => {

      const { title, url } = action.payload;

      const owner = Meteor.userId();

      return insertCollection(Links, { title, url, owner, createdAt: new Date() })

    }),

  ...

);


userId는 로그인을 하게되면 이미 서버와 동기화 되어 서버가 가지고 있는 userId를 첫번째 파라미터로 넘겨준다. 두번째 파라미터 doc은 insert할 때 입력한 파라미터값 이고, remove의 doc은 저장된 몽고디비의 document이다. 

// meteor run을 수행한 콘솔창에 찍힌다. 

=> Meteor server restarted

I20181123-15:00:36.619(9)? insert doc: { title: 'google2',

I20181123-15:00:36.676(9)?   url: 'http://www.google.com',

I20181123-15:00:36.676(9)?   owner: 'AmMBJzZ33Nc8Bsqhe',

I20181123-15:00:36.676(9)?   createdAt: 2018-11-23T06:00:36.611Z }

I20181123-15:00:39.955(9)? remove doc: { _id: 'Zrb8jGSpt2AyRn4Q3',

I20181123-15:00:39.955(9)?   title: 'google2',

I20181123-15:00:39.955(9)?   url: 'http://www.google.com',

I20181123-15:00:39.955(9)?   owner: 'AmMBJzZ33Nc8Bsqhe',

I20181123-15:00:39.955(9)?   createdAt: 2018-11-23T06:00:36.611Z }




Form 입력의 React의 Refs 변경


React에서 DOM객체 접근 방법은 3가지 이고, string과  콜백펑션말고 16.3 버전이후 나온 createRefs()를 사용한다. 

  - ref="string"

  - ref={callback-function}

  - createRefs()


DOM 객체를 받을 변수를 선언하고, ref={this.변수}로 할당한다. 값 접근은 this.변수.current 객체를 통한다.

// imports/ui/pages/Login.tsx

.. 중략 ..

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

  email: any = React.createRef();

  password: any = React.createRef();


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onLogin = (e: any) => {

    e.preventDefault();


    let email = this.email.current.value.trim();

    let password = this.password.current.value.trim();

    Meteor.loginWithPassword({ email }, password, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  public render() {

    return (

      <div>

        <h1>Login to short Link</h1>

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

        <form onSubmit={this.onLogin}>

          <input type="email" ref={this.email} name="email" placeholder="Email" />

          <input type="password" ref={this.password} name="password" placeholder="Password" />

          <button>Login</button>

        </form>

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

      </div>

    );

  }

}


Signup.tsx과 AddLink.tsx도 동일하게 변경한다. 




Meteor methods/call을 사용한 저장/삭제


imports/sdk/utils/ddp.util.ts 에 method call에 대한 observable 반환 메소드 추가

export function insertCall(methodName: string, params: any): Observable<RequestModel> {

  return from(new Promise((resolve, reject) => {

    Meteor.call(methodName, params, (error, result) => {

      if (error) {

        reject({ error: true, result: { ...error }, params: { ...params } });

      }

      if (typeof result === 'string' || typeof result === 'number') {

        resolve({ success: true, result, params: { ...params } });

      } else {

        resolve({ success: true, result: { ...result }, params: { ...params } });

      }

    });

  }));

}


export function removeCall(methodName: string, _id: string): Observable<RequestModel> {

  return from(new Promise((resolve, reject) => {

    Meteor.call(methodName, _id, (error, result) => {

      if (error) {

        reject({ error: true, result: { ...error }, params: { _id } });

      }

      if (typeof result === 'string' || typeof result === 'number') {

        resolve({ success: true, result, params: { _id } });

      } else {

        resolve({ success: true, result: { ...result }, params: { _id } });

      }

    });

  }));

}


imports/api/links.ts 에서 Meteor.methods를 추가한다. 

if (Meteor.isServer) {

 ... 

  Meteor.methods({

    insertLink(params: any) {

      if (!this.userId) {

        throw new Meteor.Error('Please login');

      }

      return Links.insert(params);

    },

    removeLink(_id: string) {

      if (!this.userId) {

        throw new Meteor.Error('Please login');

      }

      return Links.remove(_id);

    }

  });

}


imports/ui/pages/link/link.epic.ts 에서 collection 을 호출하지 않고, Meteor.call을 호출 한다. takeUntil은 브라우져 이동등을 할때 호출을 끊는 역할을 한다. 현재는 주석처리로 미구현상태임.

const addLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.ADD_REQUEST)),

    switchMap(action => {

      const { title, url } = action.payload;

      const owner = Meteor.userId();

      return insertCall('insertLink', { title, url, owner, createdAt: new Date() })

      // return insertCollection(Links, { title, url, owner, createdAt: new Date() })

    }),

    map((response: RequestModel) => {

      if (response.error) {

        return actions.addLinkFailed({ ...response.result })

      }

      return actions.addLinkSuccess(response.result)

    }),

    // takeUntil(action$.pipe(

    //   filter(isOfType(actions.ADD_REQUEST))

    // ))

  );


const removeLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.DELETE_REQUEST)),

    switchMap(action => {

      return removeCall('removeLink', action.payload);

      // return removeCollection(Links, action.payload);

    }),

    map((response: RequestModel) => {

      if (response.error) {

        return actions.removeLinkFailed({ ...response.result, ...response.params })

      }

      return actions.removeLinkSuccess(response.params._id);

    }),

    // takeUntil(action$.pipe(

    //   filter(isOfType(actions.ADD_REQUEST))

    // ))

  );




SimpleSchema 적용하기 


Validation을 위해 simple schema 패키지를 설치한다. 

$ meteor npm install --save simpl-schema


/imports/api/links.ts 에 schema를 정의한다. 

import { Meteor } from 'meteor/meteor';

import { Mongo } from 'meteor/mongo';

import SimpleSchema from 'simpl-schema';


const Links = new Mongo.Collection('links');


if (Meteor.isServer) {

 ...

  const linkSchema = new SimpleSchema({

    title: {

      type: String,

      min: 3

    },

    url: {

      type: String

    },

    owner: {

      type: String

    },

    createdAt: {

      type: Date 

    }

  });


  Meteor.methods({

    insertLink(params: any) {

      if (!this.userId) {

        throw new Meteor.Error('Please login');

      }

      try {

        linkSchema.validate(params);

        return Links.insert(params);

      } catch(e) {

        throw new Meteor.Error('no valid schema');

      }

    },

   ...

  });

}

export default Links;


account에 대한 validate도 정의해 본다. imports/api/ 폴더아래에 account-validate.ts 파일을 생성하고 server/main.ts에서 import한다. 

// account-validate.ts

import { Meteor } from 'meteor/meteor';

import { Accounts } from 'meteor/accounts-base';

import SimpleSchema from 'simpl-schema';


if (Meteor.isServer) {

  Accounts.validateNewUser((user) => {

    const email = user.emails[0].address;


    try {

      new SimpleSchema({

        email: {

          type: String,

          regEx: SimpleSchema.RegEx.Email

        }

      }).validate({ email });

    } catch(e) {

      throw new Meteor.Error(400, e.message);

    }


    return true;

  });

}


// server/main.ts

import { Meteor } from 'meteor/meteor';

import '../imports/api/links';

import '../imports/api/account-validate';


Meteor.startup(() => {

});


// Singup.tsx 파일에 테스트를 위해 noValidate를 추가한다. 

<form onSubmit={this.onCreateAccount} noValidate>




<참조>

React createRefs() 사용하기

- Meteor methods/call 사용하기

posted by peter yun 윤영식
2018.11.22 14:37 Meteor/React + Meteor

Meteor의  accounts-password를 패키지를 통한 로그인 환경 만들기를 해본다. 


  - accounts-password 패키지를 통한 미티어 사용자 가입/로그인 사용 방법 알기

  - React Router 설정

  - 미티어의 Tracker.autorun을 통해 reactivity computation과 resource 사용 방법 알기

  - 소스코드




미티어 사용자 관리


accounts-password 패키지를 설치하고, 가입 및 로그인 화면을 만든다. 

$ meteor add accounts-password

$ meteor npm install --save bcrypt

// Link 사용을 위해 

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


Signup.tsx와 Login.tsx 생성한다. 

  - email, password의 값을 읽기 위하여 typescript방식의 public refs를 정의한다. 

  - Accounts.createUser를 통해 사용자를 등록한다.

// Singup.tsx

import * as React from 'react';

import { Link } from 'react-router-dom';

import { Accounts } from 'meteor/accounts-base'


export interface SignupProps {}

export default class Signup extends React.Component<SignupProps, any> {

  // @see https://goenning.net/2016/11/02/strongly-typed-react-refs-with-typescript/

  public refs: {

    email: HTMLInputElement,

    password: HTMLInputElement

  }


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onCreateAccount = (e: any) => {

    e.preventDefault();

    let email = this.refs.email.value.trim();

    let password = this.refs.password.value.trim();

    Accounts.createUser({email, password}, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  public render() {

    return (

      <div>

        <h1>Signup to short Link</h1>

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

        <form onSubmit={this.onCreateAccount}> 

          <input type="email" ref="email" name="email" placeholder="Email"/>

          <input type="password" ref="password" name="password" placeholder="Password"/>

          <button>Create Acount</button>

        </form>

        <Link to="/login">Already have a account?</Link>

      </div>

    );

  }

}


Login.tsx

  - refs 정의

  - Meteor.loginWithPassword를 통해 로그인한다.

import * as React from 'react';

import { Link } from 'react-router-dom';

import { Meteor } from 'meteor/meteor';


export interface LoginProps {

  history: any;

}

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

  public refs: {

    email: HTMLInputElement,

    password: HTMLInputElement

  }


  constructor(props) {

    super(props);

    this.state = {

      error: ''

    };

  }


  onLogin = (e: any) => {

    e.preventDefault();

    let email = this.refs.email.value.trim();

    let password = this.refs.password.value.trim();

    Meteor.loginWithPassword({ email }, password, (err) => {

      if (err) {

        this.setState({ error: err.reason });

      } else {

        this.setState({ error: '' });

      }

    });

  }


  public render() {

    return (

      <div>

        <h1>Login to short Link</h1>

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

        <form onSubmit={this.onLogin}>

          <input type="email" ref="email" name="email" placeholder="Email" />

          <input type="password" ref="password" name="password" placeholder="Password" />

          <button>Login</button>

        </form>

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

      </div>

    );

  }

}


NotFound.tsx 파일도 생성한다. 

import * as React from 'react';

export default () => <div>Not Found Page</div>




React Router 설정


라우팅 설정을 통해 가입, 로그인후 Link화면으로 이동하는 설정한다. 


history 모듈을 설치한다.

$ meteor npm install --save history


client/main.tsx에 Route설정을 한다.

import * as React from 'react';

import { Meteor } from 'meteor/meteor';

import { render } from 'react-dom';

import { Provider } from 'react-redux';

import { Route, Router, Switch } from 'react-router-dom';

import { createBrowserHistory } from 'history';

 

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

import Login from '../imports/ui/pages/Login';

import Signup from '../imports/ui/pages/Signup';

import InfoContainer from '../imports/ui/Info';

import NotFound from '../imports/ui/pages/NotFound';

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


const browserHistory = createBrowserHistory();

const Root = (

  <Provider store={store}>

    <Router history={browserHistory}>

      <Switch>

        <Route exact path="/" component={Login} />

        <Route path="/main" component={App} />

        <Route path="/signup" component={Signup} />

        <Route path="/links" component={InfoContainer} />

        <Route path="*" component={NotFound} />

      </Switch>

    </Router>

  </Provider>

);


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


links는 인증된 사용자만 볼 수 있으므로 Tracker.autorun이라는 Reactivity Computation 영역에서 Meteor.user()라는 Reactivity Source가 로그인 상태인지 검사한다. 

  - 로그인 상태가 아니면 무조건 login화면으로 이동

  - 로그인 상태이면서 인증이 필요없는 화면인 경우 link화면으로 이동

// client/main.tsx 

import { Tracker } from 'meteor/tracker';


Tracker.autorun(() => {

  const isAuthenticated = !!Meteor.user();

  if (isAuthenticated) {

    const pathname = browserHistory.location.pathname;

    const unauthenticatedPages: any = ['/', '/signup'];

    const isUnauthenticatedPage = unauthenticatedPages.includes(pathname);

    if (isUnauthenticatedPage) {

      browserHistory.push('/links'); // main

    } else {

      browserHistory.push(pathname);

    }

  } else {

    browserHistory.push('/'); // login

  }

});


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


imports/ui/Info.tsx 에 logout 기능 추가

...

import { Accounts } from 'meteor/accounts-base';


interface InfoProps {

  links: any;

  loading: boolean

}


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


  linkList() {

    const { links, loading } = this.props;

    if (loading) {

      return <div>loading...</div>

    } else {

      return <LinkList links={links} />

    }

  }


  onLogout = () => {

    Accounts.logout();

  }


  render() {

    return (

      <div>

        <button onClick={this.onLogout}>Log out</button>

        <AddLink />

        {this.linkList()}

      </div>

    );

  }

}




가입 및 로그인 테스트 확인하기


가입을 하고, 로그인을 한다. 

Chrome Extension 으로 MiniMongoExplorer를 설치하고 user Collection의 내용을 확인한다. 


또는 Chrome DevTool의 Application에서 로그인 사용자의 userId와 console에서 확인해 볼 수 있다. 또한 Link페이지에서 로그아웃을 해보고 콘솔을 확인해 본다. 




<참조>

- 크롬 확장툴 MiniMongoExplorer

- 세번째 글 소스

posted by peter yun 윤영식
2018.11.16 16:07 Meteor/React + Meteor

Meteor에서 Redux 환경을 설정하고, 애플리케이션에 적용해 본다.


  - Meteor Collection의 Pub/Sub 설정

  - Collection의 CRUD 서비스 클래스 개발

  - Redux 환경 설정

  - Redux의 action, reducer 개발 및 store 설정

  - 애플리케이션에 Redux action 호출하기

  - 소스코드 




MongoDB Collection Pub/Sub 설정


기본설치를 하게 되면 autopublish 미티어 패키지가 설치되어 있다. 삭제한다.

$ meteor remove autopublish


삭제를 하면 links 컬렉션으로 부터 데이터를 받지 못한다. 이제 필요한 것을 받기 위해 publish, subscribe를 설정한다. 

  - server/main.tsx에서 insertLink 코드를 제거한다.

  - server/main.tsx에서 links 코드 import 문을 수정한다. 

// main.tsx 전체코드

import { Meteor } from 'meteor/meteor';

import '../imports/api/links';  // 서버에서 사용하기 위해 반드시 import해야 한다. 하지 않으면 collection 생성, 제어를 못 한다.


Meteor.startup(() => { }); // 텅빈 코드 블록


  - imports/api/links.ts 에 publish 한다. 

if (Meteor.isServer) {

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

}


  - subscribe를 사용할 때 미티어의 Tracker.autorun을 사용하지 않고, React의 props에 subscribe정보를 바로 맵핑해 주는 withTracker를 사용하기 위해 react-meteor-data 패키지를 사용한다.

$ meteor npm install --save react-meteor-data


  - imports/api/ui/Info.tsx에 withTracker를 설정한다. 

import { Meteor } from 'meteor/meteor';

import Links from '../api/links';


interface InfoProps {

links: any;

loading: boolean;

}


// 맨 하단

export default withTracker(() => {

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

return {

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

loading: !handle.ready()

}

})(Info);




MongoDB Collection CRUD


간단한 Link CRUD 애플리케이션을 만든다. 


  - Collection의 CRUD 로직을 담은 service를 생성한다. imports/ui밑에 link폴더를 생성한다. 

// imports/ui/link/link.service.ts

import Links from '../api/links';


class LinkService {

  addLink({title, url}) {

    Links.insert({title, url, createAt: new Date()});

  }


  removeLink(_id: string) {

    Links.remove(_id);

  }


  updateLink(_id: string, {title, url}) {

    Links.update(_id, {title, url, updateAt: new Date()});

  }

}


export const linkService = new LinkService();


  - imports/ui/link/AddLink.tsx 파일 추가

import * as React from 'react';

import { linkService } from './links.service';

export interface AddLinkProps {

}

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

  handleSubmit = (e: any) => {

    //ignore validation

    e.preventDefault();

    const param = {

      title: e.target.title.value,

      url: e.target.url.value

    }

    linkService.addLink(param);

  };


  public render() {

    return (

      <form onSubmit={this.handleSubmit} >

        <input type="text" name="title" placeholder="title" />

        <input type="text" name="url" placeholder="url" />

        <button>Add Link</button>

      </form>

    )

  }

}


  - imports/ui/App.tsx에서 <Info/> 만 남기고 삭제하고, Info.tsx에 AddLink를 추가한다. 

// App.tsx

class App ... {

  render() { 

    return (

      <div></InfoContainer/></div>

    )

  }

}


// Info.tsx
class Info ... {
  render() {
    return(
      <div> 
          <AddLink />
          ....
       </div>
    )
  }
}


  - link폴더 밑에 LinkList.tsx와 Link.tsx를 추가한다. 

// Link.tsx

import * as React from 'react';

export interface LinkProps {

  link: any

}


export default 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>

      </li>

    );

  }

}


// LinkList.tsx

import * as React from 'react';

import Link from './Link';

export interface LinkListProps {

  links: any[]

}


export default class LinkList extends React.Component<LinkListProps, any> {

  public render() {

    const links = this.props.links.map(

      link => <Link link={link}/>

    );

    return (

      <div>

        <h2>Links</h2>

        <ul>{links}</ul>

      </div>

    );

  }

}



  - Info.tsx에서 LinkList를 사용토록 변경한다. 

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

  linkList() {

    const { links, loading } = this.props;

    if (loading) {

      return <div>loading...</div>

    } else {

      return <LinkList links={links} />

    }

  }


  render() {

    const { links } = this.props;

    return (

      <div>

        <AddLink />

        {this.linkList()}

      </div>

    );

  }

}


  - 마지막으로 link 삭제를 한다. 

// imports/ui/link.Link.tsx

import * as React from 'react';

import { linkService } from './links.service';


export interface LinkProps {

  link: any

}


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

  removeLink = () => {

    const { link } = this.props;

    linkService.removeLink(link._id);

  }


  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

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

        <button onClick={this.removeLink}> x </button>

      </li>

    );

  }

}





Redux 관련 패키지 설치


redux 기본 패키지를 설치한다. 

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

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


redux action, reducer을 보다 편하게 사용할 수 있는 typesafe-actions 모듈을 설치한다.

$ meteor npm install --save typesafe-actions 


redux에서 비동기를 처리할 수 있는 redux-observable 과 rxjs 모듈을 설치한다. 

$ meteor npm install --save redux-observable rxjs


state 갱신으로 인한 반복 rendering을 제거해 성능향상을 위한 reselect 모듈도 설치한다. 

$ meteor npm install --save reselect




Redux 코드 개발


link action 개발

  - action type constants 정의

  - action Model 정의

  - action  에 대한 정의

// imports/ui/pages/link/link.action.ts

import { action } from 'typesafe-actions';


/**************************

 * constants, model

 **************************/

// constants

export const ADD_REQUEST = '[link] ADD_REQUEST';

export const ADD_SUCCESS = '[link] ADD_SUCCESS';

export const ADD_FAILED = '[link] ADD_FAILED';

export const DELETE_REQUEST = '[link] DELETE_REQUEST';

export const DELETE_SUCCESS = '[link] DELETE_SUCCESS';

export const DELETE_FAILED = '[link] DELETE_FAILED';

export const CHANGE = '[link] CHANGE';


// model

export type LinkModel = {

  _id?: string;

  title?: string;

  url?: string;

  visited?: boolean;

  error?: boolean;

  errorMsg?: any;

  success?: boolean;

  createdAt?: any;

  updatedAt?: any;

};


/**************************

 * actions, action-type

 **************************/

export const addLink = (params: LinkModel) => action(ADD_REQUEST, params);

export const addLinkSuccess = (params: LinkModel) => action(ADD_SUCCESS, params);

export const addLinkFailed = (params: LinkModel) => action(ADD_FAILED, params);

export const removeLink = (id: string) => action(DELETE_REQUEST, id);

export const removeLinkSuccess = (id: string) => action(DELETE_SUCCESS, id);

export const removeLinkFailed = (id: string) => action(DELETE_FAILED, id);

export const changeLink = (id: string) => action(CHANGE, id);


link reducer 개발

  - Link State 정의

  - LinkAction 타입 

  - Link reducer 분기

  - reselect를 이용한 selector는 별도로 만들지 않고, link.reduer.ts 파일이 함께 둔다. (파일이 너무 많아져서...)

// imports/ui/pages/link/link.reducer.ts

import { ActionType } from 'typesafe-actions';

import { combineReducers } from 'redux';

import { createSelector } from 'reselect';

import * as actions from './link.action';


/**************************

 * state

 **************************/

// state

export type LinkState = {

  list: actions.LinkModel[],

  linkFilter: string

};


/**************************

 * reducers

 **************************/

export type LinkAction = ActionType<typeof actions>;

export const linkReducer = combineReducers<LinkState, LinkAction>({

  list: (state = [], action) => {

    switch (action.type) {

      case actions.ADD_FAILED:

        return state;

      case actions.ADD_SUCCESS:

        return [...state, action.payload];

      case actions.DELETE_FAILED:

        return state;

      case actions.DELETE_SUCCESS:

        return state.filter(item => item._id === action.payload);

      case actions.CHANGE:

        return state.map(

          item =>

            item._id === action.payload

              ? { ...item, visited: !item.visited }

              : item

        );

      default:

        return state;

    }

  },

  linkFilter: (state = '', action) => {

    switch (action.type) {

      case actions.CHANGE:

        if (action.payload === 'visited'){

          return '';

        } else {

          return 'visited';

        }

      default:

        return state;

    }

  }

})


/**************************

 * selectors

 **************************/

export const getLinks = (state: LinkState) => state.list;

export const getLinkFilter = (state: LinkState) => state.linkFilter;

export const getFilteredLinks = createSelector(getLinks, getLinkFilter, (links, linkFilter) => {

  switch (linkFilter) {

    case 'visited':

      return links.filter(t => t.visited);

    default:

      return links;

  }

});


redux-observable을 이용한 epic을 만든다.

  - async 처리

  - rxjs 이용

  - takeUntil은 cancel을 위한 장치이다.

// imports/ui/pages/link/link.epic.ts

import { Epic, combineEpics } from 'redux-observable';

import { filter, map, switchMap, takeUntil } from 'rxjs/operators';

import { isOfType } from 'typesafe-actions';


import Links from '../../../api/links';

import { insertCollection, removeCollection, RequestModel } from '../../sdk';

import * as actions from './link.action';


const addLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.ADD_REQUEST)),

    switchMap(action => {

      const { title, url } = action.payload;

      return insertCollection(Links, { title, url, createdAt: new Date() })

    }),

    map((response: RequestModel) => {

      if (response.error) {

        return actions.addLinkFailed({ ...response.result })

      }

      return actions.addLinkSuccess(response.result)

    }),

    // takeUntil(action$.pipe(

    //   filter(isOfType(actions.ADD_REQUEST))

    // ))

  );


const removeLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.DELETE_REQUEST)),

    switchMap(action => {

      return removeCollection(Links, action.payload);

    }),

    map((response: RequestModel) => {

      if (response.error) {

        return actions.removeLinkFailed({ ...response.result, ...response.params })

      }

      return actions.removeLinkSuccess(response.params._id);

    }),

    // takeUntil(action$.pipe(

    //   filter(isOfType(actions.ADD_REQUEST))

    // ))

  );


export const linkEpic = combineEpics(addLink, removeLink);


Meteor Client Collection 을 Observable로 전환

  - meteor client collection의 insert, remove시에 call 결과를 Observable로 변환하여 반환하는 유틸을 만든다. 

  - 이는 redux-observable의 epic에서 async 데이터를 switchMap 오퍼레이터로 다루기 위함이다. 

// imports/sdk/util/ddp.util.ts

import { from, Observable } from 'rxjs';


export type RequestModel = {

  error?: boolean;

  success?: boolean;

  result: any;

  params?: any;

}


export function insertCollection(collection: any, params: any): Observable<RequestModel> {

  return from(new Promise((resolve, reject) => {

    collection.insert(params, (error, result) => {

      if (error) {

        reject({ error: true, result: { ...error }, params: { ...params } });

      }

      if (typeof result === 'string' || typeof result === 'number') {

        resolve({ success: true, result, params: {...params} });

      } else {

        resolve({ success: true, result: { ...result }, params: { ...params } });

      }

    });

  }));

}


export function removeCollection(collection: any, _id: string): Observable<RequestModel> {

  return from(new Promise((resolve, reject) => {

    collection.remove(_id, (error, result) => {

      if (error) {

        reject({ error: true, result: { ...error }, params: { _id } });

      }

      if (typeof result === 'string' || typeof result === 'number') {

        resolve({ success: true, result, params: { _id } });

      } else {

        resolve({ success: true, result: { ...result }, params: { _id } });

      }

    });

  }));

}




RootReducer와 Store 설정


link관련 action, reducer, epic 개발이 끝나면 이를 등록하는 설정을 한다. 

  - root Reducer 정의

  - root State  타입 정의

  - root Action 타입 정의

  - root Epic 정의

  - root Store 설정

// imports/ui/store.ts

import { combineEpics, createEpicMiddleware } from 'redux-observable';

import { createStore, applyMiddleware, combineReducers, compose } from 'redux';

import { RouterAction, LocationChangeAction } from 'react-router-redux';

import { routerReducer } from 'react-router-redux';

import { StateType } from 'typesafe-actions'; 


import { linkEpic } from './pages/link/link.epic';

import { linkReducer, LinkAction } from './pages/link/link.reducer';


/***********************

 * root reducer

 ***********************/

const rootReducer = combineReducers({

  router: routerReducer,

  links: linkReducer

});


/***********************

 * root state

 ***********************/

export type RootState = StateType<typeof rootReducer>;


type ReactRouterAction = RouterAction | LocationChangeAction;

export type RootAction = ReactRouterAction | LinkAction;


/***********************

 * root epic

 ***********************/

const composeEnhancers =

  (process.env.NODE_ENV === 'development' &&

    (window as any) &&

    (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||

  compose;


const rootEpic = combineEpics(linkEpic);

const epicMiddleware = createEpicMiddleware();


/***********************

 * root store

 ***********************/

function configureStore(initialState?: object) {

  // configure middlewares

  const middlewares = [epicMiddleware];

  // compose enhancers

  const enhancer = composeEnhancers(applyMiddleware(...middlewares));

  // create store

  const store = createStore(rootReducer, enhancer);

  // run epic: https://redux-observable.js.org/docs/basics/SettingUpTheMiddleware.html

  epicMiddleware.run(rootEpic);

  return store;

}


const store = configureStore();

export default store;


애플리케이션에 store를 최종 설정한다. 

// client/main.tsx

import * as React from 'react';

import { Meteor } from 'meteor/meteor';

import { render } from 'react-dom';

import { Provider } from 'react-redux';


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

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


const Root = (

  <Provider store={store}>

    <App />

  </Provider>

);


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


window 객체 인식을 위해 type definition을 정의한다. 

  - 루트에 typings 폴더 생성

  - typings/index.d.ts 파일 생성

declare var window: any;




Application에서 Redux Action 호출하기


add/remove link 리덕스 액션을 애플리케이션에 적용한다. link.service.ts 는 사용하지 않는다. 

  - AddLink.tsx에 addLink 액션 적용

// imports/ui/link/AddLink.tsx

import * as React from 'react';

import { connect } from 'react-redux';

import { addLink } from './link.action';


export interface AddLinkProps {

  addLink: Function;

}


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

  handleSubmit = (e: any) => {

    //ignore validation

    e.preventDefault();

    const param = {

      title: e.target.title.value,

      url: e.target.url.value

    }

    const { addLink } = this.props;

    addLink(param);

  };


  public render() {

    return (

      <form onSubmit={this.handleSubmit} >

        <input type="text" name="title" placeholder="title" />

        <input type="text" name="url" placeholder="url" />

        <button>Add Link</button>

      </form>

    )

  }

}


export default connect(undefined, { addLink })(AddLink);


  - Link.tsx에 removeLink 액션 적용

// imports/ui/pages/link/Link.tsx

import * as React from 'react';

import { removeLink } from './link.action';

import { connect } from 'react-redux';


export interface LinkProps {

  link: any,

  removeLink: Function

}

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

  removeLink = () => {

    const { link, removeLink } = this.props;

    removeLink(link._id);

  }


  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

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

        <button onClick={this.removeLink}> x </button>

      </li>

    );

  }

}


export default connect(undefined, { removeLink })(Link);





<참조>

- 블로그 소스 코드

react-redux-typescript-guide 소스

- redux-actions 사용하기

- redux-observable 사용하기

- typesafe-actions 사용하기

- reselect 사용하여 성능 최적화 하기

posted by peter yun 윤영식
2018.11.13 16:42 Meteor/React + Meteor

Meteor v1.8이 되면서 CLI에 react기반 애플리케이션 자동 생성 기능이 추가되었다. 여기에 Typescript와 Redux을 접목해서 개발환경을 꾸며 본다. 


  - React기반 Meteor 애플리케이션 생성

  - SCSS 환경설정

  - Typescript 환경설정

  - 미티어의 Reactivity Computation인 withTracker 확인

  - AntDesign CSS Framework 설정

  - 소스코드




React 기반환경 만들기 


meteor를 설치한다. 

// Node Version Manager를 통해 Node LTS 버전을 설치한다. 최신 Meteor 1.8 은 Node v8.*을 사용한다.

$ nvm install 8.12.0


// meteor를 설치한다. 

$ curl https://install.meteor.com | sh


// 향후 meteor v1.8.1 버전이 릴리즈 되면 성능향상을 위해 업그레이드를 반드시 수행한다. 

$ meteor update --release 1.8.1


애플리케이션 생성

// 명령어: meteor create --react <AppName>

$ meteor create --react react-first

$ cd react-first

$ meteor run




SCSS 환경 전환


.css파일을 .scss파일로 전환한다. 이를 지원하기 위해 scss 패키지를 설치한다. 

$ meteor add fourseven:scss




Typescript 관련 모듈 설치


meteor의 typescript 지원 패키지를 설치한다.

$ meteor add barbatus: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 typescript

$ meteor npm install --save-dev tslint

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




Typescript 환경 전환


root에 tsconfig.json 파일 추가

{

    "compilerOptions": {

        "target": "es6",

        "allowJs": true,

        "jsx": "preserve",

        "moduleResolution": "node",

        "types": [

            "node"

        ]

    }

}


root에 tslint.json 파일 추가 

// 과도한 lint가 귀찮아 extends는 주석처리함 

{

    // "extends": [

    //     "tslint:latest",

    //     "tslint-react"

    // ],

    "rules": {

        "quotemark": [

            true,

            "single",

            "jsx-double"

        ],

        "ordered-imports": false,

        "no-var-requires": false

    }

}


root의 client와 imports, server 폴더안의 파일 명칭 .jsx / .js 를 .tsx / .ts 로 변경한다. tests 폴더는 .js 그대로 둔다.



마직막으로 package.json에서 mainModule의 client를 main.tsx로 수정한다. 





.tsx 파일 내용을 typescript에 맞게 수정하기


1) import 문 수정

// client, imports 폴더안의 모든 .tsx 파일


// react import

import React from 'react'; ==> import * as React from 'react';


// 절대경로를 상대경로로 수정

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


// .jsx 파일을 확장자 제거 App.tsx

import Hello from './Hello.jsx' ==> import Hello from './Hello';

import Info from './Info.jsx' ==> import Info from './Info';


2) class 문 수정

class Hello extends Components {  ==> class Hello extends React.Component {


3) class에 Props 타입 설정

// info.tsx


interface InfoProps {

  links: any;

}


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

  ...

}


4) export default 문 수정

    withTracker는 Meteor의 Reactivity Computation 공간이다. Computation에는 Meteor의 Reactivity Resource가 위치한다. 즉, source가 변경되면 Computation영역이 재실행된다. 

// imports/ui/info.tsx 

export default InfoContainer = withTracker(...)

   ==> 

export default withTracker(...);


// imports/api/links.ts

export default Links = new Mongo.Collection('links');

   ==>

const Links = new Mongo.Collection('links');

export default Links;


이제 다시 meteor run 을 실행한다. 



Ant Design 설치하기


CSS Component를 제공하는 antd를 설치한다. 

$ meteor npm install --save antd

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


client/main.tsx 안에 antd css 를 import 한다. 

import '../node_modules/antd/dist/antd.css';


또는, antd.less 파일을 직접 import할 수 있다. 

$ meteor add less

$ meteor npm install --save indexof


// client/theme.less 파일 생성하고 antd.less 파일 import하기

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


antd의 Menu를 App.tsx에 적용해 본다. 

import * as React from 'react';

import Hello from './Hello';

import Info from './Info';

import { Menu, Icon } from 'antd';


const SubMenu = Menu.SubMenu;

const MenuItemGroup = Menu.ItemGroup;


class App extends React.Component {

  state = {

    current: 'mail',

  };


  handleClick = (e) => {

    console.log('click ', e);

    this.setState({

      current: e.key,

    });

  }


  render() {

    return (

      <div>

        <Menu

          onClick={this.handleClick}

          selectedKeys={[this.state.current]}

          mode="horizontal"

        >

          <Menu.Item key="mail">

            <Icon type="mail" />Navigation One

            </Menu.Item>

          <Menu.Item key="app" disabled>

            <Icon type="appstore" />Navigation Two

            </Menu.Item>

          <SubMenu title={<span className="submenu-title-wrapper"><Icon type="setting" />Navigation Three - Submenu</span>}>

            <MenuItemGroup title="Item 1">

              <Menu.Item key="setting:1">Option 1</Menu.Item>

              <Menu.Item key="setting:2">Option 2</Menu.Item>

            </MenuItemGroup>

            <MenuItemGroup title="Item 2">

              <Menu.Item key="setting:3">Option 3</Menu.Item>

              <Menu.Item key="setting:4">Option 4</Menu.Item>

            </MenuItemGroup>

          </SubMenu>

          <Menu.Item key="alipay">

            <a href="https://ant.design" target="_blank" rel="noopener noreferrer">Navigation Four - Link</a>

          </Menu.Item>

        </Menu>

        <Hello />

        <Info />

      </div>

    );

  }

}


export default App;




지금까지의 적용 소스코드, Redux 환경 설정하기는 다음 글에서..




<참조>


- meteor-react-typescript 코드 샘플

- meteor changelog

- meteor에 antd .less 파일 import하기 (데모소스)

- meteor-react-typescript-boilerplate 소스

posted by peter yun 윤영식
2018.09.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 윤영식
2016.10.23 22:59 Angular/Concept

Angular v2가 정식 릴리즈되었다. Angular v1 은 Two-way data-binding 이라는 독특한 특징으로 인해 많은 사용자 층을 확보했지만 장점 만큼이나 성능상의 단점도 존재했었다. 또한 처음엔 쉬운듯 하면서 좀 더 깊게 들어가볼려고 하면 학습곡선이 갑자기 껑충뛰기도 했다. 가장 많이 사용했던 Directive(지시자)가 대표적이다. 많은 개발자가 만들어 놓은 지시자를 쉽게 가져다 쓸 수는 있지만 직접 만들어 애플리케이션에 접목하려 할 때 첫 문턱을 만나게 된다. 그리고 jQuery사용에 익숙한 개발자에게 Angular v1 시점상의 차이로 Angular v1 방식의 개발패턴을 요구하기도 했다. 관성은 무섭다. 기존에 사용하던 방식을 버리고 Angular v1에 맞춰서 애플리케이션을 만들어 가기란 곤혹스럽다. Angular v2 또한 그런 인식의 전환을 요구할까? 그렇다 그리고 아니다. 






웹 애플리케이션 흐름

웹 애플리케이션 개발을 위해 우리가 사용하는 jQuery같은 라이브러리나 Angular, Backbone같은 프레임워크의 가장 1차적인 목적은 무엇일까? 나는 Data Projection이라 생각한다. 데이터를 화면에 출력하기 위해 DOM을 얼마나 쉽게 조작하고 상호 작용할 수 있느냐가 선택의 기준이라 생각한다. Data Projection을 일관되고 확장가능하고 배포가능하게 하는 방식으로 기술은 발전해 왔고, 현재는 화면에 대한 제어방식이 컴포넌트 기반 방식으로 발전해 오고 있다. 


Data Projection의 역사를 보면 초장기엔 Server Side Rendering 를 사용해 웹 애플리케이션을 개발했다. 예로 JSP, PHP, ASP 같이 서버 미들웨어서 데이터를 조회하고 HTML을 조작하여 결과 HTML을 브라우져에 전송하던 시대이다. 




1세대에는 AJAX가 나오고 다양한 라이브러리나 프레임워크가 나왔다. 이때는 데이터변경에 대한 DOM반영이 서버에서 클라이언트 개발자의 몫으로 넘어오게 되었다. 즉, 직접 DOM 을 얻어와서 특정 위치에 넣어 주어야 했고, DOM에서 발생하는 이벤트를 Listening해서 처리하고 DOM에 반영하는 모든 작업이 웹 개발자가 직접 코딩하던 단계였다. Java의 프레임워크 역사로 보면 Struts 로 비유할 수 있지 않을까 싶다. 




2세대로 넘어오게 되면 Model을 DOM 에 반영하는 방식은 자동화 된다. 여기에 대표적인 프레임워크가 Ember 와 Angular v1 이다. 이때 부터 Single Page Application (SPA) 개발이라는 용어가 나오게 된다. URI 변경에 대한 대응으로 Routing  개념이 나오고, Data Projection후 원하는 일부 DOM을 변경하는 역할이 프레임워크로 넘어갔고, 웹 개발자는 좀 더 애플리케이션 비즈니스 로직에 집중토록 만들었다. Java 프레임워크로 비유하자면 Struts와 Spring Framework 초기버전의 중간 지대 정도 쯤이라 생각한다.  이때부터 Frontend (프론트앤드)라는 직군이 웹 개발자와 분리되기 시작한 지점이라 생각한다. 이에 대한 자세한 설명은 태곤님이 작성한 "[번역] 프론트엔드 개발자는 왜 구하기 어렵나요?"를 참조하자. 2011년을 기점으로 2013년 웹 애플리케이션 프레임워크가 정착을 해가는 시기였고, 현재는 대부분의 스타트업이나 중견기업에서 2세대 웹 애플리케이션 프레임워크를 선택할 경우 프론트엔드 개발자와 백앤드 개발자를 구분하여 팀을 구성하고 있는 추세이다.





3세대는 2세대의 과도기를 거쳐 2세대의 장점을 흡수 하면서 성능상의 이슈를 해결하고, 점점 복잡해 지고있는 웹 애플리케이션을 보다 직관적이고 쉽게 개발할 수 있게 노력하고 있다. 대표적인 프레임워크로는 Facebook의 React와 Google의 Angular v2 (이하 Angular)이다. Angular는 Component기반 개발 방식으로 표준인 Web Components를 지원하며 Typescript를 기본 언어로 채택했다. Typescript는 Type 시스템을 제공하기 때문에 개발단계에서 버그의 가능성을 쉽게 찾을 수 있도록 도와준다. React와 Angular에 대한 장단점은 손창욱님의 "React보다 Angular v2에 더 주목해야 하는 이유"를 참조하자. Java의 Spring Framework이 성숙하면서 Annotation 같은 기능이 추가되듯, Angular v2 프레임워크는 Java의 Spring 프레임워크 최신버전과 비유할 수 있다. 



Angular v1에 대한 개발 및 컨설팅을 3년 가까이 하면서 올해 초 Angular v2를 공부하고 기존 v1 코드를 v2 코드로 전환하면서 코드 베이스는 50%가량 줄었고, 반응속도는 30%가량 개선되었다. 8명 프론트앤드 개발자와 컨버전을 진행하면서 이구동성으로 말하는 것은 "코드가 직관적으로 변했다. 코드량이 현저히 줄었다. Typescript의 타입체킹으로 인해 실수를 최소화 할 수 있었다" 이다. 



Angular v2 왜 배워야 하는가?

Angular를 왜 배워야 하는가? 답하자면 안배워도 된다. 단순 홈페이지나 업무 화면이라면 쉽고 더 빨리 만들 수 있는 워드프레스나 서비스를 이용하거나 DOM 핸들링 라이브러리나 플러그인을 사용해 개발하는 편이 낫다. 하지만 솔루션의 복잡한 요구사항을 지속적으로 반영해야 하고 DOM제어가 복잡해 질 가능성이 높다면 jQuery, React 같은 라이브러리 보다는 Angular 같은 프레임워크를 선택하는 것이 좋다. 그리고 최근에는 ES2015 표준이 확정되었고 최신 브라우져에 대부분 기능이 구현되고 있다. 2세대와 3세대 Data Projection의 가장 큰 개발 방식의 차이는 ES2015의 이해에서부터 시작한다.  즉, ES2015 문법을 잘 알고 사용하면 좀 더 쉽고 간단하게 코드 베이스를 유지하면서 오류를 최소화할 수 있다. 예로 -> 펑션은 this에 대한 오류를 방지하고, Set/Map등 Collection은 Java의 Collection과 유사한다. 



Angular v2 시작하면 초기에 배워야 하는 것들이 갑자기 늘어난다. 이것은 2세대와 3세대의 개발 패턴이 바뀌었음을 시사한다. ES2015 문법은 그대로 TypeScript에 녹아 있고, Type System과 Annotation 기능이 녹아 들어 더욱 편리한 개발을 가능토록 한다. 따라서 ES2015의 Syntax와 개념을 이해해야 한다. 그리고 Typescript를 다시 공부해야 한다. 또한 요즘 인기를 누리고 있는 Reactive Programming을 표방한 대표적인 라이브러리인 RxJS를 Angular가 근간으로 사용하고 있다. 따라서 RxJS 에 대한 개념과 사용법을 익혀야 한다. 그런후 Web Components 란 무엇인지 알아야 하고, Angular 프레임워크의 아키텍쳐를 구성하는 개념인 Change Detection 동작원리, Dependency Management, Modulization 을 알아야 하고, 다음으로 주변의 Tooling System으로 SystemJS (Webpack), Gulp 등을 알아야 한다. 


이렇게 열거해 보니 참으로 배울 것이 많다. 다시 말하지만 안 배워도 된다. 하지만 자신의 근육을 한단계 업그레이드 시키기 위해 고통스러운 인내의 시간은 필요하다. 배워야 하는 기준은 두가지 정도로 이야기 해본다. 


첫째, 서비스 버전업을 위해 요구사항이 계속 증가하고 있는가?

둘째, 더 적고 직관적인 코드 베이스를 유지하면서 성능을 높이고 싶은가?


 

프론트엔드 개발자 직군이 새롭게 자리잡게된  5년기간 동안 많은 부분이 기존의 백앤드 개발 패턴과 유사해 지고 있다. 모듈 의존성 관리, 빌드 시스템, 프레임워크의 발전은 Java개발자들이 초장기 프레임워크 없이 개발하다 Struts를 만났을 때 기쁨에서 Spring을 만나 자유를 얻었지만 여전히 배워야 할 것들은 더욱 증가했음을 알것이다. 그러나 어쩌겠는가 우리는 더 게을러 지고싶다는 욕구가 있고 프레임워크가 그것을 만족시켜줄것이라는 희망을 품고 있는 한 배움과 진보는 계속될 뿐이다. 



참조


posted by peter yun 윤영식
2016.05.01 15:34 Angular/Concept

컴포넌트 기반 개발시에 어떤 설계방식으로 해야하는지 알아본다. 새롭게 알게된 GistRun 서비스를 사용해서 테스트해 볼 것이다. 예제의 내용은 최근 Rangle.io의 Angular2 Component 온라인 강좌를 참조한다.





GistRun 사용하기 


github기능중 코드 조각을 관리할 수 있는 서비스가 있다. 한번에 최대 10개까지 파일을 만들 수 있다.  여기를 클릭해 보면 Gist에 입력한 한 주제에 대한 파일을 보여주고 Gist의 내역을 테스트 해 볼 수 있다. 



plunker 또는 jsfiddle의 내용도 불러와서 나의 Gist에 저장을 할 수 있다. 각 서비스의 장점이 있지만 일단 GistRun은 UI가 깔끔하고 속도가 빠르다. 그리고 수정사항에 대한 버전을 관리할 수 있고, Github의 종속된 서비스이므로 소스를 관리한다는 입장에 일관성을 가질 수 있을 것으로 보인다. Gist만으로 모자랐던 것을 GistRun이 생명력을 불어 넣은 격이다. 





Angular2 Component 가이드


Angular2는 컴포넌트로 시작해서 컴포넌트로 끝난다. 컴포넌트기반으로 UI를 설계할 때 자주 나오는 방식이 프리젠테이션과 컨테이너의 분리이다. 


  - Container 컴포넌트

    + 데이터와 비즈니스 로직을 다룬다

    + 컴포넌트 트리(Tree)를 렌더링 한다

    + 재사용하기 어렵다 


  - Presentational 컴포넌트

    + 상태를 포함하지 않는다. 따라서 컴포넌트 속성에 대한 수순(pure) 펑션으로 구성한다

    + 표현만을 담당한다

    + 재사용이 가능하다 


Thinking in React의 내용은 Angular2의 컴포넌트 개발 방식과 거의 일치한다. Container 컴포넌트는 하위 컴포넌트의 이벤트에 대해 XHR 요청을 수행하는 Angular2 Service를 Inject해서 서버로 부터 데이터를 요청하고, Component Tree를 통해 하위로 데이터를 내려 보낼 수 있다. 



오렌지색 부분은 전체 애플리케이션을 감싸는 Container이고 초록색은 테이블의 Row를 표현한다. 프리젠테이션 부분은 상단의 Search Input, 하단의 Row들이 된다. Angular2에서 검색을 위한 Input과 Button, Title 애플리케이션을 다음과 같이 진행한다. 



Seach The Site 레이블을 표현하는 컴포넌트를 개발한다.  @은 Typescript의 Decorator를 이용해 Angular2에 구현된 것으로 Parent -> Child 방향으로 DOM Properties를 통해 값을 받고자 할 때 사용한다. 즉, RangleLabel 컴포넌트를 사용하는 Parent 컴포넌트가 name 속성에 [] 을 이용해 값을 설정한다.  

  - 컴포넌트를 사용할 때 [name]="<expression>"  [name] 부분을 target이라 하고 <expression> 부분을 source라고 한다. 

  - []을 사용할 때 source는 항상 template expression 즉, 식이어야 한다. (statement "문"은 이벤트에서 사용함) 

  - target에 []가 있으면 source는 항상 expression으로 수행된다.

import { Component, Input } from 'angular2/core';


@Component({

  selector: 'rangle-label',

  template: '<label class="rangle-label">{{ name }}</label>',

  styles: [ `

    .rangle-label {

      color: #422D3F;

      display: block;

      font-weight: bold;

      letter-spacing: .2em;

      text-transform: uppercase;

    }

  `]

})

export class RangleLabel {

  @Input() private name: string;

}


예) 부모 컨테이너에서 사용할 때: 부모 컨테이너 컴포넌트의 name 속성을 자식 컴포넌트인 rangle-label에 내려보낸다.  

     <rangle-label [name]="name"></rangle-label>


다음으로 Input 컴포넌트를 만든다. value 속성에 대해 ngModel처럼 양방향 바인딩을 주기 위해서는 컨벤션(Convention)처럼 Input은 value이고, Output은 valueChange이어야 한다. 

  - [()] 표식은 banana in the box 표현으로 양방향 데이터 바인딩이다. 

  - [] 은 Input으로 parent -> child로 속성값을 내려 보낼 수 있다. (top down)

  - () 은 Output으로 child -> parent로 값을 올려 보낼 수 있다. (bubble up)

import { Component, Input, Output, EventEmitter } from 'angular2/core';


@Component({

  selector: 'rangle-text-field',

  template: `

    <input class="rangle-text-field"

      [placeholder]="placeholder"

      #field (keyup)="handleKeyup(field.value)">

  `,

  styles: [ `

    .rangle-text-field {

      border-radius: 3px;

      border: 1px solid #ccc;

      box-sizing: border-box;

      display: inline-block;

      font-size: inherit;

      font-weight: inherit;

      height: 2.5rem;

      padding: .5rem;

    }

  `]

})

export class RangleTextField {

  @Input() private placeholder: string;

  @Input() private value: String;

  @Output() private valueChange = new EventEmitter<String>();


  handleKeyup(fieldValue: string): void {

    this.valueChange.emit(fieldValue);

  }

}


예) 부모 컨테이너에서 사용할 때 searchTerm은 부모 컨테이너 컴포넌트의 속성이다. value를 내려보내기도 하고, 이벤트를 받기도 한다. 

      <rangle-text-field 

        placeholder="Enter Keyword"

        [(value)]="searchTerm">

      </rangle-text-field>


버튼을 만들어 보자. 템플릿의 target=source 에서 target이 ()로 감싸지면 이벤트를 나타내고 source는 statement "문"을 쓴다. expression과 statement에 대한 이해는 Angular.io의 Template Syntax 설명을 참조한다.  여기서는 클릭이 되면 바로 부모의 handleSearch가 호출된다. 

import { Component, Input, Output, EventEmitter } from 'angular2/core'


@Component({

  selector: 'rangle-button',

  template: `

    <button

      [ngClass]="dynamicStyles()"

      class="rangle-button"

      (click)="onClick.emit()">

      {{ name }}

    </button>

  `,

  styles: [ `

    :host {

      display: flex;

      align-items: stretch;

    }

    .rangle-button {

      border: none;

      border-radius: 3px;

      color: white;

      font-weight: bold;

      letter-spacing: .2em;

      margin-left: 0.5rem;

      padding: 0.5rem;

      text-transform: uppercase;

    }

    .primary {

      background: #E5373A;

    }

    .normal {

      background: #422D3F;

    }

  `]

})

export class RangleButton {

  @Input() name: string;

  @Input() isPrimary: boolean;

  @Output() onClick = new EventEmitter();


  dynamicStyles() {

    return this.isPrimary ? 'primary' : 'normal';

  }

}


예) 부모 컨테이너 컴포넌트에서 사용할 때, handleSearch는 부모 컨테이너 컴포넌트의 메소드이다. 

      <rangle-button name="Search"

           [isPrimary]="true"

           (click)="handleSearch(searchTerm)">

      </rangle-button>


다음으로 Input 과 Button 컴포넌트를 재사용할 수 있는 컨테이너를 만들어 본다. Label만을 포함하고 있고 언제는 원하는 형태로 Input과 Button을 템플릿상에 포함 시킬 수 있다. 이를 위해 ng-content 태그를 사용한다. Angular v1의 transclude와 유사하다. 하지만 큰 차이점은 역시 Angular2의 컴포넌트 아키텍쳐는 Comopnent Tree이기 때문에 Change Detection(CD)는 항시 가장 위에서 아래로 점검되고 렌더링된다. Angular v1은 parent - child 관계가 있다하더라도 Parent가 먼저 렌더링 될지 Child가 먼저될지 보장을 못했다. (물론 Directive에 priority 옵션이 있지만 적용하기에 따라 순서가 정확한지 알 수 없었다.) 즉, Angular2에서 ng-content 태그를 사용해도 렌더링에 예측이 가능하다. 





import { Component, Input } from 'angular2/core';

import { RangleLabel } from './rangle-label';


@Component({

  selector: 'rangle-bar',

  directives: [ RangleLabel ],

  template: `

    <rangle-label [name]="name">

    </rangle-label>

    <div class="row">

      <ng-content></ng-content>

    </div>

  `,

  styles: [`

    :host {

      background: #F8F8F8;

      border: solid #ccc 1px;

      display: block;

      padding: 1rem;

      margin: 1rem;

    }

    .row {

      display: flex;

      margin-top: 0.5rem;

    }

  `]

})

export class RangleBar {

  @Input() name: string;

}


예) 부모 컨테이너 컴포넌트 사용 때 

    input과 button이 있는 경우 
    <rangle-bar name="Search the site">
        <rangle-text-field placeholder="Enter Keyword"
             [(value)]="searchTerm">
        </rangle-text-field>
        <rangle-button name="Search"
            [isPrimary]="true"
            (click)="handleSearch(searchTerm)">
        </rangle-button>
    </rangle-bar>

    또는 
   
   button만 있는 경우 
   <rangle-bar name="Other Stuff">
        <rangle-button name="Button 1"
            [isPrimary]="true">
        </rangle-button>
        <rangle-button name="Button 2"></rangle-button>
        <rangle-button name="Button 3"></rangle-button>
    </rangle-bar>


당연히 <ng-content>에는 input, button 컴포넌트외에 다양한 컴포넌트가 올 수 있다. 결과는 다음과 같다. 



실데이터를 요청하는 코드는 RangleBar 에서 XHR에 대한 요청을 하고 결과를 전달하는 과정을 거치면 될 된다. Redux를 사용한다면 Action Creator를 수행한다. 애플리케이션 소스를 보자. App 컴포넌트는 가장 최상위 애플리케이션으로 Container 컴포넌트와 Presentational 컴포넌트를 사용해 화면 레이아웃을 만들고 있다. 

import { ViewEncapsulation, Component } from 'angular2/core';

import { RangleBar }  from './rangle-bar';

import { RangleButton } from './rangle-button';

import { RangleTextField } from './rangle-text-field';


@Component({

  selector: 'app',

  directives: [ RangleBar, RangleTextField, RangleButton ],

  template: `

    <rangle-bar name="Search the site">

      <rangle-text-field placeholder="Enter Keyword"

        [(value)]="searchTerm">

      </rangle-text-field>

      <rangle-button name="Search"

        [isPrimary]="true"

        (click)="handleSearch(searchTerm)">

      </rangle-button>

    </rangle-bar>


    <rangle-bar name="Other Stuff">

      <rangle-button name="Button 1"

        [isPrimary]="true">

      </rangle-button>

      <rangle-button name="Button 2"></rangle-button>

      <rangle-button name="Button 3"></rangle-button>

    </rangle-bar>

    <p> inside viewCapsulation </p>

  `,

  styles: [ 'p { background: red; border: dashed yellow 2px; }' ],

  encapsulation: ViewEncapsulation.Native

})

export class App {

  private searchTerm: string;


  handleSearch(searchTerm: string): void {

    alert(`You searched for '${searchTerm}'`);

  }

}


GistRun의 소스와 Rangle.io 동영상을 참조하자.

  - Container & Presentational Component 개발 방식에 대한 소개 

  - Component LifeCycle에 대한 소개: ng-content를 특별히 View Content라 하고, 그외 템플릿에 직접 설정된 컴포넌트를 View Child라 한다. Life Cycle에서는 둘의 시점이 나뉘어져 있다. View Content가 먼저 호출된다.  

  - ElementRef 주입받아 컴포넌트의 DOM을 사용할 수 있다.





<참조>

  

  - Angular2 Component Basic (pptsrc)- Rangle.io

  - Thinking in React

  - Presentational and Container Component - Dan Abramov

  - Rangle.io's Angular2 GitBook 

  - Change Detection Reinvented - Victor Savkin's

posted by peter yun 윤영식
2015.08.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.08.15 16:09 Meteor

트위터 부트스트랩리액트 컴포넌트로 만들어 놓은 것이 이미 있어서 미티어(Meteor) 에서 사용하는 방법을 알아본다. 



설치 하기 


  - 먼저 미티어 프로젝트 하나를 생성한다. 

$ meteor create test


  - Twiiter Bootstrap 을 React로 변환해 놓은 것 

    + https://github.com/react-bootstrap/react-bootstrap

    + 사용자 메뉴얼

    + webpack을 사용해 번들을 만들었다.


  - react-bootstrap을 미티어의 패키지를 설치한다

   + https://atmospherejs.com/firfi/meteor-react-bootstrap (설명서)

   + package.js 를 추가해서 미티어의 패키지로 만들고, atmosphere에 배포했다. 

$ meteor add firfi:meteor-react-bootstrap

Package.describe({ name: 'firfi:meteor-react-bootstrap', version: '0.0.4', // Brief, one-line summary of the package. summary: 'React-bootstrap port for Meteor', // URL to the Git repository containing the source code for this package. git: 'https://github.com/Firfi/meteor-react-bootstrap', // By default, Meteor will default to using README.md for documentation. // To avoid submitting documentation, set this field to null. documentation: 'METEOR.md' }); Package.onUse(function(api) { api.versionsFrom('1.0.5'); api.use('reactjs:react@0.2.1'); api.use('twbs:bootstrap@3.3.4', 'client'); api.addFiles('dist/react-bootstrap.js'); api.export(['ReactBootstrap']); }); Package.onTest(function(api) {

});


  - 미티어에 리액트를 사용하기 위한 공식 패키지를 설치한다. 

$ meteor add react




사용 하기 


  - FlowRouter와 ReactLayout을 설치한다. 

$ meteor add kadira:flow-router

# meteor add kadira:react-layout


  - 리액트 문구가 들어 있는 파일의 확장자는 .jsx가 되어야 한다. test.jsx 파일을 하나 만들고 다음과 같이 탭을 사용한다. 

    + ReactBootstrap 이 글로벌 변수이다. 

    + 리액트 컴포넌트 (App)는 미티어에서 글로벌 변수여야 한다. 

if (Meteor.isClient) {

  

  App = React.createClass({

    render() {

      return (

        <ReactBootstrap.TabbedArea defaultActiveKey={2}>

          <ReactBootstrap.TabPane eventKey={1} tab='Tab 1'>TabPane 1 content</ReactBootstrap.TabPane>

          <ReactBootstrap.TabPane eventKey={2} tab='Tab 2'>TabPane 2 content</ReactBootstrap.TabPane>

        </ReactBootstrap.TabbedArea>

      )

    }

  });

}


  - Flow Router를 설정한다. 

// test.js 

if(Meteor.isClient) {


  FlowRouter.route('/', {

    // do some action for this route

    action: function(params, queryParams) {

        // console.log("Params:", params);

        // console.log("Query Params:", queryParams);

        // ReactLayout.render(AppLayout, {

        //  content: <MainComponent />

        // })

        ReactLayout.render(App);

    }

  });

  

}


// test.html 

<head>

  <title>test</title>

</head>


  - 탭 결과 화면이 출력된다.

  



오류 해결


  - 크롬 DevTools에 React extension을 설치하고 결과를 보면 다음과 같이 <Unknown> 태그가 나온다. 

    

 - <Unknown>을 제거하기 위해 displayName을 지정한다. 

if (Meteor.isClient) {

  

  App = React.createClass({

    displayName: 'App',


    render() {

      return (

        <ReactBootstrap.TabbedArea defaultActiveKey={2}>

          <ReactBootstrap.TabPane eventKey={1} tab='Tab 1'>TabPane 1 content</ReactBootstrap.TabPane>

          <ReactBootstrap.TabPane eventKey={2} tab='Tab 2'>TabPane 2 content</ReactBootstrap.TabPane>

        </ReactBootstrap.TabbedArea>

      )

    }

  });

}







<참조>


  - npm을 미티어에서 직접 사용하고자 할 경우 meteorhacks:npm의 설정 가이드


posted by peter yun 윤영식
2015.08.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.07.14 22:54 Angular/Concept

Meteor는 Modern Application Platform이기도 하면서 Reactive Manifesto에서 이야기하는 User Responsive를 위한 Resilient와 Scalable을 갖추고 있다. 그래서 Meteor를 Realtime Modern Application을 위한 Reactive Platform이라 이야기를 해도 좋을 것 같다. 하지만 정작 Reactive라고 이야기를 해놓고 Reactive의 개념이 와닿지 않는 것은 거시적, 미시적 이해가 없기 때문이다. 최근에 각광 받고 있는 AngularJS는 Reactive Templating을 지원한다. Reactive라 붙일 수 있는 이유는 Reactive Programming에 대해 위키에서 찾아보면 액셀 예를 통해 어떤것을 의미하는지 이해를 할 수 있다. 다수의 b가 a를 의존할 때 a의 변화에 b가 종속이 되고, 옵저버 패턴(Observer Pattern)으로 보면 다수의 의존하는 Observer(소비자)가 데이터를 제공하는 Observerable(생산자)에 의존하는 것으로 a에 상태변경(이벤트 발생)에 대해 b에도 상태값을 알려주는 구조이다. Reactive Programming(RP)의 관건은 Asynchronous Data Flow관점에서 프로래밍을 하고 Functional Reactive Programming으로 오면 filter, map, reduce와 같은 블럭을 통해 Reactive Programming을 한다. 



리액티브 프로그래밍(RP)은 비동기와 이벤트 기반의 데이터 스트리밍(Asynchronous and Event-based Data Streaming)을 Observable Sequences로 변환해 개발하는 프로그래밍 방식이다.







Reactive 거시적, 미시적 이해


  Reactive Manifesto 의 내용을 이해할 필요가 있다. 왜 Reactive 해야 하는지 이유는 본 글에 잘 설명되어 있다. 현재는 인터넷 보급과 모바일 기기의 보급으로 365일 24시간 무정지의 끊김없는 빠른 서비스를 제공해야 한다. 이를 위해서는 장애(Failure)에 빨리 복구(Resilient)되고, 서비스의 수평적 확장이(Scalable) 용이해야 하며, Resilient와 Scalable 바탕에는 느슨한 결합(loose coupling)의 메세지 기반(Message Driven) 아키텍처가 기반이 되어야 한다. 


  - Responsive를 위한 Resilient와 Scalable 및 Message Driven에 대한 이야기

  - NDC에서 김종욱님이 발표한 RX와 Functional Reactive Programming 이야기 (원문)

    + 엔시소프트 리니지 서버의 Rx 적용 경험 공유

    + 2009년 MS 에서 Reactive eXtension 소개

    + 2011년 RxJS, 2012년 RxJava등이 나오고 현재는 RxSwift도 존재함 

    

   

  - 2013년 Infoq에 발표한 Reactive programming의 동향

    + 코세라에서 Scala 기반 오픈 강의 개설(Scala 창시자와 Rx 개발자가 직접 강의함) : Principles of Reactive Programming

    + Netflix에서는 RxJava 기반으로 광범위하게 개발을 하고 있다. 

    + Facebook은 ReactJS를 오픈소스화하고 페이스북 웹과 인스타그램 웹에 사용하고 있다.

    + Javascript 기반의 Bacon.js, Kefir.jsRxJS, Reactive.js 라이브러리를 타 프레임워크와 접목해서 사용할 수도 있다. 

  - Microsoft에서 이야기하는 Reactive eXtension 정의

  - 다양한 언어의 구현체는 ReactiveX(http://reactivex.io)에서 볼 수 있다.

  - 확장가능하고 병행처리 가능한 웹 아키텍쳐 구축하기


  - Observable에 대한 이해

    + 2분만에 이해하는 Observable : Rx is the underscore.js for events

    + FRP 연산자들에 대한 Sync와 Async에 대해 시뮬레이션 예제를 통해 보여준다. (슬라이드DevNation)

    + ReactiveX.io의 설명

       Observer는 Observable에 subscribe하고 onNext, onError, onCompleted를 Observer에 구현하면 이를 Observable이 호출

       onNext에는 emit 한다고 말하고, onError, onCompleted는 notification한다고 말함.

    + MS의 Reactive eXtension의 워크샵 (동영상)


  - Observable에 대한 기본 개념 

    

  

  - ReaciveX를 위한 Operators 종류

    + 언어별로 동일 연산자를 가진다. 

    + Create Observable, Transform Observable, Filtering Observable, Combining Observable, Error Handling, Utility, Conditional & Boolean ...

    + Operator사용을 위해서 Decision Tree의 내용을 숙지해 보자. 





Reactive Programming for JavaScript


  먼저 RxJS를 이용한 Reactive Programming에 대한 이해를 선행한 후에 FRP(Functional Reactive Programming)에 대한 이해를 위해 learnrx의 FRP 개념 이해를 위한 실습을 전부 따라해 본다. 


  - Javascript의 Sync처리와 Asynch 처리에 대한 이해를 뭔저 해본다. 동영사의 21분에서 forEach가 Sync, Async 처리 되는 과정을 이해.

   + Event-Loop 와 Call Stack, Task Queue 관계 시뮬레이션 (21분경에 나옴)

    


  - 예제들이 ES6 기준으로 되어 있는 경우가 많아 ES6 문법을 공부하자

    + Depth in ES6 이해 요약정리

  - Functional Reactive Programming (FRP) 에 대한 개념을 이해하자. 

    + Functional Programming 과 Reactive Programming의 결합 === Functional + Async Data Stream (Data Flow) 

    



  - Frontend Developer 입장에서 본 RxJS에 대한 기본 개념 이해 

    + Array에서 제공하는 map, filter, reduce는 할 때마다 다시 배열을 할당한다.(learnrx를 따라해보고 이해하자) 따라서 GC에 대한 이슈존재

    + RxJS의 Observable을 사용할 경우 배열 재할당이 없으므로 GC에 대한 이슈도 없다. 

    


  - RxJS의 깃헙 Readme를 읽자

  - RxJS 학습하기 

    + egghead.io의 RxJS 강좌와 Asynchronous 강좌를 본다.

    + RxMarbles : Rx를 다이어그램으로 보여주어서 시각적 이해를 돕는다

    + RxJSKoans 학습

    + RxJS GitBook 책을 읽자 (그외 RxJS 레퍼런스)


  - RxJS를 이용한 FRP API 소개 : 맨뒤의 참조 목록을 방문하자 

    


  - ng-conf 2015에서 Nexflix UI 개발자가 발표하는 Obserable을 보자 (슬라이드

    + RxJS 개념은 Angular 2.0 core 에서 사용한다!!!

    

  - Bacon.js  : Functional Reactive Programming(FRP) Library for Javascript
    + 강좌-1 : Hacking with jQuery
    + 강좌-2 : Bacon.js started
    + 강좌-3 : AJAX and stuffs



Reactive Programming for Another Stuffs

Reactive 프로그래밍은 다양한 언어를 지원하고 http://reactivex.io 에서 언어별로 확인이 가능하다. 
  - Java를 위한 Reactive 프로그래밍으로 RxJava를 제공하고, 안드로이드 v2.3 이상부터도 사용을 할 수 있다. 
  - 김대성님의 FRP에 대한 이야기 : RxJava를 소개하고 있다. 유튜브 동영상을 시청하면 보자.   
    

  - 안드로이드 개발을 위한 Rx기반의 아키텍쳐링 방법도 제공을 하고, RxAndroid 라이브러리도 제공을 한다
    

    + cycle.js를 React 스타일로 재구성한 cycle-react
    + Flux Pattern을 Rx-Flux 구현

  - Angular에 RX 적용하기 
    + BangJS : Reactive AngularJS 로서 Bacon.js를 사용한다.

  - D3.js와 붙여보자 




<참조>

  - React & Bacon 예

  - Neflix API를 사용해 Reactive Programming하기

  - Reactive Programming using RxJS

  - Observable 과 Promise의 차이점

  - Reactive Extension in MS

  - Rx 관련한 대부분의 자료 모음

  - RP and MVC 예제

posted by peter yun 윤영식
2015.07.11 19:45 Meteor

2013년 이맘때 미티어를 처음 접하고 클라이언트와 서버 사이에 데이터베이스가 있다는 것이 이해되지 않았다. 그 의문이 이번주 스마트링크의 스터디를 하며 조금씩 해소되는 느낌이다. 이제는 Modern Application Platform의 큰 흐름에서 Runtime이라는 주제를 놓고 볼 때 미티어는 단연 앞선 플랫폼이고 한 분야를 리딩할만한 플랫폼이라 여겨진다. 그속에 많은 사상과 기술이 녹아있다. 미티어를 한주 안에 파악해 보기 위해 다음의 과정을 밟아보길 권한다. 






Meteor 넌 뭐니?


  - 스터디에서 발표한 내용의 동영상이다. 

    + Realtime을 근간으로 Collaboration과 Share를 위해 잘 갖추어진 Modern Application Platform 이다. 

    + Reactive 영역을 프론트엔드에서 백앤드로 확장해 기술셋을 잘 구비해 놓았다.

    + 프론트앤드 리액트 템플릿엔진 : Blaze, AngularJS, ReactJS를 사용할 수 있다. 

    + 백앤드 리액트 기술 : Full Stack DB Driver = DDP + Client mini DataBase + LiveQuery 를 갖추었다. 

    
    + 동영상의 발표자료
    


  - 위의 내용을 이해하기 위하여 다음과 같은 자료를 보았다. 

    + DiscoverMeteor 강좌 : 반드시 전과장을 직접 따라서 코딩해 보면 좋다. 물론 한글로 번역이 되어 있다. 

       이번에 출간한 "실전 프로젝트로 배우는 AngularJS"의 상당 부분이 이와 유사하게 전개한다. 놀란 것은 Node.js에서 MongoDB 제어와 권한 체크를 위한 코드 작성이 너무나도 쉽게 해결된다는 것이다. 정말 말 그대로 서버는 날로 먹는 것이다. Isomorphic Programming의 정수이다. 

    + 박승현님 Meteor Dev Study : 페북 미티어 코리아 그룹을 이끄시는 박승현님의 미티어 강좌를 보고 친숙하게 이해할 수 있을 것이다. 





Meteor Environment


미티어는 Ionic과 같은 CLI 제공을 통해 실행과 빌드를 통합하고 있다. 따라서 의존성관리를 위한 npm, bower 또는 빌드를 위한 grunt, gulp 명령등을 알 필요가 없다. 단순히 meteor <command> <option> 을 통해 완료된다. 


  - Meteor Command Line Tool 명령들

  - 미티어의 패키지 저장소 : Atomsphere

  - 많이 사용하는 패키지 검색 : Fastosphere





Meteor가 AngularJS 또는 ReactJS를 만났을 때

  

                    



  Blaze를 사용하지 않고 AngularJS를 사용할 때의 방식은 확연히 차이가 보인다. 만일 JQuery 개발 방식에 익숙하다면 Blaze를 사용한다. AngularJS를 사용해 보았다면 당장 AngularJS+Meteor를 사용하길 권한다. 공식적으로 지원을 하고 예제 문서까지 친절하게 나와있다. ReactJS는 아직 Preview단계로 보여지지만 긱하게 새로운 것을 도전해 보고 싶다면 권한다. ReactJS의 Virtual DOM 개념도 관심의 대상이지만 Flux라는 개념이 참 맘에 든다. 따라서 MVC 패턴이 아닌 좀 더 견고한(페북이 말하는) 패턴이라는 Flux 패턴을 적용하고 싶다면 ReactJS를 사용해 보자. 나의 선택은 물론 ReactJS + Flux 패턴이다. 


  - Blaze, AngularJS, ReactJS 비교하며 따라하기 예제

  - AngularJS + Meteor 홈페이지 : 물론 미티어 홈페이지에 AngularJS 공식 문서도 있다.'

  - ng vegas에서 발표한 angular-meteor 개발자인 유리 골드스테인의 Angular/Angular2와 Meteor에 대한 소개

    


  - ReactJS + Meteor Preview 예제 페이지

  - 페북과 유사한 ReactJS + Meteor 샘플 예제 : 데모 사이트를 이동해 직접 사용해 보자. 

    + Data Fetching Component는 Flux의 Controller View 이다.

    + Mini-Mongo Store는 Flux의 Store 이다.

    + Action은 Flux의 Action Creator 이다.

    



Meteor는 Modern Application Platform 이면서 클라이언트의 리액트를 백앤드로 확장해 주는 Reactive Platform이라 말할 수 있다. 다시 들여다 본지 1주일밖에 되지 않기에 오류를 잡고 파보아야할 곳이 많이 존재한다. 앞으로 두달간 진행하는 파일럿 프로젝트에서 ReactJS + Meteor를 사용하면서 내공을 쌓아야 할 상황이다. 




Modern Application Platform 으로서 Meteor 

  

  "Realtime 애플리케이션을 위해 프론트/백앤드 개발을 동질로 리액트하게 할 수 있는 플랫폼을 제공해 보자" 이게 그들의 머릿속을 스친 생각이 아니었을까? 리액티브 프로그래밍을 이소모픽하게 제공한다면 View 와 Data에 대하 부분을 리액티브하게 처리할 수 있다면... 아마 미티어 개발자들은 신이 나지 않았을까 싶다. 그래서 그와 관련된 회사들을 M&A하고 개발자를 영입해서 오늘의 1.* 미티어라는 리액티브 플랫폼을 완성해 가고 있다고 본다. 미티어를 리얼타임 플랫폼을 하는 마이크로 서비스로 보고 외부 시스템과 연계는 몽고디비를 통해 한다면 다양한 연동이 가능할 것이라 생각한다. 


  이중 DDP를 통한 React Native로 DDP Client를 사용하는 방법이 있는가 하면 

  

  일라스틱서치(Elastic Search)와 연동하기도 하고 

  

  

 미티어를 통해 상상하던 모던 애플리케이션 서비스를 일관되고 쉽게 만들어 갈 수 있다라는게 나의 결론이다. 




<참조> 


  - JavaScript.isSexy에서 이야기하는 Meteor 배우는 방법

posted by peter yun 윤영식
2015.07.01 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.05.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