简体   繁体   English

Redux中的“ Reducer”能否默认返回初始状态和默认值?

[英]Can a “Reducer” in Redux return an initial state by default AND a default value?

I am learning Redux at school, as such we are using tests to insure we have benchmarks passing to help us in our understanding of the building blocks. 我正在学校学习Redux,因此我们正在使用测试来确保通过基准测试,以帮助我们理解构件。

I am up to the portion where I am creating the Reducer function and I am almost done \\o/ however I can't get one test to pass. 我已经完成了创建Reducer函数的部分,我几乎完成了\\o/但是我无法通过一项测试。

1) returns the initial state by default 1)默认返回初始状态

And below the console spits back... 在控制台下方吐口水...

Reducer returns the initial state by default: Reducer默认情况下返回初始状态:

AssertionError: expected undefined to be an object at Context. AssertionError:期望未定义为Context对象。 (tests/redux.spec.js:103:49) (试验/ redux.spec.js:103:49)

My thinking it's because the test handles some of the concerns one would be responsible for eg importing, creating action types etc. But not all. 我认为这是因为测试处理了一些可能引起的问题,例如导入,创建操作类型等。但并非全部。 So maybe I am missing something the test is not providing? 所以也许我缺少测试未提供的内容?

Anyway here is my reducer file: 无论如何,这是我的减速器文件:

import pet from "../components/PetPreview";
import { createStore } from "redux";

import { adoptPet, previewPet, addNewDog, addNewCat } from "./action-creators";
// ACTION TYPES
const PREVIEW_PET = "PREVIEW_PET";
const ADOPT_PET = "ADOPT_PET";
const ADD_NEW_DOG = "ADD_NEW_DOG";
const ADD_NEW_CAT = "ADD_NEW_CAT";


// INTITIAL STATE
const initialState = {
  dogs: [
    {
      name: "Taylor",
      imgUrl: "src/img/taylor.png"
    },
    {
      name: "Reggie",
      imgUrl: "src/img/reggie.png"
    },
    {
      name: "Pandora",
      imgUrl: "src/img/pandora.png"
    }
  ],
  cats: [
    {
      name: "Earl",
      imgUrl: "src/img/earl.png"
    },
    {
      name: "Winnie",
      imgUrl: "src/img/winnie.png"
    },
    {
      name: "Fellini",
      imgUrl: "src/img/fellini.png"
    }
  ]
// These dogs and cats are on our intial state,
// but there are a few more things we need!
};


export default function reducer(prevState = initialState, action) {
  var newState = Object.assign({}, prevState)

  console.log('initialState', typeof initialState)
  switch (action.type) {

    case PREVIEW_PET:
      // console.log('newState', newState)
      return Object.assign({}, prevState, {
        petToPreview: action.pet
      });
      break
    case ADOPT_PET:
      return Object.assign({}, prevState, {
        petToAdopt: action.pet
      });
      break
    case ADD_NEW_DOG:
      // console.log('action', action.dog)
      // console.log('prevState.dogs', prevState.dogs)
      newState.dogs = prevState.dogs.concat([action.dog])
      return newState;
      break
    case ADD_NEW_CAT:
      // console.log('action', action.dog)
      // console.log('prevState.dogs', prevState.dogs)
      newState.cats = prevState.cats.concat([action.cat])
      return newState;
      break;
    default:
      return prevState;

  }
  return initialState
}

As you can see after the switch block I am returning the initialState Shouldn't that be it? 如您所见,在switch块之后,我正在返回initialState不是吗?

Below is the redux.spec.js file: 以下是redux.spec.js文件:

import { expect } from "chai";
import { createStore } from "redux";

// You will write these functions
import {
  previewPet,
  adoptPet,
  addNewDog,
  addNewCat
} from "../src/store/action-creators";
import reducer from "../src/store/reducer";

const DOGS = [
  {
    name: "Taylor",
    imgUrl: "src/img/taylor.png"
  },
  {
    name: "Reggie",
    imgUrl: "src/img/reggie.png"
  },
  {
    name: "Pandora",
    imgUrl: "src/img/pandora.png"
  }
];

