【问题标题】:How to pass data between components in ReactJS?如何在 ReactJS 中的组件之间传递数据?
【发布时间】:2018-01-29 16:52:07
【问题描述】:

我制作了 2 个组件: 1) 内容(使用 fetch() 显示数据) 2)分页(共5页,每页16个对象)

我创建了两个状态变量,分别称为 start:0end:16。现在,每当用户单击页码时,我都想将开始和结束状态变量从分页组件注入到内容组件。

将开始和结束变量传递给内容的逻辑如下: 最初 end=16*1 (因为第 1 页而乘以 1)和 start= end-16 即 start=0

end = 16*2(因为第 2 页而乘以 2)和 start=end-16 即 start=16

这些开始和结束变量在内容组件的.slice() 中传递,以便onClick: 第 1 页应该显示文章 0 到 16 第 2 页应该显示第 16 到 32 条,依此类推,直到第 5 页

下面是分页和内容组件的代码:

content.js:

class Content extends Component {

    constructor(props){
        super(props);
        this.state = {
            matches:[],
            loading:true,
            callmatchinfo: false,
            matchid:''
        };
    }

    componentDidMount(){
        fetch('api/matches')
        .then(res => res.json())
        .then(res => {
      console.log(res)
      this.setState({
        matches:res.slice(this.props.start,this.props.end),  <---add new start and end everytime we click on new page number
        loading:false
      });
    })
    }

  viewstats(matchid){
    this.setState({
        callmatchinfo: true,
        matchid: matchid
    });
  }

  rendermatchinfo(){
    return <Matchinfo matchid={this.state.matchid} />
  }

    renderMatches() {
        return this.state.matches.map(match => {
            return (
                <div className="col-lg-3">
                    <div id="content">
                        <p className="match">MATCH {match.id}</p>
                        <h4>{match.team1}</h4>
                        <p>VS</p>
                        <h4>{match.team2}</h4>
                        <div className="winner">
                            <h3>WINNER</h3>
                            <h4>{match.winner}</h4>
                        </div>
                        <div className="stats">
                            <button type="button" onClick= {()=>{this.viewstats(match.id)}} className="btn btn-success">View Stats</button>
                        </div>
                    </div>
                </div>
            );
        })
    }

    render() {

        if (this.state.loading) {
            return <img src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif" />
        }
        else if(this.state.callmatchinfo){
        return <Matchinfo match_id={this.state.matchid} />
        }

    return (
      <div>
          <div className="row">
            {this.renderMatches()}
              </div>
          <div className="row">
            {this.state.callmatchinfo ? this.rendermatchinfo() : ''}
          </div>
      </div>
    );
  }
}

export default Content;

分页.js:

class Pagination extends Component {

    constructor(props){
        super(props)
        this.state = {
            start:0,
            end:16
        };
    }

    handleClick(){
        this.setState{
            end:getpagenumber()*16,
            start:end-16
        }
    }

    getpagenumber(val){
        return val;
    }


  render() {
    let{start,end} = this.state;

    return (
      <div>
        <Content/>
          <div className="container">                 
              <ul className="pagination">
               <li {this.getpagenumber(1)} onClick={this.handleClick}><a href="#">1</a></li>
                <li {this.getpagenumber(2)} onClick={this.handleClick}><a href="#">2</a></li>
                <li {this.getpagenumber(3)} onClick={this.handleClick}><a href="#">3</a></li>
                <li{this.getpagenumber(4)} onClick={this.handleClick}><a href="#">4</a></li>
                <li{this.getpagenumber(5)} onClick={this.handleClick}><a href="#">5</a></li>
              </ul>
          </div>
      </div>
    );
  }
}

export default Pagination;

