React 15.5へのアップデート手順(prop-typesとReact.createClass)


React 15.5がリリースされ、今までは同梱されていた`React.PropTypes`と`React.createClass`が非推奨となりそれぞれ別パッケージに分かれました。

- [React v15.5.0 - React Blog](https://facebook.github.io/react/blog/2017/04/07/react-v15.5.0.html "React v15.5.0 - React Blog")
- [prop-types](https://github.com/reactjs/prop-types "prop-types")
- [create-react-class](https://www.npmjs.com/package/create-react-class "create-react-class")

`React.PropTypes` を [prop-types](https://github.com/reactjs/prop-types "prop-types")へマイグレーションする方法としてcodemodのスクリプトが提供されています。

- [reactjs/react-codemod: React codemod scripts](https://github.com/reactjs/react-codemod "reactjs/react-codemod: React codemod scripts")

## React 15.5の警告内容

単純にReact 15.5へアップデートすると`React.PropTypes`と`React.createClass`のコンソールエラーが表示されます。
まだ非推奨の警告だけなので16.0のメジャーアップデートまでは普通に動作します。

> Warning: React.createClass is deprecated and will be removed in version 16. Use plain JavaScript classes instead. If you're not yet ready to migrate, create-react-class is available on npm as a drop-in replacement.

や

> Warning: Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead.

などの警告がでます。

![shopping cart example 2017-04-09 15-28-00.png](https://qiita-image-store.s3.amazonaws.com/0/734/9e4230d5-4b89-fe29-0902-36c693367956.png "shopping cart example 2017-04-09 15-28-00.png")


## マイグレーション手順

### `React.PropTypes`

1. jscodeshiftをインストール
2. react-codemodのマイグレーションスクリプトをローカルにコピー
3. jscodeshift + react-codemodでファイルを変換
4. [prop-types](https://github.com/reactjs/prop-types "prop-types")をnpm install

```shell-session
npm install -g jscodeshift
git clone https://github.com/reactjs/react-codemod.git
# React-PropTypes-to-prop-types.jsのパスをコピーする
# echo `pwd`/react-codemod/transforms/React-PropTypes-to-prop-types.js | pbcopy
# 指定したファイルのPropTypesをjscodeshiftで変換
find src/components -name "*.js" | xargs jscodeshift -t "React-PropTypes-to-prop-types.jsへのパス"


Processing 6 files...
Spawning 3 workers...
Sending 2 files to free worker...
Sending 2 files to free worker...
Sending 2 files to free worker...
All done.
Results:
0 errors
0 unmodified
2 skipped
4 ok
Time elapsed: 3.580seconds
```

個別にJavaScriptファイルを指定する場合は、次のような感じ。

```
jscodeshift -t "React-PropTypes-to-prop-types.jsへのパス" "変更したいJavaScriptファイルのパス"
```

これで自動的に `React.PropTypes` を [prop-types](https://github.com/reactjs/prop-types "prop-types") へ変換してくれます。

後は忘れずにReact 15.5へのアップデートと[prop-types](https://github.com/reactjs/prop-types "prop-types")をインストールすれば完了です。

```shell-session
npm i -S react@^15.5 react-dom@^15.5 prop-types
```

#### :warning: 変換に失敗する

> TypeError: Cannot read property 'name' of undefined

というエラーがでて変換に失敗する問題は、次のPRで解決されそうです。

- [React-PropTypes-to-prop-types.js has a transformation error · Issue #104 · reactjs/react-codemod](https://github.com/reactjs/react-codemod/issues/104)
- [test for existance of "init" property before reading name property by nathanstitt · Pull Request #106 · reactjs/react-codemod](https://github.com/reactjs/react-codemod/pull/106)


### `React.createClass`

`React.createClass`の場合もスクリプトが違うだけ基本的には同じです。
react-codemodの`class.js`を使い、`React.createClass`を`React.Component`へ変換します。

```shell-session
$ find src/components -name "*.js" | xargs jscodeshift -t /Users/azu/.ghq/github.com/react/react-codemod/transforms/class.js
Processing 6 files...
Spawning 3 workers...
Sending 2 files to free worker...
Sending 2 files to free worker...
Sending 2 files to free worker...
All done.
Results:
0 errors
1 unmodified
0 skipped
5 ok
Time elapsed: 4.826seconds
```

`React.createClass`の方はオプションもあるので、READMEを読むと良いと思います。

- <https://github.com/reactjs/react-codemod#explanation-of-the-new-es2015-class-transform-with-property-initializers>

注意点としては、上記のスクリプトはまだProposal段階の[tc39/proposal-class-public-fields](https://github.com/tc39/proposal-class-public-fields "tc39/proposal-class-public-fields: Stage 2 proposal for public class fields in ECMAScript")を前提に変換してきます。

そのため、次のようにES2017段階ではパースできないコードが生成されます。
(`React.createClass`はauto bindしているため、`method = () => {}`でそれを実現するためだと思います)

```js
    static propTypes = {
        products: PropTypes.arrayOf(PropTypes.shape({
            id: PropTypes.number.isRequired,
            title: PropTypes.string.isRequired,
            price: PropTypes.number.isRequired,
            quantity: PropTypes.number.isRequired
        })).isRequired,
        total: PropTypes.string.isRequired,
        onCheckoutClicked: PropTypes.func.isRequired
    };
```

これをgetterなどに直すか、Babelの[Class properties transform](http://babeljs.io/docs/plugins/transform-class-properties/ "Class properties transform")など使って変換する必要があります。

自分は、`babel-preset-stage-2`を入れて解決させました。(+ `.babelrc`へ追加)

```shell-session
npm i -D babel-preset-stage-2
```

### その他

codemodにはdouble quoteとかのオプションもありますが、ESLintの`--fix`で後からスタイルを揃えました。

## 変換結果

[Almin](https://github.com/almin/almin "Almin")の[almin/example](https://github.com/almin/almin/tree/master/example "almin/example at master · almin/almin")をまとめてReact 15.5へアップデートした結果を次のPRで見ることができます。

- [chore(example): update to React 15.5 by azu · Pull Request #158 · almin/almin](https://github.com/almin/almin/pull/158 "chore(example): update to React 15.5 by azu · Pull Request #158 · almin/almin")

9.9割ぐらいはコマンドラインのみでアップデートが完結しました。

Stateless functionがあるファイルの変換が上手くできなかったので手動で変更しました。