【问题标题】:React: How to render a parent component from child component?React:如何从子组件渲染父组件?
【发布时间】:2016-04-19 16:54:41
【问题描述】:

我的任务是为客户构建一个项目,该项目需要一个父组件包装三个子组件。具体来说,父组件按照如下方式渲染子组件:

父级 = 上层 + 中层 + 下层

PARENT 组件代表一组项目中的一个。

在 TOP 组件中有一个菜单按钮,显示组中所有项目的列表,其意图是如果用户单击其中一个链接,将显示所选项目(父级将“重新渲染到”新项目 - PARENT 中的逻辑将导致出现选定的第二个项目而不是第一个)。

我在这里说得松散,如果我在说什么不清楚,可以提供代码(我认为应该是)。

我遇到的主要问题是尝试从其中一个子组件“重新渲染”父组件。本质上,有一个显示相关项目列表的菜单按钮。单击这些项目之一应重新渲染父组件,但这次显示所选项目。但是,我不知道如何做到这一点,并希望得到一些建议或帮助。我花了一上午的时间研究这个主题并尝试了一些建议的方法,但无济于事。

也许我到目前为止所采取的方法并不是完成此类任务的最佳方法。无论如何,任何帮助都会非常有帮助。谢谢!


编辑:现在工作

编辑#2:由于这已经获得了相当多的意见,我只是想澄清这是一个概念证明,也是非常简化的原型/早期版本(没有花哨的编译等 - 这个想法是否可行事情......因为我们当时不确定)。自从我从事类似的工作已经有一段时间了,但我真的很感谢所有帮助我提出这样一个令人困惑的问题,以及当时一项非常有趣和具有挑战性的任务。

 import React from 'react';

import CardHeader from 'components/CardHeader';
import CardContent from 'components/CardContent';
import CardFooter from 'components/CardFooter';

