Webエンジニアの備忘録

およそ自己の作業メモとして残しております。

Reactで絶対パスによるimportを使いたい

コンポーネントが深くなるとパスが読みづらい

ecmascriptでは外部ファイルを取り込む際に相対パスを指定します。

ただ、コンポーネントの階層が深くなるに連れて相対パスがわかりづらくなるので、絶対パスが使えればと思うケースも出てきます。

 -import Error from './../containers/ErrorContainer';
 -import Loader from './../containers/LoaderContainer';
 -import locale from './../libs/Locale';
 -import DevTools from './../DevTools';

  ↓ ↓ ↓

 +import Error from 'jsx/containers/ErrorContainer';
 +import Loader from 'jsx/containers/LoaderContainer';
 +import locale from 'jsx/libs/Locale';
 +import DevTools from 'jsx/DevTools';

babel-plugin-module-resolverを使う

Babelifyの際にパスを解決できれば絶対パスも利用可能です。 それを実現するライブラリがこちら。

www.npmjs.com

利用方法

npmでインストール

npm install -S babel-plugin-module-resolver

/.babelrcファイルに設定

{
  "plugins": [
    "transform-inline-environment-variables",
    ["module-resolver", {
      "alias": {
        "jsx": "./jsx", // componentを置くディレクトリを絶対パス化
      }
    }]
  ]
}

aliasとしてディレクトリ名を登録できるので、相対パスもそのまま利用できます。 npmパッケージと被らない名称であれば複数aliasを増やせます。

eslintも通したい

Babelで解決させた後はeslintでもパスを許容するよう修正します。 eslint-import-resolver-babel-moduleを利用するのが便利です。

www.npmjs.com

利用方法

npmでインストール

npm install -S eslint-import-resolver-babel-module

/. eslintrcファイルに設定

{
    "settings": {
        "import/resolver": {
            "babel-module": {}
        }
    }
}

これで絶対パスもlintが通るようになります。

ただ、VSCodeなどでタグジャンプなどを利用している場合、うまく動かなくなる可能性があります。こちらについては別途プラグインなどを調査中です。(見つかったら加筆します)

react-router画面遷移時のスクロール位置を操作する

前のページのスクロール位置が残る

react-routerはURLのハッシュを用いて画面遷移を表現していますが、実際のルーティングは固定だったりします。

なので、こんな気遣いをしないと遷移後に最上部にスクロールしてくれなかったりします。

  <Router onUpdate={() => window.scrollTo(0, 0)}>
    :
  </Router>

新しいプロジェクトを作成したときに、どうやってたっけ?と思ってしまったのでメモがてら残しておきます(^_^;)


ちなみに、ルーティングにパラメータを付けて、デフォルトのスクロール位置を変更させたい時はこちらのような共用モジュールを作成して対処しています。

自分の位置を返すComponent

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class Scroller extends Component {
  componentDidMount() {
    const { active, callback } = this.props;

    if (active) {
      callback(this.scroller.offsetTop);
    }
  }
  componentWillUnmount() {
    this.scroller = null;
  }
  render() {
    return (
      <div ref={e => (this.scroller = e)} />
    );
  }
}
Scroller.propTypes = {
  active: PropTypes.bool,
  callback: PropTypes.func,
};
Scroller.defaultProps = {
  active: false,
  callback: null,
};

Scrollerはスクロールさせたい位置にマーカー的に配置します。

activeの場合のみコールバックで自分の位置を返します。

使い方はこんな感じです。

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import Scroller from './Scroller';

export default class Clips extends Component {
  componentDidUpdate() {
    if (this.position) {
      window.scrollTo(0, this.position);
      this.position = null;
    }
  }
  render() {
    const { location } = this.props;

    return (
      <div>
        <Scroller
          active={location.query.content === 'content1'}
          callback={e => (this.position = e)}
        />
        <div>
          コンテンツ1
        </div>
        <Scroller
          active={location.query.content === 'content2'}
          callback={e => (this.position = e)}
        />
        <div>
          コンテンツ2
        </div>
      </div>
    );
  }
}
Clips.propTypes = {
  location: PropTypes.shape().isRequired,
};

