この投稿は 限定共有に設定されています。 投稿者本人及びURLを知っているユーザーのみアクセスすることができます。

新卒エンジニアが Angular と React を比較してみた!!

  • 0
    コメント

自己紹介

会社: ローカルワークス
名前: 伊藤敦之
年齢: 24歳  ← ダブったので新卒です!!
趣味: 囲碁
仕事: パソコンを触ること


作ったもの紹介...


の前に...


言い訳タイム

  • Rails のプログラミングを始めて1年の初心者です!
  • JS を勉強し始めて半年の超絶初心者です!!
  • 開発期間はそれぞれ2週間です!!!

作ったもの紹介!!!

  • 本を登録して検索できるWEBアプリ

    • Angular 4.2.4
    • Angular Material
    • Rails 5.1
  • Evernote みたいな日記WEBアプリ

    • React 15.5.4
    • react-router-dom
    • redux
    • Material UI
    • axios
    • Rails 5.1

今日話したいこと

初心者からみた

  • 学習コスト度
  • ハマる度
  • 感動度

フレームワークとしてくらべてみました


Angular

学習コスト度: ★☆☆
ハマる度: ★★☆
感動度: ★☆☆

good

  • view, router, HTTP requestなどなど,開発に必要なものは一通り揃ってる
  • Angular Way に乗っかれば単一責務を実現しやすい
  • JS, html, CSS のファイルが別れていて分かりやすい

bad

  • TypeScript を勉強しなきゃいけない
  • Angular4 の参考文献は英語がほとんど
  • Angular内のルールが多い

勉強してみて

  • Angular Tutorial をやれば誰でも動かせるようになる
  • 応用箇所は先行事例が少ないので、公式リファレンスを読むしかない

React

学習コスト度: ★★☆
ハマる度: ★★☆
感動度: ★☆☆

good

  • プロダクトに入れ込む粒度を選択できる
  • 日本語の参考文献が多い
  • 先行事例が多い

bad

  • jsx が気持ち悪い
  • ライブラリーの依存が多くなるので簡単にアップデートできない
  • 勉強するものが多い

勉強してみて

  • React Tutorial だけやってもフロント全般を任せられる程の知識はつかない
  • ライブラリーごとに勉強が必要
  • ClassName が罠...

開発ツールでくらべてみました


Angular

学習コスト度: ★☆☆
ハマる度: ★☆☆
感動度: ★★★
- Angular Material
- angular cli
- Angury

React

学習コスト度: ★☆☆
ハマる度: ★☆☆
感動度: ★★★
- Material UI
- create react app
- React Developer Tools


勉強してみて

  • 開発ツールは Angular の方が使いやすく、分かりやすい
  • Material UI はCSSを当てたい時にインラインCSSみたいに書き込まなくちゃいけなくて、最悪の場合JSXにべた書きして気持ち悪い
  • create react app はコマンドラインインタフェースとしては貧弱

アーキテクチャでくらべてみました


勉強する前

「Angular も React も所詮コンポーネントフレームワークっしょ。
チュートリアルもやったし、余裕っしょ。(鼻、ホジホジ)」


勉強し始めて...

「設計、むずすぎだろォォォォォォォォォォオオオオ!!!!!!!!!!!!!(ぐはっ)(ばたっ)」


図解します


image.png


image.png


じゃ、くらべていきましょうか...


コンポーネントの設計

ここは Angular, React 同じ考え方

Container Component と Presentational Component

学習コスト度: ★☆☆
ハマる度: ★☆☆
感動度: ★☆☆
- データの流れを一方向にするための役割分担
- Container Component は how things work (データ収集や取り扱い方)
- Presentational Component は how things look (データの表示)


Angular

# Container Component
export class HomeComponent implements OnInit {
  books: Observable<Book[]>;

  private searchTerms = new BehaviorSubject<string>('');

  constructor(
    private getBookService: GetBookService,
    private bookSearchService: BookSearchService
  ) { }

  onSearch(term: string): void {
    this.searchTerms.next(term);
  }

  ngOnInit(): void {
    this.books = this.searchTerms
      .debounceTime(300)
      .distinctUntilChanged()
      .switchMap(term => term
        ? this.bookSearchService.search(term) : this.getBookService.all())
      .catch(error => {
        console.log(error);
        return Observable.of<Book[]>([]);
      });
  }

}

# Presentational Component
export class ListsComponent {
  @Input() books: Book[]
}

React

# Container Component
class Home extends Component {
  componentDidMount() {
    const { diariesActions } = this.props
    diariesActions.getDiaries()
  }

  render() {
    const { diariesActions, selectedIndex, diaries } = this.props
    return (
      <div className='body'>
        <div className='side'>
          <Options />
          <Lists lists={diaries.getResult} onClick={(id) => diariesActions.selectDiary(id)} />
        </div>
        <div className='main'>
          <Page page={diaries.getResult[selectedIndex]}/>
        </div>
      </div>
    );
  }
}

Home.propTypes = {
  diaries: PropTypes.object.isRequired,
  selectedIndex: PropTypes.number.isRequired,
  diariesActions: PropTypes.object.isRequired,
};

function mapStateToHomeProps(state) {
  return {
    diaries: state.diaries,
    selectedIndex: state.selectDiary.index
  };
}

function mapDispatchToHomeProps(dispatch) {
  return {
    diariesActions: bindActionCreators(DiariesActions, dispatch),
  };
}

export default connect(
  mapStateToHomeProps,
  mapDispatchToHomeProps
)(Home);

# Presentational Component
const Lists = ({ lists, onClick }) => (
  <div>
    <ul>
      {lists.map(list => (
        <li key={list.id} onClick={() => onClick(list.id)}>
          <List list={list} />
        </li>
      ))}
    </ul>
  </div>
);

