ReactJS

since 2018-08-16

nodejs に書いた手順で WSL Ubuntu に環境構築してからのメモ。

TODO: 過去の仕事で使った angularjs との比較、移行に関する個人的な考察。

create-react-app

$ npx create-react-app my-app

完了すると my-app フォルダができている。

$ ls my-app/
node_modules  package.json  package-lock.json  public  README.md  src

$ du -s my-app
157866  my-app

ちゃんと .gitignore があるので、ここで git init して initial commit しておく。

$ cd my-app/
$ git init
$ git add .
$ git commit -am "initial"

開発サーバーの動作を確認する

$ npm start

WSL で作業していたら、ブラウザが開けませんエラーではなく、ちゃんと Windows の Chrome が localhost:3000 を開いてくれる。

この作業フォルダは Windows から編集できる場所に作っておいて、Visual Studio Code のターミナルから WSL の bash をひらいて npm start しておく。

npm start をやり直すたびに localhost:3000 タブが増えてしまうので、基本は動かしっぱなしがよい。

ちなみに npm run build すると静的ファイルを build フォルダに作る。

index.js にこだわる

コンポーネントの概念とファイル分割の話を切り離して考えたい。

そこで App.js のインポートをやめて index.js だけでできることを模索してみる。

Visual Studio Code で my-app フォルダを開いて src/index.js を編集する。

エディタで編集を保存するたびにコンパイルされ、ブラウザがリロードされる。

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
  render() {
    return (
      <Fragment>
        <h1>hello</h1>
        <div className="text">hello</div>
      </Fragment>
    );
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));

とにかく既存の HTML を丸ごと突っ込んでみたい場合は、これが出発点と思われる。

XML を埋め込む記述 (JSX) は Visual Studio Code で快適に扱える。

  • 複数の要素を並べて書くことができないので、必要な場合は Fragment というダミー要素を使う。
  • HTML の class 属性は className と書く

なお、Chrome 開発者ツールでこのコンテンツを調べていたら、Chrome のアドオンをインストールせよと言われる。

React 開発者ツールを入れておくと React コンポーネントを Chrome で確認できる。

JavaScript で評価される値の埋め込みは {} で行う。

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
  render() {
    const msg = 'world';
    return (
      <Fragment>
        <h1>hello</h1>
        <div className="text">{msg}</div>
      </Fragment>
    );
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));

テンプレートエンジンの条件分岐やループ、あるいは ng-if / ng-show / ng-repeat のような処理は、JavaScript で記述。

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
  render() {
    const animalNames = ['dog', 'cat', 'monkey'];
    const content = animalNames.map((item, index) => {
      return <li key={index}>{item}</li>;
    });
    return (
      <Fragment>
        <h1>hello</h1>
        <ul>
          {content}
        </ul>
      </Fragment>
    );
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));

key 属性はなくても動くが、開発者ツールでつけろという warning が出る。

複雑になるとリファクタリングしようということになる。

ここでようやく新しいクラスを定義。

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
 
class Animals extends Component {
  render () {
    const animalNames = ['dog', 'cat', 'monkey'];
    const content = animalNames.map((item, index) => {
      return <li key={index}>{item}</li>;
    });
    return (
      <ul>
        {content}
      </ul>
    );
  }
}
class App extends Component {
  render() {
    return (
      <Fragment>
        <h1>hello</h1>
        <h2>animals</h2>
        <Animals/>
      </Fragment>
    );
  }
}
ReactDOM.render(<App/>, document.getElementById('root'));

こういう流れで作業をすると、一つの HTML ファイルからちょっとずつコンポーネントに分割する、みたいなリファクタリングが段階的にやれそう。

ところで animalNames は Animals の外にあったほうが便利な場合がある。

一般的には state と props の話が出てくる。

探すとたくさん記事が出てくるので、ここでは省略。

Redux

ここではいきなり、状態はグローバルに管理したい、という前提。

AngularJS でいえば $rootScope みたいなことがしたい。

Redux を使えばできる。そこでオレオレ流に突っ込んでみる。

https://redux.js.org/

https://github.com/reduxjs/react-redux

準備

$ npm i redux react-redux

新しい index.js

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
 
