Knockout.jsのコンポーネント化を劇的に簡単にする方法
Knockout.jsって便利だなーとめっちゃ思っているのですが、bindingする範囲の管理に困っていました。そこに対する解決策というか解決案です。
今まで
Knockout.jsでは、管理の範囲をid要素指定で限定することができますが、把握するのが大変です。
# #foo内でbindingする。
ko.applyBindings(view_model, docuemnt.getElementById('foo'))
これだと#foo
内の要素とかに間違えてbindingしたりすると重複bindingでエラーになります。あとDOMが辛いのにDOMで範囲を指定するのがダサいですね…。
やはりコンポーネント化か。しかし…
@sukobutoさんにtwitterで教えてもらったのですが、コンポーネント化して管理するのがよさそうです。
@patorash 全体を管理する AppViewModel を作って、そのプロパティとしてコンポーネント用の ViewModel を用意していきます。https://t.co/YgVZmf2J1I
— けん (@ken_zookie) 2015, 4月 9
たしかに、全体を管理するAppViewModelを作ると、withでコンポーネントを管理できるし、すでにbinding済みの領域との衝突が発生しなくてよさそうです。
しかし、コンポーネント化しようとすると、ko.applyBindings
を実行するタイミングが難しいのと、コンポーネントは全部一旦AppViewModelのプロパティとして登録するのか?ということになります。
class window.AppViewModel
constructor: ->
@fooViewModel = new FooViewModel()
@barViewModel = new BarViewModel()
@hogeViewModel = new HogeViewModel()
# これをViewModelがあるだけ繰り返すとかめんどい
$ ->
app_view_model = new AppViewModel()
ko.applyBindings(app_view_model)
まぁ、やってられません。
コンポーネントを動的に追加する
そこで、全体を管理するAppViewModelを修正して、動的に追加できるようにしました。
class window.AppViewModel
addViewModel: (view_model_name, view_model) ->
Object.defineProperty(this, view_model_name, {
value: view_model,
writable: false,
enumerable: true
})
addViewModels: (hash) ->
self = this
$.each hash, (key, value) ->
self.addViewModel(key, value)
キモは、definePropertyを使っているところです。これを使って、動的にプロパティを定義し、valueにコンポーネント化するViewModelを渡しています。
また、複数のViewModelを一気に登録できるように、addViewModelsも定義してあります。
複数のViewModelを登録するため、と書いていますが、本当はHashで書きたいだけです。
class InquiryViewModel
constructor: ->
@email = ko.observable('')
@body = ko.observable('')
# など、適当に…
$ ->
# ダブルクォートで囲むのがだるい…
app_view_model.addViewModel("inquiryViewModel", new InquiryViewModel())
# ダブルクォートがなくて綺麗(私的に)
app_view_model.addViewModels(inquiryViewModel: new InquiryViewModel())
例
Before(改修前)
お問い合わせフォームをKnockout.jsで作っていたとします。
html(slim)
/ お問い合わせフォーム
form#new_inquiry data-bind="with: inquiryViewModel"
input type="email" name="email" data-bind="value: email, valueUpdate: afterkeydown"
textarea name="body" data-bind="value: body, valueUpdate: afterkeydown"
input type="submit" value="送信" data-bind="enable: checkParams"
お問い合わせフォーム用のViewModel
class window.InquiryViewModel
constructor: ->
@email = ko.observable('')
@body = ko.observable('')
@checkParams = ko.computed ->
# 簡易的なチェック
@email().length > 0 && @body().length() > 0
, this
全体を管理するためのViewModel
class window.AppViewModel
constructor: ->
@inquiryViewModel = new InquiryViewModel()
$ ->
app_view_model = new AppViewModel()
ko.applyBindings(app_view_model)
beforeのように作ると、使わないViewModelまで追加した状態になってしまいます。そのようにしない方法もあるでしょうが、constructorが膨れ上がっていくと思われるのであんまり見た目的にもよくありません。
After(改修後)
そして、思いついたのが、これです。
html(slim)
お問い合わせフォームは変わりません。
お問い合わせフォーム用のViewModel
class window.InquiryViewModel
constructor: ->
@email = ko.observable('')
@body = ko.observable('')
@checkParams = ko.computed ->
# 簡易的なチェック
@email().length > 0 && @body().length() > 0
, this
$ ->
# お問い合わせフォームが存在するときのみbindingする
if $('#new_inquiry').is '*'
# app_view_modelにinquiryViewModelプロパティを追加
app_view_model.addViewModels(inquiryViewModel: new InquiryViewModel())
全体を管理するためのViewModel
class window.AppViewModel
addViewModel: (view_model_name, view_model) ->
Object.defineProperty(this, view_model_name, {
value: view_model,
writable: false,
enumerable: true
})
addViewModels: (hash) ->
self = this
$.each hash, (key, value) ->
self.addViewModel(key, value)
$ ->
window.app_view_model = new AppViewModel()
# 全てのJSの処理が終わってから、必要なコンポーネントのみ追加した状態でbindingする
$(window).load ->
ko.applyBindings(app_view_model)
このようにすることで、必要なコンポーネントのみをAppViewModelに追加した状態でbindingすることができます。余計な処理が入り込むことがなくなりますし、コンポーネントもwithで範囲指定ができるので、わかりやすいかと思います。
注意
Object.definePropertyはIE9以上から利用可能なので、IE8以下の対応が必要な場合は上記の方法は使えません。
Author And Source
この問題について(Knockout.jsのコンポーネント化を劇的に簡単にする方法), 我々は、より多くの情報をここで見つけました https://qiita.com/patorash/items/979b807c620c54b073aa著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .