【问题标题】:How to make a button onClick in one component call a function in a different component React如何使一个组件中的按钮 onClick 调用另一个组件中的函数 React
【发布时间】:2021-06-10 23:43:49
【问题描述】:

我有一个带有 NavBar 和网格系统的 react 应用程序。这个 NavBar 显示一些信息并具有运行应用程序的按钮,网格系统包含功能并可视化应用程序。我想让 NavBar 按钮单击触发一个函数来调用网格系统组件,特别是 animateAlgorithm 函数。如您所见,我已经有一个提供程序在这两个组件之间共享一些状态信息。我不明白的是如何让它调用函数。

https://github.com/austinedger0811/path-finding-visualization

App.js

import GridContainer from './Components/GridContainer'
import NavBar from './Components/NavBar'

import {OptionsProvider} from './Context/OptionsContext'

import './App.css';

function App() {

  return (
    <OptionsProvider>
      <div className="App">
        <NavBar />
        <GridContainer rows={40} colums={40} />
      </div>
    </OptionsProvider>
  );
}

export default App;

NavBar.js

import React, { useState, useContext } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import Typography from '@material-ui/core/Typography'
import IconButton from '@material-ui/core/IconButton'
import MenuIcon from '@material-ui/icons/Menu'
import Button from '@material-ui/core/Button'
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'

import { OptionsContext } from '../Context/OptionsContext'


const useStyles = makeStyles((theme) => ({
    root: {
      flexGrow: 1,
    },
    menuButton: {
      marginRight: theme.spacing(2),
    },
        button: {
            marginRight: theme.spacing(2),
        },
    title: {
        marginRight: theme.spacing(12)
    },
        formControl: {
            margin: theme.spacing(1),
            minWidth: 120,
        },
        listItemSecondaryText: {
            color: 'lightgray',
        },
  }));

    const algorithmOptions = [
        'Breadth First Search',
        'Depth First Search',
        'Dijkstra\'s',
        'Other',
    ];

    const wallOptions = [
        'None',
        'Random Wall Generation',
        'Randomized Depth First Search',
        'Recursive Division'
    ];
    
function NavBar() {

    const classes = useStyles();

        const [anchorElAlgorithm, setAnchorElAlgorithm] = React.useState(null);
        const [anchorElWall, setAnchorElWall] = React.useState(null);

        const { algorithmIndex, setAlgorithmIndex, wallIndex, setWallIndex } = useContext(OptionsContext);

        const handleClickAlgorithmListItem = (event) => {
            setAnchorElAlgorithm(event.currentTarget);
        };

        const handleClickWallListItem = (event) => {
            setAnchorElWall(event.currentTarget);
        };

        const handleAlgorithmItemClick = (event, index) => {
            setAlgorithmIndex(index);
            setAnchorElAlgorithm(null);
        };

        const handleWallItemClick = (event, index) => {
            setWallIndex(index);
            setAnchorElWall(null);
        }
    
        const handleClose = () => {
            setAnchorElAlgorithm(null);
            setAnchorElWall(null);
        };

    return (
            <div className={classes.root}>
                <AppBar position="static">
                    <Toolbar>
                        <IconButton className={classes.menuButton} edge="start" color="inherit" aria-label="menu">
                            <MenuIcon />
                        </IconButton>
                        <Typography className={classes.title} variant="h6">Path Finding Visualizer</Typography>
                        <List component="nav" aria-label="algorithm selector">
                            <ListItem
                                button
                                aria-aria-haspopup="true"
                                aria-controls="search-algorithm"
                                aria-label="Search Algorithm"
                                onClick={handleClickAlgorithmListItem}
                            >
                                <ListItemText primary="Search Algorithm" secondary={algorithmOptions[algorithmIndex]} classes={{ secondary: classes.listItemSecondaryText }} />
                            </ListItem>
                        </List>
                        <Menu
                            id="search-algorithm"
                            anchorEl={anchorElAlgorithm}
                            keepMounted
                            open={Boolean(anchorElAlgorithm)}
                            onClose={handleClose}
                        >
                            {algorithmOptions.map((option, index) => (
                                <MenuItem
                                    key={algorithmOptions}
                                    selected={index === algorithmIndex}
                                    onClick={(event) => handleAlgorithmItemClick(event, index)}
                                >
                                    {option}
                                </MenuItem>
                            ))}
                        </Menu>


                        <List component="nav" aria-label="add walls">
                            <ListItem
                                button
                                aria-aria-haspopup="true"
                                aria-controls="add-walls"
                                aria-label="Add Walls"
                                onClick={handleClickWallListItem}
                            >
                                <ListItemText primary="Wall Generation" secondary={wallOptions[wallIndex]} classes={{ secondary: classes.listItemSecondaryText }} />
                            </ListItem>
                        </List>
                        <Menu
                            id="add-walls"
                            anchorEl={anchorElWall}
                            keepMounted
                            open={Boolean(anchorElWall)}
                            onClose={handleClose}
                        >
                            {wallOptions.map((option, index) => (
                                <MenuItem
                                    key={wallOptions}
                                    selected={index === wallIndex}
                                    onClick={(event) => handleWallItemClick(event, index)}
                                >
                                    {option}
                                </MenuItem>
                            ))}
                        </Menu>
                        <Button variant="contained" color="secondary" className={classes.button} disableElevation>Run Visualization</Button>
                        <Button variant="contained" color="secondary" className={classes.button} disableElevation>Reset</Button>
                    </Toolbar>
                </AppBar> 
      </div>
    )
}

