ウェイトトレーニングを記録するツールを作った

僕は筋トレをするのが趣味だ。 それで、前々からその記録をするツールが欲しいと思っていたので、SPAを作る練習ついでに作ってみた。

機能はかなり最小限だけど、僕が欲しかったものはこれで十分。

React.jsで作って、データベース代わりにlocalStorageに記録してる。

Record Your Weight Training

あと、ついでに自分の紹介ページも作り直した。

そこで表示してるブログのFeedは、最初Google Feed APIを使っていたけど遅かったので、普通にPHPで取得してJSONで出力したものをAjaxで取って来るようにした。

どっちも古いブラウザのことは考慮していないので、かなり清らかな心で作れた。

データに応じて要素を生成するときのスマートな方法

サーバーから受け取ったデータに応じて、要素を生成したいということはよくある。

テンプレートエンジンなどを使うと、そのデータを綺麗に処理することができる。 けど、それを使わずに素のJSで書くのはなかなかめんどくさい。

たとえば、こんなデータがあるとする。

var users = [
  { name: 'tom', age: 18 },
  { name: 'bob', age: 24 },
  { name: 'john', age: 36 }
];

このデータから、以下のHTMLを生成する方法を考える。

<dl>
  <dt>name</dt>
  <dd>tom</dd>
  <dt>age</dt>
  <dd>18</dd>
</dl>

<dl>
  <dt>name</dt>
  <dd>bob</dd>
  <dt>age</dt>
  <dd>24</dd>
</dl>

<dl>
  <dt>name</dt>
  <dd>john</dd>
  <dt>age</dt>
  <dd>36</dd>
</dl>

まず、普通に書くとこんな感じだと思う。

var frag = document.createDocumentFragment();

for (var i = 0, l = users.length; i < l; i++) {
  var user = users[i];
  var dl = document.createElement('dl');

  var nameTerm = document.createElement('dt');
  nameTerm.textContent = 'name';
  dl.appendChild(nameTerm);

  var nameDesc = document.createElement('dd');
  nameDesc.textContent = user.name;
  dl.appendChild(nameDesc);

  var ageTerm = document.createElement('dt');
  ageTerm.textContent = 'age';
  dl.appendChild(ageTerm);

  var ageDesc = document.createElement('dd');
  ageDesc.textContent = user.age;
  dl.appendChild(ageDesc);

  frag.appendChild(dl);
}

var result = document.getElementById('result');

result.appendChild(frag);

書くコードが無駄に多くて、わかりにくくてあまりスマートでない。 これを、わかりやすく書いてみる。

var html = users.map(function (user) {
  return `
    <dl>
      <dt>name</dt>
      <dd>${user.name}</dd>
      <dt>age</dt>
      <dd>${user.age}</dd>
    </dl>
  `;
});

var result = document.getElementById('result');

result.insertAdjacentHTML('beforeend', html.join(''));

最初の方法みたいに、document.createElementを実行するとNodeListが返るので、個々に別の要素に追加していかないといけない。

それを、配列の中にStringとして生成すると、実際にDOMに追加するタイミングで全て連結してそのままHTMLにパースできる。

insertAdjacentHTMLの第一引数にbeforeendを指定すると要素の末尾に、afterbeginを指定すると要素の先頭に第二引数のHTMLが追加される。

要素の中身を空にしてから追加したいというときは、innerHTMLで追加してもいいけど、速度的に微妙なのでwhile文とかで除去してからinsertAdjacentHTMLで追加するのがいいと思う。

var empty = function (el) {
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }
};

var container = document.getElementById('container');
empty(container);

Dynamics.jsをReact.jsで使う

物理法則に基づいたアニメーションを実装できるライブラリのDynamics.jsをReact.jsで使ってみた。

基本的にJSのアニメーションライブラリはDOMを直接操作するけど、React.jsを使う場合は実際のDOMと仮想のDOMとの整合性が崩れてしまうのでそれは推奨されない。

なので、アニメーションする値をstateで管理してやってみた。

import React    from 'react';
import dynamics from 'dynamics.js';

class Box extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      x: 0,
      y: 0
    };
  }

  componentDidMount() {
    dynamics.animate(this.state, {
      x: 200,
      y: 20
    }, {
      change: (e) => this.setState({x: e.x, y: e.y})
    });
  }

  render() {
    const style = {
      transform: `translate(${this.state.x}px, ${this.state.y}px)`
    };

    return (
      <div className="box" style={style}></div>
    );
  }
}

React.render(<Box />, document.getElementById('app'));

Dynamics.jsはJSのオブジェクトの値をアニメーションさせることもできて、値が変更する度にchangeが呼ばれるので、そのタイミングでsetStateしてやればいい。

もっといいやり方あれば教えてください。

レンタルブログの良さ

いろいろ考えてブログはやっぱりレンタルブログがいいと思ったので、はてなブログを始めた。

この記事を書き始めるほんの少し前までは、ブログのシステムをどんな構成にしようかということばかり考えていて、ブログを書くために気が散りすぎていた。

たとえば前に使っていたブログは、WordPressから配信した投稿データをクライアント側で組み立てて、ページを再読み込みせずに切り替えられるという風な仕組みにしていたけど、

  • 初期表示が遅いから最初だけサーバーサイドで描画しておきたい
  • もうどうせならCMSまで作ってしまいたい
  • 静的サイトジェネレータで作って管理したい

みたいなことを考えてた。

僕は今まで、さっきのようなことが思い立つたびにブログを作り直してきたので、結果的にひとつのブログを長期間続けられたことはなかった。

レンタルブログにしてしまえば、そんなことに振り回されてブログが書けないというのはなくなると思ったので、今この記事を書いている。

またすぐに別のやつに移ったりしないようになー、と思う。