Flux/Alt 数据依赖,如何优雅地道地处理

Posted

技术标签:

【中文标题】Flux/Alt 数据依赖,如何优雅地道地处理【英文标题】:Flux/Alt data dependency, how to handle elegantly and idiomatically 【发布时间】:2016-05-22 13:00:46 【问题描述】:

我正在使用alt 作为我的一个项目的助焊剂实现,并且我在思考处理两个相关实体的加载存储的最佳方法时遇到了麻烦。我正在使用sources 功能和 registerAsync 来处理我的 async/api 调用并使用 AltContainer 将它们绑定到我的视图。

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

一旦我的作业存储加载了数据,我想填充对话存储。

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

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

当然,这样做违反了 store 不应该派发事件的格言,所以我必须使用 defer 在派发中间派发一个动作。这对我来说是有道理的,因为似乎在沿着这条道路前进时,我在我的代码中重新引入了各种副作用;失去了我应该看到的“功能管道”的美感。

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

我想到了几个替代方案:

我可以在获取所有对话的源/操作中调用 api/platform/jobs 端点,只是为了获取 ID。最初的方法更有效,但这似乎更符合流动的精神,因为我失去了所有的串扰。

我也可以有单个动作/源来获取两者,返回jobs:, conversations: in the action(使用promises 在那里协调依赖)并使用它填充两个商店。但这种方法对我来说似乎不必要地复杂(我觉得我不应该这样做!)。

但是我错过了另一种方式吗?奇怪的是,这样一个常见的用例会破坏通量范式的优雅和/或迫使我跳过这么多圈。

@dougajmcdonald 提出了类似的问题here,但可能措辞过于笼统,没有引起任何关注:

【问题讨论】:

【参考方案1】:

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

最流行的解决方案似乎是使用容器,例如AltContainer,将来自 api 的数据请求和从商店加载到单个组件中的任务抽象出来,没有 ui-logic。该容器将负责查看来自商店的信息并确定是否需要额外的操作来完成它们。例如;

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

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

直到您考虑让两个已挂载的容器请求相同信息的可能性(并因此复制任何初始化提取,即模型中的对话),并且如果您添加第三个(或第四个或第五个)问题只会变得更糟) 容器访问这些相同的商店。

Container 人群对此的反应是“不要超过一个!”,这是一个很好的建议……如果你的要求没问题的话。

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

这是一个示例实现;

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

【讨论】:

等我头脑清醒后,我会查看您的代码。谢谢。【参考方案2】:

到目前为止,我提供的最佳解决方案(以及示例似乎遵循的解决方案)是在我的作业操作中添加“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 以获取它的 jobsFetched 操作数据。因此,我似乎无法使用来源进行对话。

谁能做得更好?

【讨论】:

【参考方案3】:

我一直在查看@gravityplanx 的答案,但不确定它在多大程度上改善了这种情况。

重申和放大:我非常非常喜欢从源加载我的商店的替代模式。每个需要 store 的组件看起来都像这样:

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


意图一目了然。而且我可以清晰地分离关注点,从而更轻松地测试我的操作/存储和来源。

但是在我的问题的常见用例中,美丽的模式被打破了,我最终不得不在我的操作和存储中创建一些相当复杂的管道来协调对话的 ajax 调用。另一个答案似乎只是将这种复杂性转移到其他地方。我希望它消失。

我最终做的是完全分离商店(问题中建议的第一个解决方案)。我进行了一个额外的 ajax 调用以从工作中获取会话 ID,并进行另一个调用以获取 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。意图很容易阅读,不需要复杂的动作编排。

【讨论】:

以上是关于Flux/Alt 数据依赖,如何优雅地道地处理的主要内容,如果未能解决你的问题,请参考以下文章

如何写出优雅又地道的Python代码?转载

不依赖 GulpBabelWebPack,还能优雅地写代码吗?

如何在uniapp中优雅地使用WebView

如何优雅地实现C++编译期多态?

依赖注入模式如何在开始时初始化静态类的变量

组件化下如何优雅进行本地调试,即aar依赖与module依赖动态切换