如何在一个thunk函数中处理多个调用?

时间:2017-08-14 23:04:33

标签: javascript reactjs redux redux-thunk

我不知道是否每个人都读过这个:https://medium.com/@stowball/a-dummys-guide-to-redux-and-thunk-in-react-d8904a7005d3?source=linkShare-36723b3116b2-1502668727但它基本上教你如何用redux和redux thunk处理一个 API请求。这是一个很好的指南,但我想知道如果我的React组件只有一个获取请求到服务器?像这样:

componentDidMount() {
    axios.get('http://localhost:3001/customers').then(res => {
      this.setState({
        res,
        customer: res.data
      })
    })

    axios.get('http://localhost:3001/events').then(res => {
      this.setState({
        res,
        event: res.data
      })
    })

    axios.get('http://localhost:3001/locks').then(res => {
      this.setState({
        res,
        lock: res.data
      })
    })
  }

我一直在谷歌搜索疯狂,我想我已经取得了一些进展,我的动作创建者目前看起来像这样(不知道它的100%是否正确):

const fetchData = () => async dispatch => {
  try {
    const customers = await axios.get(`${settings.hostname}/customers`)
      .then(res) => res.json()

    const events = await axios.get(`${settings.hostname}/events`)
      .then(res) => res.json()

    const locks = await axios.get(`${settings.hostname}/locks`)
      .then(res) => res.json()

    dispatch(setCustomers(customers))
    dispatch(setEvents(events))
    dispatch(setLocks(locks))
  } catch(err) => console.log('Error', err)
}

所以下一步是创建你的减速器,我刚做了一个:

export function events(state = initialState, action) {
    switch (action.type) {
        case 'EVENTS_FETCH_DATA_SUCCESS':
            return action.events;
        default:
            return state;
    }
}

这是我的问题:

我现在不知道如何处理我的组件内部。如果您按照文章(https://medium.com/@stowball/a-dummys-guide-to-redux-and-thunk-in-react-d8904a7005d3?source=linkShare-36723b3116b2-1502668727)进行操作,最终会如下:

componentDidMount() {
  this.props.fetchData('http://localhost:3001/locks')
}

    Doors.propTypes = {
  fetchData: PropTypes.func.isRequired,
  doors: PropTypes.object.isRequired
}

const mapStateToProps = state => {
  return {
    doors: state.doors
  }
}

const mapDispatchToProps = dispatch => {
  return {
    fetchData: url => dispatch(doorsFetchData(url))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Doors)

我的问题

所以我的问题是我现在应该如何处理我的组件内的多个get请求?对不起,如果这个问题看起来很懒,但我真的无法理解,我真的一直在努力。

所有帮助都是超级赞赏!!

3 个答案:

答案 0 :(得分:2)

假设您的请求不相互依赖,我建议将每个操作分开。该方法可能带来的好处:

  • 更容易调试
  • 更容易测试
  • 更接近单一责任原则

建议实施

动作:

const fetchCustomers = () => dispatch => {
  try {
    axios.get(`${settings.hostname}/customers`)
      .then(res => {
           dispatch(setCustomers(res.json()))
       })

  } catch(err) => console.log('Error', err)
}

const fetchEvents = () => dispatch => {
  try {
    axios.get(`${settings.hostname}/events`)
      .then(res => {
           dispatch(setEvents(res.json()))
       })

  } catch(err) => console.log('Error', err)
}

const fetchLocks = () => dispatch => {
  try {
    axios.get(`${settings.hostname}/locks`)
      .then(res => {
           dispatch(setLocks(res.json()))
       })
  } catch(err) => console.log('Error', err)
}

门组件摘录:

import * as actionCreators from './actions.js' 
import { bindActionCreators } from 'redux'
/*...*/
componentDidMount() {
    this.props.fetchCustomers();
    this.props.fetchEvents();
    this.props.fecthLocks();
  }

const mapStateToProps = state => {
  return {
    customers: state.doors.customers,
    events: state.doors.events,
    locks: state.doors.locks
  }
}

const mapDispatchToProps = dispatch => {
  return bindActionCreators(actionCreators, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Doors) 

编辑:添加了示例reducer并相应地采用了mapStateToProps。在此reducer实例doors中,此容器的状态为eventslockscustomers

export function events(state = initialState, action) {
    switch (action.type) {
        case 'EVENTS_FETCH_SUCCESS':
            return Object.assign({}, state, {events: action.events});
        case 'CUSTOMERS_FETCH_SUCCESS':
            return Object.assign({}, state, {customers: action.customers});
        case 'LOCKS_FETCH_SUCCESS':
            return Object.assign({}, state, {locks: action.locks});
        default:
            return state;
    }
}

现在请求并行执行,它们独立失败

答案 1 :(得分:1)

最佳做法是将状态(redux术语)分解为小块。

因此,不是拥有一个对象鸡尾酒[{event-object}, {customer-object}, {event-object},...so on]的数组(状态结构),而是每个数据都有数组,然后是:每个数据的reducer。

export default combineReducers({
  events,
  customers,
  locks,
}) 

如果我们同意这种架构,那么您的调度员,行动......将遵循这种架构。

然后:

actions.js

export function getEvents() {
   return (dispatch) => axios.get(`${settings.hostname}/events`)
      .then(res) => res.json())
      .then(events => dispatch(setEvents(events)))
 }
 // export function getCustomers ... so on

然后,在你的组件中,你需要顺序调度这三个调用,除非它们之间存在依赖关系:

import {getEvents, getCustomers, ... so on} from 'actions';
componentDidMount() {
   this.props.dispatch(getEvents())  // you can use `connect` instead of `this.props.dispatch` through `mapDisptachToProps`
   this.props.dispatch(getCustomers())
   // .. so on
}

答案 2 :(得分:1)

终极版

您的events缩减器应该更像:

export function events(state = initialState, action) {
    switch (action.type) {
        case 'EVENTS_FETCH_DATA_SUCCESS':
            return Object.assign({}, state, {events: action.events});
        default:
            return state;
    }
}

或者如果你有object spread transform,你可以这样做:

case 'EVENTS_FETCH_DATA_SUCCESS':
    return {...state, events: action.events};

你应该总是使你的reducer成为纯函数,纯函数的一个要求是它不直接修改它的输入。您需要返回一个新的状态对象,并将更改合并到其中。

进行此更改后,您将需要一个类似的减速器用于其他操作(客户,锁)。


你的thunk中也有语法错误:

1) .then(res) => res.json()应为:

.then(res => res.json())


2) catch(err) => console.log('Error', err)应为:

