setState 不会更新 React 中的子级

Posted

技术标签:

【中文标题】setState 不会更新 React 中的子级【英文标题】:setState does not update children in React 【发布时间】:2022-01-16 15:13:05 【问题描述】:

我正在构建一个简单的应用程序,以直观地构建要使用不同搜索算法解决的迷宫。我有一个状态maze,其中包含每个块的状态数组,我将它传递给一个子Grid,它从数组中渲染迷宫。但是,每当我使用函数 updateMaze(y, x, newState) 更新状态时,maze 状态就会更新,但子 Grid 不会重新渲染。这是为什么呢?

App.js:

import './App.css';

import Block from './components/Block'
import Row from './components/Row'
import Grid from './components/Grid'
import MazeView from './components/MazeView';

import SideBarItem from './components/SideBarItem';

import New from './assets/new-page.png'
import Checkmark from './assets/checkmark.png'
import Maximize from './assets/maximize.png'
import Random from './assets/random.png'
import Square from './assets/square-measument.png'



import  useState, useEffect  from 'react'


const useForceUpdate = () => 
  const [count, setCount] = useState(0)
  
  const increment = () => setCount(prevCount => prevCount + 1)
  return [increment, count]



function App() 
  const [forceUpdate] = useForceUpdate()

  const [size, setSize] = useState(
    width: 15,
    height: 8
  )

  const defaultDimensions = 85

  const [dimensions, setDimensions] = useState(defaultDimensions)
  const [scale, setScale] = useState(1)
  const [MazeViewStyle, setMazeViewStyle] = useState(String())
  const [maze, setMaze] = useState([])

  const [globalW, globalH] = [window.innerWidth * 0.9 - 35, window.innerHeight * 0.85]

  const getAttrib = (columns, rows, defaultDimensions) => 
    let scale = defaultDimensions
    // If there are more columns than rows
    if (columns >= rows) 
      // Sets the scale to fill the height with rows
      scale = globalH / (rows * defaultDimensions)
      // Unless the columns do not fill the entire width of the screen
      if (columns * defaultDimensions * scale < globalW) 
        scale = globalW / (columns * defaultDimensions)
      
    
    // In the opposite scenario (rows more than columns)
    if (rows > columns) 
      // Sets the scale to fill the width with columns
      scale = globalW / (columns * defaultDimensions)
      // Unless the rows do not fill the height
      if (rows * defaultDimensions * scale < globalH) 
        scale = globalH / (rows * defaultDimensions)
      
    

    // Compute flags
    const flags = 
      centerWidth: columns * defaultDimensions < globalW,
      centerHeight: rows * defaultDimensions < globalH
    

    // Sets maximum result 1 and minimum 0
    if (scale >= 1) return  scale: 1, flags: flags 
    else if (scale <= 0.1) return  scale: 0.1, flags: centerWidth: false, centerHeight: false 
    else return scale: scale, flags:  centerWidth: false, centerHeight: false
   

  const getMazeViewAuxStyle = (flags) => 
    // Unpack a flag
    let [centerWidth, centerHeight] = [flags.centerWidth, flags.centerHeight]
    // If both flags are false return an empty string
    if (!centerWidth && !centerHeight)  return String() 

    // If the columns and rows are not sufficient
    if (dimensions * size.width < globalW && dimensions * size.height < globalH) return "small smallw smallh"

    // Otherwise find the necessary class names
    let style = "small"
    if (centerWidth) style = style + " smallw"
    if (centerHeight) style = style + " smallh"
    return style
  


  const populateArea = () => 
    // Fetch attributes of the current maze
    const fetchedAttrib = getAttrib(size.width, size.height, defaultDimensions)

    // Update the scale and dimensions
    setScale(fetchedAttrib.scale)
    setDimensions(defaultDimensions * fetchedAttrib.scale)

    // Update flags
    setMazeViewStyle(["maze-view", getMazeViewAuxStyle(fetchedAttrib.flags)].join(" "))

    // Initialize maze space
    initializeMazeSpace(size.height, size.width)
    populateRandom()
    // renderMaze()
  

  // Populates the maze in the right dimensions
  // only when a new maze is loaded
  useEffect(() => 
    populateArea()
  , [true])

  // Updates the dimensions based on scale
  useEffect (() => 
    setDimensions(defaultDimensions * scale)
  , [scale])

  const initializeMazeSpace = (rows, columns) => 
    let newMaze = maze
    for (let i = 0; i < rows; i++) 
      newMaze[i] = []
      for (let j  = 0; j < columns; j++) 
        newMaze[i][j] = "empty"
      
    
    setMaze(newMaze)
  

  const updateMaze = (i, j, blockState) => 
    if (maze.length === 0) 
      initializeMazeSpace(size.height, size.width)
    
    setMaze(() => 
      maze.map((row, a) => 
        i === a ? (row.map((block, b) => b === j ? blockState : block)) : row
      )
    )
  

  const populateRandom = (height = size.height, width = size.width) => 
    let newMaze = maze
    const classes = ["empty", "wall", "steel"]
    for (let i = 0; i < height; i++) 
      for (let j  = 0; j < width; j++) 
        let pick = classes[Math.floor(Math.random() * 3)]
        newMaze[i][j] = pick
      
    
    setMaze(newMaze)
  

  const renderMaze = () => 
    console.log(maze)
    let grid = []
    for (let i = 0; i < maze.length; i++) 
        let row = []
        for (let j = 0; j < size.width; j++) 
            row.push(<Block inheritedType=maze[i][j] dimensions=defaultDimensions * scale />)
        
        grid.push(<Row columns=row/>) 
    
    return grid
  