export default NavBar

GridContainer.js

import React, {useState, useEffect, useContext } from 'react'
import makeStyles from '@material-ui/core/styles/makeStyles'
import Button from '@material-ui/core/Button'
import Node from './Node'

import { OptionsContext } from '../Context/OptionsContext'

import './Grid.css'

const useStyles = makeStyles({
    root: {
        display: 'flex',
        justifyContent: 'center'
    },
    grid: props => ({
        display: 'grid',
        gridTemplateColumns: `repeat(${props.colums}, 1fr)`, 
        gridTemplateRows: `repeat(${props.rows}, 1fr)`,
        alignSelf: 'flex-start',
        width: props.colums * 20,
        height: props.rows * 20,
        justifyContent: 'center',
    }),
});

function GridContainer(props) {

    let classes = useStyles(props);
    const { rows, colums } = props;
    const { algorithmIndex, wallIndex } = useContext(OptionsContext);

    var visited = [];
    var path = [];
    const [Grid, setGrid] = useState([]);

    useEffect(() => {
        initGrid();
    }, []);

    var start = [4, rows / 2];
    var end = [colums - 5, colums / 2];

    const initGrid = () => {
        var grid = [];
        for (let row = 0; row < rows; row++) {
            grid.push([])
            for (let col = 0; col < colums; col++) {
                grid[row].push(createNode(row, col));
            }
        }
        setGrid([...grid]);
    }

    const createNode = (row, col) => {
        return {
            row,
            col,
            isStart: row === start[0] && col === start[1],
            isEnd: row === end[0] && col === end[1],
            isVisited: false,
            isWall: false,
            isPath: false,
            distance: Infinity,
            prevNode: null,
        };
     };

     const bfs = () => {

        var location = {
            row: start[0],
            col: start[1],
        };
    
        var grid = Grid;
        var queue = [];
        queue.push(location);
    
        while (queue.length) {
            var currentLocation = queue.shift();
            var row = currentLocation.row;
            var col = currentLocation.col;
            if (row === end[0] && col === end[1]) {
                setGrid(...[grid]);
                getPath(grid);
                return true;
            }
            if (grid[row][col].isVisited === false) {
                grid[row][col].isVisited = true;
                visited.push(grid[row][col]);
            }else {
                continue;
            }
            var neighbors = getNeighbors(grid, row, col);
            for (let neighbor of neighbors) {
                if (grid[neighbor.row][neighbor.col].isVisited !== true) {
                    queue.push(neighbor);
                    grid[neighbor.row][neighbor.col].prevNode = currentLocation;
                }
            }
        }

        return false;
    };

    const getNeighbors = (grid, row, col) => {
        let neighbors = [];
        if (validNode(grid, row, col - 1)) {
            neighbors.push({
                row: row,
                col: col - 1,
            });
        }
        if (validNode(grid, row, col + 1)) {
            neighbors.push({
                row: row,
                col: col + 1,
            });
        }
        if (validNode(grid, row - 1, col)) {
            neighbors.push({
                row: row - 1,
                col: col,
            });
        }
        if (validNode(grid, row + 1, col)) {
            neighbors.push({
                row: row + 1,
                col: col,
            });
        }
        return neighbors;
    };

    const validNode = (grid, row, col) => {
        var rowLength = grid.length;
        var colLength = grid[0].length;
        if (row < 0 || row >= rowLength || col < 0 || col >= colLength) {
            return false;
        }
        if (grid[row][col].isWall) {
            return false;
        }
        return true;
    };

    const getPath = (grid) => {
        var currentNodeCord = {
            row: end[0],
            col: end[1],
        }
        path.push(currentNodeCord);
        var curRow = currentNodeCord.row;
        var curCol = currentNodeCord.col;
        var prevNodeCord = grid[curRow][curCol].prevNode;
        while (prevNodeCord !== null) {
            currentNodeCord = prevNodeCord;
            path.push(currentNodeCord);
            curRow = currentNodeCord.row;
            curCol = currentNodeCord.col;
            var curNode = grid[curRow][curCol];
            prevNodeCord = curNode.prevNode;
        }
        path.reverse();
    };

    const animateAlgorithm = () => {
        
        bfs();
        console.log(`Visited length: ${visited.length}`)
        for (let i = 1; i <= visited.length; i++) {
            if (i === visited.length) {
                setTimeout(() => {
                    drawPath();
                }, 7 * i);
            } else {
                let nodeCord = visited[i];
                let row = nodeCord.row;
                let col = nodeCord.col;
                setTimeout(() => {
                    markVisited(row, col);
                }, 6 * i);  
            }
        }
    };

    const drawPath = () => {
        for (let i = 1; i < path.length - 1; i++) {
            let nodeCord = path[i];
            let row = nodeCord.row;
            let col = nodeCord.col;
            setTimeout(() => {
                markPath(row, col);
            }, 40 * i);
        }
    };

    const markPath = (row, col) => {
        document.getElementById(`node-${row}-${col}`).className = 'node path';
    };

    const markVisited = (row, col) => {
        document.getElementById(`node-${row}-${col}`).className = 'node visited';
    };

    const addRandomWalls = (threshold) => {
        let grid = [...Grid];
        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < colums; col++) {
                if (!grid[row][col].isStart && !grid[row][col].isEnd) {
                    grid[row][col].isWall = (Math.floor(Math.random() * 10) > threshold);
                }
            }
        }
        setGrid(grid);
    };

    const resetGridColors = () => {
        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < colums; col++) {
                if (Grid[row][col].isStart !== true && Grid[row][col].isEnd !== true){
                    document.getElementById(`node-${row}-${col}`).className = 'node';
                }
            }
        }
    };

    const logStates = () => {
        console.log(Grid)
        console.log(visited)
        console.log(path)
    };

    const reset = () => {
        visited = [];
        path = [];
        resetGridColors();
        initGrid();
    };

    var GridMap = Grid.map((row, rowIndex) => {
        return (
            <div key={rowIndex}>
                {row.map((node, nodeIndex) => {
                    const { row, col, isStart, isEnd, isWall, isPath, isVisited } = node;
                    return (
                        <Node
                            key={`${row}${col}`}
                            width={20}
                            height={20}
                            row={row}
                            col={col}
                            isStart={isStart}
                            isEnd={isEnd}
                            isWall={isWall}
                            isPath={isPath}
                            isVisited={isVisited}
                        /> 
                    );
                })}
            </div>
        );
    })

    return (
        <>
            <div className={classes.root}>
                <div className={classes.grid}>
                    {GridMap}
                </div>
            </div>
            <Button variant="contained" color="primary" onClick={ () => animateAlgorithm() }>Animate Algorithm</Button>
            <Button variant="contained" color="primary" onClick={ () => addRandomWalls(6) }>Add Random Walls</Button>
            <Button variant="contained" color="primary" onClick={ () => reset() }>Reset</Button>
            <Button variant="contained" color="secondary" onClick={ () => logStates() }>Log Data</Button>   
        </>
    )
}

