用于封装从db加载/保存的组件的React.js模式?

时间:2018-03-17 20:44:19

标签: reactjs

我正在尝试构建一个可以从用户捕获地址信息的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>
        
    )
  }
  
  
}

0 个答案:

没有答案