//   const renderMaze = () => 
//     let mazeComponents = maze.map((row) => 
//         return <Row columns=row.map(block =>  (<Block inheritedType=block dimensions=defaultDimensions * scale onAction=() => console.log("running") onDoubleClick=(e, p) => e.target.classList.remove(p)/>))/>
//     )
//     return mazeComponents
// 
  return (
    <>
      <div className='view'>
        <MazeView style=MazeViewStyle grid=<Grid rows=renderMaze() />/>
        <div className='sidebar'>
          <SideBarItem icon=New onClick=() => 
            updateMaze(0,0,"steel")
          />
          <SideBarItem icon=Square onClick=() => console.log(maze)/>
          <SideBarItem icon=Maximize onClick=() => setScale(0.5) />
          <SideBarItem icon=Random onClick=() => populateRandom()/>
          <SideBarItem icon=Checkmark />
        </div>
      </div>
    </>
  );


export default App

Grid.js:

import React from 'react'
import Row from './Row'
import Block from './Block'


const Grid = ( rows ) => 


    return (
        <div className='grid-view'>
           rows
        </div>
    )


export default Grid

注意:setScale 会触发重新渲染。

【问题讨论】:

【参考方案1】:

我认为你的 initializeMazeSpace 函数有问题,

let newMaze = maze

引用了同一个数组。因此,您的状态会发生变异,然后与自身进行比较,因此比较的结果不会改变,并且不会触发重新渲染操作。如果你想复制一个状态,试试这个

let newMaze = [...maze]

【讨论】:

当我这样做并相应地调整 setMaze 函数时,populateRandom 为 Maze 获取了一个空数组,这是为什么呢? initializeMazeSpace() 到目前为止工作正常 哦,对不起。也许问题出在 renderMaze 上,您应该为网格数组创建一个状态,然后将其作为道具下推到 Grid 组件。如果你只是推送一个函数,我认为它只会渲染 1 次 成功了。问题(?)原来是在 的 internalState 中我没有包含在我的 sn-p 中的文件中(没想到它会在那里) 好吧,Imma 出去大声笑:)

以上是关于setState 不会更新 React 中的子级的主要内容,如果未能解决你的问题,请参考以下文章

从父级中的子级访问元素标记名

仅展开父级中的子级 div

Quasar(vue)中嵌套在路由器中的子级中的父级触发方法

Spring data JPA:如何启用级联删除而不引用父级中的子级?

父级中的Java方法占位符在子级中使用[重复]

React useContext 不会将值传递给深层嵌套的子级