const CATS = [
  {
    name: "Earl",
    imgUrl: "src/img/earl.png"
  },
  {
    name: "Winnie",
    imgUrl: "src/img/winnie.png"
  },
  {
    name: "Fellini",
    imgUrl: "src/img/fellini.png"
  }
];

function getRandomPet(pets) {
  return pets[Math.floor(Math.random() * pets.length)];
}

describe("Action creators", () => {
  describe("previewPet", () => {
    it("returns properly formatted action", () => {
      const pet = getRandomPet(DOGS);

      expect(previewPet(pet)).to.be.deep.equal({
        type: "PREVIEW_PET",
        pet: pet
      });
    });
  });

  describe("adoptPet", () => {
    it("returns properly formatted action", () => {
      const pet = getRandomPet(DOGS);

      expect(adoptPet(pet)).to.be.deep.equal({
        type: "ADOPT_PET",
        pet: pet
      });
    });
  });

  describe("addNewDog", () => {
    it("returns properly formatted action", () => {
      const pet = getRandomPet(DOGS);

      expect(addNewDog(pet)).to.be.deep.equal({
        type: "ADD_NEW_DOG",
        dog: pet
      });
    });
  });

  describe("addNewCat", () => {
    it("returns properly formatted action", () => {
      const pet = getRandomPet(CATS);

      expect(addNewCat(pet)).to.be.deep.equal({
        type: "ADD_NEW_CAT",
        cat: pet
      });
    });
  });
}); // end Action creators

describe("Reducer", () => {
  let store;

  beforeEach("Create the store", () => {
    // creates a store (for testing) using your (real) reducer
    store = createStore(reducer);
  });

  it("returns the initial state by default", () => {
    // In addition to dogs and cats, we need two more fields
    expect(store.getState().petToPreview).to.be.an("object");
    expect(store.getState().petToAdopt).to.be.an("object");
  });

  describe("reduces on PREVIEW_PET action", () => {
    it("sets the action's pet as the petToPreview on state (without mutating the previous state)", () => {
      const prevState = store.getState();

      const pet = getRandomPet(DOGS);
      const action = {
        type: "PREVIEW_PET",
        pet: pet
      };
      store.dispatch(action);

      const newState = store.getState();

      // ensures the state is updated properly - deep equality compares the values of two objects' key-value pairs
      expect(store.getState().petToPreview).to.be.deep.equal(pet);
      // ensures we didn't mutate anything - regular equality compares the location of the object in memory
      expect(newState.petToPreview).to.not.be.equal(prevState.petToPreview);
    });
  });

  describe("reduces on ADOPT_PET action", () => {
    it("sets the action's pet as the petToAdopt on state (without mutating the previous state)", () => {
      const prevState = store.getState();

      const pet = getRandomPet(DOGS);
      const action = {
        type: "ADOPT_PET",
        pet: pet
      };
      store.dispatch(action);

      const newState = store.getState();

      expect(newState.petToAdopt).to.be.deep.equal(pet);
      expect(newState.petToAdopt).to.not.be.equal(prevState.petToAdopt);
    });
  });

  describe("reduces on ADD_NEW_DOG action", () => {
    it("adds the new dog to the dogs array (without mutating the previous state)", () => {
      const prevState = store.getState();

      const pet = getRandomPet(DOGS);
      const action = {
        type: "ADD_NEW_DOG",
        dog: pet
      };
      store.dispatch(action);

      const newState = store.getState();

      expect(newState.dogs.length).to.be.equal(prevState.dogs.length + 1);
      expect(newState.dogs[newState.dogs.length - 1]).to.be.deep.equal(pet);
      expect(newState.dogs).to.not.be.equal(prevState.dogs);
    });
  });

  describe("reduces on ADD_NEW_CAT action", () => {
    it("adds the new cat to the cats array (without mutating the previous state)", () => {
      const prevState = store.getState();

      const pet = getRandomPet(CATS);
      const action = {
        type: "ADD_NEW_CAT",
        cat: pet
      };
      store.dispatch(action);

      const newState = store.getState();

      expect(newState.cats.length).to.be.equal(prevState.cats.length + 1);
      expect(newState.cats[newState.cats.length - 1]).to.be.deep.equal(pet);
      expect(newState.cats).to.not.be.equal(prevState.cats);
    });
  });

  describe("handles unrecognized actions", () => {
    it("returns the previous state", () => {
      const prevState = store.getState();

      const action = {
        type: "NOT_A_THING"
      };
      store.dispatch(action);

      const newState = store.getState();

      // these should be the same object in memory AND have equivalent key-value pairs
      expect(prevState).to.be.an("object");
      expect(newState).to.be.an("object");
      expect(newState).to.be.equal(prevState);
      expect(newState).to.be.deep.equal(prevState);
    });
  });
}); // end Reducer

