我应该如何在 Ember 个组件之间共享数据?
How should I share data between Ember components?
我的 Ember 应用有一条路线,其中包含 2 个不同的组件和一个带有 index.hbs 模板的控制器。
这是它的样子:
1) 用户可以 select 从过滤器组件的下拉列表中选择多个过滤器
2) DataGrid 是一个独立于过滤器的组件
3) 用户可以通过选中框
select DataGrid 中的多行
4) 创建自定义报告按钮触发 "sendAction" 到路由的控制器
此数据不是特定于模型的...它只是我可以制作自定义报告之前所需的临时数据。
Ember 最佳实践是 "Data Down / Actions Up",根据我的阅读,您不应该尝试从控制器访问组件。
但是,问题是 控制器中的 createCustomReport 方法需要访问过滤器组件中 selected 的所有过滤器以及所有在网格组件中检查的行。
我的第一直觉是在组件本身上设置属性 - 让它保持自己的状态 - 然后从控制器获取对组件的引用,以便在将其传递给报告函数之前获取其状态。
但显然这是一个禁忌。
这是我当前的解决方案:
每次我 select 过滤器时,都会有一个 sendAction 从组件冒泡到控制器并在控制器上设置自定义 属性。
此外,每次我 select 从网格中选择一个复选框时,另一个 sendAction 会转到组件,然后冒泡到控制器并在控制器上为 [=86] 设置自定义 属性 =]编辑网格行。
然后,当我单击 "createCustomReport" 时,在控制器中触发的方法可以访问我之前设置的属性 - 因为它们现在都在控制器上。
所以看起来像这样:
import Ember from 'ember';
export default Ember.Controller.extend({
myFirstFilter: undefined,
mySecondFilter: undefined,
actions: {
createCustomReport() {
// do something with all those component properties you've been setting
},
// These are triggered by the sendAction on the respective component
firstFilterMethod(myProperty1) {
this.set('myFirstFilter', myProperty1.name);
},
secondFilterMethod(myProperty2) {
this.set('mySecondFilter', myProperty2.name);
},
... etc...
}
});
这是我的问题
我不是直接从控制器访问组件,而是使用 "Actions Up" 原则,我在控制器上设置视图特定的属性。
来自 Sencha ExtJS 背景,其中控制器引用了他们的视图,我觉得这很奇怪。
通过不获取对组件的引用,我应该将我的控制器与其视图分离...但是由于我设置的所有属性通常都在视图上,因此控制器最终成为 与视图的耦合 比我仅获取对组件的引用还要多。
这在 Ember 中被认为是 "best practice" 还是有更好的方法让我获取所有这些独立组件的数据以触发 createCustomReport 方法?
好吧,我想我已经设法解决了我自己的问题并开始使用 Ember 做事方式。
我找到了 2 种不同的解决方案,每一种都有其优点。此外,我还创建了 2 个关于如何解决状态传播和共享组件数据的 Ember Twiddle 迷你教程。
两种解决方案都完全符合 Ember 2.6 的处理方式:无需控制器。
第一个正在使用 Ember 服务。
我建立了一个简单的电影列表,可以在这里查看:
https://ember-twiddle.com/c91e98cd255a556311417ac603ab0315
通过关注文件中的评论并查看上面的 Ember Twiddle,您所有关于如何实现它的问题都应该得到解答。
由于服务是单例,我可以将它注入到我的组件和路由中,它的唯一目的是维护其关联组件的数据。
组件如下所示:
import Ember from 'ember';
export default Ember.Component.extend({
movieService: Ember.inject.service('movie-displayer-service'),
currentSelectedMovie: '',
didInsertElement: function() {
// When the component is inserted into the DOM tree, use the model to set
// the 'currentSelectedMovie' property.
this.set('currentSelectedMovie', this.get('model').currentSelectedMovie);
},
actions: {
selectMovie: function(movie) {
// Instead of saving state in the component itself, let's
// save it in a service that can be consumed anywhere
// in the application.
this.get('movieService').setupCurrentSelectedMovie(movie);
// When the movie changes, we can override the 'currentSelectedMovie' property
// that is being populated with the
this.set('currentSelectedMovie', movie);
}
}
});
服务如下所示:
import Ember from 'ember';
export default Ember.Service.extend({
currentSelectedMovie: undefined,
setupCurrentSelectedMovie: function(movie) {
this.set('currentSelectedMovie', movie);
},
showSelectedMovie: function() {
if (this.get('currentSelectedMovie')) {
alert("The current selected movie of the movie-displayer component is: \n" + this.get('currentSelectedMovie'));
} else {
alert('Please Select a Movie First');
}
}
});
这是组件的车把文件:
<div class="movie-list-container">
<h4 class="movie-list-title">Movie List</h4>
<ul>
{{#each model.movies as |movie|}}
{{!-- 'eq' is a helper function that I made
to compare two values. You can check it out in
the 'helpers' folder.
--}}
<li class="{{if (eq movie currentSelectedMovie) "selected" "not-selected"}}" {{action 'selectMovie' movie}}>{{movie}}</li>
{{/each}}
</ul>
</div>
路线如下:
import Ember from 'ember';
export default Ember.Route.extend({
movieService: Ember.inject.service('movie-displayer-service'),
model: function() {
return {
currentSelectedMovie: this.get('movieService').currentSelectedMovie,
movies: ['Captain America: Civil War', 'Guardians of the Galaxy', 'Ant Man']
}
},
actions: {
showServiceState: function() {
this.get('movieService').showSelectedMovie();
}
}
});
服务解决方案的优点:
作为单例,我可以在应用程序的任何位置访问该组件的数据。
服务解决方案的缺点:
我必须将它注入到我想要使用它的每个文件中 - 从而在我进行时创建依赖项。另一种解决方案是使用 Ember Initializer class,它会在应用程序启动时自动将其注入路由、控制器或组件。当然,这意味着它会进入 它所注入的每个 实例,这可能是过大的杀伤力。
第二种解决方案在没有服务的情况下将状态从组件发送到路由器
第二个 Ember Twiddle 是一个简单的餐厅列表,展示了如何在不需要服务的情况下传播状态:
https://ember-twiddle.com/dffc679fb96434ba6698161ba7617d15
组件的车把文件如下:
<div class="restaurant-list-container">
<ul>
{{#each model as |restaurant|}}
<li class="{{if (eq currentlySelectedRestaurant restaurant ) 'selected' 'not-selected' }}" {{action 'selectRestaurant' restaurant}}>{{restaurant}}</li>
{{/each}}
</ul>
</div>
这是路由文件:
import Ember from 'ember';
export default Ember.Route.extend({
// Properties Here
currentlySelectedRestaurant: 'Please Select a Restaurant',
model: function() {
return ['Taco Bell', 'McDonalds', 'Dennys']
},
actions: {
setupRestaurantState : function(restaurant) {
this.set('currentlySelectedRestaurant', restaurant);
},
getComponentState: function() {
alert(this.get('currentlySelectedRestaurant'));
}
}
});
这里是组件文件:
import Ember from 'ember';
export default Ember.Component.extend({
currentlySelectedRestaurant: undefined,
actions: {
selectRestaurant: function(restaurant) {
// The 'sendAction' method is where the magic happens.
// A method called 'stateSetter' references a function
// that lives either on the controller or the route.
// This was setup when the component was instantiated in the
// fancy-restaurants.hbs file.
this.sendAction('stateSetter', restaurant);
this.set('currentlySelectedRestaurant', restaurant);
}
}
});
注意路由包含未定义的状态属性:'currentlySelectedRestaurant'。
这很容易是一个具有多个属性的对象或一个数组。
您也可以使用像 "componentState" 这样的通用名称,并存储您选择从任何组件发送的任何内容:例如在过滤列表中选中的选项或从网格中选择的项目。
不使用服务的优点:
做起来更容易。只需在您的组件中使用 sendAction() 来冒泡到路由器。并且没有创建额外的文件或任何依赖项。
不使用服务的缺点
由于模型数据从路由级别向下流动,如果您更改路由,您将无法访问状态。
每个解决方案都是可行的,所以我会留给您找出最有效的解决方案。
此外,我还没有将此标记为答案,因为其他人可能有更好的解决方案,如果能得到一些反馈会很好。
我的 Ember 应用有一条路线,其中包含 2 个不同的组件和一个带有 index.hbs 模板的控制器。
这是它的样子:
1) 用户可以 select 从过滤器组件的下拉列表中选择多个过滤器
2) DataGrid 是一个独立于过滤器的组件
3) 用户可以通过选中框
select DataGrid 中的多行4) 创建自定义报告按钮触发 "sendAction" 到路由的控制器
此数据不是特定于模型的...它只是我可以制作自定义报告之前所需的临时数据。
Ember 最佳实践是 "Data Down / Actions Up",根据我的阅读,您不应该尝试从控制器访问组件。
但是,问题是 控制器中的 createCustomReport 方法需要访问过滤器组件中 selected 的所有过滤器以及所有在网格组件中检查的行。
我的第一直觉是在组件本身上设置属性 - 让它保持自己的状态 - 然后从控制器获取对组件的引用,以便在将其传递给报告函数之前获取其状态。
但显然这是一个禁忌。
这是我当前的解决方案:
每次我 select 过滤器时,都会有一个 sendAction 从组件冒泡到控制器并在控制器上设置自定义 属性。
此外,每次我 select 从网格中选择一个复选框时,另一个 sendAction 会转到组件,然后冒泡到控制器并在控制器上为 [=86] 设置自定义 属性 =]编辑网格行。
然后,当我单击 "createCustomReport" 时,在控制器中触发的方法可以访问我之前设置的属性 - 因为它们现在都在控制器上。
所以看起来像这样:
import Ember from 'ember';
export default Ember.Controller.extend({
myFirstFilter: undefined,
mySecondFilter: undefined,
actions: {
createCustomReport() {
// do something with all those component properties you've been setting
},
// These are triggered by the sendAction on the respective component
firstFilterMethod(myProperty1) {
this.set('myFirstFilter', myProperty1.name);
},
secondFilterMethod(myProperty2) {
this.set('mySecondFilter', myProperty2.name);
},
... etc...
}
});
这是我的问题
我不是直接从控制器访问组件,而是使用 "Actions Up" 原则,我在控制器上设置视图特定的属性。
来自 Sencha ExtJS 背景,其中控制器引用了他们的视图,我觉得这很奇怪。
通过不获取对组件的引用,我应该将我的控制器与其视图分离...但是由于我设置的所有属性通常都在视图上,因此控制器最终成为 与视图的耦合 比我仅获取对组件的引用还要多。
这在 Ember 中被认为是 "best practice" 还是有更好的方法让我获取所有这些独立组件的数据以触发 createCustomReport 方法?
好吧,我想我已经设法解决了我自己的问题并开始使用 Ember 做事方式。
我找到了 2 种不同的解决方案,每一种都有其优点。此外,我还创建了 2 个关于如何解决状态传播和共享组件数据的 Ember Twiddle 迷你教程。
两种解决方案都完全符合 Ember 2.6 的处理方式:无需控制器。
第一个正在使用 Ember 服务。
我建立了一个简单的电影列表,可以在这里查看: https://ember-twiddle.com/c91e98cd255a556311417ac603ab0315
通过关注文件中的评论并查看上面的 Ember Twiddle,您所有关于如何实现它的问题都应该得到解答。
由于服务是单例,我可以将它注入到我的组件和路由中,它的唯一目的是维护其关联组件的数据。
组件如下所示:
import Ember from 'ember';
export default Ember.Component.extend({
movieService: Ember.inject.service('movie-displayer-service'),
currentSelectedMovie: '',
didInsertElement: function() {
// When the component is inserted into the DOM tree, use the model to set
// the 'currentSelectedMovie' property.
this.set('currentSelectedMovie', this.get('model').currentSelectedMovie);
},
actions: {
selectMovie: function(movie) {
// Instead of saving state in the component itself, let's
// save it in a service that can be consumed anywhere
// in the application.
this.get('movieService').setupCurrentSelectedMovie(movie);
// When the movie changes, we can override the 'currentSelectedMovie' property
// that is being populated with the
this.set('currentSelectedMovie', movie);
}
}
});
服务如下所示:
import Ember from 'ember';
export default Ember.Service.extend({
currentSelectedMovie: undefined,
setupCurrentSelectedMovie: function(movie) {
this.set('currentSelectedMovie', movie);
},
showSelectedMovie: function() {
if (this.get('currentSelectedMovie')) {
alert("The current selected movie of the movie-displayer component is: \n" + this.get('currentSelectedMovie'));
} else {
alert('Please Select a Movie First');
}
}
});
这是组件的车把文件:
<div class="movie-list-container">
<h4 class="movie-list-title">Movie List</h4>
<ul>
{{#each model.movies as |movie|}}
{{!-- 'eq' is a helper function that I made
to compare two values. You can check it out in
the 'helpers' folder.
--}}
<li class="{{if (eq movie currentSelectedMovie) "selected" "not-selected"}}" {{action 'selectMovie' movie}}>{{movie}}</li>
{{/each}}
</ul>
</div>
路线如下:
import Ember from 'ember';
export default Ember.Route.extend({
movieService: Ember.inject.service('movie-displayer-service'),
model: function() {
return {
currentSelectedMovie: this.get('movieService').currentSelectedMovie,
movies: ['Captain America: Civil War', 'Guardians of the Galaxy', 'Ant Man']
}
},
actions: {
showServiceState: function() {
this.get('movieService').showSelectedMovie();
}
}
});
服务解决方案的优点:
作为单例,我可以在应用程序的任何位置访问该组件的数据。
服务解决方案的缺点:
我必须将它注入到我想要使用它的每个文件中 - 从而在我进行时创建依赖项。另一种解决方案是使用 Ember Initializer class,它会在应用程序启动时自动将其注入路由、控制器或组件。当然,这意味着它会进入 它所注入的每个 实例,这可能是过大的杀伤力。
第二种解决方案在没有服务的情况下将状态从组件发送到路由器
第二个 Ember Twiddle 是一个简单的餐厅列表,展示了如何在不需要服务的情况下传播状态:
https://ember-twiddle.com/dffc679fb96434ba6698161ba7617d15
组件的车把文件如下:
<div class="restaurant-list-container">
<ul>
{{#each model as |restaurant|}}
<li class="{{if (eq currentlySelectedRestaurant restaurant ) 'selected' 'not-selected' }}" {{action 'selectRestaurant' restaurant}}>{{restaurant}}</li>
{{/each}}
</ul>
</div>
这是路由文件:
import Ember from 'ember';
export default Ember.Route.extend({
// Properties Here
currentlySelectedRestaurant: 'Please Select a Restaurant',
model: function() {
return ['Taco Bell', 'McDonalds', 'Dennys']
},
actions: {
setupRestaurantState : function(restaurant) {
this.set('currentlySelectedRestaurant', restaurant);
},
getComponentState: function() {
alert(this.get('currentlySelectedRestaurant'));
}
}
});
这里是组件文件:
import Ember from 'ember';
export default Ember.Component.extend({
currentlySelectedRestaurant: undefined,
actions: {
selectRestaurant: function(restaurant) {
// The 'sendAction' method is where the magic happens.
// A method called 'stateSetter' references a function
// that lives either on the controller or the route.
// This was setup when the component was instantiated in the
// fancy-restaurants.hbs file.
this.sendAction('stateSetter', restaurant);
this.set('currentlySelectedRestaurant', restaurant);
}
}
});
注意路由包含未定义的状态属性:'currentlySelectedRestaurant'。
这很容易是一个具有多个属性的对象或一个数组。
您也可以使用像 "componentState" 这样的通用名称,并存储您选择从任何组件发送的任何内容:例如在过滤列表中选中的选项或从网格中选择的项目。
不使用服务的优点:
做起来更容易。只需在您的组件中使用 sendAction() 来冒泡到路由器。并且没有创建额外的文件或任何依赖项。
不使用服务的缺点
由于模型数据从路由级别向下流动,如果您更改路由,您将无法访问状态。
每个解决方案都是可行的,所以我会留给您找出最有效的解决方案。
此外,我还没有将此标记为答案,因为其他人可能有更好的解决方案,如果能得到一些反馈会很好。