无意间踩到这个大坑,救命阿!
而且这看起来是很正常很优雅的操作啊!为什么啊!
内联组件?什么东西?
如果你没听说这个概念,不用惊讶,因为是我刚编的名词
好吧,那你指的是什么?
我指的是:
JSX.Element
对象的函数打个比方,以下这个就是一个内联的组件:
interface IChildCompProps {
prop1: string,
prop2: string,
}
export default function OuterComponent() {
const [localDependency, _] = useState<number>(0);
function TheInlineComponent(props: IChildCompProps) {
switch (localDependency) {
case 0:
return <ChildComponent1 {...props} />;
case 1:
return <ChildComponent2 {...props} />;
default:
return <ChildComponent3 {...props} />;
}
}
return (
<div>
<TheInlineComponent prop1="1" prop2="2"/>
</div>
)
}
这看似是一种非常优雅的方式进行了条件渲染,但实事上这种做法极其危险
因为每次重新渲染OuterComponent
,都会重新渲染整个TheInlineComponent
。
没错,无论TheInlineComponent
内部有没有改变,都不会触发差异渲染,而是整个重新渲染。
为什么?
原因很简单。因为这是局部函数。
OuterComponent
重新渲染时,React会再次调用OuterComponent()
函数,因此TheInlineComponent
也会被重新定义。
也就是TheInlineComponent
已经不是上一个环境中的TheInlineComponent
了!
所以React差异渲染引擎会视TheInlineComponent
为新的元素,这也是为什么即使TheInlineComponent
没有改变,它还是会被无条件重新渲染。
如果你打开浏览器 DevTools,你可以发现每次
OuterComponent
重新渲染,整个TheInlineComponent
都会被替换
整个组件替换,意味着这个React组件的生命周期也再次被刷新,因此它会重新挂载(mount),这对元素内部某些React hook(比如本只应该执行一次的useEffect
)的行为会造成根本性的影响。
相信我,非常的恐怖!我因为父子组件内存在一些相互作用的state
,造成了组件究极死循环重渲染,一秒渲染1000次,还好及时发现,否则CPU该冒烟了
好吧,如果真是如此,又该如何是好?
条件渲染是非常常见的操作。而且对于比较复杂的选择逻辑而言,也不好全部用表达式写在return
中。
因此有必要解决这个问题。
如果你的选择逻辑实事上并没有依赖于组件本身,那可以将它移到最外层,成为一个独立的组件。由于不再处于OuterComponent
内,因此不会被重复定义。
但是很多情况下,使用内联已经意味着它和组件环境有一定关联了,比如我们的例子中,它依赖了OuterComponent
的localDependency
状态,因此我们没法把它移出去,又该如何是好?
其实很简单,想要解决这个问题,只需要打破“内联组件”的最后一个条件:
export default function OuterComponent() {
// ...
return (
<div>
{TheInlineComponent({prop1: "1", prop2: "2", })}
</div>
)
}
没错,使用JSX函数调用的形式使用它,就不会出问题。
这是因为,React只会管理JSX元素(
)的生命周期,而对于函数调用,是不归React管的
说人话就是:
使用JSX实例化JSX.Element
,会被React管理生命周期:
<TheInlineComponent prop1="1" prop2="2"/>
使用普通函数调用生成JSX.Element
,React不会管:
{TheInlineComponent({prop1: "1", prop2: "2", })}
笼统解释一下:
,这就是导致上述问题的原因
,由于这个东西本身是非内联组件,因此不会存在任何重复定义的问题。TheInlineComponent
有没有被视为一个JSX元素所以,注意咯,千万不要因为好康就把本地函数写成内联的组件调用了,很恐怖的哟