Flux / Alt数据依赖关系,如何优雅地和惯用地处理

时间:2016-02-11 16:21:48

标签: javascript node.js reactjs flux

我使用alt作为项目的助手实现,并且无法绕过处理两个相关实体的加载商店的最佳方式。我使用sources功能和registerAsync来处理我的异步/ api调用,并使用AltContainer将它们绑定到我的视图。

我有两个由conversationId一对一关联的实体。两者都通过api调用加载:

enter image description here

我的作业商店加载了数据后,我想填充对话商店。

我使用源来加载作业存储:

module.exports = {
    fetchJobs() {
        return {
            remote() {
                return axios.get('api/platform/jobs');

            },....

似乎是 waitFor()方法的工作,但似乎在一个商店的内容需要转换或与另一个商店的内容合并时使用。我需要根据另一个数据存储的内容获取一个数据存储的内容。

一般来说,我需要:

  • 调用第三方API并将实体列表加载到商店中。
  • 当数据到达时,我需要使用上述每个属性调用另一个API并将该数据加载到另一个商店。

我天真的解决方案是引用作业存储中的对话操作,并在数据到达时发送事件。像这样:

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);
    }
}

当然,这样做违反了商店不应该发送事件的格言,因此我必须使用延迟在调度中间发送一个动作。这对我来说很有意义,因为在我走这条路的过程中,我会在我的代码中重新引入各种副作用;失去了"功能管道的美感"我应该看到通量。

此外,我的工作存储必须持有对任何依赖实体的引用,以便它可以分派适当的操作。在这里,我只有一个,但我可以想象很多。就实体之间的依赖关系而言,这似乎完全是倒退。

我想到了几种选择:

我可以在源/操作中调用 api / platform / jobs 端点来获取所有会话,只是为了获取ID。最初的方法更有效率,但这似乎更为真实,因为我失去了所有的交谈。

我也可以使用单个动作/源同时获取两者,返回{jobs:{}, conversations: in the action}(使用promises编排依赖关系)并使用它来填充两个存储。但这种方法对我来说似乎不必要地复杂化(我觉得我不应该这样做!)。

但我错过了另一种方式吗?奇怪的是,这样一个常见的用例会破坏磁通天堂的优雅和/或迫使我跳过如此多的箍。

@dougajmcdonald提出了一个类似的问题here,但也许它的措辞过于笼统,并没有得到任何牵引力:

3 个答案:

答案 0 :(得分:1)

这绝对是Flux设计中的一个缺陷,而且你是对的,这是一个非常常见的用例。我已经研究了这个问题(以及一些类似的问题)几天来在我自己的系统中更好地实现Flux,虽然肯定有选择,但大多数都有缺点。

最流行的解决方案似乎是使用容器(例如AltContainer)来抽象从apis请求数据的任务,并从商店加载到单个组件中,没有ui逻辑。此Container将负责查看商店中的信息,并确定是否需要执行其他操作才能完成这些信息。例如;

static getPropsFromStores() {
    var data = SomeStore.getState();
    if (/*test if data is incomplete */){
        SomeAction.fetchAdditionalData();
    }
    return data;
}

这很有道理。我们在Component层中有逻辑和组件,这是Flux所说的。

直到你考虑有两个安装容器要求相同信息的可能性(因此复制任何初始化提取,即模型中的对话),如果你添加第三个(或第四个,或第五个),问题只会变得更糟容器访问那些相同的商店。

对容器人群的反应是#34;只是没有多个!"这是很好的建议......如果你的要求还可以。

所以,这是我改变的解决方案,围绕着相同的概念;在整个应用程序周围(或甚至旁边)包含一个主容器/组件。与传统容器不同,此主容器/组件将将商店属性传递给其子容器。相反,它完全负责数据的完整性和完成。

这是一个示例实现;

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>;
    }
}

答案 1 :(得分:0)

我到目前为止提供的最佳解决方案(以及示例似乎遵循的解决方案)是添加一个&#34; jobsFetched&#34;对我的工作动作采取行动,并在数据到达时发送。

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?*/

    }

}

这消除了作业存储必须保持对其所有依赖对象的引用的问题,但是我仍然必须从我的商店内部调用一个动作,并且我必须引入jobsFetched动作来设置ConversationStore来获取它& #39;数据。因此,似乎我无法使用来源进行对话。

任何人都可以做得更好吗?

答案 2 :(得分:0)

我一直在关注@ gravityplanx的答案,但我不确定它能改善这种情况。

重申并放大:我真的非常喜欢从一个来源加载我的商店的alt模式。需要商店的每个组件看起来都是这样的:

export default class extends React.Component {
    componentDidMount() {
        CurrentUser.fetch();
        Jobs.fetch(); 
    };
    render() {    
        return(
          <AltContainer stores={{user:CurrentUser, jobs:Jobs}}>
               <Body/>
          </AltContainer>)
    }

}

目的一目了然是可以理解的。我可以清楚地分离问题,从而更容易测试我的行为/商店和来源。

但是从我的问题来看,这个美丽的模式在常见的用例中被打破了,我最终不得不在我的行动和商店中创建一些相当复杂的管道来协调ajax对话的调用。另一个答案似乎只是将这种复杂性转移到别处。我希望它消失。

我最后做的是完全分开商店(问题中建议的第一个解决方案)。我做了一个额外的ajax调用来获取jobId,而另一个是在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与我的组件一起使用的好方法,并丢失了所有的编排管道。现在,我认为由此产生的清晰度值得额外的后端呼叫。

我也意识到我如何喜欢它的工作方式(就符号而言),并会询问@goatslacker,或者可能会自己动手做。我希望能够在商店的exportAsync()方法中指定依赖项。我很乐意能说:

class JobConvesationStore {
    constructor() {
        this.exportAsync(source, JobStore);
    }

在JobStore拥有其数据之前,不会调用异步方法JobConversationStore。意图易于阅读,不需要复杂的动作编排。