显示匹配文章直到 16 的屏幕截图,即 end=16 :

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    你需要一个父组件来通信两个组件,例如:

    class ParentComponent extends Component {
        state = {
            start: 0,
            end: 16,
        };
    
        onChangePagination = (start, end) => {
            this.setState({
                start,
                end,
            });
        };
    
        render() {
            const { start, end } = this.state;
            return (
                <div>
                    <Content start={start} end={end} />
                    <Pagination
                        onChange={this.onChangePagination} />
                </div>
            );
        }
    }
    

    你需要在分页组件中做另一项更改,那就是在值更改时调用onChange 回调。

    handleClick() {
        const end = (getpagenumber() * 16);
        const start = (end - 16);
        this.setState({
            end: end,
            start: start,
        });
        this.props.onChange(start, end);
    }
    
    
    renderMatches() {
        // Slide matches before looping, props will have the
        // values from the pagination component.
        return this.state.matches.slice(this.props.start, this.props.end)
            .map(match => (
                <div className="col-lg-3">
                    <div id="content">
                        <p className="match">MATCH {match.id}</p>
                        <h4>{match.team1}</h4>
                        <p>VS</p>
                        <h4>{match.team2}</h4>
                        <div className="winner">
                            <h3>WINNER</h3>
                            <h4>{match.winner}</h4>
                        </div>
                        <div className="stats">
                            <button
                                type="button"
                                onClick={ () => this.viewstats(match.id) }
                                className="btn btn-success">
                                View Stats</button>
                        </div>
                    </div>
                </div>
            )
        );
    }
    

    您可能希望从分页组件中删除本地状态,因为您实际上并不需要它。

    【讨论】:

    • 所以你是说我应该将内容和分页组件包装在另一个组件中,对吗?我已经用屏幕截图更新了问题,它现在在第 1 页上显示 16 篇匹配文章,现在点击第 2 页它应该呈现从 17 到 33 的匹配文章,其余页面也类似。
    • 是的,如果您不使用 redux、mobox 或任何其他类似的库,则必须使用父组件与子组件进行通信。
    • 他还需要在传入新的开始和结束值时将componentWillReceiveProps 添加到Content 以切片状态。使用这种方法,最好进行一次初始api调用,将所有记录保存为Content 状态,然后在渲染时,您可以通过this.props.startthis.props.end 对显示数据进行切片。
    • @ChaseDeAnda 我是 ReactJS 的新手,所以无法完全理解您的方法,但您可以看到我使用的是 fetch(),它返回 577 个 JSON 对象,我每页只需要 16 个这就是为什么我如果可能的话,我在res 上使用.slice(),您能否发布您的答案。我知道我们可以存储它,而不是一次又一次地进行新的 API 调用。
    • @Crysfel 我是否需要像@ChaseDeAnda 所说的那样添加componentWillReceiveProps,因为我是Reactjs 的新手,我不熟悉componentWillReceiveProps
    【解决方案2】:

    Crysfel 有最简单的正确解决方案,因为您需要一个父组件来管理数据,并将其作为道具传递给它的子组件,但还有另一种方法更适合大型项目。

    如果你只是在学习,或者你正在做一些小事,绝对不要使用它,但Redux 是另一种方式。

    简单地说,它是一个高度复杂的存储桶,用于存放您的状态。现在,组件不再关心自己的状态是什么,而是将非展示状态放入 redux 存储中,组件使用选择器从存储中读取。

    在您的情况下,当单击 Pagination 以改变存储时,您将触发一个操作,这将导致组件侦听任何更改的数据以进行更新。

    【讨论】:

      【解决方案3】:

      如果你想传递 startend 道具,只需这样做:

      ....
      render() {
          let{start,end} = this.state;
      
          return (
            <div>
              <Content start={start} end={end} />
      
      .....
      

      这应该回答您如何将道具传递给Content 组件的具体问题。

      这可能会对你有所帮助,但我认为你还是应该考虑一些代码重构。

      【讨论】:

        【解决方案4】:

        您需要传递一个将状态从Content 更新为Pagination 的函数:

        将此代码移至Content 中的单独函数:

        updatePage(start, end) {
            fetch('api/matches')
                .then(res => res.json())
                .then(res => {
                    console.log(res)
                    this.setState({
                        // Add new start and end everytime we click on new page number.
                        matches: res.slice(start, end),
                        loading: false,
                    });
                });
        }
        

        然后将此作为道具传递给Pagination

        <Pagination updatePage={this.updatePage} />
        

        然后,每次更新Pagination 中的页面时,调用this.props.updatePage

        handleClick() {
            const end = this.getpagenumber() * 16;
            const start = end - 16;
            this.setState({
                end,
                start,
            });
            this.props.updatePage(start, end);
        }
        

        更新:

        Content 的简单更新,只获取一次所有数据,然后使用道具在每次渲染时对匹配进行切片:

        class Content extends Component {
        
            constructor(props){
                super(props);
                this.state = {
                    matches: [],
                    loading: true,
                    callmatchinfo: false,
                    matchid: '',
                };
            }
        
            componentDidMount() {
                fetch('api/matches')
                    .then(res => res.json())
                    .then(res => {
                        // On mount, load all data and save it in state.
                        this.setState({
                            allData: res,
                            loading: false,
                        });
                    });
            }
        
            renderMatches() {
                // Here we use slice on all the data and only return
                // 15 matches at a time for rendering.
                return this.state.allData.slice(this.props.start, this.props.end)
                    .map(match => (
                        <div className="col-lg-3">
                            <div id="content">
                                <p className="match">MATCH {match.id}</p>
                                <h4>{match.team1}</h4>
                                <p>VS</p>
                                <h4>{match.team2}</h4>
                                <div className="winner">
                                    <h3>WINNER</h3>
                                    <h4>{match.winner}</h4>
                                </div>
                                <div className="stats">
                                    <button
                                        type="button"
                                        onClick={
                                            () => { this.viewstats(match.id) }
                                        }
                                        className="btn btn-success">
                                        View Stats</button>
                                </div>
                            </div>
                        </div>
                    );
                })
            }
        
            render() {...}
        }
        
        export default Content;
        

        【讨论】:

        • 所以在您的代码中,分页是内容的子组件,对吗?
        • @stonerock 是的,分页是内容的子级。我看不出你现在是如何嵌套它们的,所以我猜了一下。
        • @stonerock 更新了答案,包括只提取一次数据。
        • 太好了,很有帮助。
        • @stonerock 对,只要你将startend 作为道具传递给Content,你就不需要它了
        猜你喜欢
        • 1970-01-01
        • 2017-11-07
        • 1970-01-01
        • 1970-01-01
        • 2020-10-16
        • 2017-07-09
        • 2023-04-01
        • 2020-04-02
        • 1970-01-01
        相关资源
        最近更新 更多