React.js

奥山幸彦 / @FiNGAHOLiC

🐰 < React.jsって何?

React.jsはFacebook製の

JavaScriptライブラリ

A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES

  • Facebook

  • Instagram

  • Netflix

  • Uber

  • Twitter (Mobile)

  • Airbnb

  • Wantedly

  • ブッキングテーブル

React.js導入事例

  • Virtual DOMによるレンダリング機構

  • JSXシンタックス(JavaScriptの独自拡張構文)

  • ステートレスなコンポーネント設計

  • Isomorphic(SSR)

React.jsの特徴

Virtual DOM

A JavaScript DOM model supporting element creation, diff computation and patch operations for efficient re-rendering

効率的な再レンダリングのための

要素作成、差分計算、パッチ操作を

サポートするJavaScript DOMモデル

  1. データを元にViewをレンダリング

  2. ユーザーによるアクションが発生

  3. データを更新

  4. 該当するView部分のみを再レンダリング

一般的なアプリケーション

出来れば変更されたデータを元に

全HTMLを作り直してinnerHTMLしたいが、

パフォーマンスの側面から好ましくない

かといって変更に対応する箇所のみを

再レンダリングする仕組みを

手で書くのはツラすぎる

Virtual DOMは、データ更新の度に

変更されたデータに対応する

Viewを検知・更新してくれて、

実DOMとして再レンダリングしてくれる

仕組み

「常に完成品の仮想DOMをPushし続ける」というのは、結果として物事を単純化します。

ここでよく考えてみてください。

「状態遷移に応じて、完成品のHTMLをプッシュする」

これって見覚えがありませんか?

そうです、サーバーサイドで、HTTPリクエストに応えてHTMLを返却する操作そのものです。

Stateless functional components

再利用可能なコンポーネントを実現するためには、出来るだけコンポーネントには状態を持たせたくない

親コンポーネントのみが状態を管理し、

子・孫コンポーネントの状態は
親コンポーネントから与えられるのが理想

こうしたシンプルなコンポーネントを書くための新しい構文として、

Stateless functional componentsが

React.js v0.14で追加された

import React from 'react';

const Header = (props) => (
  <header>
    <h1>{props.companyName}</h1>
  </header>
);

Header.propTypes = {
  companyName: React.PropTypes.string.isRequired,
};

export default Header;

引数でprops(状態や振る舞い)を受け取り、

戻り値としてレンダリングしたい要素、

Virtual DOMを返すだけの単純な関数

早速試してみよう

🐰 < また前回みたいに環境を一から

手で書かなきゃダメなの?

Facebook製の

Reactアプリケーション用

ボイラープレートがあるので

煩わしい準備は必要ない

ただcreate-react-appを準備する前に

次のコマンドだけは実行してもらいたい…

$ npm i -g yarn

🐰 < Yarnって何?

FAST, RELIABLE, AND SECURE DEPENDENCY MANAGEMENT.

Yarnはnpmと互換性のあるJavaScriptのためのパッケージマネージャーで、npmに対して「インストールが高速」「より厳密にバージョンを固定」「セキュリティが高い」といった特徴があり、魅力的です。

しかもReact.js同様Facebook製!

今後npmモジュールを

インストールする場合は、

npm installは使わないで
yarn addするようにしましょう!

お待たせしました🙏

早速create-react-appを

インストールしましょう

# グローバルにcreate-react-appをインストール
$ npm i -g create-react-app

# 任意の作業ディレクトリへ移動
$ cd /Users/fingaholic/Desktop

# 任意の作業ディレクトリを作成して移動
$ mkdir my-first-react
$ cd my-first-react

# Reactプロジェクトを作成
$ create-react-app app

# 起動する!
$ cd app
$ yarn start
  • BabeI - ES2016, JSX -> JavaScript

  • webpack - Module Bundler

  • webpack-dev-server - webpack用のローカル環境

  • style-loader - JavaScriptからCSSをimport出来る

  • Autoprefixer - CSSプロパティにプレフィックスを付与

  • ESLint - ES2016の構文を検証

使用されている技術

無事起動出来たら
遊んでみましょう!

App.jsを見てもらうと、

コンポーネントは状態を持っていないので
Stateless Functional Component

の構文が使えますね!

import React from 'react';
import logo from './logo.svg';
import './App.css';

const App = () => {
  return (
    <div className="App">
      <div className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h2>Welcome to React</h2>
      </div>
      <p className="App-intro">
        To get started, edit <code>src/App.js</code> and save to reload.
      </p>
    </div>
  );
}

export default App;

コンポーネントをもっと分割したい