export default GridContainer

OptionsContext.js

import React, { useState, createContext, useMemo } from 'react'

export const OptionsContext = createContext();

export const OptionsProvider = (props) => {

    const [algorithmIndex, setAlgorithmIndex] = useState(0);
    const [wallIndex, setWallIndex] = useState(0);
    const menuValue= useMemo(() => ({
        algorithmIndex, setAlgorithmIndex,
        wallIndex, setWallIndex,
    }), [algorithmIndex, wallIndex]);

    return (
        <OptionsContext.Provider value={menuValue}>
            {props.children}
        </OptionsContext.Provider>
    );
};

任何帮助将不胜感激!

【问题讨论】:

  • 究竟想用GridContainer 组件做什么?隐藏/显示它?调用里面定义的函数?刷新里面的一些数据?
  • 我希望它在 GridContainer.js 中调用函数 animateAlgorithm()
  • 您需要使用forwardRef 包装您的GridContainer。使用反应useImperativeHandle 在其引用中定义animateAlgorithm。然后使用useRef 创建引用并将其传递给您的GridContainer。如果您需要进一步的帮助,我稍后会发布答案,但现在我要睡觉了。
  • @RômuloBourgetNovas 感谢您的回复。如果您愿意,我希望能提供一些关于我应该如何实现它的额外信息!

