【Titanium】thisとparentとchildrenを使った書き方

Titanium Advent Calendar 2014の8日目。最近多用しているthis, parent, childrenを使った書き方の紹介です。

環境: Titanium SDK 3.3.0.GA

Alloyは使ってません。

これらを使うとループの中でクロージャ(無名関数)を作成する必要がなくなったり、「引数を使いまわしたいから無名関数でネストする」必要がなくなります。

メモリ効率的にもいいような気がします(実際は分かりません)。

例えばこんなコードがあったとします。

/ui/common/testView.js

// Expose API
exports.createView = createView;

function createView(title) {
    var frameView, buttonView, i;

    frameView = Ti.UI.createView({
        layout: 'vertical',
        height: Ti.UI.SIZE
    });

    for (i = 0; i < 10; i++) {
        buttonView = Ti.UI.createButton({
            title: title + i
        });

        (function() {
            var index = i;
            buttonView.addEventListener('click', function() {
                alert(index);
            });
        })();

        frameView.add(buttonView);
    }

    return frameView;
}

これを呼び出すapp.js

var win = Ti.UI.createWindow({
    backgroundColor:'#ffffff'
});

win.add(require('/ui/common/testView').createView('ボタン'));
win.open();

これを実行すると10個のボタンを配置します。それぞれクリックすると何番目のボタンがクリックされたかalertします。

iOS Simulator Screen shot Dec 7, 2014, 1.52.43 PM

何も問題なく動きますが、クロージャの中で宣言されたindexはメモリから開放されないようです。

なのでクロージャを使わない書き方にしてみます。childrenを参照すればbuttonViewも使わなくて済みます。Clickしたあとの処理も外に出します。

/ui/common/testView.js

// Expose API
exports.createView = createView;

function createView(title) {
    var frameView, i;

    frameView = Ti.UI.createView({
        layout: 'vertical',
        height: Ti.UI.SIZE
    });

    for (i = 0; i < 10; i++) {
        frameView.add(Ti.UI.createButton({
            title: title + i
        }));

        frameView.children[i].dataIndex = i;
        frameView.children[i].addEventListener('click', handleClick);
    }

    return frameView;
}

function handleClick(e) {
    alert(e.source.dataIndex);
}

 

これでネストするクロージャがなくなってスッキリしました。

次はモジュールとして使いやすくするために引数を追加して処理を分けてみます。

app.jsはこんな感じで呼び出します。

var win = Ti.UI.createWindow({
    layout: 'vertical',
    backgroundColor:'#ffffff'
});

win.add(require('/ui/common/testView').createView({
    title: '上のボタングループ',
    frameColor: '#faa'
}));

win.add(require('/ui/common/testView').createView({
    title: '下のボタングループ',
    frameColor: '#aaf'
}));

win.open();

 

testView側ではparentを参照して、呼び出されたときの値を参照するようにします。

あと、for文の中で追加するviewが複雑になったときのためにe.sourceを参照せずthisを使うようにします。

/ui/common/testView.js

// Expose API
exports.createView = createView;

function createView(args) {
    var frameView, i;

    frameView = Ti.UI.createView({
        layout: 'vertical',
        backgroundColor: args.frameColor,
        height: Ti.UI.SIZE
    });

    // Save to custom property
    frameView.viewData = args || {};

    for (i = 0; i < 10; i++) {
        frameView.add(Ti.UI.createButton({
            title: args.title + i
        }));

        // Save to custom property
        frameView.children[i].dataIndex = i;
        frameView.children[i].addEventListener('click', handleClick);
    }

    return frameView;
}

function handleClick() {
    var data = this.parent.viewData;
    alert(data.title + ':' + data.frameColor + ':' + this.dataIndex);
}

実行結果

iOS Simulator Screen shot Dec 7, 2014, 2.35.44 PM

こんな感じで無名関数をネストせずに書いていくことが出来ます。Androidでも問題なく動きます。

addEventListenerは自動でthisをセットしてくれますが、JavaScriptのcallメソッドを使うと自分でthisをセットして関数を呼び出すことが出来ます。

 

thisやparentはviewが複雑化したときに威力を発揮します。

実体験ではカレンダーの月表示を実装したときは、thisとparentとカスタムプロパティを使ってコードがスッキリしました。

一応今までのアプリは上手く行ってますが、懸念点としては

  • parentプロパティは公式ドキュメントに載ってないので、そのうち使えなくなるかもしれない
  • view自体に値を保存するやり方はいいのかな?
  • 実際にメモリ効率よくなるの?
  • Alloy使えばヨロシクやってくれるのかな?

 

あくまでも私はこう書いているよということで。「自分はこう書いてるよ」というコメント大歓迎です。

 

< Related Posts >