【问题标题】:Successful dispatch does not cause re-render even though state has been changed即使状态已更改,成功调度也不会导致重新渲染
【发布时间】:2016-03-26 09:55:03
【问题描述】:

尽管我的状态更新成功(通过 console.log 和 redux devtools 检查)我无法让我的视图重新渲染

您可以在https://github.com/attendanceproject/djattendance/tree/attendance-redux/ap/static/react-gulp/app查看代码

大部分代码都在脚本文件夹中,与我的问题有关的最重要部分如下。每次在 WeekBar 组件中按下上一个或下一个按钮时,我都会尝试重新渲染,以便其中的日期相应更新。

容器代码

class Attendance extends Component {
  render() {
    const { dispatch, trainee, date, events, rolls, slips, selectedEvents } = this.props
    console.log('this.props', this.props)
    return (
      <div>
        <div>
        <Trainee trainee={trainee} />
        <WeekBar  
          date={date} 
          onPrevClick={date => dispatch(prevWeek(date))}
          onNextClick={date => dispatch(nextWeek(date))}/>
        </div>
        <hr />
      </div>
    )
  }
}

Attendance.propTypes = {
  trainee: PropTypes.shape({
    name: PropTypes.string,
    id: PropTypes.number,
    active: PropTypes.bool
  }).isRequired,
  date: PropTypes.object.isRequired,
  events: PropTypes.array.isRequired,
  rolls: PropTypes.array.isRequired,
  slips: PropTypes.array.isRequired,
  selectedEvents: PropTypes.array
}

function select(state) {
  return {
    trainee: state.trainee,
    date: state.date,
    events: state.events,
    rolls: state.rolls,
    slips: state.slips,
    selectedEvents: state.selectedEvents,
  }
}

export default connect(select)(Attendance)

组件代码

export default class WeekBar extends Component {
  render() {
    console.log("render props", this.props)
    // var startdate = this.props.date.weekday(0).format('M/D/YY');
    // var enddate = this.props.date.weekday(6).format('M/D/YY');
    return (
      <div className="btn-toolbar" role="toolbar">
        <div className="controls btn-group">
          <button className="btn btn-info"><span className="glyphicon glyphicon-calendar"></span></button>
        </div>
        <div className="controls btn-group">
          <button className="btn btn-default clndr-previous-button" onClick={(e) => this.handlePrev(e)}>Prev</button>
          <div className="daterange btn btn-default disabled">
            {this.props.date.weekday(0).format('M/D/YY')} to {this.props.date.weekday(6).format('M/D/YY')}
          </div>
          <button className="btn btn-default clndr-next-button" onClick={(e) => this.handleNext(e)}>Next</button>
        </div>
      </div>
    );
  }

  handlePrev(e) {
    console.log("hello!", e)
    this.props.onPrevClick(this.props.date)
  }

  handleNext(e) {
    this.props.onNextClick(this.props.date)
  }
}

WeekBar.propTypes = {
  onPrevClick: PropTypes.func.isRequired,
  onNextClick: PropTypes.func.isRequired,
  date: PropTypes.object.isRequired,
}

reducer 代码

var date = moment()
function handleWeek(state = date, action) {
  switch (action.type) {
    case PREV_WEEK:
      console.log('PREV_WEEK')
      return Object.assign({}, state, {
        date: action.date.add(-7, 'd')
      })
    case NEXT_WEEK:
      return Object.assign({}, state, {
        date: action.date.add(7, 'd')
      })
    default:
      return state
  }
}

export default handleWeek

【问题讨论】:

    标签: javascript reactjs redux


    【解决方案1】:

    我没有仔细看,但您似乎将 Moment.js 日期用作模型的一部分。具体来说,onNextClick 调度一个动作:dispatch(nextWeek(date))。

    动作创建者只是传递 Moment.js 日期:

    export function nextWeek(date) {
        return {type: NEXT_WEEK, date}
    }
    

    最后,reducer 通过调用add改变日期对象:

    return Object.assign({}, state, {
      date: action.date.add(7, 'd') // wrong! it's mutating action.date
    })
    

    来自Moment.js add documentation:

    通过增加时间来改变原始时刻。

    然而,我们在 Redux 文档中强调 reducer 必须是纯的,并且状态绝不能改变,否则 React Redux 将看不到变化。这就是使 Redux 高效的原因,因为它只重新渲染它知道已更改的内容。

    我建议的解决方案是停止使用 Moment.js 作为您的状态的一部分。使用常规的 JavaScript Date 对象,确保永远不要改变它们,并且只在组件的 render 方法中使用 Moment.js。

    最后,传递从当前状态派生的数据是一种反模式。您当前的操作如下所示:

    {type: NEXT_WEEK, date}
    

    但是这信息太多了! reducer 已经从 state 中知道了当前日期,所以不需要传递它。

    相反,您可以在没有日期的情况下触发操作:

    {type: NEXT_WEEK}
    

    并教你的 reducer 在计算新日期时使用当前日期。

    假设您更改了代码以保持 Date 对象的状态,您可以使用普通的 JS 日期 API(虽然它不是很好,因为 Dates 也是可变的):

    // create a copy of the date object
    let newDate = new Date(state.date.getTime());
    
    // mutating here is fine: we mutate a new object
    newDate.setDate(newDate.getDate() + 7);
    
    return Object.assign({}, state, {
      date: newDate
    })
    

    或者,您可以使用一个很棒的新库 date-fns,它包含不变性:

    import addDays from 'date-fns/add_days';
    
    // ...
    
    return Object.assign({}, state, {
      date: addDays(state.date, 7) // non mutating! :D
    })
    

    如果您注意不要改变状态或操作,并且始终在数据更改时创建新对象,React Redux 将正确更新 React 组件以响应这些更改。

    【讨论】:

    • 谢谢!!!!我完全没想过要研究 moment.js 我只是认为我错误地使用了 redux
    • 我的状态中的布尔变量也有类似的问题,我尝试根据当前状态的逻辑非创建一个新的布尔对象并将其用于新状态对象,但 redux 不是不捡。关于如何不改变布尔值的任何建议?
    • 请使用您遇到问题的确切代码发布一个新问题。很难猜。此外,不需要创建“新的布尔对象”,因为布尔值是 JS 中的原始值——它们无论如何都不能被改变。
    • 没关系,我想通了,这是我犯的愚蠢错误,不过感谢您总是这么快地回复帖子!!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 2022-11-04
    • 2022-12-21
    • 2015-11-03
    • 2019-05-31
    • 2021-05-07
    • 2020-02-18
    相关资源
    最近更新 更多