首页 > 解决方案 > 如何通过在该上下文类中的方法中创建的 onClick 函数传递 React 上下文的状态?

问题描述

我有一些代码可以将一组 div 元素放置在 CSS 网格中,该网格通过 React 渲染,因为在第一次渲染后速度非常快。我希望能够通过多个组件与这些元素进行交互,因此我设置了一些存储网格状态的上下文。我现在遇到了一个问题,我想在这些网格元素上设置一个 onClick 函数,以便在单击它们时更新上下文的状态。在这种情况下,我试图通过数组跟踪选定的元素。但是,我似乎无法在 onClick 函数中引用 selectedTiles。它要么无法编译,要么在运行时崩溃,要么什么都不做。

需要明确的是,如果我删除与 selectedTiles 相关的代码行,它可以正常工作并切换类。我还在构造函数中绑定了该函数。

这是我现在在三个相关文件中的代码。

索引.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import UserInterface from './components/userinterface';
import DisplayGrid from './components/displaygrid';
import {GridContextProvider} from './gridcontext';

ReactDOM.render(
  <React.StrictMode>
    <GridContextProvider>
      <UserInterface />
      <div className="grid-wrapper">
        <DisplayGrid />
      </div>
    </GridContextProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

serviceWorker.unregister();

displaygrid.js

import React from 'react';
import {GridContext} from '../gridcontext'

class DisplayGrid extends React.Component{
    static contextType = GridContext;
    render(){
        const gridData = this.context;
        return (
            <div>
                {gridData.name}<br />
                <div className= "grid-container" style={{gridTemplateColumns: gridData.columns}}>
                    {gridData.tiles}
                </div>
            </div>
        );
    }
}
export default DisplayGrid;

gridcontext.js

import React from "react";

const GridContext = React.createContext();

class GridContextProvider extends React.Component {
    constructor(props) {
        super(props);
        const topGridData = JSON.parse(localStorage.getItem('grids'))[0];
        this.state = this.makeGrid(topGridData.dims[0], topGridData.dims[1], topGridData.dims[2],
            topGridData.visible, topGridData.hex);
        this.selectTile = this.selectTile.bind(this);
        this.makeGrid = this.makeGrid.bind(this);
    }
    selectTile(event) {
        let tilesArray = this.state.selectedTiles;
        if(event.currentTarget.className === "grid-tile"){
            event.currentTarget.className = "grid-tileb";
            tilesArray.push(event.currentTarget.key);
        } else {
            event.currentTarget.className = "grid-tile";
            tilesArray.splice(tilesArray.indexOf(event.currentTarget.key),tilesArray.indexOf(event.currentTarget.key));
        }
    }
    makeGrid(x, y, tilesize, visible, hex){
        let columnStr = "";
        let tileArray = [];
        const widthStr = tilesize.toString() + "px"
        for (let i = 0; i < y; i++) {
            for (let j = 0; j < x; j++) {
                if(i===0) columnStr = columnStr + "auto ";//x loops over columns so this runs once for all columns.
                let div = (
                    <div 
                        key={"x" + j.toString() + "y" + i.toString()}//for example at coordinates 5,6 id is x5y6.  starts at 0.
                        className="grid-tile"
                        style={{
                            width: widthStr,
                            height: widthStr,
                            border: "1px solid rgba(0, 0, 0," + (visible ? "0.6)" : "0.0)")
                        }}
                        onClick={this.selectTile}
                    >
                    </div>
                )
                tileArray.push(div);
            }
        }
        const gridsDataArray = JSON.parse(localStorage.getItem('grids'));
        const index = JSON.parse(localStorage.getItem('currentGrid'));
        return {
            columns: columnStr,
            tiles: tileArray,
            selectedTiles: [],
            name: gridsDataArray[index].name,
            bgurl: gridsDataArray[index].bgurl
        };
    }
    render() {
        return (
            <GridContext.Provider value={{
                columns: this.state.columns,
                tiles: this.state.tiles,
                selectedTiles: this.state.selectedTiles,
                name: this.state.name,
                bgurl: this.state.bgurl,
                setNameBgurl: (newName, newBgurl) => {
                    this.setState({name : newName,
                    bgurl : newBgurl});
                },
                setGrid: (x, y, tilesize, visible, hex) => {
                    this.setState(this.makeGrid(x, y, tilesize, visible, hex));
                }
            }}>
                {this.props.children}
            </GridContext.Provider>
        );
    }
}

export { GridContext, GridContextProvider };

当我单击一个磁贴,指向 gridcontext 的第 15 行时,此代码在运行时给我一个“TypeError:这是未定义的”崩溃,let tilesArray = this.state.selectedTiles;这很奇怪,因为该方法已绑定。

欢迎其他反馈,因为我是 React 新手,总体上对 javascript 并没有好多少;我不认为我已经完全了解 javascript 如何使用“this”。我一直在尝试查找教程,但似乎有很多不同的方法可以使用 React,所以很难判断哪些做法是标准的。

标签: javascriptreactjs

解决方案


我弄清楚为什么会发生错误。我在绑定之前在构造函数中使用makeGrid,它引用selectTile,并且“this”的变量指针以某种方式设置为未定义的陈旧变量指针。将绑定移动到顶部可以修复该错误。

修复该错误后,立即找到第二个错误。 TypeError: tilesArray is undefined 这是因为我在状态完全形成之前引用了 selectTile,它再次留下了一个过时的变量指针。

构造函数现在必须如下所示:

    constructor(props) {
        super(props);
        const topGridData = JSON.parse(localStorage.getItem('grids'))[0];
        this.selectTile = this.selectTile.bind(this);
        this.makeGrid = this.makeGrid.bind(this);
        this.state = {selectedTiles: []}
        this.state = {
            selectedTiles: this.state.selectedTiles,
            ...this.makeGrid(topGridData.dims[0], topGridData.dims[1], topGridData.dims[2],
            topGridData.visible, topGridData.hex)
        }
    }

我立即遇到了第三个问题,即尽管尝试重用变量,但初始的空数组仍被保留为陈旧的变量指针。我将 onClick 更改onClick={(event) => this.selectTile(this.state.selectedTiles, event)}为将状态作为参数发送,并且它可以工作(尽管我必须更改 selectTile 以使用临时数组,因为我无法通过数组突变直接更改状态)。我尝试使用onClick={this.selectTile.bind(this, this.state.selectedTiles)},结果发现与onClick={this.selectTile}.

事实证明,“key”不是存储在 div 元素中的属性,React 将其拉出并在内部存储以处理列表。我最终将每个元素的 id 属性设置为相同的字符串,并在 selectTile 中引用该属性来修改数组。


推荐阅读