catch(err) {
  console.error('Error', err)
}


返回React

您已经在组件的正确轨道上了。我想你有CustomersLocks组件可以与你的Doors组件一起使用。

但为什么要将API请求分组并按顺序执行?除非每个后续请求都需要先前响应中的数据,否则您可以并行执行这些操作并减少延迟。

然后,由于你的减速器正在写回复状态,你的mapStateToProps,好......将状态映射到你的道具,你只需从你{{1}内的this.props.doors读取}} 方法。如果要在等待道具填充实际值时显示加载器,可以添加一些条件逻辑,如:

render

现在,render() { const { doors } = this.props; return ( <div className="Doors"> {doors === null ? this.renderLoader() : this.renderDoors() } </div> ); } renderLoader() { return <Loader/>; } renderDoors() { const { doors } = this.props; return doors.map(door => ( <Door key={door.id}/> )); } 呈现Doors时,您可以调用原始componentDidMount,当fetchData道具发生变化时,您的组件会自动重新呈现。每个其他组件也是如此;当它们挂载时,它们会请求新数据,当数据到达时,您的thunk会调度存储更新,并且组件的props会响应该更新而更改。


但只是为了好玩

假设您的API流程实际上非常复杂,它要求每个API请求都知道先前请求中的某些数据,例如,如果您要获取客户最喜欢的门牌,但doors端点仅返回您fetchCustomer,因此您必须致电favoriteDoorID

现在你有一个真正的用例,你的组合thunk看起来像:

fetchDoor(favoriteDoorID)

然后你会在你的一个reducers中创建一个case,监听动作类型const fetchFavoriteDoorForCustomer = (customerID) => async dispatch => { try { const customer = await axios.get(`${settings.hostname}/customer/${customerID}`) .then(res => res.json()); const doorID = customer.favoriteDoorID; const favoriteDoor = await axios.get(`${settings.hostname}/door/${doorID}`) .then(res => res.json()); dispatch(receivedFavoriteDoor(favoriteDoor)); } catch(err) { console.error('Error', err); } } 或其他类似的东西,这会将它合并到我在第一部分写的状态。

最后,需要此数据的组件将使用RECEIVED_FAVORITE_DOOR订阅商店,并使用相应的connect函数:

mapStateToProps

所以现在你可以从const mapStateToProps = state => { return { favoriteDoor: state.favoriteDoor } } 阅读,你很高兴。


希望有所帮助。