export default Lists;

state と prop の管理

ここ、一人で勉強していくのつらかったです...


Angular RxJS

学習コスト度: ★★★
ハマる度: ★★★
感動度: ★★★

Angular は React x Redux みたいに完成された設計方法がなく、state と prop の管理の設計で悩む必要がある。
今回は Angular 開発が推奨している RxJS に基いて、state と prop の管理や非同期処理を行ってみました。


こんな感じでやってみました

  • prop をいじるのはバックエンドに任せる
  • HTTP request は全部 Service に切り出す
  • prop は Observable の形で保持する
  • state が変更された時に Observer を Push するようなメソッドを用意する

今回は Angular の双方向データバインディングを使用し、イベントの発火時に state を Container Component に知らせ、そのイベントで Observer を Push するようなメソッドを用意する


# Container Component
export class HomeComponent implements OnInit {
  books: Observable<Book[]>;

  private searchTerms = new BehaviorSubject<string>('');

  constructor(
    private getBookService: GetBookService,
    private bookSearchService: BookSearchService
  ) { }

  onSearch(term: string): void {
    this.searchTerms.next(term);
  }

  ngOnInit(): void {
    this.books = this.searchTerms
      .debounceTime(300)
      .distinctUntilChanged()
      .switchMap(term => term
        ? this.bookSearchService.search(term) : this.getBookService.all())
      .catch(error => {
        console.log(error);
        return Observable.of<Book[]>([]);
      });
  }

}

# Presentational Component
export class SearchComponent {
  @Output() onSearch = new EventEmitter<string>();
  search(term: string) {
    this.onSearch.emit(term);
  }
}

勉強してみて

  • RxJS は鬼むずい
  • 参考文献がほとんどなくい

やったことは...

  • 公式リファレンスを読みます <- 英語つらい...
  • console.log で逐一データの中身を確認する

こんな牛歩戦術で RxJS を突破しました。


おそらくこんなつらいことが将来待ち受けるでしょう

  • prop 取得後の処理を Container Component に書くので、 Container Component がものすごくファットになる
  • Component が複数入れ子になっている時、双方向データバインディングでイベントを Container Component まで持っていくのはめんどくさい
  • どこからでも Observable に Push できてしまうので、テストを書くのが大変

React Redux

学習コスト度: ★★☆
ハマる度: ★★★
感動度: ★★★

「React やるなら Redux 一択でしょ!」ってことで、思考停止で Redux を始めました。笑

Redux は「Action と Reducer を state を管理してる場所に渡す」ってことなので、
登場人物は Action と Reducer ということになります。


 こんな感じでやってみました

  • Redux の Action のファイルを、バックエンドの Rails の controller の単位に合わせる
  • prop の変更はバックエンドの処理を経由して Redux の Reducer にセットさせる
  • state の変更はバックエンドの処理を経由せず Redux の Reducer にセットさせる

Action と Reducer を Rails の Controller の一部だと思うと理解は早かったと思います。


actions/daiaries.js
# バックエンドの処理を経由するAction
export function getDiaries() {
  return dispatch => {
    dispatch(getDiaryListRequest());

    Axios.get('http://127.0.0.1:9292/diaries.json').then(
      response => dispatch(getDiaryListResult(response.data))
    ).catch(
      () => dispatch(getDiaryListResult(false))
    );
  };
}

function getDiaryListRequest() {
  return {
    type: Diary.GET_LIST_AJAX_REQUEST,
  };
}

function getDiaryListResult(result) {
  return {
    type: Diary.GET_LIST_AJAX_RESULT,
    result,
  };
}

# バックエンドの処理を経由しないAction
export function selectDiary(id) {
  return {
    type: Diary.SELECT_DIARY,
    id,
  };
}
controllers/diaries_controller.rb
lass DiariesController < ApplicationController
  before_action :set_diary, only: [:show, :update, :destroy]

  def index
    @diaries = Diary.all
  end

  def show
  end

  def create
    @diary = Diary.new(diary_params)

    if @diary.save
      render :show, status: :created, location: @diary
    else
      render json: @diary.errors, status: :unprocessable_entity
    end
  end

  private
    def set_diary
      @diary = Diary.find(params[:id])
    end

    def diary_params
      params.require(:diary).permit(:title, :content)
    end
end

勉強してみて

  • Redux はめんどくさい
  • ルールがかっちりしている分、ちょっとしたことに対する手続き的なものが多い また、非同期処理をやる場合はさらに、 redux-thunk , redux-saga , redux-promise などなど学ぶことは多い

やったことは...

  • 初心者向けの日本語記事を10個ぐらい読む
  • Youtube で説明しながらコーディングしている人がいるので、それをひたすら写経 <- 英語しかないのでつらい...

Redux は RxJS とは違い、データの慣れは一方向で輪っかを描いているので、ルールがわかればそんなに苦労しないです。(理解するまで Youtube の動画10回ぐらい見ました...)


おそらくこんなつらいことが将来待ち受けるでしょう

  • どんなに小さい state の変更でも Redux のループを回さなきゃいけないので Action と Reducer が際限なく増えていく
  • 依存ライブラリーのバージョンアップ、新しいライブラリーに切り替えで動かなくなる

まとめ

  • SPA 作りたいなら Angular の方が良さそう
  • 既存フレームワークの View の部分にJSライブラリーを入れたいなら React の方が良さそう

なぜなら...

  • Angular はそれ自体で完結したフレームワーク
  • React は自分でカスタマイズしてフレームワークにする

次は、 Rails5.1 に Webpacker が標準搭載されたので、 view ライブラリーとして React を使ってみたいと思います。笑


おしまい