首页 > 解决方案 > 如何将道具传递给父级(Material-UI React)

问题描述

所以我到处寻找解决方案,但没有运气。

我正在尝试将父 React 组件this.state.nextID作为属性传递给子组件。但是,当我尝试访问子项中的该属性时,它为空。我正在使用 Material-UI 进行反应,我认为问题出在withStyles功能上,因为当我检查页面的源时,我看到了节点key上的属性。withStyles(ServerBlock)但是该节点有一个子节点,即ServerBlock,没有key属性。我究竟做错了什么?

ConfigBlock.js

class ConfigBlock extends Component {
  constructor () {
    super()
    this.state = {
    children: [],
    nextID: 0
    }
    this.handleChildUnmount = this.handleChildUnmount.bind(this);
  }
  handleChildUnmount = (key) => {
    console.log(key)
    this.state.children.splice(key, 1);
    this.setState({children: this.state.children});
  }
  addServerBlock() {
    this.state.children.push({"id": this.state.nextID, "obj": <ServerBlock unmountMe={this.handleChildUnmount} key={this.state.nextID} />})
   this.setState({children: this.state.children})
    this.state.nextID += 1
  }
  addUpstreamBlock() {
    this.state.children.push({"id": this.state.nextID, "obj": <UpstreamBlock unmountMe={this.handleChildUnmount} key={this.state.nextID} />})
    this.setState({children: this.state.children})
    this.state.nextID += 1
  }
  render () {
    const {classes} = this.props;
    return (
      <div className={classes.container}>
        <Card className={classes.card}>
          <CardContent>
            <Typography className={classes.title} color="primary">
              Config
            </Typography>
                <div>
              {this.state.children.map((child, index) => {
                return (child.obj);
              })}
            </div>
          </CardContent>
          <CardActions>
            <Button variant="contained" color="primary" className={classes.button} onClick={ this.addServerBlock.bind(this) }>
              Server
              <AddIcon />
            </Button>
            <Button variant="contained" color="primary" className={classes.button} onClick={ this.addUpstreamBlock.bind(this) }>
              Upstream
              <AddIcon />
            </Button>
          </CardActions>
        </Card>
      </div>
    );
  }
}

ConfigBlock.propTypes = {
  classes: PropTypes.object.isRequired
};

export default withStyles(styles)(ConfigBlock);

ServerBlock.js

class ServerBlock extends Component {
  constructor (props) {
    super(props)
    this.state = {
      children: []
    }
  }
  addServerBlock() {
    this.state.children.push(<NginxEntry/>)
    this.setState({children: this.state.children})
  }
  deleteMe = () => {
    this.props.unmountMe(this.props.key);
  }
  render () {
    const {classes} = this.props;
    return (
      <div className={classes.container}>
        <Card className={classes.card}>
          <CardContent>
            <Typography className={classes.title} color="primary">
              Server
            </Typography>

          </CardContent>
          <CardActions>
            <Button variant="contained" color="primary" className={classes.button} onClick={() => { console.log('onClick'); }}>
              Key/Value
              <AddIcon />
            </Button>
            <Button variant="contained" color="primary" className={classes.button} onClick={() => { console.log('onClick'); }}>
              Location
              <AddIcon />
            </Button>
            <Button variant="contained" color="primary" className={classes.button} onClick={() => { console.log('onClick'); }}>
              Comment
              <AddIcon />
            </Button>
            <Button variant="contained" color="primary" className={classes.button} onClick={ this.deleteMe }>
              <DeleteIcon />
            </Button>
          </CardActions>
        </Card>
      </div>
    );
  }
}

ServerBlock.propTypes = {
  classes: PropTypes.object.isRequired
};

export default withStyles(styles)(ServerBlock);

标签: javascriptreactjsmaterial-ui

解决方案


key是一个特殊的 React 属性,它不是一个道具,即孩子永远无法访问它的值。如果孩子需要使用该值,请通过另一个道具(以及通过键)提供它,例如

<ServerBlock
  unmountMe={this.handleChildUnmount}
  key={this.state.nextID}
  id={this.state.nextID}
/>

...除此之外,您的代码非常不寻常。您不应该直接改变状态(您应该始终使用setState),并且您通常不会将整个组件存储在您的状态中。正如深思熟虑的那样,这里是您的ConfigBlock组件的替代(未经测试)实现,它使用setState并稍微改变了一些逻辑:

class ConfigBlock extends Component {
  constructor () {
    super()
    this.state = {
      nextID = 0,
      children: [],
    }
    this.handleChildUnmount = this.handleChildUnmount.bind(this);
    // bind this function once here rather than creating new bound functions every render
    this.addBlock = this.addBlock.bind(this)
  }

  handleChildUnmount = (key) => {
    console.log(key)
    this.setState(state => {
      return {
        // `state.children.splice(key, 1)`, aside from mutating the state,
        // will not work as expected after the first unmount as the ids and
        // array positions won't stay aligned
        children: state.children.slice().filter(child => child.id !== key)
      }
    })
  }

  // Consolidate the two addBlock functions, given we're determining the type
  // of component to render in the render function.
  addBlock(blockType) {
    this.setState(state => {
      return {
        children: [...state.children, { id: state.nextID, type: blockType }]
        nextID: state.nextID + 1
      }
    })
  }

  render () {
    const {classes} = this.props;
    return (
      <div className={classes.container}>
        <Card className={classes.card}>
          <CardContent>
            <Typography className={classes.title} color="primary">
              Config
            </Typography>
            <div>
              {this.state.children.map(child => {
                // determine the component to render here rather than in the handlers
                if (child.type === 'server') {
                  return <ServerBlock key={child.id} id={child.id} unmountMe={this.handleChildUnmount(child.id)} />
                } else if (child.type === 'upstream') {
                  return <UpstreamBlock key={child.id} id={child.id} unmountMe={this.handleChildUnmount(child.id)} />
                }
              })}
            </div>
          </CardContent>
          <CardActions>
            <Button variant="contained" color="primary" className={classes.button} onClick={this.addBlock('server')}>
              Server
              <AddIcon />
            </Button>
            <Button variant="contained" color="primary" className={classes.button} onClick={this.addBlock('upstream')}>
              Upstream
              <AddIcon />
            </Button>
          </CardActions>
        </Card>
      </div>
    );
  }
}

推荐阅读