标签: javascript reactjs


【解决方案1】:

您需要做的是使用useImperativeHandle,这样您就可以从组件外部访问该功能。

首先,调用useRef 创建一个引用并将其作为prop 传递给您的GridContainer,稍后我们将在组件内部使用fowardRef 处理ref。然后,您需要将animateAlgorithm 包装在一个处理函数中,并将其用作NavBar 中的道具,以便在单击按钮时调用它。

App.js

import React, {useRef} from 'react';
import GridContainer from './Components/GridContainer'
import NavBar from './Components/NavBar'

import {OptionsProvider} from './Context/OptionsContext'

import './App.css';

function App() {
  const containerRef = useRef(null);

  const handleClick = () => {
    if (containerRef?.current) containerRef.current.animateAlgorithm();
  };

  return (
    <OptionsProvider>
      <div className="App">
        <NavBar handleClick={handleClick} />
        <GridContainer ref={containerRef} rows={40} colums={40} />
      </div>
    </OptionsProvider>
  );
}

export default App;

现在我们需要正确使用在NavBar 中定义的handleClick 属性。因此,使用 NavBar handleClick 属性设置按钮的 onClick 属性。您从未指定要单击哪个按钮,因此我假设该按钮具有 Run Visualization 内容。如果不是这个,您可以将onClick 移动到您想要的另一个按钮。

NavBar.js

import React, { useState, useContext } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import Typography from '@material-ui/core/Typography'
import IconButton from '@material-ui/core/IconButton'
import MenuIcon from '@material-ui/icons/Menu'
import Button from '@material-ui/core/Button'
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'

import { OptionsContext } from '../Context/OptionsContext'


const useStyles = makeStyles((theme) => ({
    root: {
      flexGrow: 1,
    },
    menuButton: {
      marginRight: theme.spacing(2),
    },
        button: {
            marginRight: theme.spacing(2),
        },
    title: {
        marginRight: theme.spacing(12)
    },
        formControl: {
            margin: theme.spacing(1),
            minWidth: 120,
        },
        listItemSecondaryText: {
            color: 'lightgray',
        },
  }));

    const algorithmOptions = [
        'Breadth First Search',
        'Depth First Search',
        'Dijkstra\'s',
        'Other',
    ];

    const wallOptions = [
        'None',
        'Random Wall Generation',
        'Randomized Depth First Search',
        'Recursive Division'
    ];
    
