React v0.14.x から v15.2.x へのアップデート録

react v15.2.x アップデートに関連した記録

FRESH! by AbemaTVAbemaTV の React を v0.14.x から v15.2.x にアップデートしたので、遭遇した WARN などについてメモ。

基本的には React v15.0 | React を読めばマイグレーションガイドがありますが、今回の作業で実際に遭遇したものだけ対応を記録します。

不明な prop を渡すなエラー … Unknown props foo, bar... on <***> tag. Remove these props from the element.

<div {...props} /> のように、下位コンポーネントに対する props の雑な継承で WARN が発生します。DOMAttribute として正しくない prop が要素に渡ると警告の対象です。詳細は Unknown Prop Warning | React にあります。

function MyDiv(props) {
  const { layout, ...rest } = props
  if (layout === 'horizontal') {
    return <div {...rest} style={getHorizontalStyle()} />
  } else {
    return <div {...rest} style={getVerticalStyle()} />
  }
}

この例のように余分を省いた rest が取れればよいのですが、Functional Stateless component でもなければ render 以外で使う prop もあるわけで、その場合 unused variable として eslint に怒られます。

const extendsProps = ['属性でない', 'いくつかの', 'prop名'].reduce((curt, key) => {
  delete curt[key];
  return curt;
}, assign({}, this.props));

ということで既存のコードを尊重して、雑な props 継承を行っているコンポーネント側で上記のような対応を行いました。 data-* やら aria-* を考慮し始めるとホワイトリストよりはブラックリストが妥当になってしまうでしょう。

単位を指定しろエラー … a div tag was passed a numeric string value for CSS property width (value: 128) which will be treated as a unitless number in a future version of React.

widthheight などの style に渡してる value に単位がないと WARN が発生します。今回のケースでは次のような状態でした。(かなり簡略化して表現しているので察してください)

<!-- 呼び出し側 -->
<Media width={128} height={96} />

<!-- コンポーネント側 -->
class Media extends Component {
  const { width, height } = this.props;
  render() {
    return <div style={ { width, height } }`>...</div>
  }
}

呼び出し側で単位の指定を徹底する or 単位がなければコンポーネント側で px をデフォルトとする、などの対応が考えられますが既存コードへの影響度に配慮して、コンポーネント側で px を付与する対応としました。

サバクラがズレてるぞエラー … React attempted to reuse markup in a container but a checksum was invalid.

要素についてる属性の順序がサバクラで異なって WARN が出ます。これまで何もなかったのにナゼという事案でしたが Isomorphic rendering sporadically renders attributes in a different order · Issue #6451 · facebook/react に該当していました。サーバーサイドレンダリングしている場合に限った事案です。

  • V8 の Object.assign 実装は、いくつかのバージョンでプロパティの順序が崩れる問題を持つ
  • node に限れば v6 未満で使われている V8 が該当する
  • React v0.14 までは内部で Object.assign の Polyfill を使っていた
  • React v15 からネイティブの Object.assign を利用するようになった → 壊れてる /(^o^)\

これによって Object.assign 相当の機能を使ったときにサバクラでレンダリング結果がズレる事案が発生していたようです。すでに object-assignObject.assign が壊れているケースを検知して使用を避ける実装が Add feature test against buggy V8 versions by spicyj · Pull Request #32 · sindresorhus/object-assign で入っています。

対応としては Object.assign = require('object-assign'); とサーバースクリプト冒頭で宣言し、ネイティブメソッドを Polyfill 実装に置き換えることで一旦修正としました。次は node のバージョンアップをやらないといけません...。

正気を確かめるエラー … It looks like you're using a minified copy of the development build of React.

minify されてるのに development ビルドぽいけどお前正気か?という親切な WARN が発生するようになっていますね。急に表示されるようになったので何かと思いきや、ただの親切な忠告だった系。

valueLink 使うなエラー … valueLink prop on input is deprecated; set value and onChange instead.

valueLink を自分は使ってなかったけど、他の人が使っていた箇所があったみたいでした。v15 でもまだ動くので、一旦ほっといて担当者にパス...(´・ω・`)

react-router v2.6.x にもアップデートした記録

個人的に react-router を使うことはありませんが、周囲ではよく使われているので触ることになります。アップデートで触る分には、蕁麻疹がでることもないので問題ありません。

プロパティの改名または廃止 … props.history and context.history are deprecated. Please use context.router.

context.historycontext.router に文字列置換して、context.location は廃止になっていますが react-router/v2.0.0.md at master · reactjs/react-router にあるとおり、自分で Root Component から context に注入するスタイルをとります。

export default class AppContainer extends React.Component {
  static childContextTypes = {
    location: React.PropTypes.object
  }
  getChildContext() {
    return { location: this.props.location }
  }
  /* ...略... */
}

既存コードへの変更はほとんどありませんでした。文字列置換のみ。

history 周りの依存変更 … It appears you have provided a deprecated history object to , please use a history provide by React Router with...

ReactTraining/history: Manage browser history with JavaScript を使っていたところを、react-router から import できる browserHistory に置き換えます。

// before
import { Router } from "react-router";
import createHistory from "history/lib/createBrowserHistory";

function getRoutes() {
  return <Router history={ createHistory() }> ... </Router>
}

// after
import { Router, browserHistory } from "react-router";

function getRoutes() {
  return <Router history={ browserHistory }> ... </Router>
}

メソッド名の変更1 … registerTransitionHook is deprecated; use listenBefore instead

正確には ReactTraining/history: Manage browser history with JavaScript のアップデートによるものですが、registerTransitionHook(listener)listenBefore(listener) に変更されました。

メソッド名の変更2 … pushState is deprecated; use push instead

context.router ( 以前は context.history ) に実装されていた pushState(stateObj, pathOrLoc) メソッドが push(pathOrLoc) に変更されました。

現場からは以上です

思ったより簡単に済んでよかったよかった。これからテストだけど...。