React / Flux和xhr /路由/缓存 [英] React/Flux and xhr/routing/caching

查看:136
本文介绍了React / Flux和xhr /路由/缓存的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这更像是你的意见是什么/我在思考这个问题时是否正确?问题。

在了解Flux的同时尽量严格,我试图找出XHR调用的位置,处理websockets /外部刺激,路由发生等等。

Trying to be as strict as possible while understanding Flux, I was trying to figure out where XHR calls are made, websockets/external stimuli handled, routing takes places, etc.

从我读过的文章,访谈和浏览facebook示例中,有几种方法可以处理这些事情。严格遵循flux,Action创建者可以执行所有XHR调用,并且可能需要 PENDING / SUCCESS / FAILURE 在请求完成之前和之后触发操作。 >
另一个来自facebook的Ian Obermiller,所有READ(GET)请求都由Stores直接处理(没有Action创建者/调度员的参与),WRITE(POST)请求由Action Creators处理通过整个操作>调度员>商店流程。

From what I read across articles, interviews and looking through facebook examples there are a few ways of handling these things. Following flux strictly, Action creators are the ones that do all the XHR calls with the possibility of a PENDING/SUCCESS/FAILURE Actions being fired before and after the request completes.
Another was, coming from facebook's Ian Obermiller, all the READ(GETs) requests are handled directly by the Stores(without involvement of an Action creator/dispatcher) and WRITE(POSTs) requests are handled by the Action Creators going through the entire action>dispatcher>store flow.

我们提出/希望坚持的一些理解/结论:


  1. 理想情况下,进出系统的任何事情都只能通过Actions进行。

  2. 离开/进入系统的异步调用将具有 PENDING / PROGRESS(想想文件上传)/ SUCCESS / FAILURE 操作。

  3. 整个应用程序中的单个调度程序。

  4. 操作>调度程序>存储调用是严格同步的,以便坚持调度无法在内部启动另一个调度以避免链接事件/动作。

  5. 商店在视图中持久存在(考虑到它的单页应用,您希望能够重复使用数据)

  1. Ideally, anything going in/out of the system happens only through Actions.
  2. Async calls leaving/entering the system will have PENDING/PROGRESS(think file uploads)/SUCCESS/FAILURE Actions.
  3. Single dispatcher across the entire App.
  4. Action>Dispatcher>Store calls are strictly synchronous to stick to the dispatches not being able to start another dispatch internally to avoid chaining events/actions.
  5. Stores are persisted across Views(considering its a single page app, you want to be able to reuse data)

我们得出一些结论的一些问题,但我并不完全满意:


  1. 如果采用Stores do Reads和Actions to Writes的方法,你如何处理多个商店可能使用单个XHR调用数据的情况?

    示例:TeamStore发出的API调用到 / api / teams / {id} ,返回类似于:

    {  
        entities: {  
            teams: [{  
                name: ...,  
                description: ...,  
                members: [1, 2, 4],  
                version: ...  
            }],  
            users: [{  
                id: 1  
                name: ...,  
                role: ...,  
                version: ...  
            },  
            {  
                id: 2  
                name: ...,  
                role: ...,  
                version: ...  
            },  
            {  
                id: 3  
                name: ...,  
                role: ...,  
                version: ...  
            }]  
        }  
    }  

理想情况下,我也是喜欢使用此API中返回的信息更新MemberStore。我们维护每个实体的版本号,这些版本在记录更新时更新,这是我们在内部使用的拒绝对陈旧数据的调用等。使用这个,我可以有一个内部逻辑,如果我作为副作用一些其他的API调用,我知道我的数据是陈旧的,我触发了对该记录的刷新。

解决方案似乎是,你需要商店触发一个动作(这将有效更新其他依赖商店)。这会使Store> View> Action to Store> Action短路,我不确定它是不是一个好主意。我们已经有一件事与商店进行他们自己的XHR调用不同步了。像这样的让步最终会开始蔓延到整个系统。

或知道其他商店并能够与他们沟通的商店。但这打破了商店没有Setters规则。

Ideally, I'd also like to update the MemberStore with the information returned in this API. We maintain a version number for every entity which is updated on updates to the record, which is what we use internally do reject calls on stale data, etc. Using this, I could have an internal logic, where if I as a side effect of some other API call, I know my data is stale, I trigger a refresh on that record.
The solution, it would seem, is that you'd need the store to trigger an action(which would effectively update the other dependent stores). This short circuits the Store>View>Action to Store>Action and I'm not sure if its a good idea. We already have one thing out of sync with Stores doing their own XHR calls. Concessions like these would start creeping into the entire system eventually.
Or Stores that are aware of other stores and be able to communicate with them. But this breaks the Stores have no Setters rule.


  1. 上述问题的一个简单解决方案就是你坚持将动作作为唯一的地方外部传入/外向刺激发生。这简化了多个商店更新的逻辑。

    但是现在,你在哪里以及如何处理缓存?我们得出结论,缓存将发生在API Utils / DAO级别。 (如果你看一下通量图)。

    但这引入了其他问题。为了更好地理解/解释我的意思,例如:

  1. A simple solution to the above problem would be that you stick to Actions being the ONLY place external incoming/outgoing stimulus happens. This simplifies the logic of multiple Stores getting updated.
    But now, where and how do you handle caching? We came to the conclusion that the caching would happen at the API Utils/DAO level. (if you look at the flux diagram).
    But this introduces other problems. To better understand/explain what I mean by example:


  • / api / teams 返回所有团队的列表,我显示所有团队的列表。

  • 点击团队链接后,我会查看其详细信息视图,该视图需要来自 / api / teams / {id} 如果Store中尚未存在。

    如果Actions处理所有XHR,View将执行类似 TeamActions.get([id])哪个 TeamDAO.get([id])。为了能够立即返回此调用(因为我们已将其缓存),DAO必须进行缓存,但也要保持集合/项之间的关系。这个逻辑在设计中已经存在于商店中。

    以下是问题:

  • /api/teams returns a list of all the teams with which I display a list of all the teams.
  • On clicking on a team's link, I go its details view which requires data from /api/teams/{id} if it isn't already present in the Store.
    If Actions handle all the XHRs, the View would do something like TeamActions.get([id]) which does TeamDAO.get([id]). To be able to return this call immediately(since we have it cached) the DAO would have to do caching but also maintain the relation between collections/items. This logic, by design, is already present in Stores.
    Here come the questions:

你是否在DAO中复制了这个逻辑店?

Do you duplicate this logic in DAOs and Stores?

如何处理涉及XHR API的验证?像重复的团队名称一样简单。

视图直接命中DAO并执行类似 TeamDAO.validateName([name])的操作,它返回一个承诺或者你做你创建一个动作?如果您创建一个Action,Store会通过哪个有效/无效流回View,考虑到它主要是瞬态数据?

How do you handle validation that involves XHR APIs? Something simple like duplicate Team names.
Views directly hit DAOs and do something like TeamDAO.validateName([name]) which returns a promise or do you do you create an Action? If you create an Action through which Store does Valid/Invalid flow back to the View considering its mostly transient data?

如何处理路由?我查看了react-router,我不确定是否喜欢它。我不一定认为迫切需要JSX提供路由映射/配置的方法。此外,显然,它使用了自己的RouteDispatcher,它依赖于单个调度程序规则。

我喜欢的解决方案来自一些博客文章/ SO答案,其中路由映射存储在RouteStore中。$
RouteStore还维护CURRENT_VIEW。 reactConConiner组件已在RouteStore中注册,并在更改时将其子视图替换为CURRENT_VIEW。当前视图通知AppContainer它们完全加载并且AppContainer触发RouteActions.pending / success / failure,可能带有一些上下文,以通知其他组件达到稳定状态,显示/隐藏繁忙/加载指示。

How do you handle Routing? I looked through react-router and I'm not sure I like it. I don't necessarily think forcing a react-ish JSX way of providing route mappings/configs are needed at all. Also, apparently, it employs a RouteDispatcher of its own, which ondoes the single dispatcher rule.
The solution I prefer came from some blog posts/SO answers where you have a the route mappings are stored in the RouteStore.
RouteStore also maintains CURRENT_VIEW. The react AppContainer component is registered with RouteStore and replaces its child views with the CURRENT_VIEW on change. Current Views inform the AppContainer when they're fully loaded and AppContainer fires RouteActions.pending/success/failure, possibly with some context, to inform other components of reaching a stable state, show/hide busy/loading indications.

如果你要设计类似于Gmail的路由,那么我无法设计干净的东西,怎么会你来弄吧? Gmail的一些观察结果我很喜欢:

Something that I have not been able to design cleanly was if you were to design routing similar to Gmail, how would you do it? Some observations of Gmail that I'm a big fan of:


  • 在页面准备加载之前,网址不会更改。它在加载时保留在当前URL上,并在加载完成后移动到新URL。这样做......

  • 如果失败,你根本不会丢失当前页面。因此,如果您正在撰写,并且发送失败,则不会丢失您的邮件(即您不会丢失当前的稳定视图/状态)。 (他们不会这样做,因为自动保存是可以的,但你明白了)你可以选择将邮件复制/粘贴到某个地方以便安全保存,直到你可以再次发送。

一些参考文献:

https://github.com/gaearon/flux-react-router-example
http://ianobermiller.com/blog/2014/09/15/react-and-flux-interview/
https://github.com/facebook/flux


推荐答案

这是我使用facebook Flux和Immutable.js的实现,我认为它可以响应很多您的顾虑基于几条经验法则:

It's my implementation using facebook Flux and Immutable.js that I think responds to many of your concerns, based on few rules of thumb :

商店


  • 商店负责通过 Immutable.Record 维护数据状态并通过维护缓存全球 Immutable.OrderedMap 通过 ids 引用记录实例。

  • 商店直接致电 WebAPIUtils 获取读取操作并触发操作用于操作。

  • RecordA FooRecordB之间的关系通过 foo_id 参数从 RecordA 实例解析,并通过诸如<$的调用检索c $ c> FooStore.get(this.foo_id)

  • 商店只展示 getters 等方法 get(id) getAll()

  • Stores are responsible for maintaining data state through Immutable.Record and maintaining cache through a global Immutable.OrderedMap referencing Record instance via ids.
  • Stores directly call WebAPIUtils for read operations and trigger actions for write operations.
  • Relationship between RecordA and FooRecordB are resolved from a RecordA instance through a foo_id params and retrieved via a call such as FooStore.get(this.foo_id)
  • Stores only expose getters methods such as get(id), getAll(), etc.

APIUTILS


  • 我使用SuperAgent 进行ajax调用。每个请求都包含在承诺

  • 我使用读取请求的地图承诺由url + params的哈希索引

  • 承诺已解决或被拒绝。

  • fooError 操作当然应包含服务器返回的验证错误的有效负载。

  • I use SuperAgent for ajax calls. Each request is wrapped in Promise
  • I use a map of read request Promise indexed by the hash of url + params
  • I trigger action through ActionCreators such as fooReceived or fooError when Promise is resolved or rejected.
  • fooError action should certainly contains payloads with validation errors returned by the server.

COMPONENTS


  • 控制器-view组件侦听商店中的更改。

  • 除了控制器视图组件之外,我的所有组件都是纯粹的,因此我使用ImmutableRenderMixin 仅重新呈现它真正需要的东西(意味着如果你打印 Perf.printWasted 时间,它应该很低,几毫秒。

  • Relay和GraphQL 尚未开源,我强制通过 props > propsType

  • 父组件应该只传递必要的道具。如果我的父组件包含一个对象,例如 var fooRecord = {foo:1,bar:2,baz:3}; (我没有使用 Immutable.Record 这里为了简化这个例子而且我的子组件需要显示 fooRecord.foo fooRecord.bar ,我传递整个 foo 对象,但只有 fooRecordFoo fooRecordBar 作为我的子组件的道具,因为其他组件可以编辑 foo.baz 值,使子组件重新渲染,而这个组件根本不需要这个值!

  • The controller-view component listen for changes in store(s).
  • All my components, other than controller-view component, are 'pure', so I use ImmutableRenderMixin to only re-render what it's really needed (meaning that if you print Perf.printWasted time, it should be very low, few ms.
  • Since Relay and GraphQL are not yet open sourced, I enforce to keep my component props as explicit as possible via propsType.
  • Parent component should only passes down the necessary props. If my parent component holds an object such as var fooRecord = { foo:1, bar: 2, baz: 3}; (I'm not using Immutable.Record here for the sake of simplicity of this example) and my child component need to display fooRecord.foo and fooRecord.bar, I do not pass the entire foo object but only fooRecordFoo and fooRecordBar as props to my child component because an other component could edit the foo.baz value, making the child component re-render while this component doesn't need at all this value !

ROUTING
- 我只是使用 ReactRouter

以下是一个基本示例:

api

apiUtils / Request.js

var request = require('superagent');

//based on http://stackoverflow.com/a/7616484/1836434
var hashUrl = function(url, params) {
    var string = url + JSON.stringify(params);
    var hash = 0, i, chr, len;
    if (string.length == 0) return hash;
    for (i = 0, len = string.length; i < len; i++) {
        chr   = string.charCodeAt(i);
        hash  = ((hash << 5) - hash) + chr;
        hash |= 0; // Convert to 32bit integer
    }
    return hash;
}

var _promises = {};

module.exports = {

    get: function(url, params) {
        var params = params || {};
        var hash = hashUrl(url, params);
        var promise = _promises[hash];
        if (promise == undefined) {
            promise = new Promise(function(resolve, reject) {
                request.get(url).query(params).end( function(err, res) {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(res);
                    }
                });
            });
            _promises[hash] = promise;
        }
        return promise;
    },

    post: function(url, data) {
        return new Promise(function(resolve, reject) {

            var req = request
                .post(url)
                .send(data)
                .end( function(err, res) {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(res);
                    }
                });

        });
    }

};

apiUtils / FooAPI.js

var Request = require('./Request');
var FooActionCreators = require('../actions/FooActionCreators');

var _endpoint = 'http://localhost:8888/api/foos/';

module.exports = {

    getAll: function() {
        FooActionCreators.receiveAllPending();
        Request.get(_endpoint).then( function(res) {
            FooActionCreators.receiveAllSuccess(res.body);
        }).catch( function(err) {
            FooActionCreators.receiveAllError(err);
        });
    },

    get: function(id) {
        FooActionCreators.receivePending();
        Request.get(_endpoint + id+'/').then( function(res) {
            FooActionCreators.receiveSuccess(res.body);
        }).catch( function(err) {
            FooActionCreators.receiveError(err);
        });
    },

    post: function(fooData) {
        FooActionCreators.savePending();
        Request.post(_endpoint, fooData).then (function(res) {
            if (res.badRequest) { //i.e response return code 400 due to validation errors for example
                FooActionCreators.saveInvalidated(res.body);
            }
            FooActionCreators.saved(res.body);
        }).catch( function(err) { //server errors
            FooActionCreators.savedError(err);
        });
    }

    //others foos relative endpoints helper methods...

};

商店

stores / BarStore.js

var assign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var Immutable = require('immutable');

var AppDispatcher = require('../dispatcher/AppDispatcher');
var ActionTypes = require('../constants/BarConstants').ActionTypes;
var BarAPI = require('../APIUtils/BarAPI')
var CHANGE_EVENT = 'change';

var _bars = Immutable.OrderedMap();

class Bar extends Immutable.Record({
    'id': undefined,
    'name': undefined,
    'description': undefined,
}) {

    isReady() {
        return this.id != undefined //usefull to know if we can display a spinner when the Bar is loading or the Bar's data if it is ready.
    }

    getBar() {
        return BarStore.get(this.bar_id);
    }
}

function _rehydrate(barId, field, value) {
    //Since _bars is an Immutable, we need to return the new Immutable map. Immutable.js is smart, if we update with the save values, the same reference is returned.
    _bars = _bars.updateIn([barId, field], function() {
        return value;
    });
}


var BarStore = assign({}, EventEmitter.prototype, {

    get: function(id) {
        if (!_bars.has(id)) {
            BarAPI.get(id);
            return new Bar(); //we return an empty Bar record for consistency
        }
        return _bars.get(id)
    },

    getAll: function() {
        return _bars.toList() //we want to get rid of keys and just keep the values
    },

    Bar: Bar,

    emitChange: function() {
        this.emit(CHANGE_EVENT);
    },

    addChangeListener: function(callback) {
        this.on(CHANGE_EVENT, callback);
    },

    removeChangeListener: function(callback) {
        this.removeListener(CHANGE_EVENT, callback);
    },

});

var _setBar = function(barData) {
    _bars = _bars.set(barData.id, new Bar(barData));
};

var _setBars = function(barList) {
    barList.forEach(function (barData) {
        _setbar(barData);
    });
};

BarStore.dispatchToken = AppDispatcher.register(function(action) {
    switch (action.type)
    {   
        case ActionTypes.BAR_LIST_RECEIVED_SUCESS:
            _setBars(action.barList);
            BarStore.emitChange();
            break;

        case ActionTypes.BAR_RECEIVED_SUCCESS:
            _setBar(action.bar);
            BarStore.emitChange();
            break;

        case ActionTypes.BAR_REHYDRATED:
            _rehydrate(
                action.barId,
                action.field,
                action.value
            );
            BarStore.emitChange();
            break;
    }
});

module.exports = BarStore;

商店/ FooStore.js

var assign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var Immutable = require('immutable');

var AppDispatcher = require('../dispatcher/AppDispatcher');
var ActionTypes = require('../constants/FooConstants').ActionTypes;
var BarStore = require('./BarStore');
var FooAPI = require('../APIUtils/FooAPI')
var CHANGE_EVENT = 'change';

var _foos = Immutable.OrderedMap();

class Foo extends Immutable.Record({
    'id': undefined,
    'bar_id': undefined, //relation to Bar record
    'baz': undefined,
}) {

    isReady() {
        return this.id != undefined;
    }

    getBar() {
        // The whole point to store an id reference to Bar
        // is to delegate the Bar retrieval to the BarStore,
        // if the BarStore does not have this Bar object in
        // its cache, the BarStore will trigger a GET request
        return BarStore.get(this.bar_id); 
    }
}

function _rehydrate(fooId, field, value) {
    _foos = _foos.updateIn([voucherId, field], function() {
        return value;
    });
}

var _setFoo = function(fooData) {
    _foos = _foos.set(fooData.id, new Foo(fooData));
};

var _setFoos = function(fooList) {
    fooList.forEach(function (foo) {
        _setFoo(foo);
    });
};

var FooStore = assign({}, EventEmitter.prototype, {

    get: function(id) {
        if (!_foos.has(id)) {
            FooAPI.get(id);
            return new Foo();
        }
        return _foos.get(id)
    },

    getAll: function() {
        if (_foos.size == 0) {
            FooAPI.getAll();
        }
        return _foos.toList()
    },

    Foo: Foo,

    emitChange: function() {
        this.emit(CHANGE_EVENT);
    },

    addChangeListener: function(callback) {
        this.on(CHANGE_EVENT, callback);
    },

    removeChangeListener: function(callback) {
        this.removeListener(CHANGE_EVENT, callback);
    },

});

FooStore.dispatchToken = AppDispatcher.register(function(action) {
    switch (action.type)
    {
        case ActionTypes.FOO_LIST_RECEIVED_SUCCESS:
            _setFoos(action.fooList);
            FooStore.emitChange();
            break;

        case ActionTypes.FOO_RECEIVED_SUCCESS:
            _setFoo(action.foo);
            FooStore.emitChange();
            break;

        case ActionTypes.FOO_REHYDRATED:
            _rehydrate(
                action.fooId,
                action.field,
                action.value
            );
            FooStore.emitChange();
            break;
    }
});

module.exports = FooStore;

组件

components / BarList.react.js (controller-view component)

components/BarList.react.js (controller-view component)

var React = require('react/addons');
var Immutable = require('immutable');

var BarListItem = require('./BarListItem.react');
var BarStore = require('../stores/BarStore');

function getStateFromStore() {
    return {
        barList: BarStore.getAll(),
    };
}

module.exports = React.createClass({

    getInitialState: function() {
        return getStateFromStore();
    },

    componentDidMount: function() {
        BarStore.addChangeListener(this._onChange);
    },

    componentWillUnmount: function() {
        BarStore.removeChangeListener(this._onChange);
    },

    render: function() {
        var barItems = this.state.barList.toJS().map(function (bar) {
            // We could pass the entire Bar object here
            // but I tend to keep the component not tightly coupled
            // with store data, the BarItem can be seen as a standalone
            // component that only need specific data
            return <BarItem
                        key={bar.get('id')}
                        id={bar.get('id')}
                        name={bar.get('name')}
                        description={bar.get('description')}/>
        });

        if (barItems.length == 0) {
            return (
                <p>Loading...</p>
            )
        }

        return (
            <div>
                {barItems}
            </div>
        )

    },

    _onChange: function() {
        this.setState(getStateFromStore();
    }

});

components / BarListItem.react.js

var React = require('react/addons');
var ImmutableRenderMixin = require('react-immutable-render-mixin')
var Immutable = require('immutable');

module.exports = React.createClass({

    mixins: [ImmutableRenderMixin],

    // I use propTypes to explicitly telling
    // what data this component need. This 
    // component is a standalone component
    // and we could have passed an entire
    // object such as {id: ..., name, ..., description, ...}
    // since we use all the datas (and when we use all the data it's
    // a better approach since we don't want to write dozens of propTypes)
    // but let's do that for the example's sake 
    propTypes: {
        id: React.PropTypes.number.isRequired,
        name: React.PropTypes.string.isRequired,
        description: React.PropTypes.string.isRequired
    }

    render: function() {

        return (
            <li>
                <p>{this.props.id}</p>
                <p>{this.props.name}</p>
                <p>{this.props.description}</p>
            </li>
        )

    }

});

components / BarDetail.react.js

var React = require('react/addons');
var ImmutableRenderMixin = require('react-immutable-render-mixin')
var Immutable = require('immutable');

var BarActionCreators = require('../actions/BarActionCreators');

module.exports = React.createClass({

    mixins: [ImmutableRenderMixin],

    propTypes: {
        id: React.PropTypes.number.isRequired,
        name: React.PropTypes.string.isRequired,
        description: React.PropTypes.string.isRequired
    },

    handleSubmit: function(event) {
        //Since we keep the Bar data up to date with user input
        //we can simply save the actual object in Store.
        //If the user goes back without saving, we could display a 
        //"Warning : item not saved" 
        BarActionCreators.save(this.props.id);
    },

    handleChange: function(event) {
        BarActionCreators.rehydrate(
            this.props.id,
            event.target.name, //the field we want to rehydrate
            event.target.value //the updated value
        );
    },

    render: function() {

        return (
            <form onSubmit={this.handleSumit}>
                <input
                    type="text"
                    name="name"
                    value={this.props.name}
                    onChange={this.handleChange}/>
                <textarea
                    name="description"
                    value={this.props.description}
                    onChange={this.handleChange}/>
                <input
                    type="submit"
                    defaultValue="Submit"/>
            </form>
        )

    },

});

components / FooList.react.js (controller-view component)

components/FooList.react.js (controller-view component)

var React = require('react/addons');

var FooStore = require('../stores/FooStore');
var BarStore = require('../stores/BarStore');

function getStateFromStore() {
    return {
        fooList: FooStore.getAll(),
    };
}


module.exports = React.createClass({

    getInitialState: function() {
        return getStateFromStore();
    },

    componentDidMount: function() {
        FooStore.addChangeListener(this._onChange);
        BarStore.addChangeListener(this._onChange);
    },

    componentWillUnmount: function() {
        FooStore.removeChangeListener(this._onChange);
        BarStore.removeChangeListener(this._onChange);
    },

    render: function() {

        if (this.state.fooList.size == 0) {
            return <p>Loading...</p>
        }

        return this.state.fooList.toJS().map(function (foo) {
            <FooListItem 
                fooId={foo.get('id')}
                fooBar={foo.getBar()}
                fooBaz={foo.get('baz')}/>
        });

    },

    _onChange: function() {
        this.setState(getStateFromStore();
    }

});

components / FooListItem.react.js

var React = require('react/addons');
var ImmutableRenderMixin = require('react-immutable-render-mixin')

var Bar = require('../stores/BarStore').Bar;

module.exports = React.createClass({

    mixins: [ImmutableRenderMixin],

    propTypes: {
        fooId: React.PropTypes.number.isRequired,
        fooBar: React.PropTypes.instanceOf(Bar).isRequired,
        fooBaz: React.PropTypes.string.isRequired
    }

    render: function() {

        //we could (should) use a component here but this answer is already too long...
        var bar = <p>Loading...</p>;

        if (bar.isReady()) {
            bar = (
                <div>
                    <p>{bar.get('name')}</p>
                    <p>{bar.get('description')}</p>
                </div>
            );
        }

        return (
            <div>
                <p>{this.props.fooId}</p>
                <p>{this.props.fooBaz}</p>
                {bar}
            </div>
        )

    },

});

让我们通过整个循环 FooList

Let's go through an entire loop for FooList:


  • 用户点击页面/ foos /通过 FooList 控制器视图组件列出Foos

  • FooList 控制器视图组件调用 FooStore.getAll()

  • _foos FooStore 中的map为空,所以 FooStore 通过执行请求FooAPI.getAll()

  • FooList controller-view组件将其自身呈现为加载状态,因为其状态.fooList.size == 0

  • User hits the page /foos/ listing the Foos via the FooListcontroller-view component
  • FooListcontroller-view component calls FooStore.getAll()
  • _foos map is empty in FooStore so FooStore performs a request via FooAPI.getAll()
  • The FooList controller-view component renders itself as loading state since its state.fooList.size == 0.

这是我们列表的实际外观:

Here's the actual look of our list :

++++++++++++++++++++++++
+                      +
+     "loading..."     +
+                      +
++++++++++++++++++++++++




  • FooAPI.getAll()请求解决和触发器 FooActionCreators.receiveAllSuccess 操作

  • FooStore 收到此操作,更新其内部状态,并发出变化。

    • FooAPI.getAll() request resolves and triggers the FooActionCreators.receiveAllSuccess action
    • FooStore receive this action, updates its internal state, and emits change.

      • FooList controller-view组件接收更改事件并更新其状态以从 FooStore <获取列表/ li>
      • this.state.fooList.size 不再是 == 0 所以该列表实际上可以呈现自己(注意我们使用 toJS()来显式获取原始javascript对象,因为 React 确实尚未正确处理非原始对象的映射。)

      • 我们将所需的道具传递给 FooListItem 组件。

      • By calling foo.getBar() we're telling to the FooStore that we want the Bar record back.

      • getBar() method of <$c $c>Foo record retrieve the Bar record through t he BarStore

      • BarStore does not have this Bar record in its _bars cache, so it triggers a request through BarAPI to retrieve it.

      • The same happens for all Foo in this.sate.fooList of FooList controller-view component

      • The page now looks something like this:

      • FooList controller-view component receive change event and update its state to get the list from the FooStore
      • this.state.fooList.size is no longer == 0 so the list can actually renders itself (note that we use toJS() to explicitly get a raw javascript object since React does not handle correctly mapping on not raw object yet).
      • We're passing needed props to the FooListItem component.
      • By calling foo.getBar() we're telling to the FooStore that we want the Bar record back.
      • getBar() method of Foo record retrieve the Bar record through the BarStore
      • BarStore does not have this Bar record in its _bars cache, so it triggers a request through BarAPI to retrieve it.
      • The same happens for all Foo in this.sate.fooList of FooList controller-view component
      • The page now looks something like this:
      
      ++++++++++++++++++++++++
      +                      +
      +  Foo1 "name1"        +
      +  Foo1 "baz1"         +
      +  Foo1 bar:           +
      +     "loading..."     +
      +                      +
      +  Foo2 "name2"        +
      +  Foo2 "baz2"         +
      +  Foo2 bar:           +
      +     "loading..."     +
      +                      +
      +  Foo3 "name3"        +
      +  Foo3 "baz3"         +
      +  Foo3 bar:           +
      +     "loading..."     +
      +                      +
      ++++++++++++++++++++++++
      

      -Now let’s say the BarAPI.get(2) (requested by Foo2) resolves before BarAPI.get(1) (request by Foo1). Since it’s asynchronous it’s totally plausible.
      - The BarAPI triggers the BAR_RECEIVED_SUCCESS’ action via theBarActionCreators.
      - The
      BarStore` responds to this action by updating its internal store and emits change. That’s the now the fun part...

      -Now let's say the BarAPI.get(2) (requested by Foo2) resolves before BarAPI.get(1) (request by Foo1). Since it's asynchronous it's totally plausible. - The BarAPI triggers the BAR_RECEIVED_SUCCESS' action via theBarActionCreators. - TheBarStore` responds to this action by updating its internal store and emits change. That's the now the fun part...


      • The FooList controller-view component responds to the BarStore change by updating its state.

      • The render method is called

      • The foo.getBar() call now retrieve a real Bar record from BarStore. Since this Bar record has been effectively retrieved, the ImmutablePureRenderMixin will compare old props with current props and determine that the Bar objects has changed ! Bingo, we could re-render the FooListItem component (a better approach here would be to create a separate FooListBarDetail component to let only this component to re-render, here we also re-rendering the Foo’s details that have not changed but for the sake of simplicity let’s just do that).

      • The page now looks like this :

      • The FooList controller-view component responds to the BarStore change by updating its state.
      • The render method is called
      • The foo.getBar() call now retrieve a real Bar record from BarStore. Since this Bar record has been effectively retrieved, the ImmutablePureRenderMixin will compare old props with current props and determine that the Bar objects has changed ! Bingo, we could re-render the FooListItem component (a better approach here would be to create a separate FooListBarDetail component to let only this component to re-render, here we also re-rendering the Foo's details that have not changed but for the sake of simplicity let's just do that).
      • The page now looks like this :
      
      ++++++++++++++++++++++++
      +                      +
      +  Foo1 "name1"        +
      +  Foo1 "baz1"         +
      +  Foo1 bar:           +
      +     "loading..."     +
      +                      +
      +  Foo2 "name2"        +
      +  Foo2 "baz2"         +
      +  Foo2 bar:           +
      +    "bar name"        +
      +    "bar description" +
      +                      +
      +  Foo3 "name3"        +
      +  Foo3 "baz3"         +
      +  Foo3 bar:           +
      +     "loading..."     +
      +                      +
      ++++++++++++++++++++++++
      

      If you want me to add more details from a non detailed part (such as action creators, constants, routing, etc., use of BarListDetail component with form, POST, etc.) just tell me in the comments :).

      If you want me to add more details from a non detailed part (such as action creators, constants, routing, etc., use of BarListDetail component with form, POST, etc.) just tell me in the comments :).

      这篇关于React / Flux和xhr /路由/缓存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