如何在我的react组件中重构此ASYNC调用以使其更具可读性?

时间:2019-05-26 15:37:30

标签: javascript reactjs asynchronous redux es6-promise

我希望我的组件从服务器获取对象数组。每个对象都是带有作者,正文和日期的消息。然后,我想在我的react组件中呈现这些消息。

我的react组件当前在安装前从服务器获取数据。然后它将以redux状态存储此消息列表。|

我敢肯定,有一种更好的编写此代码的方法。 1.是否可以将获取请求放置在Action或Reducer文件中? 2.我可以在组件中编写一个函数来进行异步调用吗?

import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from '../actions/actions_index.js';

class MessageList extends Component {
  constructor(props) {
    super(props)
  }

  componentWillMount() {
    fetch('https://wagon-chat.herokuapp.com/general/messages')
        .then(response => response.json(),
          error => console.log('An error occured receiving messages', error))
        .then((data) => {
          this.props.fetchMessages(data.messages);
        });
  }

  render() {
    return (
      <div className="message-list">
        {this.props.messageList.map( (message, index) => { return <Message key={index} message={message}/> })}
      </div>
    )
  }
}

function mapStateToProps(state) {
  return {
    messageList: state.messageList
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(
    { fetchMessages: fetchMessages },
    dispatch
  )
}

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

3 个答案:

答案 0 :(得分:2)

  
      
  1. 我可以将获取请求放置在Action或Reducer文件中吗?
  2.   

获取请求应放置在动作创建者中。检索到的数据将在dispatched处进行还原,以便稍后操作该数据,最后更新商店以在UI上显示。这是大多数react-redux应用程序的简单流程。

UI->动作创建者(呼叫请求,传奇等。)-> reducer->存储-> UI

  
      
  1. 我可以在组件中编写一个函数来进行异步调用吗?
  2.   

是的,这应该称为动作创建者,您可以在下面看到 actions.js 以获取更多参考。

我认为您可以安全地遵循此示例模式,此处适用大多数教程。我假设这里列出的所有文件都在同一目录中。

constant.js

const MESSAGE_FETCH__SUCCESS = 'MESSAGE/FETCH__SUCCESS'
const MESSAGE_FETCH__ERROR = 'MESSAGE/FETCH__ERROR'
export {
  MESSAGE_FETCH__SUCCESS,
  MESSAGE_FETCH__ERROR
}

actions.js

import {
  MESSAGE_FETCH__SUCCESS,
  MESSAGE_FETCH__ERROR
} from './constant';

const fetchMessageError = () => ({
  type: MESSAGE_FETCH__ERROR
})

const fetchMessageSuccess = data => ({
  type: MESSAGE_FETCH__SUCCESS,
  payload: data
})

const fetchMessages = () => {
  const data = fetch(...);

  // if error 
  if (data.error)
    fetchMessageError();
  else fetchMessageSuccess(data.data);
}

export {
  fetchMessages
}

reducers.js

import {
  MESSAGE_FETCH__SUCCESS,
  MESSAGE_FETCH__ERROR
} from './constant';

const INIT_STATE = {
  messageList: []
}

export default function( state = INIT_STATE, action ) {
  switch(action.type) {
    case MESSAGE_FETCH__SUCCESS:
      return {
        ...state,
        messageList: action.payload
      }
    case MESSAGE_FETCH__ERROR:
      // Do whatever you want here for an error case
      return {
        ...state
      }
    default:
      return state;
  }
}

index.js

请阅读我提到的评论

import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { connect } from 'react-redux';

// Actions
import { fetchMessages } from './actions';

class MessageList extends Component {
  /* If you don't do anything in the constructor, it's okay to remove calling `constructor(props)`
  */
  //constructor(props) {
  //    super(props)
  //}

  // I usually put this async call in `componentDidMount` method
  componentWillMount() {
    this.props.fetchMessage();
  }

  render() {
    return (
      <div className="message-list">
        {
          /* Each message should have an unique id so they can be used 
          for `key` index. Do not use `index` as an value to `key`. 
See this useful link for more reference: https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js
          */
          this.props.messageList.map( message => <Message key={message.id} message={message}/> )
        }
      </div>
    )
  }
}

function mapStateToProps(state) {
  return {
    messageList: state.messageList
  }
}

export default connect(mapStateToProps, {
  fetchMessages
})(MessageList);

答案 1 :(得分:1)

您可以在名为getMessages的操作中使用redux-thunk

所以: (双箭头功能是返回一个动作,请参阅redux-thunk)

const getMessages = ()=>(dispatch, getState)=>{
    fetch('https://wagon-chat.herokuapp.com/general/messages')
    .then(response => response.json(),
      error => dispatch(['error', error]))
    .then((data) => {
      dispatch(data);
    })
}

然后,您已成功将组件简化为:

componentWillMount(){
    this.props.getMessages()
}

答案 2 :(得分:0)

我认为@Duc_Hong回答了问题。

在我看来,我建议使用副作用中间件使AJAX调用更加结构化,以便我们可以处理更复杂的情况(例如,同时取消ajax请求,多个请求)并使之更可测试。

以下是使用Redux Saga的代码段

// Actions.js

const FOO_FETCH_START = 'FOO\FETCH_START'
function action(type, payload={}) {
  return {type, payload};
}
export const startFetch = () => action{FOO_FETCH_START, payload);

// reducer.js

export const foo = (state = {status: 'loading'}, action) => {
  switch (action.type) {
  case FOO_FETCH_STARTED: {
    return _.assign({}, state, {status: 'start fetching', foo: null});
  }
  case FOO_FETCH_SUCCESS: {
    return _.assign({}, state, {status: 'success', foo: action.data});
  }
  ......
  }
};
  
      
  1. 我可以将获取请求放置在Action或Reducer文件中吗?
  2.   

// Saga.js,我在这里放置了ajax调用(无论需要什么,均可获取,获取axios)

export function* fetchFoo() {
  const response = yield call(fetch, url);
  yield put({type: FOO_FETCH_SUCCESS, reponse.data});
}

// This function will be used in `rootSaga()`, it's a listener for the action FOO_FETCH_START
export function* fooSagas() {
  yield takeEvery(FOO_FETCH_START, fetchFoo);
}
  
      
  1. 我可以在组件中编写一个函数来进行异步调用吗?
  2.   

// React组件,我通过componentDidMount中的操作创建触发了获取

class Foo extends React.Component {
  componentDidMount() {
    this.props.startFetch();
  }
  render() {
    <div>
     {this.props.foo.data ? this.props.foo.data : 'Loading....'}
    <div>
  }
}
const mapStateToProps = (state) => ({foo: state.foo});
const mapDispatchToProps = { startFetch }
export default connect(mapStateToProps, mapDispatchToProps) (Foo);

// client.js,链接传奇,redux和React组件

const render = App => {
 const sagaMiddleware = createSagaMiddleware();
 const store = createStore(
    combinedReducers,
    initialState,
    composeEnhancers(applyMiddleware(sagaMiddleware))
  );
 store.runSaga(rootSaga);
 return ReactDOM.hydrate(
    <ReduxProvider store={store}>
      <BrowserRouter><AppContainer><App/></AppContainer></BrowserRouter>
    </ReduxProvider>,
    document.getElementById('root')
  );
}