ルーティングに併せてスクロールできるパッケージもありますね。 こちらは座標指定が必要なので、併せて利用できるかと思います。 www.npmjs.com

ES6におけるReactコンポーネントの書き換えについて

用途に併せてコンポーネントを書き分けよう

eslintに従いクラスコンポーネントを書き換えるケースがよくあるので、サンプルを貼っておきます。 たくさん書いてありますが、全て結果としては同じ内容を表現しています。

クラスコンポーネント(class)

  • 以下のようなクラスを作るのが最も万能ですが、内容に対してコードが長くなる場合があります。
import React, { Component } from 'react';

export default class App extends Component {
  render() {
    return (
      <div>Hello {this.props.name} !</div>
    );
  }
}
App.propTypes = {
  myName: React.PropTypes.string.isRequired,
};

メソッドコンポーネント(function)

  • メソッド単体でもコンポーネントとして利用できます。
  • { myName }はpropsオブジェクト下のキーを具体的に定義しています。
  • 省略していますが、第1引数propsの後に第2引数contextを受け取ることができます。
import React from 'react';

export default function App({ myName }) {
  return (
    <div>Hello {myName} !</div>
  );
}
App.propTypes = {
  myName: React.PropTypes.string.isRequired,
};

メソッドコンポーネント(const)

  • 上記をアロー関数で置き換えたものをconst値として渡しています。
  • constは直接defaultのexportに設定できませんが、以下のように書くとdefaultに設定できます。
import React from 'react';

const App = ({ myName }) => {
  return (
    <div>Hello {myName} !</div>
  );
};
App.propTypes = {
  myName: React.PropTypes.string.isRequired,
};
export default App;
  • コンポーネントの戻り値は必ずjsxなので、処理が複雑でない限りは以下のような書き方がベストです。
import React from 'react';

const App = ({ myName }) => (
  <div>Hello {myName} !</div>
);
App.propTypes = {
  myName: React.PropTypes.string.isRequired,
};
export default App;
  • 丸括弧がアロー関数の戻り値を表していたり、JSXを内包していたりとわかりづらいですが、どちらも省略可能なので、以下のようにも書けたりします。
    • ここまで省略すると若干読みづらい感じもします。
import React from 'react';

const App = ({ myName }) => <div>Hello {myName} !</div>;
App.propTypes = {
  myName: React.PropTypes.string.isRequired,
};
export default App;

Mac新OS「Sierra」で内蔵カメラが動かなくなった件

今回「Sierra」へのアップデートでは早くもいくつか辛酸を味わっていますが、FaceTimeカメラについてはようやく解決したのでメモを残しておきます。

以下、やってみたこと。

VDCAssistantのプロセス強制終了

カメラを握っているプロセスがあることで、他で利用できなくなっている可能性があるそうです。 以下のコマンドを実行後に再起動をおこないます。 わたしは上記では解決しませせんでした。

sudo killall VDCAssistant

Macで急にFaceTimeカメラが認識されなくなった時の対処法 - Qiita

SMCリセット

電源を切った状態からCtrl + Shift + Option と電源ボタンを押す。 こちらも改善には至りませんでした。

セーフモード再起動

ここで解決しました。 こちらは起動のアップルロゴ表示時にDキーを押して立ち上げます。 動きがカクカクしたセーフモードで起動しアプリ「PhotoBooth」でカメラをチェックすると、何事もなかったかのように動いていました。 その後、再起動をおこない通常モードで起動、この時点でカメラは正常動作していました。

www.16channel.com

カメラのアクセス権がどこかで握られていたか、設定のキャッシュが残っていたかなど定かではありませんが、類似の事象でお困りの方は上記のいずれかで改善されるかもしれないので試してみてください。 (他の情報があればコメント等いただければ幸いです)

React開発をwebpackでおこなう

概要

前回までの記事でgulpを利用しての開発を進めていましたが、いろいろ見聞してwebpackを使ってみることにしました。

利点を簡潔に言えば、jsをひとつに纏めるかファイル群としてストアするかの選択ができます。さらにwebpack-dev-serverと組み合わせれば、実ファイルを都度生成することなくローカルサーバーで試走が行えるので、タスクランナーとしてgulpと入れ替えても事足りると思います。