module.exports = React.createClass({

  getInitialState: function () {
    return {
      fullData: '',

      //Core Card
      userId: '',
      cardStack: '',
      cardId: '',

      //Load Card
      loadCard: '1',

      //Optional Fields
      name: '',
      type: '',
      headline: '',
      subtitle: '',
      ctext: '',
      imageUrl: '',
      price: '',
      mapCoordinates: '',
      logoUrl: '',
      order: '',
      email: '',
      sponsorUrl: '',
      pollId: '',
      socialButton: ''
    };
  }
  ,

  componentWillMount: function () {

    //fetches cardStack and card API JSON
    /** JSON Structure:
     [
     cardStack: ...,
     cards: ...
     ]
     **/

    var fetch = function (userId, cardStackId) {

      //AJAX

      var json = {
        'cardStack': {'id': '1', 'name': 'Test Card Stack', 'userID': 'O1AB0001'},
        'cards': [{
          'id': '1',
          'name': 'Test Card 1',
          'cardStack': '1',
          'meta': 'meta_note',
          'socialButton': 'twitter',
          'sponsorUrl': 'https://iwantmyname.com/images/logo-url-shortener-droplr.png',
          'order': 1,
          'logoUrl': 'https://iwantmyname.com/images/logo-url-shortener-droplr.png',
          'product': {
            'headline': 'Headline Test',
            'subtitle': 'Subtitle Test',
            'ctext': 'Hello!!!!',
            'imageUrl': 'http://the-mpas.com/wp-content/uploads/2012/04/Image-pic-54-copy.jpg',
            'price': '20.00'
          }
        }, {
          'id': '2',
          'name': 'Test Card 2',
          'cardStack': '1',
          'meta': 'meta_note',
          'socialButton': 'twitter',
          'sponsorUrl': 'https://iwantmyname.com/images/logo-url-shortener-droplr.png',
          'order': 2,
          'logoUrl': 'https://iwantmyname.com/images/logo-url-shortener-droplr.png',
          'product': {
            'headline': 'Headline Test 2',
            'subtitle': 'Subtitle Test 2',
            'ctext': 'Hello 2!!!!',
            'imageUrl': 'http://the-mpas.com/wp-content/uploads/2012/04/Image-pic-54-copy.jpg',
            'price': '30.00'
          }
        }]
      };

      return json;
    };

    var that = this;

    var getCard = function (cardArray, cardOrder) {
      var card;
      for (var key in cardArray) {
        if (cardArray[key].order == cardOrder) {
          card = cardArray[key];
        }
      }

      if ('product' in card) {
        that.setState({
          type: 'product',
          headline: card.product.headline,
          subtitle: card.product.subtitle,
          ctext: card.product.ctext,
          imageUrl: card.product.imageUrl,
          price: card.product.price
        })
      }
      if ('article' in card) {
        that.setState({
          type: 'article',
          headline: card.article.headline,
          ctext: card.article.ctext
        })
      }

      if ('map' in card) {
        that.setState({
          type: 'map',
          mapCoordinates: card.map.mapCoordinates
        })
      }

      if ('relatedvideo' in card) {
        that.setState({
          type: 'relatedvideo',
          headline: card.relatedvideo.headline,
          ctext: card.relatedvideo.ctext,
          imageUrl: card.relatedvideo.imageUrl
        })
      }

      if ('poll' in card) {
        that.setState({
          type: 'poll',
          headline: card.poll.headline,
          subtitle: card.poll.subtitle,
          pollId: card.poll.pollId
        })
      }

      if ('imagegallery' in card) {
        that.setState({
          type: 'imagegallery',
          headline: card.imagegallery.headline,
          ctext: card.imagegallery.ctext,
          imageUrl: card.imagegallery.imageUrl
        })
      }

      if ('profile' in card) {
        that.setState({
          type: 'profile',
          headline: card.profile.headline,
          ctext: card.profile.ctext,
          imageUrl: card.profile.imageUrl
        })
      }

      if ('newsletter' in card) {
        that.setState({
          type: 'newsletter',
          email: card.newsletter.email
        })
      }
      return card;
    };

//Entry Point HERE
    var userId = 'O1AB0001', cardStackId = 1;
    var json = fetch(userId, cardStackId);
    var myCard = getCard(json.cards, this.state.loadCard);

//Set core data
    this.setState({

      //fulldata
      fullData: json,

      //card stack
      userId: json.cardStack.name,
      cardStack: json.cardStack.id,

      //card
      cardId: myCard.id,
      socialButton: myCard.socialButton,
      order: myCard.order,
      sponsorUrl: myCard.sponsorUrl,
      logoUrl: myCard.logoUrl,
      meta: myCard.meta,
      name: myCard.name
    });
  },

  setNew: function (nextState) {

    var nsFullData = nextState.fullData;
    var nsCards = nsFullData.cards;
    var nsCardStack = nsFullData.cardStack;

    var that = this;
    var getCard = function (cardArray, cardOrder) {
      var card;
      for (var key in cardArray) {
        if (cardArray[key].order == cardOrder) {
          card = cardArray[key];
        }
      }

      if ('product' in card) {
        that.setState({
          type: 'product',
          headline: card.product.headline,
          subtitle: card.product.subtitle,
          ctext: card.product.ctext,
          imageUrl: card.product.imageUrl,
          price: card.product.price
        })
      }
      if ('article' in card) {
        that.setState({
          type: 'article',
          headline: card.article.headline,
          ctext: card.article.ctext
        })
      }

      if ('map' in card) {
        that.setState({
          type: 'map',
          mapCoordinates: card.map.mapCoordinates
        })
      }

      if ('relatedvideo' in card) {
        that.setState({
          type: 'relatedvideo',
          headline: card.relatedvideo.headline,
          ctext: card.relatedvideo.ctext,
          imageUrl: card.relatedvideo.imageUrl
        })
      }

      if ('poll' in card) {
        that.setState({
          type: 'poll',
          headline: card.poll.headline,
          subtitle: card.poll.subtitle,
          pollId: card.poll.pollId
        })
      }

      if ('imagegallery' in card) {
        that.setState({
          type: 'imagegallery',
          headline: card.imagegallery.headline,
          ctext: card.imagegallery.ctext,
          imageUrl: card.imagegallery.imageUrl
        })
      }

      if ('profile' in card) {
        that.setState({
          type: 'profile',
          headline: card.profile.headline,
          ctext: card.profile.ctext,
          imageUrl: card.profile.imageUrl
        })
      }

      if ('newsletter' in card) {
        that.setState({
          type: 'newsletter',
          email: card.newsletter.email
        })
      }
      return card;
    };

    var myCard = getCard(nsCards, this.state.loadCard);

    this.setState({

      //fulldata
      fullData: nsFullData,

      //card stack
      userId: nsCardStack.name,
      cardStack: nsCardStack.id,

      //card
      cardId: myCard.id,
      socialButton: myCard.socialButton,
      order: myCard.order,
      sponsorUrl: myCard.sponsorUrl,
      logoUrl: myCard.logoUrl,
      meta: myCard.meta,
      name: myCard.name
    });
  },

  componentWillUpdate: function (nextProps, nextState) {
    if (nextState.loadCard !== this.state.loadCard) {
      this.setNew(nextState);
    }
  },

  render: function () {

    return (
      <div className='sg-cardBase'>
        <div className='sg-cardHeaderSection'>
          <CardHeader setLoadCard={i => this.setState({loadCard: i})} data={this.state}/>
        </div>
        <div className='sg-cardContentSection'>
          <CardContent data={this.state}/>
        </div>
        <div className='sg-cardFooterSection'>
          <CardFooter data={this.state}/>
        </div>
      </div>
    );
  }
});

