无法对未安装的子组件执行 React 状态更新?
Can't perform a React State update on unMounted child component?
收到此警告:
Can't perform a React state update on unmounted component. This is a no-op...
它是由 child component
引起的,我不知道如何让它消失。
请注意,我已经阅读了很多关于为什么会发生这种情况的其他帖子,并且了解基本问题。但是,大多数解决方案建议在 componentWillUnmount
样式函数中取消订阅(我正在使用 react hooks
)
我不知道这是否指向我对 React 的一些更大的基本误解,
但我基本上是这样的:
import React, { useEffect, useRef } from 'react';
import Picker from 'emoji-picker-react';
const MyTextarea = (props) => {
const onClick = (event, emojiObject) => {
//do stuff...
}
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
});
useEffect(() => {
return () => {
console.log('will unmount');
isMountedRef.current = false;
}
});
return (
<div>
<textarea></textarea>
{ isMountedRef.current ? (
<Picker onEmojiClick={onClick}/>
):null
}
</div>
);
};
export default MyTextarea;
(tl;dr) 请注意:
MyTextarea
组件有一个 parent 组件只在某个路由上渲染。
还有一个 Menu
组件,点击后会改变 route
和 视情况而定 要么显示 MyTextarea
的 parent 组件或显示另一个组件。
一旦我单击 Menu
关闭 MyTextarea
的 parent 组件,就会出现此警告。
更多上下文
Whosebug 上的其他答案建议进行更改以防止 state
在组件未安装时进行更新。在我的情况下,我不能这样做,因为我没有设计 Picker
组件(由 MyTextarea
呈现)。 警告源自 这条 <Picker onEmojiClick={onClick}>
行,但我不想修改这个现成的组件。
这就是我尝试根据 isMountedRef
渲染组件或不渲染组件的原因。但是,这也不起作用。发生的情况是,如果我设置 useRef(true)
,组件将被渲染,或者如果我设置 useRef(null)
,它根本不会被渲染,正如许多人所建议的那样。
我不太确定你的问题到底是什么(是你无法摆脱警告还是 <Picker>
总是呈现或从不呈现),但我会尝试解决我看到的所有问题。
首先,您不需要根据是否安装 MyTextArea
来有条件地渲染 <Picker>
。由于组件仅在挂载后呈现,因此 <Picker>
如果其所在的组件尚未挂载则永远不会呈现。
话虽如此,如果您仍想跟踪组件何时安装,我建议不要使用钩子,并使用 componentDidMount
和 componentWillUnmount
以及 setState()
反而。这不仅会让您更容易理解您的组件的生命周期,而且您使用挂钩的方式也存在一些问题。
现在,您的 useRef(true)
会在组件初始化时将 isMountedRef.current
设置为 true
,因此即使在安装之前也是如此。 useRef()
与 componentDidMount()
不同。
在安装组件时使用'useEffect()'将isMountedRef.current
切换为true也不起作用。虽然它会在安装组件时触发,但 useEffect()
用于副作用,而不是状态更新,因此它不会触发重新渲染,这就是为什么当您设置 useRef(null)
时组件从不渲染的原因.
此外,您的 useEffect()
挂钩将在您的组件每次更新时触发,而不仅仅是在它安装时触发,并且您的清理函数(正在返回的函数)也会在每次更新时触发,而不仅仅是在它加载时触发卸载。因此,在每次更新时,isMountedRef.current
都会从 true 切换到 false,然后再切换回 true。但是,none 这很重要,因为无论如何组件都不会重新渲染(如上所述)。
如果你真的需要使用 useEffect()
那么你应该将它组合成一个函数并用它来更新状态,以便它触发重新渲染:
const [isMounted, setIsMounted] = useState(false); // Create state variables
useEffect(() => {
setIsMounted(true); // The effect and clean up are in one function
return () => {
console.log('will unmount');
setIsMounted(false);
}
}, [] // This prevents firing on every update, w/o it you'll get an infinite loop
);
最后,根据您共享的代码,您的组件不会导致警告,因为您的代码中的任何地方都没有状态更新。您应该检查选择器的回购协议是否有问题。
编辑:警告似乎是由您的 Picker 包引起的,并且已经存在问题 https://github.com/ealush/emoji-picker-react/issues/142
收到此警告:
Can't perform a React state update on unmounted component. This is a no-op...
它是由 child component
引起的,我不知道如何让它消失。
请注意,我已经阅读了很多关于为什么会发生这种情况的其他帖子,并且了解基本问题。但是,大多数解决方案建议在 componentWillUnmount
样式函数中取消订阅(我正在使用 react hooks
)
我不知道这是否指向我对 React 的一些更大的基本误解,
但我基本上是这样的:
import React, { useEffect, useRef } from 'react';
import Picker from 'emoji-picker-react';
const MyTextarea = (props) => {
const onClick = (event, emojiObject) => {
//do stuff...
}
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
});
useEffect(() => {
return () => {
console.log('will unmount');
isMountedRef.current = false;
}
});
return (
<div>
<textarea></textarea>
{ isMountedRef.current ? (
<Picker onEmojiClick={onClick}/>
):null
}
</div>
);
};
export default MyTextarea;
(tl;dr) 请注意:
MyTextarea
组件有一个 parent 组件只在某个路由上渲染。还有一个
Menu
组件,点击后会改变route
和 视情况而定 要么显示MyTextarea
的 parent 组件或显示另一个组件。一旦我单击
Menu
关闭MyTextarea
的 parent 组件,就会出现此警告。
更多上下文
Whosebug 上的其他答案建议进行更改以防止
state
在组件未安装时进行更新。在我的情况下,我不能这样做,因为我没有设计Picker
组件(由MyTextarea
呈现)。 警告源自 这条<Picker onEmojiClick={onClick}>
行,但我不想修改这个现成的组件。这就是我尝试根据
isMountedRef
渲染组件或不渲染组件的原因。但是,这也不起作用。发生的情况是,如果我设置useRef(true)
,组件将被渲染,或者如果我设置useRef(null)
,它根本不会被渲染,正如许多人所建议的那样。
我不太确定你的问题到底是什么(是你无法摆脱警告还是 <Picker>
总是呈现或从不呈现),但我会尝试解决我看到的所有问题。
首先,您不需要根据是否安装 MyTextArea
来有条件地渲染 <Picker>
。由于组件仅在挂载后呈现,因此 <Picker>
如果其所在的组件尚未挂载则永远不会呈现。
话虽如此,如果您仍想跟踪组件何时安装,我建议不要使用钩子,并使用 componentDidMount
和 componentWillUnmount
以及 setState()
反而。这不仅会让您更容易理解您的组件的生命周期,而且您使用挂钩的方式也存在一些问题。
现在,您的 useRef(true)
会在组件初始化时将 isMountedRef.current
设置为 true
,因此即使在安装之前也是如此。 useRef()
与 componentDidMount()
不同。
在安装组件时使用'useEffect()'将isMountedRef.current
切换为true也不起作用。虽然它会在安装组件时触发,但 useEffect()
用于副作用,而不是状态更新,因此它不会触发重新渲染,这就是为什么当您设置 useRef(null)
时组件从不渲染的原因.
此外,您的 useEffect()
挂钩将在您的组件每次更新时触发,而不仅仅是在它安装时触发,并且您的清理函数(正在返回的函数)也会在每次更新时触发,而不仅仅是在它加载时触发卸载。因此,在每次更新时,isMountedRef.current
都会从 true 切换到 false,然后再切换回 true。但是,none 这很重要,因为无论如何组件都不会重新渲染(如上所述)。
如果你真的需要使用 useEffect()
那么你应该将它组合成一个函数并用它来更新状态,以便它触发重新渲染:
const [isMounted, setIsMounted] = useState(false); // Create state variables
useEffect(() => {
setIsMounted(true); // The effect and clean up are in one function
return () => {
console.log('will unmount');
setIsMounted(false);
}
}, [] // This prevents firing on every update, w/o it you'll get an infinite loop
);
最后,根据您共享的代码,您的组件不会导致警告,因为您的代码中的任何地方都没有状态更新。您应该检查选择器的回购协议是否有问题。
编辑:警告似乎是由您的 Picker 包引起的,并且已经存在问题 https://github.com/ealush/emoji-picker-react/issues/142