React-redux:监听状态更改以触发操作

时间:2017-12-07 03:15:53

标签: javascript reactjs redux react-redux

我的redux状态是这样的:

{
  parks: [
    {
      _id:"ad1esdad",
      fullName : "Some Name"
    },
    {
      _id:"ad1es3s",
      fullName : "Some Name2"
    }
  ],
  parkInfo: {
    id : "search Id",
    start_time : "Some Time",
    end_time : "Some Time"
  }
}

我有一个parkSelector组件,用户从中选择parkId和start_time和end_time

import React, { Component } from 'react';
import { changeParkInfo } from '../../Actions';

class ParkSelector extends Component {

  constructor(props) {
    super(props);

    this.handleApply = this.handleApply.bind(this);
    this.rederOptions = this.rederOptions.bind(this);

    this.state = {
      startDate: moment().subtract(1, 'days'),
      endDate: moment(),
      parkId : this.props.parks[0]
    };
  }

  handleApply(event) {
    this.setState({
      parkId : event.target.parkId.value
      startDate: event.target.start_time.value,
      endDate: event.target.end_time.value,
    });
    this.props.changeParkInfo(this.state.parkId,this.state.startDate,this.state.endDate);
  }


  rederOptions(){
    return _.map(this.props.parks,(park,index)=>{
      return(
        <option value={park._id} key={park._id}>{park.profile.fullName}</option>
      );
    });
  }

  render() {

    return (
      <div className="row">
        <div className="pb-4 col-sm-3">
          <form onSubmit={this.handleApply}>
          <select name="parkId" value={this.state.parkId} className="form-control input-sm">
              {this.rederOptions()}
          </select>
          <input name="start_time" type="date" />
          <input name="end_time" type="date" />
          <button type="submit">Apply</button> 
          </form>
        </div>
      </div>
    )
  }
}

function mapStateToProps(state){
  return {
    parks : state.parks
  };
}

export default connect(mapStateToProps,{ changeParkInfo })(ParkSelector);

我有另一个组件“统计信息”,需要显示与parkInfo相关的信息,这些信息将加载我的api请求。

import React, { Component } from 'react';
import StatsCard from '../../components/StatsCard';
import { getDashboardStats } from '../../Actions';

class Dashboard extends Component {

  constructor(props) {
    super(props);
  }

  render() {

    return (
      <div className="animated fadeIn">
        <div className="row">
          <StatsCard text="Revenue Collected" value={9999} cardStyle="card-success" />
          <StatsCard text="Total Checkins" value={39} cardStyle="card-info" />
          <StatsCard text="Total Checkouts" value={29} cardStyle="card-danger" />
          <StatsCard text="Passes Issued" value={119} cardStyle="card-warning" />
        </div>

      </div>
    )
  }
}

function mapStateToProps(state){
  return {
    parkInfo : state.parkInfo,
    dashboardStats : state.dashboardStats
  };
}

export default connect(mapStateToProps,{ getDashboardStats })(Dashboard);

每当getDashboardStats的redux状态发生变化时,我都需要调用dashboardStats操作(使得api调用并将结果存储在redux状态的parkInfo中)。

调用此操作的最佳方式是什么,我已尝试componentWillUpdate但它会继续无限更新。这种情况的最佳做法是什么?

4 个答案:

答案 0 :(得分:0)

目标:parkInfo redux-state的更改应提示Dashboard发送getDashboardInfo并重新呈现。 (此行为在其他组件中也类似)。

我使用babel transform-class-properties,语法略有不同。

示例:

// SomeLayout.js

import ParkSelector from 'containers/ParkSelector'
import Dashboard from 'containers/Dashboard'

const SomeLayout = () => {
  return (
    <div>
      <ParkSelector />
      <Dashboard />
    </div>
  )
}

export default SomeLayout

-

// Dashboard.js

// connect maps redux-state to *props* not state, so a new park selection
//  will not trigger this component to re-render, so no infinite loop there

@connect((store) => ({ currentParkId: store.parkInfo.id }, //decorator syntax
                     { getDashboardStats })
)
class Dashboard extends Component {
  state = {
    currentId: this.props.currentParkID,
    parkInfoFoo: '',
    parkInfoBar: ''
  }

  // using null for when no park has been selected, in which case nothing runs
  //  here.
  componentWillReceiveProps(nextProps) {

    // when Dashboard receives new id via props make API call
    // assumes you are setting initial state of id to null in your reducer
    if (nextProps.currentParkId !== null) {
      getDashboardStats(`someurl/info/${nextProps.id}`).then((data) => {

        // update state of Dashboard, triggering a re-render
        this.setState({
          currentId: nextProps.id 
          parkInfoFoo: data.foo,
          parkInfoBar: data.bar 
        })
      })
    }
  }

