React+TypeScript+jQueryの共存(4)

React+TypeScript+jQueryの共存(4)

React+TypeScript+jQueryの共存(3)の続きです。

TypeScriptにしたらref属性もうまくとれない!

今まではref属性を使って、this.refs.nameとかでref属性で特定した要素のname属性の値はなんだろな?って取得することができました。
ところが、TypeScriptにしたらなんかうまくいきません。
調べてみると、

という具合にやり方を書いてくれている記事があったのですが…

すみません、自分のソースを良く見たら、そもそもref属性を見ないといけないところなんてありませんでした。
前回参考にしていたTag.tsxのTagコンポーネントは、こんな感じ。

//タグ
class Tag extends React.Component<any,any> {
  handleAjax() {
    apiURL = './api/article?tag=' + this.refs.tag.name;
    $('.overlay').removeClass('overlay');
    $('#overlay').css('display', 'none');
    $('#overlay-close').css('display', 'none');
    $('#spinner').css('display', 'block');
  }
  render() {
    return (
      <li><a ref="tag" name={this.props.id} onClick={this.handleAjax}>{this.props.tag}</a></li>
    )
  }
}

それが、こう。

class Tag extends React.Component<any,any> {

  handleAjax() {
    apiURL = './api/article?tag=' + this.props.id;
    $('.overlay').removeClass('overlay');
    $('#overlay').css('display', 'none');
    $('#overlay-close').css('display', 'none');
    $('#spinner').css('display', 'block');
  }
  render() {
    return (
      <li><a onClick={this.handleAjax.bind(this)}>{this.props.tag}</a></li>
    )
  }
}

(´ε`;)ウーン…

apiURLに代入するためのURL文字列を作っているところ。
this.refs.tag.nameとかやらなくっても全然良くて、親から渡されたidをそのまま使えばいいだけでした。
無駄なことしてましたね。
ref属性消してもコンパイル通るし(;´Д`)

というわけで私のソースは参考にならないので、ref属性を使いたくて困ってる方は上記の記事リンクを参考にしてみてください。

おそらく今jQueryで書いているところをすべてReactに置き換えたりする場合、その過程でref属性が必要になってくるのかな?という予想。

map()に渡されるデータの型定義が必要

さてもう一つの問題ですが、こちら。

//タグリスト
var TagList = React.createClass({
  render: function() {
    var tagNodes = this.props.data.map(function(tag) {
      return (
        <Tag tag={tag.name} id={tag.id} key={tag.id}/>
      )
    });
    return (
      <ul className="tag-list">
        {tagNodes}
      </ul>
    )
  }
});
 
module.exports = TagList;

かんたんに言うと、

   var tagNodes = this.props.data.map(function(tag) {

この行でエラーが出ます。

dataってなんだよ!って言われます。

これはTagListコンポーネントをいくら睨みつけてもダメで、dataを渡している親コンポーネントを修正する必要があります。
TypeScriptを導入する前のソースでは、親となるSiteHeaderコンポーネント(SiteHeader.jsx)で、getInitialStateしていました。

//サイトヘッダコンポーネント
var SiteHeader = React.createClass({
  //JSONデータ取得
  getInitialState: function() {
    return {data: []};
  },

//略

  loadTagFromServer: function() {
    var t = this;
    $.ajax({
      url: './api/tag',
      dataType: 'json',
      cache: false,
    }).done(function(data) {
      t.setState({data: data});
    }).fail(function(xhr, status, err) {
      console.error('./api/tag', status, err.toString());
    });
    tagBool = false;
  },

//略

それをTypeScriptに置き換えたのだから、TypeScriptの書き方にしないといけないですね。SiteHeader.tsxを修正します。

//サイトヘッダコンポーネント
export default class SiteHeader extends React.Component<any,any> {

  constructor() {
    super();
    this.state = {data: []};
    this.loadTagFromServer = this.loadTagFromServer.bind(this);
    this.fetchTag = this.fetchTag.bind(this);
  }

//略

  loadTagFromServer() {
    var t = this;
    $.ajax({
      url: './api/tag',
      dataType: 'json',
      cache: false,
    }).done(function(data) {
      t.setState({data: data});
    }).fail(function(xhr, status, err) {
      console.error('./api/tag', status, err.toString());
    });
    tagBool = false;
  }

constructor()の中でdataを初期化しています。
ほかのところで出てくるイベントのthisをbindしている記述が一緒に入っていますが、これは前回の記事でやったやつです。

大事なのは

    this.state = {data: []};

ここですね。

ということで、TagListコンポーネント自体はそこまで変更ありません。
exportの仕方が変わったのと、map()の無名関数の引数tagに型を定義(やっぱりany!)しただけです。

//タグリスト
export default class TagList extends React.Component<any,any> {
  render() {
    var tagNodes = this.props.data.map(function(tag:any) {
      return (
        <Tag tag={tag.name} id={tag.id} key={tag.id}/>
      )
    });
    return (
      <ul className="tag-list">
        {tagNodes}
      </ul>
    )
  }
}

ここまでやったのと同じような修正を全体にかけます。
骨が折れますね…(;´Д`)
既存プロジェクトに導入するのは無謀でしたかね。

自前のjsファイルをtsファイルに変更

あと残るはcommon.jsです。
これはjQueryのようなライブラリと違って、便利な型定義ファイルがありません。
まあ、当たり前なんですが…

var inputURL:string = '';
var inputEmail:string = '';
var inputPassword:string = '';
var inputRemember:string = '';
var deleteURL:string = '';
var tagArr:string[] = [];
var apiURL:string = './api/article';
var tmpURL:string = apiURL;
var articleBool:boolean = false;
var tagBool:boolean = false;
var loggedin:string = $('[name="user_id"]').attr('value');
var tagName:string = '';
var already: Object;

こんなふうにして自分で型をつけていかないといけませんね。

ここまでやればもうコンパイルも通る!
別に共存させるつもりでやったわけじゃないんですが、結果的にReact+TypeScript+jQueryが共存したソースができあがりました。
あとはanyをばら撒いたところらへんをちゃんと型定義するとかしないと。

とりあえずGitHubに置きました。

React使ってSPAを作るよではLaravelを使ってるんですが、このリポジトリにはLaravelの関連ファイルが上がってないので、ローカルで動かしたい場合はLaravel入れておいてくださいね。

こんなのもできたようなので、これもminamiで試してみたいです…