function NavBar(props) {
    const {handleClick} = props;
    const classes = useStyles();

        const [anchorElAlgorithm, setAnchorElAlgorithm] = React.useState(null);
        const [anchorElWall, setAnchorElWall] = React.useState(null);

        const { algorithmIndex, setAlgorithmIndex, wallIndex, setWallIndex } = useContext(OptionsContext);

        const handleClickAlgorithmListItem = (event) => {
            setAnchorElAlgorithm(event.currentTarget);
        };

        const handleClickWallListItem = (event) => {
            setAnchorElWall(event.currentTarget);
        };

        const handleAlgorithmItemClick = (event, index) => {
            setAlgorithmIndex(index);
            setAnchorElAlgorithm(null);
        };

        const handleWallItemClick = (event, index) => {
            setWallIndex(index);
            setAnchorElWall(null);
        }
    
        const handleClose = () => {
            setAnchorElAlgorithm(null);
            setAnchorElWall(null);
        };

    return (
            <div className={classes.root}>
                <AppBar position="static">
                    <Toolbar>
                        <IconButton className={classes.menuButton} edge="start" color="inherit" aria-label="menu">
                            <MenuIcon />
                        </IconButton>
                        <Typography className={classes.title} variant="h6">Path Finding Visualizer</Typography>
                        <List component="nav" aria-label="algorithm selector">
                            <ListItem
                                button
                                aria-aria-haspopup="true"
                                aria-controls="search-algorithm"
                                aria-label="Search Algorithm"
                                onClick={handleClickAlgorithmListItem}
                            >
                                <ListItemText primary="Search Algorithm" secondary={algorithmOptions[algorithmIndex]} classes={{ secondary: classes.listItemSecondaryText }} />
                            </ListItem>
                        </List>
                        <Menu
                            id="search-algorithm"
                            anchorEl={anchorElAlgorithm}
                            keepMounted
                            open={Boolean(anchorElAlgorithm)}
                            onClose={handleClose}
                        >
                            {algorithmOptions.map((option, index) => (
                                <MenuItem
                                    key={algorithmOptions}
                                    selected={index === algorithmIndex}
                                    onClick={(event) => handleAlgorithmItemClick(event, index)}
                                >
                                    {option}
                                </MenuItem>
                            ))}
                        </Menu>


                        <List component="nav" aria-label="add walls">
                            <ListItem
                                button
                                aria-aria-haspopup="true"
                                aria-controls="add-walls"
                                aria-label="Add Walls"
                                onClick={handleClickWallListItem}
                            >
                                <ListItemText primary="Wall Generation" secondary={wallOptions[wallIndex]} classes={{ secondary: classes.listItemSecondaryText }} />
                            </ListItem>
                        </List>
                        <Menu
                            id="add-walls"
                            anchorEl={anchorElWall}
                            keepMounted
                            open={Boolean(anchorElWall)}
                            onClose={handleClose}
                        >
                            {wallOptions.map((option, index) => (
                                <MenuItem
                                    key={wallOptions}
                                    selected={index === wallIndex}
                                    onClick={(event) => handleWallItemClick(event, index)}
                                >
                                    {option}
                                </MenuItem>
                            ))}
                        </Menu>
                        <Button variant="contained" color="secondary" className={classes.button} onClick={handleClick} disableElevation>Run Visualization</Button>
                        <Button variant="contained" color="secondary" className={classes.button} disableElevation>Reset</Button>
                    </Toolbar>
                </AppBar> 
      </div>
    )
}

export default NavBar

最后,我们需要处理GridContainer 中的ref。这样我们就可以通过ref成功暴露animateAlgorithm,这样父组件就可以直接调用了。

GridContainer.js