const initialState = {
  animalNames: ['dog', 'cat', 'monkey'],
};
const reducer = (state = initialState, action) => {
  return state;
}
const store = createStore(reducer);
const connectComponent = connect(
  state => {
    let o = {};
    for (let k in state) {
      o[k] = state[k];
    }
    return o;
  },
  dispatch => {
    return {
    }
  }
);
 
class Animals extends Component {
  render () {
    const content = this.props.animalNames.map((item, index) => {
      return <li key={index}>{item}</li>;
    });
    return (
      <ul>
        {content}
      </ul>
    );
  }
}
Animals = connectComponent(Animals);
class App extends Component {
  render() {
    return (
      <Fragment>
        <h1>hello</h1>
        <h2>animals</h2>
        <Animals/>
      </Fragment>
    );
  }
}
App = connectComponent(App);
ReactDOM.render(
  <Provider store={store}><App/></Provider>, 
  document.getElementById('root')
);

connect は高階関数で、普通はモジュールの export で実行するようだが、こんな感じで共通化してしかも index.js に突っ込んでみた。

dispatch はプレイスホルダーになっているが、これを使うと、どこでイベントをハンドルしても、どこかのコンポーネントが書き換わる、みたいな処理が作れる。

reverse ボタンを押すと順番がひっくり変わる。

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
 
const initialState = {
  animalNames: ['dog', 'cat', 'monkey'],
};
const reducer = (state = initialState, action) => {
  if (action.type === 'reverse') {
    return {
      ...state,
      animalNames: [...state.animalNames].reverse(),
    };
  }
  return state;
}
const store = createStore(reducer);
const connectComponent = connect(
  state => {
    let o = {};
    for (let k in state) {
      o[k] = state[k];
    }
    return o;
  },
  dispatch => {
    return {
      action: type => dispatch({type: type}),
    }
  }
);
 
class Animals extends Component {
  render () {
    const content = this.props.animalNames.map((item, index) => {
      return <li key={index}>{item}</li>;
    });
    return (
      <ul>
        {content}
      </ul>
    );
  }
}
Animals = connectComponent(Animals);
class App extends Component {
  render() {
    return (
      <Fragment>
        <h1>hello</h1>
        <h2>animals</h2>
        <Animals/>
        <button onClick={() => this.props.action('reverse')}>reverse</button>
      </Fragment>
    );
  }
}
App = connectComponent(App);
ReactDOM.render(
  <Provider store={store}><App/></Provider>, 
  document.getElementById('root')
);

onClick の値は {} で囲む。ダブルクォートではない。 中身は関数オブジェクトでなくてはいけないので、引数がある場合は無名関数を作る。

animalNames 配列は deep copy しないといけないので […state.animalNames] をつくって reverse() する。

ピリオド3個 … はスプレッド構文(スプレッド演算子)

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax

下記は、ストアの変数名をそのままアクションの名前にしたい場合の実装。

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
 
const initialState = {
  passphrase: '',
};
const reducer = (state = initialState, action) => {
  if (action.type in state) {
    const o = {...state};
    o[action.type] = action.value;
    return o;
  }
  return state;
}
const store = createStore(reducer);
const connectComponent = connect(
  state => {
    let o = {};
    for (let k in state) {
      o[k] = state[k];
    }
    return o;
  },
  dispatch => {
    return {
      action: (name, value) => dispatch({type: name, value: value}),
    }
  }
);
 
window.getPassphrase = () => {
  return store.getState().passphrase;
};
window.setPassphrase = (value) => {
  store.dispatch({type: 'passphrase', value: value});
};
 
class App extends Component {
  render() {
    return (
      <Fragment>
        <h1>passphrase</h1>
        <div>{this.props.passphrase}</div>
      </Fragment>
    );
  }
}
App = connectComponent(App);
ReactDOM.render(
  <Provider store={store}><App/></Provider>, 
  document.getElementById('root')
);

Chrome 開発者ツールの JavaScript console で setPassphrase('hoge') などと入力すると、 コンテンツを書き換えることができる。

デバッグとか evaluateJavaScript とかで使える気がする。

reactjs.txt · 最終更新: 2018/08/16 16:50 by Takuya Nishimoto
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0