Redux store.subscribe()不会在状态更改时触发

时间:2018-07-10 14:36:27

标签: reactjs redux

我正在开发一个应用程序,用户可以在其中更改输入值,并应根据这些值生成一些计算出的值。在此演示中,我仅需要填写三个输入(它们的值存储在数组中的单独文件data.js中),并且只有一个输入被计算为前三个输入的平均值。

项目结构如下:

src / index.js

import React from "react";
import ReactDOM from "react-dom";
import Block1 from "../components/Block1";
import getStore from "../store";
const store = getStore();

const rootEl = document.getElementById("root");

const render = () => {
  ReactDOM.render(<Block1 />, rootEl);
};

render();

store.subscribe(() => {
  render();
  console.log("not working");
});

components / Block1.js

import React, { Component } from "react";
import getStore from "../store";
const store = getStore();

function Item(props) {
  return (
    <tr key={props.id}>
      <td>{props.rn}</td>
      <td>{props.name}</td>

      {props.not !== "" ? (
        <td>
          <img src={"img/" + props.not} />
        </td>
      ) : (
        <td />
      )}

      {props.isInput ? (
        <td>
          <input type="text" value={props.val} onChange={props.onChange} />
        </td>
      ) : (
        <td>{props.val}</td> 
      )}

      <td>{props.um}</td>
    </tr>
  );
}

export default class Block1 extends Component {
  renderItem(d) {
    return (
      <Item
        key={d.id}
        id={d.id}
        rn={d.rn}
        name={d.name}
        not={d.not}
        val={d.val}
        um={d.um}
        isInput={d.isInput}
        onChange={e =>
          store.dispatch({
            type: "changeValue",
            payload: {
              id: d.id,
              val: e.target.value
            }        
          })
        }
      />
    );
  }

  render() {
    return (
      <div>
        <h2>Block 1. Initial data for calculation</h2>
        <table className="table">
          <thead>
            <tr>
              <th>№</th>
              <th>Name</th>
              <th>Notation</th>
              <th>Value</th>
              <th>Unit of measure</th>
            </tr>
          </thead>
          <tbody>{store.getState().map(d => this.renderItem(d))}</tbody>
        </table>
      </div>
    );
  }
}

reducers / index.js

import getData from "../data.js";
const data = getData();
const Parser = require("expr-eval").Parser;

export default (state = data, action) => {
  if (action.type === "changeValue") {
    let changedId = action.payload.id;
    // change value in input
    let data = state.slice();
    let el = data.find(d => d.id === changedId);
    el.val = parseFloat(action.payload.val);

    // find all id to re-calculate
    const toRecalculate = getAllDependentDataId(changedId);

    // recalculation
    toRecalculate.forEach(r => {
      // ... here some calculation logic which gives the result

        let el = data.find(d => d.id === r);
        el.val = Math.round((result + 0.00001) * 100) / 100;

        return data;
    });
  }
  return state;
};

store / index.js

import reducer from "../reducers";
import { createStore } from "redux";
import getData from "../data.js";

export default function getStore() {
  const store = createStore(reducer, getData());
  return store;
}

完整的示例是here

问题在于,由于某种原因,UI中不会更新新值。 store.subscribe(...)内部的代码无法运行。但是,状态正在改变-数组中的值已更新-我使用console.log检查了此结果...

请帮助我找出问题所在。

2 个答案:

答案 0 :(得分:3)

您缺少动作/动作创建者,无法从redux连接connect,provider,mapStateToProps和mapDispatchToProps。如果您仅具有这4个值,那么我就不会使用redux,而只会使用本地状态作为受控组件。

如果您尝试使用redux,那么我将从这里开始。

https://redux.js.org/basics


这是一个简单的例子,可以解决redux的平均问题: https://codesandbox.io/s/422q1znj4w

components / Block1.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { changeValue, loadData } from "../actions/action";
import getData from "../data";

function Item(props) {
  return (
    <tr key={props.id}>
      <td>{props.rn}</td>
      <td>{props.name}</td>

      {props.not !== "" ? (
        <td>
          <img src={"img/" + props.not} />
        </td>
      ) : (
        <td />
      )}

      {props.isInput ? (
        <td>
          <input type="text" value={props.val} onChange={props.onChange} />
        </td>
      ) : (
        <td>{props.val}</td>
      )}

      <td>{props.um}</td>
    </tr>
  );
}

class Block1 extends Component {
  constructor(props) {
    super(props);   
  }
  componentDidMount() {
    this.props.loadData(getData());
  }
  renderItem(d) {
    console.log(d.val);
    return (
      <Item
        key={d.id}
        id={d.id}
        rn={d.rn}
        name={d.name}
        not={d.not}
        val={d.val}
        um={d.um}
        isInput={d.isInput}
        onChange={e =>
          this.props.changeValue({ id: d.id, value: e.target.value })
        }
      />
    );
  }

  render() {
    return (
      <div>
        <h2>Block 1. Initial data for calculation</h2>
        <table className="table">
          <thead>
            <tr>
              <th>№</th>
              <th>Name</th>
              <th>Notation</th>
              <th>Value</th>
              <th>Unit of measure</th>
            </tr>
          </thead>
          <tbody>{this.props.data.map(d => this.renderItem(d))}</tbody>
        </table>
      </div>
    );
  }
}
const mapStateToProps = state => {
  return {
    data: state.data
  };
};
const mapDispatchToProps = dispatch => {
  return {
    changeValue: item => dispatch(changeValue(item)),
    loadData: data => dispatch(loadData(data))
  };
};
export default connect(mapStateToProps, mapDispatchToProps)(Block1);

actions / action.js

export function changeValue(data) {
  return {
    type: "changeValue",
    data
  };
}

export function loadData(data) {
  return {
    type: "loadData",
    data
  };
}

reducers / data.js

export function data(state = [], action) {
  if (action.type === "loadData") {
    return action.data;
  } else {
    if (action.type === "changeValue") {
      let newState = [...state];
      newState[action.data.id - 1].val = action.data.value;

      let counter = 0;
      let sum = 0;

      for (let input of newState) {
        if (input.isInput) {
          sum += parseInt(input.val);
          counter++;
        }
      }
      newState[state.length - 1].val = sum / counter;
      return newState;
    }
  }
  return state;
}

reducers / rootReducer.js

import { combineReducers } from "redux";
import { data } from "./data";
const rootReducer = combineReducers({
  data
});
export default rootReducer;

src / index.js

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import configureStore from "../store";
import Block1 from "../components/Block1";

const store = configureStore();

const rootEl = document.getElementById("root");

ReactDOM.render(
  <Provider store={store}>
    <Block1 />
  </Provider>,
  rootEl
);

store / index.js

import { createStore } from "redux";
import rootReducer from "../reducers/rootReducer";

export default function configureStore() {
  return createStore(
    rootReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  );
}

答案 1 :(得分:0)

  

就在每个dispatch()调用之前对订阅进行快照。如果在调用侦听器时进行订阅或取消订阅,则这不会对当前正在进行的dispatch()产生任何影响。但是,下一个dispatch()调用(无论是否嵌套)都将使用订阅列表的最新快照。

https://redux.js.org/api/store#subscribelistener

因此,一旦在创建商店后立即创建订阅(OR) 在调度动作调用之前。

const store = createStore(reducer);

//SUBSCRIBE the state change
store.subscribe(() => {
  console.log(store.getState());
});
//ACTION
store.dispatch({ type: "ADD", val: 1 });