我正在尝试构建一个可以从用户捕获地址信息的React组件。该组件将作为更大页面信息的一部分进行调用。由于地址数据库访问具有自己的后端端点,因此我想将加载/保存封装到Address组件中。
如果“保存”按钮是组件的一部分,这很容易。像这样:
// Standalone with save button inside AddressBlock
class AddressBlock extends Component {
constructor(props) {
super(props);
this.state = {
isSaving: false,
isLoading: false
};
}
async componentDidMount() {
getAddress(this.props.address.id)
.then( address => this.props.setAddress(address) )
.then( () => this.setState({ isLoading: false }) );
}
async handleSave() {
this.setState({ isSaving: true });
saveAddress(this.props.address)
.then( () => this.setState({ isSaving: false }) );
}
validateForm() {
return this.props.address.street && this.props.address.city && ...
}
render() {
return ( { this.state.isLoading ? "Loading" :
<div>
<FormControl
id="street"
onChange={this.props.onChange}
value={this.props.address.street}
/>
// etc for city, state, postcode
<SpinnerButton
onClick={this.handleSave}
spin={this.state.isSaving}
disabled={!this.validateForm()}
>
Save
</SpinnerButton>
</div>
});
}
}
这将状态信息保存在祖先中,地址从this.props.address传入,并且onChange也传入。所以我认为这是自上而下的React精神。
但是当这个AddressBlock是一个包含其他对象的大页面的一部分时,又有一个“保存”按钮来保存整个页面呢?仅当所有子表单都有效时,才应启用该保存按钮。而且,我想保留AddressBlock知道如何进行自己保存的封装;但正如其他人所说,将“保存”事件传递给AddressBlock并不是很好。
我可以将保存功能移到父级,但是对于封装和重用,似乎AddressBlock应该保留它自己。
我能想到的最好的方法是将AddressBlock寄存器回调到其父级,如下所示,但感觉非常笨重。
必须有更好的方法 - 我错过了什么?
// Not very pretty way to take the save button up to parent
class AddressBlock extends Component {
async componentDidMount() {
...
this.props.registerSaveFunc(this.handleSave)
this.props.registerValidateFunc(this.validateForm)
}
render() {
...
// no Button in AddressBlock (moved to parent)
}
}
class CaptureSeveralThings extends Component {
constructor(props) {
super(props);
this.state = {
address: { id: this.props.addressId },
addressSaveFunc: null,
addressValidateFunc: null,
documents: [],
documentsSaveFunc: null,
documentsValidateFunc: null,
// etc where each knows how to load and save itself
};
}
handleSave = async () => {
return Promise.all([
this.state.addressSaveFunc,
this.state.documentsSaveFunc
]);
}
validateForm = () => this.state.addressValidateFunc() && this.state.documentsValidateFunc();
setAddress = async (address) => this.setState({ address });
registerAddressSaveFunc = async (f) => this.setState({ addressSaveFunc: f });
// etc. for SaveFunc and ValidateFunc for each component
handleAddrChange = async (event) => ... etc.
render() {
return (
<div>
<AddressBlock
address={this.props.address}
setAddress={this.setAddress}
registerSaveFunc={this.registerAddressSaveFunc}
registerValidateFunc={this.registerAddressValidateFunc}
onChange={this.handleAddrChange}
/>
<DocumentsBlock
documents={this.props.documents}
setDocs={this.setDocs}
registerSaveFunc={this.registerDocsSaveFunc}
registerValidateFunc={this.registerDocsValidateFunc}
onChange={this.handleAddrChange}
/>
// simplified for this example
// to get the spinner functionality would need to register even more callbacks for when each Block is saving
<Button
onClick={this.handleSave}
disabled={this.validateForm}
>
Save All
</Button>
</div>
)
}
}