Ember模型计算属性延迟导致模板未渲染相关信息的问题
嘿,这个问题我之前也碰到过!核心就是异步计算的时序和模板渲染的时机没对上——模板先渲染完了,你的距离计算还在后台跑,等计算完赋值的时候,模板已经没再更新了(或者初始是空值直接没渲染那部分)。咱们先看看你代码里的几个小问题,再一步步解决:
首先,你把milesAway定义成了attr('string'),但其实它是通过异步逻辑计算出来的,不是后端返回的属性;另外,你的计算逻辑放在了googleLocation这个computed里,但这个computed没有被模板引用,而且它的异步操作完成后才给milesAway赋值,这时候模板已经渲染完了,自然看不到内容。
接下来给你几个具体的解决思路:
思路1:把milesAway改成异步计算属性
既然milesAway是异步计算的结果,直接把它定义成异步计算属性,让Ember自动处理异步状态,模板也能感知到它的变化。
修改后的模型代码大概是这样的:
import DS from 'ember-data'; import attr from 'ember-data/attr'; import { hasMany } from 'ember-data/relationships'; import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; export default DS.Model.extend({ featuredImages: hasMany('contentful-asset'), featured: attr('boolean'), name: attr('string'), slug: attr('string'), rating: attr('number'), location: attr('string'), latitude: attr('string'), longitude: attr('string'), bodyOne: attr('string'), googleMapsApi: service(), // 把milesAway改成异步计算属性 milesAway: computed('location', async function() { const userLocation = 'Hard Coded Test Address'; const endLocation = this.get('location'); const google = await this.get('googleMapsApi.google'); return new Promise((resolve, reject) => { const distanceMatrixService = new google.maps.DistanceMatrixService(); distanceMatrixService.getDistanceMatrix({ origins: [userLocation], destinations: [endLocation], travelMode: google.maps.TravelMode.DRIVING, unitSystem: google.maps.UnitSystem.IMPERIAL, avoidHighways: false, avoidTolls: false }, (response, status) => { if (status !== google.maps.DistanceMatrixStatus.OK) { this.toggleProperty('errorState'); reject(); return; } const element = response.rows[0].elements[0]; if (element.status === "ZERO_RESULTS") { this.toggleProperty('errorState'); reject(); return; } resolve(element.distance.text); }); }); }) });
然后模板里怎么用?如果是Ember 3.16+,可以用{{await}}助手来等待异步计算完成:
{{#if (await model.milesAway)}} 距离:{{await model.milesAway}} {{else}} 正在计算距离... {{/if}}
如果是旧版本,可以用{{#if model.milesAway.isPending}}来判断加载状态:
{{#if model.milesAway.isPending}} 正在计算距离... {{else if model.milesAway.isRejected}} 距离计算失败 {{else}} 距离:{{model.milesAway.value}} {{/if}}
思路2:用Tracked属性+异步方法(Ember Octane+推荐)
如果你的项目是Ember Octane及以上版本,用Tracked属性会更简洁,代码更直观:
import DS from 'ember-data'; import attr from 'ember-data/attr'; import { hasMany } from 'ember-data/relationships'; import { tracked } from '@glimmer/tracking'; import { inject as service } from '@ember/service'; import { task } from 'ember-concurrency'; // 可以用ember-concurrency处理异步任务 export default class WineryModel extends DS.Model { @hasMany('contentful-asset') featuredImages; @attr('boolean') featured; @attr('string') name; @attr('string') slug; @attr('number') rating; @attr('string') location; @attr('string') latitude; @attr('string') longitude; @attr('string') bodyOne; @service googleMapsApi; @tracked milesAway; @tracked errorState = false; // 用ember-concurrency的task来处理异步计算,自动管理状态 @task *calculateMilesAway() { this.errorState = false; const userLocation = 'Hard Coded Test Address'; const endLocation = this.location; const google = yield this.googleMapsApi.google; return new Promise((resolve, reject) => { const distanceMatrixService = new google.maps.DistanceMatrixService(); distanceMatrixService.getDistanceMatrix({ origins: [userLocation], destinations: [endLocation], travelMode: google.maps.TravelMode.DRIVING, unitSystem: google.maps.UnitSystem.IMPERIAL, avoidHighways: false, avoidTolls: false }, (response, status) => { if (status !== google.maps.DistanceMatrixStatus.OK) { this.errorState = true; reject(); return; } const element = response.rows[0].elements[0]; if (element.status === "ZERO_RESULTS") { this.errorState = true; reject(); return; } this.milesAway = element.distance.text; resolve(); }); }); } // 模型加载后自动触发计算 init() { super.init(...arguments); this.calculateMilesAway.perform(); } }
模板里就可以直接根据tracked属性的状态来渲染:
{{#if errorState}} 距离计算失败 {{else if (not milesAway)}} 正在计算距离... {{else}} 距离:{{milesAway}} {{/if}}
思路3:优化计算逻辑,避免重复计算
另外,你可以给计算属性加上依赖项,比如location,这样只有当酒庄的位置变化时,才会重新计算距离,而不是每次都执行。比如在思路1里,computed的依赖就是'location',这样就不会做无用的重复计算。
还有,把用户位置也改成可追踪的属性,比如从服务里获取,而不是硬编码,这样用户位置变化时也能重新计算。
总结一下,核心就是:
- 让
milesAway的定义符合它的异步计算属性本质,而不是普通的模型属性; - 模板里要处理加载中、成功、失败三种状态,避免因为初始值为空而不显示内容;
- 用Ember提供的异步处理工具(比如async computed、ember-concurrency)来管理异步逻辑,让代码更健壮。
内容的提问来源于stack exchange,提问作者Chris Johnson