  render() {
    const { currentId, parkInfoFoo } = this.state

    if (currentId !== null) {
       return <span>{parkInfoFoo}</span>
    }
    return null 
  }  
}

export default Dashboard

答案 1 :(得分:0)

我认为应该是这样的:

您的组件ParkSelector发生了更改,您可以通过发送到changeParkInfo操作来触发操作。

此操作可以完成AJAX及其成功

1)您通过其他操作更新parkInfo的商店状态。

2)您发送getDashboardStats

现在,当第(2)点成功时,它将更新商店状态dashboardStats

接下来在您的信息中心中,您不应该使用parkInfo进行连接,原因是:您没有在信息中心中使用parkInfo。

在仪表板组件内部,您应该在getDashboardStats中调用操作componentDidMount(),以便在组件加载时首次加载dashboardStats。

简而言之,这个想法是当数据更改时,操作应该调用操作的外观并使状态发生变化。

您尝试的是触发一个操作,该操作通过触发另一个操作的道具来改变进入组件的状态。因此,当您在componentWillUpdate中编写组件操作时,存在无限循环。

希望这能澄清你的问题。

答案 2 :(得分:0)

如果我的理解是正确的,那么每当Dashboard更改时,您都需要进行API调用以获取parkInfo组件中的componentWillReceiveProps

此场景中正确的生命周期钩子是 componentWillReceiveProps(nextProps){ // this check makes sure that the getDashboardStats action is not getting called for other prop changes if(this.props.parkInfo !== nextProps.parkInfo){ this.props.getDashboardStats() } }

componentWillReceiveProps

另请注意,首次不会调用this.props.getDashboardStats(),因此您可能还必须拨打componentDidMount中的{{1}}。

答案 3 :(得分:0)

我遇到了类似的问题,但是找到了合适的方法。我认为问题与Redux应用程序中还原器和订户的责任通常如何解释有关。我的项目没有使用React,它仅用于Redux,但是潜在的问题是相同的。为了强调解决潜在的问题,我从一般的角度来探讨该主题,而不直接参考您的React特定代码。

问题概括::在应用中,多个操作P,Q,R可能导致状态更改C。无论该操作是什么,您都希望此状态更改C触发异步操作X。最初是由状态变化C引起的。换句话说,异步动作X与状态变化耦合,但有意与可能导致变化C的各种动作(P,Q,R)分离。这种情况在以下情况中不会发生简单的hello-todo示例,但确实发生在实际应用程序中。

天真的答案1:您无法触发其他操作,它将导致无限循环。

天真的答案2:您不能触发其他动作,reduce不能触发动作或引起任何副作用。

尽管两个简单的答案都是正确的,但它们的基本假设是错误的。第一个错误地认为动作X是同步触发的,没有任何停止条件。第二个错误地认为动作X是在减速器中触发的。

答案:

在订阅服务器(也称为渲染器)中以异步方式触发动作X。一开始听起来可能很奇怪,但事实并非如此。即使是最简单的Redux应用程序也可以做到这一点。他们听状态变化并根据变化采取行动。让我解释一下。

订户,除了呈现HTML元素外,还定义了如何响应用户行为触发操作。除了处理用户行为外,他们还可以定义在响应世界上任何其他变化时如何触发动作。用户在五秒钟后单击按钮与setTimeout在五秒钟后触发操作之间几乎没有区别。除了让订户将动作绑定到点击事件或找到GPS位置外,我们还可以让订户将动作绑定到超时事件。初始化后,可以在每次状态更改时修改这些绑定,就像我们在状态更改时如何重新呈现按钮或整个页面一样。

由setTimeout触发的动作将导致类似循环的结构。超时触发一个动作,reduce更新状态,redux调用订阅者,订阅者设置一个新的超时,超时触发动作等。但同样,由正常呈现和事件与用户行为的绑定所引起的类似循环的结构几乎没有什么不同。它们既是异步的又是预期的循环过程,它们使应用程序可以与世界进行交流并按我们的意愿进行操作。

因此,检测订户感兴趣的状态更改,并在发生更改时自由地触发操作。即使延迟为零,也可以建议使用setTimeout,只是为了使事件循环执行顺序清晰明了。

忙循环当然是一个问题,必须避免。如果动作X本身导致状态更改,并立即将其再次触发,则说明我们有一个繁忙的循环,应用将停止响应或变慢。因此,请确保触发的动作不会引起这种状态变化。

如果您想实现重复刷新机制,例如每秒更新一次计时器,则可以用相同的方式完成。但是,这种简单的重复不需要侦听状态更改。因此,在这些情况下,最好使用redux-thunk或以其他方式编写异步动作创建器。这样,代码的意图就变得更容易理解。