最低限のサンプル

f:id:tak_taniguchi:20161010020220p:plain 以下のことをできる最低限の構成です。

  • EcmaScript6、JSXのトランスパイル実行
  • ローカルサーバーでの試走

全体構成

─ /
 └ src/
 │ └ entry.jsx
 │ └ index.html
 └ .babelrc
 └ package.json
 └ web.config.js

コード本体

シンプルにベースのindex.htmlにentry.jsxをロードするだけのサンプルです。 動作すれば画面に「Hello World」が表示されます。

index.html

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Hello World</title>
</head>
<body>
  <div id="app"></div>
  <script src="./bundle.js"></script>
</body>
</html>

entry.jsx

import React from 'react';
import { render } from 'react-dom';

render((
  <div>
    Hello World
  </div>
), document.getElementById('app'));

セッティング

以下のコンフィグで行われる処理は次のとおりです。

  • entryファイルとしてsrc下のindex.html、entry.jsxを取り込む。
    • jsxを起点にあとの処理でimportされるファイルは全て自動的にトランスパイル対象になる。
  • htmlファイルは同名でそのまま出力する。
  • jsxファイルはトランスパイルして出力する、ただしnode_modules下は対象外にする。

webpack.config.js

module.exports = {
  context: __dirname + '/src',
  entry: {
    jsx: "./entry.jsx",
    html: "./index.html",
  },
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js',
    publicPath: '/',
  },
  module: {
    loaders: [
      { test: /\.html$/, loader: "file?name=[name].[ext]" },
      { test: /\.jsx$/, exclude: /node_modules/, loader: 'babel' },
    ]
  },
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
};

補足

  • loader指定する際はnpmパッケージ名末尾から「-loader」を省略して書けるので、上記に当てられている正確なパッケージ名はfile-loader・babel-loaderです。
  • extensionsに記載された拡張子はjsx内のimport対象を探索する際のパターンになります。
import Test from './test';
// 上記の場合、「./test」「./test.js」「./test.jsx」のいずれかを探す。
// ファイルタイプ別で重複命名があるとエラーになってしまうので注意

.babelrc

{
  "presets": ["react", "es2015"]
}

loader設定時にオプション指定することもできるが、別ファイルを用意したほうが役割がはっきりするので分割しました。 webpackのコンフィグ側に記載する場合は以下のように記載できます。

        :
    loaders: [
      { test: /\.jsx$/, exclude: /node_modules/, loader: 'babel?presets[]=react&presets[]=es2015' },
    ]
        :

package.json

  • 上記コード、トランスパイラで必要なパッケージを纏めました。
  • また、scriptsにはトランスパイル実行(ビルド)とローカルサーバー起動コマンドを記載してみました。
{
  "name": "hello",
  "version": "1.0.0",
  "description": "",
  "main": "entry.jsx",
  "scripts": {
    "build": "webpack --progress --colors",
    "start": "webpack-dev-server --hot --inline --progress --colors"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.17.0",
    "babel-loader": "^6.2.5",
    "babel-preset-es2015": "^6.16.0",
    "babel-preset-react": "^6.16.0",
    "file-loader": "^0.9.0",
    "webpack": "^1.13.2",
    "webpack-dev-server": "^1.16.2"
  },
  "dependencies": {
    "react": "^15.3.2",
    "react-dom": "^15.3.2"
  }
}

実行コマンド

npm install
npm run start
npm run build

ローカルサーバーはデフォルト http://localhost:8080/ が起点となります。

【補足】ESLintもフックしておく

WebpackでESLintを実装しておくと、ビルドやウォッチのタイミングでチェックが行われるためきれいなコードが書けるかと思います。 こちらも有用なので、メモ代わりに載せておくことにしました。

