【问题标题】:Passing an identifier to a module in React将标识符传递给 React 中的模块
【发布时间】:2015-12-30 22:59:11
【问题描述】:

抱歉,我可能在这里的所有术语都错了,但我正在将一个 React 应用程序分成多个模块,并试图将两个做同样事情的模块组合起来并遇到问题。

具体来说,我试图总结收入和支出的总和,并通过以下方式调用两个不同的反应类:

<ExpenditureTotal type={this.state.cashbook.expenditure} addTotal={this.addTotal} /> 和另一个 <IncomeTotal... 也是如此)。

班级如下(显示支出,但收入相同:

/* Expenditure Running Total */
var ExpenditureTotal = React.createClass({
  render: function() {

    var expIds = Object.keys(this.props.expenditure);
    var total = expIds.reduce((prevTotal, key) => {
      var expenditure = this.props.expenditure[key];
      return prevTotal + (parseFloat(expenditure.amount));
    }, 0);

    this.props.addTotal('expenditure', total);

    return(
      <h2 className="total">Total: {h.formatPrice(total)}</h2>
    );
  }
});

我想通过使其更通用来将两者结合起来,所以我做了以下内容:

/* Cashflow Running Total */
var TotalCashflow = React.createClass({
  render: function() {
    var expIds = Object.keys(this.props.type);
    var total = expIds.reduce((prevTotal, key) => {
      var type = this.props.type[key];
      return prevTotal + (parseFloat(type.amount));
    }, 0);

    this.props.addTotal('type', total);

    return(
      <h2 className="total">Total: {h.formatPrice(total)}</h2>
    );
  }
});

它不能正常工作的唯一原因是当我将总和添加到相关的状态对象中,这就是总收入或支出:(totals: { income: val, expenditure: val})。 在我之前在模块中明确指定“支出”或“收入”的地方,(在上面的 ExpenditureTotal 中看到 this.props.addTotal('expenditure', total);,我不确定在 React 中如何将总值传递到相关位置 - 它需要读取“支出”或“收入”来填充正确的州总计键。

抱歉,如果这有点乱码,很难解释清楚。

谢谢你:)

更新:完整的应用组件:

import React from 'react';

// Firebase
import Rebase from 're-base';
var base = Rebase.createClass("FBURL")

import h from '../helpers';

import Expenditure from './Expenditure';
import Income from './Income';
import TotalCashflow from './TotalCashflow';
import AddForm from './AddForm';
import Available from './Available';

var App = React.createClass({

  // Part of React lifecycle
  getInitialState: function() {
    return {
      cashbook: {
        expenditure: {},
        income: {}
      },
      totals: {},
      available: {}
    }
  },

  componentDidMount: function() {
    // Two way data binding
    base.syncState('cashbook', {
      context: this,
      state: 'cashbook'
    });
  },

  addExpenditure: function(expenditure) {
    var timestamp = (new Date()).getTime();
    // update state object
    this.state.cashbook.expenditure['expenditure-' + timestamp] = expenditure;
    // set state
    this.setState({
      cashbook: { expenditure: this.state.cashbook.expenditure }
    });
  },
  addIncome: function(income) {
    var timestamp = (new Date()).getTime();
    // update state object
    this.state.cashbook.income['income-' + timestamp] = income;
    // set state
    this.setState({
      cashbook: { income: this.state.cashbook.income }
    });
  },
  removeExpenditure: function(key) {
    this.state.cashbook.expenditure[key] = null;
    this.setState({
      cashbook: { expenditure: this.state.cashbook.expenditure }
    });
  },
  renderExpenditure: function(key) {
    var details = this.state.cashbook.expenditure[key];
    return(
      <tr className="item" key={key}>
        <td><strong>{details.name}</strong></td>
        <td><strong>{h.formatPrice(details.amount)}</strong></td>
        <td>{details.category}</td>
        <td>{details.type}</td>
        <td>{details.date}</td>
        <td><button className="remove-item" onClick={this.removeExpenditure.bind(null, key)}>Remove</button></td>
      </tr>
    );
  },
  removeIncome: function(key) {
    this.state.cashbook.income[key] = null;
    this.setState({
      cashbook: { income: this.state.cashbook.income }
    });
  },
  renderIncome: function(key) {
    var details = this.state.cashbook.income[key];
    return(
      <tr className="item" key={key}>
        <td><strong>{details.name}</strong></td>
        <td><strong>{h.formatPrice(details.amount)}</strong></td>
        <td>{details.category}</td>
        <td>{details.type}</td>
        <td>{details.date}</td>
        <td><button className="remove-item" onClick={this.removeIncome.bind(null, key)}>Remove</button></td>
      </tr>
    );
  },
  listInventory: function() {
    return
  },
  addTotal: function(type, total) {
    this.state.totals[type] = total;
  },
  render: function() {

    return(
      <div className="cashbook">

        <Expenditure
          cashbook={this.state.cashbook.expenditure}
          renderExpenditure={this.renderExpenditure} />

        <TotalCashflow
          type={this.state.cashbook.expenditure}
          addTotal={this.addTotal}
          identifier='expenditure'
          />

        <AddForm addCashflow={this.addExpenditure} />

        <Income
          cashbook={this.state.cashbook.income}
          renderIncome={this.renderIncome} />

        <TotalCashflow
          type={this.state.cashbook.income}
          addTotal={this.addTotal}
          identifier='income'
          />

        <AddForm addCashflow={this.addIncome} />

        <Available totals={this.state.totals} />

      </div>
    );
  }
});

export default App;

【问题讨论】:

  • 我相信你所说的模块是 React 术语中的组件。而rendershould是纯的:也就是说,它不应该修改任何状态。
  • 除了cashbook.expenditure,还有cashbook.type = 'expenditure' 和收入。然后你可以访问 type 属性来查看你需要哪个

标签: reactjs


【解决方案1】:

如果我的理解正确,您希望有一个组件,它呈现“支出”和/或“收入”的总数。请注意,您不应该修改渲染函数中的状态,因为这些会一遍又一遍地触发,每次状态更改都会调用它自己。

相反,尝试从父组件 componentDidMount/componentDidUpdate 中计算必要的数据,以便 TotalCashflow 可以提供数据,而无需对其执行任何更多逻辑。

这可能是父组件:

/* Cashflow-Wrapper */
var TotalCashflowWrapper = React.createClass({
  componentDidMount: function(){
    this.setState({
        income: this.addTotal(/*x1*/)
        expenditure: this.addTotal(/*x1*/)
    });
    /*x1: hand over the relevant 'type' data that you use in the child components in your example*/
  }
  addTotal: function(type) {
    var expIds = Object.keys(type);
    return expIds.reduce((prevTotal, key) => {
       var x = type[key];
       return prevTotal + (parseFloat(x.amount));
    }, 0);
  }
  render: function() {
    return(
      <div>
         <TotalCashflow total={this.state.expenditure}/>
         <TotalCashflow total={this.state.income}/>
      </div>
    );
  }
});

这将是您的全部组成部分:

   /* Cashflow Running Total */
    var TotalCashflow = React.createClass({
      render: function() {
        return(
          <h2 className="total">Total: {h.formatPrice(this.props.total)}</h2>
        );
      }
    });

编辑

您的 App 组件存在一些问题,但首先,您可以通过以下方式重新设计/简化它。您还应该将标记从 renderExpenditure 和 renderIncome 移动到您的收入/支出组件。我一直在使用 ES6,因为我注意到您已经在使用箭头函数了。

var App = React.createClass({
   getInitialState: function () {
      return {
         cashbook: {
            expenditure: {},
            income: {}
         },
         totals: {},
         available: {}
      }
   },

   componentDidMount: function () {
      // Two way data binding
      base.syncState('cashbook', {
         context: this,
         state: 'cashbook'
      }).then(()=> {
         // after syncing cashbook, calculate totals
         this.setState({
            totals: {
               income: this.getTotal(this.state.cashbook.income),
               expenditure: this.getTotal(this.state.cashbook.expenditure),
            }
         });
      });
   },

   // Get total of obj income/expenditure
   getTotal: function (obj) {
      var expIds = Object.keys(obj);
      return expIds.reduce((prevTotal, key) => {
         var type = obj[key];
         return prevTotal + (parseFloat(type.amount));
      }, 0);
   },

   addCashflow: function (identifier, amount) {
      var timestamp = (new Date()).getTime();
      // clone cashbook and clone cashbook[identifier] and set cashbook[identifier][identifier + '-' + timestamp] to amount
      var cashbook = {
         ...this.state.cashbook,
         [identifier]: {
            ...this.state.cashbook[identifier],
            [identifier + '-' + timestamp]: amount
         }
      };
      // set state
      this.setState({
         cashbook: cashbook,
         // Update totals
         totals: {
            ...this.state.totals,
            [identifier]: this.getTotal(cashbook[identifier])
         }
      });
   },

   removeCashflow: function (identifier, key) {
      // clone cashbook and clone cashbook[identifier]
      var cashbook = {...this.state.cashbook, [identifier]: {...this.state.cashbook[identifier]}};
      delete cashbook[identifier][key];
      this.setState({
         cashbook: cashbook,
         totals: {
            ...this.state.totals,
            // Update totals
            [identifier]: this.getTotal(cashbook[identifier])
         }
      });
   },

   render: function () {
      return (
         <div className="cashbook">
            <Expenditure cashbook={this.state.cashbook.expenditure} removeCashflow={(key)=>this.removeCashflow('expenditure', key)}/>
            <TotalCashflow total={this.state.cashbook.expenditure} identifier='expenditure'/>
            {/*or drop TotalCashflow and do <h2 className="total">Total: {h.formatPrice(this.state.totals.expandature)}</h2>*/}
            <AddForm addCashflow={(amount)=>this.addCashflow('expenditure', amount)}/>
            <Income cashbook={this.state.cashbook.income} removeCashflow={(key)=>this.removeCashflow('income', key)}/>
            <TotalCashflow type={this.state.cashbook.income} identifier='income' />
            <AddForm addCashflow={(amount)=>this.addCashflow('income', amount)}/>
            <Available totals={this.state.totals}/>
         </div>
      );
   }
});

export default App;

当前应用组件的问题

问题:您没有删除密钥,只是将其设置为 null;这可能会在迭代 Object.keys() 时导致异常,并且您期望值是数字,例如在计算总数时

removeIncome: function(key) {
      // this.state.cashbook.income[key] = null;
      delete this.state.cashbook.income[key]
      this.setState({
         cashbook: { income: this.state.cashbook.income }
      });
   },

糟糕的设计:您在父组件中定义子组件的标记,尽管这似乎没有必要

renderExpenditure: function(key) {
    var details = this.state.cashbook.expenditure[key];
    return(
      <tr className="item" key={key}>
        <td><strong>{details.name}</strong></td>
        <td><strong>{h.formatPrice(details.amount)}</strong></td>
        <td>{details.category}</td>
        <td>{details.type}</td>
        <td>{details.date}</td>
        <td><button className="remove-item" onClick={this.removeExpenditure.bind(null, key)}>Remove</button></td>
      </tr>
    );
  },

这可以很容易地转移到您的支出/收入部分。

问题:变异状态、覆盖状态的现金簿和损失支出/收入

addExpenditure: function(expenditure) {
      var timestamp = (new Date()).getTime();
      // You are mutating the state here, better clone the object and change the clone, then assign the cloned
      this.state.cashbook.expenditure['expenditure-' + timestamp] = expenditure;
      // You are overwriting cashbook with an object, that only contains expenditure, thus loosing other properties like income
      this.setState({
         cashbook: { expenditure: this.state.cashbook.expenditure }
      });
   }

// 固定

addExpenditure: function(expenditure) {
      var timestamp = (new Date()).getTime();
      // clone object
      var cashbook = Object.assign({}, this.state.cashbook);
      cashbook.expenditure = Object.assign({}, cashbook.expenditure);
      // define latest expenditure
      cashbook.expenditure['expenditure-' + timestamp] = expenditure;
      // set state
      this.setState({
         cashbook: cashbook
      });
   }

// ES6

addExpenditure: function(expenditure) {
      var timestamp = (new Date()).getTime();
      this.setState({
         cashbook: {
          ...this.state.cashbook,
          expenditure: {
            ...this.state.cashbook.expenditure,
            ['expenditure-' + timestamp]: expenditure
          }
         }
      });
   }

如果你将 state 和 cashbook 对象展平,你会更好 所以而不是

this.state = {
         cashbook: {
            expenditure: {},
            income: {}
         },
         totals: {},
         available: {}
      }

拥有

this.state = {
         expenditure: {},
         income: {}
         totals: {},
         available: {}
      }

这样你就可以

addExpenditure: function(expenditure) {
      var timestamp = (new Date()).getTime();
      var exp = Object.assign({}, this.state.cashbook.expenditure);
      exp['expenditure-' + timestamp] = expenditure;
      this.setState({
         expenditure: exp
      });
   }

或 es6

addExpenditure: function(expenditure) {
      var timestamp = (new Date()).getTime();
      this.setState({
         expenditure: {
            ...this.state.cashbook.expenditure,
            ['expenditure-' + timestamp]: expenditure
         }
      });
   }

当然你需要更新 rebase 绑定和你的模型,不知道这是不是你想要的东西

componentDidMount: function() {
      base.syncState('expenditure', {
         context: this,
         state: 'expenditure'
      });
      base.syncState('income', {
         context: this,
         state: 'income'
      });
   }

【讨论】:

  • 谢谢昆博。目前正在努力正确实现这一点,我需要更新的状态在 App 组件中,但是在您的示例中,您已经设置了全新的状态对象?此外,渲染出 &lt;TotalCashflow type={this.state.cashbook.expenditure} addTotal={this.addTotal} /&gt; 的是 App 组件,但在您的组件中,它呈现在一个新组件中,所以只是不确定如何将它们拼凑在一起。
  • 这个没关系,Wrapper 可以很容易的成为 App 组件。只需将功能(componentDidMount、addTotal)合并到您的 App 组件中,您就不需要 Wrapper 和另一个状态对象。重要的是,尝试在你的父组件(App组件)中计算componentDidMount(而不是render函数)中的数据,并以正确的格式提供子数据,因此它不需要做任何复杂的处理.如果您想要更好的代码示例,则需要提供对应用组件的更多洞察。
  • 谢谢你。我将使用 App 组件完整更新我的原始帖子。
  • 我已经根据你的 App 组件扩展了我的答案。
  • 哇,太棒了。非常感谢。对不起,我今天出去了,明天早上要过一遍。你很慷慨,再次感谢你:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-18
  • 2021-02-19
  • 2017-12-01
  • 1970-01-01
  • 2012-02-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多