情景:
<Row />
- &gt; <Cell />
)中重复使用组件(<ShippingTable />
- &gt; <Grid />
)以重复使用代码。 <Row />
- &gt; <Cell />
)。this.props.data.map
循环已经只是一个对象)。问题:
this.props.data
传递给孩子,状态通过各种事件更新,一切都很顺利。<Row />
。即使this.props.data包含有效值并且已正确分配给子<Cell />
组件,但在<Cell />
的getInitialState中,它仍然是莫名其妙地未定义(或设置为设置的任何初始值) <ShippingTable />
&#39; s getInitialState
)。即使除了在.map循环中调用之外,它与完全相同的代码和数据在我的数据数组的相同渲染中工作。this.props.data
渲染中的<Cell />
确实存在且准确,但由于getInitialState中的setState失败,this.state.data未定义(或设置为任何值在<ShippingTable />
&#39; s getInitialState
)中设置。<ShippingTable />
中的另一个州属性),那么一切都会如我所期望的那样起作用。好像getInitialState
中的<Cell />
只是我的单个百分比对象在成功的setAX的AJAX数据集之前被调用,或者它没有被再次调用在从服务器填充更新数据后更改状态。
这是实际代码的精简版(但仍然很长):
更新 - 已解决
永远不要忘记,使用React,总是发送道具,发送事件,并始终保持状态尽可能远。删除了<Cell />
和<Row />
的状态跟踪职责,将其移回到ShippingTable(而不是引用父级传递的道具),其中实际跟踪状态(并且应始终跟踪) 。根据下面的@rallrall,我偏离了轨道并且莫名其妙地反对该框架。回想起来都非常清楚(尽管为什么不正确的方法对阵列起作用而不是对象确实困扰了水域 - 特别是当将对象切换到阵列时也是如此)。
var ShippingInput = React.createClass({
getInitialState: function() {
return { value: this.props.value };
},
handleChange: function(event) {
var value = event.target.value;
...conversion to decimal and validation
this.props.onChange(value);
this.setState({ value: value });
},
render: function() {
return (
<Input type="text" placeholder="0.00" bsStyle={this.validationState()} addonBefore={addOnBefore}
value={this.state.value} label={this.props.label} ref="input" onChange={this.handleChange}
groupClassName={this.props.groupClassName} labelClassName={this.props.labelClassName} onKeyDown={this.props.onKeyDown} />
);
}
});
var Cell = React.createClass({
propTypes: {
data: React.PropTypes.number.isRequired,
onChange: React.PropTypes.func.isRequired,
onRowEdit: React.PropTypes.func.isRequired
},
getInitialState: function() {
// At this point, this.props.data is undefined *only for <Cell data={this.props.percentages} /> in <Row /> even though that prop is not null there.
return {value: this.props.data};
},
handleChange: function(value) {
this.props.onChange(value);
this.setState({ value: value });
},
render: function() {
var edit = this.props.edit;
var showInput = edit ? 'group-class' : 'group-class hide';
var showText = edit ? 'hide' : 'btn btn-default btn-sm';
var val = this.props.isRates ? accounting.formatMoney(this.state.value) : this.state.value;
// {this.state.value} is undefined here for only the percentages object
// {this.props.data} is *not undefined*
var input = <ShippingInput type="text" label={this.props.label} value={this.state.value} ref="input"
isRates={this.props.isRates} groupClassName={showInput} labelClassName="label-class sr-only" onKeyDown={this.handleKeyDown} onChange={this.handleChange} />;
var text = (<a href="#" className={showText} onClick={this.handleClick}>{val}</a>);
return ( <td>{input}{text}</td> );
}
});
var Row = React.createClass({
propTypes: {
data: React.PropTypes.object.isRequired,
onCellChange: React.PropTypes.func.isRequired,
onRowCommit: React.PropTypes.func.isRequired
},
getInitialState: function() {
return {edit: false};
},
handleChange: function(prop, val) {
this.props.onCellChange(prop, val);
},
...
render: function() {
var edit = this.state.edit;
var text = edit ? 'fa fa-save fa-fw' : 'fa fa-edit fa-fw';
return <tr>
<Cell data={this.props.data.Canada} isRates={this.props.isRates} label="Canada" edit={edit} onRowEdit={this.handleRowEdit} onRowCommit={this.handleRowCommit} onChange={this.handleChange.bind(null, "Canada")} />
<Cell data={this.props.data.Us} isRates={this.props.isRates} label="United States" edit={edit} onRowEdit={this.handleRowEdit} onRowCommit={this.handleRowCommit} onChange={this.handleChange.bind(null, "Us")} />
<Cell data={this.props.data.International} isRates={this.props.isRates} label="International" edit={edit} onRowEdit={this.handleRowEdit} onRowCommit={this.handleRowCommit} onChange={this.handleChange.bind(null, "International")} />
<td>
<Button href="#" ref="commit" onClick={this.handleRowCommit} bsStyle="primary" bsSize="small"><span className={text}></span></Button>
</td>
</tr>;
}
});
var Grid = React.createClass({
propTypes: {
data: React.PropTypes.array.isRequired,
percentages: React.PropTypes.object.isRequired,
onCellChange: React.PropTypes.func.isRequired,
onRowCommit: React.PropTypes.func.isRequired
},
render: function() {
var rows = this.props.data.map(function(rowData, index) {
var id = rowData["Id"];
return <Row key={id} isRates={true} data={rowData} onCellChange={this.props.onCellChange.bind(null, index)} onRowCommit={this.props.onRowCommit.bind(null, index)} onRowDelete={this.props.onRowDelete.bind(null, index)} />;
}, this);
return (
<Table striped bordered hover responsive>
<thead>
<tr>
<th className="col-sm-4">Order Subtotal (up to)</th>
<th className="col-sm-2">Canada</th>
<th className="col-sm-2">US</th>
<th className="col-sm-2">International</th>
<th className="col-sm-1"></th>
</tr>
</thead>
<tbody>
{rows}
<tr><td colSpan="5">If the order subtotal is greater than the largest amount on the above chart, the following rates apply:</td></tr>
<Row key="Percentages" isRates={false} data={this.props.percentages} onCellChange={this.props.onPercentCellChange} onRowCommit={this.props.onPercentRowCommit} />
</tbody>
</Table>
);
}
});
var ShippingTable = React.createClass({
getInitialState: function() {
return this.props.initialData;
},
loadFromServer: function() {
$.getJSON(this.props.url, function(data) {
if (!data || data.Success === false) {
toastr.error('Error loading shipping costs. Please refresh the page and try again.');
} else if (this.isMounted()) {
// This change is not reflected in Row/Cell for this.state/props.percentages until after force a UI update (via handleAdd and handleCancel
// where this.state.add changes) even though a) percentages (a single object) holds valid values here and does all the way down to <Row />
// and b) there is no similar issue with this.state.data.
this.setState({ data: data.Value.ShippingCostMatrix, percentages: data.Value.ShippingPercentage });
}
}.bind(this));
},
componentDidMount: function() {
this.loadFromServer();
},
handleCellChange: function(rowIdx, prop, val) {
var row = copy(this.state.data[rowIdx]);
row[prop] = val;
var rows = this.state.data.slice();
rows[rowIdx] = row;
rows.sort(sortBySubtotal);
this.setState({data: rows});
},
handlePercentCellChange: function(prop, val) {
var row = copy(this.state.percentages);
row[prop] = val;
this.setState({percentages: row});
},
handleAdd: function(event) {
event.preventDefault();
this.setState({ add: true});
},
handleAddCancel: function(event) {
event.preventDefault();
this.setState({ add: false});
},
render: function() {
var ctrl;
if (this.state.add) {
ctrl = (<NewRow onAddCancel={this.handleAddCancel} onRowAdd={this.handleRowAdd} />);
}
else {
ctrl = (
<div>
<p><a onClick={this.handleAdd} className="btn btn-primary btn-lg">Add</a></p>
<Grid data={this.state.data} percentages={this.state.percentages}
onCellChange={this.handleCellChange} onPercentCellChange={this.handlePercentCellChange} onRowCommit={this.handleRowCommit} onPercentRowCommit={this.handlePercentRowCommit} onRowDelete={this.handleRowDelete} />
</div>
);
}
return <div>{ctrl}</div>;
}
});
//React.render(<ShippingTable initialData={ {data : [], percentages: { Canada: 1, Us: 1.25, International: 2.25, Id: 1 }, add: false} }
React.render(<ShippingTable initialData={ {data : [], percentages : {}, add: false} }
url="/admin/shipping/costs" update="/admin/shipping/update" create="/admin/shipping/create" delete="/admin/shipping/delete" updatePercentage="/admin/shipping/updatepercentage" />, document.getElementById('shipTable'));
答案 0 :(得分:7)
getInitialState应该返回初始组件状态,而不管道具。如果您确实想在状态中设置prop值,则应使用componentWillMount挂钩。见docs。
虽然看起来你在这里反对这个框架。当您想要更改prop值时,父组件应对此做出反应,并为子组件提供新的道具。