Thanks in advance! 提前致谢!

All paths in the switch statement lead to a return , which means your return initialState on the penultimate line is unreachable. switch语句中的所有路径均导致return ,这意味着倒数第二行上的return initialState无法访问。 Also, your newState is nothing but a clone of prevState , and is unnecessary. 另外,您的newState只是prevState的克隆, prevState是不必要的。 Removing that, and adding a helper function for switchcase combined with some es6 spread love, your code becomes 删除它,并为开关盒添加一些辅助功能,并结合一些es6传播爱,您的代码将

const switchcase = cases => defaultValue => key =>
  (key in cases ? cases[key] : defaultValue);
const reducer = (state = initialState, action) =>
  switchcase({
    [PREVIEW_PET]: { ...state, petToPreview: action.pet },
    [ADOPT_PET]: { ...state, petToAdopt: action.pet },
    [ADD_NEW_DOG]: { ...state, dogs: [...state.dogs, action.dog] },
    [ADD_NEW_CAT]: { ...state, cats: [...state.cats, action.cat] },
  })(state)(action.type);

With all the clutter gone, it's glaringly obvious that the problem is in the fact that your code returns the initialState object if action.type === undefined . 随着所有混乱的消失,问题显而易见的是,如果action.type === undefined ,您的代码将返回initialState对象。 And your initialState object contains only dogs and cats properties, whereas your test expects there to be petToPreview and petToAdopt properties. 并且您的initialState对象仅包含dogscats属性,而您的测试希望有petToPreviewpetToAdopt属性。

You can add those properties in the initialState or you can change the test depending on what functionality you want. 您可以在initialState添加这些属性,也可以根据所需的功能更改测试。

in the test cases, one of the test case default says 在测试用例中,其中一个默认的测试用例表示

it("returns the initial state by default", () => {
    // In addition to dogs and cats, we need two more fields
    expect(store.getState().petToPreview).to.be.an("object");
    expect(store.getState().petToAdopt).to.be.an("object");
  });

meaning there must be petTpPreview and petToAdapt protery attached to the store at the inital itself. 意味着在商店本身的初始位置必须附加petTpPreview和petToAdapt属性。 this can be done by adding these two as boject to the state as follows. 可以通过将以下两个作为对象添加到状态中来完成此操作。

// INTITIAL STATE
const initialState = {
  petToPreview:{},
  petToAdopt: {},
  dogs: [
    {
      name: "Taylor",
      imgUrl: "src/img/taylor.png"
    },
    {
      name: "Reggie",
      imgUrl: "src/img/reggie.png"
    },
    {
      name: "Pandora",
      imgUrl: "src/img/pandora.png"
    }
  ],
  cats: [
    {
      name: "Earl",
      imgUrl: "src/img/earl.png"
    },
    {
      name: "Winnie",
      imgUrl: "src/img/winnie.png"
    },
    {
      name: "Fellini",
      imgUrl: "src/img/fellini.png"
    }
  ]
// These dogs and cats are on our intial state,
// but there are a few more things we need!
};

hope it helps! 希望能帮助到你!

Shouldn't you return the previous state by default? 默认情况下,您不应该返回以前的状态吗? The by default is the case that the reducer doesn't care about the action, and simply return its current state, which is prevState in your case. 默认情况下,reducer不关心该操作,而只是返回其当前状态,在您的情况下为prevState。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM