如何从父级调用子级组件方法

时间:2019-10-01 08:29:33

标签: javascript reactjs frontend

在我的Reactjs应用程序中,我需要有一个名为 Wizard.js 的父组件(一个向导)和一些名为 PrimaryForm.js <的子组件(向导的步骤)< /strong>、SecondaryForm.js等。它们都是具有某些本地验证功能的基于类的组件。

“上一步”和“下一步”按钮位于向导向导中。

为推进向导的下一步,我试图从PrimaryForm调用一个方法。我在Stackoverflow中检查了类似的问题;尝试使用ref或forwardRef,但我无法使其正常工作。我目前收到“ TypeError:无法读取属性'handleCheckServer'为null ”的错误。

下面是我的父母和孩子班。任何有关我会做错事的帮助都将受到赞赏。

Wizard.js:

import React, { Component } from 'react';
...

const getSteps = () => {
  return [
    'Info',
    'Source Details',
    'Target Details',
    'Configuration'
  ];
}

class Wizard extends Component {
  constructor(props) {
    super(props);
    this.firstRef = React.createRef();
    this.handleNext = this.handleNext.bind(this);

    this.state = {
      activeStep: 1,
    }
}

  componentDidMount() {}

  handleNext = () =>  {
    if (this.state.activeStep === 1) {
      this.firstRef.current.handleCheckServer(); <<<<<<<<<<<<<<<<< This is where I try to call child method
    }
    this.setState(state => ({
      activeStep: state.activeStep + 1,
    }));
  };

  handleBack = () => {
    this.setState(state => ({
      activeStep: state.activeStep - 1,
    }));
  };

  handleReset = () => {
    this.setState({
      activeStep: 0,
    });
  };

  render() {
    const steps = getSteps();
    const currentPath = this.props.location.pathname;
    const { classes } = this.props;

    return (
      <React.Fragment>
        <CssBaseline />
        <Topbar currentPath={currentPath} />
        <div className={classes.root}>
          <Grid container spacing={2} justify="center" direction="row">
            <Grid container spacing={2} className={classes.grid} justify="center" direction="row">
              <Grid item xs={12}>
                <div className={classes.topBar}>
                  <div className={classes.block}>
                    <Typography variant="h6" gutterBottom>Wizard</Typography>
                    <Typography variant="body1">Follow the wizard steps to create a configuration.</Typography>
                  </div>
                </div>
              </Grid>
            </Grid>
            <Grid container spacing={2} alignItems="center" justify="center" className={classes.grid}>
              <Grid item xs={12}>
                <div className={classes.stepContainer}>
                  <div className={classes.bigContainer}>
                    <Stepper classes={{ root: classes.stepper }} activeStep={this.state.activeStep} alternativeLabel>
                      {steps.map(label => {
                        return (
                          <Step key={label}>
                            <StepLabel>{label}</StepLabel>
                          </Step>
                        );
                      })}
                    </Stepper>
                  </div>
                  <PrimaryForm ref={this.firstRef} />
                </div>
              </Grid>
            </Grid>
            <Grid container spacing={2} className={classes.grid}>
            <Grid item xs={12}>
              <div className={classes.flexBar}>
                <Tooltip title="Back to previous step">
                  <div>
                    <Button variant="contained"
                      disabled={(this.state.activeStep === 0)}
                      className={classes.actionButton}
                      onClick={this.handleBack}
                      size='large'>
                      <BackIcon className={classes.rightIcon} />Back
                      </Button>
                  </div>
                </Tooltip>
                <Tooltip title="Proceed the next step">
                  <div>
                    <Button
                      variant="contained" className={classes.actionButton}
                      color="primary"
                      size='large'
                      disabled={!(!this.state.isFormValid || this.state.isTestWaiting)}
                      onClick={this.handleNext}>
                    <ForwardIcon className={this.props.classes.rightIcon}/>Next</Button>
                  </div>
                </Tooltip>

                <Tooltip title="Cancel creating new configuration">
                  <Button variant="contained" color="default" className={classes.actionButton}
                    component={Link} to={'/configs'} style={{ marginLeft: 'auto' }}>
                    <CancelIcon className={classes.rightIcon} />Cancel
                      </Button>
                </Tooltip>
              </div>
            </Grid>
          </Grid>
          </Grid>
        </div>
      </React.Fragment>
    )
  }
}
export default withRouter(withStyles(styles)(Wizard));

PrimaryForm.js:

import React, { Component } from 'react';
...

class PrimaryForm extends Component {

  constructor(props) {
    super(props);
    this.handleCheckServer = this.handleCheckServer.bind(this);

    this.state = {
      hostname: {
        value: "localhost",
        isError: false,
        errorText: "",
      },
      serverIp: {
        value: "127.0.0.1",
        isError: false,
        errorText: "",
      },
      isFormValid: true,
      isTestValid: true,
      testErrorMessage: "",
      isTestWaiting: false,
    };
  }

  componentDidMount() { }

  handleCheckServer() {
    alert('Alert from Child. Server check will be done here');
  }

  evaluateFormValid = (prevState) => {
    return ((prevState.hostname.value !== "" && !prevState.hostname.isError) &&
      (prevState.serverIp.value !== "" && !prevState.serverIp.isError));
  };

  handleChange = event => {
    var valResult;
    switch (event.target.id) {
      case 'hostname':
        valResult = PrimaryFormValidator.validateHostname(event.target.value, event.target.labels[0].textContent);
        this.setState({
          ...this.state,
          hostname:
          {
            value: event.target.value,
            isError: valResult.isError,
            errorText: valResult.errorText,
          },
        });
        break;
      case 'serverIp':
        valResult = PrimaryFormValidator.validateIpAddress(event.target.value, event.target.labels[0].textContent);
        this.setState({
          ...this.state,
          serverIp:
          {
            value: event.target.value,
            isError: valResult.isError,
            errorText: valResult.errorText,
          }
        });
        break;
      default:
    }
    this.setState(prevState => ({
      ...prevState,
      isFormValid: this.evaluateFormValid(prevState),
    }));
  }

  render() {
    const { classes } = this.props;

    return (
      <React.Fragment>
        <div className={classes.bigContainer}>
          <Paper className={classes.paper}>
            <div>
              <div>
                <Typography variant="subtitle1" gutterBottom className={classes.subtitle1} color='secondary'>
                  Primary System
              </Typography>
                <Typography variant="body1" gutterBottom>
                  Information related with the primary system.
              </Typography>
              </div>
              <div className={classes.bigContainer}>
                <form className={classes.formArea}>
                  <TextField className={classes.formControl}
                    id="hostname"
                    label="FQDN Hostname *"
                    onChange={this.handleChange}
                    value={this.state.hostname.value}
                    error={this.state.hostname.isError}
                    helperText={this.state.hostname.errorText}
                    variant="outlined" autoComplete="off" />
                  <TextField className={classes.formControl}
                    id="serverIp"
                    label="Server Ip Address *"
                    onChange={this.handleChange}
                    value={this.state.serverIp.value}
                    error={this.state.serverIp.isError}
                    helperText={this.state.serverIp.errorText}
                    variant="outlined" autoComplete="off" />
                </form>
              </div>
            </div>
          </Paper>
        </div>
      </React.Fragment>
    )
  }
}
export default withRouter(withStyles(styles)(PrimaryForm));

(ps:我想在没有其他框架(例如Redux等)的情况下解决此问题)

1 个答案:

答案 0 :(得分:1)

打字稿中的例子。 这个想法是父母将其回调传递给孩子。子级调用父级的回调函数,提供自己的回调,例如子回调作为参数。父级将得到的内容(子级回调)存储在类成员变量中,并在以后调用。

import * as React from 'react'

interface ICallback {
  (num: number): string
}

type ChildProps = {
  parent_callback: (f: ICallback) => void;
}

class Child extends React.Component {
  constructor(props: ChildProps) {
    super(props);
    props.parent_callback(this.childCallback);
  }

  childCallback: ICallback = (num: number) => {
    if (num == 5) return "hello";
    return "bye";
  }

  render() {
    return (
      <>
        <div>Child</div>
      </>
    )
  }
}

class Parent extends React.Component {
  readonly state = { msg: "<not yet set>" };

  letChildRegisterItsCallback = (fun: ICallback) => {
    this.m_ChildCallback = fun;
  }

  callChildCallback() {
    const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
    console.log("Child callback returned string: " + str);
    return str;
  }

  componentDidMount() {
    this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
  }

  render() {
    return (
      <>
        <Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
        <div>{this.state.msg}</div>
      </>
    )
  }

  m_ChildCallback: ICallback | undefined = undefined;
}

P.S。
Javascript中的相同代码。唯一的区别是interface, type, readonly和类型注释被取出。粘贴到here中,确认它是有效的ES2015 Stage 2代码。

class Child extends React.Component {
  constructor(props) {
    super(props);
    props.parent_callback(this.childCallback);
  }

  childCallback = (num) => {
    if (num == 5) return "hello";
    return "bye";
  }

  render() {
    return (
      <>
        <div>Child</div>
      </>
    )
  }
}

class Parent extends React.Component {
  state = { msg: "<not yet set>" };

  letChildRegisterItsCallback = (fun) => {
    this.m_ChildCallback = fun;
  }

  callChildCallback() {
    const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
    console.log("Child callback returned string: " + str);
    return str;
  }

  componentDidMount() {
    this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
  }

  render() {
    return (
      <>
        <Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
        <div>{this.state.msg}</div>
      </>
    )
  }

  m_ChildCallback = undefined;
}