How to handle side effects in react/redux?

2019-07-26 16:59发布

问题:

I'm having some trouble figuring out where to stick an async side-effects handler in my react/redux application.

I'm using react-router, and all of my root (or almost root) level containers are calling dispatch and receiving updates with no problem. The complication is where async services fit into this. Here's an example:

Routes

<Route path='/' component={App}>
    <Route path='/home' component={Home} />
    <Route path='/locations' component={Locations} />
    <Route path='/something-else' component={SomethingElse} />
</Route>

Let's take a look at locations, where it's probably not a surprise that we need to fetch something:

class Locations extends React.Component<LocationsProps, void> {
    private _service: StoreService;

    constructor(props) {
        super(props);
        this._service = new StoreService();
    }

    render(): JSX.Element {
        const { status, stores, selectedStore } = this.props;
        return (
            <fieldset>
                <h1>Locations</h1>
                <StoresComponent 
                    status={status} 
                    stores={stores} 
                    selectedStore={selectedStore}
                    onFetch={this._onFetch.bind(this)}
                    onSelect={this._onStoreSelect.bind(this)} />
            </fieldset>
        );  
    }

    private _onFetch(): void {
        const { dispatch } = this.props;
        dispatch(fetchStores());

        this._service.find()
            .then(stores => dispatch(loadStores(stores)));
    }

    private _onStoreSelect(id: string): void {
        const { dispatch } = this.props;
        dispatch(selectStore(id));
    }

    static contextTypes: React.ValidationMap<any> = {
        status: React.PropTypes.string,
        stores: React.PropTypes.arrayOf(React.PropTypes.object)
    };
}

function mapStateToProps(state) {
    return {
        status: state.stores.status,
        stores: state.stores.list,
        selectedStore: state.stores.selectedStore
    };
}

export default connect(mapStateToProps)(Locations);

I have a very dumb stores component that relies on its container to do most of the work. The Locations container is mostly dumb as well, but the part that bothers me is the _onFetch() method, triggered by a button click inside of the Stores component.

onFetch() is dispatching that action, which is setting the status to "fetching", but it's also interacting with StoreService, which feels smelly. How should this be handled? So that Locations can just dispatch the action and wait for its props to be updated?

What I've considered

I have thought about moving all API interactions to the top level "app" container, but that still doesn't feel quite right. Is it possible to subscribe a "headless" listener to the application state? i.e. something that doesn't render anything, and only watches for fetch requests and fires off something like:

this._storeService.find()
    .then(stores => dispatch(loadStores(stores)));

回答1:

Use sagas to do any side effects like async, setinterval etc.