如何使具有异步数据的 React 组件可在 Redux 流程中重用?

How to make a react component with Async data re-usable in Redux flow?

与其说是技术问题,不如说这是一个架构问题。我正在开发具有以下结构(标准 Redux)的应用程序。

UserFormContainer->UserForm->(Fields, RoleField, Group Field)。现在,除了 Role 和 Group 之外,我的所有字段都是哑组件,它们依赖于要填充的服务器数据。根据标准的 redux,我的状态是单向流,所有数据都通过主容器,哑组件只分派动作。

我的问题是,由于角色和组字段依赖于它的数据父组件,我该如何使它们可重用?最佳做法是什么?

这是我的基本组件的一种流程。

RoleField 应该是哑组件,它从 UserForm 组件获取数据。 UserForm 应该从商店获取数据并将其作为道具传递给 RoleField。这样 RoleField 将是可重用的,因为它们可以显示从父组件传递给它们的数据。

UDPATE

您还可以制作 UserForm 一个可以接受自定义表单字段作为子项的包装器容器。请参阅下面的示例代码

render() {
  <UserForm>
    <RoleField customData={ dataFromStore }/>
  </UserForm>
}

@Deividas Karžinauskas 的回答在你有几个嵌套组件时非常有意义,我认为这是在这种情况下进行的合理方式,但由于我希望我的组件完全独立于任何其他组件,但是可测试且不包含任何数据逻辑我只是想出了稍微不同的方法。当然,Deividas Karžinauskas 的回答很有帮助。

为了让我的组件完全独立,我只是把它变成了另一个容器,所有的副作用都由 action/action 创建者处理,它仍然是可测试的,而数据是由它自己的独立减速器计算的。然后我使用 redux 将状态作为道具传递。

代码示例

import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';

class RolesField extends React.Component {

    componentWillMount() {
        //if not fetched then fetch
        this.props.fetchRoles();
    }

    roles() {
        let {roles} = this.props;
        if (roles.length > 0) {
            const {input, label, placeholder, type, meta: {touched, error, warning}} = this.props;
            let rolesUi = roles.map((role, index) => {
                return (
                    <div className="checkbox" key={index}>
                        <label htmlFor={input.name}>
                            <input {...input} placeholder={placeholder} type={type}/>
                            &nbsp;{role.name} <a href="#">( Show Permissions )</a>
                        </label>
                    </div>
                );
            });

            return rolesUi;
        }
    }


    render() {

        const {input, label, placeholder, type, meta: {touched, error, warning}} = this.props;

        return (
            <div className="form-group">
                <label className="col-sm-2 control-label" htmlFor={input.name}>
                    Administrative Roles
                </label>
                <div className="col-sm-10">
                    {/*This section should be loaded from server,
                     the component should dispatch the event and role action should load the roles.*/}
                    {this.roles()}
                </div>
            </div>
        );
    }

}

//map state to props.
function mapStateToProps(state) {
    return {
        roles: state.roles.roles
    }
}

function mapDispatchToProps(dispatch) {
    let actionCreators = {
        fetchRoles
    };

    return bindActionCreators(actionCreators, dispatch);

}

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

/*Action Types*/
const ROLE_FETCH_START = 'ROLE_FETCH_START';
const ROLE_FETCH_COMPLETE = 'ROLE_FETCH_COMPLETE';
/*End Action Types*/

/**
 * Action Creator
 * @returns {{type: string, payload: [*,*,*,*]}}
 */
function fetchRoles() {
    const roles = [
        {'id': 1, 'name': 'Super Admin'},
        {'id': 2, 'name': 'Admin'},
        {'id': 3, 'name': 'Manager'},
        {'id': 4, 'name': 'Technical Lead'}
    ];
    /*Action*/
    return {
        type: ROLE_FETCH_COMPLETE,
        payload: roles
    };
}
/**
 * Roles Reducer
 * @param state
 * @param action
 * @returns {*}
 */
export function rolesReducer(state = {roles: [], fetched: false}, action) {
    switch (action.type) {
        case ROLE_FETCH_COMPLETE :
            return {
                ...state,
                fetched: true,
                roles: action.payload
            };
            break;
    }

    return state;
}

re-use redux-form 中的组件我做了

import React from 'react';
import {Field, reduxForm} from 'redux-form';

import {renderInputField, renderDropdownList} from '../../page/components/field.jsx';
import RolesField from '../../page/containers/fields/roles.jsx';

import Box from '../../page/components/box.jsx';
import ActionButtons from '../../page/components/action-buttons';

class UserForm extends React.Component {

    actionButtons() {
        return [
            {
                'type' : 'submit',
                'label' : 'Save',
                'className' : 'btn-primary'
            },
            {
                'type' : 'button',
                'label' : 'Cancel',
                'className' : 'btn-success',
                'onClick' : this.props.reset
            },
            {
                'type' : 'button',
                'label' : 'Delete',
                'className' : 'btn-danger'
            }
        ]
    }

    render() {
        const { handleSubmit } = this.props;
        return (
            <form className="form-horizontal" role="form" onSubmit={handleSubmit}>
                <Box title="Add New User">
                    <ActionButtons buttons = {this.actionButtons()} />
                    <Field
                        name="roles[]"
                        component={RolesField}
                        label="Technical Lead"
                        type="checkbox"
                        className="form-control" />

                </Box>

            </form>
        );
    }

}

UserForm = reduxForm({
    form: 'userForm',
    validate
})(UserForm);

export default UserForm;

这是修改后的图表。我不确定这是否是最佳实践或者是否有任何相关的缺点,但它在我的用例中有效。

状态图:

抱歉图表不够清晰。