Flux / Alt数据依赖,如何优雅和惯用地处理
我使用alt作为项目的stream量实现,并且在处理两个相关实体的加载存储的最佳方法时遇到了麻烦。 我使用源特性与registerAsync一起处理我的asynchronous/ api调用,并使用AltContainer将它们绑定到我的视图。
我有两个由conversationId相互关联的实体。 两者都通过api调用加载:
一旦我的工作商店加载数据,我想填补一个对话商店。
我使用源来加载作业存储:
module.exports = { fetchJobs() { return { remote() { return axios.get('api/platform/jobs'); },....
看起来像waitFor()方法的工作,但它似乎用于一个商店的内容需要转换或合并与另一个的内容。 我需要根据另一个数据的内容获取一个数据存储的内容。
一般而言,我需要:
- 调用第三方API并将实体列表加载到商店中。
- 当数据到达时,我需要使用上述每个属性来调用另一个API并将该数据加载到另一个存储中。
我天真的解决scheme是参考作业存储中的对话操作,并在数据到达时分派事件。 像这样的东西:
var jobActions = require('../actions/Jobs'); var conversationActions = require('../actions/Conversations'); class JobStore { constructor() { this.bindListeners({ handlefullUpdate: actions.success });... } handlefullUpdate(jobs) { this.jobs = jobs; conversationActions.fetch.defer(jobs); } }
当然,这样做违反了商店不应该派遣事件的格言,因此我必须使用推迟在派遣中间派遣一个行动。 这对我来说是有意义的,因为它似乎在走下去,我在我的代码中重新引入各种副作用; 失去了我应该看到的“function性pipe道”的美感。
此外,我的工作店必须持有任何依赖实体的参考,以便它可以派发适当的行动。 在这里,我只有一个,但我可以想象很多。 就实体之间的依赖关系而言,这似乎完全倒退了。
想到几个替代scheme:
我可以在source / action中调用api / platform / jobs端点来获取所有对话,只是为了得到id。 原来的方法更有效率,但这似乎更真实的stream动精神,我失去了所有的相声。
我也可以有一个单一的action / source来获取这两个数据,返回{jobs:{}, conversations: in the action}
(使用promises编排依赖项),并使用这两个存储。 但是这种方法对我来说似乎不必要的复杂(我觉得我不应该这样做!)。
但是我错过了另一种方式? 看起来奇怪的是,这样一个常见的用例会打破stream行天堂的优雅和/或迫使我跳过这么多的箍。
@dougajmcdonald 在这里提出了一个类似的问题,但也许这是措辞太笼统,没有得到任何牵引力:
这在Fluxdevise中肯定是一个缺陷,而且你是对的,这是一个非常常见的用例。 我一直在研究这个问题(还有一些类似的问题),为了在我自己的系统中更好地实现Flux,并且在那里肯定有select,但是大多数都有缺点。
最受欢迎的解决scheme似乎是使用容器(如AltContainer )将数据从apis请求和从商店加载到单个组件(缺lessui逻辑)的任务。 该容器将负责查看来自商店的信息并确定是否需要采取额外的行动才能使其完成。 例如;
static getPropsFromStores() { var data = SomeStore.getState(); if (/*test if data is incomplete */){ SomeAction.fetchAdditionalData(); } return data; }
这说得通。 我们在组件层中有逻辑和组件,这就是Flux所说的属于它的地方。
直到你考虑到有两个挂载的容器要求相同的信息的可能性(并且因此复制了任何初始化提取,即模型中的对话),并且如果添加第三个(或第四个或第五个)容器访问那些相同的商店。
容器对容器的反应是“只有一个!”,这是一个很好的build议…如果你的要求是可以的。
所以这里是我改变的解决scheme,围绕同一个概念; 在你的整个应用程序(甚至是旁边)中包含一个主容器/组件。 与传统容器不同,此主容器/组件不会将商店属性传递给其子项。 而是完全负责数据的完整性和完整性。
这是一个示例实现;
class JobStore { constructor() { this.bindListeners({ handleJobUpdate: jobActions.success }); }, handleJobUpdate(jobs) { this.jobs = jobs; } } class ConversationStore { constructor() { this.bindListeners({ handleJobUpdate: jobActions.success, handleConversationUpdate: conversationActions.success }); }, handleJobUpdate(jobs) { this.conversations = {}; this.tmpFetchInfo = jobs.conversation_id; this.isReady = false; }, handleConversationUpdate(conversations) { this.conversations = conversations; this.tmpFetchInfo = ''; this.isReady = true; } } class MasterDataContainer { static getPropsFromStores() { var jobData = JobStore.getState(); var conversationData = ConversationStore.getState(); if (!conversationData.isReady){ ConversationAction.fetchConversations(conversationData.tmpFetchInfo); } }, render: function(){ return <div></div>; } }
到目前为止,我所使用的最好的解决scheme(以及这些示例似乎遵循的解决scheme)是为作业操作添加“jobsFetched”操作,并在数据到达时将其分派。
var jobActions = require('../actions/Jobs'); var ConversationActions = require('../actions/Conversations'); class JobStore { constructor() { this.bindListeners({ handlefullUpdate: jobActions.success... });... } handlefullUpdate(jobs) { this.jobs = jobs; ConversationActions.jobsFetched.defer(jobs); } } class ConversationStore { constructor() { this.bindListeners({ handleJobUpdate: jobActions.jobsFetched... });... } handleJobUpdate(jobs) { /*Now kick off some other action like "fetchConversations" or do the ajax call right here?*/ } }
这消除了作业存储不得不引用所有依赖对象的问题,但是我仍然需要从存储中调用一个动作,并且必须引入作业获取动作,它设置ConversationStore以获取其数据。 所以好像我不能使用我的对话来源。
谁能做得更好?
我一直在看@ @ gravityplanx的答案,但我不确定它改善了多less情况。
重申和放大:我真的很喜欢从源码加载我的商店的ALT模式。 每个需要商店的组件都是这样的:
export default class extends React.Component { componentDidMount() { CurrentUser.fetch(); Jobs.fetch(); }; render() { return( <AltContainer stores={{user:CurrentUser, jobs:Jobs}}> <Body/> </AltContainer>) } }
一目了然的意图是可以理解的。 而且我能清楚地分辨问题,更容易testing我的行为/商店和来源。
但是,从我的问题来看,这个美丽的模式在常见的用例中被打破了,我不得不在我的行为和商店中创build一些相当复杂的pipe道,来协调对话的ajax调用。 另一个答案似乎只是将这种复杂性转移到别处。 我想要它走了。
我最终所做的就是将商店完全分开(问题中提出的第一个解决scheme)。 我做了一个额外的Ajax调用来取消conversationIdclosures作业,另一个取得JobConversation源中的对话。 像这样的东西:
axios.get(window.taxFyleApi + 'api/platform/active-jobs-pick/layerConversation?jobId=' + jobId) .then((res)=> { let job = res.data[0]; //Returns an array with only one item let qBuilder = window.layer.QueryBuilder.messages().forConversation(job.conversationId)...
我保留使用AltContainer与我的组件的好方法,并失去所有的编排pipe道。 现在,我认为最终的清晰度值得额外的后端电话。
我也意识到我是如何运作的(在符号方面),并且会问@goatslacker,或者可以自己做一下。 我想能够指定商店的exportAsync()方法中的依赖项。 我希望能够说:
class JobConvesationStore { constructor() { this.exportAsync(source, JobStore); }
asynchronous方法JobConversationStore不会被调用,直到JobStore有其数据。 意图很容易阅读,不需要复杂的动作编排。