简体   繁体   中英

How is an argument for a callback in React being passed from the child component to the parent component?

I have been working through the React tutorial at https://reactjs.org/tutorial/tutorial.html and I am getting through it well enough. I have one thing that I haven't been able to wrap my head around though. Here is the code in full (codepen: https://codepen.io/gaearon/pen/EmmOqJ?editors=0010 ):

function Square(props) {  
    return (
      <button className="square"
              onClick={props.onClick}>

          {props.value}
      </button>
    );
}

function calculateWinner(squares) {
    const lines = [  
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6],
    ];

    for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];

        if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
            return squares[a];
        }
    }

    return null;
}

class Board extends React.Component {
    renderSquare(i) {
        return (
            <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)}/>  
        );
    }

    render() {  
        return (
            <div>
                <div className="status">{status}</div>
                <div className="board-row">
                    {this.renderSquare(0)}
                    {this.renderSquare(1)}
                    {this.renderSquare(2)}
                </div>
                <div className="board-row">
                    {this.renderSquare(3)}
                    {this.renderSquare(4)}
                    {this.renderSquare(5)}
                </div>
                <div className="board-row">
                    {this.renderSquare(6)}
                    {this.renderSquare(7)}
                    {this.renderSquare(8)}
                </div>
            </div>
        );
    }
}

class Game extends React.Component {
    constructor(props) {
        super(props);  

        this.state = {
            history: [{
                squares: Array(9).fill(null),
            }],
            xIsNext: true,
        };
    }

    handleClick(i) {
        const history = this.state.history;
        const current = history[history.length - 1];
        const squares = current.squares.slice();  

        if (calculateWinner(squares) || squares[i]) {  
            return;
        }

        squares[i] = this.state.xIsNext ? 'X' : 'O';

        this.setState({
            history: history.concat([{
                squares: squares,
            }]),  
            xIsNext: !this.state.xIsNext,
        });  
    }

    render() {
        const history = this.state.history;
        const current = history[history.length - 1];
        const winner = calculateWinner(current.squares);

        let status;

        if (winner) {
            status = 'Winner: ' + winner;
        } else {
            status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
        }

        return (
            <div className="game">
                <div className="game-board">
                    <Board  
                        squares={current.squares}
                        onClick={(i) => this.handleClick(i)}  
                    />
                </div>
                <div className="game-info">
                    <div>{status}</div>
                    <ol>{/* TODO */}</ol>
                </div>
            </div>
        );
    }
}

// ========================================

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

In the tutorial they pass a callback function handleClick(i) from the parent Game component to the child Board component and from there to the child Square component. My question is on how is the argument i set? My guess is that the starting point is when renderSquare(i) is called in the Board component. From there I am lost as to how i it makes its way to handleClick(i) . Is it stored in the 'onClick function object passed to Square from Board`?

My guess is that the starting point is when renderSquare(i) is called in the Board component. From there I am lost as to how i it makes its way to handleClick(i)

You are on the right track.

Within Board.render() , this.renderSquare(...) is called with a number 1~8 .

And renderSquare has an onClick handler, this.props.onClick(i) .

renderSquare(i) {
    return (
        <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)}/>  
    );
}

this.props is passed from the parent, Game.render() .
So this.props.onClick is from <Board onClick={...} />

class Game extends React.Component {
    handleClick(i) {
       ...
    }

    render() {
        ...
        return (
            <div className="game">
                <div className="game-board">
                    <Board  
                        squares={current.squares}
                        onClick={(i) => this.handleClick(i)}  
                    />
                </div>
                ...
            </div>
        );
    }
}

So this.props.onClick matches the <Board onClick={...} />
where it's implemented as (i) => this.handleClick(i) , in which this.handleClick(i) refers to Game.handleClick(i) .

Your guessings were good ! I tried to comment the key aspects of the code on this gist : https://gist.github.com/Sangrene/d6784a855e0e3324e5b499e3a5242c45

You are correct in your understanding of the flow and origin of the onClick function, it is defined in the higher level component Game.

The Game component defines the method onClick that can be invoked to handle something.

In reverse order,

  1. The HTML component [ button ] gives a promise to say, if you give me a callback function that matches a said signature, I will call it whenever something happens ( clicked in this event ).

  2. Now a higher component (parent to button) to the button Square function, is both interested in

    • using button and
    • interested in being informed when the event happens as per the promise button offered in point (1).

But because the Square function itself has no real use case for this event, it decided to delegate the handling of that event to its parent same way as the button did.

But it goes a step further to say, if you are interested in receiving this notification, my contract is further extended to say, I will only allow notification of the event if and only if you pass in your callback through a member of my input object as onClick .

  1. So another higher level component Board (parent to Square) will somehow need to satisfy component Square requirements of passing in an object with a member onClick . Notice some things though, the renderSquare method member of Board ,

    • Receives as an input argument 'i' that it uses to evaluate something (...) setting the value and onClick properties when initiating a new instance of Square , and subsequently returning that instance (because JS supports Higher Order Functions Higher Order Functions )
    • It defines an anonymous function as a delegate, of which it's implementation is just to return a member of the props called onClick ( it will appear that the Board also had a contract with any potential parent that there must be a member onClick that's passed on to it )...please note that since this syntax is not yet officially adopted, babel is used to transpile these code into something that a typical browser will understand.

    • renderSquare method and or Board itself is not doing anything to the onClick member that's been passed around except just returning it in its own anonymous function on onClick={() => this.props.onClick(i)}

    I assume you know how the result of that statement will look, but for argument's sake it basically becomes function onClick(){return [this].props.onClick(i);}

    1. The root component Game ( Board parent), is the one that defines and provides the actual implementation that satisfies the requirements of the onClick ( as defined by button component ) method and passes it on as props to the child component Board . Now it means whenever Board has an encounter with the onClick specifically from its props , it will essentially be accessing the anonymous function defined in Game which subsequently returns Game 's handleClick function see point 3.2 above.

To attempt to answer your question

My question is on how is the argument i set? My guess is that the starting point is when renderSquare(i) is called in the Board component. From there I am lost as to how i it makes its way to handleClick(i). Is it stored in the 'onClick function object passed toSquarefromBoard`?

The most important thing to notice here is that, all of this code higher up is all blueprint or definitions on how the code will work when active or invoked, think of it a just a pipeline. So the definition does not mean that the handleClick(i) is being called right away, infact it is not called up until later when our button component fires the event, when something causes / satisfied button to publish the event, of which when it happens, the reverse of these steps I've just tried to explain happens all the way down until the root Game's handleClick function gets invoked.

I realize that my answer could be very long but I hope it paints a message and hopefully helps you to sort of visualize the flow of things.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM