我不明白knockout.js映射或组件是如何工作的?

时间:2015-07-27 10:15:47

标签: javascript knockout.js knockout-mapping-plugin

过去几周我一直在尝试使用Knockout.js映射和组件,坦白说,它只是糟透了。我发誓我错过了一些让一切都运转良好的魔术片,因为目前我试图用它制作的所有东西都感觉非常脱节和杂乱,因此,大多数都是无功能的。

我已经阅读了文档,虽然几乎没有满足或帮助。

用例

我有一个主视图模型,但它不是这个故事的明星。这是我正在尝试构建的可重用组件。我们称之为TweetViewModel。此组件取决于params.action是设置为“创建”还是“显示”,将允许用户输入有关推文的信息,或将其显示给他们:

组件视图模型

define(['knockout','ko.mapping', 'jquery', 'text!components/tweet/tweet.html'], function(ko, koMapping, $, htmlString) {
    function TweetViewModel(params) {

        var self = this;

        self.tweet = params.tweet;
        self.action = params.action;

        self.tweetUrl = ko.observable();
        self.tweetUrl.subscribe(function(value) {
            var valueOfTwitterUrlField = value;

            // Check that the entered URL contains 'twitter' before sending a request (perform more thorough validation serverside)
            if (valueOfTwitterUrlField.indexOf('twitter.com') !== -1) {

                var explodedVals = valueOfTwitterUrlField.split('/');
                var id = explodedVals[explodedVals.length - 1];

                $.ajax('/myproj/create/retrievetweet/' + id, {
                    dataType: 'json',
                    type: 'GET',
                    success: function (response) {
                    }
                });
            }
            // Allow default action
            return true;
        });
    }

    return { viewModel: TweetViewModel, template: htmlString };
});

模板

<!-- ko if: action == 'show' -->
<div class="card twitter-card">

</div>
<!-- /ko -->
<!-- ko if: action == 'create' -->
<div class="card twitter-card create">
    <label>Tweet URL</label>
    <input type="url" name="tweet_url" id="tweet_url" data-bind="textInput: tweetUrl" />
    <hr/>
    <p>Alternatively, if the tweet is no longer available, enter the tweet information below</p>
    <div class="tweeter-details">
        <img src="" />
        <span class="tweeter-screen-name" data-bind="text: tweet.tweet_user_screen_name"></span>
        <label>Username</label>
        <input type="text" class="tweeter-name" data-bind="value: tweet.tweet_user_name" />
    </div>
    <textarea class="tweet-text" data-bind="text: tweet.tweet_text"></textarea>
    <div class="date-input">
        <datetime params="value: tweet.tweet_created_at, type: 'datetime'"></datetime>
    </div>
</div>
<ul class="tweet-images-list" data-bind="template: { name: 'tweet-images-template', foreach: tweet.images }">
</ul>
<div class="tweet-images-details"></div>
<!-- /ko -->
<script type="text/html" id="tweet-images-template">

</script>

通过以下HTML调用此模板:

<tweet params="action: 'create', tweet: tweetData"></tweet>

action是我感兴趣的模板的哪个部分,tweet: tweetData是我希望我的父视图模型与我的组件进行通信的方式。 tweetData可能是具有以下属性的对象:

self.tweet = {
    tweet_id: ko.observable(),
    tweet_text: ko.observable(),
    tweet_user_name: ko.observable(),
    tweet_user_screen_name: ko.observable(),
    tweet_created_at: ko.observable(),
    tweet_parent_id: ko.observable(),
    images: ko.observableArray()
}

目前,我主要关心的是展示事物的“创造”方面。简单地显示推文数据非常简单。具体来说,我想知道我的组件上的输入/输出应该如何工作。

问题

1)如果我将一些JSON / JS对象/数据集从我的parentviewmodel传递到我的组件,为什么我需要重新定义组件中的observable?

我的意思是:

self.tweet = params.tweet;

为什么这有必要?如果我不这样做,并尝试在我的模板(或tweet.tweet_user_name)上引用params.tweet.tweet_user_name,我会收到以下错误:

ReferenceError: tweet is not defined

为什么我的模板不知道传递给组件构造函数的变量?回想一下,我的构造函数定义如下:

function TweetViewModel(params) {  //SNIP

如果我必须在我的组件上重新定义我的对象,那么对其进行的更改是否会映射回parentviewmodel上的tweetData

2)当我有多个输入源和多个输出源时,如何使它们与挖空映射保持一致,而不会对未定义的属性进行调整而不观察更改?

如果您查看我的组件,您可以看到我有两个输入源来获取推文信息。一个是来自params的数据,另一个是我向服务器发出的AJAX请求,用于从Twitter API获取数据。

这些数据源看起来完全不同。理想情况下,我希望我的“模型”看起来好像我已经在上面定义了一个'推文'对象,其中有7个可观察属性,范围从imagestweet_id。我的parentviewmodel上来自tweetData的数据看起来应该是这样的,但是当我期望创建一条推文时它可能是空的。来自我的AJAX请求的数据将是我需要格式化的twitter API的原始响应。

具体来说,我有以下情况和问题:

  • 我无法保证tweetData始终包含我首选模型的信息或大纲。

这通常发生在完全创建推文时,除了我父视图模型上的可观察对象之外不存在。我传递给我的组件的变量是我期望由我的组件上的Knockout映射填充的东西 - 我不需要传入一个已经格式化了所有属性的空对象。

  • 当我使用我收到的来自AJAX请求的数据覆盖我的params.tweet值时,它实际上不会覆盖self.tweet中的值,并且它不会更新我的数据绑定

考虑以下代码:

self.tweet.tweet_text = ko.observable("foo")

$.ajax('/myproj/create/retrievetweet/' + id, {
dataType: 'json',
type: 'GET',
success: function (response) {
    self.tweet = {
        tweet_id: ko.observable(response.id),
        tweet_text: ko.observable(response.text),
        tweet_user_name: ko.observable(response.user.name),
        tweet_user_screen_name: ko.observable(response.user.screen_name),
        tweet_created_at: ko.observable(response.created_at),
        tweet_parent_id: ko.observable(response.in_reply_to_status_id),
        images: ko.observableArray(response.entities.media)
    }
}
});

这个成功回调实际上不会因为某些原因在我的模板上设置任何内容。我绑定到tweet.tweet_text的文本绑定仍会显示“foo”,即使在我收到有效的响应之后也应该按照我上面的定义重置它。

使用敲除映射与来自服务器的响应更令人厌恶。没有办法将我要观察的属性列入白名单,我只能通过ignore选项设置列入黑名单 - 考虑Twitter返回多少数据,这是手动忽略的很多属性!此外,Twitter返回的属性名称与我尝试使用的属性名称不匹配(见上文),所以我甚至不确定淘汰映射是否适合这里。

3)我是如何错误地使用Knockout.js + mapping +组件的?我试图创建的组件的起始结构是什么?

我在这里结束了我的智慧,我很高兴地尝试向我投掷的所有建议。理想情况下,我希望尽可能多地回答我的问题。

1 个答案:

答案 0 :(得分:1)

  1. 组件有自己的视图模型,因为通过设计它们是可重用的,并且可以在具有不同视图模型的不同应用程序中使用。如果要引用主视图模型,请将其作为参数传递。

  2. 不要重新分配包含绑定的observable的绑定的observable或变量。您可以使用普通函数调用(例如self.tweet.tweet_id(newValue))设置它们的值,但如果使用等号,则通常会抛出绑定变量。

  3. 如果你想将值列入白名单,我想你也可以创建和分配变量。 ko.mapping不会为您节省任何费用。