【问题讨论】:

  • 请看上面编辑过的评论。主要问题是当状态改变时 Base 根本不会重新渲染。文档facebook.github.io/react/docs/component-specs.html 说不要在 ComponentWillUpdate 中设置状态...
  • 我遇到的主要障碍是,点击我的菜单项后,我收到“'this' is undefined”

标签: reactjs


【解决方案1】:

您需要向子组件传递一个回调来修改父组件的状态。然后父级可以根据该状态重新渲染。例如:

var Parent = React.createClass({
  getInitialState: function() {
    return { index: 1 };
  },
  render: function() {
    return <div>
        <Child setIndex={i => this.setState({index: i})}/>
        <p>{this.state.index}</p>
      </div>
  }
})

var Child = React.createClass({
  render: function() {
    <button onClick={() => this.props.setIndex(5)}/>
  }
});

在您的情况下,当前选定的项目应该存储在您的父组件的状态中,并将回调传递给您的顶级组件,以便它可以更改选定的项目。

【讨论】:

  • 嗨!感谢您的建议。这种情况比我想象的要复杂一些——我们使用的是一个多维数组,用于我们域中的多态性和四个反应组件(顶部、中间、底部和底部)。需要发生的是,通过在 Top 中选择一个新项目,新项目的数据通过 Base 传递到子组件中。现在,我们一直在尝试使用 Base 的状态将属性传递给其他三个组件。但是,您提出的建议虽然可靠,但鉴于目前的安排,显然对我们没有帮助。
  • (续)因为我们正在通过回调改变状态。但是回调必须触发几个 setState 调用,然后通过相同的渲染逻辑传递新数据。
  • 很抱歉。是的,请立即查看原帖。
  • 得到它的工作 - 诀窍是使用条件逻辑来阻止“setState/componentWillUpdate Chain Effect”,从而在一个项目上设置状态会触发其他几个被设置的状态,从而再次触发它们等等。跨度>
【解决方案2】:

我假设顶部和底部保持不变,中间根据菜单变化。

顶部从父级获得回调。当在顶部选择一个选项时,它会调用回调changeCurrentView,并通知它currentView。回调changeCurrentView 设置父状态,调用渲染方法。

您现在可以更改父级中的渲染中间,但我建议中间将重新渲染不同的视图。所以 middle 从父级获取 currentView 作为视图,并重新渲染所需的视图:

class Parent extends React.Component {

  constructor(props) {
    super(props);
      
    this.state = {
      currentView: 0
    };
      
    this.changeCurrentView = this.changeCurrentView.bind(this);
  }
    
  changeCurrentView(currentView) {
    this.setState({
      currentView
    });
  }
    
  render() {
    return (
      <div>
      <Top changeView={ changeCurrentView } />
        
      <Middle currentView = { this.state.currentView } />
        
      <Bottom />
      </div>
    );
  }
}

const views = [
  <View1 />,
  <View2 />,
  <View3 />
];

const Middle = ({ currentView }) => (
  <div>{
    views[currentView];
  </div>
);

【讨论】:

  • 嗨!感谢您的建议。这种情况比我想象的要复杂一些——我们使用的是一个多维数组,用于我们域中的多态性和四个反应组件(顶部、中间、底部和底部)。需要发生的是,通过在 Top 中选择一个新项目,新项目的数据通过 Base 传递到子组件中。现在,我们一直在尝试使用 Base 的状态将属性传递给其他三个组件。但是,您提出的建议虽然可靠,但鉴于目前的安排,显然对我们没有帮助。
  • (续)因为我们正在通过回调改变状态。但是回调必须触发几个 setState 调用,然后通过相同的渲染逻辑传递新数据
  • 得到它的工作 - 诀窍是使用条件逻辑来阻止“setState/componentWillUpdate Chain Effect”,从而在一个项目上设置状态会触发其他几个被设置的状态,从而再次触发它们等等。跨度>
猜你喜欢
  • 1970-01-01
  • 2019-02-26
  • 1970-01-01
  • 1970-01-01
  • 2020-10-15
  • 2017-07-23
  • 2018-05-05
  • 1970-01-01
  • 2018-11-06
相关资源
最近更新 更多