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 的文档进行操作,它非常简单。
这种 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 的文档进行操作,它非常简单。