React 函数组件在任何变化时重新渲染

React function components re-renders when anything changes

这种 React 的东西让我抓狂,我不记得很久以前在我处理 React Native 时遇到过 class 组件的这些问题。一定是我在这里做错了什么,这是我遇到的问题之一,我有另一个组件也有类似的问题。我是新的hook系统,有些东西看了还是不是很清楚

我 运行 遇到了一些本地状态和 redux 状态的问题,这些问题导致我的对话框闪烁。每次父组件在该循环中设置日志状态时,对话框都会关闭并打开。 (所以在这种情况下,100 次取决于循环长度)每次更新状态时都会重新渲染。

图书馆react/redux/redux-thunk/material-ui

示例代码

function ToolbarModal(props){
 const [progressLog, setProgressLog] = useState("");

 const openProgressDialog = useSelector(getOpenProgressDialog);
 const handleCloseLongProgressDialog = () => {
   dispatch(handleOpenBatchRouteProgressDialog(false))
 }

 const handleSomeLogic = () => {
  for(let i=0; i<0; i++){
    //The real one is concated with prev message
   setProgressLog("SAMPLE MESSAGE")
  }
 }

 return (
  <div>
   <LongProgressDialog
     open={openProgressDialog}
     onClose={handleCloseLongProgressDialog}
     onEntered={handleSomeLogic}
     log={progressLog}
   />
  </div>
 )
}

日志属性通过 material-ui 的对话框组件传递给子组件,并仅显示在文本字段中。

function LongProgressDialog(props){
    const {open, onClose, onEntered, log} = props

    return (
        <Dialog
            open={open}
            onClose={onClose}
            onEntered={onEntered}
            maxWidth='sm'
            fullWidth
        >
            <DialogTitle>Long Progress</DialogTitle>
            <DialogContent>
                <TextField 
                 multiline
                 fullWidth
                 rows={10}
                 rowsMax={10}
                 variant='outlined'
                 disabled
                 value={log}
                 />
            </DialogContent>
        </Dialog>
    )
}

每次作为属性 传递的object 的引用发生变化时,FC(函数组件)将re-render。 因此,为防止这种情况,您传递的道具必须在每个 re-render.

上具有相同的引用

让我更详细地解释一下:

例如,这里:

const handleSomeLogic = () => {
//...

每次 FC re-render 正在创建一个新函数 (当您更新状态时会发生这种情况)。

你可以用useCallback来记住函数。

函数、objects(和数组)在您每次创建它们时都会更改它们的值,因此:

const function1 = () => {}
const function2 = () => {}
function1 === function2 // false

const emptyObj = {}
const emptyObj2 = {}
emptyObj === emptyObj2 // false

// ... etc

为了记住值,您应该使用 useMemo 挂钩。

因此,作为解决方案:用 useCallback 包装所有函数。 注意: 我可以看到您正在将进度值传递给 LongProgressDialog 组件,因此从技术上讲,每次更新进度时 LongProgressDialog 组件都会 re-render.

除此之外,您应该用 React.memo 包裹您的组件。 当 parent re-render 时,child 也会 re-render。 React.memo 阻止了这种情况,并且只会 re-render 当其道具之一已更改时的组件。

正在调查导致问题的道具

要找出您传递的是哪个道具导致 re-rendering 您可以比较组件中传递的道具的引用,在您的情况下是 LongProgressDialog 组件。

因此,例如:

// create variables outside of the FC in which we will store the reference
let prop1Ref;
let prop2Ref;

const LongProgressDialog = ({ prop1, prop2 }) => {

  // for each prop, use a effect which will be called when the prop's value/reference changes
  useEffect(() => {
    // compare the values by reference
    if (prop1Ref !== prop1) {
      // when prop ref has a value, the reference must have been changed:
      if(prop1Ref != null) {
        console.log("The reference of prop1 has changed. Memorize it properly");
      }
      prop1Ref = prop1;
    }
  }, [prop1])


}

您可以复制逻辑并将其应用于所有道具。 如果您不想手动检查它,可以使用 https://github.com/welldone-software/why-did-you-render

等便利库

我想你想用的是useRef:

import { useRef } from 'react'

const Component = props => {

  const logRef = useRef()
  const handleSomeLogic = () => {
  for(let i=0; i<100; i++){
    logRef.current = "SAMPLE MESSAGE"
  }
 }
}

然后,将 ref 传递给子组件:

 <LongProgressDialog
     open={openProgressDialog}
     onClose={handleCloseLongProgressDialog}
     onEntered={handleSomeLogic}
     log={logRef.current}
   />

在您的子组件中:

const LongProgressDialog = ({open, onClose, onEntered, log}) => {

  ...
  return (
      <p> Log value is: {log} </p>
     
   )
}

然而,

请注意,由于组件不会重新呈现,因此除非呈现组件,否则您不会在子组件中看到更新。因此,您必须根据您的情况决定是更新状态,还是更新 ref 然后重新渲染。您可以在此处查看 example codesandbox

顾鸣峰在评论里回复了他。 只需使用前向引用即可解决我的问题,从技术上讲,这是我一直在寻找但不知道它存在的东西。

如果有人需要它,只需按照 react 文档中 forward ref 的文档进行操作,它非常简单。