import React, {useState, useEffect, useContext, useImperativeHandle} from 'react'
import makeStyles from '@material-ui/core/styles/makeStyles'
import Button from '@material-ui/core/Button'
import Node from './Node'

import { OptionsContext } from '../Context/OptionsContext'

import './Grid.css'

const useStyles = makeStyles({
    root: {
        display: 'flex',
        justifyContent: 'center'
    },
    grid: props => ({
        display: 'grid',
        gridTemplateColumns: `repeat(${props.colums}, 1fr)`, 
        gridTemplateRows: `repeat(${props.rows}, 1fr)`,
        alignSelf: 'flex-start',
        width: props.colums * 20,
        height: props.rows * 20,
        justifyContent: 'center',
    }),
});

const GridContainer = React.forwardRef((props, ref) => {

    let classes = useStyles(props);
    const { rows, colums } = props;
    const { algorithmIndex, wallIndex } = useContext(OptionsContext);

    var visited = [];
    var path = [];
    const [Grid, setGrid] = useState([]);

    useEffect(() => {
        initGrid();
    }, []);

    var start = [4, rows / 2];
    var end = [colums - 5, colums / 2];

    const initGrid = () => {
        var grid = [];
        for (let row = 0; row < rows; row++) {
            grid.push([])
            for (let col = 0; col < colums; col++) {
                grid[row].push(createNode(row, col));
            }
        }
        setGrid([...grid]);
    }

    const createNode = (row, col) => {
        return {
            row,
            col,
            isStart: row === start[0] && col === start[1],
            isEnd: row === end[0] && col === end[1],
            isVisited: false,
            isWall: false,
            isPath: false,
            distance: Infinity,
            prevNode: null,
        };
     };

     const bfs = () => {

        var location = {
            row: start[0],
            col: start[1],
        };
    
        var grid = Grid;
        var queue = [];
        queue.push(location);
    
        while (queue.length) {
            var currentLocation = queue.shift();
            var row = currentLocation.row;
            var col = currentLocation.col;
            if (row === end[0] && col === end[1]) {
                setGrid(...[grid]);
                getPath(grid);
                return true;
            }
            if (grid[row][col].isVisited === false) {
                grid[row][col].isVisited = true;
                visited.push(grid[row][col]);
            }else {
                continue;
            }
            var neighbors = getNeighbors(grid, row, col);
            for (let neighbor of neighbors) {
                if (grid[neighbor.row][neighbor.col].isVisited !== true) {
                    queue.push(neighbor);
                    grid[neighbor.row][neighbor.col].prevNode = currentLocation;
                }
            }
        }

        return false;
    };

    const getNeighbors = (grid, row, col) => {
        let neighbors = [];
        if (validNode(grid, row, col - 1)) {
            neighbors.push({
                row: row,
                col: col - 1,
            });
        }
        if (validNode(grid, row, col + 1)) {
            neighbors.push({
                row: row,
                col: col + 1,
            });
        }
        if (validNode(grid, row - 1, col)) {
            neighbors.push({
                row: row - 1,
                col: col,
            });
        }
        if (validNode(grid, row + 1, col)) {
            neighbors.push({
                row: row + 1,
                col: col,
            });
        }
        return neighbors;
    };

    const validNode = (grid, row, col) => {
        var rowLength = grid.length;
        var colLength = grid[0].length;
        if (row < 0 || row >= rowLength || col < 0 || col >= colLength) {
            return false;
        }
        if (grid[row][col].isWall) {
            return false;
        }
        return true;
    };

    const getPath = (grid) => {
        var currentNodeCord = {
            row: end[0],
            col: end[1],
        }
        path.push(currentNodeCord);
        var curRow = currentNodeCord.row;
        var curCol = currentNodeCord.col;
        var prevNodeCord = grid[curRow][curCol].prevNode;
        while (prevNodeCord !== null) {
            currentNodeCord = prevNodeCord;
            path.push(currentNodeCord);
            curRow = currentNodeCord.row;
            curCol = currentNodeCord.col;
            var curNode = grid[curRow][curCol];
            prevNodeCord = curNode.prevNode;
        }
        path.reverse();
    };

    const animateAlgorithm = () => {
        
        bfs();
        console.log(`Visited length: ${visited.length}`)
        for (let i = 1; i <= visited.length; i++) {
            if (i === visited.length) {
                setTimeout(() => {
                    drawPath();
                }, 7 * i);
            } else {
                let nodeCord = visited[i];
                let row = nodeCord.row;
                let col = nodeCord.col;
                setTimeout(() => {
                    markVisited(row, col);
                }, 6 * i);  
            }
        }
    };

    useImperativeHandle(ref, () => ({
      animateAlgorithm
    });


    const drawPath = () => {
        for (let i = 1; i < path.length - 1; i++) {
            let nodeCord = path[i];
            let row = nodeCord.row;
            let col = nodeCord.col;
            setTimeout(() => {
                markPath(row, col);
            }, 40 * i);
        }
    };

    const markPath = (row, col) => {
        document.getElementById(`node-${row}-${col}`).className = 'node path';
    };

    const markVisited = (row, col) => {
        document.getElementById(`node-${row}-${col}`).className = 'node visited';
    };

    const addRandomWalls = (threshold) => {
        let grid = [...Grid];
        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < colums; col++) {
                if (!grid[row][col].isStart && !grid[row][col].isEnd) {
                    grid[row][col].isWall = (Math.floor(Math.random() * 10) > threshold);
                }
            }
        }
        setGrid(grid);
    };

    const resetGridColors = () => {
        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < colums; col++) {
                if (Grid[row][col].isStart !== true && Grid[row][col].isEnd !== true){
                    document.getElementById(`node-${row}-${col}`).className = 'node';
                }
            }
        }
    };

    const logStates = () => {
        console.log(Grid)
        console.log(visited)
        console.log(path)
    };

    const reset = () => {
        visited = [];
        path = [];
        resetGridColors();
        initGrid();
    };

    var GridMap = Grid.map((row, rowIndex) => {
        return (
            <div key={rowIndex}>
                {row.map((node, nodeIndex) => {
                    const { row, col, isStart, isEnd, isWall, isPath, isVisited } = node;
                    return (
                        <Node
                            key={`${row}${col}`}
                            width={20}
                            height={20}
                            row={row}
                            col={col}
                            isStart={isStart}
                            isEnd={isEnd}
                            isWall={isWall}
                            isPath={isPath}
                            isVisited={isVisited}
                        /> 
                    );
                })}
            </div>
        );
    })

    return (
        <>
            <div className={classes.root}>
                <div className={classes.grid}>
                    {GridMap}
                </div>
            </div>
            <Button variant="contained" color="primary" onClick={ () => animateAlgorithm() }>Animate Algorithm</Button>
            <Button variant="contained" color="primary" onClick={ () => addRandomWalls(6) }>Add Random Walls</Button>
            <Button variant="contained" color="primary" onClick={ () => reset() }>Reset</Button>
            <Button variant="contained" color="secondary" onClick={ () => logStates() }>Log Data</Button>   
        </>
    )
});

export default GridContainer

【讨论】:

  • 谢谢你。这个答案写得很好,我想我从中学到了很多。如果我想以同样的方式处理另一个按钮,比如说重置按钮,我是否只需要在 app.js 中创建另一个点击句柄函数,并将其传递给 NavBar?不知道如何实现不同的 useImperativeHandle 函数。
  • 这对于你想写的任何函数都是同样的过程...... useImperativeHandle 用于设置你将要在你的 ref 上公开的所有函数。
  • 所以是的...您需要在 app.js 中创建另一个句柄函数并将其传递给 NavBar...然后只需公开将其添加到 useImperativeHandle 内的对象的函数。
  • 知道了。再次感谢!
猜你喜欢
  • 2020-09-16
  • 2022-11-30
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 2017-12-09
  • 2017-07-13
  • 2018-06-15
相关资源
最近更新 更多