拆分Reducer

时间:2018-04-30 08:14:42

标签: javascript reactjs redux react-redux

以下所有代码在重构开始时都在1个文件中并且运行良好。我简化了一些代码。

我的reducers文件夹:

index.js

import { combineReducers } from 'redux'
import address from './address'
import questions from './questions'

export default combineReducers({
  address,
  questions
});

initialState.js

import { uniqueID } from '../utils/index';

const defaultQuestion = {
  title: 'What is the address of the property?',
  id: 0,
  question_type: 'address'
};

export const initialState = {
  questions: [defaultQuestion],
  sessionID: uniqueID(),
  session: {},
  currentQuestion: defaultQuestion,
  currentAnswer: '',
  addressSelectd: false,
  amount: 0,
  address: {
    address: {},
    isPendind: false,
    isRejected: false,
    isFulfilled: false,
    message: '',
  }
};

address.js

import {
  ON_SELECT_ADDRESS,
  SAVE_ADDRESS_PENDING,
  SAVE_ADDRESS_FULFILLED,
  SAVE_ADDRESS_REJECTED,
} from '../constants/Constants';
import { initialState } from './initialState'
import { nextQuestion } from './questions'

export default function reduce(state = initialState, action) {
  switch (action.type) {
  case ON_SELECT_ADDRESS:
    return {...state,
      currentAnswer: action.payload,
      addressSelectd: true
    };

  case SAVE_ADDRESS_PENDING:
    return {...state,
      address: {
        isPendind: true,
      },
    };

  case SAVE_ADDRESS_FULFILLED:
    return {...state,
      address: {
        isPendind: false,
        isRejected: false,
        isFulfilled: true,
        address: action.payload.address,
      },
      amount: action.payload.amount,
      currentAnswer: '',
      currentQuestion: nextQuestion(state),
    };

  case SAVE_ADDRESS_REJECTED:
    // if (action.payload == 'incorrect_address')
    return {...state,
      currentAnswer: '',
      address: {
        address: {},
        isPendind: false,
        isFulfilled: false,
        isRejected: true,
        message: 'Please find valid address',
      },
    };

  default:
    return state;
  }
}

questions.js

import {
  ON_CHANGE_ANSWER,
  ON_CHANGE_QUESTION,
  GET_QUESTIONS,
  CREATE_SESSION,
  SAVE_ANSWER,
  SAVE_CURRENT_ANSWER,
  ON_FINISH,
} from '../constants/Constants';
import { initialState } from './initialState'
import { isNullOrUndefined } from 'util';

    export const nextQuestion = (state) => {
      let nextId = state.currentQuestion.direct_question_id;
      if (isNullOrUndefined(nextId)) {
        if (state.currentAnswer === 'yes') {
          nextId = state.currentQuestion.yes_question_id;
        } else if (state.currentAnswer === 'no') {
          nextId = state.currentQuestion.no_question_id;
        }
      }
      return state.questions.find((q) => {
        return q.id === nextId;
      });
    }

export default function reduce(state = initialState, action) {
  switch (action.type) {
  case ON_CHANGE_ANSWER:
    return {...state,
      currentAnswer: action.payload
    };

  case ON_CHANGE_QUESTION:
    return {...state,
      currentQuestion: action.payload
    };

  case GET_QUESTIONS:
    return {...state,
      questions: action.payload,
      currentQuestion: action.payload[0]
    };

  case CREATE_SESSION:
    return {...state,
      session: action.payload,
    };

  case SAVE_CURRENT_ANSWER:
    return {...state,
      currentAnswer: action.payload,
    };

  case SAVE_ANSWER:
    return {...state,
      currentAnswer: '',
      currentQuestion: nextQuestion(state),
    };

  case ON_FINISH:
    return initialState;

  default:
    return state;
  }
}

我在Chrome控制台中有很多错误,例如:

Warning: Failed prop type: Invalid prop `questions` of type `object` supplied to `MyApp`, expected `array`.
Warning: Failed prop type: The prop `currentAnswer` is marked as required in `MyApp`, but its value is `undefined`.

但仅适用于questions减速机。如果我在initialState文件中添加console.log,我只看了一次(我想应该显示2次)

似乎问题reducer还没有被添加到root reducer。

configureStore:

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../reducers/index';
import { createLogger } from 'redux-logger';
import DevTools from '../web/containers/DevTools';

const createDevStoreWithMiddleware = compose(
  applyMiddleware(thunk),
  applyMiddleware(createLogger()),
  DevTools.instrument()
)(createStore);

export default function configureStore() {
  const store = createDevStoreWithMiddleware(reducers);

  return store;
}

更新

App.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import AddressSearch from '../components/AddressSearch';
import FinalScreen from '../components/FinalScreen';
import {
  onChangeAnswer,
  onChangeQuestion,
  getQuestions,
  saveAnswer,
  createSession,
  onFinish
} from '../../actions/actions';

class MyApp extends Component {

  static propTypes = {
    questions: PropTypes.array.isRequired,
    sessionID: PropTypes.string.isRequired,
    session: PropTypes.object.isRequired,
    currentQuestion: PropTypes.object.isRequired,
    currentAnswer: PropTypes.string.isRequired,
    address: PropTypes.object.isRequired,
    amount: PropTypes.number,
  };

  componentDidMount() {
    this.props.actions.getQuestions();
    this.props.actions.createSession();
  }

  onReset() {
    this.props.actions.onFinish();
    this.componentDidMount();
  }

  nextQuestion(text) {
    if (text.length > 0) {
      this.props.actions.saveAnswer(text);
    }
  }

  renderAnswers() {
    const props = this.props;
    if (props.currentQuestion.question_type === 'address') {
      return <AddressSearch
        currentAnswer={props.currentAnswer}
        message={props.address.message}
        />;
    } else if (props.currentQuestion.question_type === 'text') {
      return [
        <input
          className="question-input"
          value={props.currentAnswer}
          onChange={(event) => props.actions.onChangeAnswer(event.target.value)}
        />,
        <button
          className="main-button"
          onClick={() => this.nextQuestion(props.currentAnswer)}>
            NEXT
        </button>
      ];
    } else if (props.currentQuestion.question_type === 'bool') {
      return [
        <button
          className="yes-no-button"
          onClick={() => this.nextQuestion('yes')}>
            YES
        </button>,
        <button
          className="yes-no-button"
          onClick={() => this.nextQuestion('no')}>
            NO
        </button>
      ];
    } else if (props.currentQuestion.question_type === 'screen') {
      return (
        <button
          className="main-button"
          onClick={() => this.onReset()}>
            Back
        </button>
      );
    }
  }

  containerInner() {
    if (this.props.currentQuestion.question_type === 'success') {
      return <FinalScreen amount={this.props.amount} />;
    } else {
      return [
        <div key={0} className="question">
          {this.props.currentQuestion.title}
        </div>,
        <div key={1} className="answer">
          {this.renderAnswers()}
        </div>
      ];
    }
  }

  render() {
    return (
      <div className="react-native-web">
        {this.containerInner()}
      </div>
      );
    }
  }

const mapStateToProps = (state) => {
  return state;
};

const mapDispatchToProps = (dispatch) => {
  return {
    actions: {
      getQuestions: () => dispatch(getQuestions()),
      createSession: () => dispatch(createSession()),
      saveAnswer: (text) => dispatch(saveAnswer(text)),
      onChangeAnswer: (text) => dispatch(onChangeAnswer(text)),
      onChangeQuestion: (obj) => dispatch(onChangeQuestion(obj)),
      onFinish: () => dispatch(onFinish()),
    }
  };
};

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

1 个答案:

答案 0 :(得分:1)

mapStateToProps

const mapStateToProps = (state) => {
  return state;
};

state应根据其reducers进行拆分。这是因为combineReducers

所以,当你想在使用combineReducers时获得实际状态时,你必须做这样的事情 -

const mapStateToProps = (state) => {
  return state.address; // or state.question
};

如果你想发送状态的所有数据(即属于两个reducer),你可以这样做 -

const mapStateToProps = (state) => {
  return Object.assign({}, state.address, state.question);
};

或者您必须在reducer代码中处理它。

注意:我没有尝试过,在执行此操作时必须小心,因为由于创建了单独的对象,因此可能会导致更新问题。

编辑:有些人考虑过实施。

PS:我认为reducer设计不正确。我的意思是address以及questions减速器都具有相同的初始状态。所以,当你执行combineReducer()时,store.getState()(即商店状态)会变成这样的 -

state = {
  address: {
    questions: [{
      title: 'What is the address of the property?',
      id: 0,
      question_type: 'address'
    }],
    sessionID: 1234,
    session: {},
    currentQuestion: defaultQuestion,
    currentAnswer: '',
    addressSelectd: false,
    amount: 0,
    address: {
      address: {},
      isPendind: false,
      isRejected: false,
      isFulfilled: false,
      message: '',
    }
  },
  questions: {
    questions: [{
      title: 'What is the address of the property?',
      id: 0,
      question_type: 'address'
    }],
    sessionID: 1234,
    session: {},
    currentQuestion: defaultQuestion,
    currentAnswer: '',
    addressSelectd: false,
    amount: 0,
    address: {
      address: {},
      isPendind: false,
      isRejected: false,
      isFulfilled: false,
      message: '',
    }
  }
};

而不是这个 -

state = {
  questions: [{
    title: 'What is the address of the property?',
    id: 0,
    question_type: 'address'
  }],
  sessionID: 1234,
  session: {},
  currentQuestion: defaultQuestion,
  currentAnswer: '',
  addressSelectd: false,
  amount: 0,
  address: {
    address: {},
    isPendind: false,
    isRejected: false,
    isFulfilled: false,
    message: '',
  }
}

我强烈建议您将常见状态(如currentAnswercurrentQuestion)移动到单独的缩减器中。

编辑2 :我刚刚使用以下代码验证了Object.assign()不正确的事情。

var address = {
  questions: [{
    title: 'What is the address of the property?',
    id: 0,
    question_type: 'address'
  }],
  sessionID: 12345,
  session: {},
  currentQuestion: defaultQuestion,
  currentAnswer: '',
  addressSelectd: false,
  amount: 0,
  address: {
    address: {},
    isPendind: false,
    isRejected: false,
    isFulfilled: false,
    message: ''
  }
};

var questions = {
  questions: [{
    title: 'What is the address of the property?',
    id: 0,
    question_type: 'address'
  }],
  sessionID: 1234,
  session: {},
  currentQuestion: defaultQuestion,
  currentAnswer: '',
  addressSelectd: false,
  amount: 0,
  address: {
    address: {},
    isPendind: false,
    isRejected: false,
    isFulfilled: false,
    message: ''
  }
};

var result = Object.assign({}, address, questions);
console.log(result);

输出是 -

{
  "questions": [
    {
      "title": "What is the address of the property?",
      "id": 0,
      "question_type": "address"
    }
  ],
  "sessionID": 1234,
  "session": {},
  "currentQuestion": {
    "title": "What is the address of the property?",
    "id": 0,
    "question_type": "address"
  },
  "currentAnswer": "",
  "addressSelectd": false,
  "amount": 0,
  "address": {
    "address": {},
    "isPendind": false,
    "isRejected": false,
    "isFulfilled": false,
    "message": ""
  }
}

此处,addresssessionID: 12345,而questionssessionID: 1234,但resultsessionID: 1234

因此,Object.assign()会将address设置的值替换为question的值。这就是为什么它似乎有效。

正确的方法是重新设计减速器,使其在新的减速器中具有共同状态。