-
ES5のReact.jsソースをES6ベースに書き換える
- 2016年4月25日
- es2015
- ES5
- ES6
- React
※この記事は
http://qiita.com/kuniken/items/2e850daa26a10b5098d6
で投稿した内容と同じものです。
React.createClassはコンポーネントのインスタンス時に色々やってくれていたということがわかりました。もちろんその時にReact.Componentも取り込んでいました。
反対の視点で言えば、実装者にとって、いらないこともやっている、という見方もできます。
React.Componentのみでの宣言は、そういったある意味余計な処理は一切利用せず、実装者が都合に合わせて記述できる、というメリットがあると思います。
では、上記の差異を実装していきます。
概要
開発者にとってはメリットがありそうだけどユーザーにとってはいまいちメリットがよく分からないES6。ひとまずどのくらい開発者にとってメリットがあるのかを実際に把握するため、ES5のReact.jsソースをES6ベースに書き換えてみました。 対象のソースは、投稿[react-router v2.xとcontext]のサンプルソース(https://github.com/kunitak/react-router-1to2)です。参考
React TutorialをES6で書きなおしてみた – console.blog(self); http://sadah.hatenablog.com/entry/2015/08/03/085828 Babelで理解するEcmaScript6の import / export http://qiita.com/inuscript/items/41168a50904242005271 本家サイト https://facebook.github.io/react/docs/reusable-components.html#es6-classesES6ベースに書き換えてみる
Babelのインストール
babelifyと必要なプリセットをインストールしました。$ npm install --save babelify babel-preset-es2015 babel-preset-react
ソースの改修
client/scripts以下のソースをES6ベースに改修していきます。importの改修
requireを使ってモジュールをimportしていた箇所をimportを使って定義していきます。単純なパターン
//before
var React = require('react');
//after
import React from 'react';
これだけ。
モジュール内のメンバをimportする場合
//before
var EventEmitter = require('events').EventEmitter;
//after
import {EventEmitter} from 'events';
{}で囲みます。別名で扱いたい場合は、asをつけて変数を宣言してあげます。
//after
import {EventEmitter as Emitter} from 'events';
同一モジュールから複数のメンバをまとめてimportすることもできます。
//before
var ReactRouter = require('react-router'),
Router = ReactRouter.Router,
Route = ReactRouter.Route,
IndexRoute = ReactRouter.IndexRoute,
History = ReactRouter.History,
hashHistory = ReactRouter.hashHistory;
//after
import {Router, Route, IndexRoute, History, hashHistory} from 'react-router';
コンポーネント定義の改修
ES5ではコンポーネント定義はcreateClassを使って定義していました。 本家サイトに従い、ES6ではComponentの継承により定義したいと思います。//ES5
var Index = React.createClass({
render: function(){
return (
<div>
{this.props.children}
</div>
);
}
});
//ES6
class Index extends React.Component{
render(){
return (
<div>
{this.props.children}
</div>
);
}
}
classとして、React.Componentを継承して定義しています。
そのため、extends使って次のようなこともできます。
//ボディの定義
class Body extends React.Component{
constructor(props) {
super(props);
this.state = {message: ''}; //getInitialStateの代わりにconstructorで設定する
}
render(){
return (
<h1>ポータル {this.state.message}</h1>
);
}
}
//継承:ボディの定義
class ParentBody extends Body{
componentDidMount() {
this.setState({message: 'huga'});//画面では[ポータル huga]と表示される
}
}
さて、createClassでは定義したことのない、constructorというメソッドが出てきました。
“React.createClass” vs “extends React.Component”
React.Componentを使用した場合、幾つかcreateClassとは異なる仕様にぶち当たります。 以下のような違いがありました。React.createClass | React.Component | |
---|---|---|
thisのオートバインディング | ○ 全てのメソッドにthisをバインドしてくれる |
× constructorでthisをバインドするように記述する必要がある |
mixins利用有無 | 可 | 不可 |
state初期化 | getInitialStateメソッドを使用して初期化 | constructorでobjectの直接代入により初期化(getInitialStateメソッドは存在しない) |
props初期値指定 | getDefaultPropsメソッドを使用して指定 | コンポーネント外部でdefaultPropsオブジェクトに代入する(getDefaultPropsメソッドは存在しない) |
protoTypes、contextTypes定義 | コンポーネント内で定義 | コンポーネント外部でprotoTypes、contextTypesオブジェクトに代入する |
constructorの実装
superクラスのconstructor実装
constructorの実装は必須ではないのですが、何らかの理由で自分で実装する場合は、superクラスのconstructorも実装してあげる必要があります。constructor(props) {
super(props);
//anything to do
}
contextを利用する場合
contextを利用していて、constructorで何かしたい場合は、以下のように書けば良いようです。constructor(props, context) {
super(props, context);
//anything to do
}
state初期化
getInitialStateメソッドがないので、this.stateに直接オブジェクトを代入します。constructor(props) {
super(props);
this.state = {message: ''}; //getInitialStateの代わりにconstructorで設定する
}
componentDidMount() {
this.setState({message: 'huga'});
}
render(){
return (
<h1>ポータル {this.state.message}</h1>
);
}
カスタムメソッドへのthisのバインド
stateやprops、refsなどを扱うメソッドを自分で実装する場合は、thisをそのメソッドにバインドしてあげる必要があります。バインドしないとthisは当然nullのためエラーになります。constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);//バインド
this.state = {message: ''};
}
handleSubmit(){
var name = ReactDOM.findDOMNode(this.refs.name).value.trim();
var mail = ReactDOM.findDOMNode(this.refs.mail).value.trim();
this.props.addUser(name, mail);
}
componentDidMount() {
this.setState({message: 'huga'});
}
props初期値指定、protoTypes、contextTypes定義
これらはコンポーネント定義後に外部で実装します。props初期値指定、protoTypes定義
class User extends React.Component{
render(){
return (
<tr>
<td>{this.props.name}</td>
<td>{this.props.mail}</td>
</tr>
);
}
}
//propTypesは外で定義する
User.propTypes = {
name: React.PropTypes.string.isRequired,
mail: React.PropTypes.string
};
//props初期値指定
User.defaultProps = {name: "hoge"};
contextTypes定義
class Header extends React.Component{
render(){
return (
<header>
<Link to="/portal" style={{paddingRight: "5px"}}>ポータル</Link>
</header>
);
}
};
//contextTypesは外で定義する
Header.contextTypes = {
router: React.PropTypes.object.isRequired
}
varをlet、constに改修する
ES6からlet、constで変数宣言できるようになっているのでvarを改修しました。 ちなみに違いは以下のとおりです。- let
- 同一スコープでの再定義が不可(varは可能)。
- const
- 再代入も再定義も不可。
handleSubmit(){
const name = ReactDOM.findDOMNode(this.refs.name).value.trim();
const mail = ReactDOM.findDOMNode(this.refs.mail).value.trim();
this.props.addUser(name, mail);
}
アロー関数の導入
以下な感じです。//before
var getUserStoreStates = function(){
return UserStore.getAjaxResult();
};
//after
const getUserStoreStates = () => UserStore.getAjaxResult();
returnを書かなくても値を返してくれます。
複数行にわたる場合は、{}で囲んであげます。
const ajax = {
get : (url, params, callback) => {
request
.get(url)
.query(params)
.end((err, res) => callback(err, res))
},
post : (url, params, callback) => {
request
.post(url)
.send(params)
.end((err, res) => callback(err, res))
}
};
exportの改修
ついでにexportも改修しました。//before
module.exports = UserBox;
//after
export default UserBox;
まとめ
書き換え作業を通じて思ったメリット・デメリットは以下のとおりでした。メリット
- スキルトランスファーしやすい:
- 例:javaエンジニアでも理解しやすい構造になってきている。importとかclass、extendsとか知ってる単語が出てくる。
- extendsで簡単に既存のクラスが継承できるので、開発効率が上がる。
- アロー関数により、thisをthatに代入して、とか素人目で意味不明なことをしなくて済む。
デメリット
- es6で書いて、babelかますと、es5で書くよりサイズがでかくなってしまう。
- es5で解釈できるようにするために、babelify時に無駄にメソッドが増えてしまうため。
- また、実装時のコードはbabelifyにより若干改変されることを頭に入れてデバッグする必要がある。(許容できるレベルではある)
- ソースの管理コストを考えると、当然gulpタスクもnode.jsもes6ベースで書くことを考えねばならない。
- 他のプロジェクトは、、まあいいか。
サンプルソース
https://github.com/kunitak/es5-to-es6 ES5とES6のソース比較この記事を書いた人 : 國田健史
スタッフブログタグ:
AWS bluebird css CSV docker docker compose electron ES6 es2015 Git Heroku ITコンサルティング JavaScript justinmind less mongoDB Node.js php PostgreSQL Private Space Promise React react-router reactjs Salesforce scss Selenium Builder selenium IDE Selenium WebDriver stylus TypeScript VirtualBox VisualStudioCode vue vuejs webpack システム開発プロジェクト セキュリティ ワイヤーフレーム 上流工程 卒FIT 帳票 要件定義 設計 電力小売業界
一覧へ戻る
AWS bluebird css CSV docker docker compose electron ES6 es2015 Git Heroku ITコンサルティング JavaScript justinmind less mongoDB Node.js php PostgreSQL Private Space Promise React react-router reactjs Salesforce scss Selenium Builder selenium IDE Selenium WebDriver stylus TypeScript VirtualBox VisualStudioCode vue vuejs webpack システム開発プロジェクト セキュリティ ワイヤーフレーム 上流工程 卒FIT 帳票 要件定義 設計 電力小売業界