首页 > 解决方案 > React/Redux 中的通用化简器/动作

问题描述

我正在尝试确定如何提取多条数据以在同一组件中使用。

我在 React/Redux 中看到的每个示例都请求非常具体的数据,并且有 reducer 和 action 来处理该确切类型的数据。但是,我无法找到有关处理更多通用数据的信息。

例如,我的网站上有几个不同的组件(或类别)。这些组件之一是Cards. 因此,如果用户单击其链接,/cards/hockey则应从 API 请求曲棍球数据(如果它尚未在商店中),并将其显示在卡片页面中。如果用户单击 的链接/cards/football,它应该遵循相同的过程,检查它是否有存储的数据,如果没有从 API 中提取它,并显​​示带有该数据的卡片页面。

另一种组件类型可能是stats关于不同运动队的统计数据。

我不会总是提前知道有哪些类型的卡片可用,因此我无法在我的应用程序中对特定的运动类型进行硬编码。

所以在这种情况下,我只想创建两个组件:卡片和统计信息,但要动态加载数据来填充这些组件。

现在我有太多的重复,而且是硬编码的。这意味着如果不创建新代码来处理这些类型中的每一个,我就无法在将来动态添加新类型。

例如,现在我有 /actions/footballCardActions.js 和 /actions/hockeyCardActions.js。然后我有 /reducers/footballCardReducers.js 和 /reducers/hockeyCardReducers.js。对于 Stats 组件,我可能也有类似的组件。

我还指定了诸如FETCH_HOCKEY_CARDS_SUCCESSor之类的状态FETCH_FOOTBALL_CARDS_SUCCESS

同样,这些都是硬编码的,这使得可扩展性变得困难。

我试图遵循的一个示例是https://scotch.io/tutorials/bookshop-with-react-redux-ii-async-requests-with-thunks - 但它再次使用非常具体的数据请求,而不是通用的。

我可以做些什么来使我的代码更通用地工作,这样我就不需要对特定的数据集进行硬编码。有没有处理类似情况的好教程?

更多说明

我的组件(屏幕)之一是运动卡屏幕。菜单系统(带有链接)是在从 API 加载站点时自动生成的,因此我并不总是知道哪些链接可用。所以,可能会有曲棍球、足球以及其他一些我没有想到的运动的链接。单击菜单链接时,它将调用该运动类型的 API 并在运动卡屏幕上显示数据。

基于上面的链接(和其他类似的网站),我已经弄清楚了如何在动作和减速器部分对特定运动的每个请求进行硬编码,但是如果我一般来说,我无法弄清楚如何做到这一点不知道运动提前。

根据当前答案进一步澄清

如果有人将一项名为 MuffiBall 的新运动添加到 API 数据库,我的应用程序需要能够处理它。因此,不能指望我为添加到 API 的每项新运动添加新的 JavaScript 代码。

从数据库中检索到的所有运动卡都遵循相同的结构。

我当前代码的概述

index.js

//index.js
//Other imports here (not shown)
import Cards from './components/CardsPage'
import * as cardActions from './actions/cardActions';
import * as statsActions from './actions/statsActions';

import configureStore from './store/configureStore';

const store = configureStore();

/* Bad place to put these, and currently I am expected to know what every sport is*/
store.dispatch(hockeyActions.fetchHockey());
store.dispatch(footballActions.fetchFootball());
store.dispatch(muffiballActions.fetchMuffiball());


render(
  <Provider store={store}>
          <Router>
                <div>

                    /* Navigation menu here (not shown) */
                    /* Currently it is manually coded, */
                    /* but I will be automatically generating it based on API */

                      <Route exact path="/" component={Home} />
                      <Route path="/about" component={About} />
                      <Route path="/cards/:val" component={Cards} />
                      <Route path="/stats/:val" component={Stats} />
                </div>
          </Router>
  </Provider>,
  document.getElementById('app')
);

存储/configureStore.js

// store/configureStore.js
import {createStore, compose, applyMiddleware} from 'redux';
// Import thunk middleware
import thunk from 'redux-thunk';
import rootReducer from '../reducers';

export default function configureStore(initialState) {
  return createStore(rootReducer, initialState,
    // Apply to store
    applyMiddleware(thunk)
  );
}

动作/动作类型

// actions/actionTypes

export const FETCH_HOCKEY_SUCCESS = 'FETCH_HOCKEY_SUCCESS';
export const FETCH_FOOTBALL_SUCCESS = 'FETCH_FOOTBALL_SUCCESS';
export const FETCH_MUFFIBALL_SUCCESS = 'FETCH_MUFFIBALL_SUCCESS';

actions/hockeyActions.js(每项运动都有一个这样的文件 - 需要制作这个通用文件):

// hockeyActions.js (one such file for every sport - need to make this one generic file):

import Axios from 'axios';

const apiUrl = '/api/hockey/';
// Sync Action
export const fetchHockeySuccess = (hockey) => {
  return {
    type: 'FETCH_HOCKEY_SUCCESS',
    hockey
  }
};


//Async Action
export const fetchHockey = () => {
  // Returns a dispatcher function
  // that dispatches an action at a later time
  return (dispatch) => {
    // Returns a promise
    return Axios.get(apiUrl)
      .then(response => {
        // Dispatch another action
        // to consume data

        dispatch(fetchHockeySuccess(response.data))
      })
      .catch(error => {
        console.log(error)
        throw(error);
      });
  };
};

reducers/hockeyReducers.js(每项运动都有一个这样的文件 - 需要制作这个通用文件)

// reducers/hockeyReducers.js (one such file for every sport - need to make this one generic file)

import * as actionTypes from '../actions/actionTypes'

export const hockeyReducer = (state = [], action) => {
  switch (action.type) {
    case actionTypes.FETCH_HOCKEY_SUCCESS:
          return action.hockey;
    default:
          return state;
  }
};

减速器/index.js

// reducers/index.js

import { combineReducers } from 'redux';
import {hockeyReducer} from './hockeyReducers'
import {footballReducer} from './footballReducers'
import {muffiballReducer} from './muffiballReducers'

export default combineReducers({
  hockey: hockeyReducer,
  football: footballReducer,
  muffiball: muffiballReducer,
  // More reducers for each sport here
});

组件/CardsPage.js:

//components/CardsPage.js

import React from 'react';
import { connect } from 'react-redux';

class Cards extends React.Component{
  constructor(props){
    super(props);

    this.state = {
        data: this.props.data,
    }

  }

  componentWillReceiveProps(nextProps){
        this.setState({
                data: nextProps.data,
        })
  }

  render(){

    return(
        {/* cards displayed from this.state.data */}
    )
  }
}

const mapStateToProps = (state, ownProps) => {
  return {
    data: state[ownProps.match.params.val]
  }
};

export default connect(mapStateToProps)(Cards);

标签: javascriptreactjsredux

解决方案


退后一步,识别具有独特形状的数据类型,例如cardsstats。您将使用它自己的操作、reducers 和选择器为其中的每一个构建一个存储切片。这项运动应该只是一个变量,您可以将其用作操作和选择器的参数。例如

异步操作

export const fetchCards = (sport) => {
  return (dispatch) => {
    return Axios.get(`/api/${sport}/`)
      .then(response =>
        dispatch(fetchCardSuccess({ sport, data: response.data }))
      )
      .catch(error => {
        console.log(error)
        throw(error);
      });
  };
};

减速器

export const cardReducer = (state = {}, action) => {
  switch (action.type) {
    case actionTypes.FETCH_CARD_SUCCESS:
      return { ...state, [action.sport]: action.data };
    default:
      return state;
  }
};

卡片选择器

export const getSport(state, sport) {
  return state.cards[sport];
}

您可能需要另一个切片来管理从服务器获取的可用运动列表和其他全局数据。


推荐阅读