以Redux状态存储数据:API对象与简化表单状态的反映

时间:2017-03-23 09:20:43

标签: javascript reactjs redux react-redux

假设我的后端有一个Report模型,它具有date属性,类型为Date。 React表单包含两个输入以进行修改:<select>包含月份,<input>表示日期为YYYY格式的年份。我不关心这一天,因为我会使用moment.utc(...).endOf('month')将所选月份的日期设置为所选月份的结束。

对于此任务,我看到两个选项:

  1. 我的Redux状态保存最终的计算date对象,该对象将发布到后端,或
  2. 状态有两个属性:monthyear然后,当Report准备发布到后端时,这两个属性将被删除并转换为{{1对象。
  3. 选项(2)看起来很脏并且需要大量的预处理:将收到的date对象破坏为不属于后端的属性,以及在构建之前构建所需的属性发布到服务器。

    但是,选项(2)允许通过组件的ReportonChange属性将React组件更清晰地绑定到状态。

    选项(1)似乎更明智,因为Redux状态是后端状态的表示,但我发现自己写了很多 hacky 逻辑:

    value

    这是一个简短的版本,删除了所有不必要的东西。现在,正如您所看到的,我已经使用uncontrolled component作为年份输入。这是因为如果我通过import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import moment from 'moment'; import { reportChange } from '../actions'; const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; class Report extends Component { constructor(props) { super(props); this.handleMonthChange = this.handleMonthChange.bind(this); } handleMonthChange(event) { const { dispatch, report } = this.props, { target: { value }} = event, date = moment.utc(report.date), year = this.yearInput.value, month = value; date.month(month).year(year).endOf('month'); dispatch(reportChange({ date: date.toDate() })); } render() { const { report } = this.props; return ( <div> <select name="month" value={moment.utc(report.date).format('M')} onChange={this.handleMonthChange}> {months.map((month, index) => (<option key={index} value={index + 1}>{month}</option>))} </select> <input name="year" value={moment.utc(report.date).format('YYYY')} // this sets initial value of the input ref={input => this.yearInput = input} // uncontrolled component /> </div> ); } static propTypes = { dispatch: PropTypes.func.isRequired, report: PropTypes.object.isRequired } } const mapStateToProps = state => ({ report: state.report }); export default connect(mapStateToProps)(Report); date属性将它绑定到Redux存储中的Report对象中的原始value={moment.utc(report.date).format('YYYY')}属性,则该组件将无法使用,它始终绑定到onChange格式,因此您无法删除任何数字 - 当您点击退格键从{{YYYY移除时,它会自动更新为0201而不是201 1}}。

    此外,由于这是一个不受控制的组件,我必须在所有重要事件中手动提取其值,例如7的{​​{1}}事件处理程序,以便在提交之前更新报表对象有适当的约会等。

    我觉得我错过了什么,应该有一个更简单的方法。非常感谢任何帮助或建议。

2 个答案:

答案 0 :(得分:1)

  

选项(1)似乎更明智,因为Redux状态是后端状态的表示

我会争辩。 Redux状态应该代表您的应用程序的状态(无论它是什么,包括未存储在后端的UI状态)。所以,选项(2)是要走的路。此外,它使组件可重用,因为它们不依赖于后端数据格式。

答案 1 :(得分:1)

我强烈反对组件状态应该密切映射相关的ReduxFlux,无论哪个)状态的想法。将视图逻辑与域逻辑混合总是会导致这种复杂性。

几乎总是A Good Thing使用方便视图代码中的视图逻辑和域逻辑代码的另一种数据格式的数据格式。并实现必要的桥接代码以将其转换为另一个(具有适当的验证和相似)。您当前使用的react-redux为此类转化提供了两个位置:mapStateToPropsmapDispatchToProps

如果你让你的视图代码(React组件)拥有它自己的状态,你将解决你提到的另一种复杂问题,即与不想要的年份字段更新作斗争。

我修改了你的代码,让它看起来像我描述的方式,但是要注意我实际上没有运行它,所以可能需要一些修复:

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import moment from 'moment';

import { reportChange } from '../actions';

const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];

class Report extends Component {
  constructor(props) {
    super(props);
    this.state = { month: props.month, year: props.year }; 
  }

  componentWillReceiveProps(nextProps) {
    this.setState({ month: nextProps.month });
  }

  handleMonthChange(month) {
    this.setState({ month });
    props.onChange(month, this.state.year);
  }

  handleYearChange(year) {
    this.setState({ year });
    props.onChange(this.state.month, year);
  }

  render() {
    return (
      <div>
        <select
          name="month"
          value={this.state.month}
          onChange={e => this.handleMonthChange(e.target.value)}
        >
          {months.map((month, index) => (<option key={index} value={index + 1}>{month}</option>))}
        </select>

        <input
          name="year"
          type="text"
          value={this.state.year}
          onChange={e => handleYearChange(e.target.value)}
        />
      </div>
    );
  }

  static propTypes = {
    onChange: PropTypes.func.isRequired,
    month: PropTypes.string.isRequired,
    year: PropTypes.string.isRequierd,
  }
}

const mapStateToProps = state => {
  const date = state.report.date;
  return {
    month: moment.utc(date).format('M'),
    year: moment.utc(date).format('YYYY'),
  };
});

const mapDispatchToProps = dispatch => ({
  onChange: (month, year) => {
    const date = date.month(month).year(year).endOf('month').toDate();
    dispatch(reportChange({ date })),
  },
});

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