具体可先详见这篇文章dev.to/igor_bykov/…
1、<Com/>和Com()
//有react基础的同学都知道,<Com/>相当于调用了一下这个函数,展示出他的JSX结果
//所以以下二者的视图结果没有区别
import React from 'react'
function APP() {
const Test = ()=>{
return (
<div style={{width:200,height:200,backgroundColor:'yellow'}}>
Test
</div>
)
}
return (<>
<Test></Test>
{Test()}
</>);
}
export default APP;
为什么点不动?
但这两个的区别并非就是调用形式的不同
(以下面这个例子或者本文开始的文章中的这个例子codesandbox.io/s/crazy-dew…)为例
//代码如下例,二者呈现出了不同的表现形式,以<Com/>方式调用的组件没有正确更新
import { Button } from "antd";
import React, { useState } from "react";
const Son = (props: { onClick: () => void }) => {
const [val, setval] = useState(0);
return (
<>
<div>数据为:{val}</div>
<Button
onClick={() => {
props.onClick();
setval((i) => i + 1);
}}
>
➕
</Button>
</>
);
};
function APP() {
const [total, setTotal] = useState(0);
const onClick = () => {
setTotal((i) => i + 1);
};
const Test = (props?: { onClick?: () => void }) => {
return (
<div style={{ width: 100, height: 100, backgroundColor: "yellow" }}>
<Son onClick={props?.onClick || function () {}}></Son>
</div>
);
};
//<Com/>调用的方式没有正确更新
return (
<div style={{ margin: "auto", width: "500px" }}>
<span>total:{total}</span>
<Son onClick={onClick}></Son>
<Test onClick={onClick}></Test>
{Test({ onClick: onClick })}
</div>
);
}
export default APP;
原因
上面gif展示的不同其实很好解释
Test组件被我们定义在了另一个组件内,每次刷新后被重新执行,对于React来说并非同一个引用,也就会被认为是不同的组件,因而导致不断的重置,state一直保持为0,页面上也就展示不变
//把上述的Test组件改成这个,滚动<Com/>方式调用的组件,会发现每次点击任意按钮后它都被scroll到顶部
//因为组件被重置了,所以自动展现为初始状态,相当于每次都把组件的key设置了一个新值
const Test = (props?:{onClick?:()=>void})=>{
return (
<div style={{width:100,height:100,backgroundColor:'yellow',overflow:'scroll'}}>
<div style={{height:300}}>
<Son onClick={(props?.onClick || function(){})}></Son>
</div>
</div>
)
把Test的定义移到组件之外,就能发现一切正常了
2、是否做为组件节点
上述例子说明了两种调用方式并非完全一致,Test不能被定义在组件,因为会被重置。但Com(),也就是renderpros形式没有这种问题,为什么?
我们可以打开React Developer Tools去解决这个问题
可以看到我们只能看到<Com/>节点,而没有Com()这个节点,Com()方式的调用不会让React把他做为组件节点处理,他就纯是一个函数的执行,只是返回的是一段JSX,既然不作为节点,也就没有key不会被重置,自然能正常更新
所以区别1是 : <Com/>会被作为节点处理,而Com()不会,renderprops就只是一个纯粹的接受参数返回JSX的函数
3、状态
上面的例子似乎说明Com()比<Com/>调用更安全,但并非这样
//正如上文所说,renderprops不会被作为节点
//修改一下例子,把Son的逻辑移到Test中
function APP() {
const [total,setTotal] = useState(0)
const onClick = ()=>{
setTotal(i=>i+1)
}
const Test = (props:{onClick:()=>void})=>{
useEffect(()=>{
return ()=>
{
alert('renderprops卸载')
}
},[])
return (
(<>
<div style={{width:100,height:100,backgroundColor:'yellow',overflow:'scroll'}}>
</div>
</>)
)
}
return (
<>
<div style={{marginTop:30}}>total:{total}</div>
<Button onClick={onClick}>点击</Button>
{total<2?Test({onClick:onClick}):<div>none</div>}
</>
);
}
export default APP;
会发现当组件应该卸载掉时候确实有弹出,但不是alert,而是报错框
原因也很简单,因为renderprops不会被作为组件处理,因而也不像组件一样具备生命周期,所以不应在renderprops中维护state和生命周期等,尽管在许多正常情况下并不有问题
3、总结
1、组件不能在组件内定义的同时并且用<Com/>的方式调用
2、renderprops不会被作为组件处理,所以renderprops中不应维护自己的状态和生命周期,只应该做为一个纯粹的 function(props)=> UI