import React from 'react';
import Header from './Header';
import Intro from './Intro';
import './App.css';

const App = () => {
  return (
    <div className="App">
      <Header />
      <Intro />
    </div>
  );
}

export default App;

App.js

.App {
  text-align: center;
}

App.css

import React from 'react';
import Logo from './Logo';
import './Header.css';

const Header = () => {
  return (
    <div className="Header">
      <Logo />
      <h1>Welcome to React</h1>
    </div>
  );
}

export default Header;

Header.js

.Header {
  background-color: #222;
  color: white;
  height: 150px;
  padding: 20px;
}

Header.css

import React from 'react';
import logo from './logo.svg';
import './Logo.css';

const Logo = () => {
  return (
    <img src={logo} className="Logo" alt="logo" />
  );
}

export default Logo;

Logo.js

.Logo {
  animation: spin infinite 20s linear;
  height: 80px;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

Logo.css

import React from 'react';
import './Intro.css';

const Intro = () => {
  return (
    <p className="Intro">
      To get started, edit <code>src/App.js</code> and save to reload.
    </p>
  );
}

export default Intro;

Intro.js

.Intro {
  font-size: large;
}

Intro.css

コンポーネントは

ディレクトリにまとめたい

.
├── App.css
├── App.js
├── App.test.js
├── components
│   ├── Header
│   │   ├── index.css
│   │   └── index.js
│   ├── Intro
│   │   ├── index.css
│   │   └── index.js
│   └── Logo
│       ├── index.css
│       ├── index.js
│       └── logo.svg
├── index.css
└── index.js
import React from 'react';
import Header from './components/Header';
import Intro from './components/Intro';
import './App.css';

const App = () => {
  return (
    <div className="App">
      <Header />
      <Intro />
    </div>
  );
}

export default App;

App.js

import React from 'react';
import Logo from '../Logo';
import './index.css';

const Header = () => {
  return (
    <div className="Header">
      <Logo />
      <h1>Welcome to React</h1>
    </div>
  );
}

export default Header;

Header.js

import React from 'react';
import logo from './logo.svg';
import './index.css';

const Logo = () => {
  return (
    <img src={logo} className="Logo" alt="logo" />
  );
}

export default Logo;

Logo.js

import React from 'react';
import './index.css';

const Intro = () => {
  return (
    <p className="Intro">
      To get started, edit <code>src/App.js</code> and save to reload.
    </p>
  );
}

export default Intro;

Intro.js

Material UIを使ってみる

🐰 < Material UIって何?

A Set of React Components that ImplementGoogle's Material Design

Google Material Designが提供している

UIパーツをReactのコンポーネントとして

配布しているプロジェクト

# ローカルにreact-tap-event-pluginをインストール
$ yarn add react-tap-event-plugin

# ローカルにmaterial-uiをインストール
$ yarn add material-ui
import React from 'react'

import injectTapEventPlugin from 'react-tap-event-plugin';

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

import {Card, CardHeader, CardMedia, CardTitle, CardText} from 'material-ui/Card';
import AppBar from 'material-ui/AppBar';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';

injectTapEventPlugin();

class App extends React.Component {
  constructor() {
    super();

    this.state = {
      open: false,
    };

    this.handleDrawerToggle = this.handleDrawerToggle.bind(this);
  }
  handleDrawerToggle() {
    this.setState({
      open: !this.state.open,
    });
  }
  render() {
    return (
      <MuiThemeProvider>
        <div className="App">
          <AppBar onLeftIconButtonTouchTap={this.handleDrawerToggle} />
  
          <Card>
            <CardHeader
              title="URL Avatar"
              subtitle="Subtitle"
              avatar="//placehold.it/100x100"
            />
  
            <CardMedia>
              <img src="//placehold.it/1000x500" alt="Dummy" />
            </CardMedia>
  
            <CardTitle title="Card title" subtitle="Card subtitle" />
  
            <CardText>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
              Donec mattis pretium massa. Aliquam erat volutpat. Nulla facilisi.
              Donec vulputate interdum sollicitudin. Nunc lacinia auctor quam sed pellentesque.
              Aliquam dui mauris, mattis quis lacus id, pellentesque lobortis odio.
            </CardText>
          </Card>

          <Drawer
            docked={false}
            open={this.state.open}
            onRequestChange={this.handleDrawerToggle}
          >
            <MenuItem>Menu Item</MenuItem>
            <MenuItem>Menu Item 2</MenuItem>
          </Drawer>
        </div>
      </MuiThemeProvider>
    );
  }
}

export default App;

App.js

おつかれちゃん🐰

Thanks!

React.js

By Okuyama Yukihiko

React.js

  • 275