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

qhhrdooz  于 2023-06-29  发布在  React
关注(0)|答案(5)|浏览(156)

我的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);

每当parkInfo的redux状态改变时,我需要调用getDashboardStats action(它使API调用并将结果存储在redux状态的dashboardStats中)。
什么是最好的方式来调用这个动作,我已经尝试了componentWillUpdate,但它不断更新无限。这种情况的最佳做法是什么?

rbpvctlc

rbpvctlc1#

我遇到了类似的问题,但找到了一个合适的方法。我认为这个问题与Redux应用程序中reducer和订阅者的责任通常如何解释有关。我的项目没有使用React,它只使用Redux,但潜在的问题是相同的。为了强调回答底层问题,我从一般的Angular 来处理这个问题,而不直接引用特定于React的代码。

**Problem re-cap:**在应用中,多个操作P、Q、R可能导致状态更改C。您希望此状态更改C触发一个异步操作X,而不管最初导致状态更改C的操作是什么。换句话说,异步动作X耦合到状态改变,但有意地与可能导致改变C的广泛范围的动作(P,Q,R)解耦。这种情况不会发生在简单的hello-todo示例中,但会发生在现实世界的应用程序中。
**幼稚回答1:**不能触发其他动作,会导致死循环。
**幼稚回答2:**不能触发其他动作,减速器必须不能触发动作或产生任何副作用。

虽然这两个天真的答案都是正确的,但他们的基本假设是错误的。第一个错误地假设动作X是同步触发的,没有任何停止条件。第二个错误地假设动作X是在减速器中触发的。

回答:

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

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

setTimeout触发的动作会导致类循环结构。超时触发一个动作,reducer更新状态,redux调用订阅者,订阅者设置一个新的超时,超时触发动作等。但是同样,与由正常呈现和事件与用户行为的绑定引起的循环状结构几乎没有区别。它们都是异步的和有意的循环过程,允许应用程序与世界通信并按照我们喜欢的方式行事。
因此,检测订阅者感兴趣的状态更改,并在更改发生时自由触发操作。建议使用setTimeout,即使延迟为零,也只是为了让您清楚事件循环的执行顺序。

忙碌循环当然是一个问题,必须避免。如果动作X本身导致了这种状态变化,并立即再次触发它,那么我们就有了一个忙碌的循环,应用程序将停止响应或变得迟缓。因此,确保触发的动作不会导致这样的状态改变。

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

nnt7mjpx

nnt7mjpx2#

如果我的理解是正确的,那么每当parkInfo发生变化时,您都需要进行API调用来获取Dashboard组件中的dashboardStats
在这个场景中,正确的生命周期钩子应该是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不会第一次被调用,所以你可能需要在componentDidMount中调用this.props.getDashboardStats()

bbuxkriu

bbuxkriu3#

目标: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
kh212irz

kh212irz4#

我觉得应该是这样的:
您的组件ParkSelector发生更改,您可以通过调度到changeParkInfo操作来触发该操作。
这个动作做的 AJAX 和它的成功
1)您通过另一个操作更新parkInfo的存储状态。
2)发送getDashboardStats
现在,当点(2)成功时,它将更新存储状态dashboardStats
接下来在您的 Jmeter 板中,您不应该连接parkInfo,原因是:您未在 Jmeter 板中使用parkInfo。
在 Jmeter 板组件内部,您应该在componentDidMount()中调用action getDashboardStats,以便在组件加载时首次加载dashboardStats。
简单地说,这个想法就是当数据发生变化时,action应该调用action的facade并使状态发生变化。
你所尝试的是触发一个改变状态的动作,通过触发另一个动作的 prop 进入组件,等等。因此,当您在componentWillUpdate中编写组件操作时,存在一个无限循环。
希望这能澄清你的问题。

w41d8nur

w41d8nur5#

如果你使用的是最新版本的React,但使用的是类组件而不是函数组件(例如,为了避免主要的重构)...
componentWillReceiveProps()已弃用。
您可以重写componentDidUpdate()来分派操作。
在类组件方法(如componentWillReceiveProps())中访问dispatch的一种方法是使用mapDispatchToProps

export const mapDispatchToProps = (dispatch) => ({
  onReceiveProject: (project) => dispatch(someActionUsingProject(project)),
});

export class MyComponent extends Component {
  componentDidUpdate(
    // eslint-disable-next-line no-unused-vars
    prevProps, prevState, snapshot,
  ) {
    if (this.props.project !== prevProps.project && this.props.project) {
      this.props.onReceiveProject(this.props.project);
    }
  }

  // ...
}

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

相关问题