如何使具有异步数据的 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}/>
{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;
这是修改后的图表。我不确定这是否是最佳实践或者是否有任何相关的缺点,但它在我的用例中有效。
状态图:
抱歉图表不够清晰。
与其说是技术问题,不如说这是一个架构问题。我正在开发具有以下结构(标准 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}/>
{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;
这是修改后的图表。我不确定这是否是最佳实践或者是否有任何相关的缺点,但它在我的用例中有效。
状态图:
抱歉图表不够清晰。