導入手順

  • ESLintを設定します。セットアップは公式ドキュメントが参考になります。
  • webpack.config.jsに以下のとおり加筆します。
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -10,6 +10,9 @@ module.exports = {
     publicPath: '/',
   },
   module: {
+    preLoaders: [
+      { test: /\.jsx$/, loader: "eslint" },
+    ],
     loaders: [
       { test: /\.html$/, loader: "file?name=[name].[ext]" },
       { test: /\.jsx$/, exclude: /node_modules/, loader: 'babel' },
  • eslint-loaderも忘れずにインストールします。
npm i -D eslint-loader
  • Webpack実行時にエラーがあれば以下のように表示されます。
    • 以下はbrowser環境変数をエスケープせずに"document"を参照するコードが内包されていた例 f:id:tak_taniguchi:20161010110433p:plain

入門 React ―コンポーネントベースのWebフロントエンド開発

入門 React ―コンポーネントベースのWebフロントエンド開発

Reactjsのstate/propsの挙動を確認する極力シンプルなコード

以下の記事で紹介したサンプルコードです。 tak-taniguchi.hatenablog.com

概要

Reactのstate/propsの関係を簡潔に表すために書いてみました。 余計なコードをほとんど書いていないので、UIは見づらいですが動きがわかりやすいかと思います(笑)

  • 動作に成功すると、ブラウザの左上に「0」が表示されます。
  • 「0」をクリックすると数値が加算されていきます。

ファイル構成

─ /
 └index.html
 └bundle.js      // index.jsxをbabelifyして作ってください
 └index.jsx      // ソース本体

index.html

<!doctype html>

<head>
  <meta charset="utf-8">
  <title>react state/props sample</title>
</head>

<body>
  <div id="content"></div>
  <script src="./bundle.js"></script>
</body>

</html>

index.jsx

import React from 'react'
import ReactDom, { render } from 'react-dom'

/**
 * 親コンポーネントクラス
 */
var Parent = React.createClass({
  getInitialState: function () {
    // this.state.counterを初期化しています
    return {
      counter: 0
    }
  },
  handleAdd: function () {
    // this.state.counterを加算するハンドラーです
    this.setState({
      counter: this.state.counter + 1
    });
  },
  render: function () {
    // 子コンポーネントを呼び出しています。
    return (
      <Child counter={this.state.counter} onClickHandler={this.handleAdd} />
    );
  }
});

/**
 * 子コンポーネントクラス
 */
var Child = React.createClass({
  render: function () {
    // this.props.onClickHandlerは親クラスhandleAdd()を参照
    // this.props.counterは親クラスのstate.counterを参照
    // onClick()が実行されると親クラスのstate.counterが加算されるため、子クラスのthis.props.counterも表示が変わる
    return (
      <div onClick={this.props.onClickHandler}>{this.props.counter}</div>
    );
  }
});

render(
  <Parent />,
  $('#content')[0]
);

フロントエンド初心者がReactによるSPAを構築するに至るメモ

はじめに

最近Javascriptを用いたフロントエンド開発に従事することになり、取り急ぎ敷居が低そうなReactjsに飛びつきました。 jqueryを弄る程度で知識的に止まっていたので、学習は相当ステップを刻むことになってしまいました…

最近ようやくたたき台的なものが書けてきました。 これを一度節目に、私くらいのレベルからReactを始める人のため、バックエンドエンジニアからの理解のため、そして何より自分が忘れないためにここまでの流れを一旦まとめます。

学習の変遷(やってみたこと)

1. Reactチュートリアル

https://facebook.github.io/react/docs/tutorial-ja-JP.html

JSXに触れる

return <div>ほげほげ</div>
 ↓
return (
    <div>ほげほげ</div>
);

state/propsを理解する

  • propsはプロパティ、stateが状態を表す。
  • 親のstateをsetStateで更新し子のpropsが変更される、というのが基本的なアクション。

gulp導入

  • jsxの利用で必ずbabelifyが必要になるので、さっさと導入したほうがいい。
    • 以下は最低限な感じのgulpfile.jsサンプルです。(watchも導入したほうがいい)
var gulp        = require('gulp');
var pkg         = require('./package.json');
var browserify  = require('browserify');
var babelify    = require('babelify');
var source      = require('vinyl-source-stream');

gulp.task('js', function() {
  browserify({
      entries: './src/index.jsx',
      extensions: [".js", ".jsx"],
      debug: true
    })
    .transform(babelify)
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./public/assets/js/'));
});

gulp.task('default', ['js']);

2. ES6で書き直してみる

参考にしたサイト

eslint

  • eslintを導入しコードを機械的にチェックさせる。
    • コーディングに安心感が出る。ソロ開発は検証者がいないので、変な書き方にきづけるよう入れておくと吉。
    • わたしはVisualStudioCode(VSC)で開発を行っています。フリーな上、非常に使いやすいです。
  • VSC + eslint
    • VisualStudioCode
    • eslintプラグイン(VSC内から追加) f:id:tak_taniguchi:20160917205230p:plain
    • eslintrc.json / .eslintignore ファイルの作成
      • jsを書いているディレクトリ下にてeslint --initを実行するとeslintrcが作られます。
      • .eslintignoreは対象にしたくないファイル(gulpfile.jsなど)を記載できます。
      • わたしの設定も晒しておきます。
{
    "env": {    // (windowなど)特定のグローバル変数を許可したりします
        "browser": true,
        "node": true
    },
    "extends": "airbnb",    // React界隈ではairbnbのコードスタイルがわりとメジャーのようです
    "plugins": [
        "react",
        "jsx-a11y",
        "import"
    ]
}

ファイル構成の見直し、クラス分割

  • クラス単位でのファイル分割
    • ページ構成要素をcomponentsとし、ディレクトリを用意しました。
    • ページ構成要素が1つの場合はそのままファイル名に、複数の場合はディレクトリを作成しindex.jsを用意しました。
      • 外部参照時にcomponents/Mainと呼び出すことで、Mainがcomponent的に分割された際もcomponents/Main/index.jsxを作れば同じように呼び出せるため。
─src
  └jsx
    └components
      └Main.jsx       // components/Mainと呼びだす
      └Todos
        └index.jsx    // components/Todosと呼びだす
        └List.jsx      // Todosページの構成要素
        └Detail.jsx   // Todosページの構成要素
  • 共通クラスの用意
    • コンポーネントも使いまわすものはsharedディレクトリにまとめました。
      • とはいえ、この段階ではテンプレートエンジンにあるinclude的に使う程度です。
─src
  └jsx
    └components
      └shared
        └Header.jsx
        └Footer.jsx

3. ルーティングの考慮

React Routerの導入

import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';

import NotFound from './Components/Error/NotFound';
import Main from './Components/Main';
import Todos from './Components/Todos';
import TodosSummary from './Components/Todos/Summary';

const appHistory = useRouterHistory(createHashHistory)({ queryKey: false });

render((
  <Router history={appHistory}>
    <Route path="Todos">
      <IndexRoute name="Todos" component={Todos} />
      <Route name="TodosSummary" path="summary" component={TodosSummary} />
    </Route>
    <Route name="Main" path="/" component={Main} />
    <Route name="NotFound" path="*" component={NotFound} />
  </Router>
), document.getElementById('app'));
  • 呼び出すjsxクラスを切り替え、ルーティングのような事ができる。
    • 上記の場合はハッシュURLでのルーティングになる。(http://example.com/#/todos/summary/
    • ハッシュを利用しないこともできる。(http://example.com/todos/summary/
      • appHistory → browserHistory に置き換える。
      • 当然、アプリケーション・サーバー等でルーティングをindexに向ける等の作業が必要。

利用した書籍

入門 React ―コンポーネントベースのWebフロントエンド開発

入門 React ―コンポーネントベースのWebフロントエンド開発

ここまでで感じているReact私見(長短)

  • 良いと思ったところ
    • 仮想DOMの安心感
    • Viewに特化したわかりやすい処理系統
    • JSXでテンプレートエンジン不要
    • Flux、Redux、React Routerなどと柔軟に連携
  • 問題を感じているところ
    • デザイナーとの連携(テンプレートとロジックが近すぎるため)
    • 連携が柔軟である上でのModel、Controllerの選定リスク(Redux衰退したらどうしようとか)
    • テンプレートを内包していて容量的に重い

ひとまず締めくくります

わたしも絶賛勉強中ではありますが、上記の工程を積んでReactjsが使えるようになってきました。 次はReduxの導入・実装をおこなっていこうと思っています。