Ember 2,在 API 响应中使用包含的关系时,isPending、isSettled、isFulfilled 的奇怪行为

Ember 2, Strange behaviour with isPending, isSettled, isFulfilled when using included relationships in API response

我需要一次搞清楚为什么属性喜欢

isSettled isPending isFulfilled

如果我在 API 回复中包含或不包含数据,情况会有所不同。

我在这里问这个:https://discuss.emberjs.com/t/ember-2-show-a-single-loading-message-when-the-ids-where-included-in-the-original-response/12654 这导致我出现这种奇怪的行为:

如果我在我的 API 响应中包含数据(例如:model.posts),这些属性会立即设置为 true(并且 .isPending 为 false)如果 Chrome 还在加载真实数据(也是第一次!)。

这是一个问题,因为我不知道 posts[] 是否为空,而且我不知道我可以窥探什么,因为类似的东西不会不工作:

{{#each model.posts}}
  My posts.
{{else}}
  {{#if model.posts.isPending}}
    <div>Loading...</div>
  {{else}}
    <div>Nothing to show.</div>
  {{/if}}
{{/each}}

它总是 "Nothing to show.",直到 Chrome 加载。因为 .isPending 立即 false.

此外,如果我使用 length 属性:

{{#if (eq model.posts.length 0)}}

因为起始 posts[] 数组和空数组的长度始终为 == 0。

如果我以不同的方式加载帖子,异步的,而不是旁加载的(但有数百个 HTTP 请求,我不想要)它会起作用。 Ember 认识一个 isPending...

为什么会出现这种奇怪的行为?

更新

我对 /category/1 的 API 回复:

{
  "data": {
    "id": "1",
    "type": "categories",
    "attributes": {
      "name": "Book"
    },
    "relationships": {
      "posts": {
        "data": [{
          "id": "11",
          "type": "posts"
        }, {
          "id": "14",
          "type": "posts"
        }, {
          "id": "16",
          "type": "posts"
        }]
      }
    }
  },
  "included": [{
    "id": "11",
    "type": "posts",
    "attributes": {
      "style": false,
      "comments": true
    }
  }, {
    "id": "14",
    "type": "posts",
    "attributes": {
      "style": true,
      "comments": false
    }
  }, {
    "id": "16",
    "type": "posts",
    "attributes": {
      "style": true,
      "comments": false
    }
  }]
}

因为我在 Rails 控制器中使用 include。

其他策略?

怎么办?

我唯一的可怜的小宝宝问题:

我不想要很多很多 HTTP 请求。只有一个用于类别(模型),一个(非阻塞,带有加载消息)用于在第一个模型(类别)之后的帖子...

好的,我想我需要先告诉您加载数据的不同方式。在您的示例中,您有两个模型。我知道一个叫 post,现在我们叫另一个 my-model.

现在他们之间建立了 to-many 关系。在my-model中大概是这样的:

posts: hasMany('post'),

现在显然您想要显示给定 my-model 的所有 post。为此,您可能有一条像 /my-model-posts/:myModel_id 这样的路线。一个很有趣的问题是你如何link到这条路线。这是因为对于 link 您已经需要 my-model 实例,现在的问题是您如何加载它...

我稍后会讨论这个,现在让我们假设您的用户在给定 url 时直接点击您的应用程序,因为这是您始终必须处理的 use-case。

因为命名约定 model_id 你不必自己写你的路线。但是请记住,默认实现等效于此显式路由定义:

model(params) {
  return this.store.findRecord('my-model', params.myModel_id);
}

现在后台基本上有3种响应方式:

旁加载所有内容

{
  data: {
    type: 'my-model',
    id: 'foo',
    attributes: {...},
    relationships: {
      posts: {
        data: [{
          type: 'post',
          id: '1'
        },{
          type: 'post',
          id: '2'
        }]
      }
    }
  },
  included: [{
    type: 'post',
    id: '1',
    attributes: {...}
  }, {
    type: 'post',
    id: '2',
    attributes: {...}
  }]
}

旁加载 ID

{
  data: {
    type: 'my-model',
    id: 'foo',
    attributes: {...},
    relationships: {
      posts: {
        data: [{
          type: 'post',
          id: '1'
        },{
          type: 'post',
          id: '2'
        }]
      }
    }
  }
}

使用相关的link

{
  data: {
    type: 'my-model',
    id: 'foo',
    attributes: {...},
    relationships: {
      posts: {
        links: {
          related: '/api/model/foo/posts'
        }
      }
    }
  }
}

现在您需要了解由 findRecord 编辑的承诺 return 将等待服务器的第一次响应然后解析。您的路由器 将在进入路由 之前等待此响应。理解这一点非常重要:在这个承诺解决之前,您的路线不会被输入。要指示此阶段的加载状态,您可以使用 a loading substate..

因此对于第一个示例,如果您侧载所有内容,这就足够了。请记住,当响应为 returned 时,没有更多数据要加载。此外,在加载此响应之前,您处于加载子状态。

最后一个例子也不是很难。一种简单的方法是显示两个加载微调器。你需要这个,因为你发出了两个请求并且处于两个加载状态:

  1. 用户点击路线
  2. 您提出 findRecord 请求
  3. 加载子状态一直显示到 findRecord 请求完成
  4. 响应已 returned,模板将被渲染
  5. 您访问 model.posts,这将触发相关的 link 加载。加载数据时 model.posts.isPending 将是 true。可以用一个简单的{{#if model.posts.isPending}}loading...{{/if}}来表示加载状态
  6. 响应已 returned,model.posts.isPending 现在是 false。现在已加载所有数据。

但是,如果需要,您可以通过修改将其减少到一个单一的加载状态。您可以在 afterModel hook:

中加载 posts
afterModel(model) {
  return model.get('posts');
}

这将强制执行承诺 posts 在进入路线之前加载,让您保持在 loading substate.

另一种是直接加载posts,如果你不关心my-model实例:

model(params) {
  return this.store.findRecord('my-model', params.myModel_id)
    .then(m => m.get('posts'));
}

现在 model 将成为您的 posts 数组,您将一直处于加载子状态,直到 posts 被加载。

最后但同样重要的是让我们谈谈第二个例子:你只侧载 ids。这可能是最棘手的一个。如果你只侧加载 ids,你没有单个 属性 表示 something 仍然被加载。这是因为有很多 HTTP 请求,以及很多表明某些内容仍在加载的承诺。可能唯一的 use-case 是当您真正想尽快向用户显示数据时,所以我建议 loading-spinner 每个 post:

{{#each model.posts as |post|}}
  {{#if post.isLoading}}
    loading...
  {{else}}
    ...the data...
  {{/if}}
{{else}}
  no posts
{{/each}}

请注意,如果您没有 posts,那么在您必须发出单个 HTTP 请求之前,这是您知道的事情!

如果你想要一个单一的加载微调器,你可以在 my-model:

上创建一个计算的 属性
hasLoadingPosts: Ember.computed('posts.@each.isLoading', {
  get() {
    return this.get('posts').any(post => post.get('isLoading'));
  }
})

您也可以将其保留在加载子状态中,但这有点棘手:

afterModel(model) {
  return model.get('posts')
    .then(posts => Ember.RSVP.all(posts.toArray()));
}

您可以在 model 挂钩中做类似的事情:

model(params) {
  return this.store.findRecord('my-model', params.myModel_id)
    .then(m => m.get('posts'))
    .then(posts => Ember.RSVP.all(posts.toArray()));
}

另请注意,最后两个 promise-handling JS-snippets 适用于所有场景。如果数据已经加载,它们不会造成伤害,但会等待相关的 link 以及旁加载的 ID。

现在需要注意的一件非常重要的事情是通常不会direct-link到这样的页面。可能你在某个地方有一个 my-model 或其他东西的列表,然后是一个 link-to 来显示 post。 在这里你不会碰到model钩子!你也不需要第一个请求:my-model的数据已经在商店里了!现在的问题是,当您首先加载它时 return 编辑了什么。您是否侧载了所有内容,只有 id 或使用了相关的 link?结果和上面的场景差不多!

如果您修改了 model 挂钩,您可能也应该更改 link-to,从 {{#link-to 'my-model-posts' model}} 更改为 {{#link-to 'my-model-posts' model.posts}}。这很奇特,因为如果你使用了相关的 link 路由将再次等待这个 promise 的解析。


通常我的建议是永远不要仅下载 ID。这也可能仅在您不为后端使用关系数据库时才有用。

我建议使用相关的 links 并在极少数情况下旁加载所有内容。例如,如果你直接加载 my-model 我会旁加载所有内容,并且仅针对列表 return 相关的 links.

现在只要编程,因为只有 related link,事情通常会正常工作。如果您有时需要较少的加载指示器,这很好。

此外,如果您只显示这个 post 数组,我会更改路由的语义,并在 model 挂钩的数组中直接 return this。当然,然后如上所述更改 link-tos。

但是,如果您想显示 my-model 的某些数据(如标题),可能需要尽快显示它们。此处显示标题时需要 isPending,但还没有 post。

但是,如上文所述,当您仅侧载 id 时,这两种解决方案 都会中断。