kitak blog

Kみたいなエンジニアになりたいブログ

THE INCAL を読んだ

読んだ。

DUNE で出会ったアレハンドロ・ホドロフスキーメビウスが再びタッグを組んで作ったフレンチSFコミック。
街の一角のありふれた場面からビッグバンのように爆発して、宇宙を覆い尽くさんばかりに物語が膨らんでいく。読んでいて圧倒される作品。 両性具有の話が小道具として出てくるのだけど、その表現がダ・ヴィンチ・コードと繋がるものがあって興味深かった。

アンカル

アンカル

Ethereum トークン (ERC20 Token Standard) の残高を取得する

8月頃にプレセールで買ったとあるトークンが最近発行されて、Etherscan や My Ether Wallet でトークンが発行されたことを確認したのだけど、自分用の Wallet App を作ってみたくなったので、プログラムからトークンの残高を取得する方法を調べてみた。

web3 という Ethereum JavaScript API の module を使う。version は 1.0.0-beta.23。

const Web3 = require('web3');
const web3 = new Web3();
const WALLET_ADDRESS = 'XXX';
const CONTRACT_ADDRESS = 'XXX';

web3.setProvider(new web3.providers.HttpProvider('https://api.myetherapi.com/eth'));
web3.eth.call({
  data: `0x70a08231000000000000000000000000${WALLET_ADDRESS}`,
  to: CONTRACT_ADDRESS
}, "pending").then((res) => {
  console.log(parseInt(res, 16));
})

data の部分は、コントラクトのメソッドを SHA3 256 でエンコードした値(web3.utils.sha3('balanceOf(address)').substring(0,10))とトークンを保管しているアドレスを合わせて、全体が 32 byte になるように結合したもの。

参考

vue-router で初期表示後に、どのルートで解決されたか知りたい

初期ナビゲーションが終わった後のルーターオブジェクトにアクセスすればよい。onReadycurrentRoute のコンボでいける。

router.onReady(() => {
  router.currentRoute;
});

vue-router ひと通りドキュメントに目を通していたつもりだったのですが、ひさしぶりにみたら router オブジェクトのメソッドが色々増えていたり、学びがあった。

Webpack を使っていてファイルの相対パスを書くのがつらくなったとき

小ネタ。

Webpack(というよりモジュールバンドラ) を使っていて、ディレクトリの階層が深くなってくると import や require でロードするファイルのパスを ../../../../foo.js のように ../ の数を正確に指定するゲームになってくる。

以下のように書くことで src ディレクトリをルートにしてパスを指定することができるようになるのだけど

resolve: {
    modules: [
      path.resolve(__dirname, 'src'),
      "node_modules"
    ],
},

同僚氏に npm でいれたパッケージか、src にあるファイルか分からないから、src ディレクトリの alias を定義したらどうか、と勧められた。

resolve: {
    alias: {
        '@': resolve(__dirname, 'src'),
    },
},

これで、どの階層にあるかに関係なく、import store from "@/store" とか import router from "@/router" とか書ける。Vue の Progressive Web App template や Nuxt.js でも同様のことをやっている。最悪、記号の単純な置換か、簡単なスクリプトを書けば済むので、筋は悪くないと思う。

Vue で配列を使った算出プロパティの値が変わらないとき

1回ハマったら体が覚えて、次回から気を付けるようになるのだけど、記事にしておく。

例えば、以下のように配列をスタックとして使い、トップを算出プロパティで定義したとき。1秒後にトップの id が 100 になると思いきやそうはならない。Vue は push, pop, splice といった操作のメソッドをラップして、配列の変更を検知しているので、配列の要素へ代入しても配列の変更とはみなされない。

new Vue({
   data: {
      items: []
   },
   mounted() {
       this.items.push({id: 1});
       this.items.push({id: 2});
       this.items.push({id: 3});
       setTimeout(() => {
           this.items[this.items.length - 1] = {id: 100};
       }, 1000);
   },
   computed: {
      top() {
          if (this.items.length === 0) {
              return null;
          }
          return this.items[this.items.length - 1];
      }
   }
});

これを意図通りに動かすには setTimeout のコールバックの処理を次のように splice メソッドを使って変更するように書き換える。

this.items.splice(this.items.length - 1, 1, {id: 100});

vue-router でパスにマッチしたコンポーネントの Vue インスタンスにアクセスする

タイトルが長い。

お仕事で vue-router 絡みのプラグインを書いていてアクセスしたくなった。

VueRouter のインスタンスが router として、以下のようにプロパティを参照することでできる。

router.currentRoute.matched[0].instances['default']

あくまで内部がこうなっているというだけの話で、API として正式に提供されているものではないので注意。

matched が配列になっているのは、ネストしたルートを定義することができるから。 例えば、以下のような定義の場合、/user/3/profile のようなパスでアクセスしたら、/users/3 で1回、/users/3/profile でもう1回マッチすることになる。

{
  path: '/users/:id',
  component: User,
  children: [
    {
      path: 'profile',
      component: UserProfile
    },
  ],
}

instances がオブジェクトになっているのは名前付きビューがあるため。以下のようなルーティング定義をした場合、instances オブジェクトの default, a, b それぞれのプロパティでコンポーネントの Vue インスタンスにアクセスできる。名前付きビューを使っていない場合は default プロパティを参照すれば良いです。

[
  {
    path: '/',
    components: {
      default: Foo,
      a: Bar,
      b: Baz
    },
  },
]

(keepalive コンポーネントを使うと話が変わりそうですが)インスタンスの参照はいつ変更される分からないので、参照を変数に入れて、使いまわすとかはしないほうがよさそう。

ServiceWorker で、ネットワークから取得しようして失敗したらキャッシュにフォールバックするやつ

ここ最近、ServiceWorker を使って色々試しているのだけど、その中のひとつ。

以下の内容を、エントリーの HTML や参照系の API に適用して、アプリケーションのオフライン対応を実現する。通常はネットワークから取得した最新の内容を使うが、オフラインやネットワークが不安定な場合はキャッシュの内容を使う。

  • ネットワークから取得しようとして失敗したらキャッシュを使う
  • ネットワークから取得するとき、1秒のタイムアウトを設定する
  • ネットワークから取得しようとして成功したらキャッシュに保存する
  • ネットワークから取得しようとして失敗した場合、キャッシュが存在しなかった場合はログにその旨を出力する

ServiceWorker 内で使える API は Promise を返すものがほとんどなので、Promise を駆使するかんじ。

var CACHE_NAME = 'v1';

var fetchWithTimeout = function(request) {
    return new Promise(function(resolve, reject) {
        setTimeout(reject, 1000);
        return fetch(request).then(resolve);
    });
};

self.addEventListener('fetch', function(event) {
    // 本当はここでパスやヘッダーの内容を見て、キャッシュすべき対象か調べる
    event.respondWith(
        fetchWithTimeout(event.request)
            .then(function(response) {
                return caches.open(CACHE_NAME).then(function(cache) {
                    console.log('Put to cache', response);
                    cache.put(event.request, response.clone());
                    return response;
                });
            })
            .catch(function(e) {
                console.error('Failed to fetch', e);
                return caches.open(CACHE_NAME).then(function(cache) {
                    console.log('Fallback to cache', event.request);
                    var response = cache.match(event.request);
                    if (!response) {
                      console.error('Missing cache', event.request);
                      return;
                    }
                    return response; 
                });
            })
    );
});