Redux 状态的异步初始化

Asynchronous initialization of Redux state

我真的不确定如何在 redux 应用程序中处理多部分异步初始化过程。我需要异步加载一堆数据。只有每条数据加载完成后,才算是申请"ready"。我真的不确定这应该如何在 redux 中完成。

假设一个应用程序需要完成三件事才能被考虑 ready。这些先决条件是:

  1. theme 已从本地文件中检索到它的定义;
  2. userPrefs 已从本地存储中检索其设置;和
  3. api 已确认可以访问某个远程服务器。

一方面,我似乎可以简单地添加另一个减速器(app?)来监听这三个组件的动作。当它收到所有三个消息时,该应用程序是 "ready"。这似乎是 the advice here.

背后的想法

或者,我可能可以从消费者处获取数据。这种查找可能会发生很多次,但我想它可以被记忆化(尽管它仍然看起来很浪费)。

最后,我可以自己编写 combineReducers 以便根 reducer 有一个单独的状态树的 "top level" 分支(这看起来像是第一个选项的变体)或者写成一个reducer可以访问整个状态树。

我认为我可以编写应用程序的其余部分而无需考虑任何横切问题,但这似乎不是一种异常情况。我该如何处理这种情况?

利用 componentDidMount() 生命周期方法触发三个动作创建者,这三个动作创建者将依次启动动作以传播 reducer 的状态更改。

此组件生命周期方法将确保您的状态树已准备就绪。就那么简单。

此外,确保将动作创建者放在顶层/父组件中,可能 App.js 因为您希望在应用程序加载时 bootstrap 这些状态。

我通常将 reducer 分成不同的文件——每个文件表示一个特定的状态树。例如,我喜欢将认证相关的 reducer 与 UI 相关的 reducer 分开,等等。然后你需要像你提到的那样使用combineReducers

我接受了上面 James J. 的回答,因为他花时间给出了一个完全有效的答案,我认为他是一个很好的解决方案。不过,我认为我也应该包括我最终采用的解决方案。没有一个正确答案,但我不喜欢将任何业务逻辑放在视图层中的想法(感觉它不应该存在,redux 提供了其他替代方案)。

首先,这是一个无聊的顶级连接组件。唯一需要注意的是通过 operators 公开的 initializeApplication() 方法道具和 ready 状态道具(通过来自 redux 的 state.application 可用。

// components/Application.js
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import { operators, selectors } from './store'; // eslint-disable-line no-unused-vars


class Application extends React.Component {
    componentDidMount() {
        this.props.initializeApplication();
    }
    render() {
        return <div>Application ready? { this.props.application.ready }</div>;
    }
}

const mapStateToProps = state => ({
    application: state.application,
});
const mapDispatchToProps = ({
    initializeApplication: operators.initializeApplication,
});

export default connect(mapStateToProps, mapDispatchToProps)(Application);

这是应用程序的 redux 部分。它使用 a variation of the ducks 结构。在鸭子中,所有动作都通过 "operators" 公开(基本上,类型由动作包装,​​由动作创建者包装,由操作符包装)。

// store/Application/index.js
import { createReducer } from '../../Utils';
import { operators as UserPrefs } from '../UserPrefs';
import { operators as Api } from '../Api';


// TYPES:
const types = {
    INITIALIZED: 'Application/initialized'
};

// ACTION CREATORS:
const actions = {
    initialized: () => ({ type: INITIALIZED })
};

// OPERATORS:
const initializeApplication = () => dispatch =>
    Promise.all([
        dispatch(UserPrefs.initializeUserPrefs()),
        dispatch(Api.initializeApi())
    ])
    .then(() => dispatch(actions.initialized()));

const operators = {
    initializeApplication
};

// REDUCER:
const initialShape = {
    ready: false
};
const reducer = createReducer(initialShape)({
    [INITIALIZED]: (state) => ({...state, ready: true })
});

// EXPORTS
export default reducer;

export {
    operators,
    types
};

显然,此处调用的运算符必须 return 承诺仅在完成后才解析 运行。例如:

// store/UserPrefs/index.js
// ** NOT INCLUDING REDUCERS, TYPES, ETC **
const initializeUserPrefs = () => dispatch =>
    Promise.all([
        loadDefaultPrefs(),
        loadCurrentPrefs()
    ])
    .then(
        ([
            defaultPrefs,
            currentPrefs
        ]) => dispatch(actions.initialized(defaultPrefs, currentPrefs))
    );

const operators = {
    initializeInventory
};

export {
    operators
};

这里唯一有争议的是,其中一些鸭子式​​减速器模块必然 "know" 关于其他鸭子(application 知道 userPrefsapi)并且直接调度其操作符,引入紧密绑定,但由于 redux 允许分层化简器树,我不认为这会破坏游戏规则,但我明白为什么有些人